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

package com.facebook.samples.kotlinsampleapp.home

import androidx.compose.runtime.Immutable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.facebook.Profile
import com.facebook.ProfileTracker
import com.facebook.login.LoginBehavior
import com.facebook.login.LoginManager

class HomeViewModel : ViewModel() {

  private val profileTracker =
      object : ProfileTracker() {
        override fun onCurrentProfileChanged(oldProfile: Profile?, currentProfile: Profile?) {
          if (currentProfile != null) this@HomeViewModel.updateProfile(currentProfile)
          else this@HomeViewModel.resetProfile()
        }
      }
  private val _profileViewState = MutableLiveData(HomeViewState(Profile.getCurrentProfile()))

  val homeViewState: LiveData<HomeViewState> = _profileViewState

  override fun onCleared() {
    profileTracker.stopTracking()
    super.onCleared()
  }

  fun selectLoginBehavior(loginBehavior: LoginBehavior) {
    _profileViewState.value = _profileViewState.value?.copy(loginBehavior = loginBehavior)
  }

  fun logout() {
    LoginManager.getInstance().logOut()
  }

  private fun updateProfile(profile: Profile) {
    _profileViewState.value = _profileViewState.value?.copy(profile = profile)
  }

  private fun resetProfile() {
    _profileViewState.value = _profileViewState.value?.copy(profile = null)
  }
}

@Immutable
data class HomeViewState(
    val profile: Profile? = null,
    val loginBehavior: LoginBehavior = LoginBehavior.NATIVE_WITH_FALLBACK,
    val availableLoginBehaviors: List<LoginBehavior> =
        listOf(LoginBehavior.NATIVE_WITH_FALLBACK, LoginBehavior.WEB_ONLY)
)


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

package com.facebook.samples.kotlinsampleapp.home

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.selectable
import androidx.compose.material.Button
import androidx.compose.material.RadioButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.facebook.Profile
import com.facebook.login.LoginBehavior
import com.facebook.login.widget.LoginButton
import com.facebook.login.widget.ProfilePictureView

@Composable
fun ProfileView(
    profile: Profile?,
    loginBehavior: LoginBehavior,
    availableLoginBehaviors: List<LoginBehavior>,
    logout: () -> Unit,
    selectLoginBehavior: (LoginBehavior) -> Unit
) {
  return Row {
    ProfilePhoto(profile)
    Column(
        modifier = Modifier.padding(4.dp).height(96.dp).fillMaxSize().background(Color.White),
        verticalArrangement = Arrangement.Center) {
          if (profile != null) {

            ProfileCardEntries(
                listOf(
                    Pair("ID:", profile.id),
                    Pair("First name:", profile.firstName),
                    Pair("Last name:", profile.lastName)))
            LogoutButton(logout)
          } else {
            SignIn(loginBehavior, availableLoginBehaviors, selectLoginBehavior)
          }
        }
  }
}

@Composable
fun ProfilePhoto(profile: Profile?) {
  val updateProfile = { button: ProfilePictureView -> button.profileId = profile?.id }
  AndroidView(
      factory = { context -> ProfilePictureView(context).apply(updateProfile) },
      update = updateProfile,
      modifier = Modifier.size(width = 100.dp, height = 100.dp).padding(4.dp))
}

@Composable
fun ProfileCardEntries(pairs: List<Pair<String, String?>>) {
  Column {
    pairs
        .filter { it.second != null }
        .map {
          Row {
            Text(it.first, modifier = Modifier.padding(end = 4.dp))
            Text(it.second!!)
          }
        }
  }
}

@Composable
fun SignIn(
    currentBehavior: LoginBehavior,
    availableLoginBehaviors: List<LoginBehavior>,
    onSelected: (LoginBehavior) -> Unit
) {
  Column {
    LoginBehaviorRadio(currentBehavior, availableLoginBehaviors, onSelected)
    FBLoginButton(currentBehavior)
  }
}

@Composable
fun LoginBehaviorRadio(
    currentBehavior: LoginBehavior,
    availableLoginBehaviors: List<LoginBehavior>,
    onSelected: (LoginBehavior) -> Unit
) {
  Column {
    availableLoginBehaviors.forEach { loginBehavior ->
      Row(
          Modifier.fillMaxWidth()
              .selectable(
                  selected = loginBehavior == currentBehavior,
                  onClick = { onSelected(loginBehavior) })
              .padding(horizontal = 16.dp)) {
            RadioButton(
                selected = loginBehavior == currentBehavior,
                onClick = { onSelected(loginBehavior) })
            Text(loginBehavior.toString())
          }
    }
  }
}

@Composable
fun FBLoginButton(loginBehavior: LoginBehavior) {
  val updateValue = { loginButton: LoginButton ->
    loginButton.setPermissions("email")
    loginButton.loginBehavior = loginBehavior
  }
  AndroidView(
      factory = { context -> LoginButton(context).apply(updateValue) }, update = updateValue)
}

@Composable
fun LogoutButton(onClick: () -> Unit) {
  Button(onClick = onClick) { Text("logout") }
}


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

package com.facebook.samples.kotlinsampleapp.login

import android.util.Log
import android.widget.Toast
import androidx.activity.result.ActivityResultRegistryOwner
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.facebook.CallbackManager
import com.facebook.FacebookCallback
import com.facebook.FacebookException
import com.facebook.login.LoginManager
import com.facebook.login.LoginResult
import com.facebook.samples.kotlinsampleapp.common.MenuItem

@Composable
fun LoginMenuScreen() {
  val context = LocalContext.current
  Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(16.dp)) {
    Text("This screen shows how to implement login without the LoginButton")

    MenuItem(
        "Login",
        onClick = {
          if (context is ActivityResultRegistryOwner) {
            val callbackManager = CallbackManager.Factory.create()
            val loginManager = LoginManager.getInstance()
            loginManager.registerCallback(
                callbackManager,
                object : FacebookCallback<LoginResult> {
                  override fun onCancel() {
                    Toast.makeText(context, "Login canceled!", Toast.LENGTH_LONG).show()
                  }

                  override fun onError(error: FacebookException) {
                    Log.e("Login", error.message ?: "Unknown error")
                    Toast.makeText(context, "Login failed with errors!", Toast.LENGTH_LONG).show()
                  }

                  override fun onSuccess(result: LoginResult) {
                    Toast.makeText(context, "Login succeed!", Toast.LENGTH_LONG).show()
                  }
                })
            LoginManager.getInstance().logIn(context, callbackManager, listOf("email"))
          } else {
            Toast.makeText(
                    context,
                    "This login should only happens with an AndroidX activity.",
                    Toast.LENGTH_LONG)
                .show()
          }
        })
    MenuItem("Logout", onClick = { LoginManager.getInstance().logOut() })
  }
}


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

package com.facebook.samples.kotlinsampleapp.sharing

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.graphics.BitmapFactory
import android.net.Uri
import com.facebook.FacebookSdk.getApplicationContext
import com.facebook.share.model.ShareContent
import com.facebook.share.model.ShareHashtag
import com.facebook.share.model.ShareLinkContent
import com.facebook.share.model.SharePhoto
import com.facebook.share.model.SharePhotoContent
import com.facebook.share.model.ShareVideo
import com.facebook.share.model.ShareVideoContent
import com.facebook.share.widget.ShareDialog
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream

fun shareLink(
    context: Context,
    link: String,
    hashtag: String? = null,
    quote: String? = null
): Boolean {
  val content =
      ShareLinkContent.Builder()
          .setContentUrl(Uri.parse(link))
          .setShareHashtag(ShareHashtag.Builder().setHashtag(hashtag).build())
          .setQuote(quote)
          .build()
  return share(context, content)
}

fun sharePhotos(
    context: Context,
    drawableIds: List<Int>,
    hashtag: String? = null,
): Boolean {
  val content =
      SharePhotoContent.Builder()
          .setPhotos(
              drawableIds.map { image ->
                SharePhoto.Builder()
                    .setBitmap(
                        BitmapFactory.decodeResource(getApplicationContext().resources, image))
                    .build()
              })
          .setShareHashtag(ShareHashtag.Builder().setHashtag(hashtag).build())
          .build()
  return share(context, content)
}

fun shareVideo(
    context: Context,
    videoResId: Int,
    hashtag: String? = null,
): Boolean {
  val content =
      ShareVideoContent.Builder()
          .setVideo(
              ShareVideo.Builder().setLocalUrl(getResourceUri(resourceId = videoResId)).build())
          .setShareHashtag(ShareHashtag.Builder().setHashtag(hashtag).build())
          .build()
  return share(context, content)
}

private fun share(
    context: Context,
    content: ShareContent<*, *>,
    mode: ShareDialog.Mode = ShareDialog.Mode.AUTOMATIC
): Boolean {
  val activity = getActivity(context) ?: return false
  ShareDialog(activity).show(content, mode)
  return true
}

private fun getResourceUri(resourceId: Int): Uri {
  val dst = File.createTempFile("temp", null, getApplicationContext().cacheDir)
  val input = getApplicationContext().resources.openRawResource(resourceId)
  val out: OutputStream = FileOutputStream(dst)

  // Transfer bytes from input to out
  val buf = ByteArray(1024)
  var len: Int
  while (input.read(buf).also { len = it } > 0) {
    out.write(buf, 0, len)
  }
  input.close()
  out.close()
  return Uri.fromFile(dst)
}

private fun getActivity(context: Context): Activity? {
  var ctx = context
  while (ctx is ContextWrapper) {
    if (ctx is Activity) {
      return ctx
    }
    ctx = ctx.baseContext
  }
  return null
}


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

package com.facebook.samples.kotlinsampleapp.sharing

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.facebook.samples.kotlinsampleapp.R
import com.facebook.samples.kotlinsampleapp.common.MenuItem

@Composable
fun SharingScreen() {
  val context = LocalContext.current
  Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(16.dp)) {
    Text("This screen shows how to to share content using FB SDK")
    MenuItem("Share link", onClick = { shareLink(context, "https://developers.facebook.com") })
    MenuItem(
        "Share link with hashtag",
        onClick = {
          shareLink(context, "https://developers.facebook.com", hashtag = "#ConnectTheWorld")
        })
    MenuItem(
        "Share link with quote",
        onClick = {
          shareLink(
              context, "https://developers.facebook.com", quote = "Connect on a global scale.")
        })
    MenuItem(
        "Share photos",
        onClick = {
          sharePhotos(
              context,
              listOf(
                  R.drawable.red,
                  R.drawable.black,
                  R.drawable.green,
              ))
        })
    MenuItem("Share video", onClick = { shareVideo(context, R.raw.test) })
  }
}


/**
 * Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
 *
 * You are hereby granted a non-exclusive, worldwide, royalty-free license to
 * use, copy, modify, and distribute this software in source code or binary
 * form for use in connection with the web services and APIs provided by
 * Facebook.
 *
 * As with any software that integrates with the Facebook platform, your use
 * of this software is subject to the Facebook Developer Principles and
 * Policies [http://developers.facebook.com/policy/]. This copyright notice
 * shall be included in all copies or substantial portions of the software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */
package com.facebook.ads;

import com.facebook.ads.sdk.APIContext
import com.facebook.ads.sdk.APIRequest
import com.facebook.ads.sdk.APIRequest.DefaultRequestExecutor
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.coroutines.Continuation
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import io.mockk.mockkStatic
import io.mockk.mockkConstructor

class APIContextTest {
    private val accessToken = "access_token"
    private val appSecret = "app_secret"

    // using constructor :
    // APIContext(String accessToken, String appSecret, String appID, boolean logCrash)
    private val apiContext: APIContext = APIContext(accessToken, appSecret, null, false)

    @Test
    fun testSha256() {
        mockkStatic(APIContext::class)

        // get the expected result from https://www.freeformatter.com/hmac-generator.html
        val expectedResult = "d52ddf968d622d8af8677906b7fbae09ac1f89f7cd5c1584b27544624cc23e5a"
        val result = APIContext.sha256(appSecret, accessToken)
        verify { APIContext.toHex(any()) }
        assertEquals(expectedResult, result)
    }

    @Test
    fun testSha256EmptyAccessToken() {
        mockkStatic(APIContext::class)
        val result = APIContext.sha256(appSecret, "")
        verify { APIContext.toHex(any()) }
        assertNotNull(result);
    }

    @Test(expected = IllegalArgumentException::class)
    fun testSha256EmptyAppSecret() {
        APIContext.sha256("", accessToken)
    }

    @Test(expected = IllegalArgumentException::class)
    fun testSha256NullAppSecret() {
        APIContext.sha256(null, accessToken)
    }

    @Test(expected = IllegalArgumentException::class)
    fun testSha256NullAccessToken() {
        APIContext.sha256(appSecret, null)
    }

    @Test
    fun testGetAppID() {
        val appId = "app_id"
        val mockResponseBody = "{\"data\":{\"app_id\":\"$appId\"}}"
        initMockDefaultRequestExecutor(mockResponseBody)
        assertEquals(appId, apiContext.appID)
    }

    @Test
    fun testGetAppIDFieldNotExist() {
        val mockResponseBody = "{\"data\":{\"appName\":\"app_name\"}}"
        initMockDefaultRequestExecutor(mockResponseBody)
        assertNull(apiContext.appID)
    }

    @Test
    fun testGetAppIDResponseBodyMalFormed() {
        val mockResponseBody = "{"
        initMockDefaultRequestExecutor(mockResponseBody)
        assertNull(apiContext.appID)
    }

    @Test
    fun testGetAppIDEmptyResponseBody() {
        val mockResponseBody = ""
        initMockDefaultRequestExecutor(mockResponseBody)
        assertNull(apiContext.appID)
    }

    @Test
    fun testGetAppIDNullResponseBody() {
        val mockResponseBody = null
        initMockDefaultRequestExecutor(mockResponseBody)
        assertNull(apiContext.appID)
    }

    fun initMockDefaultRequestExecutor(mockBody : String?) {
        mockkConstructor(APIRequest.DefaultRequestExecutor::class)
        every {
            anyConstructed<APIRequest.DefaultRequestExecutor>().
            sendGet(any(), any(), apiContext)
        } returns APIRequest.ResponseWrapper(mockBody, "header")
    }
}


// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

package com.facebook.flipper.plugins.jetpackcompose

import androidx.compose.ui.platform.ComposeView
import com.facebook.flipper.plugins.jetpackcompose.descriptors.*
import com.facebook.flipper.plugins.jetpackcompose.model.*
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister

const val JetpackComposeTag = "JetpackCompose"

object UIDebuggerComposeSupport {

  fun enable(context: UIDContext) {
    addDescriptors(context.descriptorRegister)
  }

  private fun addDescriptors(register: DescriptorRegister) {
    register.register(ComposeView::class.java, ComposeViewDescriptor)
    register.register(ComposeNode::class.java, ComposeNodeDescriptor)
    register.register(ComposeInnerViewNode::class.java, ComposeInnerViewDescriptor)
  }
}


// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

package com.facebook.flipper.plugins.jetpackcompose.descriptors

import android.graphics.Bitmap
import android.view.ViewGroup
import com.facebook.flipper.plugins.jetpackcompose.model.ComposeInnerViewNode
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.ViewDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.ViewGroupDescriptor
import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred

object ComposeInnerViewDescriptor : NodeDescriptor<ComposeInnerViewNode> {

  override fun getBounds(node: ComposeInnerViewNode): Bounds {
    return node.bounds
  }

  override fun getName(node: ComposeInnerViewNode): String {
    if (node.view is ViewGroup) {
      return ViewGroupDescriptor.getName(node.view)
    }
    return ViewDescriptor.getName(node.view)
  }

  override fun getQualifiedName(node: ComposeInnerViewNode): String {
    if (node.view is ViewGroup) {
      return ViewGroupDescriptor.getQualifiedName(node.view)
    }
    return ViewDescriptor.getQualifiedName(node.view)
  }

  override fun getChildren(node: ComposeInnerViewNode): List<Any> {
    if (node.view is ViewGroup) {
      return ViewGroupDescriptor.getChildren(node.view)
    }
    return ViewDescriptor.getChildren(node.view)
  }

  override fun getSnapshot(node: ComposeInnerViewNode, bitmap: Bitmap?): Bitmap? {
    if (node.view is ViewGroup) {
      return ViewGroupDescriptor.getSnapshot(node.view, bitmap)
    }
    return ViewDescriptor.getSnapshot(node.view, bitmap)
  }

  override fun getActiveChild(node: ComposeInnerViewNode): Any? {
    if (node.view is ViewGroup) {
      return ViewGroupDescriptor.getActiveChild(node.view)
    }
    return ViewDescriptor.getActiveChild(node.view)
  }

  override fun getAttributes(
      node: ComposeInnerViewNode
  ): MaybeDeferred<Map<MetadataId, InspectableObject>> {
    if (node.view is ViewGroup) {
      return ViewGroupDescriptor.getAttributes(node.view)
    }
    return ViewDescriptor.getAttributes(node.view)
  }

  override fun getTags(node: ComposeInnerViewNode): Set<String> {
    if (node.view is ViewGroup) {
      return ViewGroupDescriptor.getTags(node.view)
    }
    return ViewDescriptor.getTags(node.view)
  }
}


// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

package com.facebook.flipper.plugins.jetpackcompose.descriptors

import android.graphics.Bitmap
import com.facebook.flipper.plugins.jetpackcompose.model.ComposeNode
import com.facebook.flipper.plugins.uidebugger.descriptors.BaseTags
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.model.Coordinate
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import com.facebook.flipper.plugins.uidebugger.util.Immediate
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred

object ComposeNodeDescriptor : NodeDescriptor<ComposeNode> {

  private const val NAMESPACE = "ComposeNode"

  private var SectionId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
  private val IdAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "id")
  private val KeyAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "key")
  private val NameAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "name")
  private val FilenameAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "filename")
  private val PackageHashAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "packageHash")
  private val LineNumberAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "lineNumber")
  private val OffsetAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "offset")
  private val LengthAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "length")
  private val BoxAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "box")
  private val BoundsAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "bounds")
  private val Bounds0AttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x0, y0)")
  private val Bounds1AttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x1, y1)")
  private val Bounds2AttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x2, y2)")
  private val Bounds3AttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x3, y3)")
  private val ViewIdAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "viewId")
  private val MergedSemanticsAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "mergedSemantics")
  private val UnmergedSemanticsAttributeId =
      MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "unmergedSemantics")

  override fun getName(node: ComposeNode): String = node.inspectorNode.name

  override fun getChildren(node: ComposeNode): List<Any> {
    return node.children
  }

  override fun getAttributes(node: ComposeNode): MaybeDeferred<Map<MetadataId, InspectableObject>> {

    val builder = mutableMapOf<MetadataId, InspectableObject>()
    val props = mutableMapOf<Int, Inspectable>()

    props[IdAttributeId] = InspectableValue.Number(node.inspectorNode.id)
    props[ViewIdAttributeId] = InspectableValue.Number(node.inspectorNode.viewId)
    props[KeyAttributeId] = InspectableValue.Number(node.inspectorNode.key)
    props[NameAttributeId] = InspectableValue.Text(node.inspectorNode.name)
    props[FilenameAttributeId] = InspectableValue.Text(node.inspectorNode.fileName)
    props[PackageHashAttributeId] = InspectableValue.Number(node.inspectorNode.packageHash)
    props[LineNumberAttributeId] = InspectableValue.Number(node.inspectorNode.lineNumber)
    props[OffsetAttributeId] = InspectableValue.Number(node.inspectorNode.offset)
    props[LengthAttributeId] = InspectableValue.Number(node.inspectorNode.length)

    props[BoxAttributeId] =
        InspectableValue.Bounds(
            Bounds(
                node.inspectorNode.left,
                node.inspectorNode.top,
                node.inspectorNode.width,
                node.inspectorNode.height))

    node.inspectorNode.bounds?.let { bounds ->
      val quadBounds = mutableMapOf<Int, Inspectable>()
      quadBounds[Bounds0AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x0, bounds.y0))
      quadBounds[Bounds1AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x1, bounds.y1))
      quadBounds[Bounds2AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x2, bounds.y2))
      quadBounds[Bounds3AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x3, bounds.y3))
      props[BoundsAttributeId] = InspectableObject(quadBounds.toMap())
    }

    val mergedSemantics = mutableMapOf<Int, Inspectable>()
    node.inspectorNode.mergedSemantics.forEach {
      val keyAttributeId =
          MetadataRegister.register(
              MetadataRegister.TYPE_ATTRIBUTE, node.inspectorNode.name, it.name)
      mergedSemantics[keyAttributeId] = InspectableValue.Text(it.value.toString())
    }
    props[MergedSemanticsAttributeId] = InspectableObject(mergedSemantics.toMap())

    val unmergedSemantics = mutableMapOf<Int, Inspectable>()
    node.inspectorNode.unmergedSemantics.forEach {
      val keyAttributeId =
          MetadataRegister.register(
              MetadataRegister.TYPE_ATTRIBUTE, node.inspectorNode.name, it.name)
      mergedSemantics[keyAttributeId] = InspectableValue.Text(it.value.toString())
    }
    props[UnmergedSemanticsAttributeId] = InspectableObject(unmergedSemantics.toMap())

    builder[SectionId] = InspectableObject(props.toMap())

    return Immediate(builder)
  }

  override fun getBounds(node: ComposeNode): Bounds {
    return node.bounds
  }

  override fun getQualifiedName(node: ComposeNode): String = node.inspectorNode.name

  override fun getSnapshot(node: ComposeNode, bitmap: Bitmap?): Bitmap? = null

  override fun getActiveChild(node: ComposeNode): Any? = null

  override fun getTags(node: ComposeNode): Set<String> = setOf(BaseTags.Android, "Compose")
}


// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

package com.facebook.flipper.plugins.jetpackcompose.descriptors

import android.os.Build
import android.view.View
import androidx.compose.ui.platform.ComposeView
import com.facebook.flipper.plugins.jetpackcompose.model.ComposeNode
import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor
import facebook.internal.androidx.compose.ui.inspection.inspector.InspectorNode
import facebook.internal.androidx.compose.ui.inspection.inspector.LayoutInspectorTree

object ComposeViewDescriptor : ChainedDescriptor<ComposeView>() {
  override fun onGetName(node: ComposeView): String = node.javaClass.simpleName

  private fun transform(view: View, nodes: List<InspectorNode>): List<ComposeNode> {
    val positionOnScreen = IntArray(2)
    view.getLocationOnScreen(positionOnScreen)

    val xOffset = positionOnScreen[0]
    val yOffset = positionOnScreen[1]

    return nodes.map { node -> ComposeNode(view, node, xOffset, yOffset) }
  }

  override fun onGetChildren(node: ComposeView): List<Any> {
    val children = mutableListOf<Any>()
    val count = node.childCount - 1
    for (i in 0..count) {
      val child: View = node.getChildAt(i)
      children.add(child)

      if (child.javaClass.simpleName.contains("AndroidComposeView") &&
          (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)) {
        val layoutInspector = LayoutInspectorTree()
        layoutInspector.hideSystemNodes = false
        return transform(child, layoutInspector.convert(child))
      }
    }

    return children
  }
}


// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

package com.facebook.flipper.plugins.jetpackcompose.model

import android.view.View
import com.facebook.flipper.plugins.uidebugger.model.Bounds

class ComposeInnerViewNode(
    val view: View,
) {
  val bounds: Bounds = Bounds(0, 0, view.width, view.height)
}


// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

package com.facebook.flipper.plugins.jetpackcompose.model

import android.os.Build
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import com.facebook.flipper.plugins.uidebugger.model.*
import facebook.internal.androidx.compose.ui.inspection.inspector.InspectorNode

class ComposeNode(
    private val parentComposeView: View,
    val inspectorNode: InspectorNode,
    xOffset: Int,
    yOffset: Int
) {
  val bounds: Bounds =
      Bounds(
          inspectorNode.left - xOffset,
          inspectorNode.top - yOffset,
          inspectorNode.width,
          inspectorNode.height)

  val children: List<Any> = collectChildren()

  private fun collectChildren(): List<Any> {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
      val viewId = inspectorNode.viewId
      if (viewId != 0L) {
        val view = parentComposeView.findViewByDrawingId(viewId)
        if (view != null) {
          return listOf(ComposeInnerViewNode(view))
        }
      }
    }

    return inspectorNode.children.map { child ->
      ComposeNode(parentComposeView, child, inspectorNode.left, inspectorNode.top)
    }
  }

  @RequiresApi(Build.VERSION_CODES.Q)
  private fun View.findViewByDrawingId(drawingId: Long): View? {
    if (this.uniqueDrawingId == drawingId) {
      return this
    }
    if (this is ViewGroup) {
      for (i in 0 until this.childCount) {
        val child = this.getChildAt(i)
        val foundView = child.findViewByDrawingId(drawingId)
        if (foundView != null) {
          return foundView
        }
      }
    }
    return null
  }
}


/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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 facebook.internal.androidx.compose.ui.inspection.inspector

/**
 * Converter for casting a parameter represented by its primitive value to its inline class type.
 *
 * For example: an androidx.compose.ui.graphics.Color instance is often represented by a long
 */
internal class InlineClassConverter {
  // Map from inline type name to inline class and conversion lambda
  private val typeMap = mutableMapOf<String, (Any) -> Any>()
  // Return value used in functions
  private val notInlineType: (Any) -> Any = { it }

  /** Clear any cached data. */
  fun clear() {
    typeMap.clear()
  }

  /**
   * Cast the specified [value] to a value of type [inlineClassName] if possible.
   *
   * @param inlineClassName the fully qualified name of the inline class.
   * @param value the value to convert to an instance of [inlineClassName].
   */
  fun castParameterValue(inlineClassName: String?, value: Any?): Any? =
      if (value != null && inlineClassName != null) typeMapperFor(inlineClassName)(value) else value

  private fun typeMapperFor(typeName: String): (Any) -> (Any) =
      typeMap.getOrPut(typeName) { loadTypeMapper(typeName.replace('.', '/')) }

  private fun loadTypeMapper(className: String): (Any) -> Any {
    val javaClass = loadClassOrNull(className) ?: return notInlineType
    val create = javaClass.declaredConstructors.singleOrNull() ?: return notInlineType
    create.isAccessible = true
    return { value -> create.newInstance(value) }
  }

  private fun loadClassOrNull(className: String): Class<*>? =
      try {
        javaClass.classLoader!!.loadClass(className)
      } catch (ex: Exception) {
        null
      }
}


/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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 facebook.internal.androidx.compose.ui.inspection.inspector

import androidx.compose.ui.layout.LayoutInfo
import androidx.compose.ui.unit.IntRect

internal const val UNDEFINED_ID = 0L

internal val emptyBox = IntRect(0, 0, 0, 0)
internal val outsideBox = IntRect(Int.MAX_VALUE, Int.MIN_VALUE, Int.MAX_VALUE, Int.MIN_VALUE)

/** Node representing a Composable for the Layout Inspector. */
class InspectorNode
internal constructor(
    /** The associated render node id or 0. */
    val id: Long,

    /** The associated key for tracking recomposition counts. */
    val key: Int,

    /**
     * The id of the associated anchor for tracking recomposition counts.
     *
     * An Anchor is a mechanism in the compose runtime that can identify a Group in the SlotTable
     * that is invariant to SlotTable updates. See [androidx.compose.runtime.Anchor] for more
     * information.
     */
    val anchorId: Int,

    /** The name of the Composable. */
    val name: String,

    /** The fileName where the Composable was called. */
    val fileName: String,

    /**
     * A hash of the package name to help disambiguate duplicate [fileName] values.
     *
     * This hash is calculated by,
     *
     * `packageName.fold(0) { hash, current -> hash * 31 + current.toInt() }?.absoluteValue`
     *
     * where the package name is the dotted name of the package. This can be used to disambiguate
     * which file is referenced by [fileName]. This number is -1 if there was no package hash
     * information generated such as when the file does not contain a package declaration.
     */
    val packageHash: Int,

    /** The line number where the Composable was called. */
    val lineNumber: Int,

    /** The UTF-16 offset in the file where the Composable was called */
    val offset: Int,

    /** The number of UTF-16 code point comprise the Composable call */
    val length: Int,

    /** The bounding box of the Composable. */
    internal val box: IntRect,

    /** The 4 corners of the polygon after transformations of the original rectangle. */
    val bounds: QuadBounds? = null,

    /** True if the code for the Composable was inlined */
    val inlined: Boolean = false,

    /** The parameters of this Composable. */
    val parameters: List<RawParameter>,

    /** The id of a android View embedded under this node. */
    val viewId: Long,

    /** The merged semantics information of this Composable. */
    val mergedSemantics: List<RawParameter>,

    /** The un-merged semantics information of this Composable. */
    val unmergedSemantics: List<RawParameter>,

    /** The children nodes of this Composable. */
    val children: List<InspectorNode>
) {
  /** Left side of the Composable in pixels. */
  val left: Int
    get() = box.left

  /** Top of the Composable in pixels. */
  val top: Int
    get() = box.top

  /** Width of the Composable in pixels. */
  val width: Int
    get() = box.width

  /** Width of the Composable in pixels. */
  val height: Int
    get() = box.height

  fun parametersByKind(kind: ParameterKind): List<RawParameter> =
      when (kind) {
        ParameterKind.Normal -> parameters
        ParameterKind.MergedSemantics -> mergedSemantics
        ParameterKind.UnmergedSemantics -> unmergedSemantics
      }
}

data class QuadBounds(
    val x0: Int,
    val y0: Int,
    val x1: Int,
    val y1: Int,
    val x2: Int,
    val y2: Int,
    val x3: Int,
    val y3: Int,
) {
  val xMin: Int
    get() = sequenceOf(x0, x1, x2, x3).minOrNull()!!

  val xMax: Int
    get() = sequenceOf(x0, x1, x2, x3).maxOrNull()!!

  val yMin: Int
    get() = sequenceOf(y0, y1, y2, y3).minOrNull()!!

  val yMax: Int
    get() = sequenceOf(y0, y1, y2, y3).maxOrNull()!!

  val outerBox: IntRect
    get() = IntRect(xMin, yMin, xMax, yMax)
}

/** Parameter definition with a raw value reference. */
class RawParameter(val name: String, val value: Any?)

/** Mutable version of [InspectorNode]. */
internal class MutableInspectorNode {
  var id = UNDEFINED_ID
  var key = 0
  var anchorId = 0
  val layoutNodes = mutableListOf<LayoutInfo>()
  val mergedSemantics = mutableListOf<RawParameter>()
  val unmergedSemantics = mutableListOf<RawParameter>()
  var name = ""
  var fileName = ""
  var packageHash = -1
  var lineNumber = 0
  var offset = 0
  var length = 0
  var box: IntRect = emptyBox
  var bounds: QuadBounds? = null
  var inlined = false
  val parameters = mutableListOf<RawParameter>()
  var viewId = UNDEFINED_ID
  val children = mutableListOf<InspectorNode>()
  var outerBox: IntRect = outsideBox

  fun reset() {
    markUnwanted()
    id = UNDEFINED_ID
    key = 0
    anchorId = 0
    viewId = UNDEFINED_ID
    layoutNodes.clear()
    mergedSemantics.clear()
    unmergedSemantics.clear()
    box = emptyBox
    bounds = null
    inlined = false
    outerBox = outsideBox
    children.clear()
  }

  fun markUnwanted() {
    name = ""
    fileName = ""
    packageHash = -1
    lineNumber = 0
    offset = 0
    length = 0
    parameters.clear()
  }

  fun shallowCopy(node: InspectorNode): MutableInspectorNode = apply {
    id = node.id
    key = node.key
    anchorId = node.anchorId
    mergedSemantics.addAll(node.mergedSemantics)
    unmergedSemantics.addAll(node.unmergedSemantics)
    name = node.name
    fileName = node.fileName
    packageHash = node.packageHash
    lineNumber = node.lineNumber
    offset = node.offset
    length = node.length
    box = node.box
    bounds = node.bounds
    inlined = node.inlined
    parameters.addAll(node.parameters)
    viewId = node.viewId
    children.addAll(node.children)
  }

  fun build(withSemantics: Boolean = true): InspectorNode =
      InspectorNode(
          id,
          key,
          anchorId,
          name,
          fileName,
          packageHash,
          lineNumber,
          offset,
          length,
          box,
          bounds,
          inlined,
          parameters.toList(),
          viewId,
          if (withSemantics) mergedSemantics.toList() else emptyList(),
          if (withSemantics) unmergedSemantics.toList() else emptyList(),
          children.toList())
}


/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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 facebook.internal.androidx.compose.ui.inspection.inspector

import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.tooling.CompositionData
import androidx.compose.runtime.tooling.CompositionGroup
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.R
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.GraphicLayerInfo
import androidx.compose.ui.layout.LayoutInfo
import androidx.compose.ui.node.InteroperableComposeUiNode
import androidx.compose.ui.node.Ref
import androidx.compose.ui.node.RootForTest
import androidx.compose.ui.platform.ViewRootForInspector
import androidx.compose.ui.semantics.getAllSemanticsNodes
import androidx.compose.ui.tooling.data.ContextCache
import androidx.compose.ui.tooling.data.ParameterInformation
import androidx.compose.ui.tooling.data.SourceContext
import androidx.compose.ui.tooling.data.SourceLocation
import androidx.compose.ui.tooling.data.UiToolingDataApi
import androidx.compose.ui.tooling.data.findParameters
import androidx.compose.ui.tooling.data.mapTree
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.toSize
import facebook.internal.androidx.compose.ui.inspection.util.AnchorMap
import facebook.internal.androidx.compose.ui.inspection.util.NO_ANCHOR_ID
import java.util.ArrayDeque
import java.util.Collections
import java.util.IdentityHashMap
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt

/**
 * The [InspectorNode.id] will be populated with:
 * - the layerId from a LayoutNode if this exists
 * - an id generated from an Anchor instance from the SlotTree if this exists
 * - a generated id if none of the above ids are available
 *
 * The interval -10000..-2 is reserved for the generated ids.
 */
@VisibleForTesting const val RESERVED_FOR_GENERATED_IDS = -10000L
const val PLACEHOLDER_ID = Long.MAX_VALUE

private val emptySize = IntSize(0, 0)

private val unwantedCalls =
    setOf(
        "CompositionLocalProvider",
        "Content",
        "Inspectable",
        "ProvideAndroidCompositionLocals",
        "ProvideCommonCompositionLocals",
    )

/** Generator of a tree for the Layout Inspector. */
@OptIn(UiToolingDataApi::class)
class LayoutInspectorTree {
  @Suppress("MemberVisibilityCanBePrivate") var hideSystemNodes = true
  var includeNodesOutsizeOfWindow = true
  var includeAllParameters = true
  private var foundNode: InspectorNode? = null
  private var windowSize = emptySize
  private val inlineClassConverter = InlineClassConverter()
  private val parameterFactory = ParameterFactory(inlineClassConverter)
  private val cache = ArrayDeque<MutableInspectorNode>()
  private var generatedId = -1L
  private val subCompositions = SubCompositionRoots()
  /** Map from [LayoutInfo] to the nearest [InspectorNode] that contains it */
  private val claimedNodes = IdentityHashMap<LayoutInfo, InspectorNode>()
  /** Map from parent tree to child trees that are about to be stitched together */
  private val treeMap = IdentityHashMap<MutableInspectorNode, MutableList<MutableInspectorNode>>()
  /** Map from owner node to child trees that are about to be stitched to this owner */
  private val ownerMap = IdentityHashMap<InspectorNode, MutableList<MutableInspectorNode>>()
  /** Map from semantics id to a list of merged semantics information */
  private val semanticsMap = mutableMapOf<Int, List<RawParameter>>()
  /* Map of seemantics id to a list of unmerged semantics information */
  private val unmergedSemanticsMap = mutableMapOf<Int, List<RawParameter>>()
  /** Set of tree nodes that were stitched into another tree */
  private val stitched = Collections.newSetFromMap(IdentityHashMap<MutableInspectorNode, Boolean>())
  private val contextCache = ContextCache()
  private val anchorMap = AnchorMap()

  /** Converts the [CompositionData] set held by [view] into a list of root nodes. */
  fun convert(view: View): List<InspectorNode> {
    windowSize = IntSize(view.width, view.height)
    parameterFactory.density = Density(view.context)
    @Suppress("UNCHECKED_CAST")
    val tables =
        view.getTag(R.id.inspection_slot_table_set) as? Set<CompositionData> ?: return emptyList()
    clear()
    collectSemantics(view)
    val result = convert(tables, view)
    clear()
    return result
  }

  fun findParameters(view: View, anchorId: Int): InspectorNode? {
    windowSize = IntSize(view.width, view.height)
    parameterFactory.density = Density(view.context)
    val identity = anchorMap[anchorId] ?: return null

    @Suppress("UNCHECKED_CAST")
    val tables = view.getTag(R.id.inspection_slot_table_set) as? Set<CompositionData> ?: return null
    val node = newNode().apply { this.anchorId = anchorId }
    val group = tables.firstNotNullOfOrNull { it.find(identity) } ?: return null
    group.findParameters(contextCache).forEach {
      val castedValue = castValue(it)
      node.parameters.add(RawParameter(it.name, castedValue))
    }
    return buildAndRelease(node)
  }

  /**
   * Add the roots to sub compositions that may have been collected from a different SlotTree.
   *
   * See [SubCompositionRoots] for details.
   */
  fun addSubCompositionRoots(view: View, nodes: List<InspectorNode>): List<InspectorNode> =
      subCompositions.addRoot(view, nodes)

  /**
   * Extract the merged semantics for this semantics owner such that they can be added to compose
   * nodes during the conversion of Group nodes.
   */
  private fun collectSemantics(view: View) {
    val root = view as? RootForTest ?: return
    val nodes = root.semanticsOwner.getAllSemanticsNodes(mergingEnabled = true)
    val unmergedNodes = root.semanticsOwner.getAllSemanticsNodes(mergingEnabled = false)
    nodes.forEach { node ->
      semanticsMap[node.id] = node.config.map { RawParameter(it.key.name, it.value) }
    }
    unmergedNodes.forEach { node ->
      unmergedSemanticsMap[node.id] = node.config.map { RawParameter(it.key.name, it.value) }
    }
  }

  /** Converts the [RawParameter]s of the [node] into displayable parameters. */
  fun convertParameters(
      rootId: Long,
      node: InspectorNode,
      kind: ParameterKind,
      maxRecursions: Int,
      maxInitialIterableSize: Int
  ): List<NodeParameter> {
    val parameters = node.parametersByKind(kind)
    return parameters.mapIndexed { index, parameter ->
      parameterFactory.create(
          rootId,
          node.id,
          node.anchorId,
          parameter.name,
          parameter.value,
          kind,
          index,
          maxRecursions,
          maxInitialIterableSize)
    }
  }

  /**
   * Converts a part of the [RawParameter] identified by [reference] into a displayable parameter.
   * If the parameter is some sort of a collection then [startIndex] and [maxElements] describes the
   * scope of the data returned.
   */
  fun expandParameter(
      rootId: Long,
      node: InspectorNode,
      reference: NodeParameterReference,
      startIndex: Int,
      maxElements: Int,
      maxRecursions: Int,
      maxInitialIterableSize: Int
  ): NodeParameter? {
    val parameters = node.parametersByKind(reference.kind)
    if (reference.parameterIndex !in parameters.indices) {
      return null
    }
    val parameter = parameters[reference.parameterIndex]
    return parameterFactory.expand(
        rootId,
        node.id,
        node.anchorId,
        parameter.name,
        parameter.value,
        reference,
        startIndex,
        maxElements,
        maxRecursions,
        maxInitialIterableSize)
  }

  /** Reset any state accumulated between windows. */
  @Suppress("unused")
  fun resetAccumulativeState() {
    subCompositions.resetAccumulativeState()
    parameterFactory.clearReferenceCache()
    // Reset the generated id. Nodes are assigned an id if there isn't a layout node id present.
    generatedId = -1L
  }

  private fun clear() {
    cache.clear()
    inlineClassConverter.clear()
    claimedNodes.clear()
    treeMap.clear()
    ownerMap.clear()
    semanticsMap.clear()
    unmergedSemanticsMap.clear()
    stitched.clear()
    subCompositions.clear()
    foundNode = null
  }

  private fun convert(tables: Set<CompositionData>, view: View): List<InspectorNode> {
    val trees = tables.mapNotNull { convert(view, it) }
    return when (trees.size) {
      0 -> listOf()
      1 -> addTree(mutableListOf(), trees.single())
      else -> stitchTreesByLayoutInfo(trees)
    }
  }

  /**
   * Stitch separate trees together using the [LayoutInfo]s found in the [CompositionData]s.
   *
   * Some constructs in Compose (e.g. ModalDrawer) will result is multiple [CompositionData]s. This
   * code will attempt to stitch the resulting [InspectorNode] trees together by looking at the
   * parent of each [LayoutInfo].
   *
   * If this algorithm is successful the result of this function will be a list with a single tree.
   */
  private fun stitchTreesByLayoutInfo(trees: List<MutableInspectorNode>): List<InspectorNode> {
    val layoutToTreeMap = IdentityHashMap<LayoutInfo, MutableInspectorNode>()
    trees.forEach { tree -> tree.layoutNodes.forEach { layoutToTreeMap[it] = tree } }
    trees.forEach { tree ->
      val layout = tree.layoutNodes.lastOrNull()
      val parentLayout =
          generateSequence(layout) { it.parentInfo }
              .firstOrNull {
                val otherTree = layoutToTreeMap[it]
                otherTree != null && otherTree != tree
              }
      if (parentLayout != null) {
        val ownerNode = claimedNodes[parentLayout]
        val ownerTree = layoutToTreeMap[parentLayout]
        if (ownerNode != null && ownerTree != null) {
          ownerMap.getOrPut(ownerNode) { mutableListOf() }.add(tree)
          treeMap.getOrPut(ownerTree) { mutableListOf() }.add(tree)
        }
      }
    }
    var parentTree = findDeepParentTree()
    while (parentTree != null) {
      addSubTrees(parentTree)
      treeMap.remove(parentTree)
      parentTree = findDeepParentTree()
    }
    val result = mutableListOf<InspectorNode>()
    trees.asSequence().filter { !stitched.contains(it) }.forEach { addTree(result, it) }
    return result
  }

  /**
   * Return a parent tree where the children trees (to be stitched under the parent) are not a
   * parent themselves. Do this to avoid rebuilding the same tree more than once.
   */
  private fun findDeepParentTree(): MutableInspectorNode? =
      treeMap.entries
          .asSequence()
          .filter { (_, children) -> children.none { treeMap.containsKey(it) } }
          .firstOrNull()
          ?.key

  private fun addSubTrees(tree: MutableInspectorNode) {
    for ((index, child) in tree.children.withIndex()) {
      tree.children[index] = addSubTrees(child) ?: child
    }
  }

  /**
   * Rebuild [node] with any possible sub trees added (stitched in). Return the rebuild node, or
   * null if no changes were found in this node or its children. Lazily allocate the new node to
   * avoid unnecessary allocations.
   */
  private fun addSubTrees(node: InspectorNode): InspectorNode? {
    var newNode: MutableInspectorNode? = null
    for ((index, child) in node.children.withIndex()) {
      val newChild = addSubTrees(child)
      if (newChild != null) {
        val newCopy = newNode ?: newNode(node)
        newCopy.children[index] = newChild
        newNode = newCopy
      }
    }
    val trees = ownerMap[node]
    if (trees == null && newNode == null) {
      return null
    }
    val newCopy = newNode ?: newNode(node)
    if (trees != null) {
      trees.forEach { addTree(newCopy.children, it) }
      stitched.addAll(trees)
    }
    return buildAndRelease(newCopy)
  }

  /**
   * Add [tree] to the end of the [out] list. The root nodes of [tree] may be a fake node that hold
   * a list of [LayoutInfo].
   */
  private fun addTree(
      out: MutableList<InspectorNode>,
      tree: MutableInspectorNode
  ): List<InspectorNode> {
    tree.children.forEach {
      if (it.name.isNotEmpty()) {
        out.add(it)
      } else {
        out.addAll(it.children)
      }
    }
    return out
  }

  private fun convert(view: View, table: CompositionData): MutableInspectorNode? {
    val fakeParent = newNode()
    val group = table.mapTree(::convert, contextCache) ?: return null
    addToParent(fakeParent, listOf(group), buildFakeChildNodes = true)
    return if (belongsToView(fakeParent.layoutNodes, view)) fakeParent else null
  }

  private fun convert(
      group: CompositionGroup,
      context: SourceContext,
      children: List<MutableInspectorNode>
  ): MutableInspectorNode {
    val parent = parse(group, context, children)
    subCompositions.captureNode(parent, context)
    addToParent(parent, children)
    return parent
  }

  /**
   * Adds the nodes in [input] to the children of [parentNode]. Nodes without a reference to a
   * wanted Composable are skipped unless [buildFakeChildNodes]. A single skipped render id and
   * layoutNode will be added to [parentNode].
   */
  private fun addToParent(
      parentNode: MutableInspectorNode,
      input: List<MutableInspectorNode>,
      buildFakeChildNodes: Boolean = false
  ) {
    // If we're adding an unwanted node from the `input` to the parent node and it has a
    // View ID, then assign it to the parent view so that we don't lose the context that we
    // found a View as a descendant of the parent node. Most likely, there were one or more
    // unwanted intermediate nodes between the node that actually owns the Android View
    // and the desired node that the View should be associated with in the inspector. If
    // there's more than one input node with a View ID, we skip this step since it's
    // unclear how these views would be related.
    input
        .singleOrNull { it.viewId != UNDEFINED_ID }
        ?.takeIf { node ->
          // Take if the node has been marked as unwanted
          node.id == UNDEFINED_ID
        }
        ?.let { nodeWithView -> parentNode.viewId = nodeWithView.viewId }

    var id: Long? = null
    input.forEach { node ->
      if (node.name.isEmpty() && !(buildFakeChildNodes && node.layoutNodes.isNotEmpty())) {
        parentNode.children.addAll(node.children)
        if (node.id > UNDEFINED_ID) {
          // If multiple siblings with a render ids are dropped:
          // Ignore them all. And delegate the drawing to a parent in the inspector.
          id = if (id == null) node.id else UNDEFINED_ID
        }
      } else {
        node.id = if (node.id != UNDEFINED_ID) node.id else --generatedId
        val withSemantics = node.packageHash !in systemPackages
        val resultNode = node.build(withSemantics)
        // TODO: replace getOrPut with putIfAbsent which requires API level 24
        node.layoutNodes.forEach { claimedNodes.getOrPut(it) { resultNode } }
        parentNode.children.add(resultNode)
        if (withSemantics) {
          node.mergedSemantics.clear()
          node.unmergedSemantics.clear()
        }
      }
      if (node.bounds != null && parentNode.box == node.box) {
        parentNode.bounds = node.bounds
      }
      parentNode.layoutNodes.addAll(node.layoutNodes)
      parentNode.mergedSemantics.addAll(node.mergedSemantics)
      parentNode.unmergedSemantics.addAll(node.unmergedSemantics)
      release(node)
    }
    val nodeId = id
    parentNode.id = if (parentNode.id <= UNDEFINED_ID && nodeId != null) nodeId else parentNode.id
  }

  @OptIn(InternalComposeUiApi::class)
  private fun parse(
      group: CompositionGroup,
      context: SourceContext,
      children: List<MutableInspectorNode>
  ): MutableInspectorNode {
    val node = newNode()
    node.name = context.name ?: ""
    node.key = group.key as? Int ?: 0
    node.inlined = context.isInline

    // If this node is associated with an android View, set the node's viewId to point to
    // the hosted view. We use the parent's uniqueDrawingId since the interopView returned here
    // will be the view itself, but we want to use the `AndroidViewHolder` that hosts the view
    // instead of the view directly.
    (group.node as? InteroperableComposeUiNode?)?.getInteropView()?.let { interopView ->
      (interopView.parent as? View)?.uniqueDrawingId?.let { viewId -> node.viewId = viewId }
    }

    val layoutInfo = group.node as? LayoutInfo
    if (layoutInfo != null) {
      return parseLayoutInfo(layoutInfo, context, node)
    }
    if (unwantedOutsideWindow(node, children)) {
      return markUnwanted(group, context, node)
    }
    node.box = context.bounds.emptyCheck()
    if (unwantedName(node.name) || (node.box == emptyBox && !subCompositions.capturing)) {
      return markUnwanted(group, context, node)
    }
    parseCallLocation(node, context.location)
    if (isHiddenSystemNode(node)) {
      return markUnwanted(group, context, node)
    }
    node.anchorId = anchorMap[group.identity]
    node.id = syntheticId(node.anchorId)
    if (includeAllParameters) {
      addParameters(context, node)
    }
    return node
  }

  private fun IntRect.emptyCheck(): IntRect = if (left >= right && top >= bottom) emptyBox else this

  private fun IntRect.inWindow(): Boolean =
      !(left > windowSize.width || right < 0 || top > windowSize.height || bottom < 0)

  private fun IntRect.union(other: IntRect): IntRect {
    if (this == outsideBox) return other else if (other == outsideBox) return this

    return IntRect(
        left = min(left, other.left),
        top = min(top, other.top),
        bottom = max(bottom, other.bottom),
        right = max(right, other.right))
  }

  private fun parseLayoutInfo(
      layoutInfo: LayoutInfo,
      context: SourceContext,
      node: MutableInspectorNode
  ): MutableInspectorNode {
    val box = context.bounds
    val size = box.size.toSize()
    val coordinates = layoutInfo.coordinates
    val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
    val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
    val bottomRight = toIntOffset(coordinates.localToWindow(Offset(size.width, size.height)))
    val bottomLeft = toIntOffset(coordinates.localToWindow(Offset(0f, size.height)))
    var bounds: QuadBounds? = null

    if (topLeft.x != box.left ||
        topLeft.y != box.top ||
        topRight.x != box.right ||
        topRight.y != box.top ||
        bottomRight.x != box.right ||
        bottomRight.y != box.bottom ||
        bottomLeft.x != box.left ||
        bottomLeft.y != box.bottom) {
      bounds =
          QuadBounds(
              topLeft.x,
              topLeft.y,
              topRight.x,
              topRight.y,
              bottomRight.x,
              bottomRight.y,
              bottomLeft.x,
              bottomLeft.y,
          )
    }
    if (!includeNodesOutsizeOfWindow) {
      // Ignore this node if the bounds are completely outside the window
      node.outerBox = bounds?.outerBox ?: box
      if (!node.outerBox.inWindow()) {
        return node
      }
    }

    node.box = box.emptyCheck()
    node.bounds = bounds
    node.layoutNodes.add(layoutInfo)
    val modifierInfo = layoutInfo.getModifierInfo()

    val unmergedSemantics = unmergedSemanticsMap[layoutInfo.semanticsId]
    if (unmergedSemantics != null) {
      node.unmergedSemantics.addAll(unmergedSemantics)
    }

    val mergedSemantics = semanticsMap[layoutInfo.semanticsId]
    if (mergedSemantics != null) {
      node.mergedSemantics.addAll(mergedSemantics)
    }

    node.id =
        modifierInfo
            .asSequence()
            .map { it.extra }
            .filterIsInstance<GraphicLayerInfo>()
            .map { it.layerId }
            .firstOrNull()
            ?: UNDEFINED_ID

    return node
  }

  private fun syntheticId(anchorId: Int): Long {
    if (anchorId == NO_ANCHOR_ID) {
      return UNDEFINED_ID
    }
    // The anchorId is an Int
    return anchorId.toLong() - Int.MAX_VALUE.toLong() + RESERVED_FOR_GENERATED_IDS
  }

  private fun belongsToView(layoutNodes: List<LayoutInfo>, view: View): Boolean =
      layoutNodes
          .asSequence()
          .flatMap { node ->
            node
                .getModifierInfo()
                .asSequence()
                .map { it.extra }
                .filterIsInstance<GraphicLayerInfo>()
                .map { it.ownerViewId }
          }
          .contains(view.uniqueDrawingId)

  private fun addParameters(context: SourceContext, node: MutableInspectorNode) {
    context.parameters.forEach {
      val castedValue = castValue(it)
      node.parameters.add(RawParameter(it.name, castedValue))
    }
  }

  private fun castValue(parameter: ParameterInformation): Any? {
    val value = parameter.value ?: return null
    if (parameter.inlineClass == null || !isPrimitive(value.javaClass)) return value
    return inlineClassConverter.castParameterValue(parameter.inlineClass, value)
  }

  private fun isPrimitive(cls: Class<*>): Boolean = cls.kotlin.javaPrimitiveType != null

  private fun toIntOffset(offset: Offset): IntOffset =
      IntOffset(offset.x.roundToInt(), offset.y.roundToInt())

  private fun markUnwanted(
      group: CompositionGroup,
      context: SourceContext,
      node: MutableInspectorNode
  ): MutableInspectorNode =
      when (node.name) {
        "rememberCompositionContext" -> subCompositions.rememberCompositionContext(node, context)
        "remember" -> subCompositions.remember(node, group)
        else -> node.apply { markUnwanted() }
      }

  private fun parseCallLocation(node: MutableInspectorNode, location: SourceLocation?) {
    val fileName = location?.sourceFile ?: return
    node.fileName = fileName
    node.packageHash = location.packageHash
    node.lineNumber = location.lineNumber
    node.offset = location.offset
    node.length = location.length
  }

  private fun isHiddenSystemNode(node: MutableInspectorNode): Boolean =
      node.packageHash in systemPackages && hideSystemNodes

  private fun unwantedName(name: String): Boolean =
      name.isEmpty() || name.startsWith("remember") || name in unwantedCalls

  private fun unwantedOutsideWindow(
      node: MutableInspectorNode,
      children: List<MutableInspectorNode>
  ): Boolean {
    if (includeNodesOutsizeOfWindow) {
      return false
    }
    node.outerBox =
        if (children.isEmpty()) outsideBox
        else children.map { g -> g.outerBox }.reduce { acc, box -> box.union(acc) }
    return !node.outerBox.inWindow()
  }

  private fun newNode(): MutableInspectorNode =
      if (cache.isNotEmpty()) cache.pop() else MutableInspectorNode()

  private fun newNode(copyFrom: InspectorNode): MutableInspectorNode =
      newNode().shallowCopy(copyFrom)

  private fun release(node: MutableInspectorNode) {
    node.reset()
    cache.add(node)
  }

  private fun buildAndRelease(node: MutableInspectorNode): InspectorNode {
    val result = node.build()
    release(node)
    return result
  }

  /**
   * Keep track of sub-composition roots.
   *
   * Examples:
   * - Popup, Dialog: When one of these is open an extra Android Window is created with its own
   *   AndroidComposeView. The contents of the Composable is a sub-composition that will be computed
   *   by calling convert.
   *
   *   The Popup/Dialog composable itself, and a few helping composables (the root) will not be
   *   included in the SlotTree with the contents, instead these composables will be found in the
   *   SlotTree for the main app and they all have empty sizes. The aim is to collect these
   *   sub-composition roots such that they can be added to the [InspectorNode]s of the contents.
   * - AndroidView: When this is used in a compose app we will see a similar pattern in the SlotTree
   *   except there isn't a sub-composition to stitch in. But we need to collect the view id
   *   separately from the "AndroidView" node itself.
   */
  private inner class SubCompositionRoots {
    /** Set to true when the nodes found should be added to a sub-composition root */
    var capturing = false
      private set

    /** The `uniqueDrawingId` of the `AndroidComposeView` that owns the root being captured */
    private var ownerView = UNDEFINED_ID

    /** The node that represent the root of the sub-composition */
    private var rootNode: MutableInspectorNode? = null

    /** The depth of the parse tree the [rootNode] was found at */
    private var rootNodeDepth = 0

    /** Last captured view that is believed to be an embbed View under an AndroidView node */
    private var androidView = UNDEFINED_ID

    /**
     * The sub-composition roots found.
     *
     * Map from View owner to a pair of [InspectorNode] indicating the actual root, and the node
     * where the content should be stitched in.
     */
    private val found = mutableMapOf<Long, InspectorNode>()

    /** Call this before converting a SlotTree for an AndroidComposeView */
    fun clear() {
      capturing = false
      ownerView = UNDEFINED_ID
      rootNode?.markUnwanted()
      rootNode?.id = UNDEFINED_ID
      rootNode = null
      rootNodeDepth = 0
    }

    /** Call this when starting converting a new set of windows */
    fun resetAccumulativeState() {
      found.clear()
    }

    /**
     * When a "rememberCompositionContext" is found in the slot tree, it indicates that a
     * sub-composition was started. We should capture all parent nodes with an empty size as the
     * "root" of the sub-composition.
     */
    fun rememberCompositionContext(
        node: MutableInspectorNode,
        context: SourceContext
    ): MutableInspectorNode {
      if (capturing) {
        save()
      }
      capturing = true
      rootNode = node
      rootNodeDepth = context.depth
      node.id = PLACEHOLDER_ID
      return node
    }

    /**
     * When "remember" is found in the slot tree and we are currently capturing, the data of the
     * [group] may contain the owner of the sub-composition.
     */
    fun remember(node: MutableInspectorNode, group: CompositionGroup): MutableInspectorNode {
      node.markUnwanted()
      if (!capturing) {
        return node
      }
      val root = findSingleRootInGroupData(group) ?: return node

      val view = root.subCompositionView
      if (view != null) {
        val composeOwner = if (view.childCount == 1) view.getChildAt(0) else return node
        ownerView = composeOwner.uniqueDrawingId
      } else {
        androidView = root.viewRoot?.uniqueDrawingId ?: UNDEFINED_ID
        // Store the viewRoot such that we can move the View under the compose node
        // in Studio. We do not need to capture the Groups found for this case, so
        // we call "reset" here to stop capturing.
        clear()
      }
      return node
    }

    private fun findSingleRootInGroupData(group: CompositionGroup): ViewRootForInspector? {
      group.data.filterIsInstance<ViewRootForInspector>().singleOrNull()?.let {
        return it
      }
      val refs = group.data.filterIsInstance<Ref<*>>().map { it.value }
      return refs.filterIsInstance<ViewRootForInspector>().singleOrNull()
    }

    /** Capture the top node of the sub-composition root until a non empty node is found. */
    fun captureNode(node: MutableInspectorNode, context: SourceContext) {
      if (!capturing) {
        return
      }
      if (node.box != emptyBox) {
        save()
        return
      }
      val depth = context.depth
      if (depth < rootNodeDepth) {
        rootNode = node
        rootNodeDepth = depth
      }
    }

    fun latestViewId(): Long {
      val id = androidView
      androidView = UNDEFINED_ID
      return id
    }

    /** If a sub-composition root has been captured, save it now. */
    private fun save() {
      val node = rootNode
      if (node != null && ownerView != UNDEFINED_ID) {
        found[ownerView] = node.build()
      }
      node?.markUnwanted()
      node?.id = UNDEFINED_ID
      node?.children?.clear()
      clear()
    }

    /**
     * Add the root of the sub-composition to the found tree.
     *
     * If a root is not found for this [owner] or if the stitching fails just return [nodes].
     */
    fun addRoot(owner: View, nodes: List<InspectorNode>): List<InspectorNode> {
      val root = found[owner.uniqueDrawingId] ?: return nodes
      val box = IntRect(0, 0, owner.width, owner.height)
      val info = StitchInfo(nodes, box)
      val result = listOf(stitch(root, info))
      return if (info.added) result else nodes
    }

    private fun stitch(node: InspectorNode, info: StitchInfo): InspectorNode {
      val children = node.children.map { stitch(it, info) }
      val index = children.indexOfFirst { it.id == PLACEHOLDER_ID }
      val newNode = newNode()
      newNode.shallowCopy(node)
      newNode.children.clear()
      if (index < 0) {
        newNode.children.addAll(children)
      } else {
        newNode.children.addAll(children.subList(0, index))
        newNode.children.addAll(info.nodes)
        newNode.children.addAll(children.subList(index + 1, children.size))
        info.added = true
      }
      newNode.box = info.bounds
      return buildAndRelease(newNode)
    }
  }

  private class StitchInfo(
      /** The nodes found that should be stitched into a sub-composition root. */
      val nodes: List<InspectorNode>,

      /** The bounds of the View containing the sub-composition */
      val bounds: IntRect
  ) {
    /** Set this to true when the [nodes] have been added to a sub-composition root */
    var added: Boolean = false
  }
}


/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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 facebook.internal.androidx.compose.ui.inspection.inspector

/** Holds data representing a Composable parameter for the Layout Inspector. */
class NodeParameter
internal constructor(
    /** The name of the parameter. */
    val name: String,

    /** The type of the parameter. */
    val type: ParameterType,

    /** The value of the parameter. */
    val value: Any?
) {
  /** Sub elements of the parameter. */
  val elements = mutableListOf<NodeParameter>()

  /** Reference to value parameter. */
  var reference: NodeParameterReference? = null

  /** The index into the composite parent parameter value. */
  var index = 0
}

/** The type of a parameter. */
enum class ParameterType {
  String,
  Boolean,
  Double,
  Float,
  Int32,
  Int64,
  Color,
  Resource,
  DimensionDp,
  DimensionSp,
  DimensionEm,
  Lambda,
  FunctionReference,
  Iterable,
}


/*
 * Copyright 2021 The Android Open Source Project
 *
 * 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 facebook.internal.androidx.compose.ui.inspection.inspector

import facebook.internal.androidx.compose.ui.inspection.util.asIntArray

/**
 * A reference to a parameter to a [NodeParameter]
 *
 * @param nodeId is the id of the node the parameter belongs to
 * @param anchorId is the anchor hash of the node the parameter belongs to
 * @param kind is this a reference to a normal, merged, or unmerged semantic parameter.
 * @param parameterIndex index into [InspectorNode.parameters], [InspectorNode.mergedSemantics], or
 *   [InspectorNode.unMergedSemantics]
 * @param indices are indices into the composite parameter
 */
class NodeParameterReference(
    val nodeId: Long,
    val anchorId: Int,
    val kind: ParameterKind,
    val parameterIndex: Int,
    val indices: IntArray
) {
  constructor(
      nodeId: Long,
      anchorId: Int,
      kind: ParameterKind,
      parameterIndex: Int,
      indices: List<Int>
  ) : this(nodeId, anchorId, kind, parameterIndex, indices.asIntArray())

  // For testing:
  override fun toString(): String {
    val suffix = if (indices.isNotEmpty()) ", ${indices.joinToString()}" else ""
    return "[$nodeId, $anchorId, $kind, $parameterIndex$suffix]"
  }
}

/** Identifies which kind of parameter the [NodeParameterReference] is a reference to. */
enum class ParameterKind {
  Normal,
  MergedSemantics,
  UnmergedSemantics
}


// WARNING: DO NOT EDIT THIS FILE MANUALLY. It's automatically generated by running:
//    frameworks/support/compose/ui/ui-inspection/generate-packages/generate_compose_packages.py -r
package facebook.internal.androidx.compose.ui.inspection.inspector

import androidx.annotation.VisibleForTesting
import kotlin.math.absoluteValue

@VisibleForTesting
fun packageNameHash(packageName: String) =
    packageName.fold(0) { hash, char -> hash * 31 + char.code }.absoluteValue

val systemPackages =
    setOf(
        -1,
        packageNameHash("androidx.compose.animation"),
        packageNameHash("androidx.compose.animation.core"),
        packageNameHash("androidx.compose.animation.graphics.vector"),
        packageNameHash("androidx.compose.desktop"),
        packageNameHash("androidx.compose.foundation"),
        packageNameHash("androidx.compose.foundation.gestures"),
        packageNameHash("androidx.compose.foundation.gestures.snapping"),
        packageNameHash("androidx.compose.foundation.interaction"),
        packageNameHash("androidx.compose.foundation.layout"),
        packageNameHash("androidx.compose.foundation.lazy"),
        packageNameHash("androidx.compose.foundation.lazy.grid"),
        packageNameHash("androidx.compose.foundation.lazy.layout"),
        packageNameHash("androidx.compose.foundation.lazy.staggeredgrid"),
        packageNameHash("androidx.compose.foundation.newtext.text"),
        packageNameHash("androidx.compose.foundation.newtext.text.copypasta"),
        packageNameHash("androidx.compose.foundation.newtext.text.copypasta.selection"),
        packageNameHash("androidx.compose.foundation.pager"),
        packageNameHash("androidx.compose.foundation.relocation"),
        packageNameHash("androidx.compose.foundation.text"),
        packageNameHash("androidx.compose.foundation.text.selection"),
        packageNameHash("androidx.compose.foundation.window"),
        packageNameHash("androidx.compose.material"),
        packageNameHash("androidx.compose.material.internal"),
        packageNameHash("androidx.compose.material.pullrefresh"),
        packageNameHash("androidx.compose.material.ripple"),
        packageNameHash("androidx.compose.material3"),
        packageNameHash("androidx.compose.material3.internal"),
        packageNameHash("androidx.compose.material3.windowsizeclass"),
        packageNameHash("androidx.compose.runtime"),
        packageNameHash("androidx.compose.runtime.livedata"),
        packageNameHash("androidx.compose.runtime.mock"),
        packageNameHash("androidx.compose.runtime.reflect"),
        packageNameHash("androidx.compose.runtime.rxjava2"),
        packageNameHash("androidx.compose.runtime.rxjava3"),
        packageNameHash("androidx.compose.runtime.saveable"),
        packageNameHash("androidx.compose.ui"),
        packageNameHash("androidx.compose.ui.awt"),
        packageNameHash("androidx.compose.ui.graphics.benchmark"),
        packageNameHash("androidx.compose.ui.graphics.vector"),
        packageNameHash("androidx.compose.ui.layout"),
        packageNameHash("androidx.compose.ui.platform"),
        packageNameHash("androidx.compose.ui.text"),
        packageNameHash("androidx.compose.ui.util"),
        packageNameHash("androidx.compose.ui.viewinterop"),
        packageNameHash("androidx.compose.ui.window"),
    )