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

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import androidx.annotation.UiThread
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.LifecycleStep
import com.facebook.litho.LifecycleTracker
import com.facebook.litho.MountContentPool
import com.facebook.litho.Size
import com.facebook.litho.SizeSpec
import com.facebook.litho.StateValue
import com.facebook.litho.TrackingMountContentPool
import com.facebook.litho.annotations.CachedValue
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnAttached
import com.facebook.litho.annotations.OnBind
import com.facebook.litho.annotations.OnBoundsDefined
import com.facebook.litho.annotations.OnCalculateCachedValue
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnCreateMountContentPool
import com.facebook.litho.annotations.OnCreateTreeProp
import com.facebook.litho.annotations.OnDetached
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.OnPrepare
import com.facebook.litho.annotations.OnUnbind
import com.facebook.litho.annotations.OnUnmount
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.State

@MountSpec
object MountSpecLifecycleTesterDrawableSpec {

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(
      context: ComponentContext,
      dummyState: StateValue<Any?>,
      @Prop lifecycleTracker: LifecycleTracker
  ) {
    dummyState.set(Any())
    lifecycleTracker.addStep(LifecycleStep.ON_CREATE_INITIAL_STATE)
  }

  @JvmStatic
  @OnPrepare
  fun onPrepare(
      c: ComponentContext,
      @Prop lifecycleTracker: LifecycleTracker,
      @State dummyState: Any?,
      @CachedValue expensiveValue: Int
  ) {
    lifecycleTracker.addStep(LifecycleStep.ON_PREPARE)
  }

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      c: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size,
      @Prop lifecycleTracker: LifecycleTracker,
      @Prop(optional = true) intrinsicSize: Size?
  ) {
    val width = SizeSpec.getSize(widthSpec)
    val height = SizeSpec.getSize(heightSpec)
    if (intrinsicSize != null) {
      size.width = SizeSpec.resolveSize(widthSpec, intrinsicSize.width)
      size.height = SizeSpec.resolveSize(heightSpec, intrinsicSize.height)
    } else {
      size.width = width
      size.height = height
    }
    lifecycleTracker.addStep(LifecycleStep.ON_MEASURE, size)
  }

  @JvmStatic
  @OnBoundsDefined
  fun onBoundsDefined(
      c: ComponentContext,
      layout: ComponentLayout,
      @Prop lifecycleTracker: LifecycleTracker
  ) {
    val bounds = Rect(layout.x, layout.y, layout.x + layout.width, layout.y + layout.height)
    lifecycleTracker.addStep(LifecycleStep.ON_BOUNDS_DEFINED, bounds)
  }

  @JvmStatic
  @UiThread
  @OnCreateMountContent
  fun onCreateMountContent(c: Context): ColorDrawable = ColorDrawable()

  @JvmStatic
  @OnCreateMountContentPool
  fun onCreateMountContentPool(): MountContentPool =
      TrackingMountContentPool("MountSpecLifecycleTester", 1, true)

  @JvmStatic
  @UiThread
  @OnMount
  fun onMount(
      context: ComponentContext,
      drawable: ColorDrawable,
      @Prop lifecycleTracker: LifecycleTracker
  ) {
    // TODO: (T64290961) Remove the StaticContainer hack for tracing OnCreateMountContent callback.
    if (drawable === StaticContainer.sLastCreatedDrawable) {
      lifecycleTracker.addStep(LifecycleStep.ON_CREATE_MOUNT_CONTENT)
      StaticContainer.sLastCreatedDrawable = null
    }
    lifecycleTracker.addStep(LifecycleStep.ON_MOUNT)
  }

  @JvmStatic
  @UiThread
  @OnUnmount
  fun onUnmount(
      context: ComponentContext,
      drawable: ColorDrawable,
      @Prop lifecycleTracker: LifecycleTracker
  ) {
    lifecycleTracker.addStep(LifecycleStep.ON_UNMOUNT)
  }

  @JvmStatic
  @UiThread
  @OnBind
  fun onBind(
      c: ComponentContext,
      drawable: ColorDrawable,
      @Prop lifecycleTracker: LifecycleTracker
  ) {
    lifecycleTracker.addStep(LifecycleStep.ON_BIND)
  }

  @JvmStatic
  @UiThread
  @OnUnbind
  fun onUnbind(
      c: ComponentContext,
      drawable: ColorDrawable,
      @Prop lifecycleTracker: LifecycleTracker
  ) {
    lifecycleTracker.addStep(LifecycleStep.ON_UNBIND)
  }

  @JvmStatic
  @OnAttached
  fun onAttached(c: ComponentContext, @Prop lifecycleTracker: LifecycleTracker) {
    lifecycleTracker.addStep(LifecycleStep.ON_ATTACHED)
  }

  @JvmStatic
  @OnDetached
  fun onDetached(c: ComponentContext, @Prop lifecycleTracker: LifecycleTracker) {
    lifecycleTracker.addStep(LifecycleStep.ON_DETACHED)
  }

  @JvmStatic
  @OnCalculateCachedValue(name = "expensiveValue")
  fun onCalculateExpensiveValue(@Prop lifecycleTracker: LifecycleTracker): Int {
    lifecycleTracker.addStep(LifecycleStep.ON_CALCULATE_CACHED_VALUE)
    return 0
  }

  @JvmStatic
  @OnCreateTreeProp
  fun onCreateTreePropRect(c: ComponentContext, @Prop lifecycleTracker: LifecycleTracker): Rect {
    lifecycleTracker.addStep(LifecycleStep.ON_CREATE_TREE_PROP)
    return Rect()
  }

  object StaticContainer {
    @SuppressLint("StaticFieldLeak") var sLastCreatedDrawable: ColorDrawable? = null
  }
}


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

package com.facebook.litho.widget

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.annotation.UiThread
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.LifecycleStep
import com.facebook.litho.LifecycleTracker
import com.facebook.litho.MountContentPool
import com.facebook.litho.Size
import com.facebook.litho.SizeSpec
import com.facebook.litho.StateValue
import com.facebook.litho.TrackingMountContentPool
import com.facebook.litho.annotations.CachedValue
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnAttached
import com.facebook.litho.annotations.OnBind
import com.facebook.litho.annotations.OnBoundsDefined
import com.facebook.litho.annotations.OnCalculateCachedValue
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnCreateMountContentPool
import com.facebook.litho.annotations.OnCreateTreeProp
import com.facebook.litho.annotations.OnDetached
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.OnPrepare
import com.facebook.litho.annotations.OnUnbind
import com.facebook.litho.annotations.OnUnmount
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.State

@MountSpec
object MountSpecLifecycleTesterSpec {

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(
      context: ComponentContext,
      dummyState: StateValue<Any?>,
      @Prop lifecycleTracker: LifecycleTracker
  ) {
    dummyState.set(Any())
    lifecycleTracker.addStep(LifecycleStep.ON_CREATE_INITIAL_STATE)
  }

  @JvmStatic
  @OnPrepare
  fun onPrepare(
      c: ComponentContext,
      @Prop lifecycleTracker: LifecycleTracker,
      @State dummyState: Any?,
      @CachedValue expensiveValue: Int,
  ) {
    lifecycleTracker.addStep(LifecycleStep.ON_PREPARE)
  }

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      c: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size,
      @Prop lifecycleTracker: LifecycleTracker,
      @Prop(optional = true) intrinsicSize: Size?,
  ) {
    val width = SizeSpec.getSize(widthSpec)
    val height = SizeSpec.getSize(heightSpec)
    if (intrinsicSize != null) {
      size.width = SizeSpec.resolveSize(widthSpec, intrinsicSize.width)
      size.height = SizeSpec.resolveSize(heightSpec, intrinsicSize.height)
    } else {
      size.width = width
      size.height = height
    }
    lifecycleTracker.addStep(LifecycleStep.ON_MEASURE, size)
  }

  @JvmStatic
  @OnBoundsDefined
  fun onBoundsDefined(
      c: ComponentContext,
      layout: ComponentLayout,
      @Prop lifecycleTracker: LifecycleTracker,
  ) {
    val bounds = Rect(layout.x, layout.y, layout.x + layout.width, layout.y + layout.height)
    lifecycleTracker.addStep(LifecycleStep.ON_BOUNDS_DEFINED, bounds)
  }

  @JvmStatic
  @UiThread
  @OnCreateMountContent
  fun onCreateMountContent(c: Context): View? {
    StaticContainer.sLastCreatedView = View(c)
    return StaticContainer.sLastCreatedView
  }

  @JvmStatic
  @OnCreateMountContentPool
  fun onCreateMountContentPool(): MountContentPool =
      TrackingMountContentPool("MountSpecLifecycleTester", 1, true)

  @JvmStatic
  @UiThread
  @OnMount
  fun onMount(
      context: ComponentContext,
      view: View,
      @Prop lifecycleTracker: LifecycleTracker,
      @Prop(optional = true) defaultAlpha: Float,
  ) {
    // TODO: (T64290961) Remove the StaticContainer hack for tracing OnCreateMountContent callback.
    if (view === StaticContainer.sLastCreatedView) {
      lifecycleTracker.addStep(LifecycleStep.ON_CREATE_MOUNT_CONTENT)
      StaticContainer.sLastCreatedView = null
    }
    lifecycleTracker.addStep(LifecycleStep.ON_MOUNT)
    if (defaultAlpha != 0f) {
      view.alpha = defaultAlpha
    }
  }

  @JvmStatic
  @UiThread
  @OnUnmount
  fun onUnmount(
      context: ComponentContext,
      view: View,
      @Prop lifecycleTracker: LifecycleTracker,
      @Prop(optional = true) defaultAlpha: Float
  ) {
    lifecycleTracker.addStep(LifecycleStep.ON_UNMOUNT)
    if (defaultAlpha != 0f) {
      view.alpha = defaultAlpha
    }
  }

  @JvmStatic
  @UiThread
  @OnBind
  fun onBind(
      c: ComponentContext,
      view: View,
      @Prop lifecycleTracker: LifecycleTracker,
      @Prop(optional = true) defaultScale: Float,
  ) {
    lifecycleTracker.addStep(LifecycleStep.ON_BIND)
    if (defaultScale != 0f) {
      view.scaleX = defaultScale
      view.scaleY = defaultScale
    }
  }

  @JvmStatic
  @UiThread
  @OnUnbind
  fun onUnbind(
      c: ComponentContext,
      view: View,
      @Prop lifecycleTracker: LifecycleTracker,
      @Prop(optional = true) defaultScale: Float
  ) {
    lifecycleTracker.addStep(LifecycleStep.ON_UNBIND)
    if (defaultScale != 0f) {
      view.scaleX = defaultScale
      view.scaleY = defaultScale
    }
  }

  @JvmStatic
  @OnAttached
  fun onAttached(c: ComponentContext, @Prop lifecycleTracker: LifecycleTracker) {
    lifecycleTracker.addStep(LifecycleStep.ON_ATTACHED)
  }

  @JvmStatic
  @OnDetached
  fun onDetached(c: ComponentContext, @Prop lifecycleTracker: LifecycleTracker) {
    lifecycleTracker.addStep(LifecycleStep.ON_DETACHED)
  }

  @JvmStatic
  @OnCalculateCachedValue(name = "expensiveValue")
  fun onCalculateExpensiveValue(@Prop lifecycleTracker: LifecycleTracker): Int {
    lifecycleTracker.addStep(LifecycleStep.ON_CALCULATE_CACHED_VALUE)
    return 0
  }

  @JvmStatic
  @OnCreateTreeProp
  fun onCreateTreePropRect(c: ComponentContext, @Prop lifecycleTracker: LifecycleTracker): Rect {
    lifecycleTracker.addStep(LifecycleStep.ON_CREATE_TREE_PROP)
    return Rect()
  }

  object StaticContainer {
    @SuppressLint("StaticFieldLeak") var sLastCreatedView: View? = null
  }
}


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

package com.facebook.litho.widget

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.annotation.UiThread
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.Diff
import com.facebook.litho.LifecycleStep
import com.facebook.litho.LifecycleTracker
import com.facebook.litho.MountContentPool
import com.facebook.litho.Size
import com.facebook.litho.SizeSpec
import com.facebook.litho.StateValue
import com.facebook.litho.TrackingMountContentPool
import com.facebook.litho.annotations.CachedValue
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnAttached
import com.facebook.litho.annotations.OnBind
import com.facebook.litho.annotations.OnBoundsDefined
import com.facebook.litho.annotations.OnCalculateCachedValue
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnCreateMountContentPool
import com.facebook.litho.annotations.OnCreateTreeProp
import com.facebook.litho.annotations.OnDetached
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.OnPrepare
import com.facebook.litho.annotations.OnUnbind
import com.facebook.litho.annotations.OnUnmount
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.ShouldUpdate
import com.facebook.litho.annotations.State

@MountSpec(isPureRender = true)
object MountSpecPureRenderLifecycleTesterSpec {

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(
      context: ComponentContext,
      dummyState: StateValue<Any?>,
      @Prop lifecycleTracker: LifecycleTracker
  ) {
    dummyState.set(Any())
    lifecycleTracker.addStep(LifecycleStep.ON_CREATE_INITIAL_STATE)
  }

  @JvmStatic
  @OnPrepare
  fun onPrepare(
      c: ComponentContext,
      @Prop lifecycleTracker: LifecycleTracker,
      @State dummyState: Any?,
      @CachedValue expensiveValue: Int
  ) {
    lifecycleTracker.addStep(LifecycleStep.ON_PREPARE)
  }

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      c: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size,
      @Prop lifecycleTracker: LifecycleTracker,
      @Prop(optional = true) intrinsicSize: Size?
  ) {
    val width = SizeSpec.getSize(widthSpec)
    val height = SizeSpec.getSize(heightSpec)
    if (intrinsicSize != null) {
      size.width = SizeSpec.resolveSize(widthSpec, intrinsicSize.width)
      size.height = SizeSpec.resolveSize(heightSpec, intrinsicSize.height)
    } else {
      size.width = width
      size.height = height
    }
    lifecycleTracker.addStep(LifecycleStep.ON_MEASURE, size)
  }

  @JvmStatic
  @OnBoundsDefined
  fun onBoundsDefined(
      c: ComponentContext,
      layout: ComponentLayout,
      @Prop lifecycleTracker: LifecycleTracker
  ) {
    val bounds = Rect(layout.x, layout.y, layout.x + layout.width, layout.y + layout.height)
    lifecycleTracker.addStep(LifecycleStep.ON_BOUNDS_DEFINED, bounds)
  }

  @JvmStatic
  @UiThread
  @OnCreateMountContent
  fun onCreateMountContent(c: Context): View? {
    StaticContainer.sLastCreatedView = View(c)
    return StaticContainer.sLastCreatedView
  }

  @JvmStatic
  @OnCreateMountContentPool
  fun onCreateMountContentPool(): MountContentPool =
      TrackingMountContentPool("MountSpecLifecycleTester", 1, true)

  @JvmStatic
  @UiThread
  @OnMount
  fun onMount(context: ComponentContext, view: View, @Prop lifecycleTracker: LifecycleTracker) {
    // TODO: (T64290961) Remove the StaticContainer hack for tracing OnCreateMountContent callback.
    if (view === StaticContainer.sLastCreatedView) {
      lifecycleTracker.addStep(LifecycleStep.ON_CREATE_MOUNT_CONTENT)
      StaticContainer.sLastCreatedView = null
    }
    lifecycleTracker.addStep(LifecycleStep.ON_MOUNT)
  }

  @JvmStatic
  @UiThread
  @OnUnmount
  fun onUnmount(context: ComponentContext, view: View, @Prop lifecycleTracker: LifecycleTracker) {
    lifecycleTracker.addStep(LifecycleStep.ON_UNMOUNT)
  }

  @JvmStatic
  @UiThread
  @OnBind
  fun onBind(c: ComponentContext, view: View, @Prop lifecycleTracker: LifecycleTracker) {
    lifecycleTracker.addStep(LifecycleStep.ON_BIND)
  }

  @JvmStatic
  @UiThread
  @OnUnbind
  fun onUnbind(c: ComponentContext, view: View, @Prop lifecycleTracker: LifecycleTracker) {
    lifecycleTracker.addStep(LifecycleStep.ON_UNBIND)
  }

  @JvmStatic
  @OnAttached
  fun onAttached(c: ComponentContext, @Prop lifecycleTracker: LifecycleTracker) {
    lifecycleTracker.addStep(LifecycleStep.ON_ATTACHED)
  }

  @JvmStatic
  @OnDetached
  fun onDetached(c: ComponentContext, @Prop lifecycleTracker: LifecycleTracker) {
    lifecycleTracker.addStep(LifecycleStep.ON_DETACHED)
  }

  @JvmStatic
  @OnCalculateCachedValue(name = "expensiveValue")
  fun onCalculateExpensiveValue(@Prop lifecycleTracker: LifecycleTracker): Int {
    lifecycleTracker.addStep(LifecycleStep.ON_CALCULATE_CACHED_VALUE)
    return 0
  }

  @JvmStatic
  @OnCreateTreeProp
  fun onCreateTreePropRect(c: ComponentContext, @Prop lifecycleTracker: LifecycleTracker): Rect {
    lifecycleTracker.addStep(LifecycleStep.ON_CREATE_TREE_PROP)
    return Rect()
  }

  @JvmStatic
  @ShouldUpdate
  fun shouldUpdate(
      @Prop(optional = true) shouldUpdate: Diff<Boolean>?,
      @Prop lifecycleTracker: Diff<LifecycleTracker>
  ): Boolean {
    lifecycleTracker.next!!.addStep(LifecycleStep.SHOULD_UPDATE)
    return shouldUpdate?.next ?: true
  }

  object StaticContainer {
    @SuppressLint("StaticFieldLeak") var sLastCreatedView: View? = null
  }
}


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

package com.facebook.litho.widget

import android.content.Context
import android.view.View
import androidx.annotation.UiThread
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.LifecycleStep
import com.facebook.litho.LifecycleStep.StepInfo
import com.facebook.litho.Size
import com.facebook.litho.TestTriggerEvent
import com.facebook.litho.annotations.FromTrigger
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnPrepare
import com.facebook.litho.annotations.OnTrigger
import com.facebook.litho.annotations.Prop
import java.util.concurrent.atomic.AtomicReference

@MountSpec
internal object MountSpecTriggerTesterSpec {

  @JvmStatic @OnPrepare fun onPrepare(c: ComponentContext) = Unit

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      context: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size
  ) {
    size.width = 600
    size.height = 800
  }

  @JvmStatic @UiThread @OnCreateMountContent fun onCreateMountContent(c: Context): View = View(c)

  @JvmStatic
  @OnTrigger(TestTriggerEvent::class)
  fun triggerTestEvent(
      c: ComponentContext,
      @Prop steps: MutableList<StepInfo>,
      @Prop triggerObjectRef: AtomicReference<Any?>,
      @FromTrigger triggerObject: Any?
  ) {
    steps.add(StepInfo(LifecycleStep.ON_TRIGGER))
    triggerObjectRef.set(triggerObject)
  }
}


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

import android.content.Context
import android.view.View
import androidx.annotation.UiThread
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.Size
import com.facebook.litho.StateValue
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnBind
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.OnUnbind
import com.facebook.litho.annotations.OnUnmount
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.State

/**
 * This MountSpec throws an exception if @OnUnmount is invoked on the component before @OnMount was
 * invoked for it. This allows tests to assert that @OnUnmount is always invoked on a component for
 * which @OnMount was invoked.
 */
@MountSpec
object MountSpecWithMountUnmountAssertionSpec {

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(
      c: ComponentContext,
      @Prop container: Container,
      holder: StateValue<Container>
  ) {
    holder.set(container)
  }

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      context: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size
  ) {
    size.width = 100
    size.height = 100
  }

  @JvmStatic @UiThread @OnCreateMountContent fun onCreateMountContent(c: Context): View = View(c)

  @JvmStatic
  @UiThread
  @OnMount
  fun onMount(
      context: ComponentContext,
      view: View,
      @State holder: Container,
      @Prop(optional = true) hasTagSet: Boolean
  ) {
    holder.value = "mounted"
    check(view.parent == null) { "The view must be attached after mount is called" }
    check(!(hasTagSet && view.tag != null)) { "The tag must be set after mount is called." }
  }

  @JvmStatic
  @UiThread
  @OnUnmount
  fun onUnmount(
      context: ComponentContext,
      view: View,
      @State holder: Container,
      @Prop(optional = true) hasTagSet: Boolean
  ) {
    checkNotNull(holder.value) {
      ("The value was never set in @onMount. Which means that @OnMount was not invoked for this instance of the component or @OnUnmount was called without @OnMount.")
    }
    check(view.parent == null) { "The view must be detached before unmount is called" }
    check(!(hasTagSet && view.tag != null)) { "The tag must be unset before unmount is called." }
  }

  @JvmStatic
  @OnBind
  fun onBind(c: ComponentContext, content: View, @Prop(optional = true) hasTagSet: Boolean) {
    checkNotNull(content.parent) { "The view must be attached when bind is called" }
    check(!(hasTagSet && content.tag == null)) { "The tag must be set before bind is called." }
  }

  @JvmStatic
  @UiThread
  @OnUnbind
  fun onUnbind(c: ComponentContext, content: View, @Prop(optional = true) hasTagSet: Boolean) {
    check(content.parent == null) { "The view must be detached when unbind is called" }
    check(!(hasTagSet && content.tag == null)) { "The tag must be set when unbind is called." }
  }

  class Container {
    var value: String? = null
  }
}


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

package com.facebook.litho.widget

import android.content.Context
import android.view.View
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.Diff
import com.facebook.litho.LifecycleStep
import com.facebook.litho.Size
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.OnUnmount
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.ShouldUpdate

/**
 * A MountSpec that lets us test the function of ShouldUpdate/pure-render by supplying an object
 * who's identity determines the result of @ShouldUpdate.
 */
@MountSpec(isPureRender = true)
object MountSpecWithShouldUpdateSpec {

  @JvmStatic @OnCreateMountContent fun onCreateMountContent(c: Context): View = View(c)

  @JvmStatic
  @ShouldUpdate(onMount = true)
  fun shouldUpdate(@Prop objectForShouldUpdate: Diff<Any>): Boolean =
      objectForShouldUpdate.previous != objectForShouldUpdate.next

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      c: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size,
      @Prop operationsOutput: MutableList<LifecycleStep>
  ) {
    size.height = 100
    size.width = 100
    operationsOutput.add(LifecycleStep.ON_MEASURE)
  }

  @JvmStatic
  @OnMount
  fun onMount(
      c: ComponentContext,
      v: View,
      @Prop operationsOutput: MutableList<LifecycleStep>,
      @Prop objectForShouldUpdate: Any?
  ) {
    operationsOutput.add(LifecycleStep.ON_MOUNT)
  }

  @JvmStatic
  @OnUnmount
  fun onUnmount(
      c: ComponentContext,
      v: View,
      @Prop operationsOutput: MutableList<LifecycleStep>,
      @Prop objectForShouldUpdate: Any?
  ) {
    operationsOutput.add(LifecycleStep.ON_UNMOUNT)
  }
}


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

import android.content.Context
import android.widget.TextView
import androidx.annotation.UiThread
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.Size
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.State
import com.facebook.litho.annotations.TreeProp
import com.facebook.litho.widget.treeprops.SimpleTreeProp

@MountSpec(isPureRender = true)
internal object MountSpecWithTreePropSpec {

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      c: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size,
      @Prop viewWidth: Int,
      @Prop viewHeight: Int
  ) {
    size.width = viewWidth
    size.height = viewHeight
  }

  @JvmStatic
  @UiThread
  @OnCreateMountContent
  fun onCreateMountContent(c: Context): TextView = TextView(c)

  @JvmStatic
  @UiThread
  @OnMount
  fun onMount(
      c: ComponentContext,
      view: TextView,
      @State count: Int,
      @TreeProp treeProp: SimpleTreeProp
  ) {
    view.text = treeProp.name
  }
}


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

package com.facebook.litho.widget

import android.content.Context
import android.view.View
import androidx.annotation.UiThread
import com.facebook.litho.BoundaryWorkingRange
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.LifecycleStep
import com.facebook.litho.LifecycleStep.StepInfo
import com.facebook.litho.Size
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnEnteredRange
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnPrepare
import com.facebook.litho.annotations.OnRegisterRanges
import com.facebook.litho.annotations.Prop

@MountSpec
internal object MountSpecWorkingRangeTesterSpec {

  @JvmStatic @OnPrepare fun onPrepare(c: ComponentContext) = Unit

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      context: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size
  ) {
    size.width = 600
    size.height = 800
  }

  @JvmStatic @UiThread @OnCreateMountContent fun onCreateMountContent(c: Context): View = View(c)

  @JvmStatic
  @OnRegisterRanges
  fun registerWorkingRanges(c: ComponentContext, @Prop steps: MutableList<StepInfo>) {
    steps.add(StepInfo(LifecycleStep.ON_REGISTER_RANGES))
    MountSpecWorkingRangeTester.registerBoundaryWorkingRange(c, BoundaryWorkingRange())
  }

  @JvmStatic
  @OnEnteredRange(name = "boundary")
  fun onEnteredWorkingRange(c: ComponentContext, @Prop steps: MutableList<StepInfo>) {
    steps.add(StepInfo(LifecycleStep.ON_ENTERED_RANGE))
  }
}


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

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.TreeProp

@LayoutSpec
object NestedTreeChildComponentSpec {
  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(
      context: ComponentContext,
      @TreeProp a: RootComponentWithTreePropsSpec.A?,
      @TreeProp b: NestedTreeParentComponentSpec.B?,
      @TreeProp c: NestedTreeComponentSpec.C?
  ): Component {
    checkNotNull(a) { "A is null" }
    checkNotNull(b) { "B is null" }
    checkNotNull(c) { "C is null" }
    return Column.create(context)
        .key("Column")
        .child(CardClip.create(context).key("CardClip").widthDip(10f).heightDip(10f))
        .build()
  }
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.LifecycleStep
import com.facebook.litho.Row
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayoutWithSizeSpec
import com.facebook.litho.annotations.OnCreateTreeProp
import com.facebook.litho.annotations.TreeProp
import com.facebook.yoga.YogaDirection

@LayoutSpec
object NestedTreeComponentSpec {

  @JvmStatic @OnCreateTreeProp fun onCreateTreeProp(c: ComponentContext): C = C()

  @JvmStatic
  @OnCreateLayoutWithSizeSpec
  fun onCreateLayout(
      c: ComponentContext,
      widthSpec: Int,
      heightSpec: Int,
      @TreeProp props: ExtraProps?
  ): Component {

    // Added to swallow the lint error.
    val s = String.format("w:%d,h:%d", widthSpec, heightSpec)
    if (props?.steps != null) {
      props.steps!!.add(LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
    }
    val row =
        Row.create(c)
            .key("Row")
            .child(NestedTreeChildComponent.create(c).key("NestedTreeChildComponent"))
    if (props?.mDirection != null) {
      row.layoutDirection(props.mDirection)
    }
    return row.build()
  }

  class C

  class ExtraProps {
    @JvmField var steps: MutableList<LifecycleStep>? = null
    @JvmField var mDirection: YogaDirection? = null
  }
}


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

package com.facebook.litho.widget

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.SizeSpec
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayoutWithSizeSpec
import com.facebook.litho.annotations.Prop

@LayoutSpec
object NestedTreeComponentWithChildStateSpec {
  @JvmStatic
  @OnCreateLayoutWithSizeSpec
  fun onCreateLayoutWithSizeSpec(
      c: ComponentContext,
      widthSpec: Int,
      heightSpec: Int,
      @Prop caller: SimpleStateUpdateEmulatorSpec.Caller
  ): Component =
      SimpleStateUpdateEmulator.create(c)
          .caller(caller)
          .widthPx(SizeSpec.getSize(widthSpec))
          .heightPx(SizeSpec.getSize(heightSpec))
          .prefix("Count: ")
          .build()
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnCreateTreeProp
import com.facebook.yoga.YogaEdge

@LayoutSpec
object NestedTreeParentComponentSpec {

  const val ROOT_KEY = "NestedTreeParentComponent"

  @JvmStatic @OnCreateTreeProp fun onCreateTreeProp(c: ComponentContext): B = B()

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(c: ComponentContext): Component =
      NestedTreeComponent.create(c).paddingPx(YogaEdge.ALL, 5).key("NestedTreeComponent").build()

  @JvmStatic
  fun create(c: ComponentContext): NestedTreeParentComponent =
      NestedTreeParentComponent.create(c).key(ROOT_KEY).build()

  class B
}


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

import android.view.View
import com.facebook.litho.ClickEvent
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.annotations.FromEvent
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnEvent
import com.facebook.litho.annotations.Prop

@LayoutSpec
internal object OnClickCallbackComponentSpec {

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(c: ComponentContext, @Prop callback: View.OnClickListener): Component =
      Column.create(c)
          .child(Text.create(c).text("hello world"))
          .clickHandler(OnClickCallbackComponent.onClick(c))
          .build()

  @JvmStatic
  @OnEvent(ClickEvent::class)
  fun onClick(c: ComponentContext, @Prop callback: View.OnClickListener, @FromEvent view: View) {
    callback.onClick(view)
  }
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout

@LayoutSpec
internal object OnErrorNotPresentChildSpec {
  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(c: ComponentContext): Component =
      ThrowExceptionGrandChildTester.create(c).build()
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentUtils
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnError
import com.facebook.litho.annotations.Prop

@LayoutSpec
internal object OnErrorPassUpChildTesterSpec {

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(c: ComponentContext, @Prop info: List<String>): Component =
      ThrowExceptionGrandChildTester.create(c).build()

  @JvmStatic
  @OnError
  fun onError(c: ComponentContext, error: Exception, @Prop info: MutableList<String>) {
    info.add("OnErrorPassUpChildTesterSpec->onError")
    ComponentUtils.raise(c, error)
  }
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentUtils
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnError
import com.facebook.litho.annotations.Prop

@LayoutSpec
internal object OnErrorPassUpParentTesterSpec {

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(
      c: ComponentContext,
      @Prop info: List<String>,
      @Prop child: Component
  ): Component = child

  @JvmStatic
  @OnError
  fun onError(c: ComponentContext, error: Exception, @Prop info: MutableList<String>) {
    info.add("OnErrorPassUpParentTesterSpec->onError")
    ComponentUtils.raise(c, error)
  }
}


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

import android.content.Context
import android.view.View
import androidx.annotation.UiThread
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.Size
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.Prop

@MountSpec
object OnMeasureCallbackComponentSpec {

  @JvmStatic
  @UiThread
  @OnCreateMountContent
  fun onCreateMountContent(context: Context): View = View(context)

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      c: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size,
      @Prop callback: Callback
  ) {
    callback.onMeasure(widthSpec, heightSpec)
  }

  fun interface Callback {
    fun onMeasure(widthSpec: Int, heightSpec: Int)
  }
}


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

package com.facebook.litho.widget

import android.annotation.SuppressLint
import android.content.Context
import android.view.View
import androidx.annotation.UiThread
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.LifecycleStep
import com.facebook.litho.LifecycleStep.StepInfo
import com.facebook.litho.MountContentPool
import com.facebook.litho.Size
import com.facebook.litho.TrackingMountContentPool
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnAttached
import com.facebook.litho.annotations.OnBind
import com.facebook.litho.annotations.OnBoundsDefined
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnCreateMountContentPool
import com.facebook.litho.annotations.OnDetached
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.OnPrepare
import com.facebook.litho.annotations.OnUnbind
import com.facebook.litho.annotations.OnUnmount
import com.facebook.litho.annotations.Prop

@MountSpec(canPreallocate = true)
object PreallocatedMountSpecLifecycleTesterSpec {

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(context: ComponentContext, @Prop steps: MutableList<StepInfo>) {
    steps.add(StepInfo(LifecycleStep.ON_CREATE_INITIAL_STATE))
  }

  @JvmStatic
  @OnPrepare
  fun onPrepare(c: ComponentContext, @Prop steps: MutableList<StepInfo>) {
    steps.add(StepInfo(LifecycleStep.ON_PREPARE))
  }

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      c: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size,
      @Prop steps: MutableList<StepInfo>
  ) {
    steps.add(StepInfo(LifecycleStep.ON_MEASURE))
    size.width = 600
    size.height = 800
  }

  @JvmStatic
  @OnBoundsDefined
  fun onBoundsDefined(
      c: ComponentContext,
      layout: ComponentLayout,
      @Prop steps: MutableList<StepInfo>
  ) {
    steps.add(StepInfo(LifecycleStep.ON_BOUNDS_DEFINED))
  }

  @JvmStatic
  @UiThread
  @OnCreateMountContentPool
  fun onCreateMountContentPool(): MountContentPool =
      TrackingMountContentPool("PreallocatedMountSpecLifecycleTester", 1, true)

  @JvmStatic
  @UiThread
  @OnCreateMountContent
  fun onCreateMountContent(c: Context): View? {
    StaticContainer.sLastCreatedView = View(c)
    return StaticContainer.sLastCreatedView
  }

  @JvmStatic
  @UiThread
  @OnMount
  fun onMount(context: ComponentContext, view: View, @Prop steps: MutableList<StepInfo>) {
    // TODO: (T64290961) Remove the StaticContainer hack for tracing OnCreateMountContent callback.
    if (view === StaticContainer.sLastCreatedView) {
      steps.add(StepInfo(LifecycleStep.ON_CREATE_MOUNT_CONTENT))
      StaticContainer.sLastCreatedView = null
    }
    steps.add(StepInfo(LifecycleStep.ON_MOUNT))
  }

  @JvmStatic
  @UiThread
  @OnUnmount
  fun onUnmount(context: ComponentContext, view: View, @Prop steps: MutableList<StepInfo>) {
    steps.add(StepInfo(LifecycleStep.ON_UNMOUNT))
  }

  @JvmStatic
  @UiThread
  @OnBind
  fun onBind(c: ComponentContext, view: View, @Prop steps: MutableList<StepInfo>) {
    steps.add(StepInfo(LifecycleStep.ON_BIND))
  }

  @JvmStatic
  @UiThread
  @OnUnbind
  fun onUnbind(c: ComponentContext, view: View, @Prop steps: MutableList<StepInfo>) {
    steps.add(StepInfo(LifecycleStep.ON_UNBIND))
  }

  @JvmStatic
  @OnAttached
  fun onAttached(c: ComponentContext, @Prop steps: MutableList<StepInfo>) {
    steps.add(StepInfo(LifecycleStep.ON_ATTACHED))
  }

  @JvmStatic
  @OnDetached
  fun onDetached(c: ComponentContext, @Prop steps: MutableList<StepInfo>) {
    steps.add(StepInfo(LifecycleStep.ON_DETACHED))
  }

  object StaticContainer {
    @SuppressLint("StaticFieldLeak") var sLastCreatedView: View? = null
  }
}


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

package com.facebook.litho.widget

import android.content.Context
import android.view.View
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.Diff
import com.facebook.litho.Size
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.ShouldUpdate

/**
 * A MountSpec that lets us test the function of ShouldUpdate/pure-render by supplying an object
 * who's identity determines the result of @ShouldUpdate.
 */
@MountSpec(isPureRender = true)
object RecordsShouldUpdateSpec {

  @JvmStatic
  @OnMeasure
  fun onMeasure(c: ComponentContext, layout: ComponentLayout, w: Int, h: Int, size: Size) {
    size.width = 100
    size.height = 100
  }

  @JvmStatic @OnCreateMountContent fun onCreateMountContent(c: Context): View = View(c)

  @JvmStatic
  @ShouldUpdate(onMount = true)
  fun shouldUpdate(
      @Prop testProp: Diff<Any?>,
      @Prop shouldUpdateCalls: Diff<MutableList<Diff<Any?>>>
  ): Boolean {
    shouldUpdateCalls.next!!.add(testProp)
    return true
  }

  @JvmStatic
  @OnMount
  fun onMount(
      c: ComponentContext,
      v: View,
      @Prop testProp: Any?,
      @Prop shouldUpdateCalls: List<Diff<Any?>>
  ) = Unit
}


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

import android.content.Context
import android.view.View
import androidx.annotation.UiThread
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.RenderAndMeasureCounter
import com.facebook.litho.Size
import com.facebook.litho.SizeSpec
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnPrepare
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.ShouldUpdate

@MountSpec(isPureRender = true)
object RenderAndLayoutCountingTesterSpec {

  @JvmStatic
  @OnPrepare
  fun onPrepare(
      c: ComponentContext,
      @Prop renderAndMeasureCounter: RenderAndMeasureCounter,
      @Prop(optional = true) listener: Listener?
  ) {
    listener?.onPrepare()
    renderAndMeasureCounter.incrementRenderCount()
  }

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      c: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size,
      @Prop renderAndMeasureCounter: RenderAndMeasureCounter,
      @Prop(optional = true) listener: Listener?
  ) {
    listener?.onMeasure()
    size.width = SizeSpec.getSize(widthSpec)
    size.height = SizeSpec.getSize(heightSpec)
    renderAndMeasureCounter.incrementMeasureCount()
  }

  @JvmStatic @UiThread @OnCreateMountContent fun onCreateMountContent(c: Context): View = View(c)

  @JvmStatic @ShouldUpdate fun shouldUpdate(): Boolean = true

  interface Listener {
    fun onPrepare()

    fun onMeasure()
  }
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.LifecycleStep.StepInfo
import com.facebook.litho.Row
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnCreateTreeProp
import com.facebook.litho.annotations.Prop

@LayoutSpec
object RootComponentInterStagePropsSpec {

  @JvmStatic @OnCreateTreeProp fun onCreateTreeProp(c: ComponentContext): A = A()

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(c: ComponentContext, @Prop steps: List<StepInfo>): Component =
      Row.create(c)
          .key("Row")
          .child(Text.create(c).text("hello world"))
          .child(
              LayoutSpecInterStagePropsTester.create(c)
                  .steps(steps)
                  .key("LayoutSpecInterStagePropsTester")
                  .flexGrow(1f))
          .build()

  class A
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.Row
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnCreateTreeProp
import com.facebook.litho.annotations.Prop

@LayoutSpec
object RootComponentWithTreePropsSpec {

  @JvmStatic @OnCreateTreeProp fun onCreateTreeProp(c: ComponentContext): A = A()

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(
      c: ComponentContext,
      @Prop(optional = true) shouldNotUpdateState: Boolean
  ): Component =
      Row.create(c)
          .key("Row")
          .child(
              if (shouldNotUpdateState) Text.create(c).text("hello world")
              else ChildComponentWithStateUpdate.create(c))
          .child(NestedTreeParentComponent.create(c).key("NestedTreeParentComponent").flexGrow(1f))
          .build()

  class A
}


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

import android.view.View
import com.facebook.litho.ClickEvent
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.StateValue
import com.facebook.litho.annotations.FromEvent
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnEvent
import com.facebook.litho.annotations.State

@LayoutSpec
object SameManualKeyComponentSpec {

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(c: ComponentContext, state: StateValue<String>) {
    state.set("SameManualKeyComponent")
  }

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(c: ComponentContext, @State state: String): Component =
      Column.create(c).clickHandler(SameManualKeyComponent.onClick(c)).build()

  @JvmStatic
  @OnEvent(ClickEvent::class)
  fun onClick(c: ComponentContext, @FromEvent view: View, @State state: String) = Unit
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.StateValue
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.State

@LayoutSpec
object SameManualKeyContainerComponentSpec {

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(c: ComponentContext, state: StateValue<String>) {
    state.set("SameManualKeyContainerComponent")
  }

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(c: ComponentContext, @State state: String): Component =
      SameManualKeyComponent.create(c).key("key").build()
}


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

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout

@LayoutSpec
object SameManualKeyRootComponentSpec {

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(c: ComponentContext): Component =
      Column.create(c)
          .key("col")
          .child(SameManualKeyContainerComponent.create(c).key("key").build())
          .build()

  @JvmStatic
  fun create(c: ComponentContext): SameManualKeyRootComponent =
      SameManualKeyRootComponent.create(c).key("root").build()

  const val globalKeyForStateUpdate: String = "root,col,key,key"
}


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

import android.view.View
import com.facebook.litho.ClickEvent
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.LongClickEvent
import com.facebook.litho.annotations.FromEvent
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnEvent
import com.facebook.litho.widget.events.EventWithoutAnnotation

@LayoutSpec(events = [EventWithoutAnnotation::class])
object SimpleLayoutSpecWithClickHandlersTesterSpec {

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(c: ComponentContext): Component =
      Column.create(c)
          .clickHandler(SimpleLayoutSpecWithClickHandlersTester.onClickEvent(c))
          .longClickHandler(SimpleLayoutSpecWithClickHandlersTester.onLongClickEvent(c))
          .child(SimpleMountSpecTester.create(c))
          .build()

  @JvmStatic
  @OnEvent(ClickEvent::class)
  fun onClickEvent(c: ComponentContext, @FromEvent view: View) = Unit

  @JvmStatic
  @OnEvent(LongClickEvent::class)
  fun onLongClickEvent(c: ComponentContext, @FromEvent view: View): Boolean = false
}


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

import android.content.Context
import android.graphics.drawable.ColorDrawable
import androidx.annotation.UiThread
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentLayout
import com.facebook.litho.Size
import com.facebook.litho.SizeSpec
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnMeasure
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.Prop

@MountSpec
internal object SimpleMountSpecTesterSpec {

  @JvmStatic
  @OnMeasure
  fun onMeasure(
      context: ComponentContext,
      layout: ComponentLayout,
      widthSpec: Int,
      heightSpec: Int,
      size: Size,
      @Prop(optional = true) measuredWidth: Int?,
      @Prop(optional = true) measuredHeight: Int?
  ) {
    if (measuredWidth == null && measuredHeight == null) {
      size.width = SizeSpec.getSize(widthSpec)
      size.height = SizeSpec.getSize(heightSpec)
    } else {
      val width = measuredWidth ?: SizeSpec.UNSPECIFIED
      val height = measuredHeight ?: SizeSpec.UNSPECIFIED
      size.width = SizeSpec.resolveSize(widthSpec, width)
      size.height = SizeSpec.resolveSize(heightSpec, height)
    }
  }

  @JvmStatic
  @UiThread
  @OnCreateMountContent
  fun onCreateMountContent(c: Context): ColorDrawable = ColorDrawable()

  @JvmStatic
  @OnMount
  fun onMount(c: ComponentContext, drawable: ColorDrawable, @Prop(optional = true) color: Int) {
    drawable.color = color
  }
}


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

import android.content.Context
import android.widget.FrameLayout
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentTree
import com.facebook.litho.LithoView
import com.facebook.litho.MeasureScope
import com.facebook.litho.MountableComponent
import com.facebook.litho.MountableComponentScope
import com.facebook.litho.MountableRenderResult
import com.facebook.litho.SimpleMountable
import com.facebook.litho.Size
import com.facebook.litho.useState
import com.facebook.rendercore.MeasureResult

/** Renders the given component in a nested LithoView. */
class SimpleNestedTreeComponent(private val contentComponent: Component) : MountableComponent() {
  override fun MountableComponentScope.render(): MountableRenderResult {
    val componentTree = useState {
      ComponentTree.create(ComponentContext.makeCopyForNestedTree(context))
          .incrementalMount(false)
          .build()
    }
    return MountableRenderResult(
        SimpleNestedTreeMountable(contentComponent, componentTree.value), null)
  }
}

private class SimpleNestedTreeMountable(
    private val contentComponent: Component,
    private val componentTree: ComponentTree
) : SimpleMountable<FrameLayout>(RenderType.VIEW) {
  override fun MeasureScope.measure(widthSpec: Int, heightSpec: Int): MeasureResult {
    val size = Size()
    componentTree.setRootAndSizeSpecSync(contentComponent, widthSpec, heightSpec, size)
    return MeasureResult(size.width, size.height)
  }

  override fun mount(c: Context, content: FrameLayout, layoutData: Any?) {
    val lithoView = content.getChildAt(0) as LithoView
    lithoView.componentTree = componentTree
  }

  override fun unmount(c: Context, content: FrameLayout, layoutData: Any?) {
    val lithoView = content.getChildAt(0) as LithoView
    lithoView.componentTree = null
  }

  // FrameLayout is used here due to some bugs around having a LithoView as the direct content type.
  override fun createContent(context: Context): FrameLayout {
    val lithoView = LithoView(context)
    val frameLayout = FrameLayout(context)
    frameLayout.addView(
        lithoView, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
    return frameLayout
  }
}


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

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.StateValue
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnUpdateState
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.State

@LayoutSpec
object SimpleStateUpdateEmulatorSpec {

  const val INITIAL_COUNT = 1

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(
      c: ComponentContext,
      count: StateValue<Int>,
      @Prop(optional = true) initialCount: Int?,
  ) {
    count.set(initialCount ?: 1)
  }

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(
      c: ComponentContext,
      @Prop caller: Caller,
      @Prop(optional = true) prefix: String?,
      @State count: Int
  ): Component? {
    caller.set(c)
    return if (count > 0) {
      Column.create(c).child(Text.create(c).text((prefix ?: "Text: ") + count)).build()
    } else {
      null
    }
  }

  @JvmStatic
  @OnUpdateState
  fun onIncrementCount(count: StateValue<Int>) {
    val counter = count.get() ?: throw RuntimeException("state value is null.")
    count.set(counter + 1)
  }

  @JvmStatic
  @OnUpdateState
  fun onDecrementCount(count: StateValue<Int>) {
    val counter = count.get() ?: throw RuntimeException("state value is null.")
    count.set(counter - 1)
  }

  class Caller {
    var c: ComponentContext? = null

    fun set(c: ComponentContext?) {
      this.c = c
    }

    fun increment() {
      SimpleStateUpdateEmulator.onIncrementCountSync(c)
    }

    fun decrement() {
      SimpleStateUpdateEmulator.onDecrementCountSync(c)
    }

    fun incrementAsync() {
      SimpleStateUpdateEmulator.onIncrementCount(c)
    }
  }
}


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

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.LifecycleStep.StepInfo
import com.facebook.litho.StateValue
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnUpdateState
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.State
import java.util.ArrayList

@LayoutSpec
object SimpleStateUpdateEmulatorWillRenderSpec {

  const val INITIAL_COUNT = 1

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(c: ComponentContext, count: StateValue<Int>) {
    count.set(1)
  }

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(
      c: ComponentContext,
      @Prop caller: Caller,
      @Prop(optional = true) prefix: String?,
      @State count: Int
  ): Component {
    val info: List<StepInfo> = ArrayList()
    caller.set(c)
    return Column.create(c)
        .child(Text.create(c).text((prefix ?: "Text: ") + count))
        .child(LayoutSpecWillRenderReuseTester.create(c).steps(info))
        .build()
  }

  @JvmStatic
  @OnUpdateState
  fun onIncrementCount(count: StateValue<Int>) {
    val counter = count.get() ?: throw RuntimeException("state value is null.")
    count.set(counter + 1)
  }

  class Caller {
    var c: ComponentContext? = null

    fun set(c: ComponentContext?) {
      this.c = c
    }

    fun increment() {
      SimpleStateUpdateEmulator.onIncrementCountSync(c)
    }

    fun incrementAsync() {
      SimpleStateUpdateEmulator.onIncrementCount(c)
    }
  }
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.Row
import com.facebook.litho.Size
import com.facebook.litho.SizeSpec
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.TreeProp

@LayoutSpec
object SizeTreePropComponentSpec {
  private const val DEFAULT_SIZE = 400

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(c: ComponentContext, @TreeProp size: Size): Component {
    val component =
        if (SizeSpec.getSize(size.width) < DEFAULT_SIZE &&
            SizeSpec.getSize(size.height) < DEFAULT_SIZE) {
          throw IllegalStateException("Available width and height needs to be > ${DEFAULT_SIZE}")
        } else {
          Row.create(c).widthPx(DEFAULT_SIZE)
        }
    return component.build()
  }
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.StateCaller
import com.facebook.litho.Transition
import com.facebook.litho.animation.AnimatedProperties
import com.facebook.litho.onCleanup
import com.facebook.litho.transition.useTransition
import com.facebook.litho.useEffect
import com.facebook.litho.useState

class StateWithTransitionTestComponent(
    private val stateCaller: StateCaller,
    private val componentCall: ComponentScope.(Boolean) -> Component,
) : KComponent() {

  override fun ComponentScope.render(): Component {
    val transition = useState<Transition?> { null }
    useTransition(transition.value)
    val state = useState { false }
    useEffect(stateCaller) {
      stateCaller.setStateUpdateListener {
        state.update { !it }
        transition.update {
          Transition.sequence(
              Transition.create(RED_TRANSITION_KEY)
                  .animator(Transition.timing(144))
                  .animate(AnimatedProperties.X),
              Transition.create(GREEN_TRANSITION_KEY)
                  .animator(Transition.timing(144))
                  .animate(AnimatedProperties.X),
              Transition.create(BLUE_TRANSITION_KEY)
                  .animator(Transition.timing(144))
                  .animate(AnimatedProperties.X))
        }
      }
      onCleanup { stateCaller.setStateUpdateListener(null) }
    }
    return componentCall(state.value)
  }

  companion object {
    const val RED_TRANSITION_KEY = "red"
    const val GREEN_TRANSITION_KEY = "green"
    const val BLUE_TRANSITION_KEY = "blue"
  }
}


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

import android.content.Context
import android.graphics.Color
import com.facebook.litho.Column
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentTree
import com.facebook.litho.LithoView
import com.facebook.litho.MeasureScope
import com.facebook.litho.MountableComponent
import com.facebook.litho.MountableComponentScope
import com.facebook.litho.MountableRenderResult
import com.facebook.litho.Row
import com.facebook.litho.SimpleMountable
import com.facebook.litho.SizeSpec
import com.facebook.litho.StateCaller
import com.facebook.litho.Style
import com.facebook.litho.Transition
import com.facebook.litho.animation.AnimatedProperties
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.litho.transition.transitionKey
import com.facebook.litho.view.backgroundColor
import com.facebook.litho.view.viewTag
import com.facebook.rendercore.MeasureResult

class TestAnimationMount(
    private val stateCaller: StateCaller,
) : MountableComponent() {
  override fun MountableComponentScope.render(): MountableRenderResult {
    return MountableRenderResult(TestAnimationMountable(stateCaller), null)
  }
}

private class TestAnimationMountable(private val stateCaller: StateCaller) :
    SimpleMountable<LithoView>(RenderType.VIEW) {
  override fun createContent(context: Context): LithoView = LithoView(context)

  override fun MeasureScope.measure(widthSpec: Int, heightSpec: Int): MeasureResult {
    // If width is undefined, set default size.
    val width =
        if (SizeSpec.getMode(widthSpec) == SizeSpec.UNSPECIFIED) {
          50
        } else {
          SizeSpec.getSize(widthSpec)
        }

    // If height is undefined, use 1.5 aspect ratio.
    val height =
        if (SizeSpec.getMode(heightSpec) == SizeSpec.UNSPECIFIED) {
          50
        } else {
          SizeSpec.getSize(heightSpec)
        }

    return MeasureResult(width, height)
  }

  override fun mount(c: Context, content: LithoView, layoutData: Any?) {
    val transitionKey = "TRANSITION_KEY"
    val component =
        TestAnimationsComponent(
            stateCaller,
            Transition.create(transitionKey)
                .animator(Transition.timing(144))
                .animate(AnimatedProperties.ALPHA)
                .disappearTo(0f)) { state ->
              Column {
                child(Row(style = Style.height(50.dp).width(50.dp).backgroundColor(Color.YELLOW)))
                child(
                    if (!state)
                        Row(
                            style =
                                Style.height(50.dp)
                                    .width(50.dp)
                                    .backgroundColor(Color.RED)
                                    .transitionKey(context, transitionKey)
                                    .viewTag("TestAnimationMount"))
                    else null)
              }
            }
    content.componentTree = ComponentTree.create(ComponentContext(c), component).build()
  }

  override fun unmount(c: Context, content: LithoView, layoutData: Any?) {}
}


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

import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.StateCaller
import com.facebook.litho.Transition
import com.facebook.litho.onCleanup
import com.facebook.litho.transition.useTransition
import com.facebook.litho.useEffect
import com.facebook.litho.useState

class TestAnimationsComponent(
    private val stateCaller: StateCaller,
    private val transition: Transition?,
    private val componentCall: ComponentScope.(Boolean) -> Component,
) : KComponent() {
  override fun ComponentScope.render(): Component {
    useTransition(transition)
    val state = useState { false }
    useEffect(stateCaller) {
      stateCaller.setStateUpdateListener { state.update { !it } }
      onCleanup { stateCaller.setStateUpdateListener(null) }
    }
    return componentCall(state.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.widget

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext

interface TestComponentProvider {
  fun getComponent(componentContext: ComponentContext, state: Boolean): Component?
}


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

package com.facebook.litho.widget

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.LifecycleStep
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.Prop

@LayoutSpec
object TestCrashFromEachLayoutLifecycleMethodParentSpec {
  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(
      c: ComponentContext,
      @Prop caller: TestCrashFromEachLayoutLifecycleMethodSpec.Caller
  ): Component =
      TestCrashFromEachLayoutLifecycleMethod.create(c)
          .crashFromStep(LifecycleStep.ON_UPDATE_STATE)
          .caller(caller)
          .build()
}


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

import android.graphics.Rect
import com.facebook.litho.BoundaryWorkingRange
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.FocusedVisibleEvent
import com.facebook.litho.FullImpressionVisibleEvent
import com.facebook.litho.InvisibleEvent
import com.facebook.litho.LifecycleStep
import com.facebook.litho.Output
import com.facebook.litho.StateValue
import com.facebook.litho.TestTriggerEvent
import com.facebook.litho.Transition
import com.facebook.litho.VisibilityChangedEvent
import com.facebook.litho.VisibleEvent
import com.facebook.litho.annotations.CachedValue
import com.facebook.litho.annotations.FromTrigger
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnAttached
import com.facebook.litho.annotations.OnCalculateCachedValue
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnCreateTransition
import com.facebook.litho.annotations.OnCreateTreeProp
import com.facebook.litho.annotations.OnDetached
import com.facebook.litho.annotations.OnEnteredRange
import com.facebook.litho.annotations.OnEvent
import com.facebook.litho.annotations.OnExitedRange
import com.facebook.litho.annotations.OnLoadStyle
import com.facebook.litho.annotations.OnRegisterRanges
import com.facebook.litho.annotations.OnTrigger
import com.facebook.litho.annotations.OnUpdateState
import com.facebook.litho.annotations.OnUpdateStateWithTransition
import com.facebook.litho.annotations.Param
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.State
import com.facebook.litho.widget.events.EventWithoutAnnotation

/**
 * LayoutSpec that implements lifecycle methods and crashes from the one provided as crashFromStep
 * prop.
 */
@LayoutSpec(events = [EventWithoutAnnotation::class])
object TestCrashFromEachLayoutLifecycleMethodSpec {

  @JvmStatic
  @OnCreateInitialState
  fun onCreateInitialState(
      c: ComponentContext,
      state: StateValue<String>,
      @Prop crashFromStep: LifecycleStep
  ) {
    state.set("hello world")
    if (crashFromStep == LifecycleStep.ON_CREATE_INITIAL_STATE) {
      throw RuntimeException("onCreateInitialState crash")
    }
  }

  @JvmStatic
  @OnCreateTreeProp
  fun onCreateTreePropRect(c: ComponentContext, @Prop crashFromStep: LifecycleStep): Rect {
    if (crashFromStep == LifecycleStep.ON_CREATE_TREE_PROP) {
      throw RuntimeException("onCreateTreeProp crash")
    }
    return Rect()
  }

  @JvmStatic
  @OnCalculateCachedValue(name = "expensiveValue")
  fun onCalculateExpensiveValue(@Prop crashFromStep: LifecycleStep): Int {
    if (crashFromStep == LifecycleStep.ON_CALCULATE_CACHED_VALUE) {
      throw RuntimeException("onCalculateCachedValue crash")
    }
    return 0
  }

  @JvmStatic
  @OnCreateLayout
  fun onCreateLayout(
      c: ComponentContext,
      @Prop crashFromStep: LifecycleStep,
      @Prop(optional = true) caller: Caller?,
      @State state: String,
      @CachedValue expensiveValue: Int
  ): Component {
    checkNotNull(state) { "OnCreateLayout called without initialised state." }
    caller?.set(c, crashFromStep)
    if (crashFromStep == LifecycleStep.ON_CREATE_LAYOUT) {
      throw RuntimeException("onCreateLayout crash")
    }
    return Column.create(c)
        .visibleHandler(TestCrashFromEachLayoutLifecycleMethod.onVisible(c))
        .invisibleHandler(TestCrashFromEachLayoutLifecycleMethod.onInvisible(c))
        .focusedHandler(TestCrashFromEachLayoutLifecycleMethod.onFocusedEventVisible(c))
        .fullImpressionHandler(
            TestCrashFromEachLayoutLifecycleMethod.onFullImpressionVisibleEvent(c))
        .visibilityChangedHandler(
            TestCrashFromEachLayoutLifecycleMethod.onVisibilityChangedEvent(c))
        .build()
  }

  @JvmStatic
  @OnLoadStyle
  fun onLoadStyle(c: ComponentContext, crashFromStep: Output<LifecycleStep>) {
    // no need for the if clause for crashFromStep, we invoke this method
    // via TestCrashFromEachLayoutLifecycleMethod.create(context, 0, R.style.Animation)
    throw RuntimeException("onLoadStyle crash")
  }

  @JvmStatic
  @OnCreateTransition
  fun onCreateTransition(c: ComponentContext, @Prop crashFromStep: LifecycleStep): Transition {
    if (crashFromStep == LifecycleStep.ON_CREATE_TRANSITION) {
      throw RuntimeException("onCreateTransition crash")
    }
    return Transition.allLayout()
  }

  @JvmStatic
  @OnAttached
  fun onAttached(c: ComponentContext, @Prop crashFromStep: LifecycleStep) {
    if (crashFromStep == LifecycleStep.ON_ATTACHED) {
      throw RuntimeException("onAttached crash")
    }
  }

  @JvmStatic
  @OnDetached
  fun onDetached(c: ComponentContext, @Prop crashFromStep: LifecycleStep) {
    if (crashFromStep == LifecycleStep.ON_DETACHED) {
      throw RuntimeException("onDetached crash")
    }
  }

  @JvmStatic
  @OnUpdateState
  fun updateState(@Param crashFromStepAsParam: LifecycleStep) {
    if (crashFromStepAsParam == LifecycleStep.ON_UPDATE_STATE) {
      throw RuntimeException("onUpdateState crash")
    }
  }

  @JvmStatic
  @OnUpdateStateWithTransition
  fun updateStateWithTransition(@Param crashFromStepAsParam: LifecycleStep): Transition {
    if (crashFromStepAsParam == LifecycleStep.ON_UPDATE_STATE_WITH_TRANSITION) {
      throw RuntimeException("onUpdateStateWithTransition crash")
    }
    return Transition.allLayout()
  }

  @JvmStatic
  @OnEvent(VisibleEvent::class)
  fun onVisible(c: ComponentContext, @Prop crashFromStep: LifecycleStep) {
    if (crashFromStep == LifecycleStep.ON_EVENT_VISIBLE) {
      throw RuntimeException("onEventVisible crash")
    }
  }

  @JvmStatic
  @OnEvent(InvisibleEvent::class)
  fun onInvisible(c: ComponentContext, @Prop crashFromStep: LifecycleStep) {
    if (crashFromStep == LifecycleStep.ON_EVENT_INVISIBLE) {
      throw RuntimeException("onEventInvisible crash")
    }
  }

  @JvmStatic
  @OnEvent(FocusedVisibleEvent::class)
  fun onFocusedEventVisible(c: ComponentContext, @Prop crashFromStep: LifecycleStep) {
    if (crashFromStep == LifecycleStep.ON_FOCUSED_EVENT_VISIBLE) {
      throw RuntimeException("onFocusedEventVisible crash")
    }
  }

  @JvmStatic
  @OnEvent(FullImpressionVisibleEvent::class)
  fun onFullImpressionVisibleEvent(c: ComponentContext, @Prop crashFromStep: LifecycleStep) {
    if (crashFromStep == LifecycleStep.ON_FULL_IMPRESSION_VISIBLE_EVENT) {
      throw RuntimeException("onFullImpressionVisible crash")
    }
  }

  @JvmStatic
  @OnEvent(VisibilityChangedEvent::class)
  fun onVisibilityChangedEvent(c: ComponentContext, @Prop crashFromStep: LifecycleStep) {
    if (crashFromStep == LifecycleStep.ON_VISIBILITY_CHANGED) {
      throw RuntimeException("onVisibilityChanged crash")
    }
  }

  @JvmStatic
  @OnRegisterRanges
  fun registerWorkingRanges(c: ComponentContext, @Prop crashFromStep: LifecycleStep) {
    if (crashFromStep == LifecycleStep.ON_REGISTER_RANGES) {
      throw RuntimeException("onRegisterRanges crash")
    }
    TestCrashFromEachLayoutLifecycleMethod.registerBoundaryWorkingRange(c, BoundaryWorkingRange())
  }

  @JvmStatic
  @OnEnteredRange(name = "boundary")
  fun onEnteredWorkingRange(c: ComponentContext, @Prop crashFromStep: LifecycleStep) {
    if (crashFromStep == LifecycleStep.ON_ENTERED_RANGE) {
      throw RuntimeException("onEnteredRange crash")
    }
  }

  @JvmStatic
  @OnExitedRange(name = "boundary")
  fun onExitedWorkingRange(c: ComponentContext, @Prop crashFromStep: LifecycleStep) {
    if (crashFromStep == LifecycleStep.ON_EXITED_RANGE) {
      throw RuntimeException("onExitedRange crash")
    }
  }

  @JvmStatic
  @OnTrigger(TestTriggerEvent::class)
  fun triggerTestEvent(
      c: ComponentContext,
      @Prop crashFromStep: LifecycleStep,
      @FromTrigger triggerObject: Any?
  ) {
    if (crashFromStep == LifecycleStep.ON_TRIGGER) {
      throw RuntimeException("onTrigger crash")
    }
  }

  class Caller {
    lateinit var c: ComponentContext
    lateinit var crashFromStep: LifecycleStep

    operator fun set(c: ComponentContext, crashFromStep: LifecycleStep) {
      this.c = c
      this.crashFromStep = crashFromStep
    }

    fun updateStateSync() {
      TestCrashFromEachLayoutLifecycleMethod.updateStateSync(c, crashFromStep)
    }

    fun updateStateWithTransition() {
      TestCrashFromEachLayoutLifecycleMethod.updateStateWithTransitionWithTransition(
          c, crashFromStep)
    }
  }
}


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

import android.content.Context
import java.util.ArrayList

/** A test [LithoRecyclerView] class used for unit testing. */
class TestLithoRecyclerView(context: Context) : LithoRecyclerView(context) {

  private var itemAnimator: ItemAnimator? = null
  private var touchInterceptor: TouchInterceptor? = null
  private val removeOnScrollListeners: MutableList<OnScrollListener> = ArrayList()
  private val addOnScrollListeners: MutableList<OnScrollListener> = ArrayList()
  private var layoutRequested = false

  override fun getItemAnimator(): ItemAnimator? = itemAnimator

  override fun setItemAnimator(animator: ItemAnimator?) {
    itemAnimator = animator
  }

  override fun removeOnScrollListener(onScrollListener: OnScrollListener) {
    removeOnScrollListeners.add(onScrollListener)
    super.removeOnScrollListener(onScrollListener)
  }

  val removeOnScrollListenersCount: Int
    get() = removeOnScrollListeners.size

  override fun addOnScrollListener(onScrollListener: OnScrollListener) {
    addOnScrollListeners.add(onScrollListener)
    super.addOnScrollListener(onScrollListener)
  }

  val addOnScrollListenersCount: Int
    get() = addOnScrollListeners.size

  override fun setTouchInterceptor(touchInterceptor: TouchInterceptor?) {
    super.setTouchInterceptor(touchInterceptor)
    this.touchInterceptor = touchInterceptor
  }

  fun getTouchInterceptor(): TouchInterceptor? = touchInterceptor

  override fun requestLayout() {
    super.requestLayout()
    layoutRequested = true
  }

  override fun isLayoutRequested(): Boolean = layoutRequested
}


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

import android.annotation.SuppressLint
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayoutWithSizeSpec

@LayoutSpec
internal object TestNullLayoutComponentSpec {
  @JvmStatic
  @OnCreateLayoutWithSizeSpec
  @SuppressLint("ComponentsUnneededLayoutWithSizeSpecDetector")
  fun onCreateLayout(c: ComponentContext, widthSpec: Int, heightSpec: Int): Component? = null
}


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

package com.facebook.litho.widget

import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import java.util.ArrayList

/** A test [RecyclerView] class used for unit testing. */
class TestRecyclerView(context: Context) : RecyclerView(context) {

  private val postAnimationRunnableList: MutableList<Runnable> = ArrayList()

  override fun postOnAnimation(r: Runnable) {
    postAnimationRunnableList.add(r)
  }

  fun getPostAnimationRunnableList(): List<Runnable> = postAnimationRunnableList
}