/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import androidx.arch.core.util.Function

/**
 * StateUpdater lets a [Component] rendered with a scoped [ComponentContext] interact with Litho's
 * state. An implementation of StateUpdater is responsible for collecting state update operations
 * and schedule a new Resolve/Layout step to occur. The default implementation of StateUpdater is
 * [ComponentTree], but it might be useful to implement this interface when integrating Litho in
 * different rendering frameworks where it's not desirable for Litho to control the
 * resolve/layout/commit process.
 */
interface StateUpdater {

  /** @return whether this tree has never been mounted before */
  var isFirstMount: Boolean

  /**
   * Enqueues a state update that will schedule a new render on the calling thread at the end of its
   * current run-loop. It is expected that the calling thread has an active Looper.
   */
  fun updateStateSync(
      globalKey: String,
      stateUpdate: StateContainer.StateUpdate,
      attribution: String?,
      isCreateLayoutInProgress: Boolean,
      isNestedTreeContext: Boolean
  )

  /**
   * Enqueues a state update that will schedule a new render on a Thread controlled by the Litho
   * infrastructure.
   */
  fun updateStateAsync(
      globalKey: String,
      stateUpdate: StateContainer.StateUpdate,
      attribution: String?,
      isCreateLayoutInProgress: Boolean,
      isNestedTreeContext: Boolean
  )

  /**
   * Enqueues a state update that will not schedule a new render. The new state will immediately be
   * visible in Event Handlers and it will be visible in the next render phase.
   */
  fun updateStateLazy(
      globalKey: String,
      stateUpdate: StateContainer.StateUpdate,
      isNestedTreeContext: Boolean
  )

  /** Same as updateStateAsync but for Hook State. */
  fun updateHookStateAsync(
      globalKey: String,
      updateBlock: HookUpdater,
      attribution: String?,
      isCreateLayoutInProgress: Boolean,
      isNestedTreeContext: Boolean
  )

  /** Same as updateStateSync but for Hook State. */
  fun updateHookStateSync(
      globalKey: String,
      updateBlock: HookUpdater,
      attribution: String?,
      isCreateLayoutInProgress: Boolean,
      isNestedTreeContext: Boolean
  )

  fun applyLazyStateUpdatesForContainer(
      globalKey: String,
      container: StateContainer,
      isNestedTreeContext: Boolean
  ): StateContainer?

  /** Returns a Cached value that is accessible across all re-render operations. */
  fun getCachedValue(cachedValueInputs: Any, isNestedTreeContext: Boolean): Any?

  /** Stores a Cached value that will be accessible across all re-render operations. */
  fun putCachedValue(cachedValueInputs: Any, cachedValue: Any?, isNestedTreeContext: Boolean)

  /**
   * Removes a state update that was previously enqueued if the state update has not been processed
   * yet.
   */
  fun removePendingStateUpdate(key: String, isNestedTreeContext: Boolean)

  fun <T> canSkipStateUpdate(
      globalKey: String,
      hookStateIndex: Int,
      newValue: T?,
      isNestedTreeContext: Boolean
  ): Boolean

  fun <T> canSkipStateUpdate(
      newValueFunction: Function<T, T>,
      globalKey: String,
      hookStateIndex: Int,
      isNestedTreeContext: Boolean
  ): Boolean

  fun getEventTrigger(key: String): EventTrigger<*>?

  fun getEventTrigger(handle: Handle, id: Int): EventTrigger<*>?
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

/**
 * Used to represent the value of a Component's State. Must be used to pass State as parameter in
 * methods annotated with @OnUpdateState.
 */
class StateValue<T> : Output<T>()


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.graphics.Rect
import android.widget.Checkable
import androidx.annotation.VisibleForTesting
import com.facebook.proguard.annotations.DoNotStrip
import java.lang.UnsupportedOperationException

/** Holds information about a [TestOutput]. */
@DoNotStrip
class TestItem {

  private val _bounds = Rect()

  @get:VisibleForTesting @get:DoNotStrip var testKey: String? = null
  @get:VisibleForTesting @get:DoNotStrip var host: ComponentHost? = null
  var content: Any? = null

  /** Unique key to identify if this test-item was reused */
  @get:DoNotStrip val acquireKey: AcquireKey = AcquireKey()

  @get:VisibleForTesting
  @get:DoNotStrip
  var bounds: Rect
    get() = _bounds
    set(bounds) {
      _bounds.set(bounds)
    }

  fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
    _bounds[left, top, right] = bottom
  }

  @get:VisibleForTesting
  @get:DoNotStrip
  val textContent: String
    get() {
      return textItems.joinToString()
    }

  val textItems: List<CharSequence>
    get() = ComponentHostUtils.extractTextContent(listOf(content)).flatMap { it.textList }

  val isChecked: Boolean
    get() =
        (content as? Checkable)?.isChecked
            ?: throw UnsupportedOperationException(
                "This Litho component can't be checked, we can't determine its check state.")

  @DoNotStrip class AcquireKey
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.graphics.Rect
import androidx.annotation.VisibleForTesting

/**
 * Stores information about a [Component] which is only available when tests are run. TestOutputs
 * are calculated in [LayoutState] and transformed into [TestItem]s while mounting.
 */
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
class TestOutput {
  var testKey: String? = null
  var hostMarker: Long = -1
  var layoutOutputId: Long = -1
  private val _bounds = Rect()
  var bounds: Rect
    get() = _bounds
    set(bounds) {
      _bounds.set(bounds)
    }

  fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
    _bounds.set(left, top, right, bottom)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.graphics.Typeface
import com.facebook.proguard.annotations.DoNotStrip

/**
 * An UI Element that contains textual content. This element can have more than one textual content.
 * For example, one [Mountable] wrapping a complex custom view can have multiple text views, and
 * therefore, multiple [TextContent.Item]
 *
 * It is responsibility of who implements the [Mountable] to implement this interface and provide
 * the correct properties.
 */
@DoNotStrip
interface TextContent {

  /** @return the list of text items that are rendered by this UI element. */
  @get:DoNotStrip val items: List<Item>

  /**
   * This is a helper method to retrieve the [CharSequence] that make up the textual content in this
   * given [TextContent]
   */
  val textList: List<CharSequence>

  interface Item {

    val text: CharSequence

    val textSize: Float

    val fontLineHeight: Float

    val typeface: Typeface

    val color: Int

    val linesCount: Int
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.util.Log
import com.facebook.litho.config.ComponentsConfiguration
import java.lang.StringBuilder

/**
 * ThreadTracingRunnable tries to help debugging crashes happening across threads showing the
 * stacktrace of the crash origin which scheduled this runnable.
 *
 * A stacktrace, together with the origin thread name, is going to be "saved" at the time this class
 * is instantiated and appended as "cause" to whatever is thrown from the [run()] method.
 *
 * To use this class, just extends it instead of implementing [Runnable]. Then just implement
 * [tracedRun()].
 *
 * If the runnable is created ahead of time and used somewhere else later, a new, more relevant,
 * stacktrace can be created calling [resetTrace()].
 */
abstract class ThreadTracingRunnable : Runnable {

  private val isTracingEnabled = ComponentsConfiguration.enableThreadTracingStacktrace

  // This Throwable is saving the call stack which created this Runnable. If null, we are not
  // tracing in this instance.
  private var tracingThrowable: Throwable? =
      if (isTracingEnabled) {
        val thread = Thread.currentThread()
        Throwable(
            StringBuilder(MESSAGE_PART_1)
                .append(thread.id)
                .append(MESSAGE_PART_2)
                .append(thread.name)
                .toString())
      } else {
        null
      }

  /** Implement here your [Runnable#run()] code. */
  abstract fun tracedRun()

  /**
   * Reset the stacktrace of this Runnable to this point. To be called right before the runnable is
   * scheduled to another thread, in case it was instantiated ahead of time with a different code
   * flow.
   */
  fun resetTrace() {
    tracingThrowable?.fillInStackTrace()
  }

  override fun run() {
    try {
      tracedRun()
    } catch (t: Throwable) {
      if (tracingThrowable != null) {
        Log.w("LithoThreadTracing", "--- start debug trace")
        Log.w("LithoThreadTracing", "Thread tracing stacktrace", tracingThrowable)
        Log.w("LithoThreadTracing", "--- end debug trace")
      }
      throw t
    }
  }

  companion object {
    private const val MESSAGE_PART_1 = "Runnable instantiated on thread id: "
    private const val MESSAGE_PART_2 = ", name: "
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

/** Specify a position to anchor a [DeprecatedLithoTooltip] on a component. */
enum class TooltipPosition {
  CENTER,
  BOTTOM_LEFT,
  CENTER_LEFT,
  TOP_LEFT,
  CENTER_TOP,
  TOP_RIGHT,
  CENTER_RIGHT,
  BOTTOM_RIGHT,
  CENTER_BOTTOM
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.view.MotionEvent
import android.view.View

/** Any interface for mounted items that need to capture motion events from its [ComponentHost]. */
interface Touchable {

  fun onTouchEvent(event: MotionEvent, host: View): Boolean

  fun shouldHandleTouchEvent(event: MotionEvent): Boolean
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import com.facebook.infer.annotation.ThreadConfined
import com.facebook.infer.annotation.ThreadSafe
import com.facebook.litho.annotations.TreeProp
import java.util.Collections
import java.util.HashMap
import java.util.Objects

/**
 * A data structure to store tree props.
 *
 * @see TreeProp
 */
@ThreadConfined(ThreadConfined.ANY)
class TreeProps {

  private val map = Collections.synchronizedMap(HashMap<Class<*>, Any?>())

  fun put(key: Class<*>, value: Any?) {
    map[key] = value
  }

  operator fun <T> get(key: Class<T>): T? = map[key] as T?

  operator fun set(key: Class<*>, value: Any?) = put(key, value)

  fun reset() {
    map.clear()
  }

  override fun equals(o: Any?): Boolean {
    if (this === o) {
      return true
    }

    if (o !is TreeProps) return false

    return map == o.map
  }

  override fun hashCode(): Int = Objects.hash(map)

  /**
   * This returns a copy of the content stored by the [TreeProps] in the raw format to which it is
   * stored.
   *
   * This is only meant to be used by the internal APIs.
   */
  @JvmName("asRawMap") internal fun asRawMap(): Map<Class<*>, Any?> = map.toMap()

  companion object {
    /** @return a copy of the provided TreeProps instance; returns null if source is null */
    @JvmStatic
    @ThreadSafe(enableChecks = false)
    fun copy(source: TreeProps?): TreeProps? =
        if (source == null) {
          null
        } else {
          acquire(source)
        }

    /**
     * Whenever a Spec sets tree props, the TreeProps map from the parent is copied. If parent
     * TreeProps are null, a new TreeProps instance is created to copy the current tree props.
     *
     * Infer knows that newProps is owned but doesn't know that newProps.mMap is owned.
     */
    @JvmStatic
    @ThreadSafe(enableChecks = false)
    fun acquire(source: TreeProps?): TreeProps {
      val newProps = TreeProps()
      if (source != null) {
        synchronized(source.map) { newProps.map.putAll(source.map) }
      }
      return newProps
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.content.res.TypedArray
import android.util.TypedValue

internal object TypedArrayUtils {
  private val tmpTypedValue = TypedValue()

  @JvmStatic
  fun isColorAttribute(a: TypedArray, idx: Int): Boolean {
    synchronized(tmpTypedValue) {
      a.getValue(idx, tmpTypedValue)
      return tmpTypedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
          tmpTypedValue.type <= TypedValue.TYPE_LAST_COLOR_INT
    }
  }
}


// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.animation.StateListAnimator
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.SparseArray
import android.view.View
import android.view.ViewOutlineProvider
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import com.facebook.litho.drawable.DrawableUtils
import com.facebook.rendercore.primitives.utils.equals
import com.facebook.rendercore.primitives.utils.isEquivalentTo
import com.facebook.yoga.YogaDirection

class ViewAttributes {

  var isHostSpec: Boolean = false
  var componentName: String = ""
  var importantForAccessibility: Int = 0
  var disableDrawableOutputs: Boolean = false

  var contentDescription: CharSequence? = null
  var viewId: Int = View.NO_ID
    set(value) {
      field = value
      flags = flags or FLAG_VIEW_ID
    }

  var viewTag: Any? = null
    set(value) {
      field = value
      flags = flags or FLAG_VIEW_TAG
    }

  var transitionName: String? = null
  var viewTags: SparseArray<Any>? = null
  var outlineProvider: ViewOutlineProvider? = null
  var clickHandler: EventHandler<ClickEvent>? = null
  var longClickHandler: EventHandler<LongClickEvent>? = null
  var focusChangeHandler: EventHandler<FocusChangedEvent>? = null
  var touchHandler: EventHandler<TouchEvent>? = null
  var interceptTouchHandler: EventHandler<InterceptTouchEvent>? = null

  var background: Drawable? = null
  var foreground: Drawable? = null
  var padding: Rect? = null
  var layoutDirection: YogaDirection = YogaDirection.INHERIT
  var stateListAnimator: StateListAnimator? = null
  @DrawableRes var stateListAnimatorRes: Int = 0
  @LayerType var layerType: Int = LayerType.LAYER_TYPE_NOT_SET
  var layoutPaint: Paint? = null

  internal var flags: Int = 0
    private set

  var scale: Float = 1f
    set(value) {
      field = value
      flags =
          if (value == 1f) {
            flags and FLAG_SCALE.inv()
          } else {
            flags or FLAG_SCALE
          }
    }

  var alpha: Float = 1f
    set(value) {
      field = value
      flags =
          if (value == 1f) {
            flags and FLAG_ALPHA.inv()
          } else {
            flags or FLAG_ALPHA
          }
    }

  var rotation: Float = 0f
    set(value) {
      field = value
      flags =
          if (value == 0f) {
            flags and FLAG_ROTATION.inv()
          } else {
            flags or FLAG_ROTATION
          }
    }

  var rotationX: Float = 0f
    set(value) {
      field = value
      flags = flags or FLAG_ROTATION_X
    }

  var rotationY: Float = 0f
    set(value) {
      field = value
      flags = flags or FLAG_ROTATION_Y
    }

  var clipChildren: Boolean = true // Default value for ViewGroup
    set(value) {
      field = value
      flags = flags or FLAG_CLIP_CHILDREN
    }

  var clipToOutline: Boolean = false
    set(value) {
      field = value
      flags = flags or FLAG_CLIP_TO_OUTLINE
    }

  var isFocusable: Boolean = false
    set(value) {
      field = value
      flags = flags or FLAG_FOCUS
    }

  var isClickable: Boolean = false
    set(value) {
      field = value
      flags = flags or FLAG_CLICKABLE
    }

  var isEnabled: Boolean = false
    set(value) {
      field = value
      flags = flags or FLAG_ENABLED
    }

  var isSelected: Boolean = false
    set(value) {
      field = value
      flags = flags or FLAG_SELECTED
    }

  var shadowElevation: Float = 0f
    set(value) {
      field = value
      flags = flags or FLAG_SHADOW_ELEVATION
    }

  @ColorInt
  var ambientShadowColor: Int = Color.BLACK
    set(value) {
      field = value
      flags = flags or FLAG_AMBIENT_SHADOW_COLOR
    }

  @ColorInt
  var spotShadowColor: Int = Color.BLACK
    set(value) {
      field = value
      flags = flags or FLAG_SPOT_SHADOW_COLOR
    }

  val isClipChildrenSet: Boolean
    get() = flags and FLAG_CLIP_CHILDREN != 0

  val isFocusableSet: Boolean
    get() = flags and FLAG_FOCUS != 0

  val isClickableSet: Boolean
    get() = flags and FLAG_CLICKABLE != 0

  val isEnabledSet: Boolean
    get() = flags and FLAG_ENABLED != 0

  val isSelectedSet: Boolean
    get() = flags and FLAG_SELECTED != 0

  val isScaleSet: Boolean
    get() = flags and FLAG_SCALE != 0

  val isAlphaSet: Boolean
    get() = flags and FLAG_ALPHA != 0

  val isRotationSet: Boolean
    get() = flags and FLAG_ROTATION != 0

  val isRotationXSet: Boolean
    get() = flags and FLAG_ROTATION_X != 0

  val isRotationYSet: Boolean
    get() = flags and FLAG_ROTATION_Y != 0

  val isTagSet: Boolean
    get() = flags and FLAG_VIEW_TAG != 0

  val isViewIdSet: Boolean
    get() = flags and FLAG_VIEW_ID != 0

  fun hasPadding(): Boolean = padding != null

  val paddingLeft: Int
    get() = padding?.left ?: 0

  val paddingTop: Int
    get() = padding?.top ?: 0

  val paddingRight: Int
    get() = padding?.right ?: 0

  val paddingBottom: Int
    get() = padding?.bottom ?: 0

  override fun equals(other: Any?): Boolean {
    if (this === other) return true
    if (other !is ViewAttributes) return false

    if (isHostSpec != other.isHostSpec) return false
    if (componentName != other.componentName) return false
    if (importantForAccessibility != other.importantForAccessibility) return false
    if (disableDrawableOutputs != other.disableDrawableOutputs) return false

    if (flags != other.flags) return false
    if (alpha != other.alpha) return false
    if (!isEquivalentTo(clickHandler, other.clickHandler)) return false
    if (clipToOutline != other.clipToOutline) return false
    if (clipChildren != other.clipChildren) return false
    if (!equals(contentDescription, other.contentDescription)) return false
    if (isEnabled != other.isEnabled) return false
    if (!isEquivalentTo(focusChangeHandler, other.focusChangeHandler)) return false
    if (isFocusable != other.isFocusable) return false
    if (!isEquivalentTo(interceptTouchHandler, other.interceptTouchHandler)) return false
    if (!isEquivalentTo(longClickHandler, other.longClickHandler)) return false
    if (!equals(outlineProvider, other.outlineProvider)) return false
    if (rotation != other.rotation) return false
    if (rotationX != other.rotationX) return false
    if (rotationY != other.rotationY) return false
    if (scale != other.scale) return false
    if (isSelected != other.isSelected) return false
    if (isClickable != other.isClickable) return false
    if (shadowElevation != other.shadowElevation) return false
    if (ambientShadowColor != other.ambientShadowColor) return false
    if (spotShadowColor != other.spotShadowColor) return false
    if (!isEquivalentTo(touchHandler, other.touchHandler)) return false
    if (viewId != other.viewId) return false
    if (!equals(viewTag, other.viewTag)) return false
    if (!equals(viewTags, other.viewTags)) return false

    if (!DrawableUtils.isEquivalentTo(background, other.background)) return false
    if (!DrawableUtils.isEquivalentTo(foreground, other.foreground)) return false
    if (!equals(padding, other.padding)) return false
    if (!equals(layoutDirection, other.layoutDirection)) return false
    if (stateListAnimatorRes != other.stateListAnimatorRes) return false
    // TODO: (T33421916) We need compare StateListAnimators more accurately
    if (!equals(stateListAnimator, other.stateListAnimator)) return false

    if (transitionName != other.transitionName) return false
    if (layerType != other.layerType) return false
    if (layoutPaint != other.layoutPaint) return false

    return true
  }

  override fun hashCode(): Int {
    var result = isHostSpec.hashCode()
    result = 31 * result + componentName.hashCode()
    result = 31 * result + importantForAccessibility
    result = 31 * result + disableDrawableOutputs.hashCode()
    result = 31 * result + (contentDescription?.hashCode() ?: 0)
    result = 31 * result + (viewId.hashCode())
    result = 31 * result + (viewTag?.hashCode() ?: 0)
    result = 31 * result + (transitionName?.hashCode() ?: 0)
    result = 31 * result + (viewTags?.hashCode() ?: 0)
    result = 31 * result + (outlineProvider?.hashCode() ?: 0)
    result = 31 * result + (clickHandler?.hashCode() ?: 0)
    result = 31 * result + (longClickHandler?.hashCode() ?: 0)
    result = 31 * result + (focusChangeHandler?.hashCode() ?: 0)
    result = 31 * result + (touchHandler?.hashCode() ?: 0)
    result = 31 * result + (interceptTouchHandler?.hashCode() ?: 0)
    result = 31 * result + (background?.hashCode() ?: 0)
    result = 31 * result + (foreground?.hashCode() ?: 0)
    result = 31 * result + (padding?.hashCode() ?: 0)
    result = 31 * result + layoutDirection.hashCode()
    result = 31 * result + (stateListAnimator?.hashCode() ?: 0)
    result = 31 * result + stateListAnimatorRes
    result = 31 * result + layerType
    result = 31 * result + (layoutPaint?.hashCode() ?: 0)
    result = 31 * result + flags
    result = 31 * result + scale.hashCode()
    result = 31 * result + alpha.hashCode()
    result = 31 * result + rotation.hashCode()
    result = 31 * result + rotationX.hashCode()
    result = 31 * result + rotationY.hashCode()
    result = 31 * result + clipChildren.hashCode()
    result = 31 * result + clipToOutline.hashCode()
    result = 31 * result + isFocusable.hashCode()
    result = 31 * result + isClickable.hashCode()
    result = 31 * result + isEnabled.hashCode()
    result = 31 * result + isSelected.hashCode()
    result = 31 * result + shadowElevation.hashCode()
    result = 31 * result + ambientShadowColor
    result = 31 * result + spotShadowColor
    return result
  }

  fun copyInto(target: ViewAttributes) {
    target.isHostSpec = isHostSpec
    target.componentName = componentName
    target.importantForAccessibility = importantForAccessibility
    target.disableDrawableOutputs = disableDrawableOutputs

    contentDescription?.let { target.contentDescription = it }
    target.viewId = viewId
    viewTag?.let { target.viewTag = it }
    transitionName?.let { target.transitionName = it }
    viewTags?.let { target.viewTags = it }
    outlineProvider?.let { target.outlineProvider = it }
    clickHandler?.let { target.clickHandler = it }
    longClickHandler?.let { target.longClickHandler = it }
    focusChangeHandler?.let { target.focusChangeHandler = it }
    touchHandler?.let { target.touchHandler = it }
    interceptTouchHandler?.let { target.interceptTouchHandler = it }

    background?.let { target.background = it }
    foreground?.let { target.foreground = it }
    padding?.let { target.padding = it }
    target.layoutDirection = layoutDirection
    stateListAnimator?.let { target.stateListAnimator = it }
    target.stateListAnimatorRes = stateListAnimatorRes
    target.layerType = layerType
    layoutPaint?.let { target.layoutPaint = it }

    if (isScaleSet) target.scale = scale
    if (isAlphaSet) target.alpha = alpha
    if (isRotationSet) target.rotation = rotation
    if (isRotationXSet) target.rotationX = rotationX
    if (isRotationYSet) target.rotationY = rotationY
    if (isClipChildrenSet) target.clipChildren = clipChildren
    target.clipToOutline = clipToOutline
    if (isFocusableSet) target.isFocusable = isFocusable
    if (isClickableSet) target.isClickable = isClickable
    if (isEnabledSet) target.isEnabled = isEnabled
    if (isSelectedSet) target.isSelected = isSelected
    if (flags and FLAG_SHADOW_ELEVATION != 0) target.shadowElevation = shadowElevation
    if (flags and FLAG_AMBIENT_SHADOW_COLOR != 0) target.ambientShadowColor = ambientShadowColor
    if (flags and FLAG_SPOT_SHADOW_COLOR != 0) target.spotShadowColor = spotShadowColor
  }

  companion object {
    private const val FLAG_SCALE = 1 shl 1
    private const val FLAG_ALPHA = 1 shl 2
    private const val FLAG_ROTATION = 1 shl 3
    private const val FLAG_ROTATION_X = 1 shl 4
    private const val FLAG_ROTATION_Y = 1 shl 5

    private const val FLAG_CLIP_CHILDREN = 1 shl 6
    private const val FLAG_CLIP_TO_OUTLINE = 1 shl 7
    private const val FLAG_FOCUS = 1 shl 8
    private const val FLAG_CLICKABLE = 1 shl 9
    private const val FLAG_ENABLED = 1 shl 10
    private const val FLAG_SELECTED = 1 shl 11

    private const val FLAG_SHADOW_ELEVATION = 1 shl 12
    private const val FLAG_AMBIENT_SHADOW_COLOR = 1 shl 13
    private const val FLAG_SPOT_SHADOW_COLOR = 1 shl 14

    private const val FLAG_VIEW_TAG = 1 shl 15
    private const val FLAG_VIEW_ID = 1 shl 16
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

@file:JvmName("ViewUtils")

package com.facebook.litho

import android.graphics.drawable.Drawable
import android.os.Build
import android.view.View
import com.facebook.litho.drawable.ComparableColorDrawable

fun setViewForeground(view: View, foreground: Drawable?) {
  foreground?.let {
    check(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      ("MountState has a ViewNodeInfo with foreground however " +
          "the current Android version doesn't support foreground on Views")
    }
    view.foreground = it
  }
}

fun setViewForeground(view: View, foregroundColor: Int) {
  check(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    ("MountState has a ViewNodeInfo with foreground however " +
        "the current Android version doesn't support foreground on Views")
  }
  view.foreground = ComparableColorDrawable.create(foregroundColor)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

interface WorkingRange {
  /**
   * Return `true` to trigger `@OnEnteredRange` method defined in the component, do nothing
   * otherwise.
   */
  fun shouldEnterRange(
      position: Int,
      firstVisibleIndex: Int,
      lastVisibleIndex: Int,
      firstFullyVisibleIndex: Int,
      lastFullyVisibleIndex: Int
  ): Boolean

  /**
   * Return true to trigger `@OnExitedRange` method defined in the component, do nothing otherwise.
   */
  fun shouldExitRange(
      position: Int,
      firstVisibleIndex: Int,
      lastVisibleIndex: Int,
      firstFullyVisibleIndex: Int,
      lastFullyVisibleIndex: Int
  ): Boolean
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import androidx.annotation.VisibleForTesting
import java.lang.Exception
import java.util.ArrayList
import java.util.LinkedHashMap
import kotlin.jvm.JvmField

/**
 * A container that stores working range related information. It provides two major methods: a
 * register method to store a working range with a component, and a dispatch method that dispatches
 * event to components to trigger their delegated methods.
 */
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
class WorkingRangeContainer {

  /**
   * Use [java.util.HashMap] to store the working range of each component. The key is composed with
   * name and working range hashcode. The value is a [RangeTuple] object that contains a working
   * range related information.
   */
  private val workingRanges: MutableMap<String, RangeTuple> by lazy { LinkedHashMap() }

  fun registerWorkingRange(
      name: String,
      workingRange: WorkingRange,
      scopedComponentInfo: ScopedComponentInfo,
      interStageProps: InterStagePropsContainer?
  ) {
    val key = "${name}_${workingRange.hashCode()}"
    val rangeTuple = workingRanges[key]
    if (rangeTuple == null) {
      workingRanges[key] = RangeTuple(name, workingRange, scopedComponentInfo, interStageProps)
    } else {
      rangeTuple.addComponent(scopedComponentInfo)
    }
  }

  /**
   * Iterate the map to check if a component is entered or exited the range, and dispatch event to
   * the component to trigger its delegate method.
   */
  fun checkWorkingRangeAndDispatch(
      position: Int,
      firstVisibleIndex: Int,
      lastVisibleIndex: Int,
      firstFullyVisibleIndex: Int,
      lastFullyVisibleIndex: Int,
      statusHandler: WorkingRangeStatusHandler
  ) {
    if (workingRanges.isEmpty()) {
      return
    }

    for (key in workingRanges.keys) {
      val rangeTuple = checkNotNull(workingRanges[key])
      var i = 0
      val size = rangeTuple.scopedComponentInfos.size
      while (i < size) {
        val scopedComponentInfo = rangeTuple.scopedComponentInfos[i]
        val scopedContext = scopedComponentInfo.context
        val component = scopedComponentInfo.component as SpecGeneratedComponent
        val globalKey = scopedContext.globalKey
        if (!statusHandler.isInRange(rangeTuple.name, component, globalKey) &&
            isEnteringRange(
                rangeTuple.workingRange,
                position,
                firstVisibleIndex,
                lastVisibleIndex,
                firstFullyVisibleIndex,
                lastFullyVisibleIndex)) {
          try {
            component.dispatchOnEnteredRange(
                scopedContext, rangeTuple.name, rangeTuple.interStagePropsContainer)
          } catch (e: Exception) {
            ComponentUtils.handle(scopedContext, e)
          }
          statusHandler.setEnteredRangeStatus(rangeTuple.name, component, globalKey)
        } else if (statusHandler.isInRange(rangeTuple.name, component, globalKey) &&
            isExitingRange(
                rangeTuple.workingRange,
                position,
                firstVisibleIndex,
                lastVisibleIndex,
                firstFullyVisibleIndex,
                lastFullyVisibleIndex)) {
          try {
            component.dispatchOnExitedRange(
                scopedContext, rangeTuple.name, rangeTuple.interStagePropsContainer)
          } catch (e: Exception) {
            ComponentUtils.handle(scopedContext, e)
          }
          statusHandler.setExitedRangeStatus(rangeTuple.name, component, globalKey)
        }
        i++
      }
    }
  }

  /**
   * Dispatch onExitRange if the status of the component is in the range. This method should only be
   * called when releasing a ComponentTree, thus no status update needed.
   */
  fun dispatchOnExitedRangeIfNeeded(statusHandler: WorkingRangeStatusHandler) {
    if (workingRanges.isEmpty()) {
      return
    }

    for (key in workingRanges.keys) {
      val rangeTuple = checkNotNull(workingRanges[key])
      var i = 0
      val size = rangeTuple.scopedComponentInfos.size
      while (i < size) {
        val scopedComponentInfo = rangeTuple.scopedComponentInfos[i]
        val scopedContext = scopedComponentInfo.context
        // working ranges are only available in Spec Components, so we can cast it here
        val component = scopedComponentInfo.component as SpecGeneratedComponent
        val globalKey = scopedContext.globalKey
        if (statusHandler.isInRange(rangeTuple.name, component, globalKey)) {
          try {
            component.dispatchOnExitedRange(
                scopedContext, rangeTuple.name, rangeTuple.interStagePropsContainer)
          } catch (e: Exception) {
            ComponentUtils.handle(scopedContext, e)
          }
        }
        i++
      }
    }
  }

  @get:VisibleForTesting
  val workingRangesForTestOnly: Map<String, RangeTuple>
    get() = workingRanges

  /**
   * A tuple that stores working range information for a list of components that share same name and
   * working range object.
   */
  @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
  class RangeTuple(
      @JvmField val name: String,
      @JvmField val workingRange: WorkingRange,
      scopedComponentInfo: ScopedComponentInfo,
      @JvmField val interStagePropsContainer: InterStagePropsContainer?
  ) {

    @JvmField val scopedComponentInfos: MutableList<ScopedComponentInfo> = ArrayList()

    fun addComponent(scopedComponentInfo: ScopedComponentInfo) {
      scopedComponentInfos.add(scopedComponentInfo)
    }

    init {
      scopedComponentInfos.add(scopedComponentInfo)
    }
  }

  /** A tuple that stores raw data of a working range registration. */
  internal class Registration(
      @JvmField val name: String,
      @JvmField val workingRange: WorkingRange,
      @JvmField val scopedComponentInfo: ScopedComponentInfo
  )

  companion object {
    @JvmStatic
    fun isEnteringRange(
        workingRange: WorkingRange,
        position: Int,
        firstVisibleIndex: Int,
        lastVisibleIndex: Int,
        firstFullyVisibleIndex: Int,
        lastFullyVisibleIndex: Int
    ): Boolean =
        workingRange.shouldEnterRange(
            position,
            firstVisibleIndex,
            lastVisibleIndex,
            firstFullyVisibleIndex,
            lastFullyVisibleIndex)

    @JvmStatic
    fun isExitingRange(
        workingRange: WorkingRange,
        position: Int,
        firstVisibleIndex: Int,
        lastVisibleIndex: Int,
        firstFullyVisibleIndex: Int,
        lastFullyVisibleIndex: Int
    ): Boolean =
        workingRange.shouldExitRange(
            position,
            firstVisibleIndex,
            lastVisibleIndex,
            firstFullyVisibleIndex,
            lastFullyVisibleIndex)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import androidx.annotation.IntDef
import androidx.annotation.VisibleForTesting
import androidx.collection.SimpleArrayMap
import java.util.HashMap

/** A handler that stores the range status of components with given working range. */
class WorkingRangeStatusHandler {
  @IntDef(STATUS_UNINITIALIZED, STATUS_IN_RANGE, STATUS_OUT_OF_RANGE)
  annotation class WorkingRangeStatus

  /**
   * Use a [SimpleArrayMap] to store status of components with given working range. The key of the
   * container is combined with the component's global key and the working range name.
   *
   * The global key guarantees the uniqueness of the component in a ComponentTree, and it's
   * consistent across different [LayoutState]s. The working range name is used to find the specific
   * working range since a component can have several working ranges. The value is an integer
   * presenting the status.
   */
  private val _status: MutableMap<String, Int> = HashMap()

  fun isInRange(name: String, component: Component, globalKey: String): Boolean =
      getStatus(name, component, globalKey) == STATUS_IN_RANGE

  /** Components in the collection share same status, we can only check the first component. */
  @WorkingRangeStatus
  private fun getStatus(name: String, component: Component, componentGlobalKey: String): Int {
    val key = generateKey(name, componentGlobalKey)
    return _status[key] ?: STATUS_UNINITIALIZED
  }

  fun setEnteredRangeStatus(name: String, component: Component?, globalKey: String) {
    setStatus(name, component, globalKey, STATUS_IN_RANGE)
  }

  fun setExitedRangeStatus(name: String, component: Component?, globalKey: String) {
    setStatus(name, component, globalKey, STATUS_OUT_OF_RANGE)
  }

  fun clear() {
    _status.clear()
  }

  @get:VisibleForTesting
  val status: Map<String, Int>
    get() = _status

  @VisibleForTesting
  fun setStatus(
      name: String,
      component: Component?,
      componentGlobalKey: String,
      @WorkingRangeStatus status: Int
  ) {
    _status[generateKey(name, componentGlobalKey)] = status
  }

  companion object {
    const val STATUS_UNINITIALIZED = 0
    const val STATUS_IN_RANGE = 1
    const val STATUS_OUT_OF_RANGE = 2

    private fun generateKey(name: String, globalKey: String): String = "$name-$globalKey"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import com.facebook.yoga.YogaAlign
import com.facebook.yoga.YogaConstants
import com.facebook.yoga.YogaDirection
import com.facebook.yoga.YogaEdge
import com.facebook.yoga.YogaFlexDirection
import com.facebook.yoga.YogaJustify
import com.facebook.yoga.YogaNode
import com.facebook.yoga.YogaPositionType
import com.facebook.yoga.YogaWrap
import kotlin.jvm.JvmField

open class YogaLayoutProps(val node: YogaNode) : LayoutProps {

  @JvmField var isPaddingSet: Boolean = false
  @JvmField var widthFromStyle: Float = YogaConstants.UNDEFINED
  @JvmField var heightFromStyle: Float = YogaConstants.UNDEFINED

  override fun widthPx(width: Int) {
    widthFromStyle = width.toFloat()
    node.setWidth(widthFromStyle)
  }

  override fun widthPercent(percent: Float) {
    widthFromStyle = percent
    node.setWidthPercent(widthFromStyle)
  }

  override fun minWidthPx(minWidth: Int) {
    widthFromStyle = minWidth.toFloat()
    node.setMinWidth(widthFromStyle)
  }

  override fun maxWidthPx(maxWidth: Int) {
    widthFromStyle = maxWidth.toFloat()
    node.setMaxWidth(widthFromStyle)
  }

  override fun minWidthPercent(percent: Float) {
    widthFromStyle = percent
    node.setMinWidthPercent(widthFromStyle)
  }

  override fun maxWidthPercent(percent: Float) {
    widthFromStyle = percent
    node.setMaxWidthPercent(widthFromStyle)
  }

  override fun heightPx(height: Int) {
    heightFromStyle = height.toFloat()
    node.setHeight(heightFromStyle)
  }

  override fun heightPercent(percent: Float) {
    heightFromStyle = percent
    node.setHeightPercent(heightFromStyle)
  }

  override fun minHeightPx(minHeight: Int) {
    heightFromStyle = minHeight.toFloat()
    node.setMinHeight(heightFromStyle)
  }

  override fun maxHeightPx(maxHeight: Int) {
    heightFromStyle = maxHeight.toFloat()
    node.setMaxHeight(heightFromStyle)
  }

  override fun minHeightPercent(percent: Float) {
    heightFromStyle = percent
    node.setMinHeightPercent(heightFromStyle)
  }

  override fun maxHeightPercent(percent: Float) {
    heightFromStyle = percent
    node.setMaxHeightPercent(heightFromStyle)
  }

  override fun layoutDirection(direction: YogaDirection) {
    node.setDirection(direction)
  }

  override fun alignSelf(alignSelf: YogaAlign) {
    node.alignSelf = alignSelf
  }

  override fun flex(flex: Float) {
    node.flex = flex
  }

  override fun flexGrow(flexGrow: Float) {
    node.flexGrow = flexGrow
  }

  override fun flexShrink(flexShrink: Float) {
    node.flexShrink = flexShrink
  }

  override fun flexBasisPx(flexBasis: Int) {
    node.setFlexBasis(flexBasis.toFloat())
  }

  override fun flexBasisPercent(percent: Float) {
    node.setFlexBasisPercent(percent)
  }

  override fun aspectRatio(aspectRatio: Float) {
    node.aspectRatio = aspectRatio
  }

  override fun positionType(positionType: YogaPositionType) {
    node.positionType = positionType
  }

  override fun positionPx(edge: YogaEdge, position: Int) {
    node.setPosition(edge, position.toFloat())
  }

  override fun positionPercent(edge: YogaEdge, percent: Float) {
    node.setPositionPercent(edge, percent)
  }

  override fun paddingPx(edge: YogaEdge, padding: Int) {
    isPaddingSet = true
    node.setPadding(edge, padding.toFloat())
  }

  override fun paddingPercent(edge: YogaEdge, percent: Float) {
    isPaddingSet = true
    node.setPaddingPercent(edge, percent)
  }

  override fun marginPx(edge: YogaEdge, margin: Int) {
    node.setMargin(edge, margin.toFloat())
  }

  override fun marginPercent(edge: YogaEdge, percent: Float) {
    node.setMarginPercent(edge, percent)
  }

  override fun marginAuto(edge: YogaEdge) {
    node.setMarginAuto(edge)
  }

  override fun isReferenceBaseline(isReferenceBaseline: Boolean) {
    node.setIsReferenceBaseline(isReferenceBaseline)
  }

  override fun useHeightAsBaseline(useHeightAsBaseline: Boolean) {
    if (useHeightAsBaseline) {
      node.setBaselineFunction { _, _, height -> height }
    }
  }

  override fun heightAuto() {
    node.setHeightAuto()
  }

  override fun widthAuto() {
    node.setWidthAuto()
  }

  override fun flexBasisAuto() {
    node.setFlexBasisAuto()
  }

  override fun setBorderWidth(edge: YogaEdge, borderWidth: Float) {
    node.setBorder(edge, borderWidth)
  }

  fun flexDirection(direction: YogaFlexDirection) {
    node.flexDirection = direction
  }

  fun wrap(wrap: YogaWrap) {
    node.wrap = wrap
  }

  fun justifyContent(justify: YogaJustify) {
    node.justifyContent = justify
  }

  fun alignItems(align: YogaAlign) {
    node.alignItems = align
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

@file:JvmName("AttributionUtils")

package com.facebook.litho.debug

import com.facebook.rendercore.LogLevel
import com.facebook.rendercore.debug.DebugEventDispatcher

fun getAttribution(attribution: String?): String? {
  if (attribution?.isNotEmpty() == true || DebugEventDispatcher.minLogLevel <= LogLevel.VERBOSE) {
    return attribution
  }

  val stack = RuntimeException().stackTrace
  val frame =
      stack.find { frame ->
        !frame.className.contains("com.facebook.litho.ComponentTree") &&
            !frame.className.contains("com.facebook.litho.debug.") &&
            !frame.className.contains("com.facebook.rendercore.debug")
      }

  return frame?.toString() ?: attribution
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho.debug

import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import com.facebook.litho.ThreadUtils
import kotlin.math.min

/**
 * Draws a row of rectangles starting from the top left corner; the number of rectangles represents
 * the number resolves. If the rectangle is red is color then that resolved results was calculated
 * on the main thread. If the overlay is red in color then the most recent layout was on the main
 * thread. The debug overlay tracks layout history, but doesn't show it on the overlay.
 */
class DebugOverlay(private val isMainThreadLayouts: List<Boolean>) : Drawable() {

  companion object {

    @JvmField var isEnabled: Boolean = false

    private const val COLOR_RED_SEMITRANSPARENT = 0x22FF0000
    private const val COLOR_GREEN_SEMITRANSPARENT = 0x2200FF00
    private const val COLOR_RED_OPAQUE = 0xCCFF0000.toInt()
    private const val COLOR_GREEN_OPAQUE = 0xCC00FF00.toInt()
    private const val BOX_WIDTH_PX = 8
    private const val BOX_HEIGHT_PX = 10
    private const val BOX_MARGIN_BETWEEN_PX = 4
    private const val BOX_MARGIN_EDGE_PX = 4
    private const val TEXT_SIZE_PX = 20f

    private val layoutThreadHistory: MutableMap<Int, MutableList<Boolean>> by lazy {
      mutableMapOf()
    }

    private val resolveThreadHistory: MutableMap<Int, MutableList<Boolean>> by lazy {
      mutableMapOf()
    }

    @JvmStatic
    @Synchronized
    fun updateLayoutHistory(id: Int) {
      val history: MutableList<Boolean> = layoutThreadHistory.getOrPut(id) { mutableListOf() }
      history.add(ThreadUtils.isMainThread())
    }

    @JvmStatic
    @Synchronized
    fun updateResolveHistory(id: Int) {
      val history: MutableList<Boolean> = resolveThreadHistory.getOrPut(id) { mutableListOf() }
      history.add(ThreadUtils.isMainThread())
    }

    @JvmStatic
    @Synchronized
    fun getDebugOverlay(id: Int): DebugOverlay {
      return DebugOverlay(resolveThreadHistory.getOrPut(id) { mutableListOf() })
    }
  }

  private val text: String
  private val textPaint: Paint = Paint()
  private val colorPaint: Paint = Paint()
  private val overlayColor: Int

  init {
    textPaint.color = Color.BLACK
    textPaint.style = Paint.Style.FILL
    textPaint.textSize = TEXT_SIZE_PX
    textPaint.textAlign = Paint.Align.LEFT
    textPaint.isAntiAlias = true
    val size = isMainThreadLayouts.size
    if (size > 0) {
      text = "${isMainThreadLayouts.size}x"
      overlayColor =
          if (isMainThreadLayouts[size - 1]) {
            COLOR_RED_SEMITRANSPARENT
          } else {
            COLOR_GREEN_SEMITRANSPARENT
          }
    } else {
      text = ""
      overlayColor = Color.TRANSPARENT
    }
  }

  override fun draw(canvas: Canvas) {
    val bounds = bounds

    // draw overlay background
    colorPaint.color = overlayColor
    canvas.drawRect(bounds, colorPaint)

    val count = isMainThreadLayouts.size
    val leftEnd = bounds.left + BOX_MARGIN_EDGE_PX
    val rightEnd = bounds.right
    val top = bounds.top + BOX_MARGIN_EDGE_PX
    val bottom = min(top + BOX_HEIGHT_PX, bounds.bottom)
    var i = 0
    var left: Int
    var right: Int

    while (i < count) {

      // calculate position for next render history rect
      left = leftEnd + i * (BOX_WIDTH_PX + BOX_MARGIN_BETWEEN_PX)
      right = left + BOX_WIDTH_PX

      // if position outside bounds then exit
      if (right >= rightEnd) {
        break
      }

      if (isMainThreadLayouts[i]) {
        colorPaint.color = COLOR_RED_OPAQUE
      } else {
        colorPaint.color = COLOR_GREEN_OPAQUE
      }

      // draw render history rect
      canvas.drawRect(
          left.toFloat(),
          top.toFloat(),
          right.toFloat(),
          bottom.toFloat(),
          colorPaint,
      )

      i++
    }

    // draw render count text
    canvas.drawText(
        text,
        leftEnd + BOX_MARGIN_EDGE_PX.toFloat(),
        top + BOX_HEIGHT_PX + TEXT_SIZE_PX + 2,
        textPaint,
    )
  }

  override fun setAlpha(alpha: Int) = Unit

  override fun setColorFilter(cf: ColorFilter?) = Unit

  override fun getOpacity(): Int {
    return PixelFormat.TRANSLUCENT
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho.debug

import com.facebook.rendercore.debug.DebugEvent

object LithoDebugEvent {
  val RenderCore: DebugEvent.Companion = DebugEvent
  const val RenderRequest = "Litho.RenderRequest"
  const val LayoutCommitted = "Litho.LayoutCommitted"
  const val StateUpdateEnqueued = "Litho.StateUpdateEnqueued"
  const val RenderOnMainThreadStarted = "RenderOnMainThreadStarted"
  const val ComponentPrepared = "Litho.Resolve.ComponentPrepared"
  const val ComponentResolved = "Litho.Resolve.ComponentResolved"
  const val ComponentTreeResolve = "Litho.ComponentTree.Resolve"
  const val ComponentTreeResolveResumed = "Litho.ComponentTree.Resolve.Resumed"
  const val ComponentTreeMountContentPreallocated = "Litho.ComponentTree.MountContent.Preallocated"
}

object LithoDebugEventAttributes {
  const val Root = "root"
  const val Attribution = "attribution"
  const val StateUpdateType = "state_update_type"
  const val RootId = "root_id"
  const val MainThreadLayoutStateRootId = "main_thread_layout_state_root_id"
  const val MainThreadLayoutStateWidthSpec = "main_thread_layout_state_width_spec"
  const val MainThreadLayoutStateHeightSpec = "main_thread_layout_state_height_spec"
  const val MainThreadLayoutStatePrettySizeSpecs = "main_thread_layout_state_size_specs_pretty"
  const val MeasureWidthSpec = "measure_width_spec"
  const val MeasureHeightSpec = "measure_height_spec"
  const val MeasurePrettySizeSpecs = "measure_size_specs_pretty"
  const val SizeSpecsMatch = "size_specs_match"
  const val IdMatch = "id_match"
  const val HasMainThreadLayoutState = "has_main_thread_layout_state"
  const val Breadcrumb = "breadcrumb"
  const val RenderSource = "source"
  const val RenderExecutionMode = "execution-mode"
  const val Forced = "forced"
  const val RunsOnMainThread = "runs_on_main_thread"
  const val Component = "component"
  const val ResolveVersion = "resolve_version"
  const val ResolveSource = "resolve_source"
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.app.Activity

fun Activity.setContent(component: Component) {
  setContentView(LithoView.create(this, component))
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.view.View

/**
 * The implicit receiver for [KComponent.render] call. This class exposes the ability to use hooks,
 * like [useState], and convenience functions, like [dp].
 */
open class ComponentScope(
    override val context: ComponentContext,
    internal var resolveStateContext: ResolveStateContext? = null
) : ResourcesScope {
  // TODO: Extract into more generic container to track hooks when needed
  internal var useStateIndex = 0
  internal var useCachedIndex = 0
  internal var transitions: MutableList<Transition>? = null
  internal var useEffectEntries: MutableList<Attachable>? = null

  /**
   * A utility function to find the View with a given tag under the current Component's LithoView.
   * To set a view tag, use Style.viewTag. An appropriate time to call this is in your Component's
   * onVisible callback.
   *
   *
As with View.findViewWithTag in general, this must be called on the main thread.
   *
   *
Note that null may be returned if the associated View doesn't exist or isn't mounted: with
   * incremental mount turned on (which is the default), if the component is off-screen, it won't be
   * mounted.
   *
   *
Finally, note that you should never hold a reference to the view returned by this function
   * as Litho may unmount your Component and mount it to a different View.
   */
  fun <T : View> findViewWithTag(tag: Any): T? {
    return context.findViewWithTag(tag)
  }

  internal fun cleanUp() {
    resolveStateContext = null
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

/**
 * Marker for the Litho container component DSL. This makes sure that when we have scope-specific
 * functions like `child()` to add children, they can only apply to the closest receiver annotated
 * with ContainerDsl.
 *
 * Any class annotated with this marker should also extend ResourcesScope
 *
 * See https://kotlinlang.org/docs/type-safe-builders.html#scope-control-dslmarker
 */
@DslMarker annotation class ContainerDsl


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import androidx.annotation.DimenRes
import java.lang.Double.doubleToRawLongBits
import java.lang.Double.longBitsToDouble
import java.lang.Float.intBitsToFloat
import java.lang.IllegalArgumentException
import java.lang.Long.toHexString

/**
 * A class which represents dimension values in different units: Px, Dp, and Sp. All can be
 * converted to pixels with a [ResourceResolver] which wraps a [Resources].
 *
 * To create a Dimen:
 * - For a literal value, use the [px], [dp], and [sp] extension functions, e.g. `10.dp`.
 * - For a value resolved from a resource, use [dimenRes], e.g. `dimenRes(R.dimen.my_dimen)`
 */
@JvmInline
value class Dimen(val encodedValue: Long) {

  /** Converts the given Dimen to pixels. */
  fun toPixels(resourceResolver: ResourceResolver): Int =
      convertEncodedValueToPixels(resourceResolver, encodedValue)
}

/** Resolve a dimen resource ID as a [Dimen] value. */
inline fun ResourcesScope.dimenRes(@DimenRes id: Int): Dimen =
    resourceResolver.resolveDimenSizeRes(id).px

/** Resolve a dimen resource ID as a [Dimen] value. */
inline fun ComponentContext.dimenRes(@DimenRes id: Int): Dimen =
    resourceResolver.resolveDimenSizeRes(id).px

/** Creates a Dimen with a constant dp (density-independent pixels) value. */
inline val Int.dp: Dimen
  get() = Dimen(doubleToRawLongBits(this.toDouble()))

/** Creates a Dimen with a constant dp (density-independent pixels) value. */
inline val Float.dp: Dimen
  get() = Dimen(doubleToRawLongBits(this.toDouble()))

/**
 * Creates a Dimen with a constant dp (density-independent pixels) value.
 *
 * The value will be narrowed to a float automatically.
 */
inline val Double.dp: Dimen
  get() = Dimen(doubleToRawLongBits(this))

/** Creates a Dimen with a constant sp (scale-independent pixels) value. */
inline val Int.sp: Dimen
  get() = Dimen(encodeSpFloat(this.toFloat()))

/** Creates a Dimen with a constant sp (scale-independent pixels) value. */
inline val Float.sp: Dimen
  get() = Dimen(encodeSpFloat(this))

/**
 * Creates a Dimen with a constant sp (scale-independent pixels) value.
 *
 * The value will be narrowed to a float automatically.
 */
inline val Double.sp: Dimen
  get() = Dimen(encodeSpFloat(this.toFloat()))

/** Creates a Dimen with a constant pixel value. */
inline val Int.px: Dimen
  get() = Dimen(encodePxInt(this))

/**
 * Creates a Dimen with a constant pixel value.
 *
 * The value will automatically be converted to an int first.
 */
inline val Float.px: Dimen
  get() = Dimen(encodePxInt(this.toInt()))

/**
 * Creates a Dimen with a constant pixel value.
 *
 * The value will automatically be converted to an int first.
 */
inline val Double.px: Dimen
  get() = Dimen(encodePxInt(this.toInt()))

//
// Below this point are implementation details for packing dimension values + type into a long for
// performance (at the time of rolling this API out, this actually moved the performance needle).
//
// They aren't relevant to usage of the APIs above thus why they are tucked away at the bottom of
// the file.
//

// 0x7FF8000000000000L is the mask used to indicate a quiet NaN, while the or'd flags below indicate
// whether the value in the payload is a px or sp value. See Dimen.toPixels() for more info.
const val NAN_MASK = 0x7FF8_0000_0000_0000L
const val PX_FLAG = NAN_MASK or 0x0001_0000_0000_0000L
const val SP_FLAG = NAN_MASK or 0x0002_0000_0000_0000L
const val PAYLOAD_MASK = 0xFFFF_FFFFL

/**
 * Note on implementation: in order to not have to materialize an actual object for non-nullable
 * usages of Dimen, we use an inline class (Dimen) that contains a Double represented as a Long
 * (i.e. the bits of the double are the bits of the long). We don't directly use a Double since the
 * JRE will sometimes canonicalize NaNs, losing the information we store in the mantissa.
 *
 * This Double encodes both the type of the Dimen (PX, DP, SP) and its value. We can do this because
 * all px/dp/sp values are 32-bits, while doubles are 64-bit. The encoding is as follows:
 * - DP: The double is not NaN, and the double value can just be cast to a float
 * - PX: The double is a quiet NaN with PX_FLAG set in the mantissa, and the least significant
 *   32-bits (the payload) are the signed int value
 * - SP: The double is a quiet NaN with SP_FLAG set in the mantissa, and the least significant
 *   32-bits (the payload) are the signed float value
 *
 * Read more about quiet NaN's here: https://www.doc.ic.ac.uk/~eedwards/compsys/float/nan.html
 */
private inline fun convertEncodedValueToPixels(
    resourceResolver: ResourceResolver,
    encodedValue: Long
): Int =
    when {
      // DP case
      encodedValue and NAN_MASK != NAN_MASK ->
          resourceResolver.dipsToPixels(longBitsToDouble(encodedValue).toFloat())
      // PX case
      encodedValue and PX_FLAG == PX_FLAG -> (encodedValue and PAYLOAD_MASK).toInt()
      // SP case
      encodedValue and SP_FLAG == SP_FLAG ->
          resourceResolver.sipsToPixels(
              intBitsToFloat((encodedValue and PAYLOAD_MASK).toInt()),
          )
      else -> throw IllegalArgumentException("Got unexpected NaN: ${toHexString(encodedValue)}")
    }

@PublishedApi
internal inline fun encodePxInt(value: Int): Long {
  return value.toLong() or PX_FLAG
}

@PublishedApi
internal inline fun encodeSpFloat(value: Float): Long {
  return value.toRawBits().toLong() or SP_FLAG
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.content.Context
import com.facebook.rendercore.Mountable

/**
 * Class responsible for setting the dynamic value on the content through the [DynamicValuesBinder]
 * that is register as [RenderUnit.DelegateBinder.extension]
 *
 * @param dynamicValue value that will be set to the content in bind and onValueChange
 * @param defaultValue value that will be set to the content after unbind call
 * @param valueSetter function or function reference that will set the dynamic value on the content
 */
internal class DynamicPropsHolder<ContentT, T>(
    private val dynamicValue: DynamicValue<T>,
    private val defaultValue: T,
    private val valueSetter: (ContentT, T) -> Unit
) : DynamicValue.OnValueChangeListener<T> {

  private var content: ContentT? = null

  override fun onValueChange(value: DynamicValue<T>) {
    content?.let {
      ThreadUtils.assertMainThread()
      valueSetter(it, value.get())
    }
  }

  fun bind(context: Context?, content: ContentT, model: Mountable<*>?, layoutData: Any?) {
    this.content = content
    dynamicValue.attachListener(this)
    valueSetter(content, dynamicValue.get())
  }

  fun unbind(context: Context?, content: ContentT, model: Mountable<*>?, layoutData: Any?) {
    valueSetter(content, defaultValue)
    dynamicValue.detach(this)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.content.Context
import com.facebook.rendercore.Mountable
import com.facebook.rendercore.RenderUnit

/**
 * Class that will be registered as MountUnmount [RenderUnit.DelegateBinder.createDelegateBinder].
 *
 * @param binders List of [DynamicPropsBinder] holding reference to DynamicValue and Content
 */
internal class DynamicValuesBinder<ContentT>(
    private val binders: ArrayList<DynamicPropsHolder<Any?, Mountable<*>>>
) : RenderUnit.Binder<Mountable<*>, ContentT, Any?> {

  override fun bind(
      context: Context?,
      content: ContentT,
      model: Mountable<*>?,
      layoutData: Any?
  ): Any? {
    binders.forEach { it.bind(context, content, model, layoutData) }
    return null
  }

  override fun unbind(
      context: Context?,
      content: ContentT,
      model: Mountable<*>?,
      layoutData: Any?,
      bindData: Any?
  ) {
    binders.forEach { it.unbind(context, content, model, layoutData) }
  }

  override fun shouldUpdate(
      currentModel: Mountable<*>?,
      newModel: Mountable<*>?,
      currentLayoutData: Any?,
      nextLayoutData: Any?
  ): Boolean = true
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import com.facebook.yoga.YogaAlign
import com.facebook.yoga.YogaJustify
import com.facebook.yoga.YogaWrap

/**
 * Constructs a new [Column]. Add children by using [FlexboxContainerScope.child] or
 * [FlexboxContainerScope.children]:
 * ```
 * Column(...) {
 *   child(Text(text = "Foo"))
 * }
 * ```
 */
@Suppress("FunctionName")
inline fun ResourcesScope.Column(
    alignContent: YogaAlign? = null,
    alignItems: YogaAlign? = null,
    justifyContent: YogaJustify? = null,
    wrap: YogaWrap? = null,
    isReversed: Boolean = false,
    style: Style? = null,
    init: FlexboxContainerScope.() -> Unit,
): Column {
  val containerScope = FlexboxContainerScope(context)
  containerScope.init()
  return createColumn(
      alignContent, alignItems, justifyContent, wrap, isReversed, style, containerScope)
}

/** Constructs a new [Column] without any children. */
@Suppress("FunctionName")
inline fun ResourcesScope.Column(
    alignContent: YogaAlign? = null,
    alignItems: YogaAlign? = null,
    justifyContent: YogaJustify? = null,
    wrap: YogaWrap? = null,
    isReversed: Boolean = false,
    style: Style? = null
): Column = createColumn(alignContent, alignItems, justifyContent, wrap, isReversed, style, null)

/** Internal function to allow [Column] to be inlineable. */
fun ResourcesScope.createColumn(
    alignContent: YogaAlign? = null,
    alignItems: YogaAlign? = null,
    justifyContent: YogaJustify? = null,
    wrap: YogaWrap? = null,
    isReversed: Boolean = false,
    style: Style? = null,
    resolvedContainerScope: FlexboxContainerScope?
): Column =
    Column(
            alignContent,
            alignItems,
            justifyContent,
            wrap,
            isReversed,
            resolvedContainerScope?.children)
        .apply { style?.applyCommonProps(context, getCommonPropsHolder()) }

/**
 * Constructs a new [Row]. Add children by using [FlexboxContainerScope.child] or
 * [FlexboxContainerScope.children]:
 * ```
 * Row(...) {
 *   child(Text(text = "Foo"))
 * }
 * ```
 */
@Suppress("FunctionName")
inline fun ResourcesScope.Row(
    alignContent: YogaAlign? = null,
    alignItems: YogaAlign? = null,
    justifyContent: YogaJustify? = null,
    wrap: YogaWrap? = null,
    isReversed: Boolean = false,
    style: Style? = null,
    init: FlexboxContainerScope.() -> Unit,
): Row {
  val containerScope = FlexboxContainerScope(context)
  containerScope.init()
  return createRow(
      alignContent, alignItems, justifyContent, wrap, isReversed, style, containerScope)
}

/** Constructs a new [Column] without any children. */
@Suppress("FunctionName")
inline fun ResourcesScope.Row(
    alignContent: YogaAlign? = null,
    alignItems: YogaAlign? = null,
    justifyContent: YogaJustify? = null,
    wrap: YogaWrap? = null,
    isReversed: Boolean = false,
    style: Style? = null
): Row = createRow(alignContent, alignItems, justifyContent, wrap, isReversed, style, null)

/** Internal function to allow [Row] to be inlineable. */
fun ResourcesScope.createRow(
    alignContent: YogaAlign? = null,
    alignItems: YogaAlign? = null,
    justifyContent: YogaJustify? = null,
    wrap: YogaWrap? = null,
    isReversed: Boolean = false,
    style: Style? = null,
    resolvedContainerScope: FlexboxContainerScope?
): Row =
    Row(
            alignContent,
            alignItems,
            justifyContent,
            wrap,
            isReversed,
            resolvedContainerScope?.children)
        .apply { style?.applyCommonProps(context, getCommonPropsHolder()) }

/**
 * The implicit receiver for the trailing lambda on [ResourcesScope.Column] or [ResourcesScope.Row].
 * The receiver gives the ability to add children to this container.
 */
@ContainerDsl
class FlexboxContainerScope(
    override val context: ComponentContext,
    internal val children: MutableList<Component> = mutableListOf(),
) : ResourcesScope {

  /** Adds a Component as a child to the Row or Column being initialized. */
  fun child(component: Component?) {
    component?.let { children.add(component) }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import com.facebook.litho.annotations.Hook

/**
 * Create a CachedValue variable within a Component. The [calculator] will provide the calculated
 * value if it hasn't already been calculated or if the inputs have changed since the previous
 * calculation.
 */
@Hook
fun <T> ComponentScope.useCached(vararg inputs: Any?, calculator: () -> T): T {
  val globalKey = context.globalKey
  val hookIndex = useCachedIndex++
  val hookKey = "$globalKey:$hookIndex"
  val cacheInputs = CachedInputs(hookKey, inputs)
  val result =
      context.getCachedValue(cacheInputs)
          ?: calculator().also { context.putCachedValue(cacheInputs, it) }

  @Suppress("UNCHECKED_CAST") return result as T
}

internal class CachedInputs(val hookKey: String, val inputs: Array<out Any?>) {
  override fun equals(other: Any?): Boolean {
    if (this === other) {
      return true
    }
    if (other == null || javaClass != other.javaClass) {
      return false
    }

    other as CachedInputs

    return hookKey == other.hookKey && inputs.contentEquals(other.inputs)
  }

  override fun hashCode(): Int {
    var result = hookKey.hashCode()
    result = 31 * result + inputs.contentHashCode()
    return result
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import androidx.annotation.UiThread
import com.facebook.litho.annotations.Hook

/**
 * [useCallback] allows a parent to pass a child component a callback which: 1) maintains
 * referential equality across multiple layout passes 2) is updated to have the latest parent props
 * and state, even if the child doesn't re-render.
 *
 * ## Example
 *
 * This sounds complicated, so it helps to step back a bit to provide context via example:
 *
 * Say we have a parent component which renders a list of children via a Collection. Let's say we
 * also want this parent to implement multi-select behavior, meaning on click, a row can be marked
 * as selected/de-selected. We will store the state of which components are currently selected in
 * the parent, and the parent will pass a lambda to the child to allow that state to be updated when
 * a row is clicked.
 *
 * When the first row is clicked, it will update the state in the parent via an onClick lambda. At
 * this point, we ideally want to re-render only that row since that's the only row whose UI will be
 * updated, i.e. to a selected state. However, that means the rest of the children will still have
 * that original onClick lambda set on them: if that lambda captured props/state, then invoking it
 * will operate on stale props/state! In this case, that means selecting a second row will de-select
 * the first one which is incorrect.
 *
 * An alternative is to always re-render all children whenever the list of selected items changes.
 * This is effective in that it will make sure all children have a lambda capturing the latest
 * props/state, however it's inefficient since it re-renders all children even though their UI
 * doesn't appear different.
 *
 * [useCallback] tries to give the best of both worlds: it doesn't cause children to re-render, and
 * also gives a mechanism for them to invoke a lambda that has captured the latest props and state.
 *
 * ## Notes: Thread Safety
 *
 * The callback that will be invoked by the function returned by [useCallback] is updated when a new
 * layout has been committed **on the main thread**. This means that [useCallback] should only be
 * used with events that will also be invoked from the main thread, e.g. onClick.
 */
@Hook
fun <R> ComponentScope.useCallback(
    callback: () -> R,
): () -> R {
  val callbackRef = useState { MemoizedCallback0(CallbackHolder(callback)) }
  useEffect(callback) {
    callbackRef.value.callbackHolder.callback = callback
    null
  }
  return callbackRef.value
}

/** 1-parameter overload of [useCallback]: refer to docs there. */
@Hook
fun <A, R> ComponentScope.useCallback(
    callback: (A) -> R,
): (A) -> R {
  val callbackRef = useState { MemoizedCallback1(CallbackHolder(callback)) }
  useEffect(callback) {
    callbackRef.value.callbackHolder.callback = callback
    null
  }
  return callbackRef.value
}

/** 2-parameter overload of [useCallback]: refer to docs there. */
@Hook
fun <A, B, R> ComponentScope.useCallback(
    callback: (A, B) -> R,
): (A, B) -> R {
  val callbackRef = useState { MemoizedCallback2(CallbackHolder(callback)) }
  useEffect(callback) {
    callbackRef.value.callbackHolder.callback = callback
    null
  }
  return callbackRef.value
}

/** 3-parameter overload of [useCallback]: refer to docs there. */
@Hook
fun <A, B, C, R> ComponentScope.useCallback(
    callback: (A, B, C) -> R,
): (A, B, C) -> R {
  val callbackRef = useState { MemoizedCallback3(CallbackHolder(callback)) }
  useEffect(callback) {
    callbackRef.value.callbackHolder.callback = callback
    null
  }
  return callbackRef.value
}

/** 4-parameter overload of [useCallback]: refer to docs there. */
@Hook
fun <A, B, C, D, R> ComponentScope.useCallback(
    callback: (A, B, C, D) -> R,
): (A, B, C, D) -> R {
  val callbackRef = useState { MemoizedCallback4(CallbackHolder(callback)) }
  useEffect(callback) {
    callbackRef.value.callbackHolder.callback = callback
    null
  }
  return callbackRef.value
}

/** 5-parameter overload of [useCallback]: refer to docs there. */
@Hook
fun <A, B, C, D, E, R> ComponentScope.useCallback(
    callback: (A, B, C, D, E) -> R,
): (A, B, C, D, E) -> R {
  val callbackRef = useState { MemoizedCallback5(CallbackHolder(callback)) }
  useEffect(callback) {
    callbackRef.value.callbackHolder.callback = callback
    null
  }
  return callbackRef.value
}

/** 6-parameter overload of [useCallback]: refer to docs there. */
@Hook
fun <A, B, C, D, E, F, R> ComponentScope.useCallback(
    callback: (A, B, C, D, E, F) -> R,
): (A, B, C, D, E, F) -> R {
  val callbackRef = useState { MemoizedCallback6(CallbackHolder(callback)) }
  useEffect(callback) {
    callbackRef.value.callbackHolder.callback = callback
    null
  }
  return callbackRef.value
}

/** 7-parameter overload of [useCallback]: refer to docs there. */
@Hook
fun <A, B, C, D, E, F, G, R> ComponentScope.useCallback(
    callback: (A, B, C, D, E, F, G) -> R,
): (A, B, C, D, E, F, G) -> R {
  val callbackRef = useState { MemoizedCallback7(CallbackHolder(callback)) }
  useEffect(callback) {
    callbackRef.value.callbackHolder.callback = callback
    null
  }
  return callbackRef.value
}

// Implementation Notes:
//
// So that we don't have to introduce a bunch of classes to support these callback holders, these
// are all value classes that hold a single CallbackHolder. Their purpose is to behave as a function
// of some type (e.g. (A, B) -> R) such that when they are invoked, they just invoke the latest
// callback that has been set on them.

@JvmInline
private value class MemoizedCallback0<R>(val callbackHolder: CallbackHolder<() -> R>) : () -> R {
  override fun invoke(): R {
    ThreadUtils.assertMainThread()
    return callbackHolder.callback()
  }
}

@JvmInline
private value class MemoizedCallback1<A, R>(val callbackHolder: CallbackHolder<(A) -> R>) :
    (A) -> R {
  override fun invoke(p1: A): R {
    ThreadUtils.assertMainThread()
    return callbackHolder.callback(p1)
  }
}

@JvmInline
private value class MemoizedCallback2<A, B, R>(val callbackHolder: CallbackHolder<(A, B) -> R>) :
    (A, B) -> R {
  override fun invoke(p1: A, p2: B): R {
    ThreadUtils.assertMainThread()
    return callbackHolder.callback(p1, p2)
  }
}

@JvmInline
private value class MemoizedCallback3<A, B, C, R>(
    val callbackHolder: CallbackHolder<(A, B, C) -> R>
) : (A, B, C) -> R {
  override fun invoke(p1: A, p2: B, p3: C): R {
    ThreadUtils.assertMainThread()
    return callbackHolder.callback(p1, p2, p3)
  }
}

@JvmInline
private value class MemoizedCallback4<A, B, C, D, R>(
    val callbackHolder: CallbackHolder<(A, B, C, D) -> R>
) : (A, B, C, D) -> R {
  override fun invoke(p1: A, p2: B, p3: C, p4: D): R {
    ThreadUtils.assertMainThread()
    return callbackHolder.callback(p1, p2, p3, p4)
  }
}

@JvmInline
private value class MemoizedCallback5<A, B, C, D, E, R>(
    val callbackHolder: CallbackHolder<(A, B, C, D, E) -> R>
) : (A, B, C, D, E) -> R {
  override fun invoke(p1: A, p2: B, p3: C, p4: D, p5: E): R {
    ThreadUtils.assertMainThread()
    return callbackHolder.callback(p1, p2, p3, p4, p5)
  }
}

@JvmInline
private value class MemoizedCallback6<A, B, C, D, E, F, R>(
    val callbackHolder: CallbackHolder<(A, B, C, D, E, F) -> R>
) : (A, B, C, D, E, F) -> R {
  override fun invoke(p1: A, p2: B, p3: C, p4: D, p5: E, p6: F): R {
    ThreadUtils.assertMainThread()
    return callbackHolder.callback(p1, p2, p3, p4, p5, p6)
  }
}

@JvmInline
private value class MemoizedCallback7<A, B, C, D, E, F, G, R>(
    val callbackHolder: CallbackHolder<(A, B, C, D, E, F, G) -> R>
) : (A, B, C, D, E, F, G) -> R {
  override fun invoke(p1: A, p2: B, p3: C, p4: D, p5: E, p6: F, p7: G): R {
    ThreadUtils.assertMainThread()
    return callbackHolder.callback(p1, p2, p3, p4, p5, p6, p7)
  }
}

/** A simple holder for a lambda that can be updated to a newer instance on the UI thread. */
private class CallbackHolder<T : Function<*>>(@UiThread internal var callback: T)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.content.Context
import android.view.View
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import com.facebook.rendercore.primitives.utils.hasEquivalentFields

/** Base class for Kotlin Components. */
abstract class KComponent : Component() {
  companion object {

    /** Method that will ensure the KComponent class is loaded. */
    @JvmStatic
    fun preload() =
        object : KComponent() {
          override fun ComponentScope.render(): Component? = null
        }
  }

  final override fun render(
      resolveStateContext: ResolveStateContext,
      c: ComponentContext,
      widthSpec: Int,
      heightSpec: Int
  ): RenderResult {
    val componentScope = ComponentScope(c, resolveStateContext)
    val componentResult = componentScope.render()
    componentScope.cleanUp()
    return RenderResult(
        componentResult, componentScope.transitions, componentScope.useEffectEntries)
  }

  abstract fun ComponentScope.render(): Component?

  /**
   * Compare this component to a different one to check if they are equivalent. This is used to be
   * able to skip rendering a component again.
   */
  final override fun isEquivalentProps(
      other: Component?,
      shouldCompareCommonProps: Boolean
  ): Boolean {
    if (this === other) {
      return true
    }
    if (other == null || javaClass != other.javaClass) {
      return false
    }
    if (id == other.id) {
      return true
    }
    if (!hasEquivalentFields(this, other)) {
      return false
    }

    return true
  }

  // All other Component lifecycle methods are made final and no-op here as they shouldn't be
  // overriden.

  final override fun canMeasure() = false

  final override fun canResolve() = false

  internal final override fun getCommonDynamicProps() = super.getCommonDynamicProps()

  final override fun getMountType() = super.getMountType()

  final override fun getSimpleName(): String = super.getSimpleName()

  internal final override fun hasCommonDynamicProps() = super.hasCommonDynamicProps()

  final override fun isPureRender() = false

  final override fun makeShallowCopy() = super.makeShallowCopy()

  final override fun onCreateMountContent(context: Context) = super.onCreateMountContent(context)

  final override fun onCreateTransition(c: ComponentContext) = super.onCreateTransition(c)

  final override fun onLoadStyle(c: ComponentContext) = super.onLoadStyle(c)

  final override fun onPopulateAccessibilityNode(
      c: ComponentContext,
      host: View,
      accessibilityNode: AccessibilityNodeInfoCompat,
      interStagePropsContainer: InterStagePropsContainer?
  ) = super.onPopulateAccessibilityNode(c, host, accessibilityNode, interStagePropsContainer)

  final override fun onPopulateExtraAccessibilityNode(
      c: ComponentContext,
      accessibilityNode: AccessibilityNodeInfoCompat,
      extraNodeIndex: Int,
      componentBoundsX: Int,
      componentBoundsY: Int,
      interStagePropsContainer: InterStagePropsContainer?
  ) =
      super.onPopulateExtraAccessibilityNode(
          c,
          accessibilityNode,
          extraNodeIndex,
          componentBoundsX,
          componentBoundsY,
          interStagePropsContainer)

  final override fun resolve(
      resolveStateContext: ResolveStateContext,
      c: ComponentContext
  ): LithoNode? = super.resolve(resolveStateContext, c)

  final override fun shouldUpdate(
      previous: Component,
      prevStateContainer: StateContainer?,
      next: Component,
      nextStateContainer: StateContainer?
  ) = super.shouldUpdate(previous, prevStateContainer, next, nextStateContainer)
}

/**
 * Sets a manual key on the given Component returned in the lambda, e.g.
 *
 * ```
 * key("my_key") { Text(...) }
 * ```
 */
inline fun key(key: String, componentLambda: () -> Component): Component {
  val component = componentLambda()
  setKeyForComponentInternal(component, key)
  return component
}

/**
 * Sets a handle on the given Component returned in the lambda, e.g.
 *
 * ```
 * handle(Handle()) { Text(...) }
 * ```
 */
inline fun handle(handle: Handle, componentLambda: () -> Component): Component {
  val component = componentLambda()
  setHandleForComponentInternal(component, handle)
  return component
}

/**
 * This is extracted out since we don't want to expose Component.setKey in the public API and will
 * hopefully change this implementation in the future.
 */
@PublishedApi
internal fun setKeyForComponentInternal(component: Component, key: String) {
  component.key = key
}

/**
 * This is extracted out since we don't want to expose Component.handle in the public API and will
 * hopefully change this implementation in the future.
 */
@PublishedApi
internal fun setHandleForComponentInternal(component: Component, handle: Handle) {
  component.handle = handle
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import androidx.annotation.UiThread
import com.facebook.infer.annotation.ThreadConfined
import com.facebook.litho.annotations.Hook

/**
 * Registers a callback to perform side-effects when this Component is attached/detached from the
 * tree.
 *
 * The [onAttach] callback will be invoked when the owning Component first becomes part of a
 * committed layout on the ComponentTree on the main thread. It should return a [CleanupFunc]
 * callback to be invoked when the owning Component is detached, e.g. the ComponentTree is released
 * or the Component is no longer part of the tree.
 *
 * Additionally, any time the list of [deps] changes between layouts, the existing onAttach's
 * cleanup callback will be invoked, and the new onAttach callback will be invoked.
 *
 * [deps] should contain any props or state your attach/cleanup callbacks use. For example, if
 * you're using useEffect to subscribe to a data store based on an id you get as a prop, [deps]
 * should include that id so that if the id changes, we will unsubscribe from the old id and
 * subscribe with the new id.
 */
@Hook
fun ComponentScope.useEffect(vararg deps: Any?, onAttach: () -> CleanupFunc?) {
  val entries = useEffectEntries ?: ArrayList()
  useEffectEntries = entries
  val uniqueId = "${context.globalKey}:${entries.size}"
  entries.add(UseEffectAttachable(uniqueId, deps, onAttach))
}

/**
 * Registers a callback to perform side-effects when this Component is attached/detached from the
 * tree.
 *
 * It is an error to call [ComponentScope.useEffect] without deps parameter.
 */
// This deprecated-error function shadows the varargs overload so that the varargs version is not
// used without deps parameters.
@Deprecated(USE_EFFECT_NO_DEPS_ERROR, level = DeprecationLevel.ERROR)
@Suppress("unused", "UNUSED_PARAMETER")
@Hook
fun ComponentScope.useEffect(onAttach: () -> CleanupFunc?): Unit =
    throw IllegalStateException(USE_EFFECT_NO_DEPS_ERROR)

/**
 * Defines a cleanup function to be invoked when the owning Component is detached or the deps in
 * [useEffect] change.
 */
inline fun onCleanup(crossinline cleanupFunc: () -> Unit) = CleanupFunc { cleanupFunc() }

/**
 * Interface for the [onCleanup] function: use [onCleanup] to define the cleanup function for your
 * useEffect hook.
 */
fun interface CleanupFunc {
  fun onCleanup()
}

private class UseEffectAttachable(
    private val id: String,
    private val deps: Array<out Any?>?,
    private val attachCallback: () -> CleanupFunc?
) : Attachable {

  @ThreadConfined(ThreadConfined.UI) var detachHandler: CleanupFunc? = null
  @ThreadConfined(ThreadConfined.UI) var isAttached: Boolean = false

  override val uniqueId: String = id

  override fun useLegacyUpdateBehavior(): Boolean = false

  @UiThread
  override fun attach() {
    check(!isAttached) { "Attach should only be called when detached!" }
    detachHandler = attachCallback()
    isAttached = true
  }

  @UiThread
  override fun detach() {
    check(isAttached) { "Detach should only be called when attached!" }
    detachHandler?.onCleanup()
    isAttached = false
  }

  @UiThread
  override fun shouldUpdate(nextEntry: Attachable) =
      !deps.contentDeepEquals((nextEntry as UseEffectAttachable).deps)
}

private const val USE_EFFECT_NO_DEPS_ERROR =
    "useEffect must provide 'deps' parameter that determines whether the existing 'onAttach' cleanup callback will be invoked, and the new 'onAttach' callback will be invoked"


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import com.facebook.litho.annotations.Hook
import java.lang.Exception

/**
 * Registers a callback to perform error handling for exceptions that might happen in child
 * components down in the tree.
 *
 * The hook parameter will receive all exceptions that are raised in the lifecycle methods of
 * components sitting underneath the error boundary in the tree, regardless of whether those are
 * other KComponents or Litho Specs.
 *
 * The KComponent can leverage the useState hook to update the state with the exception that was
 * caught, and trigger a render pass with the new state value in order to replace the crashing
 * component with an error component, or not display it at all.
 */
@Hook
fun ComponentScope.useErrorBoundary(onError: (exception: Exception) -> Unit) {
  val errorEventHandler = eventHandler<ErrorEvent> { onError(it.exception) }
  context.scopedComponentInfo.setErrorEventHandlerDuringRender(errorEventHandler)
}