/*
 * 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.collection.SparseArrayCompat
import java.util.HashMap
import kotlin.jvm.JvmField

/**
 * Read & write layout result cache used during render phase for caching measured results that
 * happen during component-measure. This cache can be accessed via components or litho-nodes.
 */
class MeasuredResultCache
/**
 * Create a new MeasureResultCache with a delegate cache. When reading caches, if they are not found
 * in this cache instance, the delegate cache is checked.
 */
@JvmOverloads
constructor(private val delegateCache: MeasuredResultCache? = null) {
  private val componentIdToNodeCache = SparseArrayCompat<LithoNode>()
  private val nodeToResultCache: MutableMap<LithoNode, LithoLayoutResult> = HashMap()
  private var isFrozen = false

  /**
   * Marks this cache as frozen, meaning it is illegal to write new values into it, and should be
   * used as read-only. Attempting to write to a frozen cache will produce an illegal state
   * exception.
   */
  fun freezeCache() {
    isFrozen = true
  }

  /**
   * Add a cached result to the cache.
   *
   * @param component The component from which the result was generated
   * @param node The node generated from the component
   * @param layoutResult The layout result
   */
  fun addCachedResult(component: Component, node: LithoNode, layoutResult: LithoLayoutResult) {
    addCachedResult(component.id, node, layoutResult)
  }

  /**
   * Add a cached result to the cache.
   *
   * @param componentId The component ID from which the result was generated
   * @param node The node generated from the component
   * @param layoutResult The layout result
   */
  fun addCachedResult(componentId: Int, node: LithoNode, layoutResult: LithoLayoutResult) {
    check(!isFrozen) { "Cannot write into a frozen cache." }
    componentIdToNodeCache.put(componentId, node)
    nodeToResultCache[node] = layoutResult
  }

  /** Return true if there exists a cached layout result for the given component. */
  fun hasCachedNode(component: Component): Boolean = hasCachedNode(component.id)

  /** Return true if there exists a cached layout result for the given component ID. */
  fun hasCachedNode(componentId: Int): Boolean =
      componentIdToNodeCache.containsKey(componentId) ||
          delegateCache?.hasCachedNode(componentId) == true

  /** Return true if there exists a cached layout result for the given LithoNode. */
  fun hasCachedNode(node: LithoNode): Boolean =
      nodeToResultCache.containsKey(node) || delegateCache?.hasCachedNode(node) == true

  /** Returns the cached LithoNode from a given component. */
  fun getCachedNode(component: Component): LithoNode? = getCachedNode(component.id)

  /** Returns the cached LithoNode from a given component ID. */
  fun getCachedNode(componentId: Int): LithoNode? {
    val currentCacheNode = componentIdToNodeCache[componentId]
    return currentCacheNode ?: delegateCache?.getCachedNode(componentId)
  }

  /** Returns the cached layout result for the given component, or null if it does not exist. */
  fun getCachedResult(component: Component): LithoLayoutResult? = getCachedResult(component.id)

  /** Returns the cached layout result for the given component ID, or null if it does not exist. */
  fun getCachedResult(componentId: Int): LithoLayoutResult? {
    val node =
        componentIdToNodeCache[componentId] ?: return delegateCache?.getCachedResult(componentId)
    return getCachedResult(node)
  }

  /** Returns the cached layout result for the given node, or null if it does not exist. */
  fun getCachedResult(node: LithoNode): LithoLayoutResult? {
    val currentLayoutResult = nodeToResultCache[node]
    return currentLayoutResult ?: delegateCache?.getCachedResult(node)
  }

  /** Remove the cached layout result for the given Node. */
  fun removeCachedResult(node: LithoNode) {
    // Should we also check for freezing here?
    nodeToResultCache.remove(node)
    delegateCache?.removeCachedResult(node)
  }

  /** Cleares the cache generated for the given component. */
  fun clearCache(component: Component) {
    clearCache(component.id)
  }

  /** Cleares the cache generated for the given component ID. */
  fun clearCache(componentId: Int) {
    check(!isFrozen) { "Cannot delete from a frozen cache" }
    val node = componentIdToNodeCache[componentId] ?: return
    nodeToResultCache.remove(node)
    componentIdToNodeCache.remove(componentId)
  }

  companion object {
    @JvmField val EMPTY = MeasuredResultCache().apply { freezeCache() }
  }
}


/*
 * 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

object MountHelper {

  /**
   * Can be used in the context of a ViewPager which binds views before ahead of displaying them and
   * requesting layout, if we want mount to happen ahead of time too.
   */
  @JvmStatic
  fun requestMount(
      componentTree: ComponentTree,
      visibleRect: Rect?,
      processVisibilityOutputs: Boolean
  ) {
    val lithoView = componentTree.lithoView
    lithoView?.mountComponent(visibleRect, processVisibilityOutputs)
  }

  private val emptyRect = Rect()

  @JvmStatic
  fun requestMount(lithoView: LithoView, processVisibilityOutputs: Boolean) {
    lithoView.mountComponent(emptyRect, processVisibilityOutputs)
  }
}


/*
 * 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.graphics.drawable.Drawable
import android.util.SparseArray
import android.view.View
import androidx.annotation.IntDef
import com.facebook.litho.ComponentHostUtils.maybeSetDrawableState
import com.facebook.litho.ComponentsSystrace.beginSection
import com.facebook.litho.ComponentsSystrace.endSection
import com.facebook.litho.ComponentsSystrace.isTracing
import com.facebook.litho.LithoLayoutData.Companion.getInterStageProps
import com.facebook.litho.LithoLayoutData.Companion.verifyAndGetLithoLayoutData
import com.facebook.rendercore.ContentAllocator
import com.facebook.rendercore.MountItemsPool.ItemPool
import com.facebook.rendercore.RenderTreeNode
import com.facebook.rendercore.RenderUnit

/** This [RenderUnit] encapsulates a Litho output to be mounted using Render Core. */
class MountSpecLithoRenderUnit
private constructor(
    id: Long,
    component: Component,
    commonDynamicProps: SparseArray<DynamicValue<Any?>>?,
    nodeInfo: NodeInfo?,
    flags: Int,
    importantForAccessibility: Int,
    @UpdateState val updateState: Int,
    context: ComponentContext?,
) :
    LithoRenderUnit(
        id,
        component,
        commonDynamicProps,
        nodeInfo,
        flags,
        importantForAccessibility,
        getRenderType(component),
        context,
    ),
    ContentAllocator<Any?> {

  private var isShouldUpdateCachingEnabled = false
  private var isShouldUpdateResultCached = false
  private var isCachedShouldUpdateResult = false

  init {
    addOptionalMountBinders(DelegateBinder.createDelegateBinder(this, mountBinder))
    addAttachBinder(DelegateBinder.createDelegateBinder(this, binderBinder))
  }

  override fun onStartUpdateRenderUnit() {
    isShouldUpdateCachingEnabled = true
  }

  override fun onEndUpdateRenderUnit() {
    isShouldUpdateCachingEnabled = false
    isShouldUpdateResultCached = false
  }

  override fun createRecyclingPool(): ItemPool? {
    return try {
      if (component is SpecGeneratedComponent) {
        component.createRecyclingPool()
      } else {
        null
      }
    } catch (e: Exception) {
      if (componentContext != null) {
        ComponentUtils.handle(componentContext, e)
      }
      null
    }
  }

  override fun createContent(c: Context): Any {
    return component.createMountContent(c)
  }

  override fun getPoolableContentType(): Any {
    return renderContentType
  }

  override fun isRecyclingDisabled(): Boolean {
    return (component is SpecGeneratedComponent && component.isRecyclingDisabled)
  }

  override fun getDescription(): String {
    return component.simpleName
  }

  override fun getContentAllocator(): ContentAllocator<Any?> {
    return this
  }

  override fun getRenderContentType(): Class<*> {
    return component.javaClass
  }

  companion object {

    const val STATE_UNKNOWN = 0
    const val STATE_UPDATED = 1
    const val STATE_DIRTY = 2

    val mountBinder: Binder<MountSpecLithoRenderUnit, Any, Any?> =
        object : Binder<MountSpecLithoRenderUnit, Any, Any?> {
          override fun shouldUpdate(
              current: MountSpecLithoRenderUnit,
              next: MountSpecLithoRenderUnit,
              currentData: Any?,
              nextData: Any?,
          ): Boolean {
            return if (next.component is HostComponent) {
              false
            } else {
              shouldUpdateMountItem(current, next, currentData, nextData)
            }
          }

          override fun bind(
              context: Context,
              content: Any,
              unit: MountSpecLithoRenderUnit,
              layoutData: Any?,
          ): Any? {
            val component = unit.component
            (component as SpecGeneratedComponent).mount(
                getComponentContext(unit),
                content,
                getInterStageProps(layoutData),
            )
            return null
          }

          override fun unbind(
              context: Context,
              content: Any,
              unit: MountSpecLithoRenderUnit,
              layoutData: Any?,
              bindData: Any?
          ) {
            (unit.component as SpecGeneratedComponent).unmount(
                getComponentContext(unit),
                content,
                getInterStageProps(layoutData),
            )
          }
        }

    val binderBinder: Binder<MountSpecLithoRenderUnit, Any, Any?> =
        object : Binder<MountSpecLithoRenderUnit, Any, Any?> {
          override fun shouldUpdate(
              current: MountSpecLithoRenderUnit,
              next: MountSpecLithoRenderUnit,
              c: Any?,
              n: Any?,
          ): Boolean {
            return true
          }

          override fun bind(
              context: Context,
              content: Any,
              unit: MountSpecLithoRenderUnit,
              layoutData: Any?,
          ): Any? {
            if (content is Drawable) {
              if (content.callback is View) {
                val view = content.callback as View?
                maybeSetDrawableState(view!!, content, unit.flags)
              }
            }
            (unit.component as SpecGeneratedComponent).bind(
                getComponentContext(unit),
                content,
                getInterStageProps(layoutData),
            )
            return null
          }

          override fun unbind(
              context: Context,
              content: Any,
              unit: MountSpecLithoRenderUnit,
              layoutData: Any?,
              bindData: Any?,
          ) {
            (unit.component as SpecGeneratedComponent).unbind(
                getComponentContext(unit),
                content,
                getInterStageProps(layoutData),
            )
          }
        }

    @JvmStatic
    fun create(
        id: Long,
        component: Component,
        commonDynamicProps: SparseArray<DynamicValue<Any?>>?,
        context: ComponentContext?,
        nodeInfo: NodeInfo?,
        flags: Int,
        importantForAccessibility: Int,
        @UpdateState updateState: Int,
    ): MountSpecLithoRenderUnit {
      return MountSpecLithoRenderUnit(
          id,
          component,
          commonDynamicProps,
          nodeInfo,
          flags,
          importantForAccessibility,
          updateState,
          context,
      )
    }

    @JvmStatic
    fun shouldUpdateMountItem(
        current: MountSpecLithoRenderUnit,
        next: MountSpecLithoRenderUnit,
        currentData: Any?,
        nextData: Any?,
    ): Boolean {
      if (current.isShouldUpdateCachingEnabled && current.isShouldUpdateResultCached) {
        return current.isCachedShouldUpdateResult
      }
      val currentLithoData = verifyAndGetLithoLayoutData(currentData)
      val nextLithoData = verifyAndGetLithoLayoutData(nextData)
      val nextContext = getComponentContext(next)
      val previousIdFromNextOutput = nextLithoData.previousLayoutStateId
      val currentContext = getComponentContext(current)
      val idFromCurrentOutput = currentLithoData.currentLayoutStateId
      val updateValueFromLayoutOutput = previousIdFromNextOutput == idFromCurrentOutput
      val result =
          shouldUpdateMountItem(
              next,
              nextData as LithoLayoutData?,
              nextContext,
              current,
              currentData as LithoLayoutData?,
              currentContext,
              updateValueFromLayoutOutput,
          )
      if (current.isShouldUpdateCachingEnabled && !current.isShouldUpdateResultCached) {
        current.isCachedShouldUpdateResult = result
        current.isShouldUpdateResultCached = true
      }
      return result
    }

    private fun getRenderType(component: Component?): RenderType {
      requireNotNull(component) { "Null output used for LithoRenderUnit." }
      return if (component.mountType == Component.MountType.DRAWABLE) {
        RenderType.DRAWABLE
      } else {
        RenderType.VIEW
      }
    }

    private fun shouldUpdateMountItem(
        nextRenderUnit: MountSpecLithoRenderUnit,
        nextLayoutData: LithoLayoutData?,
        nextContext: ComponentContext?,
        currentRenderUnit: MountSpecLithoRenderUnit,
        currentLayoutData: LithoLayoutData?,
        currentContext: ComponentContext?,
        useUpdateValueFromLayoutOutput: Boolean,
    ): Boolean {
      @UpdateState val updateState = nextRenderUnit.updateState
      val currentComponent = currentRenderUnit.component
      val nextComponent = nextRenderUnit.component

      // If the two components have different sizes and the mounted content depends on the size we
      // just return true immediately.
      if (nextComponent is SpecGeneratedComponent &&
          nextComponent.isMountSizeDependent &&
          !sameSize(
              checkNotNull(nextLayoutData),
              checkNotNull(currentLayoutData),
          )) {
        return true
      }
      if (useUpdateValueFromLayoutOutput) {
        if (updateState == STATE_UPDATED) {
          return false
        } else if (updateState == STATE_DIRTY) {
          return true
        }
      }
      return shouldUpdate(currentComponent, currentContext, nextComponent, nextContext)
    }

    private fun shouldUpdate(
        currentComponent: Component,
        currentScopedContext: ComponentContext?,
        nextComponent: Component,
        nextScopedContext: ComponentContext?,
    ): Boolean {
      val isTracing = isTracing
      return try {
        if (isTracing) {
          beginSection("MountState.shouldUpdate")
        }
        currentComponent.shouldComponentUpdate(
            currentScopedContext,
            currentComponent,
            nextScopedContext,
            nextComponent,
        )
      } catch (e: Exception) {
        ComponentUtils.handle(nextScopedContext, e)
        true
      } finally {
        if (isTracing) {
          endSection()
        }
      }
    }

    private fun sameSize(next: LithoLayoutData, current: LithoLayoutData): Boolean {
      return next.width == current.width && next.height == current.height
    }

    @JvmStatic
    @UpdateState
    fun getUpdateState(node: RenderTreeNode): Int {
      return (node.renderUnit as MountSpecLithoRenderUnit).updateState
    }
  }

  @IntDef(STATE_UPDATED, STATE_UNKNOWN, STATE_DIRTY)
  @Retention(AnnotationRetention.SOURCE)
  annotation class UpdateState
}


/*
 * 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.YogaEdge
import com.facebook.yoga.YogaNode

class NestedTreeYogaLayoutProps(node: YogaNode) : YogaLayoutProps(node) {

  var borderWidth: IntArray? = null
    private set

  var padding: Edges? = null
    private set

  var isPaddingPercentage: BooleanArray? = null
    private set

  override fun paddingPx(edge: YogaEdge, padding: Int) {
    setPadding(edge, padding.toFloat())
    setIsPaddingPercentage(edge, false)
  }

  override fun paddingPercent(edge: YogaEdge, percent: Float) {
    setPadding(edge, percent)
    setIsPaddingPercentage(edge, true)
  }

  override fun setBorderWidth(edge: YogaEdge, borderWidth: Float) {
    if (this.borderWidth == null) {
      this.borderWidth = IntArray(Border.EDGE_COUNT)
    }
    Border.setEdgeValue(this.borderWidth, edge, borderWidth.toInt())
  }

  private fun setPadding(edge: YogaEdge, width: Float) {
    if (padding == null) {
      padding = Edges()
    }
    padding?.set(edge, width)
  }

  private fun setIsPaddingPercentage(edge: YogaEdge, isPercentage: Boolean) {
    if (isPaddingPercentage == null && isPercentage) {
      isPaddingPercentage = BooleanArray(YogaEdge.ALL.intValue() + 1)
    }

    if (isPaddingPercentage != null) {
      isPaddingPercentage?.set(edge.intValue(), isPercentage)
    }
  }
}


/*
 * 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.config.ComponentsConfiguration
import com.facebook.litho.yoga.LithoYogaFactory
import com.facebook.yoga.YogaConfig
import com.facebook.yoga.YogaExperimentalFeature
import com.facebook.yoga.YogaNode
import kotlin.jvm.JvmField

/** A helper class that defines a configurable sizes for Pooling. */
object NodeConfig {

  /**
   * Custom factory for Yoga nodes. Used to enable direct byte buffers to set Yoga style properties
   * (rather than JNI)
   */
  @JvmField @Volatile var yogaNodeFactory: InternalYogaNodeFactory? = null

  /** Allows access to the internal YogaConfig instance */
  @get:JvmStatic
  val yogaConfig: YogaConfig =
      LithoYogaFactory.createYogaConfig().apply {
        if (ComponentsConfiguration.enableFixForJniLocalRefOverflow) {
          setExperimentalFeatureEnabled(YogaExperimentalFeature.FIX_JNILOCAL_REF_OVERFLOWS, true)
        }
      }

  @JvmStatic
  fun createYogaNode(): YogaNode {
    return yogaNodeFactory?.create(yogaConfig) ?: LithoYogaFactory.createYogaNode(yogaConfig)
  }

  /**
   * Toggles a Yoga setting on whether to print debug logs to adb.
   *
   * @param enable whether to print logs or not
   */
  @JvmStatic
  @Synchronized
  fun setPrintYogaDebugLogs(enable: Boolean) {
    yogaConfig.setPrintTreeFlag(enable)
  }

  fun interface InternalYogaNodeFactory {
    fun create(config: YogaConfig): YogaNode
  }
}


/*
 * 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.rendercore.primitives.utils.equals
import com.facebook.rendercore.primitives.utils.isEquivalentTo

object NodeInfoUtils {
  @JvmStatic
  fun isEquivalentTo(x: NodeInfo?, y: NodeInfo?): Boolean {
    if (x === y) {
      return true
    }
    if (x == null || y == null) {
      return false
    }
    if (x.flags != y.flags) {
      return false
    }
    if (!equals(x.accessibilityRole, y.accessibilityRole)) {
      return false
    }
    if (x.alpha != y.alpha) {
      return false
    }
    if (!isEquivalentTo(x.clickHandler, y.clickHandler)) {
      return false
    }
    if (x.clipToOutline != y.clipToOutline) {
      return false
    }
    if (x.clipChildren != y.clipChildren) {
      return false
    }
    if (!equals(x.contentDescription, y.contentDescription)) {
      return false
    }
    if (!isEquivalentTo(
        x.dispatchPopulateAccessibilityEventHandler, y.dispatchPopulateAccessibilityEventHandler)) {
      return false
    }
    if (x.enabledState != y.enabledState) {
      return false
    }
    if (!isEquivalentTo(x.focusChangeHandler, y.focusChangeHandler)) {
      return false
    }
    if (x.focusState != y.focusState) {
      return false
    }
    if (!isEquivalentTo(x.interceptTouchHandler, y.interceptTouchHandler)) {
      return false
    }
    if (!isEquivalentTo(x.longClickHandler, y.longClickHandler)) {
      return false
    }
    if (!isEquivalentTo(
        x.onInitializeAccessibilityEventHandler, y.onInitializeAccessibilityEventHandler)) {
      return false
    }
    if (!isEquivalentTo(
        x.onInitializeAccessibilityNodeInfoHandler, y.onInitializeAccessibilityNodeInfoHandler)) {
      return false
    }
    if (!isEquivalentTo(
        x.onPopulateAccessibilityEventHandler, y.onPopulateAccessibilityEventHandler)) {
      return false
    }
    if (!isEquivalentTo(
        x.onPopulateAccessibilityNodeHandler, y.onPopulateAccessibilityNodeHandler)) {
      return false
    }
    if (!isEquivalentTo(
        x.onRequestSendAccessibilityEventHandler, y.onRequestSendAccessibilityEventHandler)) {
      return false
    }
    if (!equals(x.outlineProvider, y.outlineProvider)) {
      return false
    }
    if (!isEquivalentTo(x.performAccessibilityActionHandler, y.performAccessibilityActionHandler)) {
      return false
    }
    if (x.rotation != y.rotation) {
      return false
    }
    if (x.scale != y.scale) {
      return false
    }
    if (x.selectedState != y.selectedState) {
      return false
    }
    if (!isEquivalentTo(x.sendAccessibilityEventHandler, y.sendAccessibilityEventHandler)) {
      return false
    }
    if (!isEquivalentTo(
        x.sendAccessibilityEventUncheckedHandler, y.sendAccessibilityEventUncheckedHandler)) {
      return false
    }
    if (x.shadowElevation != y.shadowElevation) {
      return false
    }
    if (x.ambientShadowColor != y.ambientShadowColor) {
      return false
    }
    if (x.spotShadowColor != y.spotShadowColor) {
      return false
    }
    if (!isEquivalentTo(x.touchHandler, y.touchHandler)) {
      return false
    }
    if (!equals(x.viewTag, y.viewTag)) {
      return false
    }

    if (!equals(x.viewId, y.viewId)) {
      return false
    }

    return equals(x.viewTags, y.viewTags)
  }
}


/*
 * 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 kotlin.jvm.JvmField

class NoOpEventHandler<E> private constructor() : EventHandler<E>(NoOpHasEventDispatcher(), ID) {

  private class NoOpHasEventDispatcher : HasEventDispatcher {
    override fun getEventDispatcher(): EventDispatcher = EventDispatcher { _, _ ->
      /* DO NOTHING HERE
      The exception should be thrown when the handler is create, much before any events  get dispatched. */
      null
    }
  }

  companion object {
    private const val ID = -1

    @JvmField @VisibleForTesting val instance: NoOpEventHandler<*> = NoOpEventHandler<Any?>()

    @JvmStatic
    fun <E> getNoOpEventHandler(): NoOpEventHandler<E> = (instance as NoOpEventHandler<E>)
  }
}


/*
 * 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.YogaNode
import java.lang.UnsupportedOperationException

/**
 * This [LithoNode] represents a component that renders to `null`. This is required to support
 * reconciliation of state, and transitions on a component that conditionally renders to null.
 */
class NullNode : LithoNode() {

  override fun createYogaNodeWriter(): YogaLayoutProps? = null

  public override fun createLayoutResult(
      node: YogaNode,
      layoutProps: YogaLayoutProps?
  ): LithoLayoutResult {
    throw UnsupportedOperationException("NullNode must not be used for layout")
  }
}


/*
 * 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.ReturnsOwnership
import com.facebook.infer.annotation.ThreadSafe

/** Type for parameters that are logical outputs. */
open class Output<T> {

  private var t: T? = null

  /** Assumed thread-safe because the one write is before all the reads */
  @ThreadSafe(enableChecks = false)
  fun set(t: T?) {
    this.t = t
  }

  @ReturnsOwnership fun get(): T? = 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

/** A object pool that has debug info for display in tools like Stetho. */
interface PoolWithDebugInfo {

  /** @return a human-readable name for the pool. */
  val name: String

  /** @return the max number of objects this pool will hold. */
  val maxSize: Int

  /** @return the number of objects currently in the pool. */
  val currentSize: 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

/**
 * Interface defining a result calculated from a [TreeFuture] that could be interrupted, and
 * therefore be partial (and resumed on the UI thread).
 */
interface PotentiallyPartialResult {
  /** Return true if this result calculated from a [TreeFuture] is partial. */
  val isPartialResult: 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.
 */

@file:JvmName("PreloadingUtils")

package com.facebook.litho

import android.os.Handler
import com.facebook.rendercore.MountItemsPool
import com.facebook.rendercore.MountState
import com.facebook.rendercore.RunnableHandler
import com.facebook.rendercore.primitives.Allocator
import com.facebook.rendercore.primitives.AspectRatioLayoutBehavior
import com.facebook.rendercore.primitives.BindScope
import com.facebook.rendercore.primitives.DrawableAllocator
import com.facebook.rendercore.primitives.EqualDimensionsLayoutBehavior
import com.facebook.rendercore.primitives.Equivalence
import com.facebook.rendercore.primitives.ExactSizeConstraintsLayoutBehavior
import com.facebook.rendercore.primitives.FillLayoutBehavior
import com.facebook.rendercore.primitives.FixedSizeLayoutBehavior
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.LayoutScope
import com.facebook.rendercore.primitives.MountBehavior
import com.facebook.rendercore.primitives.MountConfigurationScope
import com.facebook.rendercore.primitives.Primitive
import com.facebook.rendercore.primitives.PrimitiveLayoutResult
import com.facebook.rendercore.primitives.PrimitiveRenderUnit
import com.facebook.rendercore.primitives.ViewAllocator

private val preloadClassHandler: Handler =
    RunnableHandler.DefaultHandler(ComponentTree.getDefaultLayoutThreadLooper())

/**
 * Utility for preloading classes that should be loaded early but may not be accessible outside of
 * Litho.
 */
fun preloadLithoClasses(additionalClasses: (() -> Set<Class<*>>)? = null) {
  val r = Runnable {
    val classes: MutableSet<Class<*>> =
        listOf(
                SpecGeneratedComponent::class.java,
                Component::class.java,
                EventDispatcher::class.java,
                HasEventDispatcher::class.java,
                Equivalence::class.java,
                EventTriggerTarget::class.java,
                HasEventTrigger::class.java,
                LithoYogaMeasureFunction::class.java,
                Size::class.java,
                ComponentTree::class.java,
                LithoLifecycleListener::class.java,
                DefaultErrorEventHandler::class.java,
                ErrorEventHandler::class.java,
                EventHandler::class.java,
                LayoutState::class.java,
                HostComponent::class.java,
                DrawableComponent::class.java,
                StateContainer::class.java,
                CommonProps::class.java,
                LayoutProps::class.java,
                NodeInfo::class.java,
                DelegatingEventHandler::class.java,
                StateValue::class.java,
                Output::class.java,
                LithoLifecycleProviderDelegate::class.java,
                LithoLifecycleProvider::class.java,
                Diff::class.java,
                LayoutThreadPoolConfigurationImpl::class.java,
                WorkingRange::class.java,
                LithoNode::class.java,
                LithoLayoutResult::class.java,
                ComponentLayout::class.java,
                NodeConfig::class.java,
                LithoRenderContext::class.java,
                DefaultDiffNode::class.java,
                LithoRenderUnit::class.java,
                LithoLayoutData::class.java,
                Column::class.java,
                Edges::class.java,
                Row::class.java,
                InterStagePropsContainer::class.java,
                Border::class.java,
                FastMath::class.java,
                LithoView::class.java,
                ComponentHost::class.java,
                MountState::class.java,
                DynamicPropsManager::class.java,
                AnimationsDebug::class.java,
                Wrapper::class.java,
                MountContentPool::class.java,
                DefaultMountContentPool::class.java,
                LayoutSpecAttachable::class.java,
                Attachable::class.java,
                Touchable::class.java,
                ImageContent::class.java,
                AttachDetachHandler::class.java,
                DisabledMountContentPool::class.java,
                ComponentClickListener::class.java,
                CommonUtils::class.java,
                MatrixDrawable::class.java,
                VisibleEvent::class.java,
                FullImpressionVisibleEvent::class.java,
                HasLithoViewChildren::class.java,
                ComponentLongClickListener::class.java,
                FocusedVisibleEvent::class.java,
                RenderState::class.java,
                ViewAttributes::class.java,
                LithoViewAttributesExtension::class.java,
                DynamicPropsExtension::class.java,
                VisibilityChangedEvent::class.java,
                InvisibleEvent::class.java,
                TouchEvent::class.java,
                ComponentsSystrace::class.java,
                WorkContinuationInstrumenter::class.java,
                EventDispatcherInstrumenter::class.java,
                ComponentContext::class.java,
                ResourceCache::class.java,
                LruResourceCache::class.java,
                ResourceResolver::class.java,
                DynamicValue::class.java,
                InitialStateContainer::class.java,
                RenderUnitIdGenerator::class.java,
                EventHandlersController::class.java,
                EventTriggersContainer::class.java,
                WorkingRangeStatusHandler::class.java,
                StateHandler::class.java,
                IncrementalMountHelper::class.java,
                DefaultComponentsSystrace::class.java,
                LayoutStateContext::class.java,
                AccessibilityUtils::class.java,
                Layout::class.java,
                TreeProps::class.java,
                ThreadUtils::class.java,
                RenderResult::class.java,
                ComponentKeyUtils::class.java,
                ThreadPoolLayoutHandler::class.java,
                LayoutThreadPoolExecutor::class.java,
                LayoutThreadFactory::class.java,
                InternalNodeUtils::class.java,
                SizeSpec::class.java,
                TransitionId::class.java,
                OutputUnitsAffinityGroup::class.java,
                LithoAnimtableItem::class.java,
                WorkingRangeContainer::class.java,
                MeasureComparisonUtils::class.java,
                Handle::class.java,
                DrawableMatrix::class.java,
                LithoMountData::class.java,
                TransitionsExtension::class.java,
                ContextUtils::class.java,
                ComponentUtils::class.java,
                MountItemsPool::class.java,
                LogTreePopulator::class.java,
                TouchExpansionDelegate::class.java,
                ComponentTouchListener::class.java,
                EventTrigger::class.java,
                Transition::class.java,
                ComponentHostUtils::class.java,
                DelayTransitionSet::class.java,
                ParallelTransitionSet::class.java,
                TransitionManager::class.java,
                TransitionIdMap::class.java,
                ThreadTracingRunnable::class.java,
                TransitionSet::class.java,
                ReThrownException::class.java,
                TextContent::class.java,
                LithoGestureDetector::class.java,
                LithoHostListenerCoordinator::class.java,
                EndToEndTestingExtension::class.java,
                ComponentAccessibilityDelegate::class.java,
                ScopedComponentInfo::class.java,
                KStateContainer::class.java,
                EventDispatcherUtils::class.java,
                LithoViewTestHelper::class.java,
                DebugComponent::class.java,
                UnfocusedVisibleEvent::class.java,
                LithoTooltip::class.java,
                ComponentsReporter::class.java,
                InterceptTouchEvent::class.java,
                ComponentFocusChangeListener::class.java,
                FocusChangedEvent::class.java,
                ClickEvent::class.java,
                ComponentTreeDumpingHelper::class.java,
                BoundaryWorkingRange::class.java,
                RenderCompleteEvent::class.java,
                InterceptTouchEvent::class.java,
                Primitive::class.java,
                MountConfigurationScope::class.java,
                BindScope::class.java,
                MountBehavior::class.java,
                PrimitiveRenderUnit::class.java,
                LayoutScope::class.java,
                LayoutBehavior::class.java,
                PrimitiveLayoutResult::class.java,
                ExactSizeConstraintsLayoutBehavior::class.java,
                FillLayoutBehavior::class.java,
                FixedSizeLayoutBehavior::class.java,
                AspectRatioLayoutBehavior::class.java,
                EqualDimensionsLayoutBehavior::class.java,
                Equivalence::class.java,
                Allocator::class.java,
                ViewAllocator::class.java,
                DrawableAllocator::class.java)
            .toHashSet()
    if (additionalClasses != null) {
      classes.addAll(additionalClasses())
    }
    for (clazz in classes) {
      clazz.hashCode()
    }
  }
  preloadClassHandler.post(r)
}

fun preloadYogaConfig() {
  NodeConfig.createYogaNode()
}


/*
 * 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 PrepareInterStagePropsContainer


/*
 * 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.rendercore.Mountable
import com.facebook.rendercore.primitives.Primitive
import kotlin.jvm.JvmField

/**
 * The result of a [MountableComponent#prepare] or a [PrimitiveComponent#prepare] call. This will be
 * the Mountable/Primitive this component rendered to, potentially as well as other
 * non-Mountable/Primitive metadata that resulted from that call, such as transitions that should be
 * applied, as well as CommonProps for the component.
 */
class PrepareResult {

  @JvmField val primitive: Primitive?

  @JvmField val mountable: Mountable<*>?

  @JvmField val transitions: List<Transition>?

  @JvmField val useEffectEntries: List<Attachable>?

  @JvmField val commonProps: CommonProps?

  constructor(
      mountable: Mountable<*>?,
      transitions: List<Transition>?,
      useEffectEntries: List<Attachable>?,
      commonProps: CommonProps?
  ) {
    primitive = null
    this.mountable = mountable
    this.transitions = transitions
    this.useEffectEntries = useEffectEntries
    this.commonProps = commonProps
  }

  constructor(
      primitive: Primitive?,
      transitions: List<Transition>?,
      useEffectEntries: List<Attachable>?,
      commonProps: CommonProps?
  ) {
    this.primitive = primitive
    mountable = null
    this.transitions = transitions
    this.useEffectEntries = useEffectEntries
    this.commonProps = commonProps
  }
}


/*
 * 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.core.util.Pools
import com.facebook.infer.annotation.ThreadSafe
import kotlin.math.max
import kotlin.math.min

/**
 * Used to recycle objects in Litho. Can be configured to be either synchronized or not. A
 * [RecyclePool] will keep track of its own size so that it can be queried to debug pool sizes.
 */
@ThreadSafe(enableChecks = false)
open class RecyclePool<T : Any>(
    private val _name: String,
    private val _maxSize: Int,
    private val isSync: Boolean
) : PoolWithDebugInfo {

  private val pool: Pools.Pool<T> =
      if (isSync) Pools.SynchronizedPool(_maxSize) else Pools.SimplePool(_maxSize)
  private var _currentSize = 0
  val isFull: Boolean
    get() = _currentSize >= _maxSize

  open fun acquire(): T? {
    return if (isSync) {
      synchronized(this) {
        return acquireInternal()
      }
    } else {
      acquireInternal()
    }
  }

  open fun release(item: T): Boolean {
    return if (isSync) {
      synchronized(this) {
        return releaseInternal(item)
      }
    } else {
      releaseInternal(item)
    }
  }

  override val name: String
    get() = _name

  override val maxSize: Int
    get() = _maxSize

  override val currentSize: Int
    get() = _currentSize

  fun clear() {
    if (isSync) {
      synchronized(this) { clearInternal() }
    } else {
      clearInternal()
    }
  }

  private fun acquireInternal(): T? {
    val item = pool.acquire()
    _currentSize = max(0, _currentSize - 1)
    return item
  }

  private fun releaseInternal(item: T): Boolean {
    _currentSize = min(_maxSize, _currentSize + 1)
    return pool.release(item)
  }

  private fun clearInternal() {
    while (acquire() != null) {
      // no-op.
    }
  }
}


/*
 * 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 kotlin.jvm.JvmField

/**
 * The result of a [Component#render] call. This will be the Component this component rendered to,
 * potentially as well as other non-Component metadata that resulted from that call, such as
 * transitions that should be applied.
 */
class RenderResult
@JvmOverloads
constructor(
    @JvmField val component: Component?,
    @JvmField val transitions: List<Transition>? = null,
    @JvmField val useEffectEntries: List<Attachable>? = 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

import androidx.annotation.IntDef

@IntDef(
    RenderSource.TEST,
    RenderSource.NONE,
    RenderSource.SET_ROOT_SYNC,
    RenderSource.SET_ROOT_ASYNC,
    RenderSource.SET_SIZE_SPEC_SYNC,
    RenderSource.SET_SIZE_SPEC_ASYNC,
    RenderSource.UPDATE_STATE_SYNC,
    RenderSource.UPDATE_STATE_ASYNC,
    RenderSource.MEASURE_SET_SIZE_SPEC,
    RenderSource.MEASURE_SET_SIZE_SPEC_ASYNC,
    RenderSource.RELOAD_PREVIOUS_STATE)
annotation class RenderSource {
  companion object {
    const val TEST = -2
    const val NONE = -1
    const val SET_ROOT_SYNC = 0
    const val SET_ROOT_ASYNC = 1
    const val SET_SIZE_SPEC_SYNC = 2
    const val SET_SIZE_SPEC_ASYNC = 3
    const val UPDATE_STATE_SYNC = 4
    const val UPDATE_STATE_ASYNC = 5
    const val MEASURE_SET_SIZE_SPEC = 6
    const val MEASURE_SET_SIZE_SPEC_ASYNC = 7
    const val RELOAD_PREVIOUS_STATE = 8
  }
}

object RenderSourceUtils {
  @JvmStatic
  fun getExecutionMode(@RenderSource source: Int): String {
    return when (source) {
      RenderSource.SET_ROOT_SYNC,
      RenderSource.UPDATE_STATE_SYNC,
      RenderSource.SET_SIZE_SPEC_SYNC,
      RenderSource.MEASURE_SET_SIZE_SPEC -> "sync"
      RenderSource.SET_ROOT_ASYNC,
      RenderSource.UPDATE_STATE_ASYNC,
      RenderSource.SET_SIZE_SPEC_ASYNC,
      RenderSource.MEASURE_SET_SIZE_SPEC_ASYNC -> "async"
      RenderSource.TEST -> "test"
      RenderSource.NONE -> "none"
      else -> "unknown"
    }
  }

  @JvmStatic
  fun getSource(@RenderSource source: Int): String {
    return when (source) {
      RenderSource.SET_ROOT_SYNC,
      RenderSource.SET_ROOT_ASYNC -> "set-root"
      RenderSource.UPDATE_STATE_SYNC,
      RenderSource.UPDATE_STATE_ASYNC -> "update-state"
      RenderSource.SET_SIZE_SPEC_SYNC,
      RenderSource.SET_SIZE_SPEC_ASYNC -> "set-size"
      RenderSource.MEASURE_SET_SIZE_SPEC,
      RenderSource.MEASURE_SET_SIZE_SPEC_ASYNC -> "measure"
      RenderSource.TEST -> "test"
      RenderSource.NONE -> "none"
      else -> "unknown"
    }
  }
}


/*
 * 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.Component.RenderData
import java.lang.RuntimeException
import java.util.HashMap
import java.util.HashSet

/**
 * Keeps track of the last mounted @Prop/@State a component was rendered with for components that
 * care about them (currently, this is just for ComponentSpecs that use [Diff]'s of props in any of
 * their lifecycle methods).
 */
class RenderState {

  private val renderData: MutableMap<String, RenderData?> = HashMap()
  private val seenGlobalKeys: MutableSet<String> = HashSet()

  fun recordRenderData(scopedComponentInfos: List<ScopedComponentInfo>?) {
    if (scopedComponentInfos == null) {
      return
    }

    for (componentInfo in scopedComponentInfos) {
      recordRenderData(componentInfo)
    }
    seenGlobalKeys.clear()
  }

  fun applyPreviousRenderData(scopedComponentInfos: List<ScopedComponentInfo>?) {
    if (scopedComponentInfos == null) {
      return
    }
    for (componentInfo in scopedComponentInfos) {
      applyPreviousRenderData(componentInfo)
    }
  }

  private fun recordRenderData(scopedComponentInfo: ScopedComponentInfo) {
    val component = scopedComponentInfo.component
    val globalKey = scopedComponentInfo.context.globalKey
    if (!isPreviousRenderDataSupported(component)) {
      throw RuntimeException(
          "Trying to record previous render data for component that doesn't support it")
    }

    // Sanity check like in StateHandler
    if (seenGlobalKeys.contains(globalKey)) {
      // We found two components with the same global key.
      throw RuntimeException(
          "Cannot record previous render data for ${component.simpleName}, found another Component with the same key: $globalKey")
    }
    seenGlobalKeys.add(globalKey)
    val currentInfo = renderData[globalKey]
    renderData[globalKey] =
        (component as SpecGeneratedComponent).recordRenderData(
            scopedComponentInfo.context, currentInfo)
  }

  private fun applyPreviousRenderData(scopedComponentInfo: ScopedComponentInfo) {
    val component = scopedComponentInfo.component
    val globalKey = scopedComponentInfo.context.globalKey
    if (!isPreviousRenderDataSupported(component)) {
      throw RuntimeException(
          "Trying to apply previous render data to component that doesn't support it")
    }

    (component as SpecGeneratedComponent).applyPreviousRenderData(renderData[globalKey])
  }

  private fun isPreviousRenderDataSupported(component: Component): Boolean =
      component is SpecGeneratedComponent && component.needsPreviousRenderData()
}


/*
 * 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("RenderTreeNodeUtils")

package com.facebook.litho

import android.graphics.Rect
import com.facebook.rendercore.RenderTreeNode

fun create(
    unit: LithoRenderUnit,
    bounds: Rect,
    layoutData: LithoLayoutData?,
    parent: RenderTreeNode?
): RenderTreeNode =
    RenderTreeNode(parent, unit, layoutData, bounds, null, parent?.childrenCount ?: 0)


/*
 * 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.GuardedBy
import java.util.HashMap
import java.util.concurrent.atomic.AtomicInteger

/**
 * Class that handles generation of unique IDs for RenderUnits for a given ComponentTree. The ID
 * generation uses the component key to create an ID, and creates unique IDs for any components
 * under the same ComponentTree.
 */
class RenderUnitIdGenerator(
    /** Returns the ComponentTree ID that this ID-generator is linked with. */
    val componentTreeId: Int
) {

  private val nextId = AtomicInteger(1)

  @GuardedBy("this") private val keyToId = HashMap<String, Int>()

  /**
   * Calculates a returns a unique ID for a given component key and output type. The IDs will be
   * unique for components in the ComponentTree this ID generator is linked with. If an ID was
   * already generated for a given component, the same ID will be returned. Otherwise, a new unique
   * ID will be generated
   *
   * @param componentKey The component key
   * @param type The output type @see OutputUnitType
   */
  fun calculateLayoutOutputId(componentKey: String, @OutputUnitType type: Int): Long =
      addTypeAndComponentTreeToId(getId(componentKey), type, componentTreeId)

  @Synchronized
  private fun getId(key: String): Int = keyToId.getOrPut(key) { nextId.getAndIncrement() }

  companion object {
    private fun addTypeAndComponentTreeToId(
        id: Int,
        @OutputUnitType type: Int,
        componentTreeId: Int
    ): Long = id.toLong() or (type.toLong() shl 32) or (componentTreeId.toLong() shl 35)
  }
}


/*
 * 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.Pair
import java.util.concurrent.atomic.AtomicReference
import kotlin.jvm.JvmField

class ResolveResult(
    @JvmField val node: LithoNode?,
    @JvmField val context: ComponentContext,
    @JvmField val component: Component,
    cache: MeasuredResultCache,
    @JvmField val treeState: TreeState,
    override val isPartialResult: Boolean,
    @JvmField val version: Int,
    @JvmField val createdEventHandlers: List<Pair<String, EventHandler<*>>>?,
    @JvmField val attachables: List<Attachable>?,
    @JvmField val contextForResuming: ResolveStateContext?
) : PotentiallyPartialResult {

  private val cache: AtomicReference<MeasuredResultCache> = AtomicReference(cache)

  fun consumeCache(): MeasuredResultCache = cache.getAndSet(MeasuredResultCache.EMPTY)
}


/*
 * 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.Configuration

abstract class ResourceCache protected constructor(private val configuration: Configuration) {

  abstract operator fun <T> get(key: Int): T?

  abstract operator fun set(key: Int, value: Any)

  companion object {
    private var latest: ResourceCache? = null

    @JvmStatic
    @Synchronized
    fun getLatest(configuration: Configuration): ResourceCache? {
      if (latest?.configuration != configuration) {
        latest = LruResourceCache(Configuration(configuration))
      }

      return latest
    }
  }
}


/*
 * 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 java.lang.Exception
import java.lang.RuntimeException
import kotlin.jvm.JvmField

class ReThrownException
internal constructor(
    @JvmField val original: Exception,
    @JvmField val lastHandler: EventHandler<ErrorEvent>
) : RuntimeException()


/*
 * 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

/**
 * Takes a component and returns a new component that it is wrapped in. Used to create a common root
 * wrapper in [ComponentTree].
 */
fun interface RootWrapperComponentFactory {

  /** Wrap the given component and return a new root. */
  fun createWrapper(c: ComponentContext, root: Component): 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.util.SparseIntArray
import com.facebook.proguard.annotations.DoNotStrip
import java.util.ArrayList
import kotlin.collections.HashMap

class ScopedComponentInfo(
    val component: Component,
    val context: ComponentContext,
    errorEventHandler: EventHandler<ErrorEvent>?
) : Cloneable {

  var stateContainer: StateContainer? = null
  val prepareInterStagePropsContainer: PrepareInterStagePropsContainer? =
      if (component is SpecGeneratedComponent) component.createPrepareInterStagePropsContainer()
      else null

  /**
   * Holds onto how many direct component children of each type this Component has. Used for
   * automatically generating unique global keys for all sibling components of the same type.
   */
  private val childCounters: SparseIntArray by lazy { SparseIntArray(1) }

  /** Count the times a manual key is used so that clashes can be resolved. */
  @DoNotStrip private var manualKeysCounter: MutableMap<String, Int>? = null

  /**
   * Holds an event handler with its dispatcher set to the parent component, or - in case that this
   * is a root component - a default handler that reraises the exception. Null if the component
   * isn't initialized.
   */
  var errorEventHandler: EventHandler<ErrorEvent>? = errorEventHandler
    private set

  var commonProps: CommonProps? = null

  /**
   * Holds a list of working range related data. [LayoutState] will use it to update
   * [LayoutState.mWorkingRangeContainer] when calculate method is finished.
   */
  private val workingRangeRegistrations: MutableList<WorkingRangeContainer.Registration> by lazy {
    ArrayList()
  }

  /**
   * Returns the number of children of a given type @param[component] component has and then
   * increments it by 1.
   *
   * @param component the child component
   * @return the number of children components of type @param[component]
   */
  fun getChildCountAndIncrement(component: Component): Int {
    val count = childCounters.get(component.typeId, 0)
    childCounters.put(component.typeId, count + 1)
    return count
  }

  /**
   * Returns the number of children with same @param[manualKey] component has and then increments it
   * by 1.
   *
   * @param manualKey
   * @return the number of manual key usage
   */
  fun getManualKeyUsagesCountAndIncrement(manualKey: String): Int {
    val map = manualKeysCounter ?: HashMap<String, Int>(1).also { manualKeysCounter = it }
    val count = map[manualKey] ?: 0
    map[manualKey] = count + 1
    return count
  }

  /** Store a working range information into a list for later use by [LayoutState]. */
  fun registerWorkingRange(
      name: String,
      workingRange: WorkingRange,
      component: Component,
      globalKey: String
  ) {
    workingRangeRegistrations.add(WorkingRangeContainer.Registration(name, workingRange, this))
  }

  fun addWorkingRangeToNode(node: LithoNode) {
    if (CollectionsUtils.isNotNullOrEmpty(workingRangeRegistrations)) {
      node.addWorkingRanges(workingRangeRegistrations)
    }
  }

  /** This setter should only be called during the render phase of the component, never after. */
  fun setErrorEventHandlerDuringRender(errorHandler: EventHandler<ErrorEvent>?) {
    errorEventHandler = errorHandler
  }

  fun commitToLayoutState(treeState: TreeState) {
    // the get method adds the state container to the needed state container map
    treeState.keepStateContainerForGlobalKey(context.globalKey, context.isNestedTreeContext)
  }

  private fun hasState(): Boolean = component is SpecGeneratedComponent && component.hasState()

  override fun clone(): ScopedComponentInfo =
      try {
        super.clone() as ScopedComponentInfo
      } catch (e: CloneNotSupportedException) {
        throw RuntimeException(e)
      }
}


/*
 * 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.LithoLifecycleProvider.LithoLifecycle

/**
 * LithoLifecycleProvider implementation that can be used to subscribe a nested ComponentTree to
 * listen to state changes of the lifecycle provider that the parent ComponentTree is also
 * subscribed to.
 */
class SimpleNestedTreeLifecycleProvider(parentLifecycleProvider: LithoLifecycleProvider?) :
    LithoLifecycleProvider, LithoLifecycleListener {

  private val lithoLifecycleDelegate = LithoLifecycleProviderDelegate()

  override val lifecycleStatus: LithoLifecycle
    get() = lithoLifecycleDelegate.lifecycleStatus

  init {
    parentLifecycleProvider?.addListener(this)
  }

  override fun moveToLifecycle(lithoLifecycle: LithoLifecycle) {
    lithoLifecycleDelegate.moveToLifecycle(lithoLifecycle)
  }

  override fun addListener(listener: LithoLifecycleListener) {
    lithoLifecycleDelegate.addListener(listener)
  }

  override fun removeListener(listener: LithoLifecycleListener) {
    lithoLifecycleDelegate.removeListener(listener)
  }

  override fun onMovedToState(state: LithoLifecycle) {
    when (state) {
      LithoLifecycle.HINT_VISIBLE -> moveToLifecycle(LithoLifecycle.HINT_VISIBLE)
      LithoLifecycle.HINT_INVISIBLE -> moveToLifecycle(LithoLifecycle.HINT_INVISIBLE)
      LithoLifecycle.DESTROYED -> {
        /* do nothing */
      }
    }
  }
}


/*
 * 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 kotlin.jvm.JvmField

/** Public API for MeasureOutput. */
@ThreadConfined(ThreadConfined.ANY)
class Size() {

  @JvmField var width: Int = 0
  @JvmField var height: Int = 0

  constructor(width: Int, height: Int) : this() {
    this.width = width
    this.height = height
  }
}


/*
 * 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
import androidx.annotation.IntDef
import com.facebook.yoga.YogaMeasureMode
import java.lang.IllegalArgumentException
import java.lang.IllegalStateException
import kotlin.math.min

/**
 * A SizeSpec encapsulates the layout requirements passed from parent to child. Each SizeSpec
 * represents a requirement for either the width or the height. A SizeSpec is comprised of a size
 * and a mode. There are two possible modes:
 *
 * UNSPECIFIED : The parent has not imposed any constraint on the child. It can be whatever size it
 * wants.
 *
 * EXACTLY : The parent has determined an exact size for the child. The child is going to be given
 * those bounds regardless of how big it wants to be. SizeSpecs are implemented as ints to reduce
 * object allocation. This class is provided to pack and unpack the tuple into the int.
 */
object SizeSpec {

  /**
   * Size specification mode: The parent has not imposed any constraint on the child. It can be
   * whatever size it wants.
   */
  const val UNSPECIFIED = View.MeasureSpec.UNSPECIFIED

  /**
   * Size specification mode: The parent has determined an exact size for the child. The child is
   * going to be given those bounds regardless of how big it wants to be.
   */
  const val EXACTLY = View.MeasureSpec.EXACTLY

  /** Size specification mode: The child can be as large as it wants up to the specified size. */
  const val AT_MOST = View.MeasureSpec.AT_MOST

  /**
   * Creates a size specification based on the supplied size and mode.
   *
   * The mode must always be one of the following:
   * * [com.facebook.litho.SizeSpec#UNSPECIFIED]
   * * [com.facebook.litho.SizeSpec#EXACTLY]
   *
   * **Note:** On API level 17 and lower, makeMeasureSpec's implementation was such that the order
   * of arguments did not matter and overflow in either value could impact the resulting
   * MeasureSpec. [android.widget.RelativeLayout] was affected by this bug. Apps targeting API
   * levels greater than 17 will get the fixed, more strict behavior.
   *
   * @param size the size of the size specification
   * @param mode the mode of the size specification
   * @return the size specification based on size and mode
   */
  @JvmStatic
  fun makeSizeSpec(size: Int, @MeasureSpecMode mode: Int): Int =
      View.MeasureSpec.makeMeasureSpec(size, mode)

  /**
   * Extracts the mode from the supplied size specification.
   *
   * @param sizeSpec the size specification to extract the mode from
   * @return [com.facebook.litho.SizeSpec#UNSPECIFIED] or [com.facebook.litho.SizeSpec#EXACTLY]
   */
  @JvmStatic fun getMode(sizeSpec: Int): Int = View.MeasureSpec.getMode(sizeSpec)

  /**
   * Extracts the size from the supplied size specification.
   *
   * @param sizeSpec the size specification to extract the size from
   * @return the size in pixels defined in the supplied size specification
   */
  @JvmStatic fun getSize(sizeSpec: Int): Int = View.MeasureSpec.getSize(sizeSpec)

  /**
   * Returns a String representation of the specified measure specification.
   *
   * @param sizeSpec the size specification to convert to a String
   * @return a String with the following format: "MeasureSpec: MODE SIZE"
   */
  @JvmStatic fun toString(sizeSpec: Int): String = View.MeasureSpec.toString(sizeSpec)

  /**
   * Returns a simple String representation of the specified measure specification.
   *
   * @param sizeSpec the size specification to convert to a String
   * @return a String with the following format: "MODE SIZE"
   */
  @JvmStatic
  fun toSimpleString(sizeSpec: Int): String {
    val mode = getMode(sizeSpec)
    val size = getSize(sizeSpec)
    return buildString {
      when (mode) {
        UNSPECIFIED -> append("UNSPECIFIED ")
        EXACTLY -> append("EXACTLY ")
        AT_MOST -> append("AT_MOST ")
        else -> append(mode).append(" ")
      }
      append(size)
    }
  }

  /**
   * Resolve a size spec given a preferred size.
   *
   * @param sizeSpec The spec to resolve.
   * @param preferredSize The preferred size.
   * @return The resolved size.
   */
  @JvmStatic
  fun resolveSize(sizeSpec: Int, preferredSize: Int): Int =
      when (getMode(sizeSpec)) {
        EXACTLY -> getSize(sizeSpec)
        AT_MOST -> min(getSize(sizeSpec), preferredSize)
        UNSPECIFIED -> preferredSize
        else -> throw IllegalStateException("Unexpected size mode: ${getMode(sizeSpec)}")
      }

  @JvmStatic
  fun makeSizeSpecFromCssSpec(cssSize: Float, cssMode: YogaMeasureMode): Int =
      when (cssMode) {
        YogaMeasureMode.EXACTLY -> makeSizeSpec(FastMath.round(cssSize), EXACTLY)
        YogaMeasureMode.UNDEFINED -> makeSizeSpec(0, UNSPECIFIED)
        YogaMeasureMode.AT_MOST -> makeSizeSpec(FastMath.round(cssSize), AT_MOST)
        else -> throw IllegalArgumentException("Unexpected YogaMeasureMode: $cssMode")
      }

  @IntDef(UNSPECIFIED, EXACTLY, AT_MOST)
  @Retention(AnnotationRetention.SOURCE)
  annotation class MeasureSpecMode
}


/*
 * 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 kotlin.jvm.JvmField

/**
 * Implemented by the class used to store state within both Components and Sections to store state.
 */
abstract class StateContainer : Cloneable {

  abstract fun applyStateUpdate(stateUpdate: StateUpdate)

  public override fun clone(): StateContainer =
      try {
        super.clone() as StateContainer
      } catch (ex: CloneNotSupportedException) {
        // This should never happen
        throw RuntimeException(ex)
      }

  class StateUpdate(@JvmField val type: Int, vararg params: Any) {
    @JvmField val params: Array<Any>

    init {
      // Use spread operator(*) to get a writable array here,
      // [doc](https://kotlinlang.org/docs/functions.html#variable-number-of-arguments-varargs)
      this.params = arrayOf(*params)
    }
  }
}


/*
 * 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.config.ComponentsConfiguration

/** Hosts API to debug state */
val KStateContainer.state: List<Any?>
  get() =
      if (ComponentsConfiguration.isDebugModeEnabled) {
        states
      } else {
        error("State should only be read by debugging utilities.")
      }


/*
 * 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 com.facebook.infer.annotation.ThreadSafe
import com.facebook.litho.SpecGeneratedComponent.TransitionContainer
import com.facebook.litho.StateContainer.StateUpdate
import com.facebook.litho.stats.LithoStats
import com.facebook.rendercore.transitions.TransitionUtils
import java.lang.Exception
import javax.annotation.concurrent.GuardedBy
import kotlin.collections.HashSet

/** Holds information about the current State of the components in a Component Tree. */
class StateHandler @VisibleForTesting constructor(stateHandler: StateHandler? = null) {

  /** List of state updates that will be applied during the next layout pass. */
  @GuardedBy("this")
  private val _pendingStateUpdates: MutableMap<String, MutableList<StateUpdate>> =
      HashMap(INITIAL_MAP_CAPACITY)

  /** List of lazy state updates. */
  @GuardedBy("this")
  private val _pendingLazyStateUpdates: MutableMap<String, MutableList<StateUpdate>> =
      HashMap(INITIAL_MAP_CAPACITY)

  /** List of transitions from state update that will be applied on next mount. */
  @GuardedBy("this")
  private val _pendingStateUpdateTransitions: MutableMap<String, List<Transition>> = mutableMapOf()

  /** List of transitions from state update that have been applied on next mount. */
  @GuardedBy("this")
  private val _appliedStateUpdates: MutableMap<String, List<StateUpdate>> =
      HashMap(INITIAL_MAP_CAPACITY)

  /**
   * Maps a component key to a component object that retains the current state values for that key.
   */
  @GuardedBy("this") private val _stateContainers: MutableMap<String, StateContainer> = HashMap()

  /**
   * Contains all keys of components that were present in the current ComponentTree and therefore
   * their StateContainer needs to be kept around.
   */
  @GuardedBy("this") private val neededStateContainers = HashSet<String>()

  /** Map of all cached values that are stored for the current ComponentTree. */
  @GuardedBy("this") private var cachedValues: MutableMap<Any, Any?>? = null

  // These are both lists of (globalKey, updateMethod) pairs, where globalKey is the global key
  // of the component the update applies to
  @GuardedBy("this")
  private val pendingHookUpdates: MutableMap<String, MutableList<HookUpdater>> = mutableMapOf()
  private var appliedHookUpdates: Map<String, List<HookUpdater>> = emptyMap()

  var initialStateContainer: InitialStateContainer
    private set

  private var stateContainerNotFoundForKeys: HashSet<String>? = null

  @get:Synchronized
  val isEmpty: Boolean
    get() = _stateContainers.isEmpty()

  /**
   * @return whether this StateHandler has updates that haven't been committed to the
   *   source-of-truth StateHandler on the ComponentTree.
   */
  @Synchronized
  fun hasUncommittedUpdates(): Boolean =
      // Because we immediately apply Kotlin state updates at the  beginning of layout, we need to
      // also check applied state updates to see if this StateHandler has uncommitted updates.
      _pendingStateUpdates.isNotEmpty() ||
          pendingHookUpdates.isNotEmpty() ||
          appliedHookUpdates.isNotEmpty()

  /**
   * Adds a state update to the list of the state updates that will be applied for the given
   * component key during the next layout pass.
   *
   * @param key the global key of the component
   * @param stateUpdate the state update to apply to the component
   * @param isLazyStateUpdate the flag to indicate if it's a lazy state update
   */
  @Synchronized
  fun queueStateUpdate(key: String, stateUpdate: StateUpdate, isLazyStateUpdate: Boolean) {
    addStateUpdateForKey(key, stateUpdate, _pendingStateUpdates)
    if (isLazyStateUpdate) {
      addStateUpdateForKey(key, stateUpdate, _pendingLazyStateUpdates)
    }
  }

  fun keepStateContainerForGlobalKey(key: String) {
    neededStateContainers.add(key)
  }

  /**
   * StateContainer in this StateHandler should be accessed using this method as it will also ensure
   * that the state is marked as needed
   */
  fun getStateContainer(key: String): StateContainer? = _stateContainers[key]

  fun createOrGetStateContainerForComponent(
      scopedContext: ComponentContext,
      component: Component,
      key: String
  ): StateContainer {
    val currentStateContainer: StateContainer? = synchronized(this) { _stateContainers[key] }

    return if (currentStateContainer != null) {
      neededStateContainers.add(key)
      currentStateContainer
    } else {
      val initialState =
          initialStateContainer.createOrGetInitialStateForComponent(component, scopedContext, key)
      addStateContainer(key, initialState)
      initialState
    }
  }

  private fun applyStateUpdates(key: String, newStateContainer: StateContainer) {
    // If there are no state updates pending for this component, simply store its current state.
    val stateUpdatesForKey = synchronized(this) { _pendingStateUpdates[key] } ?: return

    var transitionsFromStateUpdate: MutableList<Transition>? = null
    val asTransitionContainer = newStateContainer as? TransitionContainer

    for (update in stateUpdatesForKey) {
      if (asTransitionContainer != null) {
        val transition = asTransitionContainer.applyStateUpdateWithTransition(update)
        if (transition != null) {
          if (transitionsFromStateUpdate == null) {
            transitionsFromStateUpdate = ArrayList()
          }
          TransitionUtils.setOwnerKey(transition, key)
          transitionsFromStateUpdate.add(transition)
        }
      } else {
        newStateContainer.applyStateUpdate(update)
      }
    }

    LithoStats.incrementComponentAppliedStateUpdateCountBy(stateUpdatesForKey.size.toLong())

    synchronized(this) {
      _pendingLazyStateUpdates.remove(key)
      _appliedStateUpdates[key] = stateUpdatesForKey // add to applied

      if (!transitionsFromStateUpdate.isNullOrEmpty()) {
        _pendingStateUpdateTransitions[key] = transitionsFromStateUpdate
      }
    }
  }

  @ThreadSafe(enableChecks = false)
  @Synchronized
  fun applyStateUpdatesEarly(
      context: ComponentContext,
      component: Component?,
      prevTreeRootNode: LithoNode?
  ) {
    for ((key, _) in _pendingStateUpdates) {
      try {
        var stateContainer = _stateContainers[key]
        if (stateContainer == null) {
          stateContainer = initialStateContainer.getInitialStateForComponent(key)
        }
        if (stateContainer == null) {
          if (stateContainerNotFoundForKeys == null) {
            stateContainerNotFoundForKeys = HashSet()
          }
          stateContainerNotFoundForKeys?.add(key)
          continue
        }
        val newStateContainer = stateContainer.clone()
        _stateContainers[key] = newStateContainer
        applyStateUpdates(key, newStateContainer)
      } catch (ex: Exception) {

        // Remove pending state update from ComponentTree's state handler since we don't want to
        // process this pending state update again. If we don't remove it and someone is using
        // setRoot in onError api then we can end up in an infinite loop
        context.removePendingStateUpdate(key, context.isNestedTreeContext)
        if (prevTreeRootNode != null) {
          handleExceptionDuringApplyStateUpdate(key, prevTreeRootNode, ex)
        } else {
          ComponentUtils.handleWithHierarchy(context, component, ex)
        }
      }
    }
    _pendingStateUpdates.clear()
  }

  @Synchronized
  fun removePendingStateUpdate(key: String) {
    _pendingStateUpdates.remove(key)
  }

  /**
   * Maps a component key to a component object that retains the current state values for that key.
   *
   * @param key the key of component
   * @param state the state value that needs to be retained
   */
  @Synchronized
  fun addStateContainer(key: String, state: StateContainer) {
    neededStateContainers.add(key)
    _stateContainers[key] = state
  }

  fun applyLazyStateUpdatesForContainer(
      componentKey: String,
      container: StateContainer
  ): StateContainer {
    val stateUpdatesForKey = synchronized(this) { pendingLazyStateUpdates?.get(componentKey) }

    if (stateUpdatesForKey.isNullOrEmpty()) {
      return container
    }

    val containerWithUpdatesApplied = container.clone()
    for (update in stateUpdatesForKey) {
      containerWithUpdatesApplied.applyStateUpdate(update)
    }

    return containerWithUpdatesApplied
  }

  /**
   * Removes a list of state updates that have been applied from the pending state updates list and
   * updates the map of current components with the given components.
   *
   * @param stateHandler state handler that was used to apply state updates in a layout pass
   */
  fun commit(stateHandler: StateHandler) {
    clearStateUpdates(stateHandler.appliedStateUpdates)
    clearUnusedStateContainers(stateHandler)
    copyCurrentStateContainers(stateHandler.stateContainers)
    copyPendingStateTransitions(stateHandler.pendingStateUpdateTransitions)
    commitHookState(stateHandler.appliedHookUpdates)
    stateContainerNotFoundForKeys?.clear()
  }

  @get:Synchronized
  val keysForPendingUpdates: Set<String>
    get() =
        HashSet<String>().apply {
          addAll(_appliedStateUpdates.keys)
          addAll(_pendingStateUpdates.keys)
          addAll(pendingHookUpdates.keys)
          addAll(appliedHookUpdates.keys)
        }

  private fun clearStateUpdates(appliedStateUpdates: Map<String, List<StateUpdate>>?) {
    synchronized(this) {
      if (appliedStateUpdates == null || _pendingStateUpdates.isEmpty()) {
        return
      }
    }

    for ((appliedStateUpdateKey, appliedStateUpdatesForKey) in appliedStateUpdates!!) {
      val (pendingStateUpdatesForKey, pendingLazyStateUpdatesForKey) =
          synchronized(this) {
            _pendingStateUpdates[appliedStateUpdateKey] to
                _pendingLazyStateUpdates[appliedStateUpdateKey]
          }

      if (pendingStateUpdatesForKey == null) {
        continue
      }

      if (pendingStateUpdatesForKey.size == appliedStateUpdatesForKey.size) {
        synchronized(this) {
          _pendingStateUpdates.remove(appliedStateUpdateKey)
          _pendingLazyStateUpdates.remove(appliedStateUpdateKey)
        }
      } else {
        pendingStateUpdatesForKey.removeAll(appliedStateUpdatesForKey)
        pendingLazyStateUpdatesForKey?.removeAll(appliedStateUpdatesForKey)
      }
    }
  }

  @get:Synchronized
  val stateContainers: Map<String, StateContainer>
    get() = _stateContainers

  @get:Synchronized
  val pendingStateUpdates: Map<String, MutableList<StateUpdate>>?
    get() = _pendingStateUpdates

  @get:Synchronized
  val pendingLazyStateUpdates: Map<String, MutableList<StateUpdate>>?
    get() = _pendingLazyStateUpdates

  @get:Synchronized
  val pendingStateUpdateTransitions: Map<String, List<Transition>>?
    get() = _pendingStateUpdateTransitions

  @get:Synchronized
  @get:VisibleForTesting
  val appliedStateUpdates: Map<String, List<StateUpdate>>?
    get() = _appliedStateUpdates

  @Synchronized
  fun getCachedValue(cachedValueInputs: Any): Any? {
    return cachedValues?.get(cachedValueInputs)
  }

  @Synchronized
  fun putCachedValue(cachedValueInputs: Any, cachedValue: Any?) {
    if (cachedValues == null) {
      cachedValues = HashMap()
    }

    cachedValues?.set(cachedValueInputs, cachedValue)
  }

  /**
   * Copies the information from the given map of state updates into the map of pending state
   * updates.
   */
  private fun copyStateUpdatesMap(
      pendingStateUpdates: Map<String, MutableList<StateUpdate>?>?,
      pendingLazyStateUpdates: Map<String, MutableList<StateUpdate>?>?,
      appliedStateUpdates: Map<String, List<StateUpdate>>?
  ) {
    if (CollectionsUtils.isNullOrEmpty(pendingStateUpdates) &&
        CollectionsUtils.isNullOrEmpty(appliedStateUpdates)) {
      return
    }
    synchronized(this) {
      if (pendingStateUpdates != null) {
        for (key in pendingStateUpdates.keys) {
          _pendingStateUpdates[key] = createStateUpdatesList(pendingStateUpdates[key])
        }
      }
      copyPendingLazyStateUpdates(pendingLazyStateUpdates)
      if (appliedStateUpdates != null) {
        for ((key, value) in appliedStateUpdates) {
          _appliedStateUpdates[key] = createStateUpdatesList(value)
        }
      }
    }
  }

  private fun copyPendingLazyStateUpdates(
      pendingLazyStateUpdates: Map<String, MutableList<StateUpdate>?>?
  ) {
    if (pendingLazyStateUpdates == null || pendingLazyStateUpdates.isEmpty()) {
      return
    }
    for ((key, value) in pendingLazyStateUpdates) {
      _pendingLazyStateUpdates?.set(key, createStateUpdatesList(value))
    }
  }

  /**
   * Copies the list of given state containers into the map that holds the current state containers
   * of components.
   */
  private fun copyCurrentStateContainers(stateContainers: Map<String, StateContainer>?) {
    if (stateContainers == null) {
      return
    }

    synchronized(this) {
      _stateContainers.clear()
      _stateContainers.putAll(stateContainers)
    }
  }

  private fun copyPendingStateTransitions(
      pendingStateUpdateTransitions: Map<String, List<Transition>>?
  ) {
    if (pendingStateUpdateTransitions.isNullOrEmpty()) {
      return
    }

    synchronized(this) { _pendingStateUpdateTransitions.putAll(pendingStateUpdateTransitions) }
  }

  //
  // Hooks - Experimental - see KState.kt
  //

  /**
   * Registers the given block to be run before the next layout calculation to update hook state.
   */
  @Synchronized
  fun queueHookStateUpdate(key: String, updater: HookUpdater) {
    var hookUpdaters = pendingHookUpdates[key]
    if (hookUpdaters == null) {
      hookUpdaters = ArrayList()
      pendingHookUpdates[key] = hookUpdaters
    }

    hookUpdaters.add(updater)
  }

  @get:VisibleForTesting
  val pendingHookUpdatesCount: Int
    get() = pendingHookUpdates.asSequence().sumOf { it.value.size }

  init {
    if (stateHandler == null) {
      initialStateContainer = InitialStateContainer()
    } else {
      synchronized(this) {
        initialStateContainer = stateHandler.initialStateContainer
        copyStateUpdatesMap(
            stateHandler.pendingStateUpdates,
            stateHandler.pendingLazyStateUpdates,
            stateHandler.appliedStateUpdates)
        copyCurrentStateContainers(stateHandler.stateContainers)
        copyPendingStateTransitions(stateHandler.pendingStateUpdateTransitions)
        runHooks(stateHandler)
      }
    }
  }

  /**
   * Called when creating a new StateHandler for a layout calculation. It copies the source of truth
   * state, and then the current list of HookUpdater blocks that need to be applied. Unlike normal
   * state, these blocks are run immediately to update this StateHandlers hook state before we start
   * creating components.
   *
   * @param other the ComponentTree's source-of-truth StateHandler where pending state updates are
   *   collected
   */
  private fun runHooks(other: StateHandler) {
    val updates = getHookUpdatesCopy(other.pendingHookUpdates)
    for ((key, value) in updates) {
      val stateContainer = _stateContainers[key]
      /* currentState could be null if the state is removed from the StateHandler before the update runs */
      if (stateContainer is KStateContainer) {
        var kStateContainer: KStateContainer = stateContainer

        for (hookUpdate in value) {
          kStateContainer = hookUpdate.getUpdatedStateContainer(kStateContainer)
        }
        _stateContainers[key] = kStateContainer
      }
    }
    appliedHookUpdates = updates
  }

  /**
   * Gets a state container with all applied updates for the given key without committing the
   * updates to a state handler.
   */
  fun getStateContainerWithHookUpdates(globalKey: String): KStateContainer? {
    val stateContainer = synchronized(this) { _stateContainers[globalKey] } ?: return null

    val hookUpdaters =
        synchronized(this) { pendingHookUpdates?.get(globalKey)?.let { ArrayList(it) } }

    if (hookUpdaters == null) {
      return stateContainer as KStateContainer
    }

    var stateContainerWithUpdatesApplied = stateContainer as KStateContainer
    for (hookUpdater in hookUpdaters) {
      stateContainerWithUpdatesApplied =
          hookUpdater.getUpdatedStateContainer(stateContainerWithUpdatesApplied)
    }

    return stateContainerWithUpdatesApplied
  }

  /**
   * Called on the ComponentTree's source-of-truth StateHandler when a layout has completed and new
   * state needs to be committed. In this case, we want to remove any pending state updates that
   * this StateHandler applied, while leaving new ones that have accumulated in the interim. We also
   * copy over the new mapping from hook state keys to values.
   */
  private fun commitHookState(appliedHookUpdates: Map<String, List<HookUpdater>>?) {
    if (appliedHookUpdates.isNullOrEmpty() || pendingHookUpdates.isEmpty()) {
      return
    }

    for ((globalKey, appliedHookUpdatersForKey) in appliedHookUpdates) {
      val pendingHookUpdatersForKey: MutableList<HookUpdater>?
      synchronized(this) {
        pendingHookUpdatersForKey = pendingHookUpdates[globalKey]
        if (!pendingHookUpdatersForKey.isNullOrEmpty()) {
          pendingHookUpdatersForKey.removeAll(appliedHookUpdatersForKey)
          if (pendingHookUpdatersForKey.isEmpty()) {
            pendingHookUpdates.remove(globalKey)
          }
        }
      }
    }
  }

  companion object {
    private const val INITIAL_STATE_UPDATE_LIST_CAPACITY = 4
    private const val INITIAL_MAP_CAPACITY = 4
    const val ERROR_STATE_CONTAINER_NOT_FOUND_APPLY_STATE_UPDATE_EARLY =
        "StateHandler:StateContainerNotFoundApplyStateUpdateEarly"

    private fun addStateUpdateForKey(
        key: String,
        stateUpdate: StateUpdate,
        map: MutableMap<String, MutableList<StateUpdate>>?
    ) {
      var pendingStateUpdatesForKey = map?.get(key)
      if (pendingStateUpdatesForKey == null) {
        pendingStateUpdatesForKey = createStateUpdatesList()
        map?.set(key, pendingStateUpdatesForKey)
      }

      pendingStateUpdatesForKey.add(stateUpdate)
    }

    private fun handleExceptionDuringApplyStateUpdate(
        key: String,
        current: LithoNode,
        exception: Exception
    ) {
      val scopedComponentInfos = current.scopedComponentInfos
      for (scopedComponentInfo in scopedComponentInfos) {
        val context = scopedComponentInfo.context
        if (context.globalKey == key) {
          ComponentUtils.handleWithHierarchy(context, scopedComponentInfo.component, exception)
          break
        }
      }
      for (index in 0 until current.childCount) {
        val childLithoNode = current.getChildAt(index)
        if (key.startsWith(childLithoNode.headComponentKey)) {
          handleExceptionDuringApplyStateUpdate(key, childLithoNode, exception)
        }
      }
    }

    private fun createStateUpdatesList(
        copyFrom: List<StateUpdate>? = null
    ): MutableList<StateUpdate> {
      val list: MutableList<StateUpdate> =
          ArrayList(copyFrom?.size ?: INITIAL_STATE_UPDATE_LIST_CAPACITY)
      if (copyFrom != null) {
        list.addAll(copyFrom)
      }
      return list
    }

    private fun clearUnusedStateContainers(currentStateHandler: StateHandler) {
      if (currentStateHandler._stateContainers.isEmpty()) {
        return
      }

      val neededStateContainers: Set<String> = currentStateHandler.neededStateContainers
      val stateContainerKeys: List<String> = ArrayList(currentStateHandler._stateContainers.keys)

      for (key in stateContainerKeys) {
        if (key !in neededStateContainers) {
          currentStateHandler._stateContainers.remove(key)
        }
      }
    }

    private fun getHookUpdatesCopy(
        copyFrom: Map<String, List<HookUpdater>>
    ): Map<String, List<HookUpdater>> {
      val copyInto: MutableMap<String, List<HookUpdater>> = HashMap(copyFrom.size)
      for ((key, value) in copyFrom) {
        copyInto[key] = ArrayList(value)
      }
      return copyInto
    }
  }
}