/*
 * 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.util.Log
import android.view.View
import androidx.compose.runtime.internal.ComposableLambda
import androidx.compose.ui.AbsoluteAlignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.InspectableModifier
import androidx.compose.ui.platform.InspectableValue
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontListFontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.font.ResourceFont
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import facebook.internal.androidx.compose.ui.inspection.inspector.ParameterType.DimensionDp
import java.lang.reflect.Field
import java.lang.reflect.Modifier as JavaModifier
import java.util.IdentityHashMap
import kotlin.jvm.internal.FunctionReference
import kotlin.jvm.internal.Lambda
import kotlin.math.abs
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.full.allSuperclasses
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaGetter

private val reflectionScope: ReflectionScope = ReflectionScope()

/**
 * Factory of [NodeParameter]s.
 *
 * Each parameter value is converted to a user readable value.
 */
internal class ParameterFactory(private val inlineClassConverter: InlineClassConverter) {
  /** A map from known values to a user readable string representation. */
  private val valueLookup = mutableMapOf<Any, String>()

  /** The classes we have loaded constants from. */
  private val valuesLoaded = mutableSetOf<Class<*>>()

  /**
   * Do not load constant names from instances of these classes. We prefer showing the raw values of
   * Color and Dimensions.
   */
  private val ignoredClasses = listOf(Color::class.java, Dp::class.java)
  private var creatorCache: ParameterCreator? = null

  /**
   * Do not decompose instances or lookup constants from these package prefixes
   *
   * The following instances are known to contain self recursion:
   * - kotlinx.coroutines.flow.StateFlowImpl
   * - androidx.compose.ui.node.LayoutNode
   */
  private val ignoredPackagePrefixes =
      listOf("android.", "java.", "javax.", "kotlinx.", "androidx.compose.ui.node.")

  var density = Density(1.0f)

  init {
    val textDecorationCombination =
        TextDecoration.combine(listOf(TextDecoration.LineThrough, TextDecoration.Underline))
    valueLookup[textDecorationCombination] = "LineThrough+Underline"
    valueLookup[Color.Unspecified] = "Unspecified"
    valueLookup[RectangleShape] = "RectangleShape"
    valuesLoaded.add(Enum::class.java)
    valuesLoaded.add(Any::class.java)

    // AbsoluteAlignment is not found from an instance of BiasAbsoluteAlignment,
    // because Alignment has no file level class.
    reflectionScope.withReflectiveAccess {
      loadConstantsFromEnclosedClasses(AbsoluteAlignment::class.java)
    }
  }

  /**
   * Create a [NodeParameter] from the specified parameter [name] and [value].
   *
   * Attempt to convert the value to a user readable value. For now: return null when a conversion
   * is not possible/found.
   */
  fun create(
      rootId: Long,
      nodeId: Long,
      anchorId: Int,
      name: String,
      value: Any?,
      kind: ParameterKind,
      parameterIndex: Int,
      maxRecursions: Int,
      maxInitialIterableSize: Int
  ): NodeParameter {
    val creator = creatorCache ?: ParameterCreator()
    try {
      return reflectionScope.withReflectiveAccess {
        creator.create(
            rootId,
            nodeId,
            anchorId,
            name,
            value,
            kind,
            parameterIndex,
            maxRecursions,
            maxInitialIterableSize)
      }
    } finally {
      creatorCache = creator
    }
  }

  /**
   * Create/expand the [NodeParameter] specified by [reference].
   *
   * @param rootId is the root id of the specified [nodeId].
   * @param nodeId is the [InspectorNode.id] of the node the parameter belongs to.
   * @param anchorId is the [InspectorNode.anchorId] of the node the parameter belongs to.
   * @param name is the name of the [reference].parameterIndex'th parameter of the node.
   * @param value is the value of the [reference].parameterIndex'th parameter of the node.
   * @param startIndex is the index of the 1st wanted element of a List/Array.
   * @param maxElements is the max number of elements wanted from a List/Array.
   * @param maxRecursions is the max recursion into composite types starting from reference.
   * @param maxInitialIterableSize is the max number of elements wanted in new List/Array values.
   */
  fun expand(
      rootId: Long,
      nodeId: Long,
      anchorId: Int,
      name: String,
      value: Any?,
      reference: NodeParameterReference,
      startIndex: Int,
      maxElements: Int,
      maxRecursions: Int,
      maxInitialIterableSize: Int
  ): NodeParameter? {
    val creator = creatorCache ?: ParameterCreator()
    try {
      return reflectionScope.withReflectiveAccess {
        creator.expand(
            rootId,
            nodeId,
            anchorId,
            name,
            value,
            reference,
            startIndex,
            maxElements,
            maxRecursions,
            maxInitialIterableSize)
      }
    } finally {
      creatorCache = creator
    }
  }

  fun clearReferenceCache() {
    val creator = creatorCache ?: return
    creator.clearReferenceCache()
  }

  private fun loadConstantsFrom(javaClass: Class<*>) {
    if (valuesLoaded.contains(javaClass) ||
        ignoredPackagePrefixes.any { javaClass.name.startsWith(it) }) {
      return
    }
    val related = generateSequence(javaClass) { it.superclass }.plus(javaClass.interfaces)
    related.forEach { aClass ->
      val topClass = generateSequence(aClass) { safeEnclosingClass(it) }.last()
      loadConstantsFromEnclosedClasses(topClass)
      findPackageLevelClass(topClass)?.let { loadConstantsFromStaticFinal(it) }
    }
  }

  private fun safeEnclosingClass(klass: Class<*>): Class<*>? =
      try {
        klass.enclosingClass
      } catch (_: Error) {
        // Exceptions seen on API 23...
        null
      }

  private fun findPackageLevelClass(javaClass: Class<*>): Class<*>? =
      try {
        // Note: This doesn't work when @file.JvmName is specified
        Class.forName("${javaClass.name}Kt")
      } catch (ex: Throwable) {
        null
      }

  private fun loadConstantsFromEnclosedClasses(javaClass: Class<*>) {
    if (valuesLoaded.contains(javaClass)) {
      return
    }
    loadConstantsFromObjectInstance(javaClass.kotlin)
    loadConstantsFromStaticFinal(javaClass)
    valuesLoaded.add(javaClass)
    javaClass.declaredClasses.forEach { loadConstantsFromEnclosedClasses(it) }
  }

  /**
   * Load all constants from companion objects and singletons
   *
   * Exclude: primary types and types of ignoredClasses, open and lateinit vals.
   */
  private fun loadConstantsFromObjectInstance(kClass: KClass<*>) {
    try {
      val instance = kClass.objectInstance ?: return
      kClass.declaredMemberProperties
          .asSequence()
          .filter { it.isFinal && !it.isLateinit }
          .mapNotNull { constantValueOf(it, instance)?.let { key -> Pair(key, it.name) } }
          .filter { !ignoredValue(it.first) }
          .toMap(valueLookup)
    } catch (_: Throwable) {
      // KT-16479 :  kotlin reflection does currently not support packages and files.
      // We load top level values using Java reflection instead.
      // Ignore other reflection errors as well
    }
  }

  /**
   * Load all constants from top level values from Java.
   *
   * Exclude: primary types and types of ignoredClasses. Since this is Java, inline types will also
   * (unfortunately) be excluded.
   */
  private fun loadConstantsFromStaticFinal(javaClass: Class<*>) {
    try {
      javaClass.declaredMethods
          .asSequence()
          .filter {
            it.returnType != Void.TYPE &&
                JavaModifier.isStatic(it.modifiers) &&
                JavaModifier.isFinal(it.modifiers) &&
                !it.returnType.isPrimitive &&
                it.parameterTypes.isEmpty() &&
                it.name.startsWith("get")
          }
          .mapNotNull { javaClass.getDeclaredField(it.name.substring(3)) }
          .mapNotNull { constantValueOf(it)?.let { key -> Pair(key, it.name) } }
          .filter { !ignoredValue(it.first) }
          .toMap(valueLookup)
    } catch (_: ReflectiveOperationException) {
      // ignore reflection errors
    } catch (_: NoClassDefFoundError) {
      // ignore missing classes on lower level SDKs
    }
  }

  private fun constantValueOf(field: Field?): Any? =
      try {
        field?.isAccessible = true
        field?.get(null)
      } catch (_: ReflectiveOperationException) {
        // ignore reflection errors
        null
      }

  private fun constantValueOf(property: KProperty1<out Any, *>, instance: Any): Any? =
      try {
        val field = property.javaField
        field?.isAccessible = true
        inlineClassConverter.castParameterValue(inlineResultClass(property), field?.get(instance))
      } catch (_: ReflectiveOperationException) {
        // ignore reflection errors
        null
      }

  private fun inlineResultClass(property: KProperty1<out Any, *>): String? {
    // The Java getter name will be mangled if it contains parameters of an inline class.
    // The mangled part starts with a '-'.
    if (property.javaGetter?.name?.contains('-') == true) {
      return property.returnType.toString()
    }
    return null
  }

  private fun ignoredValue(value: Any?): Boolean =
      value == null ||
          ignoredClasses.any { ignored -> ignored.isInstance(value) } ||
          value::class.java.isPrimitive

  /** Convenience class for building [NodeParameter]s. */
  private inner class ParameterCreator {
    private var rootId = 0L
    private var nodeId = 0L
    private var anchorId = 0
    private var kind: ParameterKind = ParameterKind.Normal
    private var parameterIndex = 0
    private var maxRecursions = 0
    private var maxInitialIterableSize = 0
    private var recursions = 0
    private val valueIndex = mutableListOf<Int>()
    private val valueLazyReferenceMap = IdentityHashMap<Any, MutableList<NodeParameter>>()
    private val rootValueIndexCache =
        mutableMapOf<Long, IdentityHashMap<Any, NodeParameterReference>>()
    private var valueIndexMap = IdentityHashMap<Any, NodeParameterReference>()

    fun create(
        rootId: Long,
        nodeId: Long,
        anchorId: Int,
        name: String,
        value: Any?,
        kind: ParameterKind,
        parameterIndex: Int,
        maxRecursions: Int,
        maxInitialIterableSize: Int
    ): NodeParameter =
        try {
          setup(
              rootId, nodeId, anchorId, kind, parameterIndex, maxRecursions, maxInitialIterableSize)
          create(name, value, null) ?: createEmptyParameter(name)
        } finally {
          setup()
        }

    fun expand(
        rootId: Long,
        nodeId: Long,
        anchorId: Int,
        name: String,
        value: Any?,
        reference: NodeParameterReference,
        startIndex: Int,
        maxElements: Int,
        maxRecursions: Int,
        maxInitialIterableSize: Int
    ): NodeParameter? {
      setup(
          rootId,
          nodeId,
          anchorId,
          reference.kind,
          reference.parameterIndex,
          maxRecursions,
          maxInitialIterableSize)
      var parent: Pair<String, Any?>? = null
      var new = Pair(name, value)
      for (i in reference.indices) {
        parent = new
        new = find(new.first, new.second, i) ?: return null
      }
      recursions = 0
      valueIndex.addAll(reference.indices.asSequence())
      val parameter =
          if (startIndex == 0) {
            create(new.first, new.second, parent?.second)
          } else {
            createFromCompositeValue(new.first, new.second, parent?.second, startIndex, maxElements)
          }
      if (parameter == null && reference.indices.isEmpty()) {
        return createEmptyParameter(name)
      }
      return parameter
    }

    fun clearReferenceCache() {
      rootValueIndexCache.clear()
    }

    private fun setup(
        newRootId: Long = 0,
        newNodeId: Long = 0,
        newAnchorId: Int = 0,
        newKind: ParameterKind = ParameterKind.Normal,
        newParameterIndex: Int = 0,
        maxRecursions: Int = 0,
        maxInitialIterableSize: Int = 0
    ) {
      rootId = newRootId
      nodeId = newNodeId
      anchorId = newAnchorId
      kind = newKind
      parameterIndex = newParameterIndex
      this.maxRecursions = maxRecursions
      this.maxInitialIterableSize = maxInitialIterableSize
      recursions = 0
      valueIndex.clear()
      valueLazyReferenceMap.clear()
      valueIndexMap = rootValueIndexCache.getOrPut(newRootId) { IdentityHashMap() }
    }

    private fun create(name: String, value: Any?, parentValue: Any?): NodeParameter? {
      if (value == null) {
        return null
      }
      createFromSimpleValue(name, value)?.let {
        return it
      }

      val existing =
          valueIndexMap[value] ?: return createFromCompositeValue(name, value, parentValue)

      // Do not decompose an instance we already decomposed.
      // Instead reference the data that was already decomposed.
      return createReferenceToExistingValue(name, value, parentValue, existing)
    }

    private fun create(
        name: String,
        value: Any?,
        parentValue: Any?,
        specifiedIndex: Int = 0
    ): NodeParameter? = create(name, value, parentValue)?.apply { index = specifiedIndex }

    private fun createFromSimpleValue(name: String, value: Any?): NodeParameter? {
      if (value == null) {
        return null
      }
      createFromConstant(name, value)?.let {
        return it
      }
      return when (value) {
        is AnnotatedString -> NodeParameter(name, ParameterType.String, value.text)
        is BaselineShift -> createFromBaselineShift(name, value)
        is Boolean -> NodeParameter(name, ParameterType.Boolean, value)
        is ComposableLambda -> createFromCLambda(name, value)
        is Color -> NodeParameter(name, ParameterType.Color, value.toArgb())
        is Double -> NodeParameter(name, ParameterType.Double, value)
        is Dp -> NodeParameter(name, DimensionDp, value.value)
        is Enum<*> -> NodeParameter(name, ParameterType.String, value.toString())
        is Float -> NodeParameter(name, ParameterType.Float, value)
        is FunctionReference -> createFromFunctionReference(name, value)
        is FontListFontFamily -> createFromFontListFamily(name, value)
        is FontWeight -> NodeParameter(name, ParameterType.Int32, value.weight)
        is Int -> NodeParameter(name, ParameterType.Int32, value)
        is Lambda<*> -> createFromLambda(name, value)
        is Locale -> NodeParameter(name, ParameterType.String, value.toString())
        is Long -> NodeParameter(name, ParameterType.Int64, value)
        is SolidColor -> NodeParameter(name, ParameterType.Color, value.value.toArgb())
        is String -> NodeParameter(name, ParameterType.String, value)
        is TextUnit -> createFromTextUnit(name, value)
        is ImageVector -> createFromImageVector(name, value)
        is View -> NodeParameter(name, ParameterType.String, value.javaClass.simpleName)
        else -> null
      }
    }

    private fun createFromCompositeValue(
        name: String,
        value: Any?,
        parentValue: Any?,
        startIndex: Int = 0,
        maxElements: Int = maxInitialIterableSize
    ): NodeParameter? =
        when {
          value == null -> null
          value is Modifier -> createFromModifier(name, value)
          value is InspectableValue -> createFromInspectableValue(name, value)
          value is Sequence<*> -> createFromSequence(name, value, value, startIndex, maxElements)
          value is Map<*, *> ->
              createFromSequence(name, value, value.asSequence(), startIndex, maxElements)
          value is Map.Entry<*, *> -> createFromMapEntry(name, value, parentValue)
          value is Iterable<*> ->
              createFromSequence(name, value, value.asSequence(), startIndex, maxElements)
          value.javaClass.isArray -> createFromArray(name, value, startIndex, maxElements)
          value is Offset -> createFromOffset(name, value)
          value is Shadow -> createFromShadow(name, value)
          value is TextStyle -> createFromTextStyle(name, value)
          else -> createFromKotlinReflection(name, value)
        }

    private fun find(name: String, value: Any?, index: Int): Pair<String, Any?>? =
        when {
          value == null -> null
          value is Modifier -> findFromModifier(name, value, index)
          value is InspectableValue -> findFromInspectableValue(value, index)
          value is Sequence<*> -> findFromSequence(value, index)
          value is Map<*, *> -> findFromSequence(value.asSequence(), index)
          value is Map.Entry<*, *> -> findFromMapEntry(value, index)
          value is Iterable<*> -> findFromSequence(value.asSequence(), index)
          value.javaClass.isArray -> findFromArray(value, index)
          value is Offset -> findFromOffset(value, index)
          value is Shadow -> findFromShadow(value, index)
          value is TextStyle -> findFromTextStyle(value, index)
          else -> findFromKotlinReflection(value, index)
        }

    private fun createRecursively(
        name: String,
        value: Any?,
        parentValue: Any?,
        index: Int
    ): NodeParameter? {
      valueIndex.add(index)
      recursions++
      val parameter = create(name, value, parentValue)?.apply { this.index = index }
      recursions--
      valueIndex.removeLast()
      return parameter
    }

    private fun shouldRecurseDeeper(): Boolean = recursions < maxRecursions

    /**
     * Create a [NodeParameter] as a reference to a previously created parameter.
     *
     * Use [createFromCompositeValue] to compute the data type and top value, however no children
     * will be created. Instead a reference to the previously created parameter is specified.
     */
    private fun createReferenceToExistingValue(
        name: String,
        value: Any?,
        parentValue: Any?,
        ref: NodeParameterReference
    ): NodeParameter? {
      val remember = recursions
      recursions = maxRecursions
      val parameter = createFromCompositeValue(name, value, parentValue)?.apply { reference = ref }
      recursions = remember
      return parameter
    }

    /**
     * Returns `true` if the value can be mapped to a [NodeParameter].
     *
     * Composite values should NOT be added to the [valueIndexMap] since we do not intend to include
     * this parameter in the response.
     */
    private fun hasMappableValue(value: Any?): Boolean {
      if (value == null) {
        return false
      }
      if (valueIndexMap.containsKey(value)) {
        return true
      }
      val remember = recursions
      recursions = maxRecursions
      val parameter = create("p", value, null)
      recursions = remember
      valueIndexMap.remove(value)
      return parameter != null
    }

    /**
     * Store the reference of this [NodeParameter] by its [value]
     *
     * If the value is seen in other parameter values again, there is no need to create child
     * parameters a second time.
     */
    private fun NodeParameter.store(value: Any?): NodeParameter {
      if (value != null) {
        val index = valueIndexToReference()
        valueIndexMap[value] = index
      }
      return this
    }

    /** Remove the [value] of this [NodeParameter] if there are no child elements. */
    private fun NodeParameter.removeIfEmpty(value: Any?): NodeParameter {
      if (value != null) {
        if (elements.isEmpty()) {
          valueIndexMap.remove(value)
        }
        val reference = valueIndexMap[value]
        valueLazyReferenceMap.remove(value)?.forEach { it.reference = reference }
      }
      return this
    }

    /**
     * Delay the creation of all child parameters of this composite parameter.
     *
     * If the child parameters are omitted because of [maxRecursions], store the parameter itself
     * such that its reference can be updated if it turns out that child [NodeParameter]s need to be
     * generated later.
     */
    private fun NodeParameter.withChildReference(value: Any): NodeParameter {
      valueLazyReferenceMap.getOrPut(value, { mutableListOf() }).add(this)
      reference = valueIndexToReference()
      return this
    }

    private fun valueIndexToReference(): NodeParameterReference =
        NodeParameterReference(nodeId, anchorId, kind, parameterIndex, valueIndex)

    private fun createEmptyParameter(name: String): NodeParameter =
        NodeParameter(name, ParameterType.String, "")

    private fun createFromArray(
        name: String,
        value: Any,
        startIndex: Int,
        maxElements: Int
    ): NodeParameter? {
      val sequence = arrayToSequence(value) ?: return null
      return createFromSequence(name, value, sequence, startIndex, maxElements)
    }

    private fun findFromArray(value: Any, index: Int): Pair<String, Any?>? {
      val sequence = arrayToSequence(value) ?: return null
      return findFromSequence(sequence, index)
    }

    private fun arrayToSequence(value: Any): Sequence<*>? =
        when (value) {
          is Array<*> -> value.asSequence()
          is ByteArray -> value.asSequence()
          is IntArray -> value.asSequence()
          is LongArray -> value.asSequence()
          is FloatArray -> value.asSequence()
          is DoubleArray -> value.asSequence()
          is BooleanArray -> value.asSequence()
          is CharArray -> value.asSequence()
          else -> null
        }

    private fun createFromBaselineShift(name: String, value: BaselineShift): NodeParameter {
      val converted =
          when (value.multiplier) {
            BaselineShift.None.multiplier -> "None"
            BaselineShift.Subscript.multiplier -> "Subscript"
            BaselineShift.Superscript.multiplier -> "Superscript"
            else -> return NodeParameter(name, ParameterType.Float, value.multiplier)
          }
      return NodeParameter(name, ParameterType.String, converted)
    }

    private fun createFromCLambda(name: String, value: ComposableLambda): NodeParameter? =
        try {
          val lambda =
              value.javaClass.getDeclaredField("_block").apply { isAccessible = true }.get(value)
          NodeParameter(name, ParameterType.Lambda, arrayOf<Any?>(lambda))
        } catch (_: Throwable) {
          null
        }

    private fun createFromConstant(name: String, value: Any): NodeParameter? {
      loadConstantsFrom(value.javaClass)
      return valueLookup[value]?.let { NodeParameter(name, ParameterType.String, it) }
    }

    // For now: select ResourceFontFont closest to W400 and Normal, and return the resId
    private fun createFromFontListFamily(name: String, value: FontListFontFamily): NodeParameter? =
        findBestResourceFont(value)?.let { NodeParameter(name, ParameterType.Resource, it.resId) }

    private fun createFromFunctionReference(name: String, value: FunctionReference): NodeParameter =
        NodeParameter(name, ParameterType.FunctionReference, arrayOf<Any>(value, value.name))

    private fun createFromKotlinReflection(name: String, value: Any): NodeParameter? {
      val simpleName = value::class.simpleName
      val properties = lookup(value) ?: return null
      val parameter = NodeParameter(name, ParameterType.String, simpleName)
      return when {
        properties.isEmpty() -> parameter
        !shouldRecurseDeeper() -> parameter.withChildReference(value)
        else -> {
          val elements = parameter.store(value).elements
          properties.values.mapIndexedNotNullTo(elements) { index, part ->
            createRecursively(part.name, valueOf(part, value), value, index)
          }
          parameter.removeIfEmpty(value)
        }
      }
    }

    private fun findFromKotlinReflection(value: Any, index: Int): Pair<String, Any?>? {
      val properties = lookup(value)?.entries?.iterator()?.asSequence() ?: return null
      val element = properties.elementAtOrNull(index)?.value ?: return null
      return Pair(element.name, valueOf(element, value))
    }

    private fun lookup(value: Any): Map<String, KProperty<*>>? {
      val kClass = value::class
      val simpleName = kClass.simpleName
      val qualifiedName = kClass.qualifiedName
      if (simpleName == null ||
          qualifiedName == null ||
          ignoredPackagePrefixes.any { qualifiedName.startsWith(it) }) {
        // Exit without creating a parameter for:
        // - internal synthetic classes
        // - certain android packages
        return null
      }
      return try {
        sequenceOf(kClass)
            .plus(kClass.allSuperclasses.asSequence())
            .flatMap { it.declaredMemberProperties.asSequence() }
            .associateBy { it.name }
      } catch (ex: Throwable) {
        Log.w("Compose", "Could not decompose ${kClass.simpleName}", ex)
        null
      }
    }

    private fun valueOf(property: KProperty<*>, instance: Any): Any? =
        try {
          property.isAccessible = true
          // Bug in kotlin reflection API: if the type is a nullable inline type with a null
          // value, we get an IllegalArgumentException in this line:
          property.getter.call(instance)
        } catch (ex: Throwable) {
          // TODO: Remove this warning since this is expected with nullable inline types
          Log.w("Compose", "Could not get value of ${property.name}")
          null
        }

    private fun createFromInspectableValue(name: String, value: InspectableValue): NodeParameter {
      val tempValue = value.valueOverride ?: ""
      val parameterName = name.ifEmpty { value.nameFallback } ?: "element"
      val parameterValue = if (tempValue is InspectableValue) "" else tempValue
      val parameter =
          createFromSimpleValue(parameterName, parameterValue)
              ?: NodeParameter(parameterName, ParameterType.String, "")
      if (!shouldRecurseDeeper()) {
        return parameter.withChildReference(value)
      }
      val elements = parameter.store(value).elements
      value.inspectableElements.mapIndexedNotNullTo(elements) { index, element ->
        createRecursively(element.name, element.value, value, index)
      }
      return parameter.removeIfEmpty(value)
    }

    private fun findFromInspectableValue(value: InspectableValue, index: Int): Pair<String, Any?>? {
      val elements = value.inspectableElements.toList()
      if (index !in elements.indices) {
        return null
      }
      val element = elements[index]
      return Pair(element.name, element.value)
    }

    private fun createFromMapEntry(
        name: String,
        entry: Map.Entry<*, *>,
        parentValue: Any?
    ): NodeParameter? {
      val key = createRecursively("key", entry.key, entry, 0) ?: return null
      val value = createRecursively("value", entry.value, entry, 1) ?: return null
      val keyName = (key.value?.toString() ?: "").ifEmpty { "entry" }
      val valueName = value.value?.toString()?.ifEmpty { null }
      val nodeName = if (parentValue is Map<*, *>) "[$keyName]" else name
      return NodeParameter(nodeName, ParameterType.String, valueName).apply {
        elements.add(key)
        elements.add(value)
      }
    }

    private fun findFromMapEntry(entry: Map.Entry<*, *>, index: Int): Pair<String, Any?>? =
        when (index) {
          0 -> Pair("key", entry.key)
          1 -> Pair("value", entry.value)
          else -> null
        }

    private fun createFromSequence(
        name: String,
        value: Any,
        sequence: Sequence<*>,
        startIndex: Int,
        maxElements: Int
    ): NodeParameter {
      val parameter = NodeParameter(name, ParameterType.Iterable, sequenceName(value))
      return when {
        !sequence.any() -> parameter
        !shouldRecurseDeeper() -> parameter.withChildReference(value)
        else -> {
          val elements = parameter.store(value).elements
          val rest = sequence.drop(startIndex).iterator()
          var index = startIndex
          while (rest.hasNext() && elements.size < maxElements) {
            createRecursively("[$index]", rest.next(), value, index)?.let { elements.add(it) }
            index++
          }
          while (rest.hasNext()) {
            if (hasMappableValue(rest.next())) {
              parameter.withChildReference(value)
              break
            }
          }
          parameter.removeIfEmpty(value)
        }
      }
    }

    private fun findFromSequence(value: Sequence<*>, index: Int): Pair<String, Any?>? {
      val element = value.elementAtOrNull(index) ?: return null
      return Pair("[$index]", element)
    }

    private fun sequenceName(value: Any): String =
        when (value) {
          is Array<*> -> "Array[${value.size}]"
          is ByteArray -> "ByteArray[${value.size}]"
          is IntArray -> "IntArray[${value.size}]"
          is LongArray -> "LongArray[${value.size}]"
          is FloatArray -> "FloatArray[${value.size}]"
          is DoubleArray -> "DoubleArray[${value.size}]"
          is BooleanArray -> "BooleanArray[${value.size}]"
          is CharArray -> "CharArray[${value.size}]"
          is List<*> -> "List[${value.size}]"
          is Set<*> -> "Set[${value.size}]"
          is Map<*, *> -> "Map[${value.size}]"
          is Collection<*> -> "Collection[${value.size}]"
          is Iterable<*> -> "Iterable"
          else -> "Sequence"
        }

    private fun createFromLambda(name: String, value: Lambda<*>): NodeParameter =
        NodeParameter(name, ParameterType.Lambda, arrayOf<Any>(value))

    private fun createFromModifier(name: String, value: Modifier): NodeParameter? =
        when {
          name.isNotEmpty() -> {
            val parameter = NodeParameter(name, ParameterType.String, "")
            val modifiers = unwrap(value)
            when {
              modifiers.isEmpty() -> parameter
              !shouldRecurseDeeper() -> parameter.withChildReference(value)
              else -> {
                val elements = parameter.elements
                modifiers.mapIndexedNotNullTo(elements) { index, element ->
                  createRecursively("", element, value, index)
                }
                parameter.store(value).removeIfEmpty(value)
              }
            }
          }
          value is InspectableValue -> createFromInspectableValue(name, value)
          else -> null
        }

    private fun unwrap(value: Modifier): List<Modifier.Element> {
      val collector = ModifierCollector()
      value.foldIn(collector) { acc, m -> acc.apply { add(m) } }
      return collector.modifiers
    }

    private fun findFromModifier(name: String, value: Modifier, index: Int): Pair<String, Any?>? =
        when {
          name.isNotEmpty() -> {
            val modifiers = unwrap(value)
            if (index in modifiers.indices) Pair("", modifiers[index]) else null
          }
          value is InspectableValue -> findFromInspectableValue(value, index)
          else -> null
        }

    private fun createFromOffset(name: String, value: Offset): NodeParameter {
      val parameter = NodeParameter(name, ParameterType.String, Offset::class.java.simpleName)
      val elements = parameter.elements
      val x = with(density) { value.x.toDp().value }
      val y = with(density) { value.y.toDp().value }
      elements.add(NodeParameter("x", DimensionDp, x))
      elements.add(NodeParameter("y", DimensionDp, y).apply { index = 1 })
      return parameter
    }

    private fun findFromOffset(value: Offset, index: Int): Pair<String, Any?>? =
        when (index) {
          0 -> Pair("x", with(density) { value.x.toDp() })
          1 -> Pair("y", with(density) { value.y.toDp() })
          else -> null
        }

    // Special handling of blurRadius: convert to dp:
    private fun createFromShadow(name: String, value: Shadow): NodeParameter? {
      val parameter = createFromKotlinReflection(name, value) ?: return null
      val elements = parameter.elements
      val index = elements.indexOfFirst { it.name == "blurRadius" }
      if (index >= 0) {
        val existing = elements[index]
        val blurRadius = with(density) { value.blurRadius.toDp().value }
        elements[index] = NodeParameter("blurRadius", DimensionDp, blurRadius)
        elements[index].index = existing.index
      }
      return parameter
    }

    private fun findFromShadow(value: Shadow, index: Int): Pair<String, Any?>? {
      val result = findFromKotlinReflection(value, index)
      if (result == null || result.first != "blurRadius") {
        return result
      }
      return Pair("blurRadius", with(density) { value.blurRadius.toDp() })
    }

    // Temporary handling of TextStyle: remove when TextStyle implements InspectableValue
    // Hide: paragraphStyle, spanStyle, platformStyle, lineHeightStyle
    private fun createFromTextStyle(name: String, value: TextStyle): NodeParameter? {
      val parameter = NodeParameter(name, ParameterType.String, TextStyle::class.java.simpleName)
      val elements = parameter.elements
      create("color", value.color, value)?.let { elements.add(it) }
      create("fontSize", value.fontSize, value, 1)?.let { elements.add(it) }
      create("fontWeight", value.fontWeight, value, 2)?.let { elements.add(it) }
      create("fontStyle", value.fontStyle, value, 3)?.let { elements.add(it) }
      create("fontSynthesis", value.fontSynthesis, value, 4)?.let { elements.add(it) }
      create("fontFamily", value.fontFamily, value, 5)?.let { elements.add(it) }
      create("fontFeatureSettings", value.fontFeatureSettings, value, 6)?.let { elements.add(it) }
      create("letterSpacing", value.letterSpacing, value, 7)?.let { elements.add(it) }
      create("baselineShift", value.baselineShift, value, 8)?.let { elements.add(it) }
      create("textGeometricTransform", value.textGeometricTransform, value, 9)?.let {
        elements.add(it)
      }
      create("localeList", value.localeList, value, 10)?.let { elements.add(it) }
      create("background", value.background, value, 11)?.let { elements.add(it) }
      create("textDecoration", value.textDecoration, value, 12)?.let { elements.add(it) }
      create("shadow", value.shadow, value, 13)?.let { elements.add(it) }
      create("textDirection", value.textDirection, value, 14)?.let { elements.add(it) }
      create("lineHeight", value.lineHeight, value, 15)?.let { elements.add(it) }
      create("textIndent", value.textIndent, value, 16)?.let { elements.add(it) }
      return parameter
    }

    private fun findFromTextStyle(value: TextStyle, index: Int): Pair<String, Any?>? =
        when (index) {
          0 -> Pair("color", value.color)
          1 -> Pair("fontSize", value.fontSize)
          2 -> Pair("fontWeight", value.fontWeight)
          3 -> Pair("fontStyle", value.fontStyle)
          4 -> Pair("fontSynthesis", value.fontSynthesis)
          5 -> Pair("fontFamily", value.fontFamily)
          6 -> Pair("fontFeatureSettings", value.fontFeatureSettings)
          7 -> Pair("letterSpacing", value.letterSpacing)
          8 -> Pair("baselineShift", value.baselineShift)
          9 -> Pair("textGeometricTransform", value.textGeometricTransform)
          10 -> Pair("localeList", value.localeList)
          11 -> Pair("background", value.background)
          12 -> Pair("textDecoration", value.textDecoration)
          13 -> Pair("shadow", value.shadow)
          14 -> Pair("textDirection", value.textDirection)
          15 -> Pair("lineHeight", value.lineHeight)
          16 -> Pair("textIndent", value.textIndent)
          else -> null
        }

    @Suppress("DEPRECATION")
    private fun createFromTextUnit(name: String, value: TextUnit): NodeParameter =
        when (value.type) {
          TextUnitType.Sp -> NodeParameter(name, ParameterType.DimensionSp, value.value)
          TextUnitType.Em -> NodeParameter(name, ParameterType.DimensionEm, value.value)
          else -> NodeParameter(name, ParameterType.String, "Unspecified")
        }

    private fun createFromImageVector(name: String, value: ImageVector): NodeParameter =
        NodeParameter(name, ParameterType.String, value.name)

    /**
     * Select a resource font among the font in the family to represent the font
     *
     * Prefer the font closest to [FontWeight.Normal] and [FontStyle.Normal]
     */
    private fun findBestResourceFont(value: FontListFontFamily): ResourceFont? =
        value.fonts.asSequence().filterIsInstance<ResourceFont>().minByOrNull {
          abs(it.weight.weight - FontWeight.Normal.weight) + it.style.value
        }
  }

  private class ModifierCollector {
    val modifiers = mutableListOf<Modifier.Element>()
    var start: InspectableModifier? = null

    fun add(element: Modifier.Element) =
        when {
          element == start?.end -> start = null
          start != null -> {}
          else -> {
            modifiers.add(element)
            start = element as? InspectableModifier
          }
        }
  }
}


/*
 * 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 android.annotation.SuppressLint
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import kotlin.jvm.internal.FunctionBase
import kotlin.jvm.internal.FunctionReference
import kotlin.jvm.internal.Lambda
import kotlin.jvm.internal.MutablePropertyReference0
import kotlin.jvm.internal.MutablePropertyReference1
import kotlin.jvm.internal.MutablePropertyReference2
import kotlin.jvm.internal.PropertyReference0
import kotlin.jvm.internal.PropertyReference1
import kotlin.jvm.internal.PropertyReference2
import kotlin.jvm.internal.Reflection
import kotlin.jvm.internal.ReflectionFactory
import kotlin.reflect.KClass
import kotlin.reflect.KClassifier
import kotlin.reflect.KDeclarationContainer
import kotlin.reflect.KFunction
import kotlin.reflect.KMutableProperty0
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KMutableProperty2
import kotlin.reflect.KProperty0
import kotlin.reflect.KProperty1
import kotlin.reflect.KProperty2
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.reflect.jvm.internal.ReflectionFactoryImpl

/**
 * Scope that allows to use jarjar-ed kotlin-reflect artifact that is shipped with inspector itself.
 *
 * Issue with kotlin-reflect. Many of reflective calls such as "foo::class" rely on static functions
 * defined in kotlin-stdlib's Reflection.java that delegate to ReflectionFactory. In order to
 * initialize that factory kotlin-stdlib statically detects presence or absence of kotlin-reflect in
 * classloader and chooses a factory accordingly. If there is no kotlin-reflect, very limited
 * version of ReflectionFactory is used.
 *
 * It is an issue for inspectors because they could be loaded after that factory is initialised, and
 * even if they are loaded before, they live in a separate child classloader, thus kotlin-reflect in
 * inspector wouldn't exist for kotlin-stdlib in app.
 *
 * First step to avoid the issue is using ReflectionFactoryImpl that is bundled with inspector. Code
 * for that would be fairly simple, for example instead of directly calling
 * `kClass.declaredMemberProperties`, correct instance of kClass should be obtained from factory:
 * `factory.getOrCreateKotlinClass(kClass.java).declaredMemberProperties`.
 *
 * That would work if code that works with correct KClass full implementation would never try to
 * access a default factory installed in Reflection.java. Unfortunately it is not true, it
 * eventually calls `CallableReference.getOwner()` in stdlib that uses default factory.
 *
 * As a result we have to replace the factory in Reflection.java. To avoid issues with user's code
 * factory that we setup is smart, by default it simply delegates to a factory that was previously
 * installed. Only within `reflectionScope.withReflectiveAccess{ }` factory from kotlin-reflect is
 * used.
 */
@SuppressLint("BanUncheckedReflection")
class ReflectionScope {

  companion object {
    init {
      allowHiddenApi()
    }
  }

  private val scopedReflectionFactory = installScopedReflectionFactory()

  /** Runs `block` with access to kotlin-reflect. */
  fun <T> withReflectiveAccess(block: () -> T): T {
    return scopedReflectionFactory.withMainFactory(block)
  }

  private fun installScopedReflectionFactory(): ScopedReflectionFactory {
    val factoryField = Reflection::class.java.getDeclaredField("factory")
    factoryField.isAccessible = true
    val original: ReflectionFactory = factoryField.get(null) as ReflectionFactory
    val modifiersField: Field = Field::class.java.getDeclaredField("accessFlags")
    modifiersField.isAccessible = true
    // make field non-final ðŸ˜… b/179685774 https://youtrack.jetbrains.com/issue/KT-44795
    modifiersField.setInt(factoryField, factoryField.modifiers and Modifier.FINAL.inv())
    val scopedReflectionFactory = ScopedReflectionFactory(original)
    factoryField.set(null, scopedReflectionFactory)
    return scopedReflectionFactory
  }
}

@SuppressLint("BanUncheckedReflection")
private fun allowHiddenApi() {
  try {
    val vmDebug = Class.forName("dalvik.system.VMDebug")
    val allowHiddenApiReflectionFrom =
        vmDebug.getDeclaredMethod("allowHiddenApiReflectionFrom", Class::class.java)
    allowHiddenApiReflectionFrom.invoke(null, ReflectionScope::class.java)
  } catch (e: Throwable) {
    // ignore failure, let's try to proceed without it
  }
}

private class ScopedReflectionFactory(
    private val original: ReflectionFactory,
) : ReflectionFactory() {
  private val mainFactory = ReflectionFactoryImpl()
  private val threadLocalFactory = ThreadLocal<ReflectionFactory>()

  fun <T> withMainFactory(block: () -> T): T {
    threadLocalFactory.set(mainFactory)
    try {
      return block()
    } finally {
      threadLocalFactory.set(null)
    }
  }

  val factory: ReflectionFactory
    get() = threadLocalFactory.get() ?: original

  override fun createKotlinClass(javaClass: Class<*>?): KClass<*> {
    return factory.createKotlinClass(javaClass)
  }

  override fun createKotlinClass(javaClass: Class<*>?, internalName: String?): KClass<*> {
    return factory.createKotlinClass(javaClass, internalName)
  }

  override fun getOrCreateKotlinPackage(
      javaClass: Class<*>?,
      moduleName: String?
  ): KDeclarationContainer {
    return factory.getOrCreateKotlinPackage(javaClass, moduleName)
  }

  override fun getOrCreateKotlinClass(javaClass: Class<*>?): KClass<*> {
    return factory.getOrCreateKotlinClass(javaClass)
  }

  override fun getOrCreateKotlinClass(javaClass: Class<*>?, internalName: String?): KClass<*> {
    return factory.getOrCreateKotlinClass(javaClass, internalName)
  }

  override fun renderLambdaToString(lambda: Lambda<*>?): String {
    return factory.renderLambdaToString(lambda)
  }

  override fun renderLambdaToString(lambda: FunctionBase<*>?): String {
    return factory.renderLambdaToString(lambda)
  }

  override fun function(f: FunctionReference?): KFunction<*> {
    return factory.function(f)
  }

  override fun property0(p: PropertyReference0?): KProperty0<*> {
    return factory.property0(p)
  }

  override fun mutableProperty0(p: MutablePropertyReference0?): KMutableProperty0<*> {
    return factory.mutableProperty0(p)
  }

  override fun property1(p: PropertyReference1?): KProperty1<*, *> {
    return factory.property1(p)
  }

  override fun mutableProperty1(p: MutablePropertyReference1?): KMutableProperty1<*, *> {
    return factory.mutableProperty1(p)
  }

  override fun property2(p: PropertyReference2?): KProperty2<*, *, *> {
    return factory.property2(p)
  }

  override fun mutableProperty2(p: MutablePropertyReference2?): KMutableProperty2<*, *, *> {
    return factory.mutableProperty2(p)
  }

  override fun typeOf(
      klass: KClassifier?,
      arguments: MutableList<KTypeProjection>?,
      isMarkedNullable: Boolean
  ): KType {
    return factory.typeOf(klass, arguments, isMarkedNullable)
  }

  override fun typeParameter(
      container: Any?,
      name: String?,
      variance: KVariance?,
      isReified: Boolean
  ): KTypeParameter {
    return factory.typeParameter(container, name, variance, isReified)
  }

  override fun setUpperBounds(typeParameter: KTypeParameter?, bounds: MutableList<KType>?) {
    factory.setUpperBounds(typeParameter, bounds)
  }
}


/*
 * Copyright 2022 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.util

import java.util.IdentityHashMap

const val NO_ANCHOR_ID = 0

/** A map of anchors with a unique id generator. */
class AnchorMap {
  private val anchorLookup = mutableMapOf<Int, Any>()
  private val idLookup = IdentityHashMap<Any, Int>()

  /** Return a unique id for the specified [anchor] instance. */
  operator fun get(anchor: Any?): Int =
      anchor?.let { idLookup.getOrPut(it) { generateUniqueId(it) } } ?: NO_ANCHOR_ID

  /** Return the anchor associated with a given unique anchor [id]. */
  operator fun get(id: Int): Any? = anchorLookup[id]

  private fun generateUniqueId(anchor: Any): Int {
    var id = anchor.hashCode()
    while (id == NO_ANCHOR_ID || anchorLookup.containsKey(id)) {
      id++
    }
    anchorLookup[id] = anchor
    return id
  }
}


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

private val EMPTY_INT_ARRAY = intArrayOf()

fun List<Int>.asIntArray() = if (isNotEmpty()) toIntArray() else EMPTY_INT_ARRAY


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

import android.os.Handler
import android.os.Looper
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future

object ThreadUtils {
  fun assertOnMainThread() {
    if (!Looper.getMainLooper().isCurrentThread) {
      error("This work is required on the main thread")
    }
  }

  fun assertOffMainThread() {
    if (Looper.getMainLooper().isCurrentThread) {
      error("This work is required off the main thread")
    }
  }

  /**
   * Run some logic on the main thread, returning a future that will contain any data computed by
   * and returned from the block.
   *
   * If this method is called from the main thread, it will run immediately.
   */
  fun <T> runOnMainThread(block: () -> T): Future<T> {
    return if (!Looper.getMainLooper().isCurrentThread) {
      val future = CompletableFuture<T>()
      Handler.createAsync(Looper.getMainLooper()).post { future.complete(block()) }
      future
    } else {
      CompletableFuture.completedFuture(block())
    }
  }
}


/*
 * 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.flipper.plugins.leakcanary2

import com.facebook.flipper.android.AndroidFlipperClient
import leakcanary.EventListener
import shark.HeapAnalysis
import shark.HeapAnalysisSuccess

class FlipperLeakEventListener : EventListener {
  private val leaks: MutableList<Leak> = mutableListOf()

  override fun onEvent(event: EventListener.Event) {
    if (event is EventListener.Event.HeapAnalysisDone.HeapAnalysisSucceeded) {
      val heapAnalysis = event.heapAnalysis
      leaks.addAll(heapAnalysis.toLeakList())

      AndroidFlipperClient.getInstanceIfInitialized()?.let { client ->
        (client.getPlugin(LeakCanary2FlipperPlugin.ID) as? LeakCanary2FlipperPlugin)?.reportLeaks(
            leaks)
      }
    }
  }

  private fun HeapAnalysis.toLeakList(): List<Leak> {
    return if (this is HeapAnalysisSuccess) {
      allLeaks
          .mapNotNull {
            if (it.leakTraces.isNotEmpty()) {
              it.leakTraces[0].toLeak(it.shortDescription)
            } else {
              null
            }
          }
          .toList()
    } else {
      emptyList()
    }
  }
}


/*
 * 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.flipper.plugins.leakcanary2

import com.facebook.flipper.android.AndroidFlipperClient
import leakcanary.DefaultOnHeapAnalyzedListener
import leakcanary.OnHeapAnalyzedListener
import shark.HeapAnalysis
import shark.HeapAnalysisSuccess

@Deprecated("Use FlipperLeakEventListener add to LeakCanary.config.eventListeners instead")
class FlipperLeakListener : OnHeapAnalyzedListener {
  private val leaks: MutableList<Leak> = mutableListOf()

  private val defaultListener = DefaultOnHeapAnalyzedListener.create()

  override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
    leaks.addAll(heapAnalysis.toLeakList())

    AndroidFlipperClient.getInstanceIfInitialized()?.let { client ->
      (client.getPlugin(LeakCanary2FlipperPlugin.ID) as? LeakCanary2FlipperPlugin)?.reportLeaks(
          leaks)
    }

    defaultListener.onHeapAnalyzed(heapAnalysis)
  }

  private fun HeapAnalysis.toLeakList(): List<Leak> {
    return if (this is HeapAnalysisSuccess) {
      allLeaks
          .mapNotNull {
            if (it.leakTraces.isNotEmpty()) {
              it.leakTraces[0].toLeak(it.shortDescription)
            } else {
              null
            }
          }
          .toList()
    } else {
      emptyList()
    }
  }
}


/*
 * 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.flipper.plugins.leakcanary2

import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.core.FlipperPlugin

private const val REPORT_LEAK_EVENT = "reportLeak2"
private const val CLEAR_EVENT = "clear"

class LeakCanary2FlipperPlugin : FlipperPlugin {
  private val leaks: MutableList<Leak> = mutableListOf()
  private val alreadySeenLeakSignatures: MutableSet<String> = mutableSetOf()
  private var connection: FlipperConnection? = null

  override fun getId() = ID

  override fun onConnect(connection: FlipperConnection?) {
    this.connection = connection
    connection?.receive(CLEAR_EVENT) { _, _ -> leaks.clear() }
    sendLeakList()
  }

  override fun onDisconnect() {
    connection = null
  }

  override fun runInBackground() = false

  internal fun reportLeaks(leaks: List<Leak>) {
    for (leak in leaks) {
      if (leak.signature !in alreadySeenLeakSignatures) {
        this.leaks.add(leak)
        alreadySeenLeakSignatures.add(leak.signature)
      }
    }

    sendLeakList()
  }

  private fun sendLeakList() {
    connection?.send(REPORT_LEAK_EVENT, LeakCanary2Report(leaks).toFlipperObject())
  }

  companion object {
    const val ID = "LeakCanary"
  }
}


/*
 * 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.flipper.plugins.leakcanary2

import com.facebook.flipper.core.FlipperArray
import com.facebook.flipper.core.FlipperObject
import com.facebook.flipper.core.FlipperValue
import java.util.UUID
import shark.LeakTrace
import shark.LeakTraceObject

internal data class LeakCanary2Report(val leaks: List<Leak>) : FlipperValue {
  override fun toFlipperObject(): FlipperObject =
      FlipperObject.Builder()
          .put("leaks", leaks.map { it.toFlipperObject() }.toFlipperArray())
          .build()
}

internal data class Leak(
    val title: String,
    val root: String,
    val elements: Map<String, Element>,
    val retainedSize: String,
    val signature: String,
    val details: String
) : FlipperValue {
  override fun toFlipperObject(): FlipperObject {
    return FlipperObject.Builder()
        .put("title", title)
        .put("root", root)
        .put("elements", elements.toFlipperObject())
        .put("retainedSize", retainedSize)
        .put("details", details)
        .build()
  }

  private fun Map<String, FlipperValue>.toFlipperObject(): FlipperObject =
      mapValues { it.value.toFlipperObject() }.toFlipperObject()

  @JvmName("toFlipperObjectStringFlipperObject")
  private fun Map<String, FlipperObject>.toFlipperObject(): FlipperObject =
      asIterable()
          .fold(FlipperObject.Builder()) { builder, entry -> builder.put(entry.key, entry.value) }
          .build()
}

internal fun LeakTrace.toLeak(title: String): Leak {
  val elements = getElements()
  return Leak(
      title = title,
      elements = elements.toMap(),
      retainedSize = retainedHeapByteSize?.let { "$it bytes" } ?: "unknown size",
      signature = signature,
      root = elements.first().first,
      details = "$this")
}

private fun LeakTrace.getElements(): List<Pair<String, Element>> {
  val referenceElements =
      referencePath
          .map { reference ->
            val id = UUID.randomUUID().toString()
            id to Element(id, reference.originObject)
          }
          .toMutableList()

  val leakId = UUID.randomUUID().toString()
  referenceElements.add(leakId to Element(leakId, leakingObject))

  return referenceElements.mapIndexed { index, pair ->
    pair.first to
        if (index == referenceElements.lastIndex) pair.second
        else pair.second.copy(children = listOf(referenceElements[index + 1].second.id))
  }
}

internal data class Element(
    val id: String,
    val name: String,
    val expanded: Boolean = true,
    val children: List<String> = emptyList(),
    val attributes: List<ElementAttribute>,
    val decoration: String = ""
) : FlipperValue {
  constructor(
      id: String,
      leakObject: LeakTraceObject
  ) : this(
      id = id,
      name = "${leakObject.className} (${leakObject.typeName})",
      attributes =
          listOf(
              ElementAttribute("leaking", leakObject.leakingStatus.shortName),
              ElementAttribute("retaining", leakObject.retaining)))

  override fun toFlipperObject(): FlipperObject {
    return FlipperObject.Builder()
        .put("id", id)
        .put("name", name)
        .put("expanded", expanded)
        .put("children", children.toFlipperArray())
        .put("attributes", attributes.toFlipperArray())
        .put("data", EMPTY_FLIPPER_OBJECT)
        .put("decoration", decoration)
        .put("extraInfo", EMPTY_FLIPPER_OBJECT)
        .build()
  }

  @JvmName("toFlipperArrayFlipperValue")
  private fun Iterable<FlipperValue>.toFlipperArray(): FlipperArray =
      map { it.toFlipperObject() }.toFlipperArray()

  @JvmName("toFlipperArrayString")
  private fun Iterable<String>.toFlipperArray(): FlipperArray =
      fold(FlipperArray.Builder()) { builder, row -> builder.put(row) }.build()
}

internal fun Iterable<FlipperObject>.toFlipperArray(): FlipperArray =
    fold(FlipperArray.Builder()) { builder, row -> builder.put(row) }.build()

private val LeakTraceObject.LeakingStatus.shortName: String
  get() =
      when (this) {
        LeakTraceObject.LeakingStatus.NOT_LEAKING -> "N"
        LeakTraceObject.LeakingStatus.LEAKING -> "Y"
        LeakTraceObject.LeakingStatus.UNKNOWN -> "?"
      }
private val LeakTraceObject.retaining: String
  get() = retainedHeapByteSize?.let { "$it bytes ($retainedObjectCount objects)" } ?: "unknown"

private val EMPTY_FLIPPER_OBJECT = FlipperObject.Builder().build()

data class ElementAttribute(val name: String, val value: String) : FlipperValue {
  override fun toFlipperObject(): FlipperObject {
    return FlipperObject.Builder().put("name", name).put("value", value).build()
  }
}


/*
 * 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.flipper.plugins.uidebugger.litho

import com.facebook.flipper.plugins.uidebugger.core.UIDContext

// this is not used internally
object UIDebuggerLithoSupport {

  fun enable(context: UIDContext) {}
}


/*
 * 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.flipper.plugins.retrofit2protobuf

import com.facebook.flipper.android.AndroidFlipperClient
import com.facebook.flipper.core.FlipperArray
import com.facebook.flipper.core.FlipperValue
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin
import com.facebook.flipper.plugins.retrofit2protobuf.adapter.GenericCallDefinitionsToMessageDefinitionsIfProtobuf
import com.facebook.flipper.plugins.retrofit2protobuf.adapter.RetrofitServiceToGenericCallDefinitions
import com.facebook.flipper.plugins.retrofit2protobuf.model.CallNestedMessagesPayload

object SendProtobufToFlipperFromRetrofit {
  operator fun invoke(baseUrl: String, service: Class<*>) {
    getNetworkPlugin()
        ?.addProtobufDefinitions(baseUrl, generateProtobufDefinitions(service).toFlipperArray())
  }

  private fun getNetworkPlugin(): NetworkFlipperPlugin? {
    return AndroidFlipperClient.getInstanceIfInitialized()?.let { client ->
      client.getPlugin(NetworkFlipperPlugin.ID) as? NetworkFlipperPlugin
    }
  }

  private fun generateProtobufDefinitions(service: Class<*>): List<CallNestedMessagesPayload> {
    return RetrofitServiceToGenericCallDefinitions(service)
        .let { definitions -> GenericCallDefinitionsToMessageDefinitionsIfProtobuf(definitions) }
        .let { messages ->
          messages.map {
            CallNestedMessagesPayload(
                path = it.path,
                method = it.method,
                requestMessageFullName = it.requestMessageFullName,
                requestDefinitions = it.requestModel,
                responseMessageFullName = it.responseMessageFullName,
                responseDefinitions = it.responseModel)
          }
        }
  }
}

private fun Iterable<FlipperValue>.toFlipperArray(): FlipperArray =
    fold(FlipperArray.Builder()) { builder, call -> builder.put(call) }.build()


/*
 * 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.flipper.plugins.retrofit2protobuf.adapter

import com.facebook.flipper.plugins.retrofit2protobuf.model.FullNamedMessagesCallDefinition
import com.facebook.flipper.plugins.retrofit2protobuf.model.GenericCallDefinition
import me.haroldmartin.protobufjavatoprotobufjs.ProtobufGeneratedJavaToProtobufJs

internal object GenericCallDefinitionsToMessageDefinitionsIfProtobuf {
  operator fun invoke(
      callDefinitions: List<GenericCallDefinition>
  ): List<FullNamedMessagesCallDefinition> {
    return callDefinitions.mapNotNull { definition ->
      val responseRootAndMessages =
          definition.responseType?.let { ProtobufGeneratedJavaToProtobufJs(it) }
      val requestRootAndMessages =
          definition.requestType?.let { ProtobufGeneratedJavaToProtobufJs(it) }

      FullNamedMessagesCallDefinition(
          path = definition.path,
          method = definition.method,
          responseMessageFullName = responseRootAndMessages?.rootFullName,
          responseModel = responseRootAndMessages?.descriptors,
          requestMessageFullName = requestRootAndMessages?.rootFullName,
          requestModel = requestRootAndMessages?.descriptors)
    }
  }
}


/*
 * 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.flipper.plugins.retrofit2protobuf.adapter

import com.facebook.flipper.plugins.retrofit2protobuf.model.GenericCallDefinition
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

internal object RetrofitServiceToGenericCallDefinitions {
  @Suppress("LoopWithTooManyJumpStatements")
  operator fun invoke(service: Class<*>): List<GenericCallDefinition> {
    val methodToProtobufDefinition = mutableListOf<GenericCallDefinition>()
    for (method in service.declaredMethods) {
      val responseType = method.innerGenericReturnClass ?: continue
      val (path, httpMethod) = method.annotations.urlPathAndMethod ?: continue
      methodToProtobufDefinition.add(
          GenericCallDefinition(
              path = path,
              method = httpMethod,
              responseType = responseType,
              requestType = method.requestBodyType))
    }
    return methodToProtobufDefinition
  }
}

private val Array<Annotation>.urlPathAndMethod: Pair<String, String>?
  get() {
    var path: Pair<String, String>? = null
    for (a in this) {
      path =
          when (a.annotationClass) {
            retrofit2.http.DELETE::class -> (a as retrofit2.http.DELETE).value to "DELETE"
            retrofit2.http.GET::class -> (a as retrofit2.http.GET).value to "GET"
            retrofit2.http.HEAD::class -> (a as retrofit2.http.HEAD).value to "HEAD"
            retrofit2.http.OPTIONS::class -> (a as retrofit2.http.OPTIONS).value to "OPTIONS"
            retrofit2.http.PATCH::class -> (a as retrofit2.http.PATCH).value to "PATCH"
            retrofit2.http.POST::class -> (a as retrofit2.http.POST).value to "POST"
            retrofit2.http.PUT::class -> (a as retrofit2.http.PUT).value to "PUT"
            else -> null
          }
      if (path != null) break
    }
    return path
  }

private val Method.requestBodyType: Class<*>?
  get() {
    parameterAnnotations.forEachIndexed { index, annotations ->
      annotations.forEach { annotation ->
        if (annotation.annotationClass == retrofit2.http.Body::class) {
          return parameterTypes[index]
        }
      }
    }
    return null
  }

private val Method.innerGenericReturnClass: Class<*>?
  get() = (genericReturnType as? ParameterizedType)?.innerGenericType as? Class<*>

private val ParameterizedType?.innerGenericType: Type?
  get() {
    val innerType = this?.actualTypeArguments?.get(0)
    return if (innerType is ParameterizedType) {
      innerType.innerGenericType
    } else {
      innerType
    }
  }


/*
 * 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.flipper.plugins.retrofit2protobuf.model

import com.facebook.flipper.core.FlipperArray
import com.facebook.flipper.core.FlipperObject
import com.facebook.flipper.core.FlipperValue

internal data class CallNestedMessagesPayload(
    val path: String,
    val method: String,
    val requestMessageFullName: String?,
    val requestDefinitions: Map<String, Any>?,
    val responseMessageFullName: String?,
    val responseDefinitions: Map<String, Any>?
) : FlipperValue {
  override fun toFlipperObject(): FlipperObject {
    return FlipperObject.Builder()
        .put("path", path)
        .put("method", method)
        .put("requestMessageFullName", requestMessageFullName)
        .put("requestDefinitions", requestDefinitions?.toFlipperObject())
        .put("responseMessageFullName", responseMessageFullName)
        .put("responseDefinitions", responseDefinitions?.toFlipperObject())
        .build()
  }
}

private fun Map<*, *>.toFlipperObject(): FlipperObject {
  val builder = FlipperObject.Builder()
  this.forEach { (key, value) ->
    val castValue =
        when (value) {
          is Map<*, *> -> value.toFlipperObject()
          is Iterable<*> -> value.toFlipperArray()
          else -> value
        }
    builder.put(key as String, castValue)
  }
  return builder.build()
}

private fun Iterable<*>.toFlipperArray(): FlipperArray =
    fold(FlipperArray.Builder()) { builder, value -> builder.put(value as? String) }.build()


/*
 * 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.flipper.plugins.retrofit2protobuf.model

import me.haroldmartin.protobufjavatoprotobufjs.adapter.JsDescriptors

internal data class FullNamedMessagesCallDefinition(
    val path: String,
    val method: String,
    val requestMessageFullName: String?,
    val responseMessageFullName: String?,
    val responseModel: JsDescriptors?,
    val requestModel: JsDescriptors?
)


/*
 * 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.flipper.plugins.retrofit2protobuf.model

internal data class GenericCallDefinition(
    val path: String,
    val method: String,
    val responseType: Class<*>? = null,
    val requestType: Class<*>? = null
)


package com.facebook.flipper.sample

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
fun MessageCard(name: String) {
  Row(modifier = Modifier.padding(all = 8.dp)) { Text(text = "Hello $name!") }
}

@Preview
@Composable
fun PreviewMessageCard() {
  MessageCard("Android")
}

class JetpackComposeActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent { MessageCard("Flipper") }
  }
}


/*
 * 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.flipper.plugins.uidebugger

import android.util.Log
import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.core.FlipperPlugin
import com.facebook.flipper.plugins.uidebugger.core.*
import com.facebook.flipper.plugins.uidebugger.descriptors.ApplicationRefDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.InitEvent
import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent
import kotlinx.serialization.json.Json

const val LogTag = "ui-debugger"

class UIDebuggerFlipperPlugin(val context: UIDContext) : FlipperPlugin {

  init {
    Log.i(LogTag, "Initializing ui-debugger")
  }

  override fun getId(): String {
    return "ui-debugger"
  }

  @Throws(Exception::class)
  override fun onConnect(connection: FlipperConnection) {
    this.context.connectionRef.connection = connection
    this.context.bitmapPool.makeReady()

    connection.send(
        InitEvent.name,
        Json.encodeToString(
            InitEvent.serializer(),
            InitEvent(
                ApplicationRefDescriptor.getId(context.applicationRef),
                context.frameworkEventMetadata)))

    connection.send(
        MetadataUpdateEvent.name,
        Json.encodeToString(
            MetadataUpdateEvent.serializer(),
            MetadataUpdateEvent(MetadataRegister.extractPendingMetadata())))

    context.treeObserverManager.start()
  }

  @Throws(Exception::class)
  override fun onDisconnect() {
    this.context.connectionRef.connection = null

    MetadataRegister.reset()

    context.treeObserverManager.stop()
    context.bitmapPool.recycleAll()
  }

  override fun runInBackground(): Boolean {
    return 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.flipper.plugins.uidebugger.commands

import com.facebook.flipper.core.FlipperObject
import com.facebook.flipper.core.FlipperReceiver
import com.facebook.flipper.core.FlipperResponder
import com.facebook.flipper.plugins.common.MainThreadFlipperReceiver
import com.facebook.flipper.plugins.uidebugger.core.UIDContext

/** An interface for extensions to the UIDebugger plugin */
abstract class Command(val context: UIDContext) {
  /** The command identifier to respond to */
  abstract fun identifier(): String
  /** Execute the command */
  abstract fun execute(params: FlipperObject, response: FlipperResponder)
  /** Receiver which is the low-level handler for the incoming request */
  open fun receiver(): FlipperReceiver {
    return object : MainThreadFlipperReceiver() {
      @kotlin.Throws(java.lang.Exception::class)
      override fun onReceiveOnMainThread(params: FlipperObject, responder: FlipperResponder) {
        execute(params, responder)
      }
    }
  }
}


/*
 * 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.flipper.plugins.uidebugger.commands

import com.facebook.flipper.core.FlipperConnection

object CommandRegister {
  fun <T> register(connection: FlipperConnection, cmd: T) where T : Command {
    connection.receive(cmd.identifier(), cmd.receiver())
  }
}


/*
 * 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.flipper.plugins.uidebugger.common

import android.graphics.Bitmap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

/** BitmapPool is intended to be used on the main thread. In other words, it is not thread-safe. */
class BitmapPool(private val config: Bitmap.Config = Bitmap.Config.RGB_565) {

  interface ReusableBitmap {
    val bitmap: Bitmap?

    fun readyForReuse()
  }

  val mainScope = CoroutineScope(Dispatchers.Main)

  private val container: MutableMap<String, MutableList<Bitmap>> = mutableMapOf()
  private var isRecycled = false

  private fun generateKey(width: Int, height: Int): String = "$width,$height"

  fun recycleAll() {
    isRecycled = true
    container.forEach { (_, bitmaps) ->
      bitmaps.forEach { bitmap -> bitmap.recycle() }
      bitmaps.clear()
    }

    container.clear()
  }

  fun makeReady() {
    isRecycled = false
  }

  fun getBitmap(width: Int, height: Int): ReusableBitmap {
    val key = generateKey(width, height)
    val bitmaps = container[key]

    return if (bitmaps == null || bitmaps.isEmpty()) {
      LeasedBitmap(Bitmap.createBitmap(width, height, config))
    } else {
      LeasedBitmap(bitmaps.removeLast())
    }
  }

  inner class LeasedBitmap(override val bitmap: Bitmap) : ReusableBitmap {
    override fun readyForReuse() {
      val key = generateKey(bitmap.width, bitmap.height)

      mainScope.launch {
        if (isRecycled) {
          bitmap.recycle()
        } else {
          var bitmaps = container[key]
          if (bitmaps == null) {
            bitmaps = mutableListOf()
            container[key] = bitmaps
          }
          bitmaps.add(bitmap)
        }
      }
    }
  }

  companion object {
    fun createBitmapWithDefaultConfig(width: Int, height: Int): Bitmap {
      return Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
    }
  }
}


/*
 * 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.flipper.plugins.uidebugger.common

import android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue

// Maintains 2 way mapping between some enum value and a readable string representation
open class EnumMapping<T>(private val mapping: Map<String, T>) {

  fun getStringRepresentation(enumValue: T): String {
    val entry = mapping.entries.find { (_, value) -> value == enumValue }
    return if (entry != null) {
      entry.key
    } else {
      Log.v(
          LogTag,
          "Could not convert enum value ${enumValue.toString()} to string, known values ${mapping.entries}")
      NoMapping
    }
  }

  fun getEnumValue(key: String): T {
    return mapping[key]
        ?: throw UIDebuggerException(
            "Could not convert string $key to enum value, possible values ${mapping.entries} ")
  }

  fun getInspectableValues(): Set<InspectableValue> {
    val set: MutableSet<InspectableValue> = mutableSetOf()
    mapping.entries.forEach { set.add(InspectableValue.Text(it.key)) }
    return set
  }

  fun toInspectable(value: T): InspectableValue.Enum {
    return InspectableValue.Enum(getStringRepresentation(value))
  }

  companion object {
    const val NoMapping = "__UNKNOWN_ENUM_VALUE__"
  }
}

inline fun <reified T : Enum<T>> enumerator(): Iterator<T> = enumValues<T>().iterator()

inline fun <reified T : Enum<T>> enumToSet(): Set<String> {
  val set = mutableSetOf<String>()
  val values = enumerator<T>()
  values.forEach { set.add(it.name) }
  return set
}

inline fun <reified T : Enum<T>> enumToInspectableSet(): Set<InspectableValue> {
  val set = mutableSetOf<InspectableValue>()
  val values = enumerator<T>()
  values.forEach { set.add(InspectableValue.Text(it.name)) }
  return set
}

inline fun <reified T : Enum<T>> enumMapping(): EnumMapping<T> {
  val map = mutableMapOf<String, T>()
  val values = enumerator<T>()
  values.forEach { map[it.name] = it }
  return EnumMapping<T>(map)
}


/*
 * 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.flipper.plugins.uidebugger.common

class UIDebuggerException(message: String) : Exception(message)


/*
 * 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.flipper.plugins.uidebugger.core

import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.os.Build
import android.os.Bundle
import java.lang.ref.WeakReference
import java.lang.reflect.Field
import java.lang.reflect.Method

object ActivityTracker : Application.ActivityLifecycleCallbacks {
  interface ActivityStackChangedListener {
    fun onActivityAdded(activity: Activity, stack: List<Activity>)

    fun onActivityStackChanged(stack: List<Activity>)

    fun onActivityDestroyed(activity: Activity, stack: List<Activity>)
  }

  private val activities: MutableList<WeakReference<Activity>> = mutableListOf()
  private val trackedActivities: MutableSet<Int> = mutableSetOf()
  private var activityStackChangedListener: ActivityStackChangedListener? = null

  fun setActivityStackChangedListener(listener: ActivityStackChangedListener?) {
    activityStackChangedListener = listener
  }

  fun start(application: Application) {
    initialiseActivities()
    application.registerActivityLifecycleCallbacks(this)
  }

  private fun trackActivity(activity: Activity) {
    if (trackedActivities.contains(System.identityHashCode(activity))) {
      return
    }

    trackedActivities.add(System.identityHashCode(activity))
    activities.add(WeakReference<Activity>(activity))

    FragmentTracker.trackFragmentsOfActivity(activity)

    activityStackChangedListener?.onActivityAdded(activity, this.activitiesStack)
    activityStackChangedListener?.onActivityStackChanged(this.activitiesStack)
  }

  private fun untrackActivity(activity: Activity) {
    trackedActivities.remove(System.identityHashCode(activity))
    val activityIterator: MutableIterator<WeakReference<Activity>> = activities.iterator()

    while (activityIterator.hasNext()) {
      if (activityIterator.next().get() === activity) {
        activityIterator.remove()
      }
    }

    FragmentTracker.untrackFragmentsOfActivity(activity)

    activityStackChangedListener?.onActivityDestroyed(activity, this.activitiesStack)
    activityStackChangedListener?.onActivityStackChanged(this.activitiesStack)
  }

  override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
    trackActivity(activity)
  }

  override fun onActivityStarted(activity: Activity) {
    trackActivity(activity)
  }

  override fun onActivityResumed(activity: Activity) {}

  override fun onActivityPaused(activity: Activity) {}

  override fun onActivityStopped(activity: Activity) {}

  override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}

  override fun onActivityDestroyed(activity: Activity) {
    untrackActivity(activity)
  }

  val activitiesStack: List<Activity>
    get() {
      val stack: MutableList<Activity> = ArrayList(activities.size)
      val activityIterator: MutableIterator<WeakReference<Activity>> = activities.iterator()
      while (activityIterator.hasNext()) {
        val activity: Activity? = activityIterator.next().get()
        if (activity == null) {
          activityIterator.remove()
        } else {
          stack.add(activity)
        }
      }
      return stack
    }

  /**
   * Activity tracker is used to track activities. However, it cannot track via life-cycle events
   * all those activities that were created prior to initialisation via the `start(application:
   * Application)` method.
   *
   * As such, the method below makes a 'best effort' to find these untracked activities and add them
   * to the tracked list.
   */
  @SuppressLint("PrivateApi", "DiscouragedPrivateApi")
  fun initialiseActivities() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
      return
    }

    try {
      val activityThreadClass: Class<*> = Class.forName("android.app.ActivityThread")
      val currentActivityThreadMethod: Method =
          activityThreadClass.getMethod("currentActivityThread")
      val currentActivityThread: Any? = currentActivityThreadMethod.invoke(null)

      currentActivityThread?.let { activityThread ->
        val mActivitiesField: Field = activityThreadClass.getDeclaredField("mActivities")
        mActivitiesField.isAccessible = true
        val mActivities = mActivitiesField.get(activityThread) as android.util.ArrayMap<*, *>
        for (record in mActivities.values) {
          val recordClass: Class<*> = record.javaClass
          val activityField: Field = recordClass.getDeclaredField("activity")
          activityField.isAccessible = true

          val activity = activityField.get(record)
          if (activity != null && activity is Activity) {
            trackActivity(activity)
          }
        }
      }
    } catch (e: Exception) {}
  }
}


/*
 * 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.flipper.plugins.uidebugger.core

import android.app.Activity
import android.app.Application

class ApplicationRef(val application: Application) {
  init {
    ActivityTracker.start(application)
  }

  // the root view resolver will contain all root views 100% It is needed for 2 cases:
  // 1. In some cases an activity will not be picked up by the activity tracker,
  // the root view resolver will at least find the decor view
  // 2. Dialog fragments
  val rootsResolver: RootViewResolver = RootViewResolver()

  val activitiesStack: List<Activity>
    get() {
      return ActivityTracker.activitiesStack
    }
}


/*
 * 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.flipper.plugins.uidebugger.core

import android.app.Activity
import android.os.Build
import android.os.Bundle
import android.view.View
import java.lang.ref.WeakReference

object FragmentTracker {

  class FragmentRef {
    val supportFragment: WeakReference<androidx.fragment.app.Fragment>?
    val frameworkFragment: WeakReference<android.app.Fragment>?
    val isSupportFragment: Boolean

    constructor(supportFragment: androidx.fragment.app.Fragment) {
      this.supportFragment = WeakReference(supportFragment)
      this.frameworkFragment = null
      this.isSupportFragment = true
    }

    constructor(frameworkFragment: android.app.Fragment) {
      this.supportFragment = null
      this.frameworkFragment = WeakReference(frameworkFragment)
      this.isSupportFragment = false
    }

    override fun hashCode(): Int {
      if (isSupportFragment) {
        val fragment = supportFragment?.get()
        fragment?.let { f ->
          return System.identityHashCode(f)
        }
      } else {
        val fragment = frameworkFragment?.get()
        fragment?.let { f ->
          return System.identityHashCode(f)
        }
      }
      return -1
    }

    override fun equals(other: Any?): Boolean {
      if (this === other) return true
      if (javaClass != other?.javaClass) return false

      other as FragmentRef

      if (isSupportFragment != other.isSupportFragment) return false
      if (isSupportFragment && (supportFragment != other.supportFragment)) return false
      else if (frameworkFragment != other.frameworkFragment) return false

      return true
    }

    val view: View?
      get() {
        if (isSupportFragment) {
          val fragment = supportFragment?.get()
          fragment?.let { f ->
            return f.view
          }
        } else {
          val fragment = frameworkFragment?.get()
          fragment?.let { f ->
            return f.view
          }
        }

        return null
      }

    override fun toString(): String {
      if (isSupportFragment) {
        val fragment = supportFragment?.get()
        fragment?.let { f ->
          return "$f"
        }
      } else {
        val fragment = frameworkFragment?.get()
        fragment?.let { f ->
          return "$f"
        }
      }
      return "unknown"
    }
  }

  private val activityFragments: MutableMap<Int, MutableSet<FragmentRef>> = mutableMapOf()
  private val viewFragment: MutableMap<Int, FragmentRef> = mutableMapOf()

  private val supportLibraryLifecycleTracker =
      object : androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks() {
        override fun onFragmentAttached(
            fm: androidx.fragment.app.FragmentManager,
            f: androidx.fragment.app.Fragment,
            context: android.content.Context
        ) {
          super.onFragmentAttached(fm, f, context)

          f.activity?.let { activity ->
            val fragmentRef = FragmentRef(f)
            activityFragments[System.identityHashCode(activity)]?.add(fragmentRef)
          }
        }

        override fun onFragmentViewCreated(
            fm: androidx.fragment.app.FragmentManager,
            f: androidx.fragment.app.Fragment,
            v: View,
            savedInstanceState: Bundle?
        ) {
          super.onFragmentViewCreated(fm, f, v, savedInstanceState)

          val fragmentRef = FragmentRef(f)
          viewFragment[System.identityHashCode(v)] = fragmentRef
        }

        override fun onFragmentViewDestroyed(
            fm: androidx.fragment.app.FragmentManager,
            f: androidx.fragment.app.Fragment
        ) {
          super.onFragmentViewDestroyed(fm, f)
          for (entry in viewFragment) {
            if (entry.value.supportFragment == f) {
              viewFragment.remove(entry.key)
              break
            }
          }
        }

        override fun onFragmentDetached(
            fm: androidx.fragment.app.FragmentManager,
            f: androidx.fragment.app.Fragment
        ) {
          super.onFragmentDetached(fm, f)

          f.activity?.let { activity ->
            val fragmentRef = FragmentRef(f)
            activityFragments[System.identityHashCode(activity)]?.remove(fragmentRef)
          }

          f.view?.let { view -> viewFragment.remove(System.identityHashCode(view)) }
        }
      }

  private var frameworkLifecycleTracker: Any? = null

  fun trackFragmentsOfActivity(activity: Activity) {
    activityFragments[System.identityHashCode(activity)] = mutableSetOf()
    if (activity is androidx.fragment.app.FragmentActivity) {
      activity.supportFragmentManager.registerFragmentLifecycleCallbacks(
          supportLibraryLifecycleTracker, true)
    } else {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && frameworkLifecycleTracker != null) {
        activity.fragmentManager.registerFragmentLifecycleCallbacks(
            frameworkLifecycleTracker as android.app.FragmentManager.FragmentLifecycleCallbacks,
            true)
      }
    }
  }

  fun untrackFragmentsOfActivity(activity: Activity) {
    activityFragments[System.identityHashCode(activity)]?.let { fragments ->
      fragments.forEach { f ->
        f.view?.let { view -> viewFragment.remove(System.identityHashCode(view)) }
      }
    }
    activityFragments.remove(System.identityHashCode(activity))

    if (activity is androidx.fragment.app.FragmentActivity) {
      activity.supportFragmentManager.unregisterFragmentLifecycleCallbacks(
          supportLibraryLifecycleTracker)
    } else {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && frameworkLifecycleTracker != null) {
        activity.fragmentManager.unregisterFragmentLifecycleCallbacks(
            frameworkLifecycleTracker as android.app.FragmentManager.FragmentLifecycleCallbacks)
      }
    }
  }

  fun getFragment(view: View): Any? {
    val key = System.identityHashCode(view)
    val fragmentRef = viewFragment[key]

    fragmentRef?.let { fragment ->
      fragment.supportFragment?.get()?.let { f ->
        return f
      }

      fragment.frameworkFragment?.get()?.let { f ->
        return f
      }
    }

    return null
  }

  fun getDialogFragments(activity: Activity): List<Any> {
    val key = System.identityHashCode(activity)
    val fragments = mutableListOf<Any>()

    activityFragments[key]?.forEach { fragmentRef ->
      fragmentRef.supportFragment?.get()?.let { fragment ->
        if (androidx.fragment.app.DialogFragment::class.java.isInstance(fragment)) {
          fragments.add(fragmentRef)
        }
      }
      fragmentRef.frameworkFragment?.get()?.let { fragment ->
        if (android.app.DialogFragment::class.java.isInstance(fragment)) {
          fragments.add(fragmentRef)
        }
      }
    }

    return fragments
  }

  init {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
        Class.forName("android.app.FragmentManager\$FragmentLifecycleCallbacks") != null) {
      frameworkLifecycleTracker =
          object : android.app.FragmentManager.FragmentLifecycleCallbacks() {
            @Deprecated("Deprecated in Java")
            override fun onFragmentAttached(
                fm: android.app.FragmentManager?,
                f: android.app.Fragment?,
                context: android.content.Context?
            ) {
              super.onFragmentAttached(fm, f, context)
              f?.let { fragment ->
                val fragmentRef = FragmentRef(fragment)
                activityFragments[System.identityHashCode(fragment.activity)]?.add(fragmentRef)
              }
            }

            @Deprecated("Deprecated in Java")
            override fun onFragmentViewCreated(
                fm: android.app.FragmentManager?,
                f: android.app.Fragment?,
                v: View?,
                savedInstanceState: Bundle?
            ) {
              super.onFragmentViewCreated(fm, f, v, savedInstanceState)
              if (f != null && v != null) {
                val fragmentRef = FragmentRef(f)
                viewFragment[System.identityHashCode(v)] = fragmentRef
              }
            }

            @Deprecated("Deprecated in Java")
            override fun onFragmentDetached(
                fm: android.app.FragmentManager?,
                f: android.app.Fragment?
            ) {
              super.onFragmentDetached(fm, f)
              f?.let { fragment ->
                val fragmentRef = FragmentRef(fragment)
                activityFragments[System.identityHashCode(fragment.activity)]?.remove(fragmentRef)

                fragment.view?.let { view -> viewFragment.remove(System.identityHashCode(view)) }
              }
            }
          }
    }
  }
}


/*
 * 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.flipper.plugins.uidebugger.core

import android.annotation.SuppressLint
import android.os.Build
import android.view.View
import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Modifier

/**
 * Provides access to all root views in an application.
 *
 * 95% of the time this is unnecessary and we can operate solely on current Activity's root view as
 * indicated by getWindow().getDecorView(). However in the case of popup windows, menus, and dialogs
 * the actual view hierarchy we should be operating on is not accessible thru public apis.
 *
 * In the spirit of degrading gracefully when new api levels break compatibility, callers should
 * handle a list of size 0 by assuming getWindow().getDecorView() on the currently resumed activity
 * is the sole root - this assumption will be correct often enough.
 *
 * Obviously, you need to be on the main thread to use this.
 */
class RootViewResolver {
  private var initialized = false
  private var windowManagerObj: Any? = null
  private val observableViews: ObservableViewArrayList = ObservableViewArrayList()

  interface Listener {
    fun onRootViewAdded(rootView: View)

    fun onRootViewRemoved(rootView: View)

    fun onRootViewsChanged(rootViews: List<View>)
  }

  fun attachListener(listener: Listener?) {
    if (Build.VERSION.SDK_INT < 19 || listener == null) {
      // We don't have a use for this on older APIs. If you do then modify accordingly :)
      return
    }
    if (!initialized) {
      initialize()
    }
    observableViews?.setListener(listener)
  }

  /**
   * Lists the active root views in an application at this moment.
   *
   * @return a list of all the active root views in the application.
   * @throws IllegalStateException if invoked from a thread besides the main thread.
   */
  fun rootViews(): List<View> {
    if (!initialized) {
      initialize()
    }
    return observableViews.toList()
  }

  private fun initialize() {

    initialized = true
    val accessClass =
        if (Build.VERSION.SDK_INT > 16) WINDOW_MANAGER_GLOBAL_CLAZZ else WINDOW_MANAGER_IMPL_CLAZZ
    val instanceMethod = if (Build.VERSION.SDK_INT > 16) GET_GLOBAL_INSTANCE else GET_DEFAULT_IMPL
    try {
      val clazz = Class.forName(accessClass)
      val getMethod = clazz.getMethod(instanceMethod)
      windowManagerObj = getMethod.invoke(null)

      val viewsField: Field = clazz.getDeclaredField(VIEWS_FIELD)

      viewsField.let { vf ->
        vf.isAccessible = true
        // Forgive me father for I have sinned...
        @SuppressLint("DiscouragedPrivateApi")
        val modifiers = Field::class.java.getDeclaredField("accessFlags")
        modifiers.isAccessible = true
        modifiers.setInt(vf, vf.modifiers and Modifier.FINAL.inv())

        @Suppress("unchecked_cast")
        val currentWindowManagerViews = vf[windowManagerObj] as List<View>
        observableViews.addAll(currentWindowManagerViews)
        vf[windowManagerObj] = observableViews
      }
    } catch (ite: InvocationTargetException) {} catch (cnfe: ClassNotFoundException) {} catch (
        nsfe: NoSuchFieldException) {} catch (nsme: NoSuchMethodException) {} catch (
        re: RuntimeException) {} catch (iae: IllegalAccessException) {}

    try {} catch (e: Throwable) {}
  }

  companion object {
    private const val WINDOW_MANAGER_IMPL_CLAZZ = "android.view.WindowManagerImpl"
    private const val WINDOW_MANAGER_GLOBAL_CLAZZ = "android.view.WindowManagerGlobal"
    private const val VIEWS_FIELD = "mViews"
    private const val GET_DEFAULT_IMPL = "getDefault"
    private const val GET_GLOBAL_INSTANCE = "getInstance"
  }

  class ObservableViewArrayList : ArrayList<View>() {
    private var listener: Listener? = null

    fun setListener(listener: Listener?) {
      this.listener = listener
    }

    override fun add(element: View): Boolean {
      val ret = super.add(element)
      listener?.let { l ->
        l.onRootViewAdded(element)
        l.onRootViewsChanged(this as List<View>)
      }
      return ret
    }

    override fun remove(element: View): Boolean {
      val ret = super.remove(element)
      listener?.let { l ->
        l.onRootViewRemoved(element)
        l.onRootViewsChanged(this as List<View>)
      }

      return ret
    }

    override fun removeAt(index: Int): View {
      val view = super.removeAt(index)
      listener?.let { l ->
        l.onRootViewRemoved(view)
        l.onRootViewsChanged(this as List<View>)
      }
      return view
    }
  }
}


/*
 * 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.flipper.plugins.uidebugger.core

import android.app.Application
import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEventMetadata
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverManager
import com.facebook.flipper.plugins.uidebugger.scheduler.SharedThrottle
import com.facebook.flipper.plugins.uidebugger.traversal.PartialLayoutTraversal

data class UIDContext(
    val applicationRef: ApplicationRef,
    val connectionRef: ConnectionRef,
    val descriptorRegister: DescriptorRegister,
    val observerFactory: TreeObserverFactory,
    val frameworkEventMetadata: MutableList<FrameworkEventMetadata>
) {
  val layoutTraversal: PartialLayoutTraversal =
      PartialLayoutTraversal(descriptorRegister, observerFactory)

  val treeObserverManager = TreeObserverManager(this)
  val sharedThrottle: SharedThrottle = SharedThrottle()
  val bitmapPool = BitmapPool()

  companion object {
    fun create(application: Application): UIDContext {
      return UIDContext(
          ApplicationRef(application),
          ConnectionRef(null),
          descriptorRegister = DescriptorRegister.withDefaults(),
          observerFactory = TreeObserverFactory.withDefaults(),
          frameworkEventMetadata = mutableListOf())
    }
  }
}

data class ConnectionRef(var connection: FlipperConnection?)