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

import android.graphics.Color
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.IdRes
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SnapHelper
import com.facebook.litho.Dimen
import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.Size
import com.facebook.litho.Style
import com.facebook.litho.dp
import com.facebook.litho.eventHandler
import com.facebook.litho.sections.widget.NoUpdateItemAnimator
import com.facebook.litho.useState
import com.facebook.litho.widget.Binder
import com.facebook.litho.widget.LithoRecyclerView
import com.facebook.litho.widget.RecyclerEventsController
import com.facebook.litho.widget.SectionsRecyclerView
import com.facebook.rendercore.SizeConstraints
import com.facebook.rendercore.extensions.doesMountRenderTreeHosts
import com.facebook.rendercore.primitives.LayoutBehavior
import com.facebook.rendercore.primitives.LayoutScope
import com.facebook.rendercore.primitives.PrimitiveLayoutResult
import com.facebook.rendercore.primitives.ViewAllocator
import com.facebook.rendercore.toHeightSpec
import com.facebook.rendercore.toWidthSpec
import kotlin.math.max

class ExperimentalPrimitiveRecycler(
    private val binder: Binder<RecyclerView>,
    private val hasFixedSize: Boolean = true,
    private val isClipToPaddingEnabled: Boolean = true,
    private val leftPadding: Int = 0,
    private val topPadding: Int = 0,
    private val rightPadding: Int = 0,
    private val bottomPadding: Int = 0,
    private @ColorInt val refreshProgressBarBackgroundColor: Int? = null,
    private @ColorInt val refreshProgressBarColor: Int = Color.BLACK,
    private val isClipChildrenEnabled: Boolean = true,
    private val isNestedScrollingEnabled: Boolean = true,
    private val scrollBarStyle: Int = View.SCROLLBARS_INSIDE_OVERLAY,
    private val itemDecoration: RecyclerView.ItemDecoration? = null,
    private val isHorizontalFadingEdgeEnabled: Boolean = false,
    private val isVerticalFadingEdgeEnabled: Boolean = false,
    private val fadingEdgeLength: Dimen = 0.dp,
    private @IdRes val recyclerViewId: Int = View.NO_ID,
    private val overScrollMode: Int = View.OVER_SCROLL_ALWAYS,
    private val contentDescription: CharSequence? = null,
    private val itemAnimator: RecyclerView.ItemAnimator = DEFAULT_ITEM_ANIMATOR,
    private val recyclerEventsController: RecyclerEventsController? = null,
    private val onScrollListeners: List<RecyclerView.OnScrollListener?> = emptyList(),
    private val snapHelper: SnapHelper? = null,
    private val isPullToRefreshEnabled: Boolean = true,
    private val touchInterceptor: LithoRecyclerView.TouchInterceptor? = null,
    private val onItemTouchListener: RecyclerView.OnItemTouchListener? = null,
    private val onRefresh: (() -> Unit)? = null,
    private val sectionsViewLogger: SectionsRecyclerView.SectionsRecyclerViewLogger? = null,
    private val style: Style? = null
) : PrimitiveComponent() {
  companion object {
    val DEFAULT_ITEM_ANIMATOR: RecyclerView.ItemAnimator = NoUpdateItemAnimator()

    // This is the default value for refresh spinner background from RecyclerSpec.
    private const val DEFAULT_REFRESH_SPINNER_BACKGROUND_COLOR = 0xFFFAFAFA.toInt()
  }

  override fun PrimitiveComponentScope.render(): LithoPrimitive {
    val measureVersion = useState { 0 }

    return LithoPrimitive(
        layoutBehavior = RecyclerLayoutBehavior(binder) { measureVersion.update { v -> v + 1 } },
        mountBehavior =
            MountBehavior(
                ViewAllocator { context ->
                  SectionsRecyclerView(context, LithoRecyclerView(context))
                }) {
                  doesMountRenderTreeHosts = true

                  // RecyclerSpec's @OnMount and @OnUnmount
                  bind(
                      binder,
                      hasFixedSize,
                      isClipToPaddingEnabled,
                      leftPadding,
                      topPadding,
                      rightPadding,
                      bottomPadding,
                      isClipChildrenEnabled,
                      scrollBarStyle,
                      isHorizontalFadingEdgeEnabled,
                      isVerticalFadingEdgeEnabled,
                      fadingEdgeLength,
                      refreshProgressBarBackgroundColor,
                      refreshProgressBarColor,
                      itemAnimator,
                      itemDecoration) { sectionsRecyclerView ->
                        val recyclerView: RecyclerView =
                            sectionsRecyclerView.recyclerView
                                ?: throw java.lang.IllegalStateException(
                                    "RecyclerView not found, it should not be removed from SwipeRefreshLayout")

                        recyclerView.contentDescription = contentDescription
                        recyclerView.setHasFixedSize(hasFixedSize)
                        recyclerView.clipToPadding = isClipToPaddingEnabled
                        sectionsRecyclerView.clipToPadding = isClipToPaddingEnabled
                        ViewCompat.setPaddingRelative(
                            sectionsRecyclerView.recyclerView,
                            leftPadding,
                            topPadding,
                            rightPadding,
                            bottomPadding)
                        recyclerView.clipChildren = isClipChildrenEnabled
                        sectionsRecyclerView.clipChildren = isClipChildrenEnabled
                        recyclerView.isNestedScrollingEnabled = isNestedScrollingEnabled
                        sectionsRecyclerView.isNestedScrollingEnabled = isNestedScrollingEnabled
                        recyclerView.scrollBarStyle = scrollBarStyle
                        recyclerView.isHorizontalFadingEdgeEnabled = isHorizontalFadingEdgeEnabled
                        recyclerView.isVerticalFadingEdgeEnabled = isVerticalFadingEdgeEnabled
                        recyclerView.setFadingEdgeLength(fadingEdgeLength.toPixels())
                        recyclerView.id = recyclerViewId
                        recyclerView.overScrollMode = overScrollMode

                        if (refreshProgressBarBackgroundColor != null) {
                          sectionsRecyclerView.setProgressBackgroundColorSchemeColor(
                              refreshProgressBarBackgroundColor)
                        }

                        sectionsRecyclerView.setColorSchemeColors(refreshProgressBarColor)

                        if (itemDecoration != null) {
                          recyclerView.addItemDecoration(itemDecoration)
                        }

                        sectionsRecyclerView.setItemAnimator(
                            if (itemAnimator !== DEFAULT_ITEM_ANIMATOR) itemAnimator
                            else DEFAULT_ITEM_ANIMATOR)

                        binder.mount(recyclerView)

                        onUnbind {
                          recyclerView.id = View.NO_ID

                          if (refreshProgressBarBackgroundColor != null) {
                            sectionsRecyclerView.setProgressBackgroundColorSchemeColor(
                                DEFAULT_REFRESH_SPINNER_BACKGROUND_COLOR)
                          }

                          if (itemDecoration != null) {
                            recyclerView.removeItemDecoration(itemDecoration)
                          }

                          binder.unmount(recyclerView)

                          snapHelper?.attachToRecyclerView(null)

                          sectionsRecyclerView.resetItemAnimator()
                        }
                      }

                  // RecyclerSpec's @OnBind and @OnUnbind
                  bind(Any()) { sectionsRecyclerView ->
                    sectionsRecyclerView.setSectionsRecyclerViewLogger(sectionsViewLogger)

                    // contentDescription should be set on the recyclerView itself, and not the
                    // sectionsRecycler.
                    sectionsRecyclerView.contentDescription = null

                    sectionsRecyclerView.isEnabled = isPullToRefreshEnabled && onRefresh != null

                    if (onRefresh != null) {
                      sectionsRecyclerView.setOnRefreshListener { onRefresh.invoke() }
                    }

                    val recyclerView =
                        sectionsRecyclerView.recyclerView as? LithoRecyclerView
                            ?: throw IllegalStateException(
                                "RecyclerView not found, it should not be removed from SwipeRefreshLayout " +
                                    "before unmounting")

                    for (i in onScrollListeners.indices) {
                      onScrollListeners[i]?.let { recyclerView.addOnScrollListener(it) }
                    }

                    if (touchInterceptor != null) {
                      recyclerView.setTouchInterceptor(touchInterceptor)
                    }

                    if (onItemTouchListener != null) {
                      recyclerView.addOnItemTouchListener(onItemTouchListener)
                    }

                    // We cannot detach the snap helper in unbind, so it may be possible for it to
                    // get attached twice which causes SnapHelper to raise an exception.
                    if (recyclerView.onFlingListener == null) {
                      snapHelper?.attachToRecyclerView(recyclerView)
                    }

                    binder.bind(recyclerView)

                    if (recyclerEventsController != null) {
                      recyclerEventsController.setSectionsRecyclerView(sectionsRecyclerView)
                      recyclerEventsController.snapHelper = snapHelper
                    }

                    if (sectionsRecyclerView.hasBeenDetachedFromWindow()) {
                      recyclerView.requestLayout()
                      sectionsRecyclerView.setHasBeenDetachedFromWindow(false)
                    }

                    onUnbind {
                      sectionsRecyclerView.setSectionsRecyclerViewLogger(null)

                      binder.unbind(recyclerView)

                      if (recyclerEventsController != null) {
                        recyclerEventsController.setSectionsRecyclerView(null)
                        recyclerEventsController.snapHelper = null
                      }

                      for (i in onScrollListeners.indices) {
                        onScrollListeners[i]?.let { recyclerView.removeOnScrollListener(it) }
                      }

                      if (onItemTouchListener != null) {
                        recyclerView.removeOnItemTouchListener(onItemTouchListener)
                      }

                      recyclerView.setTouchInterceptor(null)

                      sectionsRecyclerView.setOnRefreshListener(null)
                    }
                  }
                },
        style = style)
  }
}

private class RecyclerLayoutBehavior(
    private val binder: Binder<RecyclerView>,
    private val onRemeasure: () -> Unit
) : LayoutBehavior {
  override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
    val size = Size()
    binder.measure(
        size,
        sizeConstraints.toWidthSpec(),
        sizeConstraints.toHeightSpec(),
        if (binder.canMeasure() || binder.isWrapContent) eventHandler { onRemeasure() } else null)
    return PrimitiveLayoutResult(
        max(sizeConstraints.minWidth, size.width), max(sizeConstraints.minHeight, size.height))
  }
}


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

package com.facebook.litho.kotlin.widget

import android.content.Context
import android.graphics.Color
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.IdRes
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemAnimator
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import androidx.recyclerview.widget.SnapHelper
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.facebook.litho.Dimen
import com.facebook.litho.MountableComponent
import com.facebook.litho.MountableComponentScope
import com.facebook.litho.MountableRenderResult
import com.facebook.litho.Size
import com.facebook.litho.Style
import com.facebook.litho.dp
import com.facebook.litho.eventHandler
import com.facebook.litho.sections.widget.NoUpdateItemAnimator
import com.facebook.litho.useState
import com.facebook.litho.widget.Binder as LithoBinder
import com.facebook.litho.widget.LithoRecyclerView
import com.facebook.litho.widget.RecyclerEventsController
import com.facebook.litho.widget.SectionsRecyclerView
import com.facebook.rendercore.ContentAllocator
import com.facebook.rendercore.LayoutContext
import com.facebook.rendercore.MeasureResult
import com.facebook.rendercore.Mountable

class ExperimentalRecycler(
    private val binder: LithoBinder<RecyclerView>,
    private val hasFixedSize: Boolean = true,
    private val isClipToPaddingEnabled: Boolean = true,
    private val leftPadding: Int = 0,
    private val topPadding: Int = 0,
    private val rightPadding: Int = 0,
    private val bottomPadding: Int = 0,
    private @ColorInt val refreshProgressBarBackgroundColor: Int? = null,
    private @ColorInt val refreshProgressBarColor: Int = Color.BLACK,
    private val isClipChildrenEnabled: Boolean = true,
    private val isNestedScrollingEnabled: Boolean = true,
    private val scrollBarStyle: Int = View.SCROLLBARS_INSIDE_OVERLAY,
    private val itemDecoration: ItemDecoration? = null,
    private val isHorizontalFadingEdgeEnabled: Boolean = false,
    private val isVerticalFadingEdgeEnabled: Boolean = false,
    private val fadingEdgeLength: Dimen = 0.dp,
    private @IdRes val recyclerViewId: Int = View.NO_ID,
    private val overScrollMode: Int = View.OVER_SCROLL_ALWAYS,
    private val contentDescription: CharSequence? = null,
    private val itemAnimator: ItemAnimator = DEFAULT_ITEM_ANIMATOR,
    private val recyclerEventsController: RecyclerEventsController? = null,
    private val onScrollListeners: List<RecyclerView.OnScrollListener?> = emptyList(),
    private val snapHelper: SnapHelper? = null,
    private val isPullToRefreshEnabled: Boolean = true,
    private val touchInterceptor: LithoRecyclerView.TouchInterceptor? = null,
    private val onItemTouchListener: RecyclerView.OnItemTouchListener? = null,
    private val onRefresh: (() -> Unit)? = null,
    private val sectionsViewLogger: SectionsRecyclerView.SectionsRecyclerViewLogger? = null,
    private val useTwoBindersRecycler: Boolean = false,
    private val enableSeparateAnimatorBinder: Boolean = false,
    private val style: Style? = null
) : MountableComponent() {

  companion object {
    val DEFAULT_ITEM_ANIMATOR: ItemAnimator = NoUpdateItemAnimator()
  }

  override fun MountableComponentScope.render(): MountableRenderResult {
    val measureVersion = useState { 0 }
    return MountableRenderResult(
        ExperimentalRecyclerMountable(
            binder = binder,
            hasFixedSize = hasFixedSize,
            isClipToPaddingEnabled = isClipToPaddingEnabled,
            leftPadding = leftPadding,
            topPadding = topPadding,
            rightPadding = rightPadding,
            bottomPadding = bottomPadding,
            refreshProgressBarBackgroundColor = refreshProgressBarBackgroundColor,
            refreshProgressBarColor = refreshProgressBarColor,
            isClipChildrenEnabled = isClipChildrenEnabled,
            isNestedScrollingEnabled = isNestedScrollingEnabled,
            scrollBarStyle = scrollBarStyle,
            itemDecoration = itemDecoration,
            isHorizontalFadingEdgeEnabled = isHorizontalFadingEdgeEnabled,
            isVerticalFadingEdgeEnabled = isVerticalFadingEdgeEnabled,
            fadingEdgeLength = fadingEdgeLength.toPixels(),
            recyclerViewId = recyclerViewId,
            overScrollMode = overScrollMode,
            contentDescription = contentDescription,
            itemAnimator = itemAnimator,
            recyclerEventsController = recyclerEventsController,
            onScrollListeners = onScrollListeners,
            snapHelper = snapHelper,
            isPullToRefreshEnabled = isPullToRefreshEnabled,
            touchInterceptor = touchInterceptor,
            onItemTouchListener = onItemTouchListener,
            onRefresh = onRefresh,
            sectionsViewLogger = sectionsViewLogger,
            useTwoBindersRecycler = useTwoBindersRecycler,
            enableSeparateAnimatorBinder = enableSeparateAnimatorBinder,
            onRemeasure = { measureVersion.update { m -> m + 1 } }),
        style)
  }
}

// This is the default value for refresh spinner background from
// SwipeRefreshLayout.CIRCLE_BG_LIGHT which is unfortunately private.
private const val DEFAULT_REFRESH_SPINNER_BACKGROUND_COLOR = -0x50506

internal class ExperimentalRecyclerMountable(
    private val binder: LithoBinder<RecyclerView>,
    private val hasFixedSize: Boolean,
    private val isClipToPaddingEnabled: Boolean,
    private val leftPadding: Int,
    private val topPadding: Int,
    private val rightPadding: Int,
    private val bottomPadding: Int,
    private @ColorInt val refreshProgressBarBackgroundColor: Int?,
    private @ColorInt val refreshProgressBarColor: Int,
    private val isClipChildrenEnabled: Boolean,
    private val isNestedScrollingEnabled: Boolean,
    private val scrollBarStyle: Int,
    private val itemDecoration: ItemDecoration?,
    private val isHorizontalFadingEdgeEnabled: Boolean,
    private val isVerticalFadingEdgeEnabled: Boolean,
    private val fadingEdgeLength: Int,
    private @IdRes val recyclerViewId: Int,
    private val overScrollMode: Int,
    private val contentDescription: CharSequence?,
    private val itemAnimator: ItemAnimator,
    private val recyclerEventsController: RecyclerEventsController?,
    private val onScrollListeners: List<RecyclerView.OnScrollListener?>,
    private val snapHelper: SnapHelper?,
    private val isPullToRefreshEnabled: Boolean,
    private val touchInterceptor: LithoRecyclerView.TouchInterceptor?,
    private val onItemTouchListener: RecyclerView.OnItemTouchListener?,
    private val onRefresh: (() -> Unit)?,
    private val sectionsViewLogger: SectionsRecyclerView.SectionsRecyclerViewLogger?,
    private val useTwoBindersRecycler: Boolean,
    private val enableSeparateAnimatorBinder: Boolean,
    private val onRemeasure: () -> Unit
) : Mountable<SectionsRecyclerView>(RenderType.VIEW), ContentAllocator<SectionsRecyclerView> {

  companion object {
    private val CONSTANT_PROPS_ATTACH_BINDER:
        Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> =
        object : Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> {
          override fun shouldUpdate(
              currentModel: ExperimentalRecyclerMountable,
              newModel: ExperimentalRecyclerMountable,
              currentLayoutData: Any?,
              nextLayoutData: Any?
          ): Boolean = true

          override fun bind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?
          ): Any? {
            with(model) {
              sectionsViewLogger?.let { content.setSectionsRecyclerViewLogger(sectionsViewLogger) }
              // contentDescription should be set on the recyclerView itself, and not the
              // sectionsRecycler.
              content.contentDescription = null
              content.setEnabled(isPullToRefreshEnabled && onRefresh != null)
              content.setOnRefreshListener(
                  if (onRefresh != null) SwipeRefreshLayout.OnRefreshListener { onRefresh.invoke() }
                  else null)
              if (onScrollListeners.isNotEmpty()) {
                for (onScrollListener in onScrollListeners) {
                  onScrollListener?.let { content.recyclerView.addOnScrollListener(it) }
                }
              }
              touchInterceptor?.let {
                (content.recyclerView as LithoRecyclerView).setTouchInterceptor(touchInterceptor)
              }
              onItemTouchListener?.let {
                content.recyclerView.addOnItemTouchListener(onItemTouchListener)
              }
              // We cannot detach the snap helper in unbind, so it may be possible for it to get
              // attached twice which causes SnapHelper to raise an exception.
              if (snapHelper != null && content.recyclerView.getOnFlingListener() == null) {
                snapHelper.attachToRecyclerView(content.recyclerView)
              }
              recyclerEventsController?.setSectionsRecyclerView(content)
              if (content.hasBeenDetachedFromWindow()) {
                content.recyclerView.requestLayout()
                content.setHasBeenDetachedFromWindow(false)
              }
            }
            return null
          }

          override fun unbind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?,
              bindData: Any?
          ) {
            content.setSectionsRecyclerViewLogger(null)
            content.setOnRefreshListener(null)
            model.snapHelper?.attachToRecyclerView(null)
            model.recyclerEventsController?.setSectionsRecyclerView(null)
            if (model.onScrollListeners.isNotEmpty()) {
              for (onScrollListener in model.onScrollListeners) {
                onScrollListener?.let { content.recyclerView.removeOnScrollListener(it) }
              }
            }
            (content.recyclerView as LithoRecyclerView).setTouchInterceptor(null)

            model.onItemTouchListener?.let {
              content.recyclerView.removeOnItemTouchListener(model.onItemTouchListener)
            }
          }
        }

    private val CONSTANT_PROPS_MOUNT_BINDER:
        Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> =
        object : Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> {
          override fun shouldUpdate(
              currentModel: ExperimentalRecyclerMountable,
              newModel: ExperimentalRecyclerMountable,
              currentLayoutData: Any?,
              nextLayoutData: Any?
          ): Boolean {
            if (currentModel.hasFixedSize != newModel.hasFixedSize) return true
            if (currentModel.isClipToPaddingEnabled != newModel.isClipToPaddingEnabled) return true
            if (currentModel.leftPadding != newModel.leftPadding) return true
            if (currentModel.rightPadding != newModel.rightPadding) return true
            if (currentModel.topPadding != newModel.topPadding) return true
            if (currentModel.bottomPadding != newModel.bottomPadding) return true
            if (currentModel.isClipChildrenEnabled != newModel.isClipChildrenEnabled) return true
            if (currentModel.scrollBarStyle != newModel.scrollBarStyle) return true
            if (currentModel.isHorizontalFadingEdgeEnabled !=
                newModel.isHorizontalFadingEdgeEnabled)
                return true
            if (currentModel.isVerticalFadingEdgeEnabled != newModel.isVerticalFadingEdgeEnabled)
                return true
            if (currentModel.fadingEdgeLength != newModel.fadingEdgeLength) return true
            if (currentModel.refreshProgressBarBackgroundColor !=
                newModel.refreshProgressBarBackgroundColor)
                return true
            if (currentModel.refreshProgressBarColor != newModel.refreshProgressBarColor)
                return true
            val previousItemAnimator = currentModel.itemAnimator
            val nextItemAnimator = newModel.itemAnimator
            if (previousItemAnimator == null && nextItemAnimator != null ||
                previousItemAnimator != null &&
                    previousItemAnimator.javaClass != nextItemAnimator.javaClass)
                return true
            return false
          }

          override fun bind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?
          ): Any? {
            with(model) {
              contentDescription?.let {
                content.recyclerView.contentDescription = contentDescription
              }
              content.recyclerView.setHasFixedSize(hasFixedSize)
              content.recyclerView.clipToPadding = isClipToPaddingEnabled
              content.clipToPadding = isClipToPaddingEnabled
              ViewCompat.setPaddingRelative(
                  content.recyclerView,
                  leftPadding,
                  topPadding,
                  rightPadding,
                  bottomPadding,
              )
              content.recyclerView.clipChildren = isClipChildrenEnabled
              content.clipChildren = isClipChildrenEnabled
              content.recyclerView.isNestedScrollingEnabled = isNestedScrollingEnabled
              content.isNestedScrollingEnabled = isNestedScrollingEnabled
              content.recyclerView.scrollBarStyle = scrollBarStyle
              content.recyclerView.isHorizontalFadingEdgeEnabled = isHorizontalFadingEdgeEnabled
              content.recyclerView.isVerticalFadingEdgeEnabled = isVerticalFadingEdgeEnabled
              content.recyclerView.setFadingEdgeLength(fadingEdgeLength)
              content.recyclerView.id = recyclerViewId
              content.recyclerView.overScrollMode = overScrollMode
              if (refreshProgressBarBackgroundColor != null) {
                content.setProgressBackgroundColorSchemeColor(refreshProgressBarBackgroundColor)
              }
              content.setColorSchemeColors(refreshProgressBarColor)
              content.setItemAnimator(
                  if (itemAnimator != ExperimentalRecycler.DEFAULT_ITEM_ANIMATOR) itemAnimator
                  else NoUpdateItemAnimator())
            }
            return null
          }

          override fun unbind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?,
              bindData: Any?
          ) {
            content.recyclerView.id = View.NO_ID
            model.refreshProgressBarBackgroundColor?.let {
              content.setProgressBackgroundColorSchemeColor(
                  DEFAULT_REFRESH_SPINNER_BACKGROUND_COLOR)
            }
            content.resetItemAnimator()
          }
        }

    private val ITEM_DECORATOR_BINDER:
        Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> =
        object : Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> {
          override fun shouldUpdate(
              currentModel: ExperimentalRecyclerMountable,
              newModel: ExperimentalRecyclerMountable,
              currentLayoutData: Any?,
              nextLayoutData: Any?
          ): Boolean {
            val previous = currentModel.itemDecoration
            val next = newModel.itemDecoration
            val itemDecorationIsEqual = if (previous == null) next == null else previous == next
            return !itemDecorationIsEqual
          }

          override fun bind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?
          ): Any? {
            model.itemDecoration?.let { content.recyclerView.addItemDecoration(it) }
            return null
          }

          override fun unbind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?,
              bindData: Any?
          ) {
            model.itemDecoration?.let { content.recyclerView.removeItemDecoration(it) }
          }
        }

    private val CONTENT_MOUNT_BINDER:
        Binder<LithoBinder<RecyclerView>, SectionsRecyclerView, Any?> =
        object : Binder<LithoBinder<RecyclerView>, SectionsRecyclerView, Any?> {
          override fun shouldUpdate(
              currentModel: LithoBinder<RecyclerView>,
              newModel: LithoBinder<RecyclerView>,
              currentLayoutData: Any?,
              nextLayoutData: Any?
          ): Boolean = currentModel != newModel

          override fun bind(
              context: Context,
              content: SectionsRecyclerView,
              binder: LithoBinder<RecyclerView>,
              layoutData: Any?
          ): Any? {
            binder.mount(content.recyclerView)
            return null
          }

          override fun unbind(
              context: Context,
              content: SectionsRecyclerView,
              binder: LithoBinder<RecyclerView>,
              layoutData: Any?,
              bindData: Any?
          ) {
            binder.unmount(content.recyclerView)
          }
        }

    private val CONTENT_ATTACH_BINDER:
        Binder<LithoBinder<RecyclerView>, SectionsRecyclerView, Any?> =
        object : Binder<LithoBinder<RecyclerView>, SectionsRecyclerView, Any?> {
          override fun shouldUpdate(
              currentModel: LithoBinder<RecyclerView>,
              newModel: LithoBinder<RecyclerView>,
              currentLayoutData: Any?,
              nextLayoutData: Any?
          ): Boolean = true

          override fun bind(
              context: Context,
              content: SectionsRecyclerView,
              binder: LithoBinder<RecyclerView>,
              layoutData: Any?
          ): Any? {
            binder.bind(content.recyclerView)
            return null
          }

          override fun unbind(
              context: Context,
              content: SectionsRecyclerView,
              binder: LithoBinder<RecyclerView>,
              layoutData: Any?,
              bindData: Any?
          ) {
            binder.unbind(content.recyclerView)
          }
        }

    private val MOUNT_BINDER: Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> =
        object : Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> {
          override fun shouldUpdate(
              currentModel: ExperimentalRecyclerMountable,
              newModel: ExperimentalRecyclerMountable,
              currentLayoutData: Any?,
              nextLayoutData: Any?
          ): Boolean {
            if (currentModel.binder != newModel.binder) return true
            if (currentModel.hasFixedSize != newModel.hasFixedSize) return true
            if (currentModel.isClipToPaddingEnabled != newModel.isClipToPaddingEnabled) return true
            if (currentModel.leftPadding != newModel.leftPadding) return true
            if (currentModel.rightPadding != newModel.rightPadding) return true
            if (currentModel.topPadding != newModel.topPadding) return true
            if (currentModel.bottomPadding != newModel.bottomPadding) return true
            if (currentModel.isClipChildrenEnabled != newModel.isClipChildrenEnabled) return true
            if (currentModel.scrollBarStyle != newModel.scrollBarStyle) return true
            if (currentModel.isHorizontalFadingEdgeEnabled !=
                newModel.isHorizontalFadingEdgeEnabled)
                return true
            if (currentModel.isVerticalFadingEdgeEnabled != newModel.isVerticalFadingEdgeEnabled)
                return true
            if (currentModel.fadingEdgeLength != newModel.fadingEdgeLength) return true
            if (currentModel.refreshProgressBarBackgroundColor !=
                newModel.refreshProgressBarBackgroundColor)
                return true
            if (currentModel.refreshProgressBarColor != newModel.refreshProgressBarColor)
                return true
            val previousItemAnimator = currentModel.itemAnimator
            val nextItemAnimator = newModel.itemAnimator
            if (previousItemAnimator == null && nextItemAnimator != null ||
                previousItemAnimator != null &&
                    previousItemAnimator.javaClass != nextItemAnimator.javaClass)
                return true
            val previous = currentModel.itemDecoration
            val next = newModel.itemDecoration
            val itemDecorationIsEqual = if (previous == null) next == null else previous == next

            return !itemDecorationIsEqual
          }

          override fun bind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?
          ): Any? {
            with(model) {
              contentDescription?.let {
                content.recyclerView.contentDescription = contentDescription
              }
              content.recyclerView.setHasFixedSize(hasFixedSize)
              content.recyclerView.clipToPadding = isClipToPaddingEnabled
              content.clipToPadding = isClipToPaddingEnabled
              ViewCompat.setPaddingRelative(
                  content.recyclerView,
                  leftPadding,
                  topPadding,
                  rightPadding,
                  bottomPadding,
              )
              content.recyclerView.clipChildren = isClipChildrenEnabled
              content.clipChildren = isClipChildrenEnabled
              content.recyclerView.isNestedScrollingEnabled = isNestedScrollingEnabled
              content.isNestedScrollingEnabled = isNestedScrollingEnabled
              content.recyclerView.scrollBarStyle = scrollBarStyle
              content.recyclerView.isHorizontalFadingEdgeEnabled = isHorizontalFadingEdgeEnabled
              content.recyclerView.isVerticalFadingEdgeEnabled = isVerticalFadingEdgeEnabled
              content.recyclerView.setFadingEdgeLength(fadingEdgeLength)
              content.recyclerView.id = recyclerViewId
              content.recyclerView.overScrollMode = overScrollMode
              if (refreshProgressBarBackgroundColor != null) {
                content.setProgressBackgroundColorSchemeColor(refreshProgressBarBackgroundColor)
              }
              content.setColorSchemeColors(refreshProgressBarColor)
              itemDecoration?.let { content.recyclerView.addItemDecoration(it) }
              content.setItemAnimator(
                  if (itemAnimator != ExperimentalRecycler.DEFAULT_ITEM_ANIMATOR) {
                    itemAnimator
                  } else {
                    NoUpdateItemAnimator()
                  })
              binder.mount(content.recyclerView)
            }
            return null
          }

          override fun unbind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?,
              bindData: Any?
          ) {
            content.recyclerView.id = View.NO_ID
            model.refreshProgressBarBackgroundColor?.let {
              content.setProgressBackgroundColorSchemeColor(
                  DEFAULT_REFRESH_SPINNER_BACKGROUND_COLOR)
            }
            model.itemDecoration?.let {
              content.recyclerView.removeItemDecoration(model.itemDecoration)
            }
            model.binder.unmount(content.recyclerView)
            if (model.snapHelper != null) {
              model.snapHelper.attachToRecyclerView(null)
            }
            content.resetItemAnimator()
          }
        }

    private val ATTACH_BINDER: Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> =
        object : Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> {
          override fun shouldUpdate(
              currentModel: ExperimentalRecyclerMountable,
              newModel: ExperimentalRecyclerMountable,
              currentLayoutData: Any?,
              nextLayoutData: Any?
          ): Boolean = true

          override fun bind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?
          ): Any? {
            with(model) {
              sectionsViewLogger?.let { content.setSectionsRecyclerViewLogger(sectionsViewLogger) }
              content.contentDescription = null
              content.setEnabled(isPullToRefreshEnabled && onRefresh != null)
              content.setOnRefreshListener(
                  if (onRefresh != null) SwipeRefreshLayout.OnRefreshListener { onRefresh.invoke() }
                  else null)
              if (onScrollListeners.isNotEmpty()) {
                for (onScrollListener in onScrollListeners) {
                  onScrollListener?.let { content.recyclerView.addOnScrollListener(it) }
                }
              }
              touchInterceptor?.let {
                (content.recyclerView as LithoRecyclerView).setTouchInterceptor(touchInterceptor)
              }
              onItemTouchListener?.let {
                content.recyclerView.addOnItemTouchListener(onItemTouchListener)
              }
              // attached twice which causes SnapHelper to raise an exception.
              if (snapHelper != null && content.recyclerView.getOnFlingListener() == null) {
                snapHelper.attachToRecyclerView(content.recyclerView)
              }
              binder.bind(content.recyclerView)
              recyclerEventsController?.setSectionsRecyclerView(content)
              if (content.hasBeenDetachedFromWindow()) {
                content.recyclerView.requestLayout()
                content.setHasBeenDetachedFromWindow(false)
              }
            }
            return null
          }

          override fun unbind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?,
              bindData: Any?
          ) {
            content.setSectionsRecyclerViewLogger(null)
            model.binder.unbind(content.recyclerView)
            model.recyclerEventsController?.setSectionsRecyclerView(null)
            if (model.onScrollListeners.isNotEmpty()) {
              for (onScrollListener in model.onScrollListeners) {
                onScrollListener?.let { content.recyclerView.removeOnScrollListener(it) }
              }
            }
            model.onItemTouchListener?.let {
              content.recyclerView.removeOnItemTouchListener(model.onItemTouchListener)
            }
            (content.recyclerView as LithoRecyclerView).setTouchInterceptor(null)
            content.setOnRefreshListener(null)
          }
        }

    private val ANIMATOR_BINDER: Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> =
        object : Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> {
          override fun shouldUpdate(
              currentModel: ExperimentalRecyclerMountable,
              newModel: ExperimentalRecyclerMountable,
              currentLayoutData: Any?,
              nextLayoutData: Any?
          ): Boolean {
            val previousItemAnimator = currentModel.itemAnimator
            val nextItemAnimator = newModel.itemAnimator
            return (if (previousItemAnimator == null) nextItemAnimator != null
            else previousItemAnimator.javaClass != nextItemAnimator.javaClass)
          }

          override fun bind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?
          ): Any? {
            content.setItemAnimator(
                if (model.itemAnimator != ExperimentalRecycler.DEFAULT_ITEM_ANIMATOR)
                    model.itemAnimator
                else NoUpdateItemAnimator())
            return null
          }

          override fun unbind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?,
              bindData: Any?
          ) {
            content.resetItemAnimator()
          }
        }

    private val CONSTANT_PROPS_MOUNT_BINDER_WITHOUT_ANIMATOR:
        Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> =
        object : Binder<ExperimentalRecyclerMountable, SectionsRecyclerView, Any?> {
          override fun shouldUpdate(
              currentModel: ExperimentalRecyclerMountable,
              newModel: ExperimentalRecyclerMountable,
              currentLayoutData: Any?,
              nextLayoutData: Any?
          ): Boolean {
            if (currentModel.hasFixedSize != newModel.hasFixedSize) return true
            if (currentModel.isClipToPaddingEnabled != newModel.isClipToPaddingEnabled) return true
            if (currentModel.leftPadding != newModel.leftPadding) return true
            if (currentModel.rightPadding != newModel.rightPadding) return true
            if (currentModel.topPadding != newModel.topPadding) return true
            if (currentModel.bottomPadding != newModel.bottomPadding) return true
            if (currentModel.isClipChildrenEnabled != newModel.isClipChildrenEnabled) return true
            if (currentModel.scrollBarStyle != newModel.scrollBarStyle) return true
            if (currentModel.isHorizontalFadingEdgeEnabled !=
                newModel.isHorizontalFadingEdgeEnabled)
                return true
            if (currentModel.isVerticalFadingEdgeEnabled != newModel.isVerticalFadingEdgeEnabled)
                return true
            if (currentModel.fadingEdgeLength != newModel.fadingEdgeLength) return true
            if (currentModel.refreshProgressBarBackgroundColor !=
                newModel.refreshProgressBarBackgroundColor)
                return true
            if (currentModel.refreshProgressBarColor != newModel.refreshProgressBarColor)
                return true
            return false
          }

          override fun bind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?
          ): Any? {
            with(model) {
              contentDescription?.let {
                content.recyclerView.contentDescription = contentDescription
              }
              content.recyclerView.setHasFixedSize(hasFixedSize)
              content.recyclerView.clipToPadding = isClipToPaddingEnabled
              content.clipToPadding = isClipToPaddingEnabled
              ViewCompat.setPaddingRelative(
                  content.recyclerView,
                  leftPadding,
                  topPadding,
                  rightPadding,
                  bottomPadding,
              )
              content.recyclerView.clipChildren = isClipChildrenEnabled
              content.clipChildren = isClipChildrenEnabled
              content.recyclerView.isNestedScrollingEnabled = isNestedScrollingEnabled
              content.isNestedScrollingEnabled = isNestedScrollingEnabled
              content.recyclerView.scrollBarStyle = scrollBarStyle
              content.recyclerView.isHorizontalFadingEdgeEnabled = isHorizontalFadingEdgeEnabled
              content.recyclerView.isVerticalFadingEdgeEnabled = isVerticalFadingEdgeEnabled
              content.recyclerView.setFadingEdgeLength(fadingEdgeLength)
              content.recyclerView.id = recyclerViewId
              content.recyclerView.overScrollMode = overScrollMode
              if (refreshProgressBarBackgroundColor != null) {
                content.setProgressBackgroundColorSchemeColor(refreshProgressBarBackgroundColor)
              }
              content.setColorSchemeColors(refreshProgressBarColor)
            }
            return null
          }

          override fun unbind(
              context: Context,
              content: SectionsRecyclerView,
              model: ExperimentalRecyclerMountable,
              layoutData: Any?,
              bindData: Any?
          ) {
            content.recyclerView.id = View.NO_ID
            model.refreshProgressBarBackgroundColor?.let {
              content.setProgressBackgroundColorSchemeColor(
                  DEFAULT_REFRESH_SPINNER_BACKGROUND_COLOR)
            }
          }
        }
  }

  init {
    if (useTwoBindersRecycler) {
      addOptionalMountBinders(
          DelegateBinder.createDelegateBinder(this, MOUNT_BINDER),
      )
      addAttachBinder(
          DelegateBinder.createDelegateBinder(this, ATTACH_BINDER),
      )
    } else {
      addOptionalMountBinder(DelegateBinder.createDelegateBinder(this, ITEM_DECORATOR_BINDER))
      if (enableSeparateAnimatorBinder) {
        addOptionalMountBinders(
            DelegateBinder.createDelegateBinder(this, ANIMATOR_BINDER),
            DelegateBinder.createDelegateBinder(this, CONSTANT_PROPS_MOUNT_BINDER_WITHOUT_ANIMATOR))
      } else {
        addOptionalMountBinder(
            DelegateBinder.createDelegateBinder(this, CONSTANT_PROPS_MOUNT_BINDER))
      }
      addOptionalMountBinder(
          DelegateBinder.createDelegateBinder(binder, CONTENT_MOUNT_BINDER),
      )
      addAttachBinders(
          DelegateBinder.createDelegateBinder(this, CONSTANT_PROPS_ATTACH_BINDER),
          DelegateBinder.createDelegateBinder(binder, CONTENT_ATTACH_BINDER),
      )
    }
  }

  override fun measure(
      context: LayoutContext<*>,
      widthSpec: Int,
      heightSpec: Int,
      previousLayoutData: Any?
  ): MeasureResult {
    val size = Size()
    binder.measure(
        size,
        widthSpec,
        heightSpec,
        if (binder.canMeasure() || binder.isWrapContent) eventHandler { onRemeasure() } else null)
    return MeasureResult(size.width, size.height, null)
  }

  override fun createContent(context: Context): SectionsRecyclerView =
      SectionsRecyclerView(context, LithoRecyclerView(context))

  override fun doesMountRenderTreeHosts(): Boolean = true

  override fun getContentAllocator(): ContentAllocator<SectionsRecyclerView> = this
}


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

package com.facebook.litho.kotlin.widget

import android.view.View
import androidx.annotation.IdRes
import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.OrientationHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemAnimator
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import androidx.recyclerview.widget.SnapHelper
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.Component.ContainerBuilder
import com.facebook.litho.ComponentContext
import com.facebook.litho.EventHandler
import com.facebook.litho.LithoStartupLogger
import com.facebook.litho.StateValue
import com.facebook.litho.Style
import com.facebook.litho.TouchEvent
import com.facebook.litho.Wrapper
import com.facebook.litho.annotations.FromTrigger
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.OnDetached
import com.facebook.litho.annotations.OnTrigger
import com.facebook.litho.annotations.OnUpdateState
import com.facebook.litho.annotations.Param
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.PropDefault
import com.facebook.litho.annotations.ResType
import com.facebook.litho.annotations.State
import com.facebook.litho.dp
import com.facebook.litho.flexbox.flex
import com.facebook.litho.flexbox.position
import com.facebook.litho.flexbox.positionType
import com.facebook.litho.sections.BaseLoadEventsHandler
import com.facebook.litho.sections.LoadEventsHandler
import com.facebook.litho.sections.Section
import com.facebook.litho.sections.SectionContext
import com.facebook.litho.sections.SectionTree
import com.facebook.litho.sections.SectionTree.Target.DynamicConfig
import com.facebook.litho.sections.widget.ClearRefreshingEvent
import com.facebook.litho.sections.widget.ListRecyclerConfiguration
import com.facebook.litho.sections.widget.NoUpdateItemAnimator
import com.facebook.litho.sections.widget.RecyclerBinderConfiguration
import com.facebook.litho.sections.widget.RecyclerCollectionEventsController
import com.facebook.litho.sections.widget.RecyclerConfiguration
import com.facebook.litho.sections.widget.RecyclerDynamicConfigEvent
import com.facebook.litho.sections.widget.ScrollEvent
import com.facebook.litho.sections.widget.SectionBinderTarget
import com.facebook.litho.view.onTouch
import com.facebook.litho.widget.Binder
import com.facebook.litho.widget.LayoutInfo
import com.facebook.litho.widget.LithoRecyclerView.TouchInterceptor
import com.facebook.litho.widget.RecyclerBinder
import com.facebook.litho.widget.RecyclerBinder.CommitPolicy
import com.facebook.litho.widget.RecyclerEventsController
import com.facebook.litho.widget.SectionsRecyclerView.SectionsRecyclerViewLogger
import com.facebook.litho.widget.StickyHeaderControllerFactory
import com.facebook.litho.widget.ViewportInfo.ViewportChanged
import com.facebook.yoga.YogaAlign
import com.facebook.yoga.YogaEdge
import com.facebook.yoga.YogaPositionType
import main.kotlin.com.facebook.litho.kotlin.widget.ExperimentalRecyclerWrapper

/**
 * Experimental implementation of {@link RecyclerCollectionComponentSpec} that wraps a {@link
 * ExperimentalRecycler} instead of {@link RecyclerSpec}
 */
@LayoutSpec
object ExperimentalRecyclerCollectionComponentSpec {

  @get:PropDefault
  val recyclerConfiguration: RecyclerConfiguration = ListRecyclerConfiguration.create().build()
  @get:PropDefault val nestedScrollingEnabled: Boolean = true
  @get:PropDefault val scrollBarStyle: Int = View.SCROLLBARS_INSIDE_OVERLAY
  @get:PropDefault val recyclerViewId: Int = View.NO_ID
  @get:PropDefault val overScrollMode: Int = View.OVER_SCROLL_ALWAYS
  @get:PropDefault internal val asyncStateUpdates: Boolean = false
  @get:PropDefault val itemAnimator: ItemAnimator = NoUpdateItemAnimator()
  @get:PropDefault internal val asyncPropUpdates: Boolean = false
  @get:PropDefault internal val setRootAsync: Boolean = false
  @get:PropDefault val clipToPadding: Boolean = true
  @get:PropDefault val clipChildren: Boolean = true
  @get:PropDefault val incrementalMount: Boolean = true
  @get:PropDefault val refreshProgressBarColor: Int = -0xbd984e // blue
  @get:PropDefault val useTwoBindersRecycler: Boolean = false
  @get:PropDefault val enableSeparateAnimatorBinder: Boolean = false

  @OnCreateLayout
  fun onCreateLayout(
      c: ComponentContext?,
      @Prop section: Section?,
      @Prop(optional = true) loadingComponent: Component?,
      @Prop(optional = true) emptyComponent: Component?,
      @Prop(optional = true) errorComponent: Component?,
      @Prop(optional = true, varArg = "onScrollListener")
      onScrollListeners: List<OnScrollListener?>?,
      @Prop(optional = true) loadEventsHandler: LoadEventsHandler?,
      @Prop(optional = true) clipToPadding: Boolean,
      @Prop(optional = true) clipChildren: Boolean,
      @Prop(optional = true) nestedScrollingEnabled: Boolean,
      @Prop(optional = true) scrollBarStyle: Int,
      @Prop(optional = true) itemDecoration: ItemDecoration?,
      @Prop(optional = true) itemAnimator: ItemAnimator?,
      @Prop(optional = true) ignoreLoadingUpdates: Boolean,
      @Prop(optional = true) @IdRes recyclerViewId: Int,
      @Prop(optional = true) overScrollMode: Int,
      @Prop(optional = true, resType = ResType.DIMEN_SIZE) leftPadding: Int,
      @Prop(optional = true, resType = ResType.DIMEN_SIZE) rightPadding: Int,
      @Prop(optional = true, resType = ResType.DIMEN_SIZE) topPadding: Int,
      @Prop(optional = true, resType = ResType.DIMEN_SIZE) bottomPadding: Int,
      @Prop(optional = true) recyclerTouchEventHandler: EventHandler<TouchEvent?>?,
      @Prop(optional = true) horizontalFadingEdgeEnabled: Boolean,
      @Prop(optional = true) verticalFadingEdgeEnabled: Boolean,
      @Prop(optional = true, resType = ResType.DIMEN_SIZE) fadingEdgeLength: Int,
      @Prop(optional = true, resType = ResType.COLOR) refreshProgressBarBackgroundColor: Int?,
      @Prop(optional = true, resType = ResType.COLOR) refreshProgressBarColor: Int,
      @Prop(optional = true) touchInterceptor: TouchInterceptor?,
      @Prop(optional = true) itemTouchListener: OnItemTouchListener?,
      @Prop(optional = true) setRootAsync: Boolean,
      @Prop(optional = true) disablePTR: Boolean,
      @Prop(optional = true) recyclerConfiguration: RecyclerConfiguration,
      @Prop(optional = true) sectionsViewLogger: SectionsRecyclerViewLogger?,
      @Prop(optional = true) useTwoBindersRecycler: Boolean,
      @Prop(optional = true) enableSeparateAnimatorBinder: Boolean,
      @State(canUpdateLazily = true) hasSetSectionTreeRoot: Boolean,
      @State internalEventsController: RecyclerCollectionEventsController,
      @State layoutInfo: LayoutInfo,
      @State loadingState: LoadingState,
      @State binder: Binder<RecyclerView>,
      @State sectionTree: SectionTree,
      @State recyclerCollectionLoadEventsHandler: RecyclerCollectionLoadEventsHandler,
      @State snapHelper: SnapHelper?
  ): Component? {

    // This is a side effect from OnCreateLayout, so it's inherently prone to race conditions:
    recyclerCollectionLoadEventsHandler.setLoadEventsHandler(loadEventsHandler)

    // More side effects in OnCreateLayout. Watch out:
    if (hasSetSectionTreeRoot && setRootAsync) {
      sectionTree.setRootAsync(section)
    } else {
      ExperimentalRecyclerCollectionComponent.lazyUpdateHasSetSectionTreeRoot(c, true)
      sectionTree.setRoot(section)
    }
    val isErrorButNoErrorComponent = loadingState == LoadingState.ERROR && errorComponent == null
    val isEmptyButNoEmptyComponent = loadingState == LoadingState.EMPTY && emptyComponent == null
    val shouldHideComponent = isEmptyButNoEmptyComponent || isErrorButNoErrorComponent
    if (shouldHideComponent) {
      return null
    }
    val canPTR = recyclerConfiguration.orientation != OrientationHelper.HORIZONTAL && !disablePTR
    val onScrolledListener =
        object : RecyclerView.OnScrollListener() {
          override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            internalEventsController.updateFirstLastFullyVisibleItemPositions(layoutInfo)
          }
        }
    val onScrollListenersList =
        if (onScrollListeners != null) onScrollListeners.toMutableList() else mutableListOf()
    onScrollListenersList.add(onScrolledListener)
    val touchStyle =
        if (recyclerTouchEventHandler != null)
            Style.onTouch {
              recyclerTouchEventHandler?.dispatchEvent(it)
              true
            }
        else null
    val positionStyle =
        if (!binder.canMeasure() &&
            !recyclerConfiguration.recyclerBinderConfiguration.isWrapContent) {
          Style.positionType(YogaPositionType.ABSOLUTE).position(all = 0.dp)
        } else null
    val recycler =
        ExperimentalRecyclerWrapper(
            style = Style.flex(shrink = 0f) + touchStyle + positionStyle,
            isClipToPaddingEnabled = clipToPadding,
            isClipChildrenEnabled = clipChildren,
            nestedScrollingEnabled = nestedScrollingEnabled,
            scrollBarStyle = scrollBarStyle,
            recyclerViewId = recyclerViewId,
            overScrollMode = overScrollMode,
            recyclerEventsController = internalEventsController,
            onRefresh =
                if (!canPTR) null
                else {
                  { sectionTree.refresh() }
                },
            pullToRefresh = canPTR,
            itemDecoration = itemDecoration,
            horizontalFadingEdgeEnabled = horizontalFadingEdgeEnabled,
            verticalFadingEdgeEnabled = verticalFadingEdgeEnabled,
            fadingEdgeLength = fadingEdgeLength.dp,
            onScrollListeners = onScrollListenersList,
            refreshProgressBarBackgroundColor = refreshProgressBarBackgroundColor,
            refreshProgressBarColor = refreshProgressBarColor,
            snapHelper = snapHelper,
            touchInterceptor = touchInterceptor,
            onItemTouchListener = itemTouchListener,
            binder = binder,
            itemAnimator = itemAnimator,
            sectionsViewLogger = sectionsViewLogger,
            useTwoBindersRecycler = useTwoBindersRecycler,
            enableSeparateAnimatorBinder = enableSeparateAnimatorBinder,
            leftPadding = leftPadding,
            topPadding = topPadding,
            rightPadding = rightPadding,
            bottomPadding = bottomPadding)
    val containerBuilder: ContainerBuilder<*> =
        Column.create(c).flexShrink(0f).alignContent(YogaAlign.FLEX_START).child(recycler)
    if (loadingState == LoadingState.LOADING && loadingComponent != null) {
      containerBuilder.child(
          Wrapper.create(c)
              .delegate(loadingComponent)
              .flexShrink(0f)
              .positionType(YogaPositionType.ABSOLUTE)
              .positionPx(YogaEdge.ALL, 0))
    } else if (loadingState == LoadingState.EMPTY) {
      containerBuilder.child(
          Wrapper.create(c)
              .delegate(emptyComponent)
              .flexShrink(0f)
              .positionType(YogaPositionType.ABSOLUTE)
              .positionPx(YogaEdge.ALL, 0))
    } else if (loadingState == LoadingState.ERROR) {
      containerBuilder.child(
          Wrapper.create(c)
              .delegate(errorComponent)
              .flexShrink(0f)
              .positionType(YogaPositionType.ABSOLUTE)
              .positionPx(YogaEdge.ALL, 0))
    }
    return containerBuilder.build()
  }

  @OnCreateInitialState
  fun createInitialState(
      c: ComponentContext,
      snapHelper: StateValue<SnapHelper?>,
      sectionTree: StateValue<SectionTree?>,
      recyclerCollectionLoadEventsHandler: StateValue<RecyclerCollectionLoadEventsHandler?>,
      binder: StateValue<Binder<RecyclerView>?>,
      loadingState: StateValue<LoadingState?>,
      internalEventsController: StateValue<RecyclerCollectionEventsController?>,
      layoutInfo: StateValue<LayoutInfo?>,
      @Prop section: Section,
      @Prop(optional = true) recyclerConfiguration: RecyclerConfiguration,
      @Prop(optional = true) eventsController: RecyclerCollectionEventsController?,
      @Prop(optional = true) asyncPropUpdates: Boolean,
      @Prop(optional = true)
      asyncStateUpdates:
          Boolean, // NB: This is a *workaround* for sections that use non-threadsafe models, e.g.
      // models that
      // may be updated from the main thread while background changesets would be calculated. It has
      // negative performance implications since it forces all changesets to be calculated on the
      // main thread!
      @Prop(optional = true) forceSyncStateUpdates: Boolean, // Caution: ignoreLoadingUpdates breaks
      // loadingComponent/errorComponent/emptyComponent.
      // It's intended to be a temporary workaround, not something you should use often.
      @Prop(optional = true) ignoreLoadingUpdates: Boolean,
      @Prop(optional = true) sectionTreeTag: String?,
      @Prop(optional = true)
      canMeasureRecycler:
          Boolean, // Don't use this. If false, off incremental mount for all subviews of this
      // Recycler.
      @Prop(optional = true) incrementalMount: Boolean,
      @Prop(optional = true) startupLogger: LithoStartupLogger?,
      @Prop(optional = true) stickyHeaderControllerFactory: StickyHeaderControllerFactory?
  ) {
    val binderConfiguration = recyclerConfiguration.recyclerBinderConfiguration
    val newLayoutInfo = recyclerConfiguration.getLayoutInfo(c)
    layoutInfo.set(newLayoutInfo)
    val recyclerBinderBuilder =
        RecyclerBinder.Builder()
            .layoutInfo(newLayoutInfo)
            .rangeRatio(binderConfiguration.rangeRatio)
            .layoutHandlerFactory(binderConfiguration.layoutHandlerFactory)
            .wrapContent(binderConfiguration.isWrapContent)
            .enableStableIds(binderConfiguration.enableStableIds)
            .invalidStateLogParamsList(binderConfiguration.invalidStateLogParamsList)
            .threadPoolConfig(binderConfiguration.threadPoolConfiguration)
            .hscrollAsyncMode(binderConfiguration.hScrollAsyncMode)
            .isCircular(binderConfiguration.isCircular)
            .hasDynamicItemHeight(binderConfiguration.hasDynamicItemHeight())
            .incrementalMount(incrementalMount)
            .stickyHeaderControllerFactory(stickyHeaderControllerFactory)
            .componentsConfiguration(binderConfiguration.componentsConfiguration)
            .isReconciliationEnabled(binderConfiguration.isReconciliationEnabled)
            .isLayoutDiffingEnabled(binderConfiguration.isLayoutDiffingEnabled)
            .componentWarmer(binderConfiguration.componentWarmer)
            .lithoViewFactory(binderConfiguration.lithoViewFactory)
            .errorEventHandler(binderConfiguration.errorEventHandler)
            .recyclerViewItemPrefetch(binderConfiguration.enableItemPrefetch)
            .startupLogger(startupLogger)
    if (binderConfiguration.estimatedViewportCount != RecyclerBinderConfiguration.Builder.UNSET) {
      recyclerBinderBuilder.estimatedViewportCount(binderConfiguration.estimatedViewportCount)
    }
    val recyclerBinder = recyclerBinderBuilder.build(c)
    val targetBinder =
        SectionBinderTarget(recyclerBinder, binderConfiguration.useBackgroundChangeSets)
    val sectionContext = SectionContext(c)
    binder.set(targetBinder)
    snapHelper.set(recyclerConfiguration.snapHelper)
    val sectionTreeInstance =
        SectionTree.create(sectionContext, targetBinder)
            .tag(
                if (sectionTreeTag == null || sectionTreeTag == "") section.simpleName
                else sectionTreeTag)
            .asyncPropUpdates(asyncPropUpdates)
            .asyncStateUpdates(asyncStateUpdates)
            .forceSyncStateUpdates(forceSyncStateUpdates)
            .changeSetThreadHandler(binderConfiguration.changeSetThreadHandler)
            .postToFrontOfQueueForFirstChangeset(
                binderConfiguration.isPostToFrontOfQueueForFirstChangeset)
            .build()
    sectionTree.set(sectionTreeInstance)
    val internalEventsControllerInstance = eventsController ?: RecyclerCollectionEventsController()
    internalEventsControllerInstance.setSectionTree(sectionTreeInstance)
    internalEventsControllerInstance.setSnapMode(recyclerConfiguration.snapMode)
    internalEventsController.set(internalEventsControllerInstance)
    val recyclerCollectionLoadEventsHandlerInstance =
        RecyclerCollectionLoadEventsHandler(
            c, internalEventsControllerInstance, ignoreLoadingUpdates)
    recyclerCollectionLoadEventsHandler.set(recyclerCollectionLoadEventsHandlerInstance)
    sectionTreeInstance.setLoadEventsHandler(recyclerCollectionLoadEventsHandlerInstance)
    val viewPortChanged =
        ViewportChanged {
            firstVisibleIndex,
            lastVisibleIndex,
            firstFullyVisibleIndex,
            lastFullyVisibleIndex,
            state ->
          sectionTreeInstance.viewPortChanged(
              firstVisibleIndex,
              lastVisibleIndex,
              firstFullyVisibleIndex,
              lastFullyVisibleIndex,
              state)
        }
    targetBinder.setViewportChangedListener(viewPortChanged)
    targetBinder.setCanMeasure(canMeasureRecycler)
    if (ignoreLoadingUpdates) {
      loadingState.set(LoadingState.LOADED)
    } else {
      loadingState.set(LoadingState.LOADING)
    }
  }

  @OnUpdateState
  fun updateLoadingState(
      loadingState: StateValue<LoadingState?>,
      @Param currentLoadingState: LoadingState?
  ) {
    loadingState.set(currentLoadingState)
  }

  @OnTrigger(ScrollEvent::class)
  fun onScroll(
      c: ComponentContext?,
      @FromTrigger position: Int,
      @FromTrigger animate: Boolean,
      @State sectionTree: SectionTree,
      @State internalEventsController: RecyclerCollectionEventsController?
  ) {
    sectionTree.requestFocusOnRoot(position)
  }

  @OnTrigger(RecyclerDynamicConfigEvent::class)
  fun onRecyclerConfigChanged(
      c: ComponentContext?,
      @FromTrigger @CommitPolicy commitPolicy: Int,
      @State sectionTree: SectionTree
  ) {
    sectionTree.setTargetConfig(DynamicConfig(commitPolicy))
  }

  @OnTrigger(ClearRefreshingEvent::class)
  fun onClearRefreshing(
      c: ComponentContext?,
      @State internalEventsController: RecyclerCollectionEventsController
  ) {
    internalEventsController.clearRefreshing()
  }

  @OnDetached
  fun onDetached(c: ComponentContext?, @State binder: Binder<RecyclerView>) {
    binder.detach()
  }

  @VisibleForTesting
  enum class LoadingState {
    /** We're loading but don't have any content yet. */
    LOADING,

    /** A load completed and we have content. */
    LOADED,

    /** A load completed, but the content is empty. */
    EMPTY,

    /** A load failed with an error. */
    ERROR
  }

  class RecyclerCollectionLoadEventsHandler(
      private val componentContext: ComponentContext,
      private val recyclerEventsController: RecyclerEventsController,
      private val ignoreLoadingUpdates: Boolean
  ) : BaseLoadEventsHandler() {
    private var delegate: LoadEventsHandler? = null
    private var lastState = LoadingState.LOADING

    /** May be called from any thread (in OnCreateLayout). (Does this need synchronization?) */
    fun setLoadEventsHandler(delegate: LoadEventsHandler?) {
      this.delegate = delegate
    }

    /**
     * One would hope this is only called from one thread, since onLoadSucceeded could arrive before
     * onLoadStarted if you post them on different threads. But use synchronized to defend against
     * bad clients.
     *
     * This method exists to avoid thrashing Litho with state updates as we do a bunch of load
     * operations. In theory you could call updateLoadingStateAsync every single time and get the
     * same result, but it's more efficient to avoid all the unnecessary updates.
     */
    @Synchronized
    private fun updateState(newState: LoadingState) {
      if (ignoreLoadingUpdates) {
        return
      }
      if (lastState != newState) {
        lastState = newState
        ExperimentalRecyclerCollectionComponent.updateLoadingStateAsync(componentContext, newState)
      }
    }

    override fun onLoadStarted(empty: Boolean) {
      updateState(if (empty) LoadingState.LOADING else LoadingState.LOADED)
      val delegate = delegate
      delegate?.onLoadStarted(empty)
    }

    override fun onLoadSucceeded(empty: Boolean) {
      updateState(if (empty) LoadingState.EMPTY else LoadingState.LOADED)
      recyclerEventsController.clearRefreshing()
      val delegate = delegate
      delegate?.onLoadSucceeded(empty)
    }

    override fun onLoadFailed(empty: Boolean) {
      updateState(if (empty) LoadingState.ERROR else LoadingState.LOADED)
      recyclerEventsController.clearRefreshing()
      val delegate = delegate
      delegate?.onLoadFailed(empty)
    }

    override fun onInitialLoad() {
      val delegate = delegate
      delegate?.onInitialLoad()
    }
  }
}


/*
 * 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 main.kotlin.com.facebook.litho.kotlin.widget

import android.graphics.Color
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SnapHelper
import com.facebook.litho.Dimen
import com.facebook.litho.Style
import com.facebook.litho.dp
import com.facebook.litho.kotlin.widget.ExperimentalPrimitiveRecycler
import com.facebook.litho.widget.Binder
import com.facebook.litho.widget.LithoRecyclerView
import com.facebook.litho.widget.RecyclerEventsController
import com.facebook.litho.widget.SectionsRecyclerView

fun ExperimentalRecyclerWrapper(
    binder: Binder<RecyclerView>,
    hasFixedSize: Boolean? = null,
    isClipToPaddingEnabled: Boolean? = null,
    refreshProgressBarBackgroundColor: Int? = null,
    refreshProgressBarColor: Int? = null,
    isClipChildrenEnabled: Boolean? = null,
    nestedScrollingEnabled: Boolean? = null,
    scrollBarStyle: Int? = null,
    itemDecoration: RecyclerView.ItemDecoration? = null,
    horizontalFadingEdgeEnabled: Boolean? = null,
    verticalFadingEdgeEnabled: Boolean? = null,
    fadingEdgeLength: Dimen? = null,
    recyclerViewId: Int? = null,
    overScrollMode: Int? = null,
    contentDescription: CharSequence? = null,
    itemAnimator: RecyclerView.ItemAnimator? = ExperimentalPrimitiveRecycler.DEFAULT_ITEM_ANIMATOR,
    recyclerEventsController: RecyclerEventsController? = null,
    onScrollListeners: List<RecyclerView.OnScrollListener?>? = null,
    snapHelper: SnapHelper? = null,
    pullToRefresh: Boolean? = null,
    touchInterceptor: LithoRecyclerView.TouchInterceptor? = null,
    onItemTouchListener: RecyclerView.OnItemTouchListener? = null,
    onRefresh: (() -> Unit)? = null,
    sectionsViewLogger: SectionsRecyclerView.SectionsRecyclerViewLogger? = null,
    useTwoBindersRecycler: Boolean = false,
    enableSeparateAnimatorBinder: Boolean = false,
    leftPadding: Int = 0,
    topPadding: Int = 0,
    rightPadding: Int = 0,
    bottomPadding: Int = 0,
    style: Style? = null
): ExperimentalPrimitiveRecycler =
    ExperimentalPrimitiveRecycler(
        binder = binder,
        hasFixedSize = hasFixedSize ?: true,
        isClipToPaddingEnabled = isClipToPaddingEnabled ?: true,
        refreshProgressBarBackgroundColor = refreshProgressBarBackgroundColor,
        refreshProgressBarColor = refreshProgressBarColor ?: Color.BLACK,
        isClipChildrenEnabled = isClipChildrenEnabled ?: true,
        isNestedScrollingEnabled = nestedScrollingEnabled ?: true,
        scrollBarStyle = scrollBarStyle ?: View.SCROLLBARS_INSIDE_OVERLAY,
        itemDecoration = itemDecoration,
        isHorizontalFadingEdgeEnabled = horizontalFadingEdgeEnabled ?: false,
        isVerticalFadingEdgeEnabled = verticalFadingEdgeEnabled ?: false,
        fadingEdgeLength = fadingEdgeLength ?: 0.dp,
        recyclerViewId = recyclerViewId ?: View.NO_ID,
        overScrollMode = overScrollMode ?: View.OVER_SCROLL_ALWAYS,
        contentDescription = contentDescription,
        itemAnimator = itemAnimator ?: ExperimentalPrimitiveRecycler.DEFAULT_ITEM_ANIMATOR,
        recyclerEventsController = recyclerEventsController,
        onScrollListeners = onScrollListeners ?: emptyList(),
        snapHelper = snapHelper,
        isPullToRefreshEnabled = pullToRefresh ?: true,
        touchInterceptor = touchInterceptor,
        onItemTouchListener = onItemTouchListener,
        onRefresh = onRefresh,
        sectionsViewLogger = sectionsViewLogger,
        leftPadding = leftPadding,
        topPadding = topPadding,
        rightPadding = rightPadding,
        bottomPadding = bottomPadding,
        style = style)


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

package com.facebook.litho.kotlin.widget

import android.view.View
import com.facebook.litho.Component
import com.facebook.litho.Dimen
import com.facebook.litho.ResourcesScope
import com.facebook.litho.widget.HorizontalScroll
import com.facebook.litho.widget.HorizontalScrollEventsController

/** Builder function for creating [HorizontalScrollSpec] components. */
@Suppress("FunctionName")
inline fun ResourcesScope.HorizontalScroll(
    initialScrollPosition: Dimen? = null,
    scrollbarEnabled: Boolean = true,
    fillViewport: Boolean = false,
    eventsController: HorizontalScrollEventsController? = null,
    noinline onScrollChange: ((View, scrollX: Int, oldScrollX: Int) -> Unit)? = null,
    horizontalFadingEdgeEnabled: Boolean = false,
    fadingEdgeLength: Int = 0,
    child: ResourcesScope.() -> Component
): HorizontalScroll =
    HorizontalScroll.create(context)
        .contentProps(child())
        .apply { initialScrollPosition?.let { initialScrollPosition(it.toPixels()) } }
        .scrollbarEnabled(scrollbarEnabled)
        .fillViewport(fillViewport)
        .eventsController(eventsController)
        .onScrollChangeListener(onScrollChange)
        .horizontalFadingEdgeEnabled(horizontalFadingEdgeEnabled)
        .fadingEdgeLength(fadingEdgeLength)
        .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.kotlin.widget

import android.graphics.drawable.Drawable
import android.widget.ImageView.ScaleType
import com.facebook.litho.ResourcesScope
import com.facebook.litho.Style
import com.facebook.litho.kotlinStyle
import com.facebook.litho.widget.Image

/** Builder function for creating [ImageSpec] components. */
@Suppress("NOTHING_TO_INLINE", "FunctionName")
inline fun ResourcesScope.Image(
    drawable: Drawable,
    style: Style? = null,
    scaleType: ScaleType = ScaleType.FIT_CENTER
): Image = Image.create(context).drawable(drawable).scaleType(scaleType).kotlinStyle(style).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.kotlin.widget

import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
import com.facebook.litho.ResourcesScope
import com.facebook.litho.Style
import com.facebook.litho.kotlinStyle
import com.facebook.litho.widget.Progress

/** Builder function for creating [ProgressSpec] components. */
@Suppress("NOTHING_TO_INLINE", "FunctionName")
inline fun ResourcesScope.Progress(
    @ColorInt color: Int? = null,
    style: Style? = null,
    indeterminateDrawable: Drawable? = null
): Progress =
    Progress.create(context)
        .apply {
          color?.let { color(color) }
          indeterminateDrawable?.let { indeterminateDrawable(indeterminateDrawable) }
        }
        .kotlinStyle(style)
        .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.kotlin.widget

import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentScope
import com.facebook.litho.ResourcesScope
import com.facebook.litho.Style
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayoutWithSizeSpec
import com.facebook.litho.annotations.Prop
import com.facebook.litho.kotlinStyle
import com.facebook.rendercore.SizeConstraints

/**
 * Utility for creating a layout tree that depends on its own and/or its children's size.
 *
 * For example, Component A is returned if it fits within the specified width otherwise Component B
 * is returned.
 *
 * Example:
 * ```
 * SizeConstraintsAwareComponent { sizeConstraints ->
 *     val textComponent = Text(textSize = 16.sp, text = "Some text to measure")
 *
 *     val textOutputSize = Size()
 *
 *     textComponent.measure(
 *     context,
 *     SizeSpec.makeSizeSpec(0, UNSPECIFIED),
 *     SizeSpec.makeSizeSpec(0, UNSPECIFIED),
 *     textOutputSize)
 *
 *     // Small component to use in case textComponent doesnâ€™t fit within
 *     // the current layout.
 *     val imageComponent = Image(drawable = drawableRes(R.drawable.ic_launcher))
 *
 *     // Assuming sizeConstraints.hasBoundedWidth == true
 *     val doesTextFit = textOutputSize.width <= sizeConstraints.maxWidth
 *
 *     if (doesTextFit) textComponent else imageComponent
 * }
 * ```
 */
@Suppress("FunctionName")
fun ResourcesScope.SizeConstraintsAwareComponent(
    style: Style? = null,
    content: ComponentScope.(SizeConstraints) -> Component
): Component {
  return SizeSpecsWrapperComponent.create(context).content(content).kotlinStyle(style).build()
}

@LayoutSpec
private object SizeSpecsWrapperComponentSpec {
  @JvmStatic
  @OnCreateLayoutWithSizeSpec
  fun onCreateLayoutWithSizeSpec(
      c: ComponentContext,
      widthSpec: Int,
      heightSpec: Int,
      @Prop content: ComponentScope.(SizeConstraints) -> Component
  ): Component {
    val scope = ComponentScope(c, c.renderStateContext)
    return scope.content(SizeConstraints.fromMeasureSpecs(widthSpec, heightSpec))
  }
}


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

import androidx.annotation.ColorInt
import com.facebook.litho.ResourcesScope
import com.facebook.litho.Style
import com.facebook.litho.kotlinStyle
import com.facebook.litho.widget.SolidColor

/** Builder function for creating [SolidColorSpec] components. */
@Suppress("NOTHING_TO_INLINE", "FunctionName")
inline fun ResourcesScope.SolidColor(@ColorInt color: Int, style: Style? = null): SolidColor =
    SolidColor.create(context).color(color).kotlinStyle(style).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.kotlin.widget

import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
import androidx.annotation.LayoutRes
import com.facebook.litho.Dimen
import com.facebook.litho.ResourcesScope
import com.facebook.litho.eventHandler
import com.facebook.litho.sp
import com.facebook.litho.widget.ItemSelectedEvent
import com.facebook.litho.widget.Spinner

/** Builder function for creating [SpinnerSpec] components. */
@Suppress("NOTHING_TO_INLINE", "FunctionName")
inline fun ResourcesScope.Spinner(
    options: List<String>,
    selectedOption: String,
    @LayoutRes itemLayout: Int = android.R.layout.simple_dropdown_item_1line,
    selectedTextSize: Dimen = 16.sp,
    @ColorInt selectedTextColor: Int = 0xDE000000.toInt(),
    caret: Drawable? = null,
    noinline onItemSelected: (ItemSelectedEvent) -> Unit
): Spinner =
    Spinner.create(context)
        .options(options)
        .selectedOption(selectedOption)
        .itemLayout(itemLayout)
        .selectedTextColor(selectedTextColor)
        .selectedTextSizePx(selectedTextSize.toPixels().toFloat())
        .caret(caret)
        .itemSelectedEventHandler(eventHandler(onItemSelected))
        .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.kotlin.widget

import android.graphics.Color
import android.graphics.Typeface
import android.graphics.Typeface.DEFAULT
import android.graphics.Typeface.NORMAL
import android.text.TextUtils
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.StyleRes
import androidx.core.text.TextDirectionHeuristicCompat
import com.facebook.litho.Dimen
import com.facebook.litho.DynamicValue
import com.facebook.litho.Handle
import com.facebook.litho.ResourcesScope
import com.facebook.litho.Style
import com.facebook.litho.dp
import com.facebook.litho.kotlinStyle
import com.facebook.litho.sp
import com.facebook.litho.widget.Text
import com.facebook.litho.widget.TextAlignment
import com.facebook.litho.widget.VerticalGravity

/**
 * Temporary builder function for creating [TextSpec] components. In the future it will either be
 * auto-generated or modified to have the final set of parameters.
 */
@Suppress("NOTHING_TO_INLINE", "FunctionName")
inline fun ResourcesScope.Text(
    text: CharSequence?,
    style: Style? = null,
    @ColorInt textColor: Int = Color.BLACK,
    textSize: Dimen = 14.sp,
    textStyle: Int = NORMAL,
    typeface: Typeface? = DEFAULT,
    @ColorInt shadowColor: Int = Color.GRAY,
    shadowRadius: Dimen = 0.dp,
    alignment: TextAlignment = TextAlignment.TEXT_START,
    breakStrategy: Int = 0,
    verticalGravity: VerticalGravity = VerticalGravity.TOP,
    isSingleLine: Boolean = false,
    ellipsize: TextUtils.TruncateAt? = null,
    lineSpacingMultiplier: Float = 1f,
    lineHeight: Dimen? = null,
    extraSpacing: Dimen? = null,
    letterSpacing: Float = 0f,
    minLines: Int = 0,
    maxLines: Int = Int.MAX_VALUE,
    includeFontPadding: Boolean = true,
    accessibleClickableSpans: Boolean = false,
    clipToBounds: Boolean = true,
    handle: Handle? = null,
    customEllipsisText: CharSequence? = null,
    @ColorInt backgroundColor: Int? = null,
    @ColorInt highlightColor: Int? = null,
    textDirection: TextDirectionHeuristicCompat? = null,
    @AttrRes defStyleAttr: Int = 0,
    @StyleRes defStyleRes: Int = 0,
    dynamicTextColor: DynamicValue<Int>? = null,
    testKey: String? = null
): Text {
  val builder =
      Text.create(context, defStyleAttr, defStyleRes)
          .text(text)
          .dynamicTextColor(dynamicTextColor)
          .textColor(textColor)
          .textSizePx(textSize.toPixels())
          .textStyle(textStyle)
          .typeface(typeface)
          .shadowColor(shadowColor)
          .shadowRadiusPx(shadowRadius.toPixels().toFloat())
          .alignment(alignment)
          .breakStrategy(breakStrategy)
          .verticalGravity(verticalGravity)
          .spacingMultiplier(lineSpacingMultiplier)
          .isSingleLine(isSingleLine)
          .minLines(minLines)
          .maxLines(maxLines)
          .apply { lineHeight?.let { lineHeightPx(it.toPixels().toFloat()) } }
          .apply { extraSpacing?.let { extraSpacingPx(it.toPixels().toFloat()) } }
          .letterSpacing(letterSpacing)
          .shouldIncludeFontPadding(includeFontPadding)
          .accessibleClickableSpans(accessibleClickableSpans)
          .clipToBounds(clipToBounds)
          .apply { customEllipsisText?.let { customEllipsisText(customEllipsisText) } }
          .apply { ellipsize?.let { ellipsize(it) } }
          .handle(handle)
          .apply { backgroundColor?.let { backgroundColor(backgroundColor) } }
          .apply { highlightColor?.let { highlightColor(it) } }
          .textDirection(textDirection)
          .kotlinStyle(style)

  if (testKey != null) {
    builder.testKey(testKey)
  }

  return builder.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.kotlin.widget

import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.text.InputFilter
import android.text.TextWatcher
import android.view.Gravity
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import androidx.annotation.ColorInt
import com.facebook.litho.Dimen
import com.facebook.litho.Handle
import com.facebook.litho.ResourcesScope
import com.facebook.litho.Style
import com.facebook.litho.eventHandler
import com.facebook.litho.eventHandlerWithReturn
import com.facebook.litho.kotlinStyle
import com.facebook.litho.sp
import com.facebook.litho.widget.EditorActionEvent
import com.facebook.litho.widget.InputConnectionEvent
import com.facebook.litho.widget.InputFocusChangedEvent
import com.facebook.litho.widget.KeyPreImeEvent
import com.facebook.litho.widget.KeyUpEvent
import com.facebook.litho.widget.SelectionChangedEvent
import com.facebook.litho.widget.TextChangedEvent
import com.facebook.litho.widget.TextInput

/** Builder function for creating [TextInputSpec] components. */
@Suppress("NOTHING_TO_INLINE", "FunctionName")
inline fun ResourcesScope.TextInput(
    initialText: CharSequence,
    style: Style? = null,
    hint: CharSequence = "",
    @ColorInt textColor: Int = Color.BLACK,
    @ColorInt hintTextColor: Int = Color.LTGRAY,
    textSize: Dimen = 14.sp,
    typeface: Typeface = Typeface.DEFAULT,
    textAlignment: Int = View.TEXT_ALIGNMENT_GRAVITY,
    gravity: Int = Gravity.CENTER_VERTICAL or Gravity.START,
    editable: Boolean = true,
    multiline: Boolean = false,
    minLines: Int = 1,
    maxLines: Int = Int.MAX_VALUE,
    inputType: Int = EditorInfo.TYPE_CLASS_TEXT,
    imeOptions: Int = EditorInfo.IME_NULL,
    inputFilter: InputFilter? = null,
    inputFilters: List<InputFilter>? = null,
    /** Called when the text entered by the user changes. */
    noinline onTextChanged: ((TextChangedEvent) -> Unit)? = null,
    /** Called when the selection (particular case: cursor position) gets changed by user. */
    noinline onSelectionChanged: ((SelectionChangedEvent) -> Unit)? = null,
    /** Called when the input focus changed by user. */
    noinline onInputFocusChanged: ((InputFocusChangedEvent) -> Unit)? = null,
    /** Called when underlying [android.widget.EditText#onKeyUp()] happens. */
    noinline onKeyUp: ((KeyUpEvent) -> Boolean)? = null,
    /** Called when underlying [android.widget.EditText#onKeyPreIme()] happens. */
    noinline onKeyPreIme: ((KeyPreImeEvent) -> Boolean)? = null,
    /** Called when the return key is pressed or the IME signals an 'action'. */
    noinline onEditorAction: ((EditorActionEvent) -> Boolean)? = null,
    /**
     * Called when underlying
     * [android.widget.EditText#onCreateInputConnection(EditorInfo editorInfo)] happens.
     */
    noinline onInputConnection: ((InputConnectionEvent) -> InputConnection)? = null,
    handle: Handle? = null,
    inputBackground: Drawable? = null,
    textWatcher: TextWatcher? = null,
): TextInput =
    TextInput.create(context)
        .inputFilters(inputFilters)
        .initialText(initialText)
        .hint(hint)
        .textColorStateList(ColorStateList.valueOf(textColor))
        .hintColorStateList(ColorStateList.valueOf(hintTextColor))
        .textSizePx(textSize.toPixels())
        .typeface(typeface)
        .textAlignment(textAlignment)
        .gravity(gravity)
        .editable(editable)
        .multiline(multiline)
        .minLines(minLines)
        .maxLines(maxLines)
        .inputType(inputType)
        .imeOptions(imeOptions)
        .inputFilter(inputFilter)
        .inputBackground(inputBackground)
        .textWatcher(textWatcher)
        .handle(handle)
        .kotlinStyle(style)
        .apply {
          onTextChanged?.let { textChangedEventHandler(eventHandler(it)) }
          onSelectionChanged?.let { selectionChangedEventHandler(eventHandler(it)) }
          onInputFocusChanged?.let { inputFocusChangedEventHandler(eventHandler(it)) }
          onKeyUp?.let { keyUpEventHandler(eventHandlerWithReturn(it)) }
          onKeyPreIme?.let { keyPreImeEventHandler(eventHandlerWithReturn(it)) }
          onEditorAction?.let { editorActionEventHandler(eventHandlerWithReturn(it)) }
          onInputConnection?.let { inputConnectionEventHandler(eventHandlerWithReturn(it)) }
        }
        .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.kotlin.widget

import android.view.MotionEvent
import androidx.core.widget.NestedScrollView
import com.facebook.litho.Component
import com.facebook.litho.Dimen
import com.facebook.litho.ResourcesScope
import com.facebook.litho.Style
import com.facebook.litho.dp
import com.facebook.litho.kotlinStyle
import com.facebook.litho.px
import com.facebook.litho.widget.VerticalScroll
import com.facebook.litho.widget.VerticalScrollEventsController

/** Builder function for creating [VerticalScrollSpec] components. */
@Suppress("FunctionName")
inline fun ResourcesScope.VerticalScroll(
    initialScrollPosition: Dimen = 0.px,
    scrollbarEnabled: Boolean = false,
    scrollbarFadingEnabled: Boolean = true,
    verticalFadingEdgeEnabled: Boolean = false,
    nestedScrollingEnabled: Boolean = false,
    fadingEdgeLength: Dimen = 0.dp,
    fillViewport: Boolean = false,
    incrementalMountEnabled: Boolean = false,
    eventsController: VerticalScrollEventsController? = null,
    noinline onScrollChange: ((NestedScrollView, scrollY: Int, oldScrollY: Int) -> Unit)? = null,
    noinline onInterceptTouch: ((NestedScrollView, event: MotionEvent) -> Boolean)? = null,
    style: Style? = null,
    child: ResourcesScope.() -> Component
): VerticalScroll =
    VerticalScroll.create(context)
        .childComponent(child())
        .initialScrollOffsetPixels(initialScrollPosition.toPixels())
        .scrollbarEnabled(scrollbarEnabled)
        .scrollbarFadingEnabled(scrollbarFadingEnabled)
        .verticalFadingEdgeEnabled(verticalFadingEdgeEnabled)
        .nestedScrollingEnabled(nestedScrollingEnabled)
        .fadingEdgeLengthPx(fadingEdgeLength.toPixels())
        .fillViewport(fillViewport)
        .incrementalMountEnabled(incrementalMountEnabled)
        .eventsController(eventsController)
        .apply {
          onScrollChange?.let {
            onScrollChangeListener { v, _, scrollY, _, oldScrollY -> it(v, scrollY, oldScrollY) }
          }
        }
        .onInterceptTouchListener(onInterceptTouch)
        .kotlinStyle(style)
        .build()