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

@Suppress("NOTHING_TO_INLINE")
inline fun <reified E : Any, R> eventHandlerWithReturn(
    noinline onEvent: (E) -> R
): EventHandler<E> = KEventHandler(onEvent)

@Suppress("NOTHING_TO_INLINE")
inline fun <reified E : Any> eventHandler(
    noinline onEvent: (E) -> Unit,
): EventHandler<E> = eventHandler(onEvent, null)

@Suppress("NOTHING_TO_INLINE")
inline fun <reified E : Any> eventHandler(
    noinline onEvent: (E) -> Unit,
    tag: String?
): EventHandler<E> = KEventHandler(onEvent, tag)
/**
 * [EventHandler] for codegen-free Components which squashes [EventHandler], [HasEventDispatcher]
 * and [EventDispatcher] together in one object allocation.
 */
class KEventHandler<E : Any, R>(
    private val onEvent: (event: E) -> R,
    private val tag: String? = null,
) : EventHandler<E>(-1, EventDispatchInfo(null, null), null), HasEventDispatcher, EventDispatcher {

  init {
    dispatchInfo.hasEventDispatcher = this
    dispatchInfo.tag = tag
  }

  override fun dispatchEvent(event: E) {
    applyOnValidTag { RenderCoreSystrace.beginSection("onEvent: $tag") }

    onEvent(event)

    applyOnValidTag { RenderCoreSystrace.endSection() }
  }

  private fun applyOnValidTag(block: () -> Unit) {
    if (!tag.isNullOrEmpty()) block()
  }

  override fun dispatchOnEvent(eventHandler: EventHandler<*>, eventState: Any): R {
    @Suppress("UNCHECKED_CAST") return onEvent(eventState as E)
  }

  override fun getEventDispatcher(): EventDispatcher {
    return this
  }

  override fun isEquivalentTo(other: EventHandler<*>?): Boolean {
    if (this === other) return true
    if (other == null || javaClass != other.javaClass) return false

    other as KEventHandler<*, *>

    return onEvent == other.onEvent
  }
}


/*
 * 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:Suppress("MatchingDeclarationName")

package com.facebook.litho

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

/** A simple mutable holder of a value. */
class Ref<T>(value: T) {
  var value: T = value
    @UiThread
    get() {
      assertMainThread()
      return field
    }
    @UiThread
    set(value) {
      assertMainThread()
      field = value
    }
}

/**
 * Declares a mutable, main-thread confined reference that will be persisted across renders with an
 * initial value provided by the initializer. It's similar to [useState], except that the returned
 * reference is mutable and updating it will *not* cause a re-render.
 *
 * IMPORTANT: It's only safe to read/write the [Ref]'s value on the main thread!
 *
 * An example of a safe way to use `useRef` is to store an Animator that is started/stopped from
 * [useEffect] or a click event, both of which run on the UI thread.
 */
@Hook
fun <T> ComponentScope.useRef(initializer: () -> T): Ref<T> = useState { Ref(initializer()) }.value


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

package com.facebook.litho

import com.facebook.litho.annotations.Hook

/**
 * Declares a state variable within a Component. The initializer will provide the initial value if
 * it hasn't already been initialized in a previous render of the Component.
 *
 * Assignments to the state variables are allowed only in [updateState] block to batch updates and
 * trigger a UI layout only once per batch.
 */
@Hook
fun <T> ComponentScope.useState(initializer: () -> T): State<T> {
  val globalKey = context.globalKey
  val hookIndex = useStateIndex++
  val treeState: TreeState =
      resolveStateContext?.treeState
          ?: throw IllegalStateException("Cannot create state outside of layout calculation")

  val isNestedTreeContext = context.isNestedTreeContext
  val kState = treeState.getStateContainer(globalKey, isNestedTreeContext) as KStateContainer?

  if (kState == null || kState.states.size <= hookIndex) {
    // The initial state was not computed yet. let's create it and put it in the state
    val state =
        treeState.createOrGetInitialHookState(
            globalKey,
            hookIndex,
            initializer,
            isNestedTreeContext,
            context.scopedComponentInfo.component.simpleName)
    treeState.addStateContainer(globalKey, state, isNestedTreeContext)

    context.scopedComponentInfo.stateContainer = state

    return State(context, hookIndex, state.states[hookIndex] as T)
  } else {
    context.scopedComponentInfo.stateContainer = kState
  }

  // Only need to mark this global key as seen once
  if (hookIndex == 0) {
    treeState.keepStateContainerForGlobalKey(globalKey, isNestedTreeContext)
  }

  return State(context, hookIndex, kState.states[hookIndex] as T)
}

/** Interface with which a component gets the value from a state or updates it. */
class State<T>
internal constructor(
    private val context: ComponentContext,
    private val hookStateIndex: Int,
    val value: T
) {

  /**
   * Updates this state value and enqueues a new layout calculation reflecting it to execute in the
   * background.
   */
  fun update(newValue: T) {
    if (canSkip(newValue)) {
      return
    }

    context.updateHookStateAsync(context.globalKey, HookUpdaterValue(newValue))
  }

  /**
   * Uses [newValueFunction] to update this state value using the previous state value, and enqueues
   * a new layout calculation reflecting it to execute in the background.
   *
   * [newValueFunction] receives the current state value and can use it to compute the update: this
   * is useful when there could be other enqueued updates that may not have been applied yet.
   *
   * For example, if your state update should increment a counter, using the function version of
   * [update] with `count -> count + 1` will allow you to account for updates that are in flight but
   * not yet applied (e.g. if the user has tapped a button triggering the update multiple times in
   * succession).
   */
  fun update(newValueFunction: (T) -> T) {
    if (canSkip(newValueFunction)) {
      return
    }

    context.updateHookStateAsync(context.globalKey, HookUpdaterLambda(newValueFunction))
  }

  /**
   * Updates this state value and enqueues a new layout calculation reflecting it to execute on the
   * current thread. If called on the main thread, this means that the UI will be updated for the
   * current frame.
   *
   * Note: If [updateSync] is used on the main thread, it can easily cause dropped frames and
   * degrade user experience. Therefore it should only be used in exceptional circumstances or when
   * it's known to be executed off the main thread.
   */
  fun updateSync(newValue: T) {
    if (canSkip(newValue)) {
      return
    }

    context.updateHookStateSync(context.globalKey, HookUpdaterValue(newValue))
  }

  /**
   * Uses [newValueFunction] to update this state value using the previous state value, and enqueues
   * a new layout calculation reflecting it to execute on the current thread.
   *
   * [newValueFunction] receives the current state value and can use it to compute the update: this
   * is useful when there could be other enqueued updates that may not have been applied yet.
   *
   * For example, if your state update should increment a counter, using the function version of
   * [update] with `count -> count + 1` will allow you to account for updates that are in flight but
   * not yet applied (e.g. if the user has tapped a button triggering the update multiple times in
   * succession).
   *
   * Note: If [updateSync] is used on the main thread, it can easily cause dropped frames and
   * degrade user experience. Therefore it should only be used in exceptional circumstances or when
   * it's known to be executed off the main thread.
   */
  fun updateSync(newValueFunction: (T) -> T) {
    if (canSkip(newValueFunction)) {
      return
    }

    context.updateHookStateSync(context.globalKey, HookUpdaterLambda(newValueFunction))
  }

  inner class HookUpdaterValue(val newValue: T) : HookUpdater {
    override fun getUpdatedStateContainer(currentState: KStateContainer): KStateContainer {
      return currentState.copyAndMutate(hookStateIndex, newValue)
    }
  }

  inner class HookUpdaterLambda(val newValueFunction: (T) -> T) : HookUpdater {
    override fun getUpdatedStateContainer(currentState: KStateContainer): KStateContainer {
      return currentState.copyAndMutate(
          hookStateIndex, newValueFunction(currentState.states[hookStateIndex] as T))
    }
  }

  private fun canSkip(newValue: T): Boolean {
    return context.stateUpdater?.canSkipStateUpdate(
        context.globalKey, hookStateIndex, newValue, context.isNestedTreeContext)
        ?: false
  }

  private fun canSkip(newValueFunction: (T) -> T): Boolean {
    return context.stateUpdater?.canSkipStateUpdate(
        newValueFunction, context.globalKey, hookStateIndex, context.isNestedTreeContext)
        ?: false
  }

  /**
   * We consider two state objects equal if they 1) belong to the same Tree, 2) have the same global
   * key and hook index, and 3) have the same value (according to its own .equals check)
   */
  override fun equals(other: Any?): Boolean {
    if (this === other) {
      return true
    }

    if (other !is State<*>) {
      return false
    }

    return context.lithoTree === other.context.lithoTree &&
        context.globalKey == other.context.globalKey &&
        hookStateIndex == other.hookStateIndex &&
        value == other.value
  }

  override fun hashCode(): Int {
    return context.globalKey.hashCode() * 17 + (value?.hashCode() ?: 0) * 11 + hookStateIndex
  }
}


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

/**
 * Defines a single tree prop, i.e. a mapping from type to concrete instance of that type that is
 * visible to the subtree, to be used with [TreePropProvider].
 *
 * You can create a Pair with the `to` infix function, e.g. `String::class to "Hello World!"`.
 */
typealias ClassValuePair<T> = Pair<Class<T>, T>

/**
 * A component which provides one or more tree props to the given child hierarchy. A tree prop is a
 * mapping from a type to an instance of that type which is accessible throughout the entire subtree
 * where it's defined.
 *
 * Tree props are useful for providing theme info, logging tags, or other things that generally need
 * to be available throughout a hierarchy, without having to manual thread them through as
 * individual props. Tree props can be accessed in children via [getTreeProp].
 */
@Suppress("FunctionNaming")
inline fun TreePropProvider(
    vararg props: ClassValuePair<*>,
    component: () -> Component?
): TreePropProviderImpl? {
  val resolvedComponent = component() ?: return null
  return TreePropProviderImpl(props, child = resolvedComponent)
}

/** See [TreePropProvider]. */
class TreePropProviderImpl(
    private val props: Array<out ClassValuePair<*>>,
    private val child: Component
) : KComponent() {
  override fun ComponentScope.render(): Component {
    props.forEach { createTreeProp(it.first, it.second) }
    return child
  }
}

private fun <T> ComponentScope.createTreeProp(clazz: Class<out T>, value: T) {
  if (!context.isParentTreePropsCloned) {
    context.treeProps = TreeProps.acquire(context.treeProps)
    context.isParentTreePropsCloned = true
  }
  context.treeProps?.put(clazz, value)
}

/**
 * Returns the instance registered for this type in this hierarchy. Tree props are registered for a
 * sub-hierarchy via [TreePropProvider] or [com.facebook.litho.annotations.OnCreateTreeProp] in the
 * specs API.
 */
@Suppress("UNCHECKED_CAST")
inline fun <reified T : Any> ComponentScope.getTreeProp(): T? = context.getTreeProp(T::class.java)


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

package com.facebook.litho

import android.content.Context
import com.facebook.rendercore.LayoutContext
import com.facebook.rendercore.MeasureResult
import com.facebook.rendercore.utils.MeasureSpecUtils

/**
 * The scope for the [SimpleMountable] measure method. Provides access to [androidContext],
 * [previousLayoutData] and utility methods that help return the appropriate [MeasureResult].
 */
class MeasureScope(val layoutContext: LayoutContext<*>, val previousLayoutData: Any?) {

  val androidContext: Context
    get() = layoutContext.androidContext

  val resourceResolver: ResourceResolver by lazy {
    ResourceResolver(
        androidContext, ResourceCache.getLatest(androidContext.resources.configuration))
  }

  /**
   * Measure according to an aspect ratio an width and height constraints. This version of
   * forAspectRatio will respect the intrinsic size of the component being measured.
   *
   * @param widthSpec A SizeSpec for the width
   * @param heightSpec A SizeSpec for the height
   * @param intrinsicWidth A pixel value for the intrinsic width of the measured component
   * @param intrinsicHeight A pixel value for the intrinsic height of the measured component
   * @param aspectRatio The aspect ration size against
   */
  fun forAspectRatio(
      widthSpec: Int,
      heightSpec: Int,
      intrinsicWidth: Int,
      intrinsicHeight: Int,
      aspectRatio: Float
  ): MeasureResult =
      MeasureResult.forAspectRatio(
          widthSpec, heightSpec, intrinsicWidth, intrinsicHeight, aspectRatio)

  /**
   * Returns a {@link MeasureResult} to respect both size specs and try to keep both width and
   * height equal. This will only not guarantee equal width and height if these specs use modes and
   * sizes which prevent it.
   */
  fun withEqualSize(widthSpec: Int, heightSpec: Int): MeasureResult =
      MeasureResult.withEqualDimensions(widthSpec, heightSpec, null)

  /**
   * Returns a {@link MeasureResult} with sizes set based on the provided {@param widthSpec} and
   * {@param heightSpec}.
   *
   * <p>This method should only be used for Mountable Components which do not measure themselves -
   * it's the parent that has determined the exact size for this child.
   *
   * @throws IllegalArgumentException if the widthSpec or heightSpec is not exact
   */
  fun fromSpecs(widthSpec: Int, heightSpec: Int): MeasureResult =
      MeasureResult.fromSpecs(widthSpec, heightSpec)

  /**
   * Returns a {@link MeasureResult} that respects both specs and the desired width and height. The
   * desired size is usually the necessary pixels to render the inner content.
   */
  fun withExactSize(
      widthSpec: Int,
      heightSpec: Int,
      desiredWidthPx: Int,
      desiredHeightPx: Int
  ): MeasureResult =
      MeasureResult.withDesiredPx(widthSpec, heightSpec, desiredWidthPx, desiredHeightPx)
}

/**
 * Extracts the mode from the supplied size specification.
 *
 * @param spec the size specification to extract the mode from.
 * @return [android.view.View.MeasureSpec.UNSPECIFIED], [android.view.View.MeasureSpec.AT_MOST] or
 *   [android.view.View.MeasureSpec.EXACTLY]
 */
inline fun MeasureScope.getMode(spec: Int): Int {
  return MeasureSpecUtils.getMode(spec)
}

/**
 * Extracts the size from the supplied size specification.
 *
 * @param spec the size specification to extract the size from.
 * @return the size in pixels defined in the supplied size specification.
 */
inline fun MeasureScope.getSize(spec: Int): Int {
  return MeasureSpecUtils.getSize(spec)
}


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

package com.facebook.litho

import android.content.Context
import android.view.View
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import com.facebook.rendercore.Mountable
import com.facebook.rendercore.RenderUnit.DelegateBinder.createDelegateBinder
import com.facebook.rendercore.incrementalmount.ExcludeFromIncrementalMountBinder
import com.facebook.rendercore.primitives.utils.hasEquivalentFields

/**
 * Base class for Kotlin mountable components. This class encapsulates some of the Mount Spec APIs.
 * All Kotlin mountable components must extend this class.
 */
@Deprecated(
    "Mountable API is deprecated. Use Primitive API instead. Docs: https://fburl.com/staticdocs/j7w6qqyz; example component: https://fburl.com/code/9kigeb7a",
    replaceWith = ReplaceWith("PrimitiveComponent()", "com.facebook.litho.PrimitiveComponent"))
abstract class MountableComponent() : Component() {

  final override fun prepare(
      resolveStateContext: ResolveStateContext,
      c: ComponentContext
  ): PrepareResult {
    val mountableComponentScope = MountableComponentScope(c, resolveStateContext)
    val mountableRenderResult = mountableComponentScope.render()

    mountableComponentScope.cleanUp()

    var commonProps: CommonProps? = null
    if (mountableRenderResult.style != null) {
      commonProps = CommonProps()
      mountableRenderResult.style.applyCommonProps(c, commonProps)
    }

    // generate ID and set it on the Mountable
    val idGenerator = c.renderUnitIdGenerator
    if (idGenerator == null) {
      throw IllegalStateException("Attempt to use a released RenderStateContext")
    } else {
      mountableRenderResult.mountable.id =
          idGenerator.calculateLayoutOutputId(c.globalKey, OutputUnitType.CONTENT)
    }

    mountableRenderResult.mountable.addOptionalMountBinder(
        createDelegateBinder(
            mountableRenderResult.mountable, DynamicValuesBinder(mountableComponentScope.binders)))
    if (mountableComponentScope.shouldExcludeFromIncrementalMount) {
      mountableRenderResult.mountable.addAttachBinder(
          createDelegateBinder(
              mountableRenderResult.mountable, ExcludeFromIncrementalMountBinder.INSTANCE))
    }

    return PrepareResult(
        mountableRenderResult.mountable,
        mountableComponentScope.transitions,
        mountableComponentScope.useEffectEntries,
        commonProps)
  }

  /** This function must return [Mountable] which are immutable. */
  abstract fun MountableComponentScope.render(): MountableRenderResult

  final override fun getMountType(): MountType = MountType.MOUNTABLE

  final override fun canMeasure(): Boolean {
    return true
  }

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

    return true
  }

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

  final override fun canResolve(): Boolean = false

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

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

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

  final override fun isPureRender(): Boolean = true

  final override fun implementsShouldUpdate(): Boolean = true

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

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

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

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

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

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

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

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

  final override fun render(
      resolveStateContext: ResolveStateContext,
      c: ComponentContext,
      widthSpec: Int,
      heightSpec: Int
  ): RenderResult {
    return super.render(resolveStateContext, c, widthSpec, heightSpec)
  }

  final override fun isEqualivalentTreeProps(
      current: ComponentContext,
      next: ComponentContext
  ): Boolean {
    return super.isEqualivalentTreeProps(current, next)
  }
}

/**
 * A class that holds a [Mountable] and [Style] that should be applied to the [MountableComponent].
 */
class MountableRenderResult(val mountable: Mountable<*>, val style: Style?)


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

/** The implicit receiver for [MountableComponent.render] call. */
class MountableComponentScope
internal constructor(context: ComponentContext, resolveStateContext: ResolveStateContext) :
    ComponentScope(context, resolveStateContext) {
  internal val binders: ArrayList<DynamicPropsHolder<Any?, Mountable<*>>> by lazy { ArrayList(2) }
  var shouldExcludeFromIncrementalMount: Boolean = false

  /**
   * Creates a binding between the dynamic value, and the contentâ€™s property.
   *
   * @param defaultValue value that will be set to the Content after unbind
   * @param valueSetter function or function reference that will set the dynamic value on the
   *   content
   */
  fun <ValueT, ContentT> DynamicValue<ValueT>.bindTo(
      defaultValue: ValueT,
      valueSetter: (ContentT, ValueT) -> Unit
  ) {
    addBinder(DynamicPropsHolder(this, defaultValue, valueSetter))
  }

  /**
   * Indicates whether the component skips Incremental Mount.
   *
   * @param shouldExclude if this is true then the Component will not be involved in Incremental
   *   Mount.
   */
  fun excludeFromIncrementalMount(shouldExclude: Boolean) {
    shouldExcludeFromIncrementalMount = shouldExclude
  }

  /** Retrieves a styled attribute value for provided {@param id}. */
  fun getIntAttrValue(
      c: ComponentContext,
      id: Int,
      attrs: IntArray,
      defaultValue: Boolean
  ): Boolean {
    val a = c.obtainStyledAttributes(attrs, 0)
    var i = 0
    val size = a.indexCount
    while (i < size) {
      val attr = a.getIndex(i)
      if (attr == id) {
        return a.getInt(attr, 0) != 0
      }
      i++
    }
    a.recycle()
    return defaultValue
  }

  private fun <ValueT, ContentT> addBinder(binder: DynamicPropsHolder<ContentT, ValueT>) {
    binders.add(binder as DynamicPropsHolder<Any?, Mountable<*>>)
  }
}


/*
 * 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.util.SparseArray
import android.view.View
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.MountBehavior
import com.facebook.rendercore.primitives.Primitive
import com.facebook.rendercore.primitives.utils.hasEquivalentFields

/**
 * Base class for Kotlin primitive components. This class encapsulates some of the Mount Spec APIs.
 * All Kotlin primitive components must extend this class.
 */
abstract class PrimitiveComponent : Component() {

  final override fun prepare(
      resolveStateContext: ResolveStateContext,
      c: ComponentContext
  ): PrepareResult {
    val primitiveComponentScope = PrimitiveComponentScope(c, resolveStateContext)
    val lithoPrimitive = primitiveComponentScope.render()

    primitiveComponentScope.cleanUp()

    var commonProps: CommonProps? = null
    if (lithoPrimitive.style != null) {
      commonProps = CommonProps()
      lithoPrimitive.style.applyCommonProps(c, commonProps)
    }

    return PrepareResult(
        lithoPrimitive.primitive,
        primitiveComponentScope.transitions,
        primitiveComponentScope.useEffectEntries,
        commonProps)
  }

  /** This function must return [LithoPrimitive] which are immutable. */
  abstract fun PrimitiveComponentScope.render(): LithoPrimitive

  final override fun getMountType(): MountType = MountType.PRIMITIVE

  final override fun canMeasure(): Boolean = true

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

    return true
  }

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

  final override fun canResolve(): Boolean = false

  internal final override fun getCommonDynamicProps(): SparseArray<DynamicValue<*>>? =
      super.getCommonDynamicProps()

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

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

  final override fun isPureRender(): Boolean = true

  final override fun implementsShouldUpdate(): Boolean = true

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

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

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

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

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

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

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

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

  final override fun render(
      resolveStateContext: ResolveStateContext,
      c: ComponentContext,
      widthSpec: Int,
      heightSpec: Int
  ): RenderResult {
    return super.render(resolveStateContext, c, widthSpec, heightSpec)
  }

  final override fun isEqualivalentTreeProps(
      current: ComponentContext,
      next: ComponentContext
  ): Boolean {
    return super.isEqualivalentTreeProps(current, next)
  }
}

/**
 * A class that represents a [Primitive] with [Style] that should be applied to the
 * [PrimitiveComponent].
 */
class LithoPrimitive(val primitive: Primitive, val style: Style?) {
  constructor(
      layoutBehavior: LayoutBehavior,
      mountBehavior: MountBehavior<*>,
      style: Style?
  ) : this(Primitive(layoutBehavior, mountBehavior), style)
}


/*
 * 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.ContentAllocator
import com.facebook.rendercore.primitives.MountBehavior as PrimitiveMountBehavior
import com.facebook.rendercore.primitives.MountConfigurationScope
import com.facebook.rendercore.primitives.Primitive
import kotlin.reflect.KFunction2
import kotlin.reflect.KMutableProperty1

/** The implicit receiver for [PrimitiveComponent.render] call. */
class PrimitiveComponentScope
internal constructor(context: ComponentContext, resolveStateContext: ResolveStateContext) :
    ComponentScope(context, resolveStateContext) {

  /**
   * Generates an unique id and creates a [com.facebook.rendercore.primitives.MountBehavior] with
   * it.
   *
   * @param contentAllocator Provides a [View]/[Drawable] content.
   * @param mountConfigurationCall A function that allows for applying properties to the content.
   */
  @Suppress("FunctionName", "NOTHING_TO_INLINE")
  inline fun <ContentType : Any> MountBehavior(
      contentAllocator: ContentAllocator<ContentType>,
      noinline mountConfigurationCall: MountConfigurationScope<ContentType>.() -> Unit
  ): PrimitiveMountBehavior<ContentType> {
    return PrimitiveMountBehavior(id = createId(), contentAllocator, mountConfigurationCall)
  }

  /**
   * Generates an unique id and creates a [com.facebook.rendercore.primitives.MountBehavior] with
   * it.
   *
   * @param description A description of the underlying [RenderUnit]. Mainly for debugging purposes
   *   such as tracing and logs. Maximum description length is 127 characters. Everything above that
   *   will be truncated.
   * @param contentAllocator Provides a [View]/[Drawable] content.
   * @param mountConfigurationCall A function that allows for applying properties to the content.
   */
  @Suppress("FunctionName", "NOTHING_TO_INLINE")
  inline fun <ContentType : Any> MountBehavior(
      description: String,
      contentAllocator: ContentAllocator<ContentType>,
      noinline mountConfigurationCall: MountConfigurationScope<ContentType>.() -> Unit
  ): PrimitiveMountBehavior<ContentType> {
    return PrimitiveMountBehavior(
        id = createId(), description, contentAllocator, mountConfigurationCall)
  }

  /**
   * Creates a binding between the dynamic value, and the contentâ€™s property.
   *
   * @param dynamicValue value that will be set on the Content
   * @param bindCall function or function reference that will set the dynamic value on the content
   */
  fun <ContentType : Any, T> MountConfigurationScope<ContentType>.bindDynamic(
      dynamicValue: DynamicValue<T>?,
      bindCall: BindDynamicScope.(ContentType, T) -> UnbindDynamicFunc
  ) {
    val bindDynamicScope = if (dynamicValue != null) BindDynamicScope() else null
    addBinder(
        dynamicValue,
        { content, value -> bindDynamicScope?.bindCall(content, value) },
        { bindDynamicScope?.unbindDynamicFunc?.onUnbindDynamic() })
  }

  /**
   * Creates a binding between the dynamic value, and the contentâ€™s property.
   *
   * @param dynamicValue value that will be set on the Content
   * @param setter function reference that will set the dynamic value on the content
   */
  fun <ContentType : Any, T> MountConfigurationScope<ContentType>.bindDynamic(
      dynamicValue: DynamicValue<T>?,
      setter: KFunction2<ContentType, T, Any?>,
      default: T
  ) {
    addBinder(
        dynamicValue,
        { content, value -> setter(content, value) },
        { content -> setter(content, default) })
  }

  /**
   * Creates a binding between the dynamic value, and the contentâ€™s property.
   *
   * @param dynamicValue value that will be set on the Content
   * @param setter property reference that will set the dynamic value on the content
   */
  fun <ContentType : Any, T> MountConfigurationScope<ContentType>.bindDynamic(
      dynamicValue: DynamicValue<T>?,
      setter: KMutableProperty1<ContentType, T>,
      default: T
  ) {
    addBinder(
        dynamicValue,
        { content, value -> setter.set(content, value) },
        { content -> setter.set(content, default) })
  }

  /**
   * Creates a binding between the dynamic value, and the contentâ€™s property. The default value of
   * the property is assumed to be null, so after unbind, null value will be set to the Content.
   *
   * @param setter function reference that will set the dynamic value on the content
   */
  inline fun <ContentType : Any, T> MountConfigurationScope<ContentType>.bindDynamic(
      dynamicValue: DynamicValue<T?>?,
      setter: KFunction2<ContentType, T?, Any?>,
  ) = bindDynamic(dynamicValue, setter, null)

  /**
   * Creates a binding between the dynamic value, and the contentâ€™s property. The default value of
   * the property is assumed to be null, so after unbind, null value will be set to the Content.
   *
   * @param setter property reference that will set the dynamic value on the content
   */
  inline fun <ContentType : Any, T> MountConfigurationScope<ContentType>.bindDynamic(
      dynamicValue: DynamicValue<T?>?,
      setter: KMutableProperty1<ContentType, T?>,
  ) = bindDynamic(dynamicValue, setter, null)

  /**
   * Adds a binder for a [DynamicValue] using [MountConfigurationScope.bind] and passing Any() as
   * deps in order to make sure that the binder will always update. This is equivalent to returning
   * true from shouldUpdate().
   */
  private inline fun <ContentType : Any, T> MountConfigurationScope<ContentType>.addBinder(
      dynamicValue: DynamicValue<T>?,
      crossinline bindCall: (ContentType, T) -> Unit,
      crossinline unbindCall: (ContentType) -> Unit
  ) {
    var listener: DynamicValue.OnValueChangeListener<T>? = null

    bind(dynamicValue) { content ->
      if (dynamicValue != null) {
        if (listener == null) {
          listener =
              DynamicValue.OnValueChangeListener {
                ThreadUtils.assertMainThread()
                bindCall(content, dynamicValue.get())
              }
        }
        listener?.let { dynamicValue.attachListener(it) }
        bindCall(content, dynamicValue.get())
      }
      onUnbind {
        if (dynamicValue != null) {
          unbindCall(content)
          listener?.let { dynamicValue.detach(it) }
          listener = null
        }
      }
    }
  }

  /** Creates an unique ID for a given component. */
  @PublishedApi
  internal fun createId(): Long {
    // TODO(zielinskim): calculateLayoutOutputId is mutated during resolve/layout and it may race.
    // Ideally, we'd like to replace this hacky solution with something else.
    return context.renderUnitIdGenerator?.calculateLayoutOutputId(
        context.globalKey, OutputUnitType.CONTENT)
        ?: throw IllegalStateException("Attempt to use a released RenderStateContext")
  }
}

/**
 * Interface for the [onUnbindDynamic] function: use [onUnbindDynamic] to define the cleanup
 * function for your dynamic values.
 */
fun interface UnbindDynamicFunc {
  fun onUnbindDynamic()
}

class BindDynamicScope {

  // Cache the [UnbindDynamicFunc] to avoid creating a new instance on each [DynamicValue] update
  internal var unbindDynamicFunc: UnbindDynamicFunc? = null

  /**
   * Defines an unbind function to be invoked when the content needs to be updated or a [Primitive]
   * is detached.
   */
  fun onUnbindDynamic(unbindDynamic: () -> Unit): UnbindDynamicFunc {
    val unbindDynamicFunc = this.unbindDynamicFunc ?: UnbindDynamicFunc { unbindDynamic() }
    if (this.unbindDynamicFunc == null) {
      this.unbindDynamicFunc = unbindDynamicFunc
    }
    return unbindDynamicFunc
  }
}


/*
 * 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:Suppress("FunctionName", "NOTHING_TO_INLINE")

package com.facebook.litho

import com.facebook.rendercore.primitives.AspectRatioLayoutBehavior as PrimitiveAspectRatioLayoutBehavior
import com.facebook.rendercore.primitives.FillLayoutBehavior as PrimitiveFillLayoutBehavior
import com.facebook.rendercore.primitives.FixedSizeLayoutBehavior as PrimitiveFixedSizeLayoutBehavior
import com.facebook.rendercore.primitives.LayoutBehavior

/**
 * Returns a [LayoutBehavior] with sizes set based on the widthSpec and heightSpec if the spec mode
 * is EXACTLY or AT_MOST, otherwise uses default values.
 *
 * @param defaultWidth The [Dimen] value for the width of the measured component using the
 *   UNSPECIFIED mode.
 * @param defaultHeight The [Dimen] value for the height of the measured component using the
 *   UNSPECIFIED mode.
 * @param layoutData The data to be returned from the layout pass.
 */
inline fun ResourcesScope.FillLayoutBehavior(
    defaultWidth: Dimen,
    defaultHeight: Dimen,
    layoutData: Any? = null
): LayoutBehavior {
  return PrimitiveFillLayoutBehavior(
      defaultWidth = defaultWidth.toPixels(), defaultHeight = defaultHeight.toPixels(), layoutData)
}

/**
 * Returns a [LayoutBehavior] with sizes set based provided values.
 *
 * @param width The [Dimen] value for the width of the measured component.
 * @param height The [Dimen] value for the height of the measured component.
 * @param layoutData The data to be returned from the layout pass.
 */
inline fun ResourcesScope.FixedSizeLayoutBehavior(
    width: Dimen,
    height: Dimen,
    layoutData: Any? = null,
): LayoutBehavior {
  return PrimitiveFixedSizeLayoutBehavior(
      width = width.toPixels(), height = height.toPixels(), layoutData)
}

/**
 * Returns a [LayoutBehavior] with sizes set according to an aspect ratio an width and height
 * constraints. It will respect the intrinsic size of the component being measured.
 *
 * @param aspectRatio The aspect ratio for calculating size.
 * @param intrinsicWidth The [Dimen] value for the intrinsic width of the measured component.
 * @param intrinsicHeight The [Dimen] value for the intrinsic height of the measured component.
 * @param layoutData The data to be returned from the layout pass.
 */
inline fun ResourcesScope.AspectRatioLayoutBehavior(
    aspectRatio: Float,
    intrinsicWidth: Dimen,
    intrinsicHeight: Dimen,
    layoutData: Any? = null
): LayoutBehavior {
  return PrimitiveAspectRatioLayoutBehavior(
      aspectRatio = aspectRatio,
      intrinsicWidth = intrinsicWidth.toPixels(),
      intrinsicHeight = intrinsicHeight.toPixels(),
      layoutData)
}


/*
 * 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.drawable.Drawable
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
import androidx.annotation.IntegerRes
import androidx.annotation.StringRes
import com.facebook.litho.drawable.ComparableColorDrawable

/** Return a string for a resource ID. */
fun ResourcesScope.stringRes(@StringRes id: Int): String =
    requireNotNull(resourceResolver.resolveStringRes(id)) {
      "String resource not found for ID #0x${Integer.toHexString(id)}"
    }

/** Return a string for a resource ID, substituting the format arguments with [arg]. */
fun ResourcesScope.stringRes(@StringRes id: Int, arg: Any): String =
    requireNotNull(resourceResolver.resolveStringRes(id, arg)) {
      "String resource not found for ID #0x${Integer.toHexString(id)}"
    }

/** Return a string for a resource ID, substituting the format arguments with [formatArgs]. */
fun ResourcesScope.stringRes(@StringRes id: Int, vararg formatArgs: Any): String =
    requireNotNull(resourceResolver.resolveStringRes(id, *formatArgs)) {
      "String resource not found for ID #0x${Integer.toHexString(id)}"
    }

/** Retrieve a [android.graphics.drawable.Drawable] for a resource ID as a [Drawable] instance. */
fun ResourcesScope.drawableRes(@DrawableRes id: Int): Drawable =
    requireNotNull(resourceResolver.resolveDrawableRes(id)) {
      "Drawable resource not found for ID #0x${Integer.toHexString(id)}"
    }

/**
 * Retrieve a [android.graphics.drawable.Drawable], corresponding to an attribute resource ID, as a
 * [Drawable] instance. If given attribute ID can not be found, default Drawable resource ID
 * [defResId] is used.
 */
fun ResourcesScope.drawableAttr(@AttrRes id: Int, @DrawableRes defResId: Int = 0): Drawable =
    drawableRes(resourceResolver.resolveResIdAttr(id, defResId))

/**
 * Return a [android.graphics.drawable.Drawable] for a [ColorInt] value as a [Drawable] instance.
 */
fun ResourcesScope.drawableColor(@ColorInt color: Int): Drawable =
    ComparableColorDrawable.create(color)

/**
 * Return a [android.graphics.drawable.Drawable] for a [ColorInt] value as a [Drawable] instance.
 */
fun ResourcesScope.drawableColor(@ColorInt color: Long): Drawable =
    ComparableColorDrawable.create(color.toInt())

/** Return a [ColorInt] value for a color resource ID. */
@ColorInt fun ResourcesScope.colorRes(@ColorRes id: Int): Int = resourceResolver.resolveColorRes(id)

/**
 * Return a [ColorInt] value, corresponding to an attribute resource ID. If given attribute ID can
 * not be found, default color resource ID [defResId] is used.
 */
@ColorInt
fun ResourcesScope.colorAttr(@AttrRes id: Int, @ColorRes defResId: Int = 0): Int =
    resourceResolver.resolveColorAttr(id, defResId)

fun ResourcesScope.intRes(@IntegerRes id: Int): Int = resourceResolver.resolveIntRes(id)

fun ResourcesScope.floatRes(@DimenRes id: Int): Float = resourceResolver.resolveFloatRes(id)


/*
 * 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 androidx.annotation.StyleableRes

/**
 * The receiver for [KComponent] methods which need to access resources and widgets like [Row],
 * [Column], and [Text]. This class exposes the ability to access functions defined in [Resources]
 * like [stringRes]/[drawableRes] etc.
 */
interface ResourcesScope {
  val context: ComponentContext
  val androidContext: Context
    get() = context.androidContext

  val resourceResolver: ResourceResolver
    get() = context.resourceResolver

  fun Dimen.toPixels(): Int = this.toPixels(resourceResolver)

  /** Retrieves a styled attribute value for provided {@param id}. */
  fun getIntAttrValue(
      componentContext: ComponentContext,
      @StyleableRes id: Int,
      @StyleableRes attrs: IntArray,
      defaultValue: Int
  ): Int {
    val typedArray = componentContext.obtainStyledAttributes(attrs, 0)
    for (i in 0 until typedArray.indexCount) {
      val attributeIndex = typedArray.getIndex(i)
      if (attributeIndex == id) {
        return typedArray.getInt(attributeIndex, defaultValue)
      }
    }
    typedArray.recycle()
    return defaultValue
  }
}


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

package com.facebook.litho

import android.content.Context
import com.facebook.rendercore.ContentAllocator
import com.facebook.rendercore.LayoutContext
import com.facebook.rendercore.MeasureResult
import com.facebook.rendercore.Mountable
import com.facebook.rendercore.RenderUnit
import com.facebook.rendercore.primitives.utils.isEqualOrEquivalentTo

/**
 * This is a simplified implementation of a [Mountable] which requires only one [Binder]. Must be
 * immutable, and not cause side effects.
 */
@Deprecated(
    "Mountable API is deprecated. Use Primitive API instead. Docs: https://fburl.com/staticdocs/j7w6qqyz; example component: https://fburl.com/code/9kigeb7a")
abstract class SimpleMountable<ContentT : Any>(renderType: RenderType) :
    Mountable<ContentT>(renderType), ContentAllocator<ContentT> {

  init {
    addOptionalMountBinder(
        DelegateBinder.createDelegateBinder(
            this, BINDER as Binder<SimpleMountable<ContentT>, ContentT, Any?>))
  }

  final override fun measure(
      context: LayoutContext<*>,
      widthSpec: Int,
      heightSpec: Int,
      previousLayoutData: Any?
  ): MeasureResult {
    val measureScope = MeasureScope(context, previousLayoutData)
    return measureScope.measure(widthSpec, heightSpec)
  }

  abstract fun MeasureScope.measure(widthSpec: Int, heightSpec: Int): MeasureResult

  /**
   * Called just before mounting the content. Use it to set properties on the content. This method
   * is always called from the main thread.
   */
  abstract fun mount(c: Context, content: ContentT, layoutData: Any?)

  /**
   * Called just after unmounting the content. Use it to unset properties on the content. This
   * method is always called from the main thread.
   */
  abstract fun unmount(c: Context, content: ContentT, layoutData: Any?)

  /**
   * Called to check if properties need to be reset. This is expected to be done by invoking
   * [unmount] and then [mount] if this function returns true.
   */
  fun shouldUpdate(
      currentMountable: SimpleMountable<ContentT>,
      newMountable: SimpleMountable<ContentT>,
      currentLayoutData: Any?,
      nextLayoutData: Any?
  ): Boolean = shouldUpdate(newMountable, currentLayoutData, nextLayoutData)

  open fun shouldUpdate(
      newMountable: SimpleMountable<ContentT>,
      currentLayoutData: Any?,
      nextLayoutData: Any?
  ): Boolean = currentLayoutData != nextLayoutData || !isEqualOrEquivalentTo(this, newMountable)

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

private val BINDER: RenderUnit.Binder<*, *, *> =
    object : RenderUnit.Binder<SimpleMountable<Any>, Any, Any?> {
      override fun shouldUpdate(
          currentMountable: SimpleMountable<Any>,
          newMountable: SimpleMountable<Any>,
          currentLayoutData: Any?,
          nextLayoutData: Any?
      ): Boolean {
        return currentMountable.shouldUpdate(
            currentMountable,
            newMountable,
            currentLayoutData,
            nextLayoutData,
        )
      }

      override fun bind(
          context: Context,
          content: Any,
          mountable: SimpleMountable<Any>,
          layoutData: Any?
      ): Any? {
        mountable.mount(context, content, layoutData)
        return null
      }

      override fun unbind(
          context: Context,
          content: Any,
          mountable: SimpleMountable<Any>,
          layoutData: Any?,
          bindData: Any?
      ) {
        mountable.unmount(context, content, layoutData)
      }
    }


/*
 * 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.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate

interface StyleItemField

/**
 * Part of a [Style] that can apply an attribute to an underlying Component, e.g. width or click
 * handling.
 */
interface StyleItem<T> {

  val value: T
  val field: StyleItemField

  /** Sets this style item value on the given [CommonProps]. */
  fun applyCommonProps(context: ComponentContext, commonProps: CommonProps) = Unit
}

/** exposed to avoid package-private error on [Component] */
internal fun Component.getCommonPropsHolder() = getOrCreateCommonProps()

internal fun Component.getOrCreateCommonDynamicPropsHolder() = getOrCreateCommonDynamicProps()

/**
 * An immutable ordered collection of attributes ( [StyleItem] s) that can be applied to a
 * component, like width or click handling.
 *
 * Adding new attributes to a Style (e.g. by calling `.background`) will return a new Style object.
 * Ordering matters in that the last definition of an attribute 'wins'.
 *
 * Styles can also be added via the `+` operator, with attributes from the right-hand side Style
 * taking precedence if the two define different values for the same attribute, similar to adding
 * maps.
 */
open class Style(
    /**
     * This is the Style we're adding to, e.g. `Style.padding()` when calling `.background()` in
     * `Style.padding().background()`
     */
    private val previousStyle: Style?,

    /**
     * This is the [StyleItem] we're adding, e.g. the background when calling `.background()` in
     * `Style.padding().background()`
     */
    private val item: StyleItem<*>?,
) {

  operator fun plus(other: Style?): Style {
    if (other == null) {
      return this
    }
    return CombinedStyle(if (this === Style) null else this, other)
  }

  inline operator fun plus(nextItem: StyleItem<*>?): Style {
    if (nextItem == null) {
      return this
    }
    return Style(if (this === Style) null else this, nextItem)
  }

  open fun forEach(lambda: (StyleItem<*>) -> Unit) {
    previousStyle?.forEach(lambda)
    if (item != null) {
      lambda(item)
    }
  }

  internal fun applyCommonProps(context: ComponentContext, commonProps: CommonProps) {
    forEach { it.applyCommonProps(context, commonProps) }
  }

  override fun equals(other: Any?): Boolean {
    if (this === other) {
      return true
    }
    if (other == null || javaClass != other.javaClass) {
      return false
    }
    other as Style
    return previousStyle == other.previousStyle && item == other.item
  }

  override fun hashCode(): Int {
    var result = previousStyle?.hashCode() ?: 0
    result = 31 * result + (item?.hashCode() ?: 0)
    return result
  }

  /**
   * An empty Style singleton that can be used to build a chain of style items.
   *
   * This is a bit of a trick that lets us make `Style.background()` look like a static call, but
   * actually be a member call since `Style` is now a singleton object. Otherwise we'd need to
   * define both a static `Style.background()` and a member function to support
   * `Style.padding(...).background()`.
   */
  companion object : Style(null, null)
}

/**
 * A subclass of [Style] which combines two Styles, as opposed to adding a single [StyleItem] to an
 * existing Style.
 *
 * A CombinedStyle is created by combining two Styles with `+`. Attributes from the right-hand side
 * Style take precedence if the two define different values for the same attribute, similar to
 * adding maps.
 */
@DataClassGenerate
private data class CombinedStyle(val first: Style?, val second: Style?) : Style(first, null) {

  override fun forEach(lambda: (StyleItem<*>) -> Unit) {
    first?.forEach(lambda)
    second?.forEach(lambda)
  }
}


/*
 * 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.drawable.Drawable
import com.facebook.litho.AccessibilityRole.AccessibilityRoleType
import com.facebook.litho.accessibility.accessibilityRole
import com.facebook.litho.animated.translationX
import com.facebook.litho.animated.translationY
import com.facebook.litho.core.height
import com.facebook.litho.core.heightPercent
import com.facebook.litho.core.margin
import com.facebook.litho.core.maxHeight
import com.facebook.litho.core.maxWidth
import com.facebook.litho.core.minHeight
import com.facebook.litho.core.minWidth
import com.facebook.litho.core.padding
import com.facebook.litho.core.width
import com.facebook.litho.core.widthPercent
import com.facebook.litho.flexbox.alignSelf
import com.facebook.litho.flexbox.border
import com.facebook.litho.flexbox.flex
import com.facebook.litho.flexbox.position
import com.facebook.litho.flexbox.positionType
import com.facebook.litho.transition.transitionKey
import com.facebook.litho.view.background
import com.facebook.litho.view.clipChildren
import com.facebook.litho.view.testKey
import com.facebook.litho.view.wrapInView
import com.facebook.yoga.YogaAlign
import com.facebook.yoga.YogaEdge
import com.facebook.yoga.YogaPositionType

/**
 * Backwards compatibility for a Kotlin Component to accept a [Style] and apply it to a Java Spec
 * Component. Using this builder method is the equivalent of setting all common props the Style
 * defines.
 */
fun <T : Component.Builder<T>> Component.Builder<T>.kotlinStyle(style: Style?): T {
  val context = context ?: error("Context should not be null")
  style?.applyCommonProps(context, component.getCommonPropsHolder())
  return getThis()
}

/**
 * Backwards compatibility to allow Java code to define Styles used by Kotlin components
 *
 * Usage:
 * ```
 * new MyKotlinComponent(
 *     prop1,
 *     prop2,
 *     StyleCompat.flexShrink(0f)
 *         .marginDip(YogaEdge.ALL, 4f)
 *         .build())
 * ```
 */
object StyleCompat {

  /** @see [JavaStyle.background] */
  @JvmStatic fun background(value: Drawable): JavaStyle = JavaStyle().background(value)

  /** @see [JavaStyle.clipChildren] */
  @JvmStatic fun clipChildren(value: Boolean): JavaStyle = JavaStyle().clipChildren(value)

  /** @see [JavaStyle.flexShrink] */
  @JvmStatic fun flexShrink(value: Float): JavaStyle = JavaStyle().flexShrink(value)

  /** @see [JavaStyle.flexGrow] */
  @JvmStatic fun flexGrow(value: Float): JavaStyle = JavaStyle().flexGrow(value)

  /** @see [JavaStyle.flexBasisDip]] */
  @JvmStatic fun flexBasisPx(value: Float): JavaStyle = JavaStyle().flexBasisDip(value)

  /** @see [JavaStyle.flexBasisPx] */
  @JvmStatic fun flexBasisPx(value: Int): JavaStyle = JavaStyle().flexBasisPx(value)

  /** @see [JavaStyle.widthDip] */
  @JvmStatic fun widthDip(value: Float): JavaStyle = JavaStyle().widthDip(value)

  /** @see [JavaStyle.heightDip] */
  @JvmStatic fun heightDip(value: Float): JavaStyle = JavaStyle().heightDip(value)

  /** @see [JavaStyle.widthPx] */
  @JvmStatic fun widthPx(value: Int): JavaStyle = JavaStyle().widthPx(value)

  /** @see [JavaStyle.heightPx] */
  @JvmStatic fun heightPx(value: Int): JavaStyle = JavaStyle().heightPx(value)

  /** @see [JavaStyle.widthPercent] */
  @JvmStatic fun widthPercent(value: Float): JavaStyle = JavaStyle().widthPercent(value)

  /** @see [JavaStyle.heightPercent] */
  @JvmStatic fun heightPercent(value: Float): JavaStyle = JavaStyle().heightPercent(value)

  /** @see [JavaStyle.maxWidthDip] */
  @JvmStatic fun maxWidthDip(value: Float): JavaStyle = JavaStyle().maxWidthDip(value)

  /** @see [JavaStyle.maxHeightDip] */
  @JvmStatic fun maxHeightDip(value: Float): JavaStyle = JavaStyle().maxHeightDip(value)

  /** @see [JavaStyle.maxWidthPx] */
  @JvmStatic fun maxWidthPx(value: Int): JavaStyle = JavaStyle().maxWidthPx(value)

  /** @see [JavaStyle.maxHeightPx] */
  @JvmStatic fun maxHeightPx(value: Int): JavaStyle = JavaStyle().maxHeightPx(value)

  /** @see [JavaStyle.minWidthDip] */
  @JvmStatic fun minWidthDip(value: Float): JavaStyle = JavaStyle().minWidthDip(value)

  /** @see [JavaStyle.minHeightDip] */
  @JvmStatic fun minHeightDip(value: Float): JavaStyle = JavaStyle().minHeightDip(value)

  /** @see [JavaStyle.minWidthPx] */
  @JvmStatic fun minWidthPx(value: Int): JavaStyle = JavaStyle().minWidthPx(value)

  /** @see [JavaStyle.minHeightPx] */
  @JvmStatic fun minHeightPx(value: Int): JavaStyle = JavaStyle().minHeightPx(value)

  /** @see [JavaStyle.marginDip] */
  @JvmStatic
  fun marginDip(yogaEdge: YogaEdge, value: Float): JavaStyle =
      JavaStyle().marginDip(yogaEdge, value)

  /** @see [JavaStyle.marginPx] */
  @JvmStatic
  fun marginPx(yogaEdge: YogaEdge, value: Int): JavaStyle = JavaStyle().marginPx(yogaEdge, value)

  /** @see [JavaStyle.paddingDip] */
  @JvmStatic
  fun paddingDip(yogaEdge: YogaEdge, value: Float): JavaStyle =
      JavaStyle().paddingDip(yogaEdge, value)

  /** @see [JavaStyle.paddingPx] */
  @JvmStatic
  fun paddingPx(yogaEdge: YogaEdge, value: Int): JavaStyle = JavaStyle().paddingPx(yogaEdge, value)

  /** @see [JavaStyle.positionDip] */
  @JvmStatic
  fun positionDip(yogaEdge: YogaEdge, value: Float): JavaStyle =
      JavaStyle().positionDip(yogaEdge, value)

  /** @see [JavaStyle.positionPx] */
  @JvmStatic
  fun positionPx(yogaEdge: YogaEdge, value: Int): JavaStyle =
      JavaStyle().positionPx(yogaEdge, value)

  /** @see [JavaStyle.transitionKey] */
  @JvmStatic
  fun transitionKey(
      context: ComponentContext,
      transitionKey: String?,
      transitionKeyType: Transition.TransitionKeyType = Transition.TransitionKeyType.LOCAL
  ): JavaStyle = JavaStyle().transitionKey(context, transitionKey, transitionKeyType)

  /** @see [JavaStyle.wrapInView] */
  @JvmStatic fun wrapInView(): JavaStyle = JavaStyle().wrapInView()

  /** @see [JavaStyle.accessibilityRole] */
  @JvmStatic
  fun accessibilityRole(@AccessibilityRoleType role: String): JavaStyle =
      JavaStyle().accessibilityRole(role)

  /** @see [JavaStyle.translationY] */
  @JvmStatic
  fun translationY(value: DynamicValue<Float>): JavaStyle = JavaStyle().translationY(value)

  /** @see [JavaStyle.translationX] */
  @JvmStatic
  fun translationX(value: DynamicValue<Float>): JavaStyle = JavaStyle().translationX(value)

  /** @see [JavaStyle.testKey] */
  @JvmStatic fun testKey(value: String?): JavaStyle = JavaStyle().testKey(value)

  /** @see [JavaStyle.alignSelf] */
  @JvmStatic fun alignSelf(value: YogaAlign): JavaStyle = JavaStyle().alignSelf(value)

  /** @see [JavaStyle.positionType] */
  @JvmStatic fun positionType(value: YogaPositionType): JavaStyle = JavaStyle().positionType(value)

  /** @see [JavaStyle.border] */
  @JvmStatic fun border(border: Border): JavaStyle = JavaStyle().border(border)
}

class JavaStyle {

  private var style: Style = Style

  fun background(value: Drawable): JavaStyle {
    style = style.background(background = value)
    return this
  }

  fun clipChildren(value: Boolean): JavaStyle {
    style = style.clipChildren(clipChildren = value)
    return this
  }

  fun flexShrink(value: Float): JavaStyle {
    style = style.flex(shrink = value)
    return this
  }

  fun flexGrow(value: Float): JavaStyle {
    style = style.flex(grow = value)
    return this
  }

  fun flexBasisDip(value: Float): JavaStyle {
    style = style.flex(basis = value.dp)
    return this
  }

  fun flexBasisPx(value: Int): JavaStyle {
    style = style.flex(basis = value.px)
    return this
  }

  fun widthDip(value: Float): JavaStyle {
    style = style.width(value.dp)
    return this
  }

  fun heightDip(value: Float): JavaStyle {
    style = style.height(value.dp)
    return this
  }

  fun widthPx(value: Int): JavaStyle {
    style = style.width(value.px)
    return this
  }

  fun heightPx(value: Int): JavaStyle {
    style = style.height(value.px)
    return this
  }

  fun widthPercent(value: Float): JavaStyle {
    style = style.widthPercent(value)
    return this
  }

  fun heightPercent(value: Float): JavaStyle {
    style = style.heightPercent(value)
    return this
  }

  fun maxWidthDip(value: Float): JavaStyle {
    style = style.maxWidth(value.dp)
    return this
  }

  fun maxHeightDip(value: Float): JavaStyle {
    style = style.maxHeight(value.dp)
    return this
  }

  fun maxWidthPx(value: Int): JavaStyle {
    style = style.maxWidth(value.px)
    return this
  }

  fun maxHeightPx(value: Int): JavaStyle {
    style = style.maxHeight(value.px)
    return this
  }

  fun minWidthDip(value: Float): JavaStyle {
    style = style.minWidth(value.dp)
    return this
  }

  fun minHeightDip(value: Float): JavaStyle {
    style = style.minHeight(value.dp)
    return this
  }

  fun minWidthPx(value: Int): JavaStyle {
    style = style.minWidth(value.px)
    return this
  }

  fun minHeightPx(value: Int): JavaStyle {
    style = style.minHeight(value.px)
    return this
  }

  fun wrapInView(): JavaStyle {
    style = style.wrapInView()
    return this
  }

  fun alignSelf(value: YogaAlign): JavaStyle {
    style = style.alignSelf(value)
    return this
  }

  fun positionType(value: YogaPositionType): JavaStyle {
    style = style.positionType(value)
    return this
  }

  fun border(border: Border): JavaStyle {
    style = style.border(border)
    return this
  }

  fun marginDip(yogaEdge: YogaEdge, value: Float): JavaStyle {
    val valueDip = value.dp
    when (yogaEdge) {
      YogaEdge.LEFT -> style = style.margin(left = valueDip)
      YogaEdge.VERTICAL -> style = style.margin(vertical = valueDip)
      YogaEdge.TOP -> style = style.margin(top = valueDip)
      YogaEdge.RIGHT -> style = style.margin(right = valueDip)
      YogaEdge.BOTTOM -> style = style.margin(bottom = valueDip)
      YogaEdge.START -> style = style.margin(start = valueDip)
      YogaEdge.END -> style = style.margin(end = valueDip)
      YogaEdge.HORIZONTAL -> style = style.margin(horizontal = valueDip)
      YogaEdge.ALL -> style = style.margin(all = valueDip)
    }
    return this
  }

  fun marginPx(yogaEdge: YogaEdge, value: Int): JavaStyle {
    val valuePx = value.px
    when (yogaEdge) {
      YogaEdge.LEFT -> style = style.margin(left = valuePx)
      YogaEdge.VERTICAL -> style = style.margin(vertical = valuePx)
      YogaEdge.TOP -> style = style.margin(top = valuePx)
      YogaEdge.RIGHT -> style = style.margin(right = valuePx)
      YogaEdge.BOTTOM -> style = style.margin(bottom = valuePx)
      YogaEdge.START -> style = style.margin(start = valuePx)
      YogaEdge.END -> style = style.margin(end = valuePx)
      YogaEdge.HORIZONTAL -> style = style.margin(horizontal = valuePx)
      YogaEdge.ALL -> style = style.margin(all = valuePx)
    }
    return this
  }

  fun paddingDip(yogaEdge: YogaEdge, value: Float): JavaStyle {
    val valueDip = value.dp
    when (yogaEdge) {
      YogaEdge.LEFT -> style = style.padding(left = valueDip)
      YogaEdge.VERTICAL -> style = style.padding(vertical = valueDip)
      YogaEdge.TOP -> style = style.padding(top = valueDip)
      YogaEdge.RIGHT -> style = style.padding(right = valueDip)
      YogaEdge.BOTTOM -> style = style.padding(bottom = valueDip)
      YogaEdge.START -> style = style.padding(start = valueDip)
      YogaEdge.END -> style = style.padding(end = valueDip)
      YogaEdge.HORIZONTAL -> style = style.padding(horizontal = valueDip)
      YogaEdge.ALL -> style = style.padding(all = valueDip)
    }
    return this
  }

  fun paddingPx(yogaEdge: YogaEdge, value: Int): JavaStyle {
    val valuePx = value.px
    when (yogaEdge) {
      YogaEdge.LEFT -> style = style.padding(left = valuePx)
      YogaEdge.VERTICAL -> style = style.padding(vertical = valuePx)
      YogaEdge.TOP -> style = style.padding(top = valuePx)
      YogaEdge.RIGHT -> style = style.padding(right = valuePx)
      YogaEdge.BOTTOM -> style = style.padding(bottom = valuePx)
      YogaEdge.START -> style = style.padding(start = valuePx)
      YogaEdge.END -> style = style.padding(end = valuePx)
      YogaEdge.HORIZONTAL -> style = style.padding(horizontal = valuePx)
      YogaEdge.ALL -> style = style.padding(all = valuePx)
    }
    return this
  }

  fun positionDip(yogaEdge: YogaEdge, value: Float): JavaStyle {
    val valueDip = value.dp
    when (yogaEdge) {
      YogaEdge.LEFT -> style = style.position(left = valueDip)
      YogaEdge.VERTICAL -> style = style.position(vertical = valueDip)
      YogaEdge.TOP -> style = style.position(top = valueDip)
      YogaEdge.RIGHT -> style = style.position(right = valueDip)
      YogaEdge.BOTTOM -> style = style.position(bottom = valueDip)
      YogaEdge.START -> style = style.position(start = valueDip)
      YogaEdge.END -> style = style.position(end = valueDip)
      YogaEdge.HORIZONTAL -> style = style.position(horizontal = valueDip)
      YogaEdge.ALL -> style = style.position(all = valueDip)
    }
    return this
  }

  fun positionPx(yogaEdge: YogaEdge, value: Int): JavaStyle {
    val valuePx = value.px
    when (yogaEdge) {
      YogaEdge.LEFT -> style = style.position(left = valuePx)
      YogaEdge.VERTICAL -> style = style.position(vertical = valuePx)
      YogaEdge.TOP -> style = style.position(top = valuePx)
      YogaEdge.RIGHT -> style = style.position(right = valuePx)
      YogaEdge.BOTTOM -> style = style.position(bottom = valuePx)
      YogaEdge.START -> style = style.position(start = valuePx)
      YogaEdge.END -> style = style.position(end = valuePx)
      YogaEdge.HORIZONTAL -> style = style.position(horizontal = valuePx)
      YogaEdge.ALL -> style = style.position(all = valuePx)
    }
    return this
  }

  fun transitionKey(
      context: ComponentContext,
      key: String?,
      keyType: Transition.TransitionKeyType
  ): JavaStyle {
    style = style.transitionKey(context, key, keyType)
    return this
  }

  fun accessibilityRole(@AccessibilityRoleType role: String): JavaStyle {
    style = style.accessibilityRole(role)
    return this
  }

  fun translationY(value: DynamicValue<Float>): JavaStyle {
    style = style.translationY(value)
    return this
  }

  fun translationX(value: DynamicValue<Float>): JavaStyle {
    style = style.translationX(value)
    return this
  }

  fun testKey(value: String?): JavaStyle {
    style = style.testKey(value)
    return this
  }

  fun build(): Style {
    return style
  }
}


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

import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate
import com.facebook.litho.AccessibilityRole.AccessibilityRoleType
import com.facebook.litho.CommonProps
import com.facebook.litho.ComponentContext
import com.facebook.litho.OnInitializeAccessibilityEventEvent
import com.facebook.litho.OnInitializeAccessibilityNodeInfoEvent
import com.facebook.litho.OnPopulateAccessibilityEventEvent
import com.facebook.litho.OnPopulateAccessibilityNodeEvent
import com.facebook.litho.OnRequestSendAccessibilityEventEvent
import com.facebook.litho.PerformAccessibilityActionEvent
import com.facebook.litho.SendAccessibilityEventEvent
import com.facebook.litho.SendAccessibilityEventUncheckedEvent
import com.facebook.litho.Style
import com.facebook.litho.StyleItem
import com.facebook.litho.StyleItemField
import com.facebook.litho.annotations.ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_AUTO
import com.facebook.litho.annotations.ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_NO
import com.facebook.litho.annotations.ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
import com.facebook.litho.annotations.ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_YES
import com.facebook.litho.eventHandler

/** Enums for [AccessibilityStyleItem]. */
@PublishedApi
internal enum class AccessibilityField : StyleItemField {
  ACCESSIBILITY_HEADING,
  ACCESSIBILITY_ROLE,
  ACCESSIBILITY_ROLE_DESCRIPTION,
  CONTENT_DESCRIPTION,
  IMPORTANT_FOR_ACCESSIBILITY,
  ON_INITIALIZE_ACCESSIBILITY_EVENT,
  ON_INITIALIZE_ACCESSIBILITY_NODE_INFO,
  ON_POPULATE_ACCESSIBILITY_EVENT,
  ON_POPULATE_ACCESSIBILITY_NODE,
  ON_REQUEST_SEND_ACCESSIBILITY_EVENT,
  PERFORM_ACCESSIBILITY_ACTION,
  SEND_ACCESSIBILITY_EVENT,
  SEND_ACCESSIBILITY_EVENT_UNCHECKED,
}

@PublishedApi
@DataClassGenerate
internal data class AccessibilityStyleItem(
    override val field: AccessibilityField,
    override val value: Any?
) : StyleItem<Any?> {
  override fun applyCommonProps(context: ComponentContext, commonProps: CommonProps) {
    when (field) {
      AccessibilityField.ACCESSIBILITY_HEADING -> commonProps.accessibilityHeading(value as Boolean)
      AccessibilityField.ACCESSIBILITY_ROLE -> commonProps.accessibilityRole(value as String)
      AccessibilityField.ACCESSIBILITY_ROLE_DESCRIPTION ->
          commonProps.accessibilityRoleDescription(value as CharSequence)
      AccessibilityField.CONTENT_DESCRIPTION ->
          commonProps.contentDescription(value as CharSequence)
      AccessibilityField.IMPORTANT_FOR_ACCESSIBILITY ->
          commonProps.importantForAccessibility(value as Int)
      AccessibilityField.ON_INITIALIZE_ACCESSIBILITY_EVENT ->
          commonProps.onInitializeAccessibilityEventHandler(
              eventHandler(value as (OnInitializeAccessibilityEventEvent) -> Unit))
      AccessibilityField.ON_INITIALIZE_ACCESSIBILITY_NODE_INFO ->
          commonProps.onInitializeAccessibilityNodeInfoHandler(
              eventHandler(value as (OnInitializeAccessibilityNodeInfoEvent) -> Unit))
      AccessibilityField.ON_POPULATE_ACCESSIBILITY_EVENT ->
          commonProps.onPopulateAccessibilityEventHandler(
              eventHandler(value as (OnPopulateAccessibilityEventEvent) -> Unit))
      AccessibilityField.ON_POPULATE_ACCESSIBILITY_NODE ->
          commonProps.onPopulateAccessibilityNodeHandler(
              eventHandler(value as (OnPopulateAccessibilityNodeEvent) -> Unit))
      AccessibilityField.ON_REQUEST_SEND_ACCESSIBILITY_EVENT ->
          commonProps.onRequestSendAccessibilityEventHandler(
              eventHandler(value as (OnRequestSendAccessibilityEventEvent) -> Unit))
      AccessibilityField.PERFORM_ACCESSIBILITY_ACTION ->
          commonProps.performAccessibilityActionHandler(
              eventHandler(value as (PerformAccessibilityActionEvent) -> Unit))
      AccessibilityField.SEND_ACCESSIBILITY_EVENT ->
          commonProps.sendAccessibilityEventHandler(
              eventHandler(value as (SendAccessibilityEventEvent) -> Unit))
      AccessibilityField.SEND_ACCESSIBILITY_EVENT_UNCHECKED ->
          commonProps.sendAccessibilityEventUncheckedHandler(
              eventHandler(value as (SendAccessibilityEventUncheckedEvent) -> Unit))
    }
  }
}

/**
 * Whether the view is a heading for a section of content for accessibility purposes.
 *
 * Note: Since this attribute is available only on API 19 and above, calling this method on lower
 * APIs will have no effect.
 *
 * See [android.view.View.setAccessibilityHeading].
 */
inline fun Style.accessibilityHeading(isAccessibilityHeading: Boolean): Style =
    this + AccessibilityStyleItem(AccessibilityField.ACCESSIBILITY_HEADING, isAccessibilityHeading)

/**
 * The Android Talkback "role" this component has. This will be read out when the view is visited in
 * Talkback mode. See [AccessibilityRoleType] for possible roles.
 */
inline fun Style.accessibilityRole(@AccessibilityRoleType accessibilityRole: String?): Style =
    this +
        accessibilityRole?.let { AccessibilityStyleItem(AccessibilityField.ACCESSIBILITY_ROLE, it) }

/**
 * The description for this Component's [accessibilityRole]. This will be read out when the view is
 * visited in Talkback mode.
 */
inline fun Style.accessibilityRoleDescription(accessibilityRoleDescription: CharSequence?): Style =
    this +
        accessibilityRoleDescription?.let {
          AccessibilityStyleItem(AccessibilityField.ACCESSIBILITY_ROLE_DESCRIPTION, it)
        }

/**
 * A description of the contents of this Component for accessibility.
 *
 * See [android.view.View.setContentDescription].
 */
inline fun Style.contentDescription(contentDescription: CharSequence?): Style =
    this +
        contentDescription?.let {
          AccessibilityStyleItem(AccessibilityField.CONTENT_DESCRIPTION, it)
        }

/**
 * Sets whether this Component is "important for accessibility". If it is, it fires accessibility
 * events and is reported to accessibility services that query the screen. The value for this
 * property can be one of the values in [ImportantForAccessibility].
 *
 * See [android.view.View.setImportantForAccessibility].
 */
inline fun Style.importantForAccessibility(
    importantForAccessibility: ImportantForAccessibility
): Style =
    this +
        AccessibilityStyleItem(
            AccessibilityField.IMPORTANT_FOR_ACCESSIBILITY, importantForAccessibility.asInt)

/**
 * Initializes an [AccessibilityEvent] with information about the the host View which dispatched the
 * event.
 *
 * See [android.view.View.AccessibilityDelegateCompat#onInitializeAccessibilityEvent].
 */
inline fun Style.onInitializeAccessibilityEvent(
    noinline onInitializeAccessibilityEventHandler: (OnInitializeAccessibilityEventEvent) -> Unit
): Style =
    this +
        AccessibilityStyleItem(
            AccessibilityField.ON_INITIALIZE_ACCESSIBILITY_EVENT,
            onInitializeAccessibilityEventHandler)

/**
 * Gives a chance to the host View to populate the accessibility event with its text content.
 *
 * See [android.view.View.AccessibilityDelegateCompat#onPopulateAccessibilityEvent].
 */
inline fun Style.onPopulateAccessibilityEvent(
    noinline onPopulateAccessibilityEventHandler: (OnPopulateAccessibilityEventEvent) -> Unit
): Style =
    this +
        AccessibilityStyleItem(
            AccessibilityField.ON_POPULATE_ACCESSIBILITY_EVENT, onPopulateAccessibilityEventHandler)

/** Gives a chance to the component to implement its own accessibility support. */
inline fun Style.onPopulateAccessibilityNode(
    noinline onPopulateAccessibilityNodeHandler: (OnPopulateAccessibilityNodeEvent) -> Unit
): Style =
    this +
        AccessibilityStyleItem(
            AccessibilityField.ON_POPULATE_ACCESSIBILITY_NODE, onPopulateAccessibilityNodeHandler)

/**
 * Called when a child of the host View has requested sending an [AccessibilityEvent] and gives an
 * opportunity to the parent (the host) to augment the event.
 *
 * See [android.view.View.AccessibilityDelegateCompat#onRequestSendAccessibilityEvent].
 */
inline fun Style.onRequestSendAccessibilityEvent(
    noinline onRequestSendAccessibilityEventHandler: (OnRequestSendAccessibilityEventEvent) -> Unit
): Style =
    this +
        AccessibilityStyleItem(
            AccessibilityField.ON_REQUEST_SEND_ACCESSIBILITY_EVENT,
            onRequestSendAccessibilityEventHandler)

/**
 * Performs the specified accessibility action on the view.
 *
 * See [android.view.View.AccessibilityDelegateCompat#performAccessibilityAction].
 */
inline fun Style.performAccessibilityAction(
    noinline performAccessibilityActionHandler: (PerformAccessibilityActionEvent) -> Unit
): Style =
    this +
        AccessibilityStyleItem(
            AccessibilityField.PERFORM_ACCESSIBILITY_ACTION, performAccessibilityActionHandler)

/**
 * Sends an accessibility event of the given type. If accessibility is not enabled this method has
 * no effect.
 *
 * See [android.view.View.AccessibilityDelegateCompat#sendAccessibilityEvent].
 */
inline fun Style.sendAccessibilityEvent(
    noinline sendAccessibilityEventHandler: (SendAccessibilityEventEvent) -> Unit
): Style =
    this +
        AccessibilityStyleItem(
            AccessibilityField.SEND_ACCESSIBILITY_EVENT, sendAccessibilityEventHandler)

/**
 * Sends an accessibility event. This method behaves exactly as sendAccessibilityEvent() but takes
 * as an argument an empty [AccessibilityEvent] and does not perform a check whether accessibility
 * is enabled.
 *
 * See [android.view.View.AccessibilityDelegateCompat#sendAccessibilityEventUnchecked].
 */
inline fun Style.sendAccessibilityEventUnchecked(
    noinline sendAccessibilityEventUncheckedHandler: (SendAccessibilityEventUncheckedEvent) -> Unit
): Style =
    this +
        AccessibilityStyleItem(
            AccessibilityField.SEND_ACCESSIBILITY_EVENT_UNCHECKED,
            sendAccessibilityEventUncheckedHandler)

/**
 * Initializes an [AccessibilityNodeInfoCompat] with information about the host view.
 *
 * See [android.view.View.AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo].
 */
inline fun Style.onInitializeAccessibilityNodeInfo(
    noinline onInitializeAccessibilityNodeInfoHandler:
        (OnInitializeAccessibilityNodeInfoEvent) -> Unit
): Style =
    this +
        AccessibilityStyleItem(
            AccessibilityField.ON_INITIALIZE_ACCESSIBILITY_NODE_INFO,
            onInitializeAccessibilityNodeInfoHandler)

/**
 * Enum values for [importantForAccessibility].
 *
 * Note: if you are looking for YES_HIDE_DESCENDANTS, it has been deprecated: prefer to add an
 * intermediate child with `NO_HIDE_DESCENDANTS` instead.
 */
enum class ImportantForAccessibility(val asInt: Int) {
  /** Automatically determine whether a view is important for accessibility. */
  AUTO(IMPORTANT_FOR_ACCESSIBILITY_AUTO),

  /** The view is important for accessibility. */
  YES(IMPORTANT_FOR_ACCESSIBILITY_YES),

  /** The view is not important for accessibility. */
  NO(IMPORTANT_FOR_ACCESSIBILITY_NO),

  /** The view is not important for accessibility, nor are any of its descendant views. */
  NO_HIDE_DESCENDANTS(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS),
}


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

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.Interpolator
import androidx.dynamicanimation.animation.FloatValueHolder
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.facebook.litho.DynamicValue

object Animated {
  /**
   * Returns an [AnimatedAnimation] ready for running timing animation that calculate animated
   * values and set them on target param.
   *
   * By default it uses [AccelerateDecelerateInterpolator] and duration of 300 milliseconds.
   */
  fun timing(
      target: DynamicValue<Float>,
      to: Float,
      duration: Long = 300,
      easing: Interpolator = Easing.accelerateDecelerate(),
      animationFinishListener: AnimationFinishListener? = null,
      onUpdate: ((Float) -> Unit)? = null,
  ): AnimatedAnimation {

    val animator = ValueAnimator.ofFloat(target.get(), to)
    animator.duration = duration
    animator.interpolator = easing
    animator.addUpdateListener { animation ->
      val animatedValue = animation.animatedValue as Float
      target.set(animatedValue)
      onUpdate?.invoke(animatedValue)
    }
    val animatorAnimation = AnimatorAnimation(animator)
    if (animationFinishListener != null) {
      animatorAnimation.addListener(animationFinishListener)
    }
    return animatorAnimation
  }

  /**
   * Returns an [AnimatedAnimation] ready for running spring animation based on the [SpringConfig]
   * params
   *
   * By default it uses medium stiffness and damping.
   */
  fun spring(
      target: DynamicValue<Float>,
      to: Float,
      springConfig: SpringConfig = SpringConfig(),
      animationFinishListener: AnimationFinishListener? = null,
      onUpdate: ((Float) -> Unit)? = null,
  ): AnimatedAnimation {
    val springAnimation = SpringAnimation(DynamicFloatValueHolder(target))
    val springForce = SpringForce()
    springForce.finalPosition = to
    springForce.stiffness = springConfig.stiffness
    springForce.dampingRatio = springConfig.dampingRatio
    springAnimation.spring = springForce
    onUpdate?.let { springAnimation.addUpdateListener { _, value, _ -> onUpdate(value) } }
    val animatedSpringAnimation = AnimatedSpringAnimation(springAnimation)
    animationFinishListener?.let { animatedSpringAnimation.addListener(animationFinishListener) }

    return animatedSpringAnimation
  }

  /**
   * Returns [SequenceAnimation] ready for running collection of animations in sequence one after
   * another. The order of arguments is the order of the sequence
   */
  fun sequence(vararg animations: AnimatedAnimation): AnimatedAnimation =
      SequenceAnimation(animations)

  /** Returns [ParallelAnimation] ready for running collection of animations in parallel */
  fun parallel(vararg animations: AnimatedAnimation): AnimatedAnimation =
      ParallelAnimation(animations)

  /**
   * Returns [AnimatedAnimation] ready for running collection of animations in stagger with given
   * value in milliseconds
   */
  fun stagger(staggerMs: Long, vararg animations: AnimatedAnimation): AnimatedAnimation {
    val staggerAnimations: ArrayList<AnimatedAnimation> = ArrayList()
    animations.forEachIndexed { index, animation ->
      staggerAnimations.add(delay(staggerMs * index, animation))
    }
    return ParallelAnimation(staggerAnimations.toTypedArray())
  }

  /**
   * Returns [LoopAnimation] ready for running [AnimatedAnimation] object repeated @repeatCount
   * times. By default it runs infinite number of times.
   */
  fun loop(animation: AnimatedAnimation, repeatCount: Int = -1): AnimatedAnimation =
      LoopAnimation(repeatCount, animation)

  /**
   * Returns [AnimatedAnimation] ready for running single or a collection of animations after given
   * delay in milliseconds
   */
  fun delay(startDelay: Long, animation: AnimatedAnimation): AnimatedAnimation {
    val delayAnimator = ValueAnimator.ofFloat(0f, 1f)
    delayAnimator.startDelay = startDelay
    return sequence(AnimatorAnimation(delayAnimator), animation)
  }
}

/**
 * Interface representing single animation (like [Animated.timing] or [Animated.spring]) or a
 * collection of them
 */
interface AnimatedAnimation {
  fun isActive(): Boolean

  fun start()

  fun cancel()

  fun addListener(finishListener: AnimationFinishListener)
}

/** Listener that allows to listen for animation finish or cancel events. */
fun interface AnimationFinishListener {
  /** triggers when animation or set of animations is completed or cancelled */
  fun onFinish(cancelled: Boolean)
}

private class AnimatedSpringAnimation(val springAnimation: SpringAnimation) : AnimatedAnimation {
  override fun isActive(): Boolean = springAnimation.isRunning

  override fun start() {
    springAnimation.start()
  }

  override fun cancel() {
    springAnimation.cancel()
  }

  override fun addListener(animationFinishListener: AnimationFinishListener) {
    springAnimation.addEndListener { _, cancelled, _, _ ->
      animationFinishListener.onFinish(cancelled)
    }
  }
}

internal class AnimatorAnimation(val animator: ValueAnimator) : AnimatedAnimation {
  private var animationCancelled: Boolean = false
  private var _isActive: Boolean = false

  override fun isActive(): Boolean = _isActive

  override fun start() {
    _isActive = true
    animationCancelled = false
    animator.start()
  }

  override fun cancel() {
    _isActive = false
    animationCancelled = true
    animator.cancel()
  }

  override fun addListener(animatorFinishListener: AnimationFinishListener) {
    animator.addListener(
        object : AnimatorListenerAdapter() {
          override fun onAnimationEnd(animation: Animator) {
            if (animationCancelled) {
              // do not call onFinish if animation was cancelled
              return
            }
            animatorFinishListener.onFinish(false)
            _isActive = false
          }

          override fun onAnimationCancel(animation: Animator) {
            animatorFinishListener.onFinish(true)
          }
        })
  }
}

/**
 * The stiffer the spring is, the harder it is to stretch it and the faster it undergoes dampening.
 * Spring damping ratio describes behavior after the disturbance, over-damp values (more than 1)
 * make the spring quickly return to the rest position. Under-damp values (less than 1) cause
 * overshooting. Damping equal to 0 will cause the spring to oscillate forever. (The lower the
 * damping ratio, the higher the bounciness)
 */
class SpringConfig(
    var stiffness: Float = SpringForce.STIFFNESS_LOW,
    var dampingRatio: Float = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
)

/** Bridge between Android [FloatValueHolder] and Litho DynamicValue<Float> */
private class DynamicFloatValueHolder(val dynamicValue: DynamicValue<Float>) : FloatValueHolder() {
  override fun getValue(): Float {
    return dynamicValue.get()
  }

  override fun setValue(value: Float) {
    dynamicValue.set(value)
  }
}


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

import android.graphics.drawable.Drawable
import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate
import com.facebook.litho.CommonProps
import com.facebook.litho.ComponentContext
import com.facebook.litho.DynamicPropsManager.KEY_ALPHA
import com.facebook.litho.DynamicPropsManager.KEY_BACKGROUND_COLOR
import com.facebook.litho.DynamicPropsManager.KEY_BACKGROUND_DRAWABLE
import com.facebook.litho.DynamicPropsManager.KEY_ELEVATION
import com.facebook.litho.DynamicPropsManager.KEY_FOREGROUND_COLOR
import com.facebook.litho.DynamicPropsManager.KEY_ROTATION
import com.facebook.litho.DynamicPropsManager.KEY_SCALE_X
import com.facebook.litho.DynamicPropsManager.KEY_SCALE_Y
import com.facebook.litho.DynamicPropsManager.KEY_TRANSLATION_X
import com.facebook.litho.DynamicPropsManager.KEY_TRANSLATION_Y
import com.facebook.litho.DynamicValue
import com.facebook.litho.Style
import com.facebook.litho.StyleItem
import com.facebook.litho.StyleItemField

/** Enums for [DynamicStyleItem]. */
@PublishedApi
internal enum class DynamicField : StyleItemField {
  ALPHA,
  BACKGROUND_COLOR,
  BACKGROUND_DRAWABLE,
  ELEVATION,
  FOREGROUND,
  ROTATION,
  SCALE_X,
  SCALE_Y,
  TRANSLATION_X,
  TRANSLATION_Y,
}

/**
 * Common style item for all dynamic value styles. See note on [DynamicField] about this pattern.
 */
@PublishedApi
@DataClassGenerate
internal data class DynamicStyleItem(
    override val field: DynamicField,
    override val value: DynamicValue<*>
) : StyleItem<DynamicValue<*>> {
  override fun applyCommonProps(context: ComponentContext, commonProps: CommonProps) {
    val commonDynamicProps = commonProps.orCreateCommonDynamicProps
    when (field) {
      DynamicField.ALPHA -> commonDynamicProps.put(KEY_ALPHA, value)
      DynamicField.BACKGROUND_COLOR -> commonDynamicProps.put(KEY_BACKGROUND_COLOR, value)
      DynamicField.BACKGROUND_DRAWABLE -> commonDynamicProps.put(KEY_BACKGROUND_DRAWABLE, value)
      DynamicField.ELEVATION -> commonDynamicProps.put(KEY_ELEVATION, value)
      DynamicField.FOREGROUND -> commonDynamicProps.put(KEY_FOREGROUND_COLOR, value)
      DynamicField.ROTATION -> commonDynamicProps.put(KEY_ROTATION, value)
      DynamicField.SCALE_X -> commonDynamicProps.put(KEY_SCALE_X, value)
      DynamicField.SCALE_Y -> commonDynamicProps.put(KEY_SCALE_Y, value)
      DynamicField.TRANSLATION_X -> commonDynamicProps.put(KEY_TRANSLATION_X, value)
      DynamicField.TRANSLATION_Y -> commonDynamicProps.put(KEY_TRANSLATION_Y, value)
    }
  }
}

inline fun Style.alpha(alpha: DynamicValue<Float>): Style =
    this + DynamicStyleItem(DynamicField.ALPHA, alpha)

inline fun Style.background(background: DynamicValue<out Drawable>): Style =
    this + DynamicStyleItem(DynamicField.BACKGROUND_DRAWABLE, background)

inline fun Style.backgroundColor(background: DynamicValue<Int>): Style =
    this + DynamicStyleItem(DynamicField.BACKGROUND_COLOR, background)

inline fun Style.foregroundColor(foreground: DynamicValue<Int>): Style =
    this + DynamicStyleItem(DynamicField.FOREGROUND, foreground)

inline fun Style.elevation(elevation: DynamicValue<Float>): Style =
    this + DynamicStyleItem(DynamicField.ELEVATION, elevation)

inline fun Style.rotation(rotation: DynamicValue<Float>): Style =
    this + DynamicStyleItem(DynamicField.ROTATION, rotation)

inline fun Style.scaleX(scaleX: DynamicValue<Float>): Style =
    this + DynamicStyleItem(DynamicField.SCALE_X, scaleX)

inline fun Style.scaleY(scaleY: DynamicValue<Float>): Style =
    this + DynamicStyleItem(DynamicField.SCALE_Y, scaleY)

inline fun Style.translationX(translationX: DynamicValue<Float>): Style =
    this + DynamicStyleItem(DynamicField.TRANSLATION_X, translationX)

inline fun Style.translationY(translationY: DynamicValue<Float>): Style =
    this + DynamicStyleItem(DynamicField.TRANSLATION_Y, translationY)


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

import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.AccelerateInterpolator
import android.view.animation.BounceInterpolator
import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import android.view.animation.OvershootInterpolator
import androidx.core.view.animation.PathInterpolatorCompat

object Easing {

  /** quadratic Bezier curve */
  fun bezier(x: Float, y: Float) = PathInterpolatorCompat.create(x, y)

  /** cubic Bezier curve */
  fun bezier(x1: Float, y1: Float, x2: Float, y2: Float) =
      PathInterpolatorCompat.create(x1, y1, x2, y2)

  fun linear() = LinearInterpolator()

  fun accelerate(factor: Float = 1.0f) = AccelerateInterpolator(factor)

  fun decelerate(factor: Float = 1.0f) = DecelerateInterpolator(factor)

  /**
   * An interpolator where the rate of change starts and ends slowly but accelerates through the
   * middle.
   */
  fun accelerateDecelerate() = AccelerateDecelerateInterpolator()

  fun bounce() = BounceInterpolator()

  /**
   * An interpolator where the change flings forward and overshoots the last value then comes back.
   */
  fun overshoot(tension: Float = 2.0f) = OvershootInterpolator(tension)
}