/*
 * 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.samples.litho.kotlin.triggers

import android.graphics.Color
import android.graphics.Typeface
import android.widget.LinearLayout
import android.widget.PopupWindow
import androidx.annotation.ColorInt
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.Handle
import com.facebook.litho.KComponent
import com.facebook.litho.LithoTooltipController
import com.facebook.litho.LithoView
import com.facebook.litho.Style
import com.facebook.litho.core.margin
import com.facebook.litho.core.padding
import com.facebook.litho.dp
import com.facebook.litho.handle
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.useCached
import com.facebook.litho.view.backgroundColor
import com.facebook.litho.view.onClick
import com.facebook.litho.visibility.onVisible
import com.facebook.yoga.YogaAlign

class TooltipTriggerExampleKComponent : KComponent() {

  override fun ComponentScope.render(): Component? {
    // creating_handle_start
    val anchorHandle = useCached { Handle() }
    // creating_handle_end

    return Column(alignItems = YogaAlign.CENTER) {
      child(
          Text(
              text = "Click to trigger/show tooltip",
              defStyleAttr = android.R.attr.buttonStyle,
              typeface = Typeface.DEFAULT_BOLD,
              style =
                  Style.padding(horizontal = 20.dp, vertical = 25.dp).onClick {
                    showToolTip(anchorHandle)
                  }))
      child(
          // using_handle_start
          handle(anchorHandle) {
            Text(
                text = "Tooltip anchor",
                style = Style.margin(top = 50.dp).onVisible { showToolTip(anchorHandle) })
          }
          // using_handle_end
          )
    }
  }

  // showing_tooltip_start
  private fun ComponentScope.showToolTip(anchorHandle: Handle) {
    LithoTooltipController.showTooltipOnHandle(
        context, createTooltip("Example Tooltip"), anchorHandle, 0, 0)
  }
  // showing_tooltip_end

  private fun ComponentScope.createTooltip(tooltipText: String) =
      PopupWindow(
          LithoView.create(
              context,
              Column(style = Style.padding(all = 15.dp).backgroundColor(LITHO_PINK)) {
                child(Text(text = tooltipText, textColor = Color.WHITE))
              }),
          LinearLayout.LayoutParams.WRAP_CONTENT,
          LinearLayout.LayoutParams.WRAP_CONTENT,
          true)

  private companion object {
    @ColorInt private val LITHO_PINK = -0xc9481
  }
}


/*
 * 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.samples.litho.kotlin.widgets

import android.graphics.Color
import android.text.InputFilter
import android.text.InputType
import android.widget.ImageView
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.FlexboxContainerScope
import com.facebook.litho.KComponent
import com.facebook.litho.ResourcesScope
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.margin
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.litho.drawableRes
import com.facebook.litho.kotlin.widget.Card
import com.facebook.litho.kotlin.widget.HorizontalScroll
import com.facebook.litho.kotlin.widget.Image
import com.facebook.litho.kotlin.widget.Progress
import com.facebook.litho.kotlin.widget.SolidColor
import com.facebook.litho.kotlin.widget.Spinner
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.kotlin.widget.TextInput
import com.facebook.litho.kotlin.widget.VerticalScroll
import com.facebook.litho.sp
import com.facebook.litho.view.background
import com.facebook.litho.widget.TextAlignment
import com.facebook.samples.litho.R
import com.facebook.samples.litho.kotlin.drawable.RoundedRect

class WidgetsExamplesComponent : KComponent() {

  override fun ComponentScope.render(): Component {

    val myOptions = listOf("option 1", "option 2", "option 3")

    return Column {
      child(Text("Text Example"))
      child(
          // start_text_widget_example
          Text(
              text = "This is my example text",
              textSize = 12.sp,
              textColor = Color.RED,
              alignment = TextAlignment.CENTER)
          // end_text_widget_example
          )
      child(Text("TextInput Example"))
      child(
          // start_textinput_widget_example
          TextInput(
              initialText = "Initial text",
              multiline = true,
              inputType = InputType.TYPE_CLASS_TEXT,
              inputFilter = InputFilter.LengthFilter(/* maxLength = */ 10))
          // end_textinput_widget_example
          )
      child(Text("Image Example"))
      child(
          // start_image_example
          Image(
              drawable = drawableRes(R.drawable.ic_launcher),
              scaleType = ImageView.ScaleType.CENTER_CROP)
          // end_image_example
          )
      child(Text("Card Example"))
      child(
          // start_card_example
          Card(cornerRadius = 5.dp, clippingColor = Color.RED, child = { Text("my content") })
          // end_card_example
          )
      child(Text("SolidColor Example"))
      child(
          // start_solidcolor_example
          SolidColor(color = Color.RED)
          // end_solidcolor_example
          )
      child(Text("Progress Example"))
      child(
          // start_progress_example
          Progress(indeterminateDrawable = drawableRes(R.drawable.ic_launcher))
          // end_progress_example
          )
      child(Text("Spinner Example"))
      child(
          // start_spinner_example
          Spinner(options = myOptions, selectedOption = myOptions.get(0), onItemSelected = {})
          // end_spinner_example
          )
      child(Text("HorizontalScroll Example"))
      child(
          // start_horizontalscroll_example
          HorizontalScroll { Row { getComponents(this) } }
          // end_horizontalscroll_example
          )
      child(Text("VerticalScroll Example"))
      child(
          // start_verticalscroll_example
          VerticalScroll(style = Style.height(100.dp)) { Column { getComponents(this) } }
          // end_verticalscroll_example
          )
    }
  }

  private fun ResourcesScope.getComponents(flexboxContainerScope: FlexboxContainerScope) {
    for (i in 1..10) {
      flexboxContainerScope.child(
          Column(
              style =
                  Style.width(50.dp)
                      .height(50.dp)
                      .margin(all = 5.dp)
                      .background(RoundedRect(if (i % 2 == 0) 0xff666699 else 0xffd9d9d9, 8.dp))))
    }
  }
}


/*
 * 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.samples.litho.onboarding

import android.os.Bundle
import com.facebook.litho.LithoView
import com.facebook.samples.litho.NavigatableDemoActivity
import com.facebook.samples.litho.kotlin.documentation.HelloComponent

class FirstComponentActivity : NavigatableDemoActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // start_example
    setContentView(LithoView.create(this, HelloComponent(name = "Linda")))
    // end_example
  }
}


/*
 * 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.samples.litho.onboarding
// start_example
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.LithoView
import com.facebook.litho.dp
import com.facebook.litho.kotlin.widget.Text

class MyActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val lithoView = LithoView.create(this /* context */, MyComponent())
    setContentView(lithoView)
  }
}

class MyComponent() : KComponent() {
  override fun ComponentScope.render(): Component = Text(text = "Hello, World!", textSize = 50.dp)
}
// end_example


/*
 * 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.samples.litho.onboarding

import android.graphics.Typeface
import android.widget.ImageView
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.drawableRes
import com.facebook.litho.flexbox.aspectRatio
import com.facebook.litho.kotlin.widget.Image
import com.facebook.litho.kotlin.widget.Text
import com.facebook.samples.litho.onboarding.model.Post

// start_example
class PostComponent(val post: Post) : KComponent() {
  override fun ComponentScope.render(): Component {
    return Column {
      child(
          Row {
            child(Image(drawable = drawableRes(post.user.avatarRes)))
            child(Text(text = post.user.username, textStyle = Typeface.BOLD))
          })
      child(
          Image(
              drawable = drawableRes(post.imageRes),
              scaleType = ImageView.ScaleType.CENTER_CROP,
              style = Style.aspectRatio(1f)))
    }
  }
}
// end_example


/*
 * 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.samples.litho.onboarding

import android.graphics.Typeface
import android.widget.ImageView
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.margin
import com.facebook.litho.core.padding
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.litho.drawableRes
import com.facebook.litho.flexbox.aspectRatio
import com.facebook.litho.kotlin.widget.Image
import com.facebook.litho.kotlin.widget.Text
import com.facebook.samples.litho.onboarding.model.Post
import com.facebook.yoga.YogaAlign

// start_example
class PostStyledKComponent(val post: Post) : KComponent() {
  override fun ComponentScope.render(): Component {
    return Column {
      child(
          Row(alignItems = YogaAlign.CENTER, style = Style.padding(all = 8.dp)) {
            child(
                Image(
                    drawable = drawableRes(post.user.avatarRes),
                    style = Style.width(36.dp).height(36.dp).margin(start = 4.dp, end = 8.dp)))
            child(Text(text = post.user.username, textStyle = Typeface.BOLD))
          })
      child(
          Image(
              drawable = drawableRes(post.imageRes),
              scaleType = ImageView.ScaleType.CENTER_CROP,
              style = Style.aspectRatio(1f)))
    }
  }
}
// end_example


/*
 * 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.samples.litho.onboarding

import android.graphics.Typeface
import android.widget.ImageView
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.margin
import com.facebook.litho.core.padding
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.litho.drawableRes
import com.facebook.litho.flexbox.aspectRatio
import com.facebook.litho.kotlin.widget.Image
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.useState
import com.facebook.litho.view.onClick
import com.facebook.samples.litho.R
import com.facebook.samples.litho.onboarding.model.Post
import com.facebook.yoga.YogaAlign

class PostWithActionsKComponent(val post: Post) : KComponent() {
  override fun ComponentScope.render(): Component {
    // start_state_hook
    val isLiked = useState { false }
    // end_state_hook

    return Column {
      child(
          Row(alignItems = YogaAlign.CENTER, style = Style.padding(all = 8.dp)) {
            child(
                Image(
                    drawable = drawableRes(post.user.avatarRes),
                    style = Style.width(36.dp).height(36.dp).margin(start = 4.dp, end = 8.dp)))
            child(Text(text = post.user.username, textStyle = Typeface.BOLD))
          })
      child(
          Image(
              drawable = drawableRes(post.imageRes),
              scaleType = ImageView.ScaleType.CENTER_CROP,
              style = Style.aspectRatio(1f)))
      child(
          // start_image_button
          Image(
              drawable =
                  drawableRes(
                      if (isLiked.value) R.drawable.ic_baseline_favorite_24
                      else R.drawable.ic_baseline_favorite_border_24),
              style =
                  Style.width(32.dp).height(32.dp).margin(all = 6.dp).onClick {
                    isLiked.update { isLiked -> !isLiked }
                  })
          // end_image_button
          )
      if (post.text != null) {
        child(
            Row(style = Style.margin(bottom = 4.dp)) {
              child(
                  Text(
                      text = post.user.username,
                      textStyle = Typeface.BOLD,
                      style = Style.margin(horizontal = 4.dp)))
              child(Text(text = post.text, style = Style.margin(horizontal = 4.dp)))
            })
      }
    }
  }
}


/*
 * 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.samples.litho.onboarding

import android.widget.Toast
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.padding
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.litho.drawableRes
import com.facebook.litho.kotlin.widget.Image
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.sp
import com.facebook.litho.view.onClick
import com.facebook.samples.litho.onboarding.model.User
import com.facebook.yoga.YogaAlign

// start_example
class StoryKComponent(private val user: User) : KComponent() {
  override fun ComponentScope.render(): Component {
    return Column(
        alignItems = YogaAlign.CENTER,
        style =
            Style.padding(all = 4.dp).onClick {
              Toast.makeText(
                      context.androidContext, "Open ${user.username} Story", Toast.LENGTH_SHORT)
                  .show()
            }) {
          child(
              Image(
                  drawable = drawableRes(user.avatarRes), style = Style.width(72.dp).height(72.dp)))
          child(Text(text = user.username, textSize = 10.sp))
        }
  }
}
// end_example


/*
 * 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.samples.litho.onboarding

import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.widget.collection.LazyList
import com.facebook.samples.litho.onboarding.model.Post

// start_example
class UserFeedKComponent(private val posts: List<Post>) : KComponent() {
  override fun ComponentScope.render(): Component {

    return LazyList {
      posts.forEach { post ->
        child(id = post.id, component = PostWithActionsKComponent(post = post))
      }
    }
  }
}
// end_example


/*
 * 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.samples.litho.onboarding

import androidx.recyclerview.widget.RecyclerView.HORIZONTAL
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.padding
import com.facebook.litho.dp
import com.facebook.litho.px
import com.facebook.litho.view.backgroundColor
import com.facebook.litho.widget.collection.CrossAxisWrapMode
import com.facebook.litho.widget.collection.LazyList
import com.facebook.samples.litho.onboarding.model.Post
import com.facebook.samples.litho.onboarding.model.User

// start_example
class UserFeedWithStoriesKComponent(
    private val posts: List<Post>,
    private val usersWithStories: List<User>
) : KComponent() {
  override fun ComponentScope.render(): Component {
    return LazyList {
      child(
          LazyList(
              orientation = HORIZONTAL,
              crossAxisWrapMode = CrossAxisWrapMode.MatchFirstChild,
              startPadding = 4.dp,
              topPadding = 4.dp,
              style = Style.padding(vertical = 6.dp)) {
                usersWithStories.forEach { user -> child(StoryKComponent(user = user)) }
              })

      child(Row(style = Style.height(1.px).backgroundColor(0x22888888)))

      posts.forEach { post ->
        child(id = post.id, component = PostWithActionsKComponent(post = post))
      }
    }
  }
}
// end_example


/*
 * 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.samples.litho.onboarding.model

import androidx.annotation.DrawableRes
import com.facebook.samples.litho.R

// start_example
class User(val username: String, @DrawableRes val avatarRes: Int)

class Post(
    val id: String,
    val user: User,
    @DrawableRes val imageRes: Int,
    val text: String? = null
)
// end_example

val BEN = User(username = "Ben", avatarRes = R.drawable.ic_launcher)
val LUCAS = User(username = "Lucas", avatarRes = R.drawable.ic_launcher)
val LILY = User(username = "Lily", avatarRes = R.drawable.ic_launcher)
val LYRA = User(username = "Lyra", avatarRes = R.drawable.ic_launcher)
val NEBULA = User(username = "Nebula", avatarRes = R.drawable.ic_launcher)
val TOTO_LOKI = User(username = "Toto & Loki", avatarRes = R.drawable.ic_launcher)

val NEBULAS_POST =
    Post(id = "post1", user = NEBULA, imageRes = R.drawable.nebula4, text = "Meow you doin?")

val FEED =
    listOf(
            Post(
                id = "post1",
                user = BEN,
                imageRes = R.drawable.ben,
                text = "Did someone say tuna?"),
            Post(
                id = "post2", user = LILY, imageRes = R.drawable.lily, text = "Whisker-y Business"),
            Post(
                id = "post3",
                user = LILY,
                imageRes = R.drawable.lily2,
                text = "I cat even right now"),
            Post(
                id = "post4",
                user = LILY,
                imageRes = R.drawable.lily3,
                text = "It ainâ€™t easy being puff-fect"),
            Post(
                id = "post5", user = LYRA, imageRes = R.drawable.lyra, text = "Purr-fect morning!"),
            Post(
                id = "post6",
                user = LYRA,
                imageRes = R.drawable.lyra2,
                text = "Napping is the thing I love meowst"),
            Post(
                id = "post7",
                user = LYRA,
                imageRes = R.drawable.lyra3,
                text = "Looking good, feline good"),
            Post(
                id = "post8",
                user = LYRA,
                imageRes = R.drawable.lyra4,
                text = "Itâ€™s the purrfect time for a nap"),
            Post(
                id = "post9",
                user = LYRA,
                imageRes = R.drawable.lyra5,
                text = "How claw-some is that?"),
            Post(
                id = "post10",
                user = LUCAS,
                imageRes = R.drawable.lucascat,
                text = "Stay pawsitive!"),
            NEBULAS_POST,
            Post(
                id = "post11",
                user = NEBULA,
                imageRes = R.drawable.nebula,
                text = "Iâ€™ll have a meow-tini"),
            Post(
                id = "post12",
                user = NEBULA,
                imageRes = R.drawable.nebula2,
                text = "Meow-nificent"),
            Post(
                id = "post13",
                user = NEBULA,
                imageRes = R.drawable.nebula3,
                text = "Every day is a cat day for me"),
            Post(
                id = "post15",
                user = NEBULA,
                imageRes = R.drawable.nebula5,
                text = "Weâ€™re purrfect for each other"),
            Post(
                id = "post16",
                user = NEBULA,
                imageRes = R.drawable.nebula6,
                text = "So fur so good!"),
            Post(
                id = "post17",
                user = NEBULA,
                imageRes = R.drawable.nebula7,
                text = "Catitude is everything "),
            Post(
                id = "post18",
                user = TOTO_LOKI,
                imageRes = R.drawable.toto_loki,
                text = "Do you believe in furry tails"),
            Post(
                id = "post19",
                user = TOTO_LOKI,
                imageRes = R.drawable.toto_loki2,
                text = "Best fur-ends"),
            Post(
                id = "post20",
                user = TOTO_LOKI,
                imageRes = R.drawable.toto_loki3,
                text = "Tabby or not tabby? That is the question"),
            Post(
                id = "post21",
                user = TOTO_LOKI,
                imageRes = R.drawable.toto_loki4,
                text = "Claw-less"),
            Post(
                id = "post22",
                user = TOTO_LOKI,
                imageRes = R.drawable.toto_loki5,
                text = "You had me at meow"),
            Post(
                id = "post23",
                user = TOTO_LOKI,
                imageRes = R.drawable.toto_loki6,
                text = "Donâ€™t worry. Be tabby"),
        )
        .shuffled()

val USER_STORIES = List(10) { User("story$it", R.drawable.ic_launcher) }


/*
 * 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.samples.litho.onboarding.viewcompat

import android.content.Context
import com.facebook.litho.ComponentContext
import com.facebook.litho.annotations.MountSpec
import com.facebook.litho.annotations.OnCreateMountContent
import com.facebook.litho.annotations.OnMount
import com.facebook.litho.annotations.Prop

// start_example
@MountSpec
object CircularImageSpec {

  @OnCreateMountContent
  fun onCreateMountContent(androidContext: Context): CustomImageView {
    return CustomImageView(androidContext)
  }

  @OnMount
  fun onMount(context: ComponentContext, image: CustomImageView, @Prop url: String) {
    image.setUrl(url)
  }
}
// end_example


/*
 * 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.samples.litho.onboarding.viewcompat

import android.content.Context
import androidx.appcompat.widget.AppCompatImageView

class CustomImageView(context: Context) : AppCompatImageView(context) {
  fun setUrl(url: String) = Unit
}


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

package com.facebook.samples.litho.onboarding.viewcompat

import com.facebook.litho.ResourcesScope
import com.facebook.litho.Style
import com.facebook.litho.kotlinStyle

// start_kwrapper
inline fun ResourcesScope.CircularImage(
    url: String,
    style: Style? = null,
) = CircularImage.create(context).url(url).kotlinStyle(style).build()
// end_kwrapper


/*
 * 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.samples.litho.onboarding.viewcompat

import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.yoga.YogaEdge

class RootComponentUsingBuilder : KComponent() {

  // start_use_mount_spec
  override fun ComponentScope.render(): Component {
    return CircularImage.create(context)
        .widthDip(100f)
        .heightDip(100f)
        .marginDip(YogaEdge.TOP, 20f)
        .url("image/url")
        .build()
  }
  // end_use_mount_spec
}


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

package com.facebook.samples.litho.onboarding.viewcompat

import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.margin
import com.facebook.litho.core.width
import com.facebook.litho.dp

class RootComponentUsingWrapper : KComponent() {

  // start_use_mount_spec
  override fun ComponentScope.render(): Component {
    return CircularImage(
        url = "image/url", style = Style.width(100.dp).height(100.dp).margin(top = 10.dp))
  }
  // end_use_mount_spec
}


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

package com.facebook.samples.litho.previews

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.kotlin.widget.Text
import com.facebook.samples.litho.kotlin.animations.animatedbadge.AnimatedBadgeKotlin

/* Litho docs: Litho Android Studio UI Preview  */
class SamplesComponentPreviews {

  fun preview_AnimatedBadgeKotlin(c: ComponentContext): Component = AnimatedBadgeKotlin()

  fun preview_TryitComponent(c: ComponentContext): Component = TryitComponent()
}

/* Works with dependencies present in the app */
class TryitComponent : KComponent() {
  override fun ComponentScope.render(): Component {
    return Column { child(Text(text = "Hello")) }
  }
}


/*
 * 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.samples.litho.kotlin.mountables.widgets

import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.px
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.testrunner.LithoTestRunner
import junit.framework.Assert.assertNotNull
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Tests for [CardClip] */
@RunWith(LithoTestRunner::class)
class CardClipTest {

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  @Test
  fun `CardClip should render`() {
    val testLithoView =
        lithoViewRule.render { CardClip(style = Style.width(100.px).height(100.px)) }

    // should find an CardClip in the tree
    assertNotNull(testLithoView.findComponent(CardClip::class))

    // should mount an CardClip
    assertThat(testLithoView.lithoView.mountItemCount).isEqualTo(1)
  }
}


/*
 * 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.samples.litho.kotlin.mountables.widgets

import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.px
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.testrunner.LithoTestRunner
import junit.framework.Assert.assertNotNull
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Tests for [CardShadow] */
@RunWith(LithoTestRunner::class)
class CardShadowTest {

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  @Test
  fun `CardShadow should render`() {
    val testLithoView =
        lithoViewRule.render { CardShadow(style = Style.width(100.px).height(100.px)) }

    // should find an CardShadow in the tree
    assertNotNull(testLithoView.findComponent(CardShadow::class))

    // should mount an CardShadow
    assertThat(testLithoView.lithoView.mountItemCount).isEqualTo(1)
  }
}


/*
 * 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.samples.litho.kotlin.mountables.widgets

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import com.facebook.litho.LithoView
import com.facebook.litho.MatrixDrawable
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.px
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.assertj.LithoAssertions.assertThat
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.HorizontalScrollLithoView
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Tests for [HorizontalScroll] */
@RunWith(LithoTestRunner::class)
class HorizontalScrollTest {
  @Rule @JvmField val lithoViewRule: LithoViewRule = LithoViewRule()

  @Test
  fun `HorizontalScroll Component should render`() {
    val component =
        HorizontalScroll(style = Style.width(100.px).height(500.px)) {
          Image(
              drawable = ColorDrawable(Color.RED),
              style = Style.width(100.px).height(500.px),
          )
        }

    val testLithoView = lithoViewRule.render { component }

    assertThat(testLithoView).willRenderContent()
    assertThat(testLithoView).containsExactlyOne(HorizontalScroll::class.java)
    assertThat(testLithoView.lithoView.mountItemCount).isEqualTo(1)
    val scrollView = testLithoView.lithoView.getMountItemAt(0).content as HorizontalScrollLithoView
    assertThat(scrollView.measuredWidth).isEqualTo(100)
    assertThat(scrollView.measuredHeight).isEqualTo(500)
    assertThat(scrollView.childCount).isEqualTo(1)
    val innerLithoView = scrollView.getChildAt(0) as LithoView
    assertThat(innerLithoView.measuredWidth).isEqualTo(100)
    assertThat(innerLithoView.measuredHeight).isEqualTo(500)
    val innerContent = innerLithoView.getMountItemAt(0).content as MatrixDrawable<*>
    assertThat(innerContent.bounds.width()).isEqualTo(100)
    assertThat(innerContent.bounds.height()).isEqualTo(500)
  }
}


/*
 * 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.samples.litho.kotlin.mountables.widgets

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.View
import com.facebook.litho.AccessibilityRole
import com.facebook.litho.EventHandler
import com.facebook.litho.MatrixDrawable
import com.facebook.litho.OnInitializeAccessibilityNodeInfoEvent
import com.facebook.litho.Style
import com.facebook.litho.accessibility.ImportantForAccessibility
import com.facebook.litho.accessibility.accessibilityRole
import com.facebook.litho.accessibility.accessibilityRoleDescription
import com.facebook.litho.accessibility.contentDescription
import com.facebook.litho.accessibility.importantForAccessibility
import com.facebook.litho.accessibility.onInitializeAccessibilityNodeInfo
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.px
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.testrunner.LithoTestRunner
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

/** Tests for [ExperimentalImage] */
@RunWith(LithoTestRunner::class)
class ImageTest {

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  @Test
  fun `Image should render`() {
    lithoViewRule
        .render {
          Image(
              drawable = ColorDrawable(Color.RED),
              style = Style.width(100.px).height(100.px),
          )
        }
        .apply {
          // should find an Image in the tree
          findComponent(Image::class)

          // should mount an Image
          assertThat(lithoView.mountItemCount).isEqualTo(1)

          // content of Image should be a MatrixDrawable
          val content = lithoView.getMountItemAt(0).content as MatrixDrawable<*>
          assertThat(content.bounds.width()).isEqualTo(100)
          assertThat(content.bounds.height()).isEqualTo(100)

          // Matrix drawable should host a ColorDrawable
          val drawable = content.mountedDrawable
          assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
        }
  }

  @Test
  fun `when a11y props are set on style it should set them on the rendered content`() {

    val eventHandler: EventHandler<OnInitializeAccessibilityNodeInfoEvent> = mock()

    val node =
        lithoViewRule
            .render {
              Image(
                  drawable = ColorDrawable(Color.RED),
                  style =
                      Style.width(100.px)
                          .height(100.px)
                          .accessibilityRole(AccessibilityRole.IMAGE)
                          .accessibilityRoleDescription("Accessibility Test")
                          .contentDescription("Accessibility Test")
                          .importantForAccessibility(ImportantForAccessibility.YES)
                          .onInitializeAccessibilityNodeInfo { eventHandler },
              )
            }
            .apply {
              // should find an Image in the tree
              findComponent(Image::class)

              // verify a11y properties are correctly set on the View
              assertThat(lithoView.contentDescription).isEqualTo("Accessibility Test")
              assertThat(lithoView.importantForAccessibility)
                  .isEqualTo(View.IMPORTANT_FOR_ACCESSIBILITY_YES)
            }
            .currentRootNode
            ?.node

    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.accessibilityRole).isEqualTo(AccessibilityRole.IMAGE)
    assertThat(nodeInfo?.accessibilityRoleDescription).isEqualTo("Accessibility Test")
    assertThat(nodeInfo?.onInitializeAccessibilityNodeInfoHandler).isNotNull
  }
}


/*
 * 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.samples.litho.kotlin.mountables.widgets

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.px
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.assertj.LithoAssertions.assertThat
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.ProgressView
import junit.framework.Assert.assertNotNull
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Tests for [ProgressComponent] */
@RunWith(LithoTestRunner::class)
class ProgressTest {

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  @Test
  fun `ProgressComponent should render`() {
    val testLithoView =
        lithoViewRule.render { Progress(style = Style.width(100.px).height(100.px)) }

    // should find an Progress in the tree
    assertNotNull(testLithoView.findComponent(Progress::class))

    // should mount an Progress
    assertThat(testLithoView.lithoView.mountItemCount).isEqualTo(1)

    val content = testLithoView.lithoView.getMountItemAt(0).content as ProgressView
    assertThat(content.measuredWidth).isEqualTo(100)
    assertThat(content.measuredHeight).isEqualTo(100)
  }

  @Test
  fun `same instance should be equivalent`() {
    val component = Progress()
    val component2 = Progress()

    assertThat(component).isEquivalentTo(component2)
    assertThat(component).isEquivalentTo(component2, true)
  }

  @Test
  fun `components with same prop values should be equivalent`() {
    val colorDrawable = ColorDrawable(Color.RED)
    val color = Color.BLACK
    val firstProgressWithColorDrawable = Progress(indeterminateDrawable = colorDrawable)
    val secondProgressWithColorDrawable = Progress(indeterminateDrawable = colorDrawable)
    val firstProgressWithColor = Progress(color = color)
    val secondProgressWithColor = Progress(color = color)
    val firstProgressWithBothParams = Progress(indeterminateDrawable = colorDrawable, color = color)
    val secondProgressWithBothParams =
        Progress(indeterminateDrawable = colorDrawable, color = color)

    assertThat(firstProgressWithColorDrawable).isEquivalentTo(secondProgressWithColorDrawable)
    assertThat(firstProgressWithColorDrawable).isEquivalentTo(secondProgressWithColorDrawable, true)
    assertThat(firstProgressWithColor).isEquivalentTo(secondProgressWithColor)
    assertThat(firstProgressWithColor).isEquivalentTo(secondProgressWithColor, true)
    assertThat(firstProgressWithBothParams).isEquivalentTo(secondProgressWithBothParams)
    assertThat(firstProgressWithBothParams).isEquivalentTo(secondProgressWithBothParams, true)
  }
}


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

package com.facebook.samples.litho.kotlin.mountables.widgets

import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.px
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.assertj.LithoAssertions.assertThat
import com.facebook.litho.testing.testrunner.LithoTestRunner
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Tests for [TransparencyEnabledCardClip] */
@RunWith(LithoTestRunner::class)
class TransparencyEnabledCardClipTest {

  @Rule @JvmField val lithoViewRule: LithoViewRule = LithoViewRule()

  @Test
  fun `TransparencyEnabledCardClip should render`() {
    val component = TransparencyEnabledCardClip(style = Style.width(100.px).height(100.px))

    val testLithoView = lithoViewRule.render { component }

    assertThat(testLithoView).willRenderContent()
    assertThat(testLithoView).containsExactlyOne(TransparencyEnabledCardClip::class)
    assertThat(testLithoView.lithoView.mountItemCount).isEqualTo(1)
  }
}


/*
 * 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.samples.litho.kotlin.mountables.widgets

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import com.facebook.litho.LithoView
import com.facebook.litho.MatrixDrawable
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.px
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.assertj.LithoAssertions.assertThat
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.LithoScrollView
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Tests for [VerticalScroll] */
@RunWith(LithoTestRunner::class)
class VerticalScrollTest {

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  @Test
  fun `VerticalScroll Component should render`() {
    lithoViewRule
        .render {
          VerticalScroll(
              style = Style.width(100.px).height(100.px),
          ) {
            Image(
                drawable = ColorDrawable(Color.RED),
                style = Style.width(100.px).height(500.px),
            )
          }
        }
        .apply {

          // should find an VerticalScroll Component in the tree
          findComponent(VerticalScroll::class)

          // should mount an VerticalScroll Component
          assertThat(lithoView.mountItemCount).isEqualTo(1)

          // content of VerticalScroll Component should be a MatrixDrawable
          val content = lithoView.getMountItemAt(0).content as LithoScrollView
          assertThat(content.measuredWidth).isEqualTo(100)
          assertThat(content.measuredHeight).isEqualTo(100)
          assertThat(content.childCount).isEqualTo(1)

          val innerLithoView = content.getChildAt(0) as LithoView

          // The inner LithoView's height should be 500
          assertThat(innerLithoView.measuredWidth).isEqualTo(100)
          assertThat(innerLithoView.measuredHeight).isEqualTo(500)

          // The content of Image Component should be a MatrixDrawable
          val innerContent = innerLithoView.getMountItemAt(0).content as MatrixDrawable<*>
          assertThat(innerContent.bounds.width()).isEqualTo(100)
          assertThat(innerContent.bounds.height()).isEqualTo(500)
        }
  }

  @Test
  fun `same instance should be equivalent`() {
    val component =
        VerticalScroll(style = Style.width(100.px).height(100.px)) {
          Image(
              drawable = ColorDrawable(Color.RED),
              style = Style.width(100.px).height(500.px),
          )
        }

    assertThat(component).isEquivalentTo(component)
    assertThat(component).isEquivalentTo(component, true)
  }

  @Test
  fun `components with same prop values should be equivalent`() {
    val color = ColorDrawable(Color.RED)
    val a =
        VerticalScroll(style = Style.width(100.px).height(100.px)) {
          Image(
              drawable = color,
              style = Style.width(100.px).height(500.px),
          )
        }

    val b =
        VerticalScroll(style = Style.width(100.px).height(100.px)) {
          Image(
              drawable = color,
              style = Style.width(100.px).height(500.px),
          )
        }

    assertThat(a).isEquivalentTo(b)
    assertThat(a).isEquivalentTo(b, true)
  }

  @Test
  fun `components with different prop values should not be equivalent`() {
    val a =
        VerticalScroll(style = Style.width(100.px).height(100.px)) {
          Image(
              drawable = ColorDrawable(Color.RED), // red here
              style = Style.width(100.px).height(500.px),
          )
        }

    val b =
        VerticalScroll(style = Style.width(100.px).height(100.px)) {
          Image(
              drawable = ColorDrawable(Color.BLUE), // blue here
              style = Style.width(100.px).height(500.px),
          )
        }

    assertThatThrownBy { assertThat(a).isEquivalentTo(b) }.isInstanceOf(AssertionError::class.java)
    assertThatThrownBy { assertThat(a).isEquivalentTo(b, true) }
        .isInstanceOf(AssertionError::class.java)
  }

  @Test
  fun `components with different style values should not be equivalent`() {
    val color = ColorDrawable(Color.RED)
    val a =
        VerticalScroll(style = Style.width(100.px).height(100.px) /* 100 here */) {
          Image(
              drawable = color,
              style = Style.width(100.px).height(500.px),
          )
        }

    val b =
        VerticalScroll(style = Style.width(200.px).height(200.px) /* 200 here */) {
          Image(
              drawable = color,
              style = Style.width(100.px).height(500.px),
          )
        }

    assertThatThrownBy { assertThat(a).isEquivalentTo(b, true) }
        .isInstanceOf(AssertionError::class.java)
  }
}


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

package com.facebook.samples.lithoktbarebones

import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext
import com.facebook.litho.annotations.LayoutSpec
import com.facebook.litho.annotations.OnCreateLayout
import com.facebook.litho.annotations.Prop
import com.facebook.litho.widget.Text
import com.facebook.yoga.YogaEdge.ALL

@LayoutSpec
object ListItemSpec {

  @OnCreateLayout
  fun onCreateLayout(
      c: ComponentContext,
      @Prop color: Int,
      @Prop title: String,
      @Prop subtitle: String
  ): Component =
      Column.create(c)
          .paddingDip(ALL, 16f)
          .backgroundColor(color)
          .child(Text.create(c).text(title).textSizeSp(40f))
          .child(Text.create(c).text(subtitle).textSizeSp(20f))
          .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.samples.lithoktbarebones

import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import androidx.recyclerview.widget.OrientationHelper
import com.facebook.litho.ComponentContext
import com.facebook.litho.LithoView
import com.facebook.litho.widget.ComponentRenderInfo
import com.facebook.litho.widget.LinearLayoutInfo
import com.facebook.litho.widget.Recycler
import com.facebook.litho.widget.RecyclerBinder

class SampleActivity : Activity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val context = ComponentContext(this)

    val recyclerBinder =
        RecyclerBinder.Builder()
            .layoutInfo(LinearLayoutInfo(this, OrientationHelper.VERTICAL, false))
            .build(context)

    val component = Recycler.create(context).binder(recyclerBinder).build()

    addContent(recyclerBinder, context)

    setContentView(LithoView.create(context, component))
  }

  private fun addContent(recyclerBinder: RecyclerBinder, context: ComponentContext) {
    for (i in 0 until 31) {
      recyclerBinder.insertItemAt(
          i,
          ComponentRenderInfo.create()
              .component(
                  ListItem.create(context)
                      .color(if (i % 2 == 0) Color.WHITE else Color.LTGRAY)
                      .title("Hello, world!")
                      .subtitle("Litho tutorial")
                      .build())
              .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.samples.lithoktbarebones

import android.app.Application
import com.facebook.soloader.SoLoader

class SampleApplication : Application() {
  override fun onCreate() {
    super.onCreate()

    SoLoader.init(this, false)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.marianatrench.integrationtests

/**
 * Mirrors JavaCheckCast.java to show the differences between Java and Kotlin byte code where the
 * check-cast instruction is concerned. When casting from derived to base, Kotlin's generated byte
 * code contains the check-cast instruction.
 */
public class KotlinCheckCast {

  public open class Base {
    public open fun run(argument: Object) = Unit
  }

  public class DerivedWithSink : Base() {
    public override fun run(argument: Object) {
      Origin.sink(argument)
    }
  }

  public class DerivedNoSink : Base() {
    public override fun run(argument: Object) = Unit
  }

  // User-defined shim to call klass.run(argument)
  public fun shimmed(klass: Base, argument: Object) = Unit

  public fun issueFromDerivedType() {
    // There is a check-cast instruction (DerivedWithSink to Base) in the byte
    // code. The corresponding register type is Base. Analysis will consider
    // all derived classes, hence this source is seen to flow into a sink.
    shimmed(DerivedWithSink(), Origin.source() as Object)
  }

  public fun noIssueFromDerivedType() {
    // False positive because of check-cast instruction (see
    // `issueFromCheckCastType`).
    shimmed(DerivedNoSink(), Origin.source() as Object)
  }

  public fun issueFromVirtualBaseType() {
    var base: Base? = null

    // Each block has a check-cast from Derived -> Base
    if ((0..1).random() > 0) {
      base = DerivedWithSink()
    } else {
      base = DerivedNoSink()
    }

    // No check-cast instruction here (type of `base` is already Base).
    shimmed(base, Origin.source() as Object)
  }

  public fun issueFromCastedType(base: Base) {
    val derived = base as DerivedWithSink
    shimmed(derived, Origin.source() as Object)
  }

  public fun noIssueFromCastedType(base: Base) {
    // Ideally, the type should be handled as DerivedNoSink. There is a
    // check-cast instruction for it. However, another check-cast (to Base)
    // instruction is introduced when calling shimmed(derived, ...).
    val derived = base as DerivedNoSink
    shimmed(derived, Origin.source() as Object)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react

import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.interfaces.ReactHostInterface

@OptIn(UnstableReactNativeAPI::class)
/** Interface that represents an instance of a React Native application */
interface ReactApplication {
  /** Get the default [ReactNativeHost] for this app. */
  val reactNativeHost: ReactNativeHost

  /**
   * Get the default [ReactHostInterface] for this app. This method will be used by the new
   * architecture of react native
   */
  val reactHostInterface: ReactHostInterface?
    get() = null
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.bridgeless

import com.facebook.jni.HybridData
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.proguard.annotations.DoNotStripAny
import com.facebook.soloader.SoLoader

@DoNotStripAny
abstract class BindingsInstaller(@field:DoNotStrip private val mHybridData: HybridData?) {
  companion object {
    init {
      SoLoader.loadLibrary("rninstance")
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.bridgeless

import com.facebook.infer.annotation.ThreadSafe
import com.facebook.react.ReactPackage
import com.facebook.react.ReactPackageTurboModuleManagerDelegate
import com.facebook.react.bridge.JSBundleLoader
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.fabric.ReactNativeConfig
import com.facebook.react.turbomodule.core.TurboModuleManager

/**
 * [ReactHostDelegate] is an interface that defines parameters required to initialize React Native.
 * This interface works in combination with [ReactHost]
 */
@ThreadSafe
@UnstableReactNativeAPI
interface ReactHostDelegate {
  /**
   * Path to your app's main module on Metro. This is used when reloading JS during development. All
   * paths are relative to the root folder the packager is serving files from. Examples:
   * `index.android` or `subdirectory/index.android`
   */
  val jSMainModulePath: String

  /**
   * Object that holds a native C++ references that allow host applications to install C++ objects
   * into jsi::Runtime during the initialization of React Native
   */
  val bindingsInstaller: BindingsInstaller?

  /** list of [ReactPackage] to expose Native Modules and View Components to JS */
  val reactPackages: List<ReactPackage>

  /** Object that holds a native reference to the javascript engine */
  val jSEngineInstance: JSEngineInstance

  /**
   * Bundle loader to use when setting up JS environment. <p>Example:
   * [JSBundleLoader.createFileLoader(application, bundleFile)]
   */
  val jSBundleLoader: JSBundleLoader

  /** TODO: combine getTurboModuleManagerDelegate inside [ReactPackage] */
  val turboModuleManagerDelegateBuilder: ReactPackageTurboModuleManagerDelegate.Builder

  /**
   * Callback that can be used by React Native host applications to react to exceptions thrown by
   * the internals of React Native.
   */
  fun handleInstanceException(error: Exception)

  /**
   * ReactNative Configuration that allows to customize the behavior of key/value pairs used by the
   * framework to enable/disable experimental capabilities
   */
  fun getReactNativeConfig(turboModuleManager: TurboModuleManager): ReactNativeConfig

  @UnstableReactNativeAPI
  class ReactHostDelegateBase(
      override val jSMainModulePath: String,
      override val jSBundleLoader: JSBundleLoader,
      override val jSEngineInstance: JSEngineInstance,
      override val turboModuleManagerDelegateBuilder:
          ReactPackageTurboModuleManagerDelegate.Builder,
      override val reactPackages: List<ReactPackage> = emptyList(),
      override val bindingsInstaller: BindingsInstaller? = null,
      private val reactNativeConfig: ReactNativeConfig = ReactNativeConfig.DEFAULT_CONFIG,
      private val exceptionHandler: (error: Exception) -> Unit = {}
  ) : ReactHostDelegate {

    override fun getReactNativeConfig(turboModuleManager: TurboModuleManager) = reactNativeConfig

    override fun handleInstanceException(error: Exception) = exceptionHandler(error)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.bridgeless.hermes

import com.facebook.jni.HybridData
import com.facebook.jni.annotations.DoNotStrip
import com.facebook.react.bridgeless.JSEngineInstance
import com.facebook.soloader.SoLoader

class HermesInstance : JSEngineInstance(initHybrid()!!) {

  companion object {
    @JvmStatic @DoNotStrip protected external fun initHybrid(): HybridData?

    init {
      SoLoader.loadLibrary("hermesinstancejni")
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.common.annotations

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@RequiresOptIn(
    level = RequiresOptIn.Level.ERROR,
    message = "This API is experimental and is likely to change or to be removed in the future")
annotation class UnstableReactNativeAPI


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.common.annotations

/**
 * Annotates a method that should have restricted visibility but it's required to be public for use
 * in test code only.
 */
annotation class VisibleForTesting


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.common.mapbuffer

/**
 * MapBuffer is an optimized sparse array format for transferring props-like data between C++ and
 * JNI. It is designed to:
 * - be compact to optimize space when sparse (sparse is the common case).
 * - be accessible through JNI with zero/minimal copying.
 * - work recursively for nested maps/arrays.
 * - support dynamic types that map to JSON.
 * - have minimal APK size and build time impact.
 *
 * See <react/renderer/mapbuffer/MapBuffer.h> for more information and native implementation.
 *
 * Limitations:
 * - Keys are usually sized as 2 bytes, with each buffer supporting up to 65536 entries as a result.
 * - O(log(N)) random key access for native buffers due to selected structure. Faster access can be
 *   achieved by retrieving [MapBuffer.Entry] with [entryAt] on known offsets.
 */
interface MapBuffer : Iterable<MapBuffer.Entry> {
  companion object {
    /**
     * Key are represented as 2 byte values, and typed as Int for ease of access. The serialization
     * format only allows to store [UShort] values.
     */
    internal val KEY_RANGE = IntRange(UShort.MIN_VALUE.toInt(), UShort.MAX_VALUE.toInt())
  }

  /**
   * Data types supported by [MapBuffer]. Keep in sync with definition in
   * `<react/renderer/mapbuffer/MapBuffer.h>`, as enum serialization relies on correct order.
   */
  enum class DataType {
    BOOL,
    INT,
    DOUBLE,
    STRING,
    MAP
  }

  /**
   * Number of elements inserted into current MapBuffer.
   *
   * @return number of elements in the [MapBuffer]
   */
  val count: Int

  /**
   * Checks whether entry for given key exists in MapBuffer.
   *
   * @param key key to lookup the entry
   * @return whether entry for the given key exists in the MapBuffer.
   */
  fun contains(key: Int): Boolean

  /**
   * Provides offset of the key to use for [entryAt], for cases when offset is not statically known
   * but can be cached.
   *
   * @param key key to lookup offset for
   * @return offset for the given key to be used for entry access, -1 if key wasn't found.
   */
  fun getKeyOffset(key: Int): Int

  /**
   * Provides parsed access to a MapBuffer without additional lookups for provided offset.
   *
   * @param offset offset of entry in the MapBuffer structure. Can be looked up for known keys with
   *   [getKeyOffset].
   * @return parsed entry for structured access for given offset
   */
  fun entryAt(offset: Int): MapBuffer.Entry

  /**
   * Provides parsed [DataType] annotation associated with the given key.
   *
   * @param key key to lookup type for
   * @return data type of the given key.
   * @throws IllegalArgumentException if the key doesn't exist
   */
  fun getType(key: Int): DataType

  /**
   * Provides parsed [Boolean] value if the entry for given key exists with [DataType.BOOL] type
   *
   * @param key key to lookup [Boolean] value for
   * @return value associated with the requested key
   * @throws IllegalArgumentException if the key doesn't exist
   * @throws IllegalStateException if the data type doesn't match
   */
  fun getBoolean(key: Int): Boolean

  /**
   * Provides parsed [Int] value if the entry for given key exists with [DataType.INT] type
   *
   * @param key key to lookup [Int] value for
   * @return value associated with the requested key
   * @throws IllegalArgumentException if the key doesn't exist
   * @throws IllegalStateException if the data type doesn't match
   */
  fun getInt(key: Int): Int

  /**
   * Provides parsed [Double] value if the entry for given key exists with [DataType.DOUBLE] type
   *
   * @param key key to lookup [Double] value for
   * @return value associated with the requested key
   * @throws IllegalArgumentException if the key doesn't exist
   * @throws IllegalStateException if the data type doesn't match
   */
  fun getDouble(key: Int): Double

  /**
   * Provides parsed [String] value if the entry for given key exists with [DataType.STRING] type
   *
   * @param key key to lookup [String] value for
   * @return value associated with the requested key
   * @throws IllegalArgumentException if the key doesn't exist
   * @throws IllegalStateException if the data type doesn't match
   */
  fun getString(key: Int): String

  /**
   * Provides parsed [MapBuffer] value if the entry for given key exists with [DataType.MAP] type
   *
   * @param key key to lookup [MapBuffer] value for
   * @return value associated with the requested key
   * @throws IllegalArgumentException if the key doesn't exist
   * @throws IllegalStateException if the data type doesn't match
   */
  fun getMapBuffer(key: Int): MapBuffer

  /**
   * Provides parsed [List<MapBuffer>] value if the entry for given key exists with [DataType.MAP]
   * type
   *
   * @param key key to lookup [List<MapBuffer>] value for
   * @return value associated with the requested key
   * @throws IllegalArgumentException if the key doesn't exist
   * @throws IllegalStateException if the data type doesn't match
   */
  fun getMapBufferList(key: Int): List<MapBuffer>

  /** Iterable entry representing parsed MapBuffer values */
  interface Entry {
    /**
     * Key of the given entry. Usually represented as 2 byte unsigned integer with the value range
     * of [0,65536)
     */
    val key: Int

    /** Parsed [DataType] of the entry */
    val type: DataType

    /**
     * Entry value represented as [Boolean]
     *
     * @throws IllegalStateException if the data type doesn't match [DataType.BOOL]
     */
    val booleanValue: Boolean

    /**
     * Entry value represented as [Int]
     *
     * @throws IllegalStateException if the data type doesn't match [DataType.INT]
     */
    val intValue: Int

    /**
     * Entry value represented as [Double]
     *
     * @throws IllegalStateException if the data type doesn't match [DataType.DOUBLE]
     */
    val doubleValue: Double

    /**
     * Entry value represented as [String]
     *
     * @throws IllegalStateException if the data type doesn't match [DataType.STRING]
     */
    val stringValue: String

    /**
     * Entry value represented as [MapBuffer]
     *
     * @throws IllegalStateException if the data type doesn't match [DataType.MAP]
     */
    val mapBufferValue: MapBuffer
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.common.mapbuffer

import com.facebook.react.bridge.ReactMarker
import com.facebook.react.bridge.ReactMarkerConstants
import com.facebook.soloader.SoLoader
import com.facebook.systrace.Systrace

object MapBufferSoLoader {
  @Volatile private var didInit = false

  @JvmStatic
  fun staticInit() {
    if (didInit) {
      return
    }
    didInit = true

    Systrace.beginSection(
        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
        "ReadableMapBufferSoLoader.staticInit::load:mapbufferjni")
    ReactMarker.logMarker(ReactMarkerConstants.LOAD_REACT_NATIVE_MAPBUFFER_SO_FILE_START)
    SoLoader.loadLibrary("mapbufferjni")
    ReactMarker.logMarker(ReactMarkerConstants.LOAD_REACT_NATIVE_MAPBUFFER_SO_FILE_END)
    Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.common.mapbuffer

import com.facebook.jni.HybridData
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.common.mapbuffer.MapBuffer.Companion.KEY_RANGE
import java.lang.StringBuilder
import java.nio.ByteBuffer
import java.nio.ByteOrder
import javax.annotation.concurrent.NotThreadSafe

/**
 * Read-only implementation of the [MapBuffer], imported from C++ environment. Use
 * `<react/common/mapbuffer/JReadableMapBuffer.h> to create it.
 *
 * See [MapBuffer] documentation for more details
 */
@NotThreadSafe
@DoNotStrip
class ReadableMapBuffer : MapBuffer {

  // Hybrid data must be kept in the `mHybridData` field for fbjni to work
  @field:DoNotStrip private val mHybridData: HybridData?

  // Byte data of the mapBuffer
  private val buffer: ByteBuffer
  // Amount of items serialized on the ByteBuffer
  override var count = 0
    private set

  @DoNotStrip
  private constructor(hybridData: HybridData) {
    this.mHybridData = hybridData
    this.buffer = importByteBuffer()
    readHeader()
  }

  private constructor(buffer: ByteBuffer) {
    this.mHybridData = null
    this.buffer = buffer
    readHeader()
  }

  private external fun importByteBuffer(): ByteBuffer

  private fun readHeader() {
    // byte order
    val storedAlignment = buffer.short
    if (storedAlignment.toInt() != ALIGNMENT) {
      buffer.order(ByteOrder.LITTLE_ENDIAN)
    }
    // count
    count = readUnsignedShort(buffer.position()).toInt()
  }

  // returns the relative offset of the first byte of dynamic data
  private val offsetForDynamicData: Int
    get() = getKeyOffsetForBucketIndex(count)

  /**
   * @param key Key to search for
   * @return the "bucket index" for a key or -1 if not found. It uses a binary search algorithm
   *   (log(n))
   */
  private fun getBucketIndexForKey(intKey: Int): Int {
    if (intKey !in KEY_RANGE) {
      return -1
    }
    val key = intKey.toUShort()

    var lo = 0
    var hi = count - 1
    while (lo <= hi) {
      val mid = lo + hi ushr 1
      val midVal = readUnsignedShort(getKeyOffsetForBucketIndex(mid))
      when {
        midVal < key -> lo = mid + 1
        midVal > key -> hi = mid - 1
        else -> return mid
      }
    }
    return -1
  }

  private fun readDataType(bucketIndex: Int): MapBuffer.DataType {
    val value = readUnsignedShort(getKeyOffsetForBucketIndex(bucketIndex) + TYPE_OFFSET).toInt()
    return MapBuffer.DataType.values()[value]
  }

  private fun getTypedValueOffsetForKey(key: Int, expected: MapBuffer.DataType): Int {
    val bucketIndex = getBucketIndexForKey(key)
    require(bucketIndex != -1) { "Key not found: $key" }
    val dataType = readDataType(bucketIndex)
    check(!(dataType !== expected)) { "Expected $expected for key: $key, found $dataType instead." }
    return getKeyOffsetForBucketIndex(bucketIndex) + VALUE_OFFSET
  }

  private fun readUnsignedShort(bufferPosition: Int): UShort {
    return buffer.getShort(bufferPosition).toUShort()
  }

  private fun readDoubleValue(bufferPosition: Int): Double {
    return buffer.getDouble(bufferPosition)
  }

  private fun readIntValue(bufferPosition: Int): Int {
    return buffer.getInt(bufferPosition)
  }

  private fun readBooleanValue(bufferPosition: Int): Boolean {
    return readIntValue(bufferPosition) == 1
  }

  private fun readStringValue(bufferPosition: Int): String {
    val offset = offsetForDynamicData + buffer.getInt(bufferPosition)
    val sizeOfString = buffer.getInt(offset)
    val result = ByteArray(sizeOfString)
    val stringOffset = offset + Int.SIZE_BYTES
    buffer.position(stringOffset)
    buffer[result, 0, sizeOfString]
    return String(result)
  }

  private fun readMapBufferValue(position: Int): ReadableMapBuffer {
    val offset = offsetForDynamicData + buffer.getInt(position)
    val sizeMapBuffer = buffer.getInt(offset)
    val newBuffer = ByteArray(sizeMapBuffer)
    val bufferOffset = offset + Int.SIZE_BYTES
    buffer.position(bufferOffset)
    buffer[newBuffer, 0, sizeMapBuffer]
    return ReadableMapBuffer(ByteBuffer.wrap(newBuffer))
  }

  private fun readMapBufferListValue(position: Int): List<ReadableMapBuffer> {
    val readMapBufferList = arrayListOf<ReadableMapBuffer>()
    var offset = offsetForDynamicData + buffer.getInt(position)
    val sizeMapBufferList = buffer.getInt(offset)
    offset += Int.SIZE_BYTES
    var curLen = 0
    while (curLen < sizeMapBufferList) {
      val sizeMapBuffer = buffer.getInt(offset + curLen)
      val newMapBuffer = ByteArray(sizeMapBuffer)
      curLen = curLen + Int.SIZE_BYTES
      buffer.position(offset + curLen)
      buffer[newMapBuffer, 0, sizeMapBuffer]
      readMapBufferList.add(ReadableMapBuffer(ByteBuffer.wrap(newMapBuffer)))
      curLen = curLen + sizeMapBuffer
    }
    return readMapBufferList
  }

  private fun getKeyOffsetForBucketIndex(bucketIndex: Int): Int {
    return HEADER_SIZE + BUCKET_SIZE * bucketIndex
  }

  override fun contains(key: Int): Boolean {
    // TODO T83483191: Add tests
    return getBucketIndexForKey(key) != -1
  }

  override fun getKeyOffset(key: Int): Int = getBucketIndexForKey(key)

  override fun entryAt(offset: Int): MapBuffer.Entry =
      MapBufferEntry(getKeyOffsetForBucketIndex(offset))

  override fun getType(key: Int): MapBuffer.DataType {
    val bucketIndex = getBucketIndexForKey(key)
    require(bucketIndex != -1) { "Key not found: $key" }
    return readDataType(bucketIndex)
  }

  override fun getInt(key: Int): Int =
      readIntValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.INT))

  override fun getDouble(key: Int): Double =
      readDoubleValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.DOUBLE))

  override fun getString(key: Int): String =
      readStringValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.STRING))

  override fun getBoolean(key: Int): Boolean =
      readBooleanValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.BOOL))

  override fun getMapBuffer(key: Int): ReadableMapBuffer =
      readMapBufferValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.MAP))

  override fun getMapBufferList(key: Int): List<ReadableMapBuffer> =
      readMapBufferListValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.MAP))

  override fun hashCode(): Int {
    buffer.rewind()
    return buffer.hashCode()
  }

  override fun equals(other: Any?): Boolean {
    if (other !is ReadableMapBuffer) {
      return false
    }
    val thisByteBuffer = buffer
    val otherByteBuffer = other.buffer
    if (thisByteBuffer === otherByteBuffer) {
      return true
    }
    thisByteBuffer.rewind()
    otherByteBuffer.rewind()
    return thisByteBuffer == otherByteBuffer
  }

  override fun toString(): String {
    val builder = StringBuilder("{")
    for (entry in this) {
      val key = entry.key
      builder.append(key)
      builder.append('=')
      when (entry.type) {
        MapBuffer.DataType.BOOL -> builder.append(entry.booleanValue)
        MapBuffer.DataType.INT -> builder.append(entry.intValue)
        MapBuffer.DataType.DOUBLE -> builder.append(entry.doubleValue)
        MapBuffer.DataType.STRING -> builder.append(entry.stringValue)
        MapBuffer.DataType.MAP -> builder.append(entry.mapBufferValue.toString())
      }
      builder.append(',')
    }
    builder.append('}')
    return builder.toString()
  }

  override fun iterator(): Iterator<MapBuffer.Entry> {
    return object : Iterator<MapBuffer.Entry> {
      var current = 0
      val last = count - 1

      override fun hasNext(): Boolean {
        return current <= last
      }

      override fun next(): MapBuffer.Entry {
        return MapBufferEntry(getKeyOffsetForBucketIndex(current++))
      }
    }
  }

  private inner class MapBufferEntry(private val bucketOffset: Int) : MapBuffer.Entry {
    private fun assertType(expected: MapBuffer.DataType) {
      val dataType = type
      check(!(expected !== dataType)) {
        ("Expected " +
            expected +
            " for key: " +
            key +
            " found " +
            dataType.toString() +
            " instead.")
      }
    }

    override val key: Int
      get() = readUnsignedShort(bucketOffset).toInt()

    override val type: MapBuffer.DataType
      get() = MapBuffer.DataType.values()[readUnsignedShort(bucketOffset + TYPE_OFFSET).toInt()]

    override val doubleValue: Double
      get() {
        assertType(MapBuffer.DataType.DOUBLE)
        return readDoubleValue(bucketOffset + VALUE_OFFSET)
      }

    override val intValue: Int
      get() {
        assertType(MapBuffer.DataType.INT)
        return readIntValue(bucketOffset + VALUE_OFFSET)
      }

    override val booleanValue: Boolean
      get() {
        assertType(MapBuffer.DataType.BOOL)
        return readBooleanValue(bucketOffset + VALUE_OFFSET)
      }

    override val stringValue: String
      get() {
        assertType(MapBuffer.DataType.STRING)
        return readStringValue(bucketOffset + VALUE_OFFSET)
      }

    override val mapBufferValue: MapBuffer
      get() {
        assertType(MapBuffer.DataType.MAP)
        return readMapBufferValue(bucketOffset + VALUE_OFFSET)
      }
  }

  companion object {
    // Value used to verify if the data is serialized with LittleEndian order.
    private const val ALIGNMENT = 0xFE

    // 8 bytes = 2 (alignment) + 2 (count) + 4 (size)
    private const val HEADER_SIZE = 8

    // 10 bytes = 2 (key) + 2 (type) + 8 (value)
    private const val BUCKET_SIZE = 12

    // 2 bytes = 2 (key)
    private const val TYPE_OFFSET = 2

    // 4 bytes = 2 (key) + 2 (type)
    private const val VALUE_OFFSET = 4

    init {
      MapBufferSoLoader.staticInit()
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.common.mapbuffer

import android.util.SparseArray
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.common.mapbuffer.MapBuffer.Companion.KEY_RANGE
import com.facebook.react.common.mapbuffer.MapBuffer.DataType
import javax.annotation.concurrent.NotThreadSafe

/**
 * Implementation of writeable Java-only MapBuffer, which can be used to send information through
 * JNI.
 *
 * See [MapBuffer] for more details
 */
@NotThreadSafe
@DoNotStrip
class WritableMapBuffer : MapBuffer {
  private val values: SparseArray<Any> = SparseArray<Any>()

  /*
   * Write methods
   */

  /**
   * Adds a boolean value for given key to the MapBuffer.
   *
   * @param key entry key
   * @param value entry value
   * @throws IllegalArgumentException if key is out of [UShort] range
   */
  fun put(key: Int, value: Boolean): WritableMapBuffer = putInternal(key, value)

  /**
   * Adds an int value for given key to the MapBuffer.
   *
   * @param key entry key
   * @param value entry value
   * @throws IllegalArgumentException if key is out of [UShort] range
   */
  fun put(key: Int, value: Int): WritableMapBuffer = putInternal(key, value)

  /**
   * Adds a double value for given key to the MapBuffer.
   *
   * @param key entry key
   * @param value entry value
   * @throws IllegalArgumentException if key is out of [UShort] range
   */
  fun put(key: Int, value: Double): WritableMapBuffer = putInternal(key, value)

  /**
   * Adds a string value for given key to the MapBuffer.
   *
   * @param key entry key
   * @param value entry value
   * @throws IllegalArgumentException if key is out of [UShort] range
   */
  fun put(key: Int, value: String): WritableMapBuffer = putInternal(key, value)

  /**
   * Adds a [MapBuffer] value for given key to the current MapBuffer.
   *
   * @param key entry key
   * @param value entry value
   * @throws IllegalArgumentException if key is out of [UShort] range
   */
  fun put(key: Int, value: MapBuffer): WritableMapBuffer = putInternal(key, value)

  private fun putInternal(key: Int, value: Any): WritableMapBuffer {
    require(key in KEY_RANGE) {
      "Only integers in [${UShort.MIN_VALUE};${UShort.MAX_VALUE}] range are allowed for keys."
    }

    values.put(key, value)
    return this
  }

  /*
   * Read methods
   */

  override val count: Int
    get() = values.size()

  override fun contains(key: Int): Boolean = values.get(key) != null

  override fun getKeyOffset(key: Int): Int = values.indexOfKey(key)

  override fun entryAt(offset: Int): MapBuffer.Entry = MapBufferEntry(offset)

  override fun getType(key: Int): DataType {
    val value = values.get(key)
    require(value != null) { "Key not found: $key" }
    return value.dataType(key)
  }

  override fun getBoolean(key: Int): Boolean = verifyValue(key, values.get(key))

  override fun getInt(key: Int): Int = verifyValue(key, values.get(key))

  override fun getDouble(key: Int): Double = verifyValue(key, values.get(key))

  override fun getString(key: Int): String = verifyValue(key, values.get(key))

  override fun getMapBuffer(key: Int): MapBuffer = verifyValue(key, values.get(key))

  override fun getMapBufferList(key: Int): List<MapBuffer> = verifyValue(key, values.get(key))

  /** Generalizes verification of the value types based on the requested type. */
  private inline fun <reified T> verifyValue(key: Int, value: Any?): T {
    require(value != null) { "Key not found: $key" }
    check(value is T) {
      "Expected ${T::class.java} for key: $key, found ${value.javaClass} instead."
    }
    return value
  }

  private fun Any.dataType(key: Int): DataType {
    return when (val value = this) {
      is Boolean -> DataType.BOOL
      is Int -> DataType.INT
      is Double -> DataType.DOUBLE
      is String -> DataType.STRING
      is MapBuffer -> DataType.MAP
      else -> throw IllegalStateException("Key $key has value of unknown type: ${value.javaClass}")
    }
  }

  override fun iterator(): Iterator<MapBuffer.Entry> =
      object : Iterator<MapBuffer.Entry> {
        var count = 0

        override fun hasNext(): Boolean = count < values.size()

        override fun next(): MapBuffer.Entry = MapBufferEntry(count++)
      }

  private inner class MapBufferEntry(private val index: Int) : MapBuffer.Entry {
    override val key: Int = values.keyAt(index)
    override val type: DataType = values.valueAt(index).dataType(key)
    override val booleanValue: Boolean
      get() = verifyValue(key, values.valueAt(index))

    override val intValue: Int
      get() = verifyValue(key, values.valueAt(index))

    override val doubleValue: Double
      get() = verifyValue(key, values.valueAt(index))

    override val stringValue: String
      get() = verifyValue(key, values.valueAt(index))

    override val mapBufferValue: MapBuffer
      get() = verifyValue(key, values.valueAt(index))
  }

  /*
   * JNI hooks
   */

  @DoNotStrip
  @Suppress("UNUSED")
  /** JNI hook for MapBuffer to retrieve sorted keys from this class. */
  private fun getKeys(): IntArray = IntArray(values.size()) { values.keyAt(it) }

  @DoNotStrip
  @Suppress("UNUSED")
  /** JNI hook for MapBuffer to retrieve sorted values from this class. */
  private fun getValues(): Array<Any> = Array(values.size()) { values.valueAt(it) }

  companion object {
    init {
      MapBufferSoLoader.staticInit()
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.defaults

import com.facebook.jni.annotations.DoNotStrip
import com.facebook.react.bridgeless.BindingsInstaller
import com.facebook.react.common.annotations.UnstableReactNativeAPI

/**
 * A utility class that provides users a default [BindingsInstaller] class that's used to initialize
 * [ReactHostDelegate]
 */
@DoNotStrip @UnstableReactNativeAPI class DefaultBindingsInstaller : BindingsInstaller(null) {}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.defaults

import com.facebook.jni.HybridData
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.fabric.ComponentFactory

/**
 * A utility class that provides users a ComponentRegistry they can customize with a C++
 * implementation of its native methods.
 *
 * This class works together with the [DefaultNewArchitectureEntryPoint] and it's C++ implementation
 * is hosted inside the React Native framework
 */
@DoNotStrip
class DefaultComponentsRegistry
@DoNotStrip
private constructor(componentFactory: ComponentFactory) {

  @DoNotStrip private val hybridData: HybridData = initHybrid(componentFactory)

  @DoNotStrip private external fun initHybrid(componentFactory: ComponentFactory): HybridData

  companion object {
    @JvmStatic
    @DoNotStrip
    fun register(componentFactory: ComponentFactory) = DefaultComponentsRegistry(componentFactory)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.defaults

import com.facebook.react.ReactNativeHost
import com.facebook.react.bridge.JSIModulePackage
import com.facebook.react.bridge.JSIModuleProvider
import com.facebook.react.bridge.JSIModuleSpec
import com.facebook.react.bridge.JSIModuleType
import com.facebook.react.bridge.JavaScriptContextHolder
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.UIManager
import com.facebook.react.fabric.ComponentFactory
import com.facebook.react.fabric.FabricJSIModuleProvider
import com.facebook.react.fabric.ReactNativeConfig
import com.facebook.react.uimanager.ViewManagerRegistry

/**
 * A utility class that allows users to create a JSIModulePackage to use Fabric. This essentially
 * allows users to just provide C++ implementation for the methods of `DefaultComponentsRegistry`
 * without providing all the extra machinery for the New Architecture.
 *
 * `ReactNativeHost` is required to create Fabric's ViewManagers.
 */
class DefaultJSIModulePackage(private val reactNativeHost: ReactNativeHost) : JSIModulePackage {

  override fun getJSIModules(
      reactApplicationContext: ReactApplicationContext,
      jsContext: JavaScriptContextHolder
  ): List<JSIModuleSpec<UIManager>> =
      listOf<JSIModuleSpec<UIManager>>(JSIModuleForFabric(reactApplicationContext, reactNativeHost))

  private inner class JSIModuleForFabric(
      private val reactApplicationContext: ReactApplicationContext,
      private val reactNativeHost: ReactNativeHost
  ) : JSIModuleSpec<UIManager> {
    override fun getJSIModuleType(): JSIModuleType = JSIModuleType.UIManager

    override fun getJSIModuleProvider(): JSIModuleProvider<UIManager> {
      val componentFactory = ComponentFactory()

      DefaultComponentsRegistry.register(componentFactory)

      val viewManagers =
          reactNativeHost.reactInstanceManager.getOrCreateViewManagers(reactApplicationContext)
      val viewManagerRegistry = ViewManagerRegistry(viewManagers)
      return FabricJSIModuleProvider(
          reactApplicationContext,
          componentFactory,
          ReactNativeConfig.DEFAULT_CONFIG,
          viewManagerRegistry)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.defaults

import com.facebook.react.config.ReactFeatureFlags
import com.facebook.soloader.SoLoader

/**
 * A utility class that serves as an entry point for users setup the New Architecture.
 *
 * This class needs to be invoked as `DefaultNewArchitectureEntryPoint.load(...)` by passing a
 * series of optional parameters.
 *
 * By default it loads a library called `appmodules`. `appmodules` is a convention used to refer to
 * the application dynamic library. If changed here should be updated also inside the template.
 *
 * By default it also enables both TurboModules, Fabric and Concurrent React (aka React 18)
 */
object DefaultNewArchitectureEntryPoint {
  @JvmStatic
  @JvmOverloads
  fun load(
      turboModulesEnabled: Boolean = true,
      fabricEnabled: Boolean = true,
      bridgelessEnaled: Boolean = false,
      dynamicLibraryName: String = "appmodules",
  ) {
    ReactFeatureFlags.useTurboModules = turboModulesEnabled
    ReactFeatureFlags.enableFabricRenderer = fabricEnabled
    ReactFeatureFlags.unstable_useFabricInterop = fabricEnabled
    ReactFeatureFlags.enableBridgelessArchitecture = bridgelessEnaled

    this.privateFabricEnabled = fabricEnabled
    this.privateTurboModulesEnabled = turboModulesEnabled
    this.privateConcurrentReactEnabled = fabricEnabled
    this.privateBridgelessEnabled = bridgelessEnaled

    SoLoader.loadLibrary("react_newarchdefaults")
    SoLoader.loadLibrary(dynamicLibraryName)
  }

  @Deprecated(
      message =
          "Calling DefaultNewArchitectureEntryPoint.load() with different fabricEnabled and concurrentReactEnabled is deprecated. Please use a single flag for both Fabric and Concurrent React",
      replaceWith = ReplaceWith("load(turboModulesEnabled, fabricEnabled, dynamicLibraryName)"),
      level = DeprecationLevel.WARNING)
  fun load(
      turboModulesEnabled: Boolean = true,
      fabricEnabled: Boolean = true,
      bridgelessEnaled: Boolean = false,
      @Suppress("UNUSED_PARAMETER") concurrentReactEnabled: Boolean = true,
      dynamicLibraryName: String = "appmodules",
  ) {
    load(turboModulesEnabled, fabricEnabled, bridgelessEnaled, dynamicLibraryName)
  }

  private var privateFabricEnabled: Boolean = false
  @JvmStatic
  val fabricEnabled: Boolean
    get() = privateFabricEnabled

  private var privateTurboModulesEnabled: Boolean = false
  @JvmStatic
  val turboModulesEnabled: Boolean
    get() = privateTurboModulesEnabled

  private var privateConcurrentReactEnabled: Boolean = false
  @JvmStatic
  val concurrentReactEnabled: Boolean
    get() = privateConcurrentReactEnabled

  private var privateBridgelessEnabled: Boolean = false
  @JvmStatic
  val bridgelessEnaled: Boolean
    get() = privateBridgelessEnabled
}