Infinite loop in CodeWriter.generateImportAliases

diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
index 2884c65b..5d827276 100644
--- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
@@ -706,14 +706,12 @@ internal class CodeWriter constructor(
         .generateImports(
           generatedImports,
           canonicalName = ClassName::canonicalName,
-          packageName = ClassName::packageName,
           capitalizeAliases = true,
         )
       val suggestedMemberImports = importsCollector.suggestedMemberImports()
         .generateImports(
           generatedImports,
           canonicalName = MemberName::canonicalName,
-          packageName = MemberName::packageName,
           capitalizeAliases = false,
         )
       importsCollector.close()
@@ -730,7 +728,6 @@ internal class CodeWriter constructor(
     private fun <T> Map<String, Set<T>>.generateImports(
       generatedImports: MutableMap<String, Import>,
       canonicalName: T.() -> String,
-      packageName: T.() -> String,
       capitalizeAliases: Boolean,
     ): Map<String, T> {
       return flatMap { (simpleName, qualifiedNames) ->
@@ -740,7 +737,7 @@ internal class CodeWriter constructor(
             generatedImports[canonicalName] = Import(canonicalName)
           }
         } else {
-          generateImportAliases(simpleName, qualifiedNames, packageName, capitalizeAliases)
+          generateImportAliases(simpleName, qualifiedNames, canonicalName, capitalizeAliases)
             .onEach { (alias, qualifiedName) ->
               val canonicalName = qualifiedName.canonicalName()
               generatedImports[canonicalName] = Import(canonicalName, alias)
@@ -752,11 +749,14 @@ internal class CodeWriter constructor(
     private fun <T> generateImportAliases(
       simpleName: String,
       qualifiedNames: Set<T>,
-      packageName: T.() -> String,
+      canonicalName: T.() -> String,
       capitalizeAliases: Boolean,
     ): List<Pair<String, T>> {
-      val packageNameSegments = qualifiedNames.associateWith { qualifiedName ->
-        qualifiedName.packageName().split('.').map { it.replaceFirstChar(Char::uppercaseChar) }
+      val canonicalNameSegments = qualifiedNames.associateWith { qualifiedName ->
+        qualifiedName.canonicalName().split('.')
+          .dropLast(1) // Last segment of the canonical name is the simple name, drop it to avoid repetition.
+          .filter { it != "Companion" }
+          .map { it.replaceFirstChar(Char::uppercaseChar) }
       }
       val aliasNames = mutableMapOf<String, T>()
       var segmentsToUse = 0
@@ -764,7 +764,7 @@ internal class CodeWriter constructor(
       while (aliasNames.size != qualifiedNames.size) {
         segmentsToUse += 1
         aliasNames.clear()
-        for ((qualifiedName, segments) in packageNameSegments) {
+        for ((qualifiedName, segments) in canonicalNameSegments) {
           val aliasPrefix = segments.takeLast(min(segmentsToUse, segments.size))
             .joinToString(separator = "")
             .replaceFirstChar { if (!capitalizeAliases) it.lowercaseChar() else it }
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
index 9d3f1711..bd021020 100644
--- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
@@ -1333,6 +1333,41 @@ class KotlinPoetTest {
     )
   }

+  // https://github.com/square/kotlinpoet/issues/1518
+  @Test fun generatedImportAliasesSamePackageDifferentContainingClasses() {
+    val strokeCapRound = MemberName(
+      enclosingClassName = ClassName("androidx.compose.ui.graphics", "StrokeCap").nestedClass("Companion"),
+      simpleName = "Round",
+    )
+    val strokeJoinRound = MemberName(
+      enclosingClassName = ClassName("androidx.compose.ui.graphics", "StrokeJoin").nestedClass("Companion"),
+      simpleName = "Round",
+    )
+    val file = FileSpec.builder("com.example", "Test")
+      .addFunction(
+        FunSpec.builder("main")
+          .addStatement("val strokeCapRound = %M()", strokeCapRound)
+          .addStatement("val strokeJoinRound = %M()", strokeJoinRound)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import kotlin.Unit
+      |import androidx.compose.ui.graphics.StrokeCap.Companion.Round as strokeCapRound
+      |import androidx.compose.ui.graphics.StrokeJoin.Companion.Round as strokeJoinRound
+      |
+      |public fun main(): Unit {
+      |  val strokeCapRound = strokeCapRound()
+      |  val strokeJoinRound = strokeJoinRound()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
   @Test fun memberImportsOverGeneratedImportAliases() {
     val squareTaco = ClassName("com.squareup.tacos", "Taco")
     val blockTaco = ClassName("xyz.block.tacos", "Taco")
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt
index 308aeb2c..565e61e2 100644
--- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt
@@ -171,11 +171,11 @@ class MemberNameTest {
       |package com.example
       |
       |import kotlin.Unit
-      |import com.squareup.tacos.SquareTacos.Companion.createTaco as squareupTacosCreateTaco
+      |import com.squareup.tacos.SquareTacos.Companion.createTaco as squareTacosCreateTaco
       |import com.twitter.tacos.TwitterTacos.Companion.createTaco as twitterTacosCreateTaco
       |
       |public fun makeTastyTacos(): Unit {
-      |  squareupTacosCreateTaco()
+      |  squareTacosCreateTaco()
       |  twitterTacosCreateTaco()
       |}
       |
@@ -416,11 +416,11 @@ class MemberNameTest {
       |package com.example
       |
       |import kotlin.Unit
-      |import com.squareup.tacos.SquareTacos.Companion.`when` as squareupTacosWhen
+      |import com.squareup.tacos.SquareTacos.Companion.`when` as squareTacosWhen
       |import com.twitter.tacos.TwitterTacos.Companion.`when` as twitterTacosWhen
       |
       |public fun whenTastyTacos(): Unit {
-      |  squareupTacosWhen()
+      |  squareTacosWhen()
       |  twitterTacosWhen()
       |}
       |

Infinite loop in CodeWriter.generateImportAliases
**Describe the bug**

Ambiguous import aliases can result in the call to `CodeWriter.generateImportAliases` failing to return.

**To Reproduce**

I've narrowed this down in the debugger. The call to `generateImportAliases` passes these arguments:

- `simpleName`: `"Round"`
- `qualifiedNames`: `setOf(MemberName("androidx.compose.ui.graphics.StrokeCap.Companion.Round"), MemberName("androidx.compose.ui.graphics.StrokeJoin.Companion.Round"))`
- `packageName`: `CodeWriter$Companion$withCollectedImports$suggestedMemberImports$2@10685`
- `capitalizeAliases`: `false`

This results in the `while` loop repeating forever, clearing `aliasNames` and writing both qualified names to the same key `"androidxComposeUiGraphicsRound"`. This fails the length check, which expects there to be a unique alias for both qualified names.

**Expected behavior**

`generateImportAliases` should take the containing class names into account, so there isn't a collision between nested types with the same name in the same package.

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import java.lang.reflect.Array
import java.util.Objects
import javax.lang.model.element.AnnotationMirror
import javax.lang.model.element.AnnotationValue
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.lang.model.type.TypeMirror
import javax.lang.model.util.SimpleAnnotationValueVisitor8
import kotlin.LazyThreadSafetyMode.NONE
import kotlin.reflect.KClass

/** A generated annotation on a declaration. */
public class AnnotationSpec private constructor(
  builder: Builder,
  private val tagMap: TagMap = builder.buildTagMap(),
) : Taggable by tagMap {
  @Deprecated(
    message = "Use typeName instead. This property will be removed in KotlinPoet 2.0.",
    replaceWith = ReplaceWith("typeName"),
  )
  public val className: ClassName
    get() = typeName as? ClassName ?: error("ClassName is not available. Call typeName instead.")
  public val typeName: TypeName = builder.typeName
  public val members: List<CodeBlock> = builder.members.toImmutableList()
  public val useSiteTarget: UseSiteTarget? = builder.useSiteTarget

  /** Lazily-initialized toString of this AnnotationSpec.  */
  private val cachedString by lazy(NONE) {
    buildCodeString {
      emit(this, inline = true, asParameter = false)
    }
  }

  internal fun emit(codeWriter: CodeWriter, inline: Boolean, asParameter: Boolean = false) {
    if (!asParameter) {
      codeWriter.emit("@")
    }
    if (useSiteTarget != null) {
      codeWriter.emit(useSiteTarget.keyword + ":")
    }
    codeWriter.emitCode("%T", typeName)

    if (members.isEmpty() && !asParameter) {
      // @Singleton
      return
    }

    val whitespace = if (inline) "" else "\n"
    val memberSeparator = if (inline) ", " else ",\n"
    val memberSuffix = if (!inline && members.size > 1) "," else ""

    // Inline:
    //   @Column(name = "updated_at", nullable = false)
    //
    // Not inline:
    //   @Column(
    //       name = "updated_at",
    //       nullable = false,
    //   )

    codeWriter.emit("(")
    if (members.size > 1) codeWriter.emit(whitespace).indent(1)
    codeWriter.emitCode(
      codeBlock = members
        .map { if (inline) it.replaceAll("[⇥|⇤]", "") else it }
        .joinToCode(separator = memberSeparator, suffix = memberSuffix),
      isConstantContext = true,
    )
    if (members.size > 1) codeWriter.unindent(1).emit(whitespace)
    codeWriter.emit(")")
  }

  public fun toBuilder(): Builder {
    val builder = Builder(typeName)
    builder.members += members
    builder.useSiteTarget = useSiteTarget
    builder.tags += tagMap.tags
    return builder
  }

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

  override fun hashCode(): Int = toString().hashCode()

  override fun toString(): String = cachedString

  public enum class UseSiteTarget(internal val keyword: String) {
    FILE("file"),
    PROPERTY("property"),
    FIELD("field"),
    GET("get"),
    SET("set"),
    RECEIVER("receiver"),
    PARAM("param"),
    SETPARAM("setparam"),
    DELEGATE("delegate"),
  }

  public class Builder internal constructor(
    internal val typeName: TypeName,
  ) : Taggable.Builder<Builder> {
    internal var useSiteTarget: UseSiteTarget? = null

    public val members: MutableList<CodeBlock> = mutableListOf()
    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()

    public fun addMember(format: String, vararg args: Any): Builder =
      addMember(CodeBlock.of(format, *args))

    public fun addMember(codeBlock: CodeBlock): Builder = apply {
      members += codeBlock
    }

    public fun useSiteTarget(useSiteTarget: UseSiteTarget?): Builder = apply {
      this.useSiteTarget = useSiteTarget
    }

    public fun build(): AnnotationSpec = AnnotationSpec(this)

    public companion object {
      /**
       * Creates a [CodeBlock] with parameter `format` depending on the given `value` object.
       * Handles a number of special cases, such as appending "f" to `Float` values, and uses
       * `%L` for other types.
       */
      internal fun memberForValue(value: Any) = when (value) {
        is Class<*> -> CodeBlock.of("%T::class", value)
        is Enum<*> -> CodeBlock.of("%T.%L", value.javaClass, value.name)
        is String -> CodeBlock.of("%S", value)
        is Float -> CodeBlock.of("%Lf", value)
        is Char -> CodeBlock.of("'%L'", characterLiteralWithoutSingleQuotes(value))
        else -> CodeBlock.of("%L", value)
      }
    }
  }

  /**
   * Annotation value visitor adding members to the given builder instance.
   */
  @OptIn(DelicateKotlinPoetApi::class)
  private class Visitor(
    val builder: CodeBlock.Builder,
  ) : SimpleAnnotationValueVisitor8<CodeBlock.Builder, String>(builder) {

    override fun defaultAction(o: Any, name: String) =
      builder.add(Builder.memberForValue(o))

    override fun visitAnnotation(a: AnnotationMirror, name: String) =
      builder.add("%L", get(a))

    override fun visitEnumConstant(c: VariableElement, name: String) =
      builder.add("%T.%L", c.asType().asTypeName(), c.simpleName)

    override fun visitType(t: TypeMirror, name: String) =
      builder.add("%T::class", t.asTypeName())

    override fun visitArray(values: List<AnnotationValue>, name: String): CodeBlock.Builder {
      builder.add("arrayOf(⇥⇥")
      values.forEachIndexed { index, value ->
        if (index > 0) builder.add(", ")
        value.accept(this, name)
      }
      builder.add("⇤⇤)")
      return builder
    }
  }

  public companion object {
    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    @JvmOverloads
    public fun get(
      annotation: Annotation,
      includeDefaultValues: Boolean = false,
    ): AnnotationSpec {
      try {
        @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
        val javaAnnotation = annotation as java.lang.annotation.Annotation
        val builder = builder(javaAnnotation.annotationType())
          .tag(annotation)
        val methods = annotation.annotationType().declaredMethods.sortedBy { it.name }
        for (method in methods) {
          val value = method.invoke(annotation)
          if (!includeDefaultValues) {
            if (Objects.deepEquals(value, method.defaultValue)) {
              continue
            }
          }
          val member = CodeBlock.builder()
          member.add("%L = ", method.name)
          if (value.javaClass.isArray) {
            member.add("arrayOf(⇥⇥")
            for (i in 0 until Array.getLength(value)) {
              if (i > 0) member.add(", ")
              member.add(Builder.memberForValue(Array.get(value, i)))
            }
            member.add("⇤⇤)")
            builder.addMember(member.build())
            continue
          }
          if (value is Annotation) {
            member.add("%L", get(value))
            builder.addMember(member.build())
            continue
          }
          member.add("%L", Builder.memberForValue(value))
          builder.addMember(member.build())
        }
        return builder.build()
      } catch (e: Exception) {
        throw RuntimeException("Reflecting $annotation failed!", e)
      }
    }

    @DelicateKotlinPoetApi(
      message = "Mirror APIs don't give complete information on Kotlin types. Consider using" +
        " the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    public fun get(annotation: AnnotationMirror): AnnotationSpec {
      val element = annotation.annotationType.asElement() as TypeElement
      val builder = builder(element.asClassName()).tag(annotation)
      for (executableElement in annotation.elementValues.keys) {
        val member = CodeBlock.builder()
        val visitor = Visitor(member)
        val name = executableElement.simpleName.toString()
        member.add("%L = ", name)
        val value = annotation.elementValues[executableElement]!!
        value.accept(visitor, name)
        builder.addMember(member.build())
      }
      return builder.build()
    }

    @JvmStatic public fun builder(type: ClassName): Builder = Builder(type)

    @JvmStatic public fun builder(type: ParameterizedTypeName): Builder = Builder(type)

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    public fun builder(type: Class<out Annotation>): Builder =
      builder(type.asClassName())

    @JvmStatic public fun builder(type: KClass<out Annotation>): Builder =
      builder(type.asClassName())
  }
}

/*
 * Copyright (C) 2014 Google, Inc.
 *
 * 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
 *
 * https://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.
 */
@file:JvmName("ClassNames")

package com.squareup.kotlinpoet

import java.util.ArrayDeque
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.NestingKind.MEMBER
import javax.lang.model.element.NestingKind.TOP_LEVEL
import javax.lang.model.element.PackageElement
import javax.lang.model.element.TypeElement
import kotlin.reflect.KClass

/** A fully-qualified class name for top-level and member classes.  */
public class ClassName internal constructor(
  names: List<String>,
  nullable: Boolean = false,
  annotations: List<AnnotationSpec> = emptyList(),
  tags: Map<KClass<*>, Any> = emptyMap()
) : TypeName(nullable, annotations, TagMap(tags)), Comparable<ClassName> {
  /**
   * Returns a class name created from the given parts. For example, calling this with package name
   * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
   */
  @Deprecated("", level = DeprecationLevel.HIDDEN)
  public constructor(packageName: String, simpleName: String, vararg simpleNames: String) :
    this(listOf(packageName, simpleName, *simpleNames))

  @Deprecated(
    "Simple names must not be empty. Did you forget an argument?",
    level = DeprecationLevel.ERROR,
    replaceWith = ReplaceWith("ClassName(packageName, TODO())"),
  )
  public constructor(packageName: String) : this(packageName, listOf())

  /**
   * Returns a class name created from the given parts. For example, calling this with package name
   * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
   */
  public constructor(packageName: String, vararg simpleNames: String) :
    this(listOf(packageName, *simpleNames)) {
      require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" }
      require(simpleNames.none { it.isEmpty() }) {
        "simpleNames must not contain empty items: ${simpleNames.contentToString()}"
      }
    }

  /**
   * Returns a class name created from the given parts. For example, calling this with package name
   * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
   */
  public constructor(packageName: String, simpleNames: List<String>) :
    this(mutableListOf(packageName).apply { addAll(simpleNames) }) {
      require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" }
      require(simpleNames.none { it.isEmpty() }) {
        "simpleNames must not contain empty items: $simpleNames"
      }
    }

  /** From top to bottom. This will be `["java.util", "Map", "Entry"]` for `Map.Entry`. */
  private val names = names.toImmutableList()

  /** String representation of the names used when comparing to other ClassName */
  private val comparableNames = names.joinToString()

  /** String representation of the annotations used when comparing to other ClassName */
  private val comparableAnnotations = annotations.joinToString()

  /** Fully qualified name using `.` as a separator, like `kotlin.collections.Map.Entry`. */
  public val canonicalName: String = if (names[0].isEmpty())
    names.subList(1, names.size).joinToString(".") else
    names.joinToString(".")

  /** Package name, like `"kotlin.collections"` for `Map.Entry`. */
  public val packageName: String get() = names[0]

  /** Simple name of this class, like `"Entry"` for `Map.Entry`. */
  public val simpleName: String get() = names[names.size - 1]

  /**
   * The enclosing classes, outermost first, followed by the simple name. This is `["Map", "Entry"]`
   * for `Map.Entry`.
   */
  public val simpleNames: List<String> get() = names.subList(1, names.size)

  override fun copy(
    nullable: Boolean,
    annotations: List<AnnotationSpec>,
    tags: Map<KClass<*>, Any>
  ): ClassName {
    return ClassName(names, nullable, annotations, tags)
  }

  /**
   * Returns the enclosing class, like `Map` for `Map.Entry`. Returns null if this class is not
   * nested in another class.
   */
  public fun enclosingClassName(): ClassName? {
    return if (names.size != 2)
      ClassName(names.subList(0, names.size - 1)) else
      null
  }

  /**
   * Returns the top class in this nesting group. Equivalent to chained calls to
   * [ClassName.enclosingClassName] until the result's enclosing class is null.
   */
  public fun topLevelClassName(): ClassName = ClassName(names.subList(0, 2))

  /**
   * Fully qualified name using `.` to separate package from the top level class name, and `$` to
   * separate nested classes, like `kotlin.collections.Map$Entry`.
   */
  public fun reflectionName(): String {
    // trivial case: no nested names
    if (names.size == 2) {
      return if (packageName.isEmpty())
        names[1] else
        packageName + "." + names[1]
    }
    // concat top level class name and nested names
    return buildString {
      append(topLevelClassName().canonicalName)
      for (name in simpleNames.subList(1, simpleNames.size)) {
        append('$').append(name)
      }
    }
  }

  /**
   * Callable reference to the constructor of this class. Emits the enclosing class if one exists,
   * followed by the reference operator `::`, followed by either [simpleName] or the
   * fully-qualified name if this is a top-level class.
   *
   * Note: As `::$packageName.$simpleName` is not valid syntax, an aliased import may be required
   * for a top-level class with a conflicting name.
   */
  public fun constructorReference(): CodeBlock {
    val enclosing = enclosingClassName()
    return if (enclosing != null) {
      CodeBlock.of("%T::%N", enclosing, simpleName)
    } else {
      CodeBlock.of("::%T", this)
    }
  }

  /** Returns a new [ClassName] instance for the specified `name` as nested inside this class. */
  public fun nestedClass(name: String): ClassName = ClassName(names + name)

  /**
   * Returns a class that shares the same enclosing package or class. If this class is enclosed by
   * another class, this is equivalent to `enclosingClassName().nestedClass(name)`. Otherwise
   * it is equivalent to `get(packageName(), name)`.
   */
  public fun peerClass(name: String): ClassName {
    val result = names.toMutableList()
    result[result.size - 1] = name
    return ClassName(result)
  }

  /**
   * Orders by the fully-qualified name. Nested types are ordered immediately after their
   * enclosing type. For example, the following types are ordered by this method:
   *
   * ```
   * com.example.Robot
   * com.example.Robot.Motor
   * com.example.RoboticVacuum
   * ```
   * Comparison is consistent with equals()
   */
  override fun compareTo(other: ClassName): Int = COMPARATOR.compare(this, other)

  override fun emit(out: CodeWriter) =
    out.emit(out.lookupName(this).escapeSegmentsIfNecessary())

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

    other as ClassName

    if (names != other.names) return false

    return true
  }

  override fun hashCode(): Int {
    var result = super.hashCode()
    result = 31 * result + names.hashCode()
    return result
  }

  public companion object {
    /**
     * Returns a new [ClassName] instance for the given fully-qualified class name string. This
     * method assumes that the input is ASCII and follows typical Java style (lowercase package
     * names, UpperCamelCase class names) and may produce incorrect results or throw
     * [IllegalArgumentException] otherwise. For that reason, the constructor should be preferred as
     * it can create [ClassName] instances without such restrictions.
     */
    @JvmStatic public fun bestGuess(classNameString: String): ClassName {
      val names = mutableListOf<String>()

      // Add the package name, like "java.util.concurrent", or "" for no package.
      var p = 0
      while (p < classNameString.length && Character.isLowerCase(classNameString.codePointAt(p))) {
        p = classNameString.indexOf('.', p) + 1
        require(p != 0) { "couldn't make a guess for $classNameString" }
      }
      names += if (p != 0) classNameString.substring(0, p - 1) else ""

      // Add the class names, like "Map" and "Entry".
      for (part in classNameString.substring(p).split('.')) {
        require(part.isNotEmpty() && Character.isUpperCase(part.codePointAt(0))) {
          "couldn't make a guess for $classNameString"
        }

        names += part
      }

      require(names.size >= 2) { "couldn't make a guess for $classNameString" }
      return ClassName(names)
    }

    private val COMPARATOR: Comparator<ClassName> = compareBy<ClassName> { it.comparableNames }
      .thenBy { it.isNullable }
      .thenBy { it.comparableAnnotations }
  }
}

@DelicateKotlinPoetApi(
  message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
    " the kotlinpoet-metadata APIs instead."
)
@JvmName("get")
public fun Class<*>.asClassName(): ClassName {
  require(!isPrimitive) { "primitive types cannot be represented as a ClassName" }
  require(Void.TYPE != this) { "'void' type cannot be represented as a ClassName" }
  require(!isArray) { "array types cannot be represented as a ClassName" }
  val names = mutableListOf<String>()
  var c = this
  while (true) {
    names += c.simpleName
    val enclosing = c.enclosingClass ?: break
    c = enclosing
  }
  // Avoid unreliable Class.getPackage(). https://github.com/square/javapoet/issues/295
  val lastDot = c.name.lastIndexOf('.')
  if (lastDot != -1) names += c.name.substring(0, lastDot)
  names.reverse()
  return ClassName(names)
}

@JvmName("get")
public fun KClass<*>.asClassName(): ClassName {
  var qualifiedName = requireNotNull(qualifiedName) { "$this cannot be represented as a ClassName" }

  // First, check for Kotlin types whose enclosing class name is a type that is mapped to a JVM
  // class. Thus, the class backing the nested Kotlin type does not have an enclosing class
  // (i.e., a parent) and the normal algorithm will fail.
  val names = when (qualifiedName) {
    "kotlin.Boolean.Companion" -> listOf("kotlin", "Boolean", "Companion")
    "kotlin.Byte.Companion" -> listOf("kotlin", "Byte", "Companion")
    "kotlin.Char.Companion" -> listOf("kotlin", "Char", "Companion")
    "kotlin.Double.Companion" -> listOf("kotlin", "Double", "Companion")
    "kotlin.Enum.Companion" -> listOf("kotlin", "Enum", "Companion")
    "kotlin.Float.Companion" -> listOf("kotlin", "Float", "Companion")
    "kotlin.Int.Companion" -> listOf("kotlin", "Int", "Companion")
    "kotlin.Long.Companion" -> listOf("kotlin", "Long", "Companion")
    "kotlin.Short.Companion" -> listOf("kotlin", "Short", "Companion")
    "kotlin.String.Companion" -> listOf("kotlin", "String", "Companion")
    else -> {
      val names = ArrayDeque<String>()
      var target: Class<*>? = java
      while (target != null) {
        target = target.enclosingClass

        val dot = qualifiedName.lastIndexOf('.')
        if (dot == -1) {
          if (target != null) throw AssertionError(this) // More enclosing classes than dots.
          names.addFirst(qualifiedName)
          qualifiedName = ""
        } else {
          names.addFirst(qualifiedName.substring(dot + 1))
          qualifiedName = qualifiedName.substring(0, dot)
        }
      }

      names.addFirst(qualifiedName)
      names.toList()
    }
  }

  return ClassName(names)
}

/** Returns the class name for `element`. */
@DelicateKotlinPoetApi(
  message = "Element APIs don't give complete information on Kotlin types. Consider using" +
    " the kotlinpoet-metadata APIs instead."
)
@JvmName("get")
public fun TypeElement.asClassName(): ClassName {
  fun isClassOrInterface(e: Element) = e.kind.isClass || e.kind.isInterface

  fun getPackage(type: Element): PackageElement {
    var t = type
    while (t.kind != ElementKind.PACKAGE) {
      t = t.enclosingElement
    }
    return t as PackageElement
  }

  val names = mutableListOf<String>()
  var e: Element = this
  while (isClassOrInterface(e)) {
    val eType = e as TypeElement
    require(eType.nestingKind.isOneOf(TOP_LEVEL, MEMBER)) {
      "unexpected type testing"
    }
    names += eType.simpleName.toString()
    e = eType.enclosingElement
  }
  names += getPackage(this).qualifiedName.toString()
  names.reverse()
  return ClassName(names)
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://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.
 */
@file:JvmName("CodeBlocks")

package com.squareup.kotlinpoet

import java.lang.reflect.Type
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import javax.lang.model.element.Element
import javax.lang.model.type.TypeMirror
import kotlin.reflect.KClass

/**
 * A fragment of a .kt file, potentially containing declarations, statements, and documentation.
 * Code blocks are not necessarily well-formed Kotlin code, and are not validated. This class
 * assumes kotlinc will check correctness later!
 *
 * Code blocks support placeholders like [java.text.Format]. This class primarily uses a percent
 * sign `%` but has its own set of permitted placeholders:
 *
 *  * `%L` emits a *literal* value with no escaping. Arguments for literals may be strings,
 *    primitives, [type declarations][TypeSpec], [annotations][AnnotationSpec] and even other code
 *    blocks.
 *  * `%N` emits a *name*, using name collision avoidance where necessary. Arguments for names may
 *    be strings (actually any [character sequence][CharSequence]), [parameters][ParameterSpec],
 *    [properties][PropertySpec], [functions][FunSpec], and [types][TypeSpec].
 *  * `%S` escapes the value as a *string*, wraps it with double quotes, and emits that. For
 *    example, `6" sandwich` is emitted `"6\" sandwich"`. `%S` will also escape all dollar signs
 *    (`$`), use `%P` for string templates.
 *  * `%P` - Similar to `%S`, but doesn't escape dollar signs (`$`) to allow creation of string
 *    templates. If the string contains dollar signs that should be escaped - use `%S`.
 *  * `%T` emits a *type* reference. Types will be imported if possible. Arguments for types may be
 *    [classes][Class].
 *  * `%M` emits a *member* reference. A member is either a function or a property. If the member is
 *    importable, e.g. it's a top-level function or a property declared inside an object, the import
 *    will be resolved if possible. Arguments for members must be of type [MemberName].
 *  * `%%` emits a percent sign.
 *  * `·` emits a space that never wraps. KotlinPoet prefers to wrap lines longer than 100 columns.
 *    It does this by replacing normal spaces with a newline and indent. Note that spaces in strings
 *    are never wrapped.
 *  * `⇥` increases the indentation level.
 *  * `⇤` decreases the indentation level.
 *  * `«` begins a statement. For multiline statements, every line after the first line is
 *    double-indented.
 *  * `»` ends a statement.
 */
public class CodeBlock private constructor(
  internal val formatParts: List<String>,
  internal val args: List<Any?>,
) {
  /** A heterogeneous list containing string literals and value placeholders.  */

  public fun isEmpty(): Boolean = formatParts.isEmpty()

  public fun isNotEmpty(): Boolean = !isEmpty()

  /**
   * Returns a code block with `prefix` stripped off, or null if this code block doesn't start with
   * `prefix`.
   *
   * This is a pretty basic implementation that might not cover cases like mismatched whitespace. We
   * could offer something more lenient if necessary.
   */
  internal fun withoutPrefix(prefix: CodeBlock): CodeBlock? {
    if (formatParts.size < prefix.formatParts.size) return null
    if (args.size < prefix.args.size) return null

    var prefixArgCount = 0
    var firstFormatPart: String? = null

    // Walk through the formatParts of prefix to confirm that it's a of this.
    prefix.formatParts.forEachIndexed { index, formatPart ->
      if (formatParts[index] != formatPart) {
        // We've found a format part that doesn't match. If this is the very last format part check
        // for a string prefix match. If that doesn't match, we're done.
        if (index == prefix.formatParts.size - 1 && formatParts[index].startsWith(formatPart)) {
          firstFormatPart = formatParts[index].substring(formatPart.length)
        } else {
          return null
        }
      }

      // If the matching format part has an argument, check that too.
      if (formatPart.startsWith("%") && !formatPart[1].isMultiCharNoArgPlaceholder) {
        if (args[prefixArgCount] != prefix.args[prefixArgCount]) {
          return null // Argument doesn't match.
        }
        prefixArgCount++
      }
    }

    // We found a prefix. Prepare the suffix as a result.
    val resultFormatParts = ArrayList<String>()
    firstFormatPart?.let {
      resultFormatParts.add(it)
    }
    for (i in prefix.formatParts.size until formatParts.size) {
      resultFormatParts.add(formatParts[i])
    }

    val resultArgs = ArrayList<Any?>()
    for (i in prefix.args.size until args.size) {
      resultArgs.add(args[i])
    }

    return CodeBlock(resultFormatParts, resultArgs)
  }

  /**
   * Returns a copy of the code block without leading and trailing no-arg placeholders
   * (`⇥`, `⇤`, `«`, `»`).
   */
  internal fun trim(): CodeBlock {
    var start = 0
    var end = formatParts.size
    while (start < end && formatParts[start] in NO_ARG_PLACEHOLDERS) {
      start++
    }
    while (start < end && formatParts[end - 1] in NO_ARG_PLACEHOLDERS) {
      end--
    }
    return when {
      start > 0 || end < formatParts.size -> CodeBlock(formatParts.subList(start, end), args)
      else -> this
    }
  }

  /**
   * Returns a copy of the code block with selected format parts replaced, similar to
   * [java.lang.String.replaceAll].
   *
   * **Warning!** This method leaves the arguments list unchanged. Take care when replacing
   * placeholders with arguments, such as `%L`, as it can result in a code block, where
   * placeholders don't match their arguments.
   */
  internal fun replaceAll(oldValue: String, newValue: String) =
    CodeBlock(formatParts.map { it.replace(oldValue, newValue) }, args)

  internal fun hasStatements() = formatParts.any { "«" in it }

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

  override fun hashCode(): Int = toString().hashCode()

  override fun toString(): String = buildCodeString { emitCode(this@CodeBlock) }

  internal fun toString(codeWriter: CodeWriter): String = buildCodeString(codeWriter) {
    emitCode(this@CodeBlock)
  }

  public fun toBuilder(): Builder {
    val builder = Builder()
    builder.formatParts += formatParts
    builder.args.addAll(args)
    return builder
  }

  public class Builder {
    internal val formatParts = mutableListOf<String>()
    internal val args = mutableListOf<Any?>()

    public fun isEmpty(): Boolean = formatParts.isEmpty()

    public fun isNotEmpty(): Boolean = !isEmpty()

    /**
     * Adds code using named arguments.
     *
     * Named arguments specify their name after the '%' followed by : and the corresponding type
     * character. Argument names consist of characters in `a-z, A-Z, 0-9, and _` and must start
     * with a lowercase character.
     *
     * For example, to refer to the type [java.lang.Integer] with the argument name `clazz` use a
     * format string containing `%clazz:T` and include the key `clazz` with value
     * `java.lang.Integer.class` in the argument map.
     */
    public fun addNamed(format: String, arguments: Map<String, *>): Builder = apply {
      var p = 0

      for (argument in arguments.keys) {
        require(LOWERCASE matches argument) {
          "argument '$argument' must start with a lowercase character"
        }
      }

      while (p < format.length) {
        val nextP = format.nextPotentialPlaceholderPosition(startIndex = p)
        if (nextP == -1) {
          formatParts += format.substring(p, format.length)
          break
        }

        if (p != nextP) {
          formatParts += format.substring(p, nextP)
          p = nextP
        }

        var matchResult: MatchResult? = null
        val colon = format.indexOf(':', p)
        if (colon != -1) {
          val endIndex = (colon + 2).coerceAtMost(format.length)
          matchResult = NAMED_ARGUMENT.matchEntire(format.substring(p, endIndex))
        }
        if (matchResult != null) {
          val argumentName = matchResult.groupValues[ARG_NAME]
          require(arguments.containsKey(argumentName)) {
            "Missing named argument for %$argumentName"
          }
          val formatChar = matchResult.groupValues[TYPE_NAME].first()
          addArgument(format, formatChar, arguments[argumentName])
          formatParts += "%$formatChar"
          p += matchResult.range.last + 1
        } else if (format[p].isSingleCharNoArgPlaceholder) {
          formatParts += format.substring(p, p + 1)
          p++
        } else {
          require(p < format.length - 1) { "dangling % at end" }
          require(format[p + 1].isMultiCharNoArgPlaceholder) {
            "unknown format %${format[p + 1]} at ${p + 1} in '$format'"
          }
          formatParts += format.substring(p, p + 2)
          p += 2
        }
      }
    }

    /**
     * Add code with positional or relative arguments.
     *
     * Relative arguments map 1:1 with the placeholders in the format string.
     *
     * Positional arguments use an index after the placeholder to identify which argument index
     * to use. For example, for a literal to reference the 3rd argument: "%3L" (1 based index)
     *
     * Mixing relative and positional arguments in a call to add is invalid and will result in an
     * error.
     */
    public fun add(format: String, vararg args: Any?): Builder = apply {
      var hasRelative = false
      var hasIndexed = false

      var relativeParameterCount = 0
      val indexedParameterCount = IntArray(args.size)

      var p = 0
      while (p < format.length) {
        if (format[p].isSingleCharNoArgPlaceholder) {
          formatParts += format[p].toString()
          p++
          continue
        }

        if (format[p] != '%') {
          var nextP = format.nextPotentialPlaceholderPosition(startIndex = p + 1)
          if (nextP == -1) nextP = format.length
          formatParts += format.substring(p, nextP)
          p = nextP
          continue
        }

        p++ // '%'.

        // Consume zero or more digits, leaving 'c' as the first non-digit char after the '%'.
        val indexStart = p
        var c: Char
        do {
          require(p < format.length) { "dangling format characters in '$format'" }
          c = format[p++]
        } while (c in '0'..'9')
        val indexEnd = p - 1

        // If 'c' doesn't take an argument, we're done.
        if (c.isMultiCharNoArgPlaceholder) {
          require(indexStart == indexEnd) { "%% may not have an index" }
          formatParts += "%$c"
          continue
        }

        // Find either the indexed argument, or the relative argument. (0-based).
        val index: Int
        if (indexStart < indexEnd) {
          index = Integer.parseInt(format.substring(indexStart, indexEnd)) - 1
          hasIndexed = true
          if (args.isNotEmpty()) {
            indexedParameterCount[index % args.size]++ // modulo is needed, checked below anyway
          }
        } else {
          index = relativeParameterCount
          hasRelative = true
          relativeParameterCount++
        }

        require(index >= 0 && index < args.size) {
          "index ${index + 1} for '${format.substring(
            indexStart - 1,
            indexEnd + 1,
          )}' not in range (received ${args.size} arguments)"
        }
        require(!hasIndexed || !hasRelative) { "cannot mix indexed and positional parameters" }

        addArgument(format, c, args[index])

        formatParts += "%$c"
      }

      if (hasRelative) {
        require(relativeParameterCount >= args.size) {
          "unused arguments: expected $relativeParameterCount, received ${args.size}"
        }
      }
      if (hasIndexed) {
        val unused = mutableListOf<String>()
        for (i in args.indices) {
          if (indexedParameterCount[i] == 0) {
            unused += "%" + (i + 1)
          }
        }
        val s = if (unused.size == 1) "" else "s"
        require(unused.isEmpty()) { "unused argument$s: ${unused.joinToString(", ")}" }
      }
    }

    private fun addArgument(format: String, c: Char, arg: Any?) {
      when (c) {
        'N' -> this.args += argToName(arg).escapeIfNecessary()
        'L' -> this.args += argToLiteral(arg)
        'S' -> this.args += argToString(arg)
        'P' -> this.args += if (arg is CodeBlock) arg else argToString(arg)
        'T' -> this.args += argToType(arg)
        'M' -> this.args += arg
        else -> throw IllegalArgumentException(
          String.format("invalid format string: '%s'", format),
        )
      }
    }

    private fun argToName(o: Any?) = when (o) {
      is CharSequence -> o.toString()
      is ParameterSpec -> o.name
      is PropertySpec -> o.name
      is FunSpec -> o.name
      is TypeSpec -> o.name!!
      is MemberName -> o.simpleName
      else -> throw IllegalArgumentException("expected name but was $o")
    }

    private fun argToLiteral(o: Any?) = if (o is Number) formatNumericValue(o) else o

    private fun argToString(o: Any?) = o?.toString()

    private fun formatNumericValue(o: Number): Any? {
      val format = DecimalFormatSymbols().apply {
        decimalSeparator = '.'
        groupingSeparator = '_'
      }

      val precision = if (o is Float || o is Double) o.toString().split(".").last().length else 0

      val pattern = when (o) {
        is Float, is Double -> "###,##0.0" + "#".repeat(precision - 1)
        else -> "###,##0"
      }

      return DecimalFormat(pattern, format).format(o)
    }

    private fun logDeprecationWarning(o: Any) {
      println(
        "Deprecation warning: converting $o to TypeName. Conversion of TypeMirror and" +
          " TypeElement is deprecated in KotlinPoet, use kotlin-metadata APIs instead.",
      )
    }

    private fun argToType(o: Any?) = when (o) {
      is TypeName -> o
      is TypeMirror -> {
        logDeprecationWarning(o)
        o.asTypeName()
      }
      is Element -> {
        logDeprecationWarning(o)
        o.asType().asTypeName()
      }
      is Type -> o.asTypeName()
      is KClass<*> -> o.asTypeName()
      else -> throw IllegalArgumentException("expected type but was $o")
    }

    /**
     * @param controlFlow the control flow construct and its code, such as `if (foo == 5)`.
     *     Shouldn't contain newline characters. Can contain opening braces, e.g.
     *     `beginControlFlow("list.forEach { element ->")`. If there's no opening brace at the end
     *     of the string, it will be added.
     */
    public fun beginControlFlow(controlFlow: String, vararg args: Any?): Builder = apply {
      add(controlFlow.withOpeningBrace(), *args)
      indent()
    }

    private fun String.withOpeningBrace(): String {
      for (i in length - 1 downTo 0) {
        if (this[i] == '{') {
          return "$this\n"
        } else if (this[i] == '}') {
          break
        }
      }
      return "$this·{\n"
    }

    /**
     * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
     *     Shouldn't contain braces or newline characters.
     */
    public fun nextControlFlow(controlFlow: String, vararg args: Any?): Builder = apply {
      unindent()
      add("}·$controlFlow·{\n", *args)
      indent()
    }

    public fun endControlFlow(): Builder = apply {
      unindent()
      add("}\n")
    }

    public fun addStatement(format: String, vararg args: Any?): Builder = apply {
      add("«")
      add(format, *args)
      add("\n»")
    }

    public fun add(codeBlock: CodeBlock): Builder = apply {
      formatParts += codeBlock.formatParts
      args.addAll(codeBlock.args)
    }

    public fun indent(): Builder = apply {
      formatParts += "⇥"
    }

    public fun unindent(): Builder = apply {
      formatParts += "⇤"
    }

    public fun clear(): Builder = apply {
      formatParts.clear()
      args.clear()
    }

    public fun build(): CodeBlock = CodeBlock(formatParts.toImmutableList(), args.toImmutableList())
  }

  public companion object {
    private val NAMED_ARGUMENT = Regex("%([\\w_]+):([\\w]).*")
    private val LOWERCASE = Regex("[a-z]+[\\w_]*")
    private const val ARG_NAME = 1
    private const val TYPE_NAME = 2
    private val NO_ARG_PLACEHOLDERS = setOf("⇥", "⇤", "«", "»")
    internal val EMPTY = CodeBlock(emptyList(), emptyList())

    @JvmStatic public fun of(format: String, vararg args: Any?): CodeBlock =
      Builder().add(format, *args).build()

    @JvmStatic public fun builder(): Builder = Builder()

    internal val Char.isMultiCharNoArgPlaceholder get() = this == '%'
    internal val Char.isSingleCharNoArgPlaceholder get() = isOneOf('⇥', '⇤', '«', '»')
    internal val String.isPlaceholder
      get() = (length == 1 && first().isSingleCharNoArgPlaceholder) ||
        (length == 2 && first().isMultiCharNoArgPlaceholder)

    internal fun String.nextPotentialPlaceholderPosition(startIndex: Int) =
      indexOfAny(charArrayOf('%', '«', '»', '⇥', '⇤'), startIndex)
  }
}

@JvmOverloads
public fun Collection<CodeBlock>.joinToCode(
  separator: CharSequence = ", ",
  prefix: CharSequence = "",
  suffix: CharSequence = "",
): CodeBlock {
  val blocks = toTypedArray()
  val placeholders = Array(blocks.size) { "%L" }
  return CodeBlock.of(placeholders.joinToString(separator, prefix, suffix), *blocks)
}

/**
 * Builds new [CodeBlock] by populating newly created [CodeBlock.Builder] using provided
 * [builderAction] and then converting it to [CodeBlock].
 */
public inline fun buildCodeBlock(builderAction: CodeBlock.Builder.() -> Unit): CodeBlock {
  return CodeBlock.builder().apply(builderAction).build()
}

/**
 * Calls [CodeBlock.Builder.indent] then executes the provided [builderAction] on the
 * [CodeBlock.Builder] and then executes [CodeBlock.Builder.unindent] before returning the
 * original [CodeBlock.Builder].
 */
public inline fun CodeBlock.Builder.withIndent(builderAction: CodeBlock.Builder.() -> Unit): CodeBlock.Builder {
  return indent().also(builderAction).unindent()
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import java.io.Closeable
import kotlin.math.min

/** Sentinel value that indicates that no user-provided package has been set.  */
private val NO_PACKAGE = String()

internal val NULLABLE_ANY = ANY.copy(nullable = true)

private fun extractMemberName(part: String): String {
  require(Character.isJavaIdentifierStart(part[0])) { "not an identifier: $part" }
  for (i in 1..part.length) {
    if (!part.substring(0, i).isIdentifier) {
      return part.substring(0, i - 1)
    }
  }
  return part
}

internal inline fun buildCodeString(builderAction: CodeWriter.() -> Unit): String {
  val stringBuilder = StringBuilder()
  CodeWriter(stringBuilder, columnLimit = Integer.MAX_VALUE).use {
    it.builderAction()
  }
  return stringBuilder.toString()
}

internal fun buildCodeString(
  codeWriter: CodeWriter,
  builderAction: CodeWriter.() -> Unit,
): String {
  val stringBuilder = StringBuilder()
  codeWriter.emitInto(stringBuilder, builderAction)
  return stringBuilder.toString()
}

/**
 * Converts a [FileSpec] to a string suitable to both human- and kotlinc-consumption. This honors
 * imports, indentation, and deferred variable names.
 */
internal class CodeWriter constructor(
  out: Appendable,
  private val indent: String = DEFAULT_INDENT,
  imports: Map<String, Import> = emptyMap(),
  private val importedTypes: Map<String, ClassName> = emptyMap(),
  private val importedMembers: Map<String, MemberName> = emptyMap(),
  columnLimit: Int = 100,
) : Closeable {
  private var out = LineWrapper(out, indent, columnLimit)
  private var indentLevel = 0

  private var kdoc = false
  private var comment = false
  private var packageName = NO_PACKAGE
  private val typeSpecStack = mutableListOf<TypeSpec>()
  private val memberImportNames = mutableSetOf<String>()
  private val importableTypes = mutableMapOf<String, List<ClassName>>().withDefault { emptyList() }
  private val importableMembers = mutableMapOf<String, List<MemberName>>().withDefault { emptyList() }
  private val referencedNames = mutableSetOf<String>()
  private var trailingNewline = false

  val imports = imports.also {
    for ((memberName, _) in imports) {
      val lastDotIndex = memberName.lastIndexOf('.')
      if (lastDotIndex >= 0) {
        memberImportNames.add(memberName.substring(0, lastDotIndex))
      }
    }
  }

  /**
   * When emitting a statement, this is the line of the statement currently being written. The first
   * line of a statement is indented normally and subsequent wrapped lines are double-indented. This
   * is -1 when the currently-written line isn't part of a statement.
   */
  var statementLine = -1

  fun indent(levels: Int = 1) = apply {
    indentLevel += levels
  }

  fun unindent(levels: Int = 1) = apply {
    require(indentLevel - levels >= 0) { "cannot unindent $levels from $indentLevel" }
    indentLevel -= levels
  }

  fun pushPackage(packageName: String) = apply {
    check(this.packageName === NO_PACKAGE) { "package already set: ${this.packageName}" }
    this.packageName = packageName
  }

  fun popPackage() = apply {
    check(packageName !== NO_PACKAGE) { "package already set: $packageName" }
    packageName = NO_PACKAGE
  }

  fun pushType(type: TypeSpec) = apply {
    this.typeSpecStack.add(type)
  }

  fun popType() = apply {
    this.typeSpecStack.removeAt(typeSpecStack.size - 1)
  }

  fun emitComment(codeBlock: CodeBlock) {
    trailingNewline = true // Force the '//' prefix for the comment.
    comment = true
    try {
      emitCode(codeBlock)
      emit("\n")
    } finally {
      comment = false
    }
  }

  fun emitKdoc(kdocCodeBlock: CodeBlock) {
    if (kdocCodeBlock.isEmpty()) return

    emit("/**\n")
    kdoc = true
    try {
      emitCode(kdocCodeBlock, ensureTrailingNewline = true)
    } finally {
      kdoc = false
    }
    emit(" */\n")
  }

  fun emitAnnotations(annotations: List<AnnotationSpec>, inline: Boolean) {
    for (annotationSpec in annotations) {
      annotationSpec.emit(this, inline)
      emit(if (inline) " " else "\n")
    }
  }

  /**
   * Emits `modifiers` in the standard order. Modifiers in `implicitModifiers` will not
   * be emitted except for [KModifier.PUBLIC]
   */
  fun emitModifiers(
    modifiers: Set<KModifier>,
    implicitModifiers: Set<KModifier> = emptySet(),
  ) {
    if (shouldEmitPublicModifier(modifiers, implicitModifiers)) {
      emit(KModifier.PUBLIC.keyword)
      emit(" ")
    }
    val uniqueNonPublicExplicitOnlyModifiers =
      modifiers
        .filterNot { it == KModifier.PUBLIC }
        .filterNot { implicitModifiers.contains(it) }
        .toEnumSet()
    for (modifier in uniqueNonPublicExplicitOnlyModifiers) {
      emit(modifier.keyword)
      emit(" ")
    }
  }

  /**
   * Emits the `context` block for [contextReceivers].
   */
  fun emitContextReceivers(contextReceivers: List<TypeName>, suffix: String = "") {
    if (contextReceivers.isNotEmpty()) {
      val receivers = contextReceivers
        .map { CodeBlock.of("%T", it) }
        .joinToCode(prefix = "context(", suffix = ")")
      emitCode(receivers)
      emit(suffix)
    }
  }

  /**
   * Emit type variables with their bounds. If a type variable has more than a single bound - call
   * [emitWhereBlock] with same input to produce an additional `where` block.
   *
   * This should only be used when declaring type variables; everywhere else bounds are omitted.
   */
  fun emitTypeVariables(typeVariables: List<TypeVariableName>) {
    if (typeVariables.isEmpty()) return

    emit("<")
    typeVariables.forEachIndexed { index, typeVariable ->
      if (index > 0) emit(", ")
      if (typeVariable.variance != null) {
        emit("${typeVariable.variance.keyword} ")
      }
      if (typeVariable.isReified) {
        emit("reified ")
      }
      emitCode("%L", typeVariable.name)
      if (typeVariable.bounds.size == 1 && typeVariable.bounds[0] != NULLABLE_ANY) {
        emitCode(" : %T", typeVariable.bounds[0])
      }
    }
    emit(">")
  }

  /**
   * Emit a `where` block containing type bounds for each type variable that has at least two
   * bounds.
   */
  fun emitWhereBlock(typeVariables: List<TypeVariableName>) {
    if (typeVariables.isEmpty()) return

    var firstBound = true
    for (typeVariable in typeVariables) {
      if (typeVariable.bounds.size > 1) {
        for (bound in typeVariable.bounds) {
          if (!firstBound) emitCode(", ") else emitCode(" where ")
          emitCode("%L : %T", typeVariable.name, bound)
          firstBound = false
        }
      }
    }
  }

  fun emitCode(s: String) = emitCode(CodeBlock.of(s))

  fun emitCode(format: String, vararg args: Any?) = emitCode(CodeBlock.of(format, *args))

  fun emitCode(
    codeBlock: CodeBlock,
    isConstantContext: Boolean = false,
    ensureTrailingNewline: Boolean = false,
  ) = apply {
    var a = 0
    var deferredTypeName: ClassName? = null // used by "import static" logic
    val partIterator = codeBlock.formatParts.listIterator()
    while (partIterator.hasNext()) {
      when (val part = partIterator.next()) {
        "%L" -> emitLiteral(codeBlock.args[a++], isConstantContext)

        "%N" -> emit(codeBlock.args[a++] as String)

        "%S" -> {
          val string = codeBlock.args[a++] as String?
          // Emit null as a literal null: no quotes.
          val literal = if (string != null) {
            stringLiteralWithQuotes(
              string,
              isInsideRawString = false,
              isConstantContext = isConstantContext,
            )
          } else {
            "null"
          }
          emit(literal, nonWrapping = true)
        }

        "%P" -> {
          val string = codeBlock.args[a++]?.let { arg ->
            if (arg is CodeBlock) {
              arg.toString(this@CodeWriter)
            } else {
              arg as String?
            }
          }
          // Emit null as a literal null: no quotes.
          val literal = if (string != null) {
            stringLiteralWithQuotes(
              string,
              isInsideRawString = true,
              isConstantContext = isConstantContext,
            )
          } else {
            "null"
          }
          emit(literal, nonWrapping = true)
        }

        "%T" -> {
          var typeName = codeBlock.args[a++] as TypeName
          if (typeName.isAnnotated) {
            typeName.emitAnnotations(this)
            typeName = typeName.copy(annotations = emptyList())
          }
          // defer "typeName.emit(this)" if next format part will be handled by the default case
          var defer = false
          if (typeName is ClassName && partIterator.hasNext()) {
            if (!codeBlock.formatParts[partIterator.nextIndex()].startsWith("%")) {
              val candidate = typeName
              if (candidate.canonicalName in memberImportNames) {
                check(deferredTypeName == null) { "pending type for static import?!" }
                deferredTypeName = candidate
                defer = true
              }
            }
          }
          if (!defer) typeName.emit(this)
          typeName.emitNullable(this)
        }

        "%M" -> {
          val memberName = codeBlock.args[a++] as MemberName
          memberName.emit(this)
        }

        "%%" -> emit("%")

        "⇥" -> indent()

        "⇤" -> unindent()

        "«" -> {
          check(statementLine == -1) {
            """
            |Can't open a new statement until the current statement is closed (opening « followed
            |by another « without a closing »).
            |Current code block:
            |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
            |- Arguments: ${codeBlock.args}
            |
            """.trimMargin()
          }
          statementLine = 0
        }

        "»" -> {
          check(statementLine != -1) {
            """
            |Can't close a statement that hasn't been opened (closing » is not preceded by an
            |opening «).
            |Current code block:
            |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
            |- Arguments: ${codeBlock.args}
            |
            """.trimMargin()
          }
          if (statementLine > 0) {
            unindent(2) // End a multi-line statement. Decrease the indentation level.
          }
          statementLine = -1
        }

        else -> {
          // Handle deferred type.
          var doBreak = false
          if (deferredTypeName != null) {
            if (part.startsWith(".")) {
              if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
                // Okay, static import hit and all was emitted, so clean-up and jump to next part.
                deferredTypeName = null
                doBreak = true
              }
            }
            if (!doBreak) {
              deferredTypeName!!.emit(this)
              deferredTypeName = null
            }
          }
          if (!doBreak) {
            emit(part)
          }
        }
      }
    }
    if (ensureTrailingNewline && out.hasPendingSegments) {
      emit("\n")
    }
  }

  private fun emitStaticImportMember(canonical: String, part: String): Boolean {
    val partWithoutLeadingDot = part.substring(1)
    if (partWithoutLeadingDot.isEmpty()) return false
    val first = partWithoutLeadingDot[0]
    if (!Character.isJavaIdentifierStart(first)) return false
    val explicit = imports[canonical + "." + extractMemberName(partWithoutLeadingDot)]
    if (explicit != null) {
      if (explicit.alias != null) {
        val memberName = extractMemberName(partWithoutLeadingDot)
        emit(partWithoutLeadingDot.replaceFirst(memberName, explicit.alias))
      } else {
        emit(partWithoutLeadingDot)
      }
      return true
    }
    return false
  }

  private fun emitLiteral(o: Any?, isConstantContext: Boolean) {
    when (o) {
      is TypeSpec -> o.emit(this, null)
      is AnnotationSpec -> o.emit(this, inline = true, asParameter = isConstantContext)
      is PropertySpec -> o.emit(this, emptySet())
      is FunSpec -> o.emit(
        codeWriter = this,
        enclosingName = null,
        implicitModifiers = setOf(KModifier.PUBLIC),
        includeKdocTags = true,
      )
      is TypeAliasSpec -> o.emit(this)
      is CodeBlock -> emitCode(o, isConstantContext = isConstantContext)
      else -> emit(o.toString())
    }
  }

  /**
   * Returns the best name to identify `className` with in the current context. This uses the
   * available imports and the current scope to find the shortest name available. It does not honor
   * names visible due to inheritance.
   */
  fun lookupName(className: ClassName): String {
    // Find the shortest suffix of className that resolves to className. This uses both local type
    // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
    var nameResolved = false
    var c: ClassName? = className
    while (c != null) {
      val alias = imports[c.canonicalName]?.alias
      val simpleName = alias ?: c.simpleName
      val resolved = resolve(simpleName)
      nameResolved = resolved != null

      // We don't care about nullability and type annotations here, as it's irrelevant for imports.
      if (resolved == c.copy(nullable = false, annotations = emptyList())) {
        if (alias != null) return alias
        val suffixOffset = c.simpleNames.size - 1
        referencedNames.add(className.topLevelClassName().simpleName)
        return className.simpleNames.subList(
          suffixOffset,
          className.simpleNames.size,
        ).joinToString(".")
      }
      c = c.enclosingClassName()
    }

    // If the name resolved but wasn't a match, we're stuck with the fully qualified name.
    if (nameResolved) {
      return className.canonicalName
    }

    // If the class is in the same package, we're done.
    if (packageName == className.packageName) {
      referencedNames.add(className.topLevelClassName().simpleName)
      return className.simpleNames.joinToString(".")
    }

    // We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
    if (!kdoc) {
      importableType(className)
    }

    return className.canonicalName
  }

  fun lookupName(memberName: MemberName): String {
    val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
    // Match an imported member.
    val importedMember = importedMembers[simpleName]
    if (importedMember == memberName) {
      return simpleName
    } else if (importedMember != null && memberName.enclosingClassName != null) {
      val enclosingClassName = lookupName(memberName.enclosingClassName)
      return "$enclosingClassName.$simpleName"
    }

    // If the member is in the same package, we're done.
    if (packageName == memberName.packageName && memberName.enclosingClassName == null) {
      referencedNames.add(memberName.simpleName)
      return memberName.simpleName
    }

    // We'll have to use the fully-qualified name.
    // Mark the member as importable for a future pass unless the name clashes with
    // a method in the current context
    if (!kdoc && (
        memberName.isExtension ||
          !isMethodNameUsedInCurrentContext(memberName.simpleName)
        )
    ) {
      importableMember(memberName)
    }

    return memberName.canonicalName
  }

  // TODO(luqasn): also honor superclass members when resolving names.
  private fun isMethodNameUsedInCurrentContext(simpleName: String): Boolean {
    for (it in typeSpecStack.reversed()) {
      if (it.funSpecs.any { it.name == simpleName }) {
        return true
      }
      if (!it.modifiers.contains(KModifier.INNER)) {
        break
      }
    }
    return false
  }

  private fun importableType(className: ClassName) {
    val topLevelClassName = className.topLevelClassName()
    val simpleName = imports[className.canonicalName]?.alias ?: topLevelClassName.simpleName
    // Check for name clashes with members.
    if (simpleName !in importableMembers) {
      importableTypes[simpleName] = importableTypes.getValue(simpleName) + topLevelClassName
    }
  }

  private fun importableMember(memberName: MemberName) {
    if (memberName.packageName.isNotEmpty()) {
      val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
      // Check for name clashes with types.
      if (simpleName !in importableTypes) {
        importableMembers[simpleName] = importableMembers.getValue(simpleName) + memberName
      }
    }
  }

  /**
   * Returns the class or enum value referenced by `simpleName`, using the current nesting context and
   * imports.
   */
  // TODO(jwilson): also honor superclass members when resolving names.
  private fun resolve(simpleName: String): ClassName? {
    // Match a child of the current (potentially nested) class.
    for (i in typeSpecStack.indices.reversed()) {
      val typeSpec = typeSpecStack[i]
      if (simpleName in typeSpec.nestedTypesSimpleNames) {
        return stackClassName(i, simpleName)
      }
    }

    if (typeSpecStack.size > 0) {
      val typeSpec = typeSpecStack[0]
      if (typeSpec.name == simpleName) {
        // Match the top-level class.
        return ClassName(packageName, simpleName)
      }
      if (typeSpec.isEnum && typeSpec.enumConstants.keys.contains(simpleName)) {
        // Match a top level enum value.
        // Enum values are not proper classes but can still be modeled using ClassName.
        return ClassName(packageName, typeSpec.name!!).nestedClass(simpleName)
      }
    }

    // Match an imported type.
    val importedType = importedTypes[simpleName]
    if (importedType != null) return importedType

    // No match.
    return null
  }

  /** Returns the class named `simpleName` when nested in the class at `stackDepth`.  */
  private fun stackClassName(stackDepth: Int, simpleName: String): ClassName {
    var className = ClassName(packageName, typeSpecStack[0].name!!)
    for (i in 1..stackDepth) {
      className = className.nestedClass(typeSpecStack[i].name!!)
    }
    return className.nestedClass(simpleName)
  }

  /**
   * Emits `s` with indentation as required. It's important that all code that writes to
   * [CodeWriter.out] does it through here, since we emit indentation lazily in order to avoid
   * unnecessary trailing whitespace.
   */
  fun emit(s: String, nonWrapping: Boolean = false) = apply {
    var first = true
    for (line in s.split('\n')) {
      // Emit a newline character. Make sure blank lines in KDoc & comments look good.
      if (!first) {
        if ((kdoc || comment) && trailingNewline) {
          emitIndentation()
          out.appendNonWrapping(if (kdoc) " *" else "//")
        }
        out.newline()
        trailingNewline = true
        if (statementLine != -1) {
          if (statementLine == 0) {
            indent(2) // Begin multiple-line statement. Increase the indentation level.
          }
          statementLine++
        }
      }

      first = false
      if (line.isEmpty()) continue // Don't indent empty lines.

      // Emit indentation and comment prefix if necessary.
      if (trailingNewline) {
        emitIndentation()
        if (kdoc) {
          out.appendNonWrapping(" * ")
        } else if (comment) {
          out.appendNonWrapping("// ")
        }
      }

      if (nonWrapping) {
        out.appendNonWrapping(line)
      } else {
        out.append(
          line,
          indentLevel = if (kdoc) indentLevel else indentLevel + 2,
          linePrefix = if (kdoc) " * " else "",
        )
      }
      trailingNewline = false
    }
  }

  private fun emitIndentation() {
    for (j in 0 until indentLevel) {
      out.appendNonWrapping(indent)
    }
  }

  /**
   * Returns whether a [KModifier.PUBLIC] should be emitted.
   *
   * If [modifiers] contains [KModifier.PUBLIC], this method always returns `true`.
   *
   * Otherwise, this will return `true` when [KModifier.PUBLIC] is one of the [implicitModifiers]
   * and there are no other opposing modifiers (like [KModifier.PROTECTED] etc.) supplied by the
   * consumer in [modifiers].
   */
  private fun shouldEmitPublicModifier(
    modifiers: Set<KModifier>,
    implicitModifiers: Set<KModifier>,
  ): Boolean {
    if (modifiers.contains(KModifier.PUBLIC)) {
      return true
    }

    if (!implicitModifiers.contains(KModifier.PUBLIC)) {
      return false
    }

    val hasOtherConsumerSpecifiedVisibility =
      modifiers.containsAnyOf(KModifier.PRIVATE, KModifier.INTERNAL, KModifier.PROTECTED)

    return !hasOtherConsumerSpecifiedVisibility
  }

  /**
   * Returns the types that should have been imported for this code. If there were any simple name
   * collisions, import aliases will be generated.
   */
  private fun suggestedTypeImports(): Map<String, Set<ClassName>> {
    return importableTypes.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
  }

  /**
   * Returns the members that should have been imported for this code. If there were any simple name
   * collisions, import aliases will be generated.
   */
  private fun suggestedMemberImports(): Map<String, Set<MemberName>> {
    return importableMembers.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
  }

  /**
   * Perform emitting actions on the current [CodeWriter] using a custom [Appendable]. The
   * [CodeWriter] will continue using the old [Appendable] after this method returns.
   */
  inline fun emitInto(out: Appendable, action: CodeWriter.() -> Unit) {
    val codeWrapper = this
    LineWrapper(out, indent = DEFAULT_INDENT, columnLimit = Int.MAX_VALUE).use { newOut ->
      val oldOut = codeWrapper.out
      codeWrapper.out = newOut
      action()
      codeWrapper.out = oldOut
    }
  }

  override fun close() {
    out.close()
  }

  companion object {
    /**
     * Makes a pass to collect imports by executing [emitStep], and returns an instance of
     * [CodeWriter] pre-initialized with collected imports.
     */
    fun withCollectedImports(
      out: Appendable,
      indent: String,
      memberImports: Map<String, Import>,
      emitStep: (importsCollector: CodeWriter) -> Unit,
    ): CodeWriter {
      // First pass: emit the entire class, just to collect the types we'll need to import.
      val importsCollector = CodeWriter(
        NullAppendable,
        indent,
        memberImports,
        columnLimit = Integer.MAX_VALUE,
      )
      emitStep(importsCollector)
      val generatedImports = mutableMapOf<String, Import>()
      val suggestedTypeImports = importsCollector.suggestedTypeImports()
        .generateImports(
          generatedImports,
          canonicalName = ClassName::canonicalName,
          packageName = ClassName::packageName,
          capitalizeAliases = true,
        )
      val suggestedMemberImports = importsCollector.suggestedMemberImports()
        .generateImports(
          generatedImports,
          canonicalName = MemberName::canonicalName,
          packageName = MemberName::packageName,
          capitalizeAliases = false,
        )
      importsCollector.close()

      return CodeWriter(
        out,
        indent,
        memberImports + generatedImports.filterKeys { it !in memberImports },
        suggestedTypeImports,
        suggestedMemberImports,
      )
    }

    private fun <T> Map<String, Set<T>>.generateImports(
      generatedImports: MutableMap<String, Import>,
      canonicalName: T.() -> String,
      packageName: T.() -> String,
      capitalizeAliases: Boolean,
    ): Map<String, T> {
      return flatMap { (simpleName, qualifiedNames) ->
        if (qualifiedNames.size == 1) {
          listOf(simpleName to qualifiedNames.first()).also {
            val canonicalName = qualifiedNames.first().canonicalName()
            generatedImports[canonicalName] = Import(canonicalName)
          }
        } else {
          generateImportAliases(simpleName, qualifiedNames, packageName, capitalizeAliases)
            .onEach { (alias, qualifiedName) ->
              val canonicalName = qualifiedName.canonicalName()
              generatedImports[canonicalName] = Import(canonicalName, alias)
            }
        }
      }.toMap()
    }

    private fun <T> generateImportAliases(
      simpleName: String,
      qualifiedNames: Set<T>,
      packageName: T.() -> String,
      capitalizeAliases: Boolean,
    ): List<Pair<String, T>> {
      val packageNameSegments = qualifiedNames.associateWith { qualifiedName ->
        qualifiedName.packageName().split('.').map { it.replaceFirstChar(Char::uppercaseChar) }
      }
      val aliasNames = mutableMapOf<String, T>()
      var segmentsToUse = 0
      // Iterate until we have unique aliases for all names.
      while (aliasNames.size != qualifiedNames.size) {
        segmentsToUse += 1
        aliasNames.clear()
        for ((qualifiedName, segments) in packageNameSegments) {
          val aliasPrefix = segments.takeLast(min(segmentsToUse, segments.size))
            .joinToString(separator = "")
            .replaceFirstChar { if (!capitalizeAliases) it.lowercaseChar() else it }
          val aliasName = aliasPrefix + simpleName.replaceFirstChar(Char::uppercaseChar)
          aliasNames[aliasName] = qualifiedName
        }
      }
      return aliasNames.toList()
    }
  }
}

/*
 * Copyright (C) 2022 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

/** A KotlinPoet spec type that can have a context receiver. */
public interface ContextReceivable {

  /** The originating elements of this type. */
  @ExperimentalKotlinPoetApi
  public val contextReceiverTypes: List<TypeName>

  /** The builder analogue to [ContextReceivable] types. */
  public interface Builder<out T : Builder<T>> {

    /** Mutable map of the current originating elements this builder contains. */
    @ExperimentalKotlinPoetApi
    public val contextReceiverTypes: MutableList<TypeName>

    /** Adds the given [receiverTypes] to this type's list of originating elements. */
    @Suppress("UNCHECKED_CAST")
    @ExperimentalKotlinPoetApi
    public fun contextReceivers(receiverTypes: Iterable<TypeName>): T = apply {
      contextReceiverTypes += receiverTypes
    } as T

    /** Adds the given [receiverTypes] to this type's list of originating elements. */
    @ExperimentalKotlinPoetApi
    public fun contextReceivers(vararg receiverTypes: TypeName): T =
      contextReceivers(receiverTypes.toList())
  }
}

@ExperimentalKotlinPoetApi
internal fun ContextReceivable.Builder<*>.buildContextReceivers() =
  ContextReceivers(contextReceiverTypes.toImmutableList())

@JvmInline
@ExperimentalKotlinPoetApi
internal value class ContextReceivers(
  override val contextReceiverTypes: List<TypeName>,
) : ContextReceivable

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

/**
 * Marks declarations in the KotlinPoet API that are **delicate** —
 * they have limited use-case and shall be used with care in general code.
 * Any use of a delicate declaration has to be carefully reviewed to make sure it is
 * properly used and does not create problems like lossy Java -> Kotlin type parsing.
 * Carefully read documentation and [message] of any declaration marked as `DelicateKotlinPoetApi`.
 */
@MustBeDocumented
@Retention(value = AnnotationRetention.BINARY)
@RequiresOptIn(
  level = RequiresOptIn.Level.WARNING,
  message = "This is a delicate API and its use requires care." +
    " Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.",
)
public annotation class DelicateKotlinPoetApi(val message: String)

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import kotlin.reflect.KClass

public object Dynamic : TypeName(false, emptyList(), TagMap(emptyMap())) {

  override fun copy(
    nullable: Boolean,
    annotations: List<AnnotationSpec>,
    tags: Map<KClass<*>, Any>,
  ): Nothing = throw UnsupportedOperationException("dynamic doesn't support copying")

  override fun emit(out: CodeWriter) = out.apply {
    emit("dynamic")
  }
}

/*
 * Copyright (C) 2022 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY
import kotlin.annotation.AnnotationTarget.TYPEALIAS

/**
 * Indicates that a given API is experimental and subject to change.
 */
@RequiresOptIn
@Retention(AnnotationRetention.BINARY)
@Target(CLASS, FUNCTION, PROPERTY, TYPEALIAS)
public annotation class ExperimentalKotlinPoetApi

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
import java.io.ByteArrayInputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStreamWriter
import java.net.URI
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.Files
import java.nio.file.Path
import javax.annotation.processing.Filer
import javax.tools.JavaFileObject
import javax.tools.JavaFileObject.Kind
import javax.tools.SimpleJavaFileObject
import javax.tools.StandardLocation
import kotlin.reflect.KClass

/**
 * A Kotlin file containing top level objects like classes, objects, functions, properties, and type
 * aliases.
 *
 * Items are output in the following order:
 * - Comment
 * - Annotations
 * - Package
 * - Imports
 * - Members
 */
public class FileSpec private constructor(
  builder: Builder,
  private val tagMap: TagMap = builder.buildTagMap(),
) : Taggable by tagMap {
  public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
  public val comment: CodeBlock = builder.comment.build()
  public val packageName: String = builder.packageName
  public val name: String = builder.name
  public val members: List<Any> = builder.members.toList()
  public val defaultImports: Set<String> = builder.defaultImports.toSet()
  public val body: CodeBlock = builder.body.build()
  public val isScript: Boolean = builder.isScript
  private val memberImports = builder.memberImports.associateBy(Import::qualifiedName)
  private val indent = builder.indent
  private val extension = if (isScript) "kts" else "kt"

  @Throws(IOException::class)
  public fun writeTo(out: Appendable) {
    val codeWriter = CodeWriter.withCollectedImports(
      out = out,
      indent = indent,
      memberImports = memberImports,
      emitStep = { importsCollector -> emit(importsCollector, collectingImports = true) },
    )
    emit(codeWriter, collectingImports = false)
    codeWriter.close()
  }

  /** Writes this to `directory` as UTF-8 using the standard directory structure.  */
  @Throws(IOException::class)
  public fun writeTo(directory: Path) {
    require(Files.notExists(directory) || Files.isDirectory(directory)) {
      "path $directory exists but is not a directory."
    }
    var outputDirectory = directory
    if (packageName.isNotEmpty()) {
      for (packageComponent in packageName.split('.').dropLastWhile { it.isEmpty() }) {
        outputDirectory = outputDirectory.resolve(packageComponent)
      }
    }

    Files.createDirectories(outputDirectory)

    val outputPath = outputDirectory.resolve("$name.$extension")
    OutputStreamWriter(Files.newOutputStream(outputPath), UTF_8).use { writer -> writeTo(writer) }
  }

  /** Writes this to `directory` as UTF-8 using the standard directory structure.  */
  @Throws(IOException::class)
  public fun writeTo(directory: File): Unit = writeTo(directory.toPath())

  /** Writes this to `filer`.  */
  @Throws(IOException::class)
  public fun writeTo(filer: Filer) {
    val originatingElements = members.asSequence()
      .filterIsInstance<OriginatingElementsHolder>()
      .flatMap { it.originatingElements.asSequence() }
      .toSet()
    val filerSourceFile = filer.createResource(
      StandardLocation.SOURCE_OUTPUT,
      packageName,
      "$name.$extension",
      *originatingElements.toTypedArray(),
    )
    try {
      filerSourceFile.openWriter().use { writer -> writeTo(writer) }
    } catch (e: Exception) {
      try {
        filerSourceFile.delete()
      } catch (ignored: Exception) {
      }
      throw e
    }
  }

  private fun emit(codeWriter: CodeWriter, collectingImports: Boolean) {
    if (comment.isNotEmpty()) {
      codeWriter.emitComment(comment)
    }

    if (annotations.isNotEmpty()) {
      codeWriter.emitAnnotations(annotations, inline = false)
      codeWriter.emit("\n")
    }

    codeWriter.pushPackage(packageName)

    val escapedPackageName = packageName.escapeSegmentsIfNecessary()

    if (escapedPackageName.isNotEmpty()) {
      codeWriter.emitCode("package·%L\n", escapedPackageName)
      codeWriter.emit("\n")
    }

    // If we don't have default imports or are collecting them, we don't need to filter
    var isDefaultImport: (String) -> Boolean = { false }
    if (!collectingImports && defaultImports.isNotEmpty()) {
      val defaultImports = defaultImports.map(String::escapeSegmentsIfNecessary)
      isDefaultImport = { importName ->
        importName.substringBeforeLast(".") in defaultImports
      }
    }
    // Aliased imports should always appear at the bottom of the imports list.
    val (aliasedImports, nonAliasedImports) = codeWriter.imports.values
      .partition { it.alias != null }
    val imports = nonAliasedImports.asSequence().map { it.toString() }
      .filterNot(isDefaultImport)
      .toSortedSet()
      .plus(aliasedImports.map { it.toString() }.toSortedSet())

    if (imports.isNotEmpty()) {
      for (import in imports) {
        codeWriter.emitCode("import·%L", import)
        codeWriter.emit("\n")
      }
      codeWriter.emit("\n")
    }

    if (isScript) {
      codeWriter.emitCode(body)
    } else {
      members.forEachIndexed { index, member ->
        if (index > 0) codeWriter.emit("\n")
        when (member) {
          is TypeSpec -> member.emit(codeWriter, null)
          is FunSpec -> member.emit(codeWriter, null, setOf(KModifier.PUBLIC), true)
          is PropertySpec -> member.emit(codeWriter, setOf(KModifier.PUBLIC))
          is TypeAliasSpec -> member.emit(codeWriter)
          else -> throw AssertionError()
        }
      }
    }

    codeWriter.popPackage()
  }

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

  override fun hashCode(): Int = toString().hashCode()

  override fun toString(): String = buildString { writeTo(this) }

  public fun toJavaFileObject(): JavaFileObject {
    val uri = URI.create(
      if (packageName.isEmpty()) {
        name
      } else {
        packageName.replace('.', '/') + '/' + name
      } + ".$extension",
    )
    return object : SimpleJavaFileObject(uri, Kind.SOURCE) {
      private val lastModified = System.currentTimeMillis()
      override fun getCharContent(ignoreEncodingErrors: Boolean): String {
        return this@FileSpec.toString()
      }

      override fun openInputStream(): InputStream {
        return ByteArrayInputStream(getCharContent(true).toByteArray(UTF_8))
      }

      override fun getLastModified() = lastModified
    }
  }

  @JvmOverloads
  public fun toBuilder(packageName: String = this.packageName, name: String = this.name): Builder {
    val builder = Builder(packageName, name, isScript)
    builder.annotations.addAll(annotations)
    builder.comment.add(comment)
    builder.members.addAll(this.members)
    builder.indent = indent
    builder.memberImports.addAll(memberImports.values)
    builder.defaultImports.addAll(defaultImports)
    builder.tags += tagMap.tags
    builder.body.add(body)
    return builder
  }

  public class Builder internal constructor(
    public val packageName: String,
    public val name: String,
    public val isScript: Boolean,
  ) : Taggable.Builder<Builder> {
    internal val comment = CodeBlock.builder()
    internal val memberImports = sortedSetOf<Import>()
    internal var indent = DEFAULT_INDENT
    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()

    public val defaultImports: MutableSet<String> = mutableSetOf()
    public val imports: List<Import> get() = memberImports.toList()
    public val members: MutableList<Any> = mutableListOf()
    public val annotations: MutableList<AnnotationSpec> = mutableListOf()
    internal val body = CodeBlock.builder()

    /**
     * Add an annotation to the file.
     *
     * The annotation must either have a [`file` use-site target][AnnotationSpec.UseSiteTarget.FILE]
     * or not have a use-site target specified (in which case it will be changed to `file`).
     */
    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
      val spec = when (annotationSpec.useSiteTarget) {
        FILE -> annotationSpec
        null -> annotationSpec.toBuilder().useSiteTarget(FILE).build()
        else -> error(
          "Use-site target ${annotationSpec.useSiteTarget} not supported for file annotations.",
        )
      }
      annotations += spec
    }

    public fun addAnnotation(annotation: ClassName): Builder =
      addAnnotation(AnnotationSpec.builder(annotation).build())

    public fun addAnnotation(annotation: Class<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun addAnnotation(annotation: KClass<*>): Builder =
      addAnnotation(annotation.asClassName())

    /** Adds a file-site comment. This is prefixed to the start of the file and different from [addBodyComment]. */
    public fun addFileComment(format: String, vararg args: Any): Builder = apply {
      comment.add(format.replace(' ', '·'), *args)
    }

    @Deprecated(
      "Use addFileComment() instead.",
      ReplaceWith("addFileComment(format, args)"),
      DeprecationLevel.ERROR,
    )
    public fun addComment(format: String, vararg args: Any): Builder = addFileComment(format, *args)

    public fun clearComment(): Builder = apply {
      comment.clear()
    }

    public fun addType(typeSpec: TypeSpec): Builder = apply {
      if (isScript) {
        body.add("%L", typeSpec)
      } else {
        members += typeSpec
      }
    }

    public fun addFunction(funSpec: FunSpec): Builder = apply {
      require(!funSpec.isConstructor && !funSpec.isAccessor) {
        "cannot add ${funSpec.name} to file $name"
      }
      if (isScript) {
        body.add("%L", funSpec)
      } else {
        members += funSpec
      }
    }

    public fun addProperty(propertySpec: PropertySpec): Builder = apply {
      if (isScript) {
        body.add("%L", propertySpec)
      } else {
        members += propertySpec
      }
    }

    public fun addTypeAlias(typeAliasSpec: TypeAliasSpec): Builder = apply {
      if (isScript) {
        body.add("%L", typeAliasSpec)
      } else {
        members += typeAliasSpec
      }
    }

    public fun addImport(constant: Enum<*>): Builder = addImport(
      (constant as java.lang.Enum<*>).declaringClass.asClassName(),
      constant.name,
    )

    public fun addImport(`class`: Class<*>, vararg names: String): Builder = apply {
      require(names.isNotEmpty()) { "names array is empty" }
      addImport(`class`.asClassName(), names.toList())
    }

    public fun addImport(`class`: KClass<*>, vararg names: String): Builder = apply {
      require(names.isNotEmpty()) { "names array is empty" }
      addImport(`class`.asClassName(), names.toList())
    }

    public fun addImport(className: ClassName, vararg names: String): Builder = apply {
      require(names.isNotEmpty()) { "names array is empty" }
      addImport(className, names.toList())
    }

    public fun addImport(`class`: Class<*>, names: Iterable<String>): Builder =
      addImport(`class`.asClassName(), names)

    public fun addImport(`class`: KClass<*>, names: Iterable<String>): Builder =
      addImport(`class`.asClassName(), names)

    public fun addImport(className: ClassName, names: Iterable<String>): Builder = apply {
      require("*" !in names) { "Wildcard imports are not allowed" }
      for (name in names) {
        memberImports += Import(className.canonicalName + "." + name)
      }
    }

    public fun addImport(packageName: String, vararg names: String): Builder = apply {
      require(names.isNotEmpty()) { "names array is empty" }
      addImport(packageName, names.toList())
    }

    public fun addImport(packageName: String, names: Iterable<String>): Builder = apply {
      require("*" !in names) { "Wildcard imports are not allowed" }
      for (name in names) {
        memberImports += if (packageName.isNotEmpty()) {
          Import("$packageName.$name")
        } else {
          Import(name)
        }
      }
    }

    public fun addImport(import: Import): Builder = apply {
      memberImports += import
    }

    public fun clearImports(): Builder = apply {
      memberImports.clear()
    }

    public fun addAliasedImport(`class`: Class<*>, `as`: String): Builder =
      addAliasedImport(`class`.asClassName(), `as`)

    public fun addAliasedImport(`class`: KClass<*>, `as`: String): Builder =
      addAliasedImport(`class`.asClassName(), `as`)

    public fun addAliasedImport(className: ClassName, `as`: String): Builder = apply {
      memberImports += Import(className.canonicalName, `as`)
    }

    public fun addAliasedImport(
      className: ClassName,
      memberName: String,
      `as`: String,
    ): Builder = apply {
      memberImports += Import("${className.canonicalName}.$memberName", `as`)
    }

    public fun addAliasedImport(memberName: MemberName, `as`: String): Builder = apply {
      memberImports += Import(memberName.canonicalName, `as`)
    }

    /**
     * Adds a default import for the given [packageName].
     *
     * The format of this should be the qualified name of the package, e.g. `kotlin`, `java.lang`,
     * `org.gradle.api`, etc.
     */
    public fun addDefaultPackageImport(packageName: String): Builder = apply {
      defaultImports += packageName
    }

    /**
     * Adds Kotlin's standard default package imports as described
     * [here](https://kotlinlang.org/docs/packages.html#default-imports).
     */
    public fun addKotlinDefaultImports(
      includeJvm: Boolean = true,
      includeJs: Boolean = true,
    ): Builder = apply {
      defaultImports += KOTLIN_DEFAULT_IMPORTS
      if (includeJvm) {
        defaultImports += KOTLIN_DEFAULT_JVM_IMPORTS
      }
      if (includeJs) {
        defaultImports += KOTLIN_DEFAULT_JS_IMPORTS
      }
    }

    public fun indent(indent: String): Builder = apply {
      this.indent = indent
    }

    public fun addCode(format: String, vararg args: Any?): Builder = apply {
      check(isScript) {
        "addCode() is only allowed in script files"
      }
      body.add(format, *args)
    }

    public fun addNamedCode(format: String, args: Map<String, *>): Builder = apply {
      check(isScript) {
        "addNamedCode() is only allowed in script files"
      }
      body.addNamed(format, args)
    }

    public fun addCode(codeBlock: CodeBlock): Builder = apply {
      check(isScript) {
        "addCode() is only allowed in script files"
      }
      body.add(codeBlock)
    }

    /** Adds a comment to the body of this script file in the order that it was added. */
    public fun addBodyComment(format: String, vararg args: Any): Builder = apply {
      check(isScript) {
        "addBodyComment() is only allowed in script files"
      }
      body.add("//·${format.replace(' ', '·')}\n", *args)
    }

    /**
     * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
     * Shouldn't contain braces or newline characters.
     */
    public fun beginControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
      check(isScript) {
        "beginControlFlow() is only allowed in script files"
      }
      body.beginControlFlow(controlFlow, *args)
    }

    /**
     * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
     * Shouldn't contain braces or newline characters.
     */
    public fun nextControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
      check(isScript) {
        "nextControlFlow() is only allowed in script files"
      }
      body.nextControlFlow(controlFlow, *args)
    }

    public fun endControlFlow(): Builder = apply {
      check(isScript) {
        "endControlFlow() is only allowed in script files"
      }
      body.endControlFlow()
    }

    public fun addStatement(format: String, vararg args: Any): Builder = apply {
      check(isScript) {
        "addStatement() is only allowed in script files"
      }
      body.addStatement(format, *args)
    }

    public fun clearBody(): Builder = apply {
      check(isScript) {
        "clearBody() is only allowed in script files"
      }
      body.clear()
    }

    public fun build(): FileSpec {
      for (annotationSpec in annotations) {
        if (annotationSpec.useSiteTarget != FILE) {
          error(
            "Use-site target ${annotationSpec.useSiteTarget} not supported for file annotations.",
          )
        }
      }
      return FileSpec(this)
    }
  }

  public companion object {
    @JvmStatic public fun get(packageName: String, typeSpec: TypeSpec): FileSpec {
      val fileName = typeSpec.name
        ?: throw IllegalArgumentException("file name required but type has no name")
      return builder(packageName, fileName).addType(typeSpec).build()
    }

    @JvmStatic public fun builder(className: ClassName): Builder {
      require(className.simpleNames.size == 1) {
        "nested types can't be used to name a file: ${className.simpleNames.joinToString(".")}"
      }
      return builder(className.packageName, className.simpleName)
    }

    @JvmStatic public fun builder(packageName: String, fileName: String): Builder =
      Builder(packageName, fileName, isScript = false)

    @JvmStatic public fun scriptBuilder(fileName: String, packageName: String = ""): Builder =
      Builder(packageName, fileName, isScript = true)
  }
}

internal const val DEFAULT_INDENT = "  "

private val KOTLIN_DEFAULT_IMPORTS = setOf(
  "kotlin",
  "kotlin.annotation",
  "kotlin.collections",
  "kotlin.comparisons",
  "kotlin.io",
  "kotlin.ranges",
  "kotlin.sequences",
  "kotlin.text",
)
private val KOTLIN_DEFAULT_JVM_IMPORTS = setOf("java.lang")
private val KOTLIN_DEFAULT_JS_IMPORTS = setOf("kotlin.js")

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import com.squareup.kotlinpoet.KModifier.ABSTRACT
import com.squareup.kotlinpoet.KModifier.EXPECT
import com.squareup.kotlinpoet.KModifier.EXTERNAL
import com.squareup.kotlinpoet.KModifier.INLINE
import com.squareup.kotlinpoet.KModifier.VARARG
import java.lang.reflect.Type
import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.Modifier
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.ExecutableType
import javax.lang.model.type.TypeVariable
import javax.lang.model.util.Types
import kotlin.DeprecationLevel.WARNING
import kotlin.reflect.KClass

/** A generated function declaration. */
@OptIn(ExperimentalKotlinPoetApi::class)
public class FunSpec private constructor(
  builder: Builder,
  private val tagMap: TagMap = builder.buildTagMap(),
  private val delegateOriginatingElementsHolder: OriginatingElementsHolder = builder.buildOriginatingElements(),
  private val contextReceivers: ContextReceivers = builder.buildContextReceivers(),
) : Taggable by tagMap, OriginatingElementsHolder by delegateOriginatingElementsHolder, ContextReceivable by contextReceivers {
  public val name: String = builder.name
  public val kdoc: CodeBlock = builder.kdoc.build()
  public val returnKdoc: CodeBlock = builder.returnKdoc
  public val receiverKdoc: CodeBlock = builder.receiverKdoc
  public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
  public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
  public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
  public val receiverType: TypeName? = builder.receiverType

  public val returnType: TypeName? = builder.returnType
  public val parameters: List<ParameterSpec> = builder.parameters.toImmutableList()
  public val delegateConstructor: String? = builder.delegateConstructor
  public val delegateConstructorArguments: List<CodeBlock> =
    builder.delegateConstructorArguments.toImmutableList()
  public val body: CodeBlock = builder.body.build()
  private val isExternalGetter = name == GETTER && builder.modifiers.contains(EXTERNAL)
  private val isEmptySetter = name == SETTER && parameters.isEmpty()

  init {
    require(body.isEmpty() || !builder.modifiers.containsAnyOf(ABSTRACT, EXPECT)) {
      "abstract or expect function ${builder.name} cannot have code"
    }
    if (name == GETTER) {
      require(!isExternalGetter || body.isEmpty()) {
        "external getter cannot have code"
      }
    } else if (name == SETTER) {
      require(parameters.size <= 1) {
        "$name can have at most one parameter"
      }
      require(parameters.isNotEmpty() || body.isEmpty()) {
        "parameterless setter cannot have code"
      }
    }
    require(INLINE in modifiers || typeVariables.none { it.isReified }) {
      "only type parameters of inline functions can be reified!"
    }
  }

  internal fun parameter(name: String) = parameters.firstOrNull { it.name == name }

  internal fun emit(
    codeWriter: CodeWriter,
    enclosingName: String?,
    implicitModifiers: Set<KModifier>,
    includeKdocTags: Boolean = false,
  ) {
    if (includeKdocTags) {
      codeWriter.emitKdoc(kdocWithTags())
    } else {
      codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
    }
    codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
    codeWriter.emitAnnotations(annotations, false)
    codeWriter.emitModifiers(modifiers, implicitModifiers)

    if (!isConstructor && !name.isAccessor) {
      codeWriter.emitCode("fun·")
    }

    if (typeVariables.isNotEmpty()) {
      codeWriter.emitTypeVariables(typeVariables)
      codeWriter.emit(" ")
    }
    emitSignature(codeWriter, enclosingName)
    codeWriter.emitWhereBlock(typeVariables)

    if (shouldOmitBody(implicitModifiers)) {
      codeWriter.emit("\n")
      return
    }

    val asExpressionBody = body.asExpressionBody()

    if (asExpressionBody != null) {
      codeWriter.emitCode(CodeBlock.of(" = %L", asExpressionBody), ensureTrailingNewline = true)
    } else if (!isEmptySetter) {
      codeWriter.emitCode("·{\n")
      codeWriter.indent()
      codeWriter.emitCode(body.returnsWithoutLinebreak(), ensureTrailingNewline = true)
      codeWriter.unindent()
      codeWriter.emit("}\n")
    } else {
      codeWriter.emit("\n")
    }
  }

  private fun shouldOmitBody(implicitModifiers: Set<KModifier>): Boolean {
    if (canNotHaveBody(implicitModifiers)) {
      check(body.isEmpty()) { "function $name cannot have code" }
      return true
    }
    return canBodyBeOmitted(implicitModifiers) && body.isEmpty()
  }

  private fun canNotHaveBody(implicitModifiers: Set<KModifier>) =
    ABSTRACT in modifiers || EXPECT in modifiers + implicitModifiers

  private fun canBodyBeOmitted(implicitModifiers: Set<KModifier>) = isConstructor ||
    EXTERNAL in (modifiers + implicitModifiers) ||
    ABSTRACT in modifiers

  private fun emitSignature(codeWriter: CodeWriter, enclosingName: String?) {
    if (isConstructor) {
      codeWriter.emitCode("constructor", enclosingName)
    } else if (name == GETTER) {
      codeWriter.emitCode("get")
    } else if (name == SETTER) {
      codeWriter.emitCode("set")
    } else {
      if (receiverType != null) {
        if (receiverType is LambdaTypeName) {
          codeWriter.emitCode("(%T).", receiverType)
        } else {
          codeWriter.emitCode("%T.", receiverType)
        }
      }
      codeWriter.emitCode("%N", this)
    }

    if (!isEmptySetter && !isExternalGetter) {
      parameters.emit(codeWriter) { param ->
        param.emit(codeWriter, includeType = name != SETTER)
      }
    }

    if (returnType != null) {
      codeWriter.emitCode(": %T", returnType)
    } else if (emitUnitReturnType()) {
      codeWriter.emitCode(": %T", UNIT)
    }

    if (delegateConstructor != null) {
      codeWriter.emitCode(
        delegateConstructorArguments
          .joinToCode(prefix = " : $delegateConstructor(", suffix = ")"),
      )
    }
  }

  public val isConstructor: Boolean get() = name.isConstructor

  public val isAccessor: Boolean get() = name.isAccessor

  private fun kdocWithTags(): CodeBlock {
    return with(kdoc.ensureEndsWithNewLine().toBuilder()) {
      var newLineAdded = false
      val isNotEmpty = isNotEmpty()
      if (receiverKdoc.isNotEmpty()) {
        if (isNotEmpty) {
          add("\n")
          newLineAdded = true
        }
        add("@receiver %L", receiverKdoc.ensureEndsWithNewLine())
      }
      parameters.forEachIndexed { index, parameterSpec ->
        if (parameterSpec.kdoc.isNotEmpty()) {
          if (!newLineAdded && index == 0 && isNotEmpty) {
            add("\n")
            newLineAdded = true
          }
          add("@param %L %L", parameterSpec.name, parameterSpec.kdoc.ensureEndsWithNewLine())
        }
      }
      if (returnKdoc.isNotEmpty()) {
        if (!newLineAdded && isNotEmpty) {
          add("\n")
          newLineAdded = true
        }
        add("@return %L", returnKdoc.ensureEndsWithNewLine())
      }
      build()
    }
  }

  /**
   * Returns whether [Unit] should be emitted as the return type.
   *
   * [Unit] is emitted as return type on a function unless:
   *   - It's a constructor
   *   - It's a getter/setter on a property
   *   - It's an expression body
   */
  private fun emitUnitReturnType(): Boolean {
    if (isConstructor) {
      return false
    }
    if (name == GETTER || name == SETTER) {
      // Getter/setters don't emit return types
      return false
    }

    return body.asExpressionBody() == null
  }

  private fun CodeBlock.asExpressionBody(): CodeBlock? {
    val codeBlock = this.trim()
    val asReturnExpressionBody = codeBlock.withoutPrefix(RETURN_EXPRESSION_BODY_PREFIX_SPACE)
      ?: codeBlock.withoutPrefix(RETURN_EXPRESSION_BODY_PREFIX_NBSP)
    if (asReturnExpressionBody != null) {
      return asReturnExpressionBody
    }
    if (codeBlock.withoutPrefix(THROW_EXPRESSION_BODY_PREFIX_SPACE) != null ||
      codeBlock.withoutPrefix(THROW_EXPRESSION_BODY_PREFIX_NBSP) != null
    ) {
      return codeBlock
    }
    return null
  }

  private fun CodeBlock.returnsWithoutLinebreak(): CodeBlock {
    val returnWithSpace = RETURN_EXPRESSION_BODY_PREFIX_SPACE.formatParts[0]
    val returnWithNbsp = RETURN_EXPRESSION_BODY_PREFIX_NBSP.formatParts[0]
    var originCodeBlockBuilder: CodeBlock.Builder? = null
    for ((i, formatPart) in formatParts.withIndex()) {
      if (formatPart.startsWith(returnWithSpace)) {
        val builder = originCodeBlockBuilder ?: toBuilder()
        originCodeBlockBuilder = builder
        builder.formatParts[i] = formatPart.replaceFirst(returnWithSpace, returnWithNbsp)
      }
    }
    return originCodeBlockBuilder?.build() ?: this
  }

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

  override fun hashCode(): Int = toString().hashCode()

  override fun toString(): String = buildCodeString {
    emit(
      codeWriter = this,
      enclosingName = "Constructor",
      implicitModifiers = TypeSpec.Kind.CLASS.implicitFunctionModifiers(),
      includeKdocTags = true,
    )
  }

  @JvmOverloads
  public fun toBuilder(name: String = this.name): Builder {
    val builder = Builder(name)
    builder.kdoc.add(kdoc)
    builder.returnKdoc = returnKdoc
    builder.receiverKdoc = receiverKdoc
    builder.annotations += annotations
    builder.modifiers += modifiers
    builder.typeVariables += typeVariables
    builder.returnType = returnType
    builder.parameters += parameters
    builder.delegateConstructor = delegateConstructor
    builder.delegateConstructorArguments += delegateConstructorArguments
    builder.body.add(body)
    builder.receiverType = receiverType
    builder.tags += tagMap.tags
    builder.originatingElements += originatingElements
    builder.contextReceiverTypes += contextReceiverTypes
    return builder
  }

  public class Builder internal constructor(
    internal val name: String,
  ) : Taggable.Builder<Builder>, OriginatingElementsHolder.Builder<Builder>, ContextReceivable.Builder<Builder> {
    internal val kdoc = CodeBlock.builder()
    internal var returnKdoc = CodeBlock.EMPTY
    internal var receiverKdoc = CodeBlock.EMPTY
    internal var receiverType: TypeName? = null
    internal var returnType: TypeName? = null
    internal var delegateConstructor: String? = null
    internal var delegateConstructorArguments = listOf<CodeBlock>()
    internal val body = CodeBlock.builder()

    public val annotations: MutableList<AnnotationSpec> = mutableListOf()
    public val modifiers: MutableList<KModifier> = mutableListOf()
    public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
    public val parameters: MutableList<ParameterSpec> = mutableListOf()
    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
    override val originatingElements: MutableList<Element> = mutableListOf()
    override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()

    public fun addKdoc(format: String, vararg args: Any): Builder = apply {
      kdoc.add(format, *args)
    }

    public fun addKdoc(block: CodeBlock): Builder = apply {
      kdoc.add(block)
    }

    public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
      this.annotations += annotationSpecs
    }

    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
      annotations += annotationSpec
    }

    public fun addAnnotation(annotation: ClassName): Builder = apply {
      annotations += AnnotationSpec.builder(annotation).build()
    }

    public fun addAnnotation(annotation: Class<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun addAnnotation(annotation: KClass<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
      this.modifiers += modifiers
    }

    public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
      this.modifiers += modifiers
    }

    public fun jvmModifiers(modifiers: Iterable<Modifier>) {
      var visibility = KModifier.INTERNAL
      for (modifier in modifiers) {
        when (modifier) {
          Modifier.PUBLIC -> visibility = KModifier.PUBLIC
          Modifier.PROTECTED -> visibility = KModifier.PROTECTED
          Modifier.PRIVATE -> visibility = KModifier.PRIVATE
          Modifier.ABSTRACT -> this.modifiers += KModifier.ABSTRACT
          Modifier.FINAL -> this.modifiers += KModifier.FINAL
          Modifier.NATIVE -> this.modifiers += KModifier.EXTERNAL
          Modifier.DEFAULT -> Unit
          Modifier.STATIC -> addAnnotation(JvmStatic::class)
          Modifier.SYNCHRONIZED -> addAnnotation(Synchronized::class)
          Modifier.STRICTFP -> addAnnotation(Strictfp::class)
          else -> throw IllegalArgumentException("unexpected fun modifier $modifier")
        }
      }
      this.modifiers += visibility
    }

    public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
      this.typeVariables += typeVariables
    }

    public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
      typeVariables += typeVariable
    }

    @ExperimentalKotlinPoetApi
    override fun contextReceivers(receiverTypes: Iterable<TypeName>): Builder = apply {
      check(!name.isConstructor) { "constructors cannot have context receivers" }
      check(!name.isAccessor) { "$name cannot have context receivers" }
      contextReceiverTypes += receiverTypes
    }

    @JvmOverloads public fun receiver(
      receiverType: TypeName,
      kdoc: CodeBlock = CodeBlock.EMPTY,
    ): Builder = apply {
      check(!name.isConstructor) { "$name cannot have receiver type" }
      this.receiverType = receiverType
      this.receiverKdoc = kdoc
    }

    @JvmOverloads public fun receiver(
      receiverType: Type,
      kdoc: CodeBlock = CodeBlock.EMPTY,
    ): Builder = receiver(receiverType.asTypeName(), kdoc)

    public fun receiver(
      receiverType: Type,
      kdoc: String,
      vararg args: Any,
    ): Builder = receiver(receiverType, CodeBlock.of(kdoc, args))

    @JvmOverloads public fun receiver(
      receiverType: KClass<*>,
      kdoc: CodeBlock = CodeBlock.EMPTY,
    ): Builder = receiver(receiverType.asTypeName(), kdoc)

    public fun receiver(
      receiverType: KClass<*>,
      kdoc: String,
      vararg args: Any,
    ): Builder = receiver(receiverType, CodeBlock.of(kdoc, args))

    @JvmOverloads public fun returns(
      returnType: TypeName,
      kdoc: CodeBlock = CodeBlock.EMPTY,
    ): Builder = apply {
      check(!name.isConstructor && !name.isAccessor) { "$name cannot have a return type" }
      this.returnType = returnType
      this.returnKdoc = kdoc
    }

    @JvmOverloads public fun returns(returnType: Type, kdoc: CodeBlock = CodeBlock.EMPTY): Builder =
      returns(returnType.asTypeName(), kdoc)

    public fun returns(returnType: Type, kdoc: String, vararg args: Any): Builder =
      returns(returnType.asTypeName(), CodeBlock.of(kdoc, args))

    @JvmOverloads public fun returns(
      returnType: KClass<*>,
      kdoc: CodeBlock = CodeBlock.EMPTY,
    ): Builder = returns(returnType.asTypeName(), kdoc)

    public fun returns(returnType: KClass<*>, kdoc: String, vararg args: Any): Builder =
      returns(returnType.asTypeName(), CodeBlock.of(kdoc, args))

    public fun addParameters(parameterSpecs: Iterable<ParameterSpec>): Builder = apply {
      for (parameterSpec in parameterSpecs) {
        addParameter(parameterSpec)
      }
    }

    public fun addParameter(parameterSpec: ParameterSpec): Builder = apply {
      parameters += parameterSpec
    }

    public fun callThisConstructor(args: List<CodeBlock>): Builder = apply {
      callConstructor("this", args)
    }

    public fun callThisConstructor(args: Iterable<CodeBlock>): Builder = apply {
      callConstructor("this", args.toList())
    }

    public fun callThisConstructor(vararg args: String): Builder = apply {
      callConstructor("this", args.map { CodeBlock.of(it) })
    }

    public fun callThisConstructor(vararg args: CodeBlock = emptyArray()): Builder = apply {
      callConstructor("this", args.toList())
    }

    public fun callSuperConstructor(args: Iterable<CodeBlock>): Builder = apply {
      callConstructor("super", args.toList())
    }

    public fun callSuperConstructor(args: List<CodeBlock>): Builder = apply {
      callConstructor("super", args)
    }

    public fun callSuperConstructor(vararg args: String): Builder = apply {
      callConstructor("super", args.map { CodeBlock.of(it) })
    }

    public fun callSuperConstructor(vararg args: CodeBlock = emptyArray()): Builder = apply {
      callConstructor("super", args.toList())
    }

    private fun callConstructor(constructor: String, args: List<CodeBlock>) {
      check(name.isConstructor) { "only constructors can delegate to other constructors!" }
      delegateConstructor = constructor
      delegateConstructorArguments = args
    }

    public fun addParameter(name: String, type: TypeName, vararg modifiers: KModifier): Builder =
      addParameter(ParameterSpec.builder(name, type, *modifiers).build())

    public fun addParameter(name: String, type: Type, vararg modifiers: KModifier): Builder =
      addParameter(name, type.asTypeName(), *modifiers)

    public fun addParameter(name: String, type: KClass<*>, vararg modifiers: KModifier): Builder =
      addParameter(name, type.asTypeName(), *modifiers)

    public fun addParameter(name: String, type: TypeName, modifiers: Iterable<KModifier>): Builder =
      addParameter(ParameterSpec.builder(name, type, modifiers).build())

    public fun addParameter(name: String, type: Type, modifiers: Iterable<KModifier>): Builder =
      addParameter(name, type.asTypeName(), modifiers)

    public fun addParameter(
      name: String,
      type: KClass<*>,
      modifiers: Iterable<KModifier>,
    ): Builder = addParameter(name, type.asTypeName(), modifiers)

    public fun addCode(format: String, vararg args: Any?): Builder = apply {
      body.add(format, *args)
    }

    public fun addNamedCode(format: String, args: Map<String, *>): Builder = apply {
      body.addNamed(format, args)
    }

    public fun addCode(codeBlock: CodeBlock): Builder = apply {
      body.add(codeBlock)
    }

    public fun addComment(format: String, vararg args: Any): Builder = apply {
      body.add("//·${format.replace(' ', '·')}\n", *args)
    }

    /**
     * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
     * * Shouldn't contain braces or newline characters.
     */
    public fun beginControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
      body.beginControlFlow(controlFlow, *args)
    }

    /**
     * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
     * *     Shouldn't contain braces or newline characters.
     */
    public fun nextControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
      body.nextControlFlow(controlFlow, *args)
    }

    public fun endControlFlow(): Builder = apply {
      body.endControlFlow()
    }

    public fun addStatement(format: String, vararg args: Any): Builder = apply {
      body.addStatement(format, *args)
    }

    public fun clearBody(): Builder = apply {
      body.clear()
    }

    public fun build(): FunSpec {
      check(typeVariables.isEmpty() || !name.isAccessor) { "$name cannot have type variables" }
      check(!(name == GETTER && parameters.isNotEmpty())) { "$name cannot have parameters" }
      check(!(name == SETTER && parameters.size > 1)) { "$name can have at most one parameter" }
      return FunSpec(this)
    }
  }

  public companion object {
    private const val CONSTRUCTOR = "constructor()"
    internal const val GETTER = "get()"
    internal const val SETTER = "set()"

    internal val String.isConstructor get() = this == CONSTRUCTOR
    internal val String.isAccessor get() = this.isOneOf(GETTER, SETTER)

    private val RETURN_EXPRESSION_BODY_PREFIX_SPACE = CodeBlock.of("return ")
    private val RETURN_EXPRESSION_BODY_PREFIX_NBSP = CodeBlock.of("return·")
    private val THROW_EXPRESSION_BODY_PREFIX_SPACE = CodeBlock.of("throw ")
    private val THROW_EXPRESSION_BODY_PREFIX_NBSP = CodeBlock.of("throw·")

    @JvmStatic public fun builder(name: String): Builder = Builder(name)

    @JvmStatic public fun constructorBuilder(): Builder = Builder(CONSTRUCTOR)

    @JvmStatic public fun getterBuilder(): Builder = Builder(GETTER)

    @JvmStatic public fun setterBuilder(): Builder = Builder(SETTER)

    @DelicateKotlinPoetApi(
      message = "Element APIs don't give complete information on Kotlin types. Consider using" +
        " the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    public fun overriding(method: ExecutableElement): Builder {
      var modifiers: Set<Modifier> = method.modifiers
      require(
        Modifier.PRIVATE !in modifiers &&
          Modifier.FINAL !in modifiers &&
          Modifier.STATIC !in modifiers,
      ) {
        "cannot override method with modifiers: $modifiers"
      }

      val methodName = method.simpleName.toString()
      val funBuilder = builder(methodName)

      funBuilder.addModifiers(KModifier.OVERRIDE)

      modifiers = modifiers.toMutableSet()
      modifiers.remove(Modifier.ABSTRACT)
      funBuilder.jvmModifiers(modifiers)

      method.typeParameters
        .map { it.asType() as TypeVariable }
        .map { it.asTypeVariableName() }
        .forEach { funBuilder.addTypeVariable(it) }

      funBuilder.returns(method.returnType.asTypeName())
      funBuilder.addParameters(ParameterSpec.parametersOf(method))
      if (method.isVarArgs) {
        funBuilder.parameters[funBuilder.parameters.lastIndex] = funBuilder.parameters.last()
          .toBuilder()
          .addModifiers(VARARG)
          .build()
      }

      if (method.thrownTypes.isNotEmpty()) {
        val throwsValueString = method.thrownTypes.joinToString { "%T::class" }
        funBuilder.addAnnotation(
          AnnotationSpec.builder(Throws::class)
            .addMember(throwsValueString, *method.thrownTypes.toTypedArray())
            .build(),
        )
      }

      return funBuilder
    }

    @Deprecated(
      message = "Element APIs don't give complete information on Kotlin types. Consider using" +
        " the kotlinpoet-metadata APIs instead.",
      level = WARNING,
    )
    @JvmStatic
    public fun overriding(
      method: ExecutableElement,
      enclosing: DeclaredType,
      types: Types,
    ): Builder {
      val executableType = types.asMemberOf(enclosing, method) as ExecutableType
      val resolvedParameterTypes = executableType.parameterTypes
      val resolvedReturnType = executableType.returnType

      val builder = overriding(method)
      builder.returns(resolvedReturnType.asTypeName())
      var i = 0
      val size = builder.parameters.size
      while (i < size) {
        val parameter = builder.parameters[i]
        val type = resolvedParameterTypes[i].asTypeName()
        builder.parameters[i] = parameter.toBuilder(parameter.name, type).build()
        i++
      }

      return builder
    }
  }
}

/*
 * Copyright (C) 2017 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

public data class Import internal constructor(
  val qualifiedName: String,
  val alias: String? = null,
) : Comparable<Import> {

  private val importString = buildString {
    append(qualifiedName.escapeSegmentsIfNecessary())
    if (alias != null) {
      append("·as·${alias.escapeIfNecessary()}")
    }
  }

  override fun toString(): String = importString

  override fun compareTo(other: Import): Int = importString.compareTo(other.importString)
}

/*
 * Copyright (C) 2017 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import com.squareup.kotlinpoet.KModifier.INTERNAL
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.KModifier.PROTECTED
import com.squareup.kotlinpoet.KModifier.PUBLIC
import java.util.EnumSet

public enum class KModifier(
  internal val keyword: String,
  private vararg val targets: Target,
) {
  // Modifier order defined here:
  // https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers

  // Access.
  PUBLIC("public", Target.PROPERTY),
  PROTECTED("protected", Target.PROPERTY),
  PRIVATE("private", Target.PROPERTY),
  INTERNAL("internal", Target.PROPERTY),

  // Multiplatform modules.
  EXPECT("expect", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
  ACTUAL("actual", Target.CLASS, Target.FUNCTION, Target.PROPERTY),

  FINAL("final", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
  OPEN("open", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
  ABSTRACT("abstract", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
  SEALED("sealed", Target.CLASS),
  CONST("const", Target.PROPERTY),

  EXTERNAL("external", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
  OVERRIDE("override", Target.FUNCTION, Target.PROPERTY),
  LATEINIT("lateinit", Target.PROPERTY),
  TAILREC("tailrec", Target.FUNCTION),
  VARARG("vararg", Target.PARAMETER),
  SUSPEND("suspend", Target.FUNCTION),
  INNER("inner", Target.CLASS),

  ENUM("enum", Target.CLASS),
  ANNOTATION("annotation", Target.CLASS),
  VALUE("value", Target.CLASS),
  FUN("fun", Target.INTERFACE),

  COMPANION("companion", Target.CLASS),

  // Call-site compiler tips.
  INLINE("inline", Target.FUNCTION),
  NOINLINE("noinline", Target.PARAMETER),
  CROSSINLINE("crossinline", Target.PARAMETER),
  REIFIED("reified", Target.TYPE_PARAMETER),

  INFIX("infix", Target.FUNCTION),
  OPERATOR("operator", Target.FUNCTION),

  DATA("data", Target.CLASS),

  IN("in", Target.VARIANCE_ANNOTATION),
  OUT("out", Target.VARIANCE_ANNOTATION),
  ;

  internal enum class Target {
    CLASS,
    VARIANCE_ANNOTATION,
    PARAMETER,
    TYPE_PARAMETER,
    FUNCTION,
    PROPERTY,
    INTERFACE,
  }

  internal fun checkTarget(target: Target) {
    require(target in targets) { "unexpected modifier $this for $target" }
  }
}

internal val VISIBILITY_MODIFIERS: Set<KModifier> = EnumSet.of(PUBLIC, INTERNAL, PROTECTED, PRIVATE)

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

public enum class KOperator(
  internal val operator: String,
  internal val functionName: String,
) {
  UNARY_PLUS("+", "unaryPlus"),
  PLUS("+", "plus"),
  UNARY_MINUS("-", "unaryMinus"),
  MINUS("-", "minus"),
  TIMES("*", "times"),
  DIV("/", "div"),
  REM("%", "rem"),
  PLUS_ASSIGN("+=", "plusAssign"),
  MINUS_ASSIGN("-=", "minusAssign"),
  TIMES_ASSIGN("*=", "timesAssign"),
  DIV_ASSIGN("/=", "divAssign"),
  REM_ASSIGN("%=", "remAssign"),
  INC("++", "inc"),
  DEC("--", "dec"),
  EQUALS("==", "equals"),
  NOT_EQUALS("!=", "equals"),
  NOT("!", "not"),
  RANGE_TO("..", "rangeTo"),
  CONTAINS("in", "contains"),
  NOT_CONTAINS("!in", "contains"),
  GT(">", "compareTo"),
  LT("<", "compareTo"),
  GE(">=", "compareTo"),
  LE("<=", "compareTo"),
  ITERATOR("in", "iterator"),
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import kotlin.reflect.KClass

@OptIn(ExperimentalKotlinPoetApi::class)
public class LambdaTypeName private constructor(
  public val receiver: TypeName? = null,
  @property:ExperimentalKotlinPoetApi
  public val contextReceivers: List<TypeName> = emptyList(),
  parameters: List<ParameterSpec> = emptyList(),
  public val returnType: TypeName = UNIT,
  nullable: Boolean = false,
  public val isSuspending: Boolean = false,
  annotations: List<AnnotationSpec> = emptyList(),
  tags: Map<KClass<*>, Any> = emptyMap(),
) : TypeName(nullable, annotations, TagMap(tags)) {
  public val parameters: List<ParameterSpec> = parameters.toImmutableList()

  init {
    for (param in parameters) {
      require(param.annotations.isEmpty()) { "Parameters with annotations are not allowed" }
      require(param.modifiers.isEmpty()) { "Parameters with modifiers are not allowed" }
      require(param.defaultValue == null) { "Parameters with default values are not allowed" }
    }
  }

  override fun copy(
    nullable: Boolean,
    annotations: List<AnnotationSpec>,
    tags: Map<KClass<*>, Any>,
  ): LambdaTypeName {
    return copy(nullable, annotations, this.isSuspending, tags)
  }

  public fun copy(
    nullable: Boolean = this.isNullable,
    annotations: List<AnnotationSpec> = this.annotations.toList(),
    suspending: Boolean = this.isSuspending,
    tags: Map<KClass<*>, Any> = this.tags.toMap(),
  ): LambdaTypeName {
    return LambdaTypeName(receiver, contextReceivers, parameters, returnType, nullable, suspending, annotations, tags)
  }

  override fun emit(out: CodeWriter): CodeWriter {
    if (isNullable) {
      out.emit("(")
    }

    if (isSuspending) {
      out.emit("suspend·")
    }

    out.emitContextReceivers(contextReceivers, suffix = "·")

    receiver?.let {
      if (it.isAnnotated) {
        out.emitCode("(%T).", it)
      } else {
        out.emitCode("%T.", it)
      }
    }

    parameters.emit(out)
    out.emitCode(if (returnType is LambdaTypeName) "·->·(%T)" else "·->·%T", returnType)

    if (isNullable) {
      out.emit(")")
    }
    return out
  }

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

    other as LambdaTypeName

    if (receiver != other.receiver) return false
    if (contextReceivers != other.contextReceivers) return false
    if (returnType != other.returnType) return false
    if (isSuspending != other.isSuspending) return false
    if (parameters != other.parameters) return false

    return true
  }

  override fun hashCode(): Int {
    var result = super.hashCode()
    result = 31 * result + (receiver?.hashCode() ?: 0)
    result = 31 * result + contextReceivers.hashCode()
    result = 31 * result + returnType.hashCode()
    result = 31 * result + isSuspending.hashCode()
    result = 31 * result + parameters.hashCode()
    return result
  }
  public companion object {
    /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
    @ExperimentalKotlinPoetApi @JvmStatic
    public fun get(
      receiver: TypeName? = null,
      parameters: List<ParameterSpec> = emptyList(),
      returnType: TypeName,
      contextReceivers: List<TypeName> = emptyList(),
    ): LambdaTypeName = LambdaTypeName(receiver, contextReceivers, parameters, returnType)

    /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
    @JvmStatic public fun get(
      receiver: TypeName? = null,
      parameters: List<ParameterSpec> = emptyList(),
      returnType: TypeName,
    ): LambdaTypeName = LambdaTypeName(receiver, emptyList(), parameters, returnType)

    /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
    @JvmStatic public fun get(
      receiver: TypeName? = null,
      vararg parameters: TypeName = emptyArray(),
      returnType: TypeName,
    ): LambdaTypeName {
      return LambdaTypeName(
        receiver,
        emptyList(),
        parameters.toList().map { ParameterSpec.unnamed(it) },
        returnType,
      )
    }

    /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
    @JvmStatic public fun get(
      receiver: TypeName? = null,
      vararg parameters: ParameterSpec = emptyArray(),
      returnType: TypeName,
    ): LambdaTypeName = LambdaTypeName(receiver, emptyList(), parameters.toList(), returnType)
  }
}

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import java.io.Closeable

/**
 * Implements soft line wrapping on an appendable. To use, append characters using
 * [LineWrapper.append], which will replace spaces with newlines where necessary. Use
 * [LineWrapper.appendNonWrapping] to append a string that never wraps.
 */
internal class LineWrapper(
  private val out: Appendable,
  private val indent: String,
  private val columnLimit: Int,
) : Closeable {

  private var closed = false

  /**
   * Segments of the current line to be joined by spaces or wraps. Never empty, but contains a lone
   * empty string if no data has been emitted since the last newline.
   */
  private val segments = mutableListOf("")

  /** Number of indents in wraps. -1 if the current line has no wraps. */
  private var indentLevel = -1

  /** Optional prefix that will be prepended to wrapped lines. */
  private var linePrefix = ""

  /** @return whether or not there are pending segments for the current line. */
  val hasPendingSegments get() = segments.size != 1 || segments[0].isNotEmpty()

  /** Emit `s` replacing its spaces with line wraps as necessary. */
  fun append(s: String, indentLevel: Int = -1, linePrefix: String = "") {
    check(!closed) { "closed" }

    var pos = 0
    while (pos < s.length) {
      when (s[pos]) {
        ' ' -> {
          // Each space starts a new empty segment.
          this.indentLevel = indentLevel
          this.linePrefix = linePrefix
          segments += ""
          pos++
        }

        '\n' -> {
          // Each newline emits the current segments.
          newline()
          pos++
        }

        '·' -> {
          // Render · as a non-breaking space.
          segments[segments.size - 1] += " "
          pos++
        }

        else -> {
          var next = s.indexOfAny(SPECIAL_CHARACTERS, pos)
          if (next == -1) next = s.length
          segments[segments.size - 1] += s.substring(pos, next)
          pos = next
        }
      }
    }
  }

  /** Emit `s` leaving spaces as-is. */
  fun appendNonWrapping(s: String) {
    check(!closed) { "closed" }
    require(!s.contains("\n"))

    segments[segments.size - 1] += s
  }

  fun newline() {
    check(!closed) { "closed" }

    emitCurrentLine()
    out.append("\n")
    indentLevel = -1
  }

  /** Flush any outstanding text and forbid future writes to this line wrapper.  */
  override fun close() {
    emitCurrentLine()
    closed = true
  }

  private fun emitCurrentLine() {
    foldUnsafeBreaks()

    var start = 0
    var columnCount = segments[0].length

    for (i in 1 until segments.size) {
      val segment = segments[i]
      val newColumnCount = columnCount + 1 + segment.length

      // If this segment doesn't fit in the current run, print the current run and start a new one.
      if (newColumnCount > columnLimit) {
        emitSegmentRange(start, i)
        start = i
        columnCount = segment.length + indent.length * indentLevel
        continue
      }

      columnCount = newColumnCount
    }

    // Print the last run.
    emitSegmentRange(start, segments.size)

    segments.clear()
    segments += ""
  }

  private fun emitSegmentRange(startIndex: Int, endIndex: Int) {
    // If this is a wrapped line we need a newline and an indent.
    if (startIndex > 0) {
      out.append("\n")
      for (i in 0 until indentLevel) {
        out.append(indent)
      }
      out.append(linePrefix)
    }

    // Emit each segment separated by spaces.
    out.append(segments[startIndex])
    for (i in startIndex + 1 until endIndex) {
      out.append(" ")
      out.append(segments[i])
    }
  }

  /**
   * Any segment that starts with '+' or '-' can't have a break preceding it. Combine it with the
   * preceding segment. Note that this doesn't apply to the first segment.
   */
  private fun foldUnsafeBreaks() {
    var i = 1
    while (i < segments.size) {
      val segment = segments[i]
      if (UNSAFE_LINE_START.matches(segment)) {
        segments[i - 1] = segments[i - 1] + " " + segments[i]
        segments.removeAt(i)
        if (i > 1) i--
      } else {
        i++
      }
    }
  }

  companion object {
    private val UNSAFE_LINE_START = Regex("\\s*[-+].*")
    private val SPECIAL_CHARACTERS = " \n·".toCharArray()
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import kotlin.reflect.KClass

/**
 * Represents the name of a member (such as a function or a property).
 *
 * @param packageName e.g. `kotlin.collections`
 * @param enclosingClassName e.g. `Map.Entry.Companion`, if the member is declared inside the
 * companion object of the Map.Entry class
 * @param simpleName e.g. `isBlank`, `size`
 * @param isExtension whether the member is an extension property or an extension function. Default
 * is false.
 *
 * If there is a member with the same name as this member in a local scope, the generated code will
 * include this member's fully-qualified name to avoid ambiguity, e.g.:
 *
 * ```kotlin
 * package com.squareup.tacos
 *
 * import kotlin.Unit
 *
 * public class TacoTest {
 *   public fun test(): Unit {
 *     kotlin.error("errorText")
 *   }
 *
 *   public fun error(): Unit {
 *   }
 * }
 * ```
 *
 * However, since Kotlin compiler does not allow fully-qualified extension members, if [isExtension]
 * is set to true for this [MemberName], the generated code will include an import for this member
 * and its simple name at the call site, e.g.:
 *
 * ```kotlin
 * package com.squareup.tacos
 *
 * import kotlin.Unit
 * import kotlin.hashCode
 *
 * public class TacoTest {
 *   public override fun hashCode(): Unit {
 *     var result = super.hashCode
 *     if (result == 0) {
 *       result = result * 37 + embedded_message.hashCode()
 *       super.hashCode = result
 *     }
 *     return result
 *   }
 * }
 * ```
 */
public data class MemberName internal constructor(
  public val packageName: String,
  public val enclosingClassName: ClassName?,
  public val simpleName: String,
  public val operator: KOperator? = null,
  public val isExtension: Boolean = false,
) {
  // TODO(egorand): Reduce the number of overloaded constructors in KotlinPoet 2.0.

  public constructor(
    packageName: String,
    simpleName: String,
  ) : this(packageName, enclosingClassName = null, simpleName)

  public constructor(
    packageName: String,
    simpleName: String,
    isExtension: Boolean,
  ) : this(packageName, enclosingClassName = null, simpleName, operator = null, isExtension)

  public constructor(
    enclosingClassName: ClassName,
    simpleName: String,
  ) : this(enclosingClassName.packageName, enclosingClassName, simpleName)

  public constructor(
    enclosingClassName: ClassName,
    simpleName: String,
    isExtension: Boolean,
  ) : this(enclosingClassName.packageName, enclosingClassName, simpleName, operator = null, isExtension)

  public constructor(
    packageName: String,
    operator: KOperator,
  ) : this(packageName, enclosingClassName = null, operator.functionName, operator)

  public constructor(
    enclosingClassName: ClassName,
    operator: KOperator,
  ) : this(enclosingClassName.packageName, enclosingClassName, operator.functionName, operator)

  /** Fully qualified name using `.` as a separator, like `kotlin.String.isBlank`. */
  public val canonicalName: String = buildString {
    if (enclosingClassName != null) {
      append(enclosingClassName.canonicalName)
      append('.')
    } else if (packageName.isNotBlank()) {
      append(packageName)
      append('.')
    }
    append(simpleName)
  }

  /**
   * Callable reference to this member. Emits [enclosingClassName] if it exists, followed by
   * the reference operator `::`, followed by either [simpleName] or the fully-qualified
   * name if this is a top-level member.
   *
   * Note: As `::$packageName.$simpleName` is not valid syntax, an aliased import may be
   * required for a top-level member with a conflicting name.
   */
  public fun reference(): CodeBlock = when (enclosingClassName) {
    null -> CodeBlock.of("::%M", this)
    else -> CodeBlock.of("%T::%N", enclosingClassName, simpleName)
  }

  internal fun emit(out: CodeWriter) {
    if (operator == null) {
      out.emit(out.lookupName(this).escapeSegmentsIfNecessary())
    } else {
      out.lookupName(this)
      out.emit(operator.operator)
    }
  }

  override fun toString(): String = canonicalName

  public companion object {
    @Suppress("NOTHING_TO_INLINE")
    @JvmSynthetic
    @JvmStatic
    public inline fun ClassName.member(simpleName: String): MemberName =
      MemberName(this, simpleName)

    @JvmStatic
    @JvmName("get")
    public fun KClass<*>.member(simpleName: String): MemberName =
      asClassName().member(simpleName)

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    @JvmName("get")
    public fun Class<*>.member(simpleName: String): MemberName =
      asClassName().member(simpleName)
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import java.util.UUID

/**
 * Assigns Kotlin identifier names to avoid collisions, keywords, and invalid characters. To use,
 * first create an instance and allocate all of the names that you need. Typically this is a
 * mix of user-supplied names and constants:
 *
 * ```kotlin
 * val nameAllocator = NameAllocator()
 * for (property in properties) {
 *   nameAllocator.newName(property.name, property)
 * }
 * nameAllocator.newName("sb", "string builder")
 * ```
 *
 * Pass a unique tag object to each allocation. The tag scopes the name, and can be used to look up
 * the allocated name later. Typically the tag is the object that is being named. In the above
 * example we use `property` for the user-supplied property names, and `"string builder"` for our
 * constant string builder.
 *
 * Once we've allocated names we can use them when generating code:
 *
 * ```kotlin
 * val builder = FunSpec.builder("toString")
 *     .addModifiers(KModifier.OVERRIDE)
 *     .returns(String::class)
 *
 * builder.addStatement("val %N = %T()",
 *     nameAllocator.get("string builder"), StringBuilder::class)
 *
 * for (property in properties) {
 *   builder.addStatement("%N.append(%N)",
 *       nameAllocator.get("string builder"), nameAllocator.get(property))
 * }
 * builder.addStatement("return %N.toString()", nameAllocator.get("string builder"))
 * return builder.build()
 * ```
 *
 * The above code generates unique names if presented with conflicts. Given user-supplied properties
 * with names `ab` and `sb` this generates the following:
 *
 * ```kotlin
 * override fun toString(): kotlin.String {
 *   val sb_ = java.lang.StringBuilder()
 *   sb_.append(ab)
 *   sb_.append(sb)
 *   return sb_.toString()
 * }
 * ```
 *
 * The underscore is appended to `sb` to avoid conflicting with the user-supplied `sb` property.
 * Underscores are also prefixed for names that start with a digit, and used to replace name-unsafe
 * characters like space or dash.
 *
 * When dealing with multiple independent inner scopes, use a [copy][NameAllocator.copy] of the
 * NameAllocator used for the outer scope to further refine name allocation for a specific inner
 * scope.
 */
public class NameAllocator private constructor(
  private val allocatedNames: MutableSet<String>,
  private val tagToName: MutableMap<Any, String>,
) {
  public constructor() : this(mutableSetOf(), mutableMapOf())

  /**
   * Return a new name using `suggestion` that will not be a Java identifier or clash with other
   * names. The returned value can be queried multiple times by passing `tag` to
   * [NameAllocator.get].
   */
  @JvmOverloads public fun newName(
    suggestion: String,
    tag: Any = UUID.randomUUID().toString(),
  ): String {
    var result = toJavaIdentifier(suggestion)
    while (result.isKeyword || !allocatedNames.add(result)) {
      result += "_"
    }

    val replaced = tagToName.put(tag, result)
    if (replaced != null) {
      tagToName[tag] = replaced // Put things back as they were!
      throw IllegalArgumentException("tag $tag cannot be used for both '$replaced' and '$result'")
    }

    return result
  }

  /** Retrieve a name created with [NameAllocator.newName]. */
  public operator fun get(tag: Any): String = requireNotNull(tagToName[tag]) { "unknown tag: $tag" }

  /**
   * Create a deep copy of this NameAllocator. Useful to create multiple independent refinements
   * of a NameAllocator to be used in the respective definition of multiples, independently-scoped,
   * inner code blocks.
   *
   * @return A deep copy of this NameAllocator.
   */
  public fun copy(): NameAllocator {
    return NameAllocator(allocatedNames.toMutableSet(), tagToName.toMutableMap())
  }
}

private fun toJavaIdentifier(suggestion: String) = buildString {
  var i = 0
  while (i < suggestion.length) {
    val codePoint = suggestion.codePointAt(i)
    if (i == 0 &&
      !Character.isJavaIdentifierStart(codePoint) &&
      Character.isJavaIdentifierPart(codePoint)
    ) {
      append("_")
    }

    val validCodePoint: Int = if (Character.isJavaIdentifierPart(codePoint)) {
      codePoint
    } else {
      '_'.code
    }
    appendCodePoint(validCodePoint)
    i += Character.charCount(codePoint)
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import javax.lang.model.element.Element

/** A type that can have originating [elements][Element]. */
public interface OriginatingElementsHolder {

  /** The originating elements of this type. */
  public val originatingElements: List<Element>

  /** The builder analogue to [OriginatingElementsHolder] types. */
  public interface Builder<out T : Builder<T>> {

    /** Mutable map of the current originating elements this builder contains. */
    public val originatingElements: MutableList<Element>

    /** Adds an [originatingElement] to this type's list of originating elements. */
    @Suppress("UNCHECKED_CAST")
    public fun addOriginatingElement(originatingElement: Element): T = apply {
      originatingElements += originatingElement
    } as T
  }
}

internal fun OriginatingElementsHolder.Builder<*>.buildOriginatingElements() =
  OriginatingElements(originatingElements.toImmutableList())

internal fun List<Element>.buildOriginatingElements() =
  OriginatingElements(toImmutableList())

@JvmInline
internal value class OriginatingElements(
  override val originatingElements: List<Element>,
) : OriginatingElementsHolder

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import com.squareup.kotlinpoet.KModifier.CROSSINLINE
import com.squareup.kotlinpoet.KModifier.NOINLINE
import com.squareup.kotlinpoet.KModifier.VARARG
import java.lang.reflect.Type
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.Modifier
import javax.lang.model.element.VariableElement
import kotlin.DeprecationLevel.ERROR
import kotlin.reflect.KClass

/** A generated parameter declaration. */
public class ParameterSpec private constructor(
  builder: Builder,
  private val tagMap: TagMap = builder.buildTagMap(),
) : Taggable by tagMap {
  public val name: String = builder.name
  public val kdoc: CodeBlock = builder.kdoc.build()
  public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
  public val modifiers: Set<KModifier> = builder.modifiers
    .also {
      LinkedHashSet(it).apply {
        removeAll(ALLOWED_PARAMETER_MODIFIERS)
        if (!isEmpty()) {
          throw IllegalArgumentException("Modifiers $this are not allowed on Kotlin parameters. Allowed modifiers: $ALLOWED_PARAMETER_MODIFIERS")
        }
      }
    }
    .toImmutableSet()
  public val type: TypeName = builder.type
  public val defaultValue: CodeBlock? = builder.defaultValue

  public constructor(name: String, type: TypeName, vararg modifiers: KModifier) :
    this(builder(name, type, *modifiers))
  public constructor(name: String, type: TypeName, modifiers: Iterable<KModifier>) :
    this(builder(name, type, modifiers))

  internal fun emit(
    codeWriter: CodeWriter,
    includeType: Boolean = true,
    emitKdoc: Boolean = false,
    inlineAnnotations: Boolean = true,
  ) {
    if (emitKdoc) codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
    codeWriter.emitAnnotations(annotations, inlineAnnotations)
    codeWriter.emitModifiers(modifiers)
    if (name.isNotEmpty()) codeWriter.emitCode("%N", this)
    if (name.isNotEmpty() && includeType) codeWriter.emitCode(":·")
    if (includeType) codeWriter.emitCode("%T", type)
    emitDefaultValue(codeWriter)
  }

  internal fun emitDefaultValue(codeWriter: CodeWriter) {
    if (defaultValue != null) {
      codeWriter.emitCode(if (defaultValue.hasStatements()) " = %L" else " = «%L»", defaultValue)
    }
  }

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

  override fun hashCode(): Int = toString().hashCode()

  override fun toString(): String = buildCodeString { emit(this) }

  public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
    val builder = Builder(name, type)
    builder.kdoc.add(kdoc)
    builder.annotations += annotations
    builder.modifiers += modifiers
    builder.defaultValue = defaultValue
    builder.tags += tagMap.tags
    return builder
  }

  public class Builder internal constructor(
    internal val name: String,
    internal val type: TypeName,
  ) : Taggable.Builder<Builder> {
    internal var defaultValue: CodeBlock? = null

    public val kdoc: CodeBlock.Builder = CodeBlock.builder()
    public val annotations: MutableList<AnnotationSpec> = mutableListOf()
    public val modifiers: MutableList<KModifier> = mutableListOf()
    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()

    public fun addKdoc(format: String, vararg args: Any): Builder = apply {
      kdoc.add(format, *args)
    }

    public fun addKdoc(block: CodeBlock): Builder = apply {
      kdoc.add(block)
    }

    public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
      annotations += annotationSpecs
    }

    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
      annotations += annotationSpec
    }

    public fun addAnnotation(annotation: ClassName): Builder = apply {
      annotations += AnnotationSpec.builder(annotation).build()
    }

    public fun addAnnotation(annotation: Class<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun addAnnotation(annotation: KClass<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
      this.modifiers += modifiers
    }

    public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
      this.modifiers += modifiers
    }

    @Deprecated(
      "There are no jvm modifiers applicable to parameters in Kotlin",
      ReplaceWith(""),
      level = ERROR,
    )
    public fun jvmModifiers(modifiers: Iterable<Modifier>): Builder = apply {
      throw IllegalArgumentException("JVM modifiers are not permitted on parameters in Kotlin")
    }

    public fun defaultValue(format: String, vararg args: Any?): Builder =
      defaultValue(CodeBlock.of(format, *args))

    public fun defaultValue(codeBlock: CodeBlock?): Builder = apply {
      this.defaultValue = codeBlock
    }

    public fun build(): ParameterSpec = ParameterSpec(this)
  }

  public companion object {
    @DelicateKotlinPoetApi(
      message = "Element APIs don't give complete information on Kotlin types. Consider using" +
        " the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    public fun get(element: VariableElement): ParameterSpec {
      val name = element.simpleName.toString()
      val type = element.asType().asTypeName()
      return builder(name, type)
        .build()
    }

    @DelicateKotlinPoetApi(
      message = "Element APIs don't give complete information on Kotlin types. Consider using" +
        " the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    public fun parametersOf(method: ExecutableElement): List<ParameterSpec> =
      method.parameters.map(::get)

    @JvmStatic public fun builder(
      name: String,
      type: TypeName,
      vararg modifiers: KModifier,
    ): Builder {
      return Builder(name, type).addModifiers(*modifiers)
    }

    @JvmStatic public fun builder(name: String, type: Type, vararg modifiers: KModifier): Builder =
      builder(name, type.asTypeName(), *modifiers)

    @JvmStatic public fun builder(
      name: String,
      type: KClass<*>,
      vararg modifiers: KModifier,
    ): Builder = builder(name, type.asTypeName(), *modifiers)

    @JvmStatic public fun builder(
      name: String,
      type: TypeName,
      modifiers: Iterable<KModifier>,
    ): Builder {
      return Builder(name, type).addModifiers(modifiers)
    }

    @JvmStatic public fun builder(
      name: String,
      type: Type,
      modifiers: Iterable<KModifier>,
    ): Builder = builder(name, type.asTypeName(), modifiers)

    @JvmStatic public fun builder(
      name: String,
      type: KClass<*>,
      modifiers: Iterable<KModifier>,
    ): Builder = builder(name, type.asTypeName(), modifiers)

    @JvmStatic public fun unnamed(type: KClass<*>): ParameterSpec = unnamed(type.asTypeName())

    @JvmStatic public fun unnamed(type: Type): ParameterSpec = unnamed(type.asTypeName())

    @JvmStatic public fun unnamed(type: TypeName): ParameterSpec = Builder("", type).build()
  }
}

// From https://kotlinlang.org/spec/syntax-and-grammar.html#grammar-rule-parameterModifier
private val ALLOWED_PARAMETER_MODIFIERS = setOf(VARARG, NOINLINE, CROSSINLINE)

internal fun List<ParameterSpec>.emit(
  codeWriter: CodeWriter,
  forceNewLines: Boolean = false,
  emitBlock: (ParameterSpec) -> Unit = { it.emit(codeWriter) },
) = with(codeWriter) {
  emit("(")

  if (isNotEmpty()) {
    val emitNewLines = size > 2 || forceNewLines
    if (emitNewLines) {
      emit("\n")
      indent(1)
    }
    forEachIndexed { index, parameter ->
      if (index > 0) {
        emit(if (emitNewLines) "\n" else ", ")
      }
      emitBlock(parameter)
      if (emitNewLines) {
        emit(",")
      }
    }
    if (emitNewLines) {
      unindent(1)
      emit("\n")
    }
  }
  emit(")")
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://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.
 */
@file:JvmName("ParameterizedTypeNames")

package com.squareup.kotlinpoet

import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance

public class ParameterizedTypeName internal constructor(
  private val enclosingType: TypeName?,
  public val rawType: ClassName,
  typeArguments: List<TypeName>,
  nullable: Boolean = false,
  annotations: List<AnnotationSpec> = emptyList(),
  tags: Map<KClass<*>, Any> = emptyMap(),
) : TypeName(nullable, annotations, TagMap(tags)) {
  public val typeArguments: List<TypeName> = typeArguments.toImmutableList()

  init {
    require(typeArguments.isNotEmpty() || enclosingType != null) {
      "no type arguments: $rawType"
    }
  }

  override fun copy(
    nullable: Boolean,
    annotations: List<AnnotationSpec>,
    tags: Map<KClass<*>, Any>,
  ): ParameterizedTypeName {
    return ParameterizedTypeName(enclosingType, rawType, typeArguments, nullable, annotations, tags)
  }

  public fun copy(
    nullable: Boolean = this.isNullable,
    annotations: List<AnnotationSpec> = this.annotations,
    tags: Map<KClass<*>, Any> = this.tags,
    typeArguments: List<TypeName> = this.typeArguments,
  ): ParameterizedTypeName {
    return ParameterizedTypeName(enclosingType, rawType, typeArguments, nullable, annotations, tags)
  }

  public fun plusParameter(typeArgument: TypeName): ParameterizedTypeName =
    ParameterizedTypeName(
      enclosingType,
      rawType,
      typeArguments + typeArgument,
      isNullable,
      annotations,
    )

  public fun plusParameter(typeArgument: KClass<*>): ParameterizedTypeName =
    plusParameter(typeArgument.asClassName())

  public fun plusParameter(typeArgument: Class<*>): ParameterizedTypeName =
    plusParameter(typeArgument.asClassName())

  override fun emit(out: CodeWriter): CodeWriter {
    if (enclosingType != null) {
      enclosingType.emitAnnotations(out)
      enclosingType.emit(out)
      out.emit("." + rawType.simpleName)
    } else {
      rawType.emitAnnotations(out)
      rawType.emit(out)
    }
    if (typeArguments.isNotEmpty()) {
      out.emit("<")
      typeArguments.forEachIndexed { index, parameter ->
        if (index > 0) out.emit(",·")
        parameter.emitAnnotations(out)
        parameter.emit(out)
        parameter.emitNullable(out)
      }
      out.emit(">")
    }
    return out
  }

  /**
   * Returns a new [ParameterizedTypeName] instance for the specified `name` as nested inside this
   * class, with the specified `typeArguments`.
   */
  public fun nestedClass(name: String, typeArguments: List<TypeName>): ParameterizedTypeName =
    ParameterizedTypeName(this, rawType.nestedClass(name), typeArguments)

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

    other as ParameterizedTypeName

    if (enclosingType != other.enclosingType) return false
    if (rawType != other.rawType) return false
    if (typeArguments != other.typeArguments) return false

    return true
  }

  override fun hashCode(): Int {
    var result = super.hashCode()
    result = 31 * result + (enclosingType?.hashCode() ?: 0)
    result = 31 * result + rawType.hashCode()
    result = 31 * result + typeArguments.hashCode()
    return result
  }

  public companion object {
    /** Returns a parameterized type, applying `typeArguments` to `this`. */
    @JvmStatic
    @JvmName("get")
    public fun ClassName.parameterizedBy(
      vararg typeArguments: TypeName,
    ): ParameterizedTypeName = ParameterizedTypeName(null, this, typeArguments.toList())

    /** Returns a parameterized type, applying `typeArguments` to `this`. */
    @JvmStatic
    @JvmName("get")
    public fun KClass<*>.parameterizedBy(
      vararg typeArguments: KClass<*>,
    ): ParameterizedTypeName =
      ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })

    /** Returns a parameterized type, applying `typeArguments` to `this`. */
    @JvmStatic
    @JvmName("get")
    public fun Class<*>.parameterizedBy(
      vararg typeArguments: Type,
    ): ParameterizedTypeName =
      ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })

    /** Returns a parameterized type, applying `typeArguments` to `this`. */
    @JvmStatic
    @JvmName("get")
    public fun ClassName.parameterizedBy(
      typeArguments: List<TypeName>,
    ): ParameterizedTypeName = ParameterizedTypeName(null, this, typeArguments)

    /** Returns a parameterized type, applying `typeArguments` to `this`. */
    @JvmStatic
    @JvmName("get")
    public fun KClass<*>.parameterizedBy(
      typeArguments: Iterable<KClass<*>>,
    ): ParameterizedTypeName =
      ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })

    /** Returns a parameterized type, applying `typeArguments` to `this`. */
    @JvmStatic
    @JvmName("get")
    public fun Class<*>.parameterizedBy(
      typeArguments: Iterable<Type>,
    ): ParameterizedTypeName =
      ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })

    /** Returns a parameterized type, applying `typeArgument` to `this`. */
    @JvmStatic
    @JvmName("get")
    public fun ClassName.plusParameter(
      typeArgument: TypeName,
    ): ParameterizedTypeName = parameterizedBy(typeArgument)

    /** Returns a parameterized type, applying `typeArgument` to `this`. */
    @JvmStatic
    @JvmName("get")
    public fun KClass<*>.plusParameter(
      typeArgument: KClass<*>,
    ): ParameterizedTypeName = parameterizedBy(typeArgument)

    /** Returns a parameterized type, applying `typeArgument` to `this`. */
    @JvmStatic
    @JvmName("get")
    public fun Class<*>.plusParameter(
      typeArgument: Class<*>,
    ): ParameterizedTypeName = parameterizedBy(typeArgument)

    /** Returns a parameterized type equivalent to `type`. */
    internal fun get(
      type: ParameterizedType,
      map: MutableMap<Type, TypeVariableName>,
    ): ParameterizedTypeName {
      val rawType = (type.rawType as Class<*>).asClassName()
      val ownerType = if (type.ownerType is ParameterizedType &&
        !Modifier.isStatic((type.rawType as Class<*>).modifiers)
      ) {
        type.ownerType as ParameterizedType
      } else {
        null
      }

      val typeArguments = type.actualTypeArguments.map { get(it, map = map) }
      return if (ownerType != null) {
        get(ownerType, map = map).nestedClass(rawType.simpleName, typeArguments)
      } else {
        ParameterizedTypeName(null, rawType, typeArguments)
      }
    }

    /** Returns a type name equivalent to type with given list of type arguments. */
    internal fun get(
      type: KClass<*>,
      nullable: Boolean,
      typeArguments: List<KTypeProjection>,
    ): TypeName {
      if (typeArguments.isEmpty()) {
        return type.asTypeName().run { if (nullable) copy(nullable = true) else this }
      }

      val effectiveType = if (type.java.isArray) Array<Unit>::class else type
      val enclosingClass = type.java.enclosingClass?.kotlin

      return ParameterizedTypeName(
        enclosingClass?.let {
          get(it, false, typeArguments.drop(effectiveType.typeParameters.size))
        },
        effectiveType.asTypeName(),
        typeArguments.take(effectiveType.typeParameters.size).map { (paramVariance, paramType) ->
          val typeName = paramType?.asTypeName() ?: return@map STAR
          when (paramVariance) {
            null -> STAR
            KVariance.INVARIANT -> typeName
            KVariance.IN -> WildcardTypeName.consumerOf(typeName)
            KVariance.OUT -> WildcardTypeName.producerOf(typeName)
          }
        },
        nullable,
        effectiveType.annotations.map { AnnotationSpec.get(it) },
      )
    }
  }
}

/** Returns a parameterized type equivalent to `type`.  */
@DelicateKotlinPoetApi(
  message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
    "using the kotlinpoet-metadata APIs instead.",
)
@JvmName("get")
public fun ParameterizedType.asParameterizedTypeName(): ParameterizedTypeName =
  ParameterizedTypeName.get(this, mutableMapOf())

/**
 * Returns a [TypeName] equivalent to the given Kotlin KType using reflection, maybe using kotlin-reflect
 * if required.
 */
public fun KType.asTypeName(): TypeName {
  val classifier = this.classifier
  if (classifier is KTypeParameter) {
    return classifier.asTypeVariableName().run { if (isMarkedNullable) copy(nullable = true) else this }
  }

  if (classifier == null || classifier !is KClass<*>) {
    throw IllegalArgumentException("Cannot build TypeName for $this")
  }

  return ParameterizedTypeName.get(classifier, this.isMarkedNullable, this.arguments)
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import com.squareup.kotlinpoet.FunSpec.Companion.GETTER
import com.squareup.kotlinpoet.FunSpec.Companion.SETTER
import com.squareup.kotlinpoet.KModifier.Target.PROPERTY
import java.lang.reflect.Type
import java.util.EnumSet
import javax.lang.model.element.Element
import kotlin.reflect.KClass

/** A generated property declaration. */
@OptIn(ExperimentalKotlinPoetApi::class)
public class PropertySpec private constructor(
  builder: Builder,
  private val tagMap: TagMap = builder.buildTagMap(),
  private val delegateOriginatingElementsHolder: OriginatingElementsHolder = builder.buildOriginatingElements(),
  private val contextReceivers: ContextReceivers = builder.buildContextReceivers(),
) : Taggable by tagMap, OriginatingElementsHolder by delegateOriginatingElementsHolder, ContextReceivable by contextReceivers {
  public val mutable: Boolean = builder.mutable
  public val name: String = builder.name
  public val type: TypeName = builder.type
  public val kdoc: CodeBlock = builder.kdoc.build()
  public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
  public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
  public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
  public val initializer: CodeBlock? = builder.initializer
  public val delegated: Boolean = builder.delegated
  public val getter: FunSpec? = builder.getter
  public val setter: FunSpec? = builder.setter
  public val receiverType: TypeName? = builder.receiverType

  init {
    require(
      typeVariables.none { it.isReified } ||
        (getter != null || setter != null) &&
        (getter == null || KModifier.INLINE in getter.modifiers) &&
        (setter == null || KModifier.INLINE in setter.modifiers),
    ) {
      "only type parameters of properties with inline getters and/or setters can be reified!"
    }
    require(mutable || setter == null) {
      "only a mutable property can have a setter"
    }
    if (contextReceiverTypes.isNotEmpty()) {
      requireNotNull(getter) { "properties with context receivers require a $GETTER" }
      if (mutable) {
        requireNotNull(setter) { "mutable properties with context receivers require a $SETTER" }
      }
    }
  }

  internal fun emit(
    codeWriter: CodeWriter,
    implicitModifiers: Set<KModifier>,
    withInitializer: Boolean = true,
    emitKdoc: Boolean = true,
    inline: Boolean = false,
    inlineAnnotations: Boolean = inline,
  ) {
    val isInlineProperty = getter?.modifiers?.contains(KModifier.INLINE) ?: false &&
      (!mutable || setter?.modifiers?.contains(KModifier.INLINE) ?: false)
    val propertyModifiers = if (isInlineProperty) modifiers + KModifier.INLINE else modifiers
    if (emitKdoc) {
      codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
    }
    codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
    codeWriter.emitAnnotations(annotations, inlineAnnotations)
    codeWriter.emitModifiers(propertyModifiers, implicitModifiers)
    codeWriter.emitCode(if (mutable) "var·" else "val·")
    if (typeVariables.isNotEmpty()) {
      codeWriter.emitTypeVariables(typeVariables)
      codeWriter.emit(" ")
    }
    if (receiverType != null) {
      if (receiverType is LambdaTypeName) {
        codeWriter.emitCode("(%T).", receiverType)
      } else {
        codeWriter.emitCode("%T.", receiverType)
      }
    }
    codeWriter.emitCode("%N: %T", this, type)
    if (withInitializer && initializer != null) {
      if (delegated) {
        codeWriter.emit(" by ")
      } else {
        codeWriter.emitCode(" = ")
      }
      val initializerFormat = if (initializer.hasStatements()) "%L" else "«%L»"
      codeWriter.emitCode(
        codeBlock = CodeBlock.of(initializerFormat, initializer),
        isConstantContext = KModifier.CONST in modifiers,
      )
    }
    codeWriter.emitWhereBlock(typeVariables)
    if (!inline) codeWriter.emit("\n")
    val implicitAccessorModifiers = EnumSet.noneOf(KModifier::class.java)
    for (modifier in implicitModifiers) {
      // Omit visibility modifiers, accessor visibility will default to the property's visibility.
      if (modifier !in VISIBILITY_MODIFIERS) {
        implicitAccessorModifiers.add(modifier)
      }
    }
    if (isInlineProperty) {
      implicitAccessorModifiers.add(KModifier.INLINE)
    }
    if (getter != null) {
      codeWriter.emitCode("⇥")
      getter.emit(codeWriter, null, implicitAccessorModifiers, false)
      codeWriter.emitCode("⇤")
    }
    if (setter != null) {
      codeWriter.emitCode("⇥")
      setter.emit(codeWriter, null, implicitAccessorModifiers, false)
      codeWriter.emitCode("⇤")
    }
  }

  internal fun fromPrimaryConstructorParameter(parameter: ParameterSpec): PropertySpec {
    val builder = toBuilder()
      .addAnnotations(parameter.annotations)
    builder.isPrimaryConstructorParameter = true
    builder.modifiers += parameter.modifiers
    if (builder.kdoc.isEmpty()) {
      builder.addKdoc(parameter.kdoc)
    }
    return builder.build()
  }

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

  override fun hashCode(): Int = toString().hashCode()

  override fun toString(): String = buildCodeString { emit(this, emptySet()) }

  @JvmOverloads
  public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
    val builder = Builder(name, type)
    builder.mutable = mutable
    builder.kdoc.add(kdoc)
    builder.annotations += annotations
    builder.modifiers += modifiers
    builder.typeVariables += typeVariables
    builder.initializer = initializer
    builder.delegated = delegated
    builder.setter = setter
    builder.getter = getter
    builder.receiverType = receiverType
    builder.tags += tagMap.tags
    builder.originatingElements += originatingElements
    builder.contextReceiverTypes += contextReceiverTypes
    return builder
  }

  public class Builder internal constructor(
    internal val name: String,
    internal val type: TypeName,
  ) : Taggable.Builder<Builder>,
    OriginatingElementsHolder.Builder<Builder>,
    ContextReceivable.Builder<Builder> {
    internal var isPrimaryConstructorParameter = false
    internal var mutable = false
    internal val kdoc = CodeBlock.builder()
    internal var initializer: CodeBlock? = null
    internal var delegated = false
    internal var getter: FunSpec? = null
    internal var setter: FunSpec? = null
    internal var receiverType: TypeName? = null

    public val annotations: MutableList<AnnotationSpec> = mutableListOf()
    public val modifiers: MutableList<KModifier> = mutableListOf()
    public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
    override val originatingElements: MutableList<Element> = mutableListOf()
    override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()

    /** True to create a `var` instead of a `val`. */
    public fun mutable(mutable: Boolean = true): Builder = apply {
      this.mutable = mutable
    }

    public fun addKdoc(format: String, vararg args: Any): Builder = apply {
      kdoc.add(format, *args)
    }

    public fun addKdoc(block: CodeBlock): Builder = apply {
      kdoc.add(block)
    }

    public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
      annotations += annotationSpecs
    }

    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
      annotations += annotationSpec
    }

    public fun addAnnotation(annotation: ClassName): Builder = apply {
      annotations += AnnotationSpec.builder(annotation).build()
    }

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    public fun addAnnotation(annotation: Class<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun addAnnotation(annotation: KClass<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
      this.modifiers += modifiers
    }

    public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
      this.modifiers += modifiers
    }

    public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
      this.typeVariables += typeVariables
    }

    public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
      typeVariables += typeVariable
    }

    public fun initializer(format: String, vararg args: Any?): Builder =
      initializer(CodeBlock.of(format, *args))

    public fun initializer(codeBlock: CodeBlock?): Builder = apply {
      this.initializer = codeBlock
      this.delegated = false
    }

    public fun delegate(format: String, vararg args: Any?): Builder =
      delegate(CodeBlock.of(format, *args))

    public fun delegate(codeBlock: CodeBlock): Builder = apply {
      this.initializer = codeBlock
      this.delegated = true
    }

    public fun getter(getter: FunSpec?): Builder = apply {
      require(getter == null || getter.name == GETTER) { "${getter!!.name} is not a getter" }
      this.getter = getter
    }

    public fun setter(setter: FunSpec?): Builder = apply {
      require(setter == null || setter.name == SETTER) { "${setter!!.name} is not a setter" }
      this.setter = setter
    }

    public fun receiver(receiverType: TypeName?): Builder = apply {
      this.receiverType = receiverType
    }

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    public fun receiver(receiverType: Type): Builder = receiver(receiverType.asTypeName())

    public fun receiver(receiverType: KClass<*>): Builder = receiver(receiverType.asTypeName())

    public fun build(): PropertySpec {
      if (KModifier.INLINE in modifiers) {
        throw IllegalArgumentException(
          "KotlinPoet doesn't allow setting the inline modifier on " +
            "properties. You should mark either the getter, the setter, or both inline.",
        )
      }
      for (it in modifiers) {
        if (!isPrimaryConstructorParameter) it.checkTarget(PROPERTY)
      }
      return PropertySpec(this)
    }
  }

  public companion object {
    @JvmStatic public fun builder(
      name: String,
      type: TypeName,
      vararg modifiers: KModifier,
    ): Builder {
      return Builder(name, type).addModifiers(*modifiers)
    }

    @JvmStatic public fun builder(name: String, type: Type, vararg modifiers: KModifier): Builder =
      builder(name, type.asTypeName(), *modifiers)

    @JvmStatic public fun builder(
      name: String,
      type: KClass<*>,
      vararg modifiers: KModifier,
    ): Builder = builder(name, type.asTypeName(), *modifiers)

    @JvmStatic public fun builder(
      name: String,
      type: TypeName,
      modifiers: Iterable<KModifier>,
    ): Builder {
      return Builder(name, type).addModifiers(modifiers)
    }

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    public fun builder(
      name: String,
      type: Type,
      modifiers: Iterable<KModifier>,
    ): Builder = builder(name, type.asTypeName(), modifiers)

    @JvmStatic public fun builder(
      name: String,
      type: KClass<*>,
      modifiers: Iterable<KModifier>,
    ): Builder = builder(name, type.asTypeName(), modifiers)
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import kotlin.reflect.KClass

/** A type that can be tagged with extra metadata of the user's choice. */
public interface Taggable {

  /** Returns all tags. */
  public val tags: Map<KClass<*>, Any> get() = emptyMap()

  /** Returns the tag attached with [type] as a key, or null if no tag is attached with that key. */
  public fun <T : Any> tag(type: Class<T>): T? = tag(type.kotlin)

  /** Returns the tag attached with [type] as a key, or null if no tag is attached with that key. */
  public fun <T : Any> tag(type: KClass<T>): T? {
    @Suppress("UNCHECKED_CAST")
    return tags[type] as T?
  }

  /** The builder analogue to [Taggable] types. */
  public interface Builder<out T : Builder<T>> {

    /** Mutable map of the current tags this builder contains. */
    public val tags: MutableMap<KClass<*>, Any>

    /**
     * Attaches [tag] to the request using [type] as a key. Tags can be read from a
     * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
     * [type].
     *
     * Use this API to attach originating elements, debugging, or other application data to a spec
     * so that you may read it in other APIs or callbacks.
     */
    public fun tag(type: Class<*>, tag: Any?): T = tag(type.kotlin, tag)

    /**
     * Attaches [tag] to the request using [type] as a key. Tags can be read from a
     * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
     * [type].
     *
     * Use this API to attach originating elements, debugging, or other application data to a spec
     * so that you may read it in other APIs or callbacks.
     */
    @Suppress("UNCHECKED_CAST")
    public fun tag(type: KClass<*>, tag: Any?): T = apply {
      if (tag == null) {
        this.tags.remove(type)
      } else {
        this.tags[type] = tag
      }
    } as T
  }
}

/** Returns the tag attached with [T] as a key, or null if no tag is attached with that key. */
public inline fun <reified T : Any> Taggable.tag(): T? = tag(T::class)

/**
 * Attaches [tag] to the request using [T] as a key. Tags can be read from a
 * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
 * [T].
 *
 * Use this API to attach debugging or other application data to a spec so that you may read it in
 * other APIs or callbacks.
 */

public inline fun <reified T : Any> AnnotationSpec.Builder.tag(tag: T?): AnnotationSpec.Builder =
  tag(T::class, tag)

/**
 * Attaches [tag] to the request using [T] as a key. Tags can be read from a
 * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
 * [T].
 *
 * Use this API to attach debugging or other application data to a spec so that you may read it in
 * other APIs or callbacks.
 */
public inline fun <reified T : Any> FileSpec.Builder.tag(tag: T?): FileSpec.Builder =
  tag(T::class, tag)

/**
 * Attaches [tag] to the request using [T] as a key. Tags can be read from a
 * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
 * [T].
 *
 * Use this API to attach debugging or other application data to a spec so that you may read it in
 * other APIs or callbacks.
 */
public inline fun <reified T : Any> FunSpec.Builder.tag(tag: T?): FunSpec.Builder =
  tag(T::class, tag)

/**
 * Attaches [tag] to the request using [T] as a key. Tags can be read from a
 * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
 * [T].
 *
 * Use this API to attach debugging or other application data to a spec so that you may read it in
 * other APIs or callbacks.
 */
public inline fun <reified T : Any> ParameterSpec.Builder.tag(tag: T?): ParameterSpec.Builder =
  tag(T::class, tag)

/**
 * Attaches [tag] to the request using [T] as a key. Tags can be read from a
 * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
 * [T].
 *
 * Use this API to attach debugging or other application data to a spec so that you may read it in
 * other APIs or callbacks.
 */
public inline fun <reified T : Any> PropertySpec.Builder.tag(tag: T?): PropertySpec.Builder =
  tag(T::class, tag)

/**
 * Attaches [tag] to the request using [T] as a key. Tags can be read from a
 * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
 * [T].
 *
 * Use this API to attach debugging or other application data to a spec so that you may read it in
 * other APIs or callbacks.
 */
public inline fun <reified T : Any> TypeAliasSpec.Builder.tag(tag: T?): TypeAliasSpec.Builder =
  tag(T::class, tag)

/**
 * Attaches [tag] to the request using [T] as a key. Tags can be read from a
 * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
 * [T].
 *
 * Use this API to attach debugging or other application data to a spec so that you may read it in
 * other APIs or callbacks.
 */
public inline fun <reified T : Any> TypeSpec.Builder.tag(tag: T?): TypeSpec.Builder =
  tag(T::class, tag)

internal fun Taggable.Builder<*>.buildTagMap(): TagMap = TagMap(tags)

@JvmInline
internal value class TagMap private constructor(override val tags: Map<KClass<*>, Any>) : Taggable {
  companion object {
    operator fun invoke(tags: Map<KClass<*>, Any>): TagMap = TagMap(tags.toImmutableMap())
  }
}

/*
 * Copyright (C) 2017 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import com.squareup.kotlinpoet.KModifier.ACTUAL
import com.squareup.kotlinpoet.KModifier.INTERNAL
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.KModifier.PUBLIC
import java.lang.reflect.Type
import kotlin.reflect.KClass

/** A generated typealias declaration */
public class TypeAliasSpec private constructor(
  builder: Builder,
  private val tagMap: TagMap = builder.buildTagMap(),
) : Taggable by tagMap {
  public val name: String = builder.name
  public val type: TypeName = builder.type
  public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
  public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
  public val kdoc: CodeBlock = builder.kdoc.build()
  public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()

  internal fun emit(codeWriter: CodeWriter) {
    codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
    codeWriter.emitAnnotations(annotations, false)
    codeWriter.emitModifiers(modifiers, setOf(PUBLIC))
    codeWriter.emitCode("typealias %N", name)
    codeWriter.emitTypeVariables(typeVariables)
    codeWriter.emitCode(" = %T", type)
    codeWriter.emit("\n")
  }

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

  override fun hashCode(): Int = toString().hashCode()

  override fun toString(): String = buildCodeString { emit(this) }

  @JvmOverloads
  public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
    val builder = Builder(name, type)
    builder.modifiers += modifiers
    builder.typeVariables += typeVariables
    builder.kdoc.add(kdoc)
    builder.annotations += annotations
    builder.tags += tagMap.tags
    return builder
  }

  public class Builder internal constructor(
    internal val name: String,
    internal val type: TypeName,
  ) : Taggable.Builder<Builder> {
    internal val kdoc = CodeBlock.builder()

    public val modifiers: MutableSet<KModifier> = mutableSetOf()
    public val typeVariables: MutableSet<TypeVariableName> = mutableSetOf()
    public val annotations: MutableList<AnnotationSpec> = mutableListOf()
    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()

    public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
      modifiers.forEach(this::addModifier)
    }

    public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
      modifiers.forEach(this::addModifier)
    }

    private fun addModifier(modifier: KModifier) {
      this.modifiers.add(modifier)
    }

    public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
      this.typeVariables += typeVariables
    }

    public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
      typeVariables += typeVariable
    }

    public fun addKdoc(format: String, vararg args: Any): Builder = apply {
      kdoc.add(format, *args)
    }

    public fun addKdoc(block: CodeBlock): Builder = apply {
      kdoc.add(block)
    }

    public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
      this.annotations += annotationSpecs
    }

    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
      annotations += annotationSpec
    }

    public fun addAnnotation(annotation: ClassName): Builder = apply {
      annotations += AnnotationSpec.builder(annotation).build()
    }

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    public fun addAnnotation(annotation: Class<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun addAnnotation(annotation: KClass<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun build(): TypeAliasSpec {
      for (it in modifiers) {
        require(it in ALLOWABLE_MODIFIERS) {
          "unexpected typealias modifier $it"
        }
      }
      return TypeAliasSpec(this)
    }

    private companion object {
      private val ALLOWABLE_MODIFIERS = setOf(PUBLIC, INTERNAL, PRIVATE, ACTUAL)
    }
  }

  public companion object {
    @JvmStatic public fun builder(name: String, type: TypeName): Builder = Builder(name, type)

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    public fun builder(name: String, type: Type): Builder =
      builder(name, type.asTypeName())

    @JvmStatic public fun builder(name: String, type: KClass<*>): Builder =
      builder(name, type.asTypeName())
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://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.
 */
@file:JvmName("TypeNames")

package com.squareup.kotlinpoet

import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import java.lang.reflect.GenericArrayType
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.TypeVariable
import java.lang.reflect.WildcardType
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.element.TypeParameterElement
import javax.lang.model.type.ArrayType
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.ErrorType
import javax.lang.model.type.NoType
import javax.lang.model.type.PrimitiveType
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
import javax.lang.model.util.SimpleTypeVisitor8
import kotlin.reflect.KClass
import kotlin.reflect.typeOf

/**
 * Any type in Kotlin's type system. This class identifies simple types like `Int` and `String`,
 * nullable types like `Int?`, composite types like `Array<String>` and `Set<String>`, and
 * unassignable types like `Unit`.
 *
 * Type names are dumb identifiers only and do not model the values they name. For example, the
 * type name for `kotlin.List` doesn't know about the `size()` function, the fact that lists are
 * collections, or even that it accepts a single type parameter.
 *
 * Instances of this class are immutable value objects that implement `equals()` and `hashCode()`
 * properly.
 *
 * Referencing existing types
 * --------------------------
 *
 * In an annotation processor you can get a type name instance for a type mirror by calling
 * [asTypeName]. In reflection code, you can use [asTypeName].

 * Defining new types
 * ------------------
 *
 * Create new reference types like `com.example.HelloWorld` with [ClassName.bestGuess]. To build composite
 * types like `Set<Long>`, use the factory methods on [ParameterizedTypeName], [TypeVariableName],
 * and [WildcardTypeName].
 */
public sealed class TypeName constructor(
  public val isNullable: Boolean,
  annotations: List<AnnotationSpec>,
  internal val tagMap: TagMap,
) : Taggable by tagMap {
  public val annotations: List<AnnotationSpec> = annotations.toImmutableList()

  /** Lazily-initialized toString of this type name.  */
  private val cachedString: String by lazy {
    buildCodeString {
      emitAnnotations(this)
      emit(this)
      if (isNullable) emit("?")
    }
  }

  public fun copy(
    nullable: Boolean = this.isNullable,
    annotations: List<AnnotationSpec> = this.annotations.toList(),
  ): TypeName {
    return copy(nullable, annotations, this.tags)
  }

  public abstract fun copy(
    nullable: Boolean = this.isNullable,
    annotations: List<AnnotationSpec> = this.annotations.toList(),
    tags: Map<KClass<*>, Any> = this.tags,
  ): TypeName

  public val isAnnotated: Boolean get() = annotations.isNotEmpty()

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

    other as TypeName

    if (isNullable != other.isNullable) return false
    if (annotations != other.annotations) return false
    // do not check for equality of tags, these are considered side-channel data

    return true
  }

  override fun hashCode(): Int {
    var result = isNullable.hashCode()
    result = 31 * result + annotations.hashCode()
    return result
  }

  override fun toString(): String = cachedString

  internal abstract fun emit(out: CodeWriter): CodeWriter

  internal fun emitAnnotations(out: CodeWriter) {
    for (annotation in annotations) {
      annotation.emit(out, true)
      out.emit(" ")
    }
  }

  internal fun emitNullable(out: CodeWriter) {
    if (isNullable) {
      out.emit("?")
    }
  }

  public companion object {
    internal fun get(
      mirror: TypeMirror,
      typeVariables: Map<TypeParameterElement, TypeVariableName>,
    ): TypeName {
      return mirror.accept(
        object : SimpleTypeVisitor8<TypeName, Void?>() {
          override fun visitPrimitive(t: PrimitiveType, p: Void?): TypeName {
            return when (t.kind) {
              TypeKind.BOOLEAN -> BOOLEAN
              TypeKind.BYTE -> BYTE
              TypeKind.SHORT -> SHORT
              TypeKind.INT -> INT
              TypeKind.LONG -> LONG
              TypeKind.CHAR -> CHAR
              TypeKind.FLOAT -> FLOAT
              TypeKind.DOUBLE -> DOUBLE
              else -> throw AssertionError()
            }
          }

          override fun visitDeclared(t: DeclaredType, p: Void?): TypeName {
            val rawType: ClassName = (t.asElement() as TypeElement).asClassName()
            val enclosingType = t.enclosingType
            val enclosing = if (enclosingType.kind != TypeKind.NONE &&
              Modifier.STATIC !in t.asElement().modifiers
            ) {
              enclosingType.accept(this, null)
            } else {
              null
            }
            if (t.typeArguments.isEmpty() && enclosing !is ParameterizedTypeName) {
              return rawType
            }

            val typeArgumentNames = mutableListOf<TypeName>()
            for (typeArgument in t.typeArguments) {
              typeArgumentNames += get(typeArgument, typeVariables)
            }
            return if (enclosing is ParameterizedTypeName) {
              enclosing.nestedClass(rawType.simpleName, typeArgumentNames)
            } else {
              ParameterizedTypeName(null, rawType, typeArgumentNames)
            }
          }

          override fun visitError(t: ErrorType, p: Void?): TypeName {
            return visitDeclared(t, p)
          }

          override fun visitArray(t: ArrayType, p: Void?): ParameterizedTypeName {
            return ARRAY.parameterizedBy(get(t.componentType, typeVariables))
          }

          override fun visitTypeVariable(
            t: javax.lang.model.type.TypeVariable,
            p: Void?,
          ): TypeName {
            return TypeVariableName.get(t, typeVariables.toMutableMap())
          }

          override fun visitWildcard(t: javax.lang.model.type.WildcardType, p: Void?): TypeName {
            return WildcardTypeName.get(t, typeVariables)
          }

          override fun visitNoType(t: NoType, p: Void?): TypeName {
            if (t.kind == TypeKind.VOID) return UNIT
            return super.visitUnknown(t, p)
          }

          override fun defaultAction(e: TypeMirror?, p: Void?): TypeName {
            throw IllegalArgumentException("Unexpected type mirror: " + e!!)
          }
        },
        null,
      )
    }

    internal fun get(type: Type, map: MutableMap<Type, TypeVariableName>): TypeName {
      return when (type) {
        is Class<*> -> when {
          type === Void.TYPE -> UNIT
          type === Boolean::class.javaPrimitiveType -> BOOLEAN
          type === Byte::class.javaPrimitiveType -> BYTE
          type === Short::class.javaPrimitiveType -> SHORT
          type === Int::class.javaPrimitiveType -> INT
          type === Long::class.javaPrimitiveType -> LONG
          type === Char::class.javaPrimitiveType -> CHAR
          type === Float::class.javaPrimitiveType -> FLOAT
          type === Double::class.javaPrimitiveType -> DOUBLE
          type.isArray -> ARRAY.parameterizedBy(get(type.componentType, map))
          else -> type.asClassName()
        }
        is ParameterizedType -> ParameterizedTypeName.get(type, map)
        is WildcardType -> WildcardTypeName.get(type, map)
        is TypeVariable<*> -> TypeVariableName.get(type, map)
        is GenericArrayType -> ARRAY.parameterizedBy(get(type.genericComponentType, map))
        else -> throw IllegalArgumentException("unexpected type: $type")
      }
    }
  }
}

@JvmField public val ANY: ClassName = ClassName("kotlin", "Any")

@JvmField public val ARRAY: ClassName = ClassName("kotlin", "Array")

@JvmField public val UNIT: ClassName = ClassName("kotlin", "Unit")

@JvmField public val BOOLEAN: ClassName = ClassName("kotlin", "Boolean")

@JvmField public val BYTE: ClassName = ClassName("kotlin", "Byte")

@JvmField public val SHORT: ClassName = ClassName("kotlin", "Short")

@JvmField public val INT: ClassName = ClassName("kotlin", "Int")

@JvmField public val LONG: ClassName = ClassName("kotlin", "Long")

@JvmField public val CHAR: ClassName = ClassName("kotlin", "Char")

@JvmField public val FLOAT: ClassName = ClassName("kotlin", "Float")

@JvmField public val DOUBLE: ClassName = ClassName("kotlin", "Double")

@JvmField public val STRING: ClassName = ClassName("kotlin", "String")

@JvmField public val CHAR_SEQUENCE: ClassName = ClassName("kotlin", "CharSequence")

@JvmField public val COMPARABLE: ClassName = ClassName("kotlin", "Comparable")

@JvmField public val THROWABLE: ClassName = ClassName("kotlin", "Throwable")

@JvmField public val ANNOTATION: ClassName = ClassName("kotlin", "Annotation")

@JvmField public val NOTHING: ClassName = ClassName("kotlin", "Nothing")

@JvmField public val NUMBER: ClassName = ClassName("kotlin", "Number")

@JvmField public val ITERABLE: ClassName = ClassName("kotlin.collections", "Iterable")

@JvmField public val COLLECTION: ClassName = ClassName("kotlin.collections", "Collection")

@JvmField public val LIST: ClassName = ClassName("kotlin.collections", "List")

@JvmField public val SET: ClassName = ClassName("kotlin.collections", "Set")

@JvmField public val MAP: ClassName = ClassName("kotlin.collections", "Map")

@JvmField public val MAP_ENTRY: ClassName = MAP.nestedClass("Entry")

@JvmField public val MUTABLE_ITERABLE: ClassName =
  ClassName("kotlin.collections", "MutableIterable")

@JvmField public val MUTABLE_COLLECTION: ClassName =
  ClassName("kotlin.collections", "MutableCollection")

@JvmField public val MUTABLE_LIST: ClassName = ClassName("kotlin.collections", "MutableList")

@JvmField public val MUTABLE_SET: ClassName = ClassName("kotlin.collections", "MutableSet")

@JvmField public val MUTABLE_MAP: ClassName = ClassName("kotlin.collections", "MutableMap")

@JvmField public val MUTABLE_MAP_ENTRY: ClassName = MUTABLE_MAP.nestedClass("Entry")

@JvmField public val BOOLEAN_ARRAY: ClassName = ClassName("kotlin", "BooleanArray")

@JvmField public val BYTE_ARRAY: ClassName = ClassName("kotlin", "ByteArray")

@JvmField public val CHAR_ARRAY: ClassName = ClassName("kotlin", "CharArray")

@JvmField public val SHORT_ARRAY: ClassName = ClassName("kotlin", "ShortArray")

@JvmField public val INT_ARRAY: ClassName = ClassName("kotlin", "IntArray")

@JvmField public val LONG_ARRAY: ClassName = ClassName("kotlin", "LongArray")

@JvmField public val FLOAT_ARRAY: ClassName = ClassName("kotlin", "FloatArray")

@JvmField public val DOUBLE_ARRAY: ClassName = ClassName("kotlin", "DoubleArray")

@JvmField public val ENUM: ClassName = ClassName("kotlin", "Enum")

@JvmField public val U_BYTE: ClassName = ClassName("kotlin", "UByte")

@JvmField public val U_SHORT: ClassName = ClassName("kotlin", "UShort")

@JvmField public val U_INT: ClassName = ClassName("kotlin", "UInt")

@JvmField public val U_LONG: ClassName = ClassName("kotlin", "ULong")

@JvmField public val U_BYTE_ARRAY: ClassName = ClassName("kotlin", "UByteArray")

@JvmField public val U_SHORT_ARRAY: ClassName = ClassName("kotlin", "UShortArray")

@JvmField public val U_INT_ARRAY: ClassName = ClassName("kotlin", "UIntArray")

@JvmField public val U_LONG_ARRAY: ClassName = ClassName("kotlin", "ULongArray")

/** The wildcard type `*` which is shorthand for `out Any?`. */
@JvmField public val STAR: WildcardTypeName = WildcardTypeName.producerOf(ANY.copy(nullable = true))

/** [Dynamic] is a singleton `object` type, so this is a shorthand for it in Java. */
@JvmField public val DYNAMIC: Dynamic = Dynamic

/** Returns a [TypeName] equivalent to this [TypeMirror]. */
@DelicateKotlinPoetApi(
  message = "Mirror APIs don't give complete information on Kotlin types. Consider using" +
    " the kotlinpoet-metadata APIs instead.",
)
@JvmName("get")
public fun TypeMirror.asTypeName(): TypeName = TypeName.get(this, mutableMapOf())

/** Returns a [TypeName] equivalent to this [KClass].  */
@JvmName("get")
public fun KClass<*>.asTypeName(): ClassName = asClassName()

/** Returns a [TypeName] equivalent to this [Type].  */
@JvmName("get")
public fun Type.asTypeName(): TypeName = TypeName.get(this, mutableMapOf())

/**
 * Returns a [TypeName] equivalent of the reified type parameter [T] using reflection, maybe using kotlin-reflect
 * if required.
 */
public inline fun <reified T> typeNameOf(): TypeName = typeOf<T>().asTypeName()

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import com.squareup.kotlinpoet.KModifier.ABSTRACT
import com.squareup.kotlinpoet.KModifier.ANNOTATION
import com.squareup.kotlinpoet.KModifier.COMPANION
import com.squareup.kotlinpoet.KModifier.ENUM
import com.squareup.kotlinpoet.KModifier.EXPECT
import com.squareup.kotlinpoet.KModifier.EXTERNAL
import com.squareup.kotlinpoet.KModifier.FUN
import com.squareup.kotlinpoet.KModifier.INLINE
import com.squareup.kotlinpoet.KModifier.INTERNAL
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.KModifier.PROTECTED
import com.squareup.kotlinpoet.KModifier.PUBLIC
import com.squareup.kotlinpoet.KModifier.SEALED
import com.squareup.kotlinpoet.KModifier.VALUE
import java.lang.reflect.Type
import javax.lang.model.element.Element
import kotlin.reflect.KClass

/** A generated class, interface, or enum declaration. */
@OptIn(ExperimentalKotlinPoetApi::class)
public class TypeSpec private constructor(
  builder: Builder,
  private val tagMap: TagMap = builder.buildTagMap(),
  private val delegateOriginatingElements: OriginatingElementsHolder = builder.originatingElements
    .plus(builder.typeSpecs.flatMap(TypeSpec::originatingElements))
    .buildOriginatingElements(),
  private val contextReceivers: ContextReceivers = builder.buildContextReceivers(),
) : Taggable by tagMap, OriginatingElementsHolder by delegateOriginatingElements, ContextReceivable by contextReceivers {
  public val kind: Kind = builder.kind
  public val name: String? = builder.name
  public val kdoc: CodeBlock = builder.kdoc.build()
  public val annotationSpecs: List<AnnotationSpec> = builder.annotationSpecs.toImmutableList()
  public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
  public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
  public val primaryConstructor: FunSpec? = builder.primaryConstructor
  public val superclass: TypeName = builder.superclass
  public val superclassConstructorParameters: List<CodeBlock> =
    builder.superclassConstructorParameters.toImmutableList()

  public val isEnum: Boolean = builder.isEnum
  public val isAnnotation: Boolean = builder.isAnnotation
  public val isCompanion: Boolean = builder.isCompanion
  public val isAnonymousClass: Boolean = builder.isAnonymousClass
  public val isFunctionalInterface: Boolean = builder.isFunInterface

  /**
   * Map of superinterfaces - entries with a null value represent a regular superinterface (with
   * no delegation), while non-null [CodeBlock] values represent delegates
   * for the corresponding [TypeSpec] interface (key) value
   */
  public val superinterfaces: Map<TypeName, CodeBlock?> = builder.superinterfaces.toImmutableMap()
  public val enumConstants: Map<String, TypeSpec> = builder.enumConstants.toImmutableMap()
  public val propertySpecs: List<PropertySpec> = builder.propertySpecs.toImmutableList()
  public val initializerBlock: CodeBlock = builder.initializerBlock.build()
  public val initializerIndex: Int = builder.initializerIndex
  public val funSpecs: List<FunSpec> = builder.funSpecs.toImmutableList()
  public val typeSpecs: List<TypeSpec> = builder.typeSpecs.toImmutableList()
  internal val nestedTypesSimpleNames = typeSpecs.map { it.name }.toImmutableSet()

  @JvmOverloads
  public fun toBuilder(kind: Kind = this.kind, name: String? = this.name): Builder {
    val builder = Builder(kind, name)
    builder.modifiers += modifiers
    builder.kdoc.add(kdoc)
    builder.annotationSpecs += annotationSpecs
    builder.typeVariables += typeVariables
    builder.superclass = superclass
    builder.superclassConstructorParameters += superclassConstructorParameters
    builder.enumConstants += enumConstants
    builder.propertySpecs += propertySpecs
    builder.funSpecs += funSpecs
    builder.typeSpecs += typeSpecs
    builder.initializerBlock.add(initializerBlock)
    builder.initializerIndex = initializerIndex
    builder.superinterfaces.putAll(superinterfaces)
    builder.primaryConstructor = primaryConstructor
    builder.tags += tagMap.tags
    builder.originatingElements += originatingElements
    builder.contextReceiverTypes += contextReceiverTypes
    return builder
  }

  internal fun emit(
    codeWriter: CodeWriter,
    enumName: String?,
    implicitModifiers: Set<KModifier> = emptySet(),
    isNestedExternal: Boolean = false,
  ) {
    // Types.
    val areNestedExternal = EXTERNAL in modifiers || isNestedExternal

    // Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put
    // it back afterwards when this type is complete.
    val previousStatementLine = codeWriter.statementLine
    codeWriter.statementLine = -1

    val constructorProperties: Map<String, PropertySpec> = constructorProperties()
    val superclassConstructorParametersBlock = superclassConstructorParameters.joinToCode()

    try {
      if (enumName != null) {
        codeWriter.emitKdoc(kdocWithConstructorParameters())
        codeWriter.emitAnnotations(annotationSpecs, false)
        codeWriter.emitCode("%N", enumName)
        if (superclassConstructorParametersBlock.isNotEmpty()) {
          codeWriter.emit("(")
          codeWriter.emitCode(superclassConstructorParametersBlock)
          codeWriter.emit(")")
        }
        if (hasNoBody) {
          return // Avoid unnecessary braces "{}".
        }
        codeWriter.emit(" {\n")
      } else if (isAnonymousClass) {
        codeWriter.emitCode("object")
        val supertype = if (superclass != ANY) {
          if (!areNestedExternal && !modifiers.contains(EXPECT)) {
            listOf(CodeBlock.of(" %T(%L)", superclass, superclassConstructorParametersBlock))
          } else {
            listOf(CodeBlock.of(" %T", superclass))
          }
        } else {
          listOf()
        }

        val allSuperTypes = supertype + if (superinterfaces.isNotEmpty()) {
          superinterfaces.keys.map { CodeBlock.of(" %T", it) }
        } else {
          emptyList()
        }

        if (allSuperTypes.isNotEmpty()) {
          codeWriter.emitCode(" :")
          codeWriter.emitCode(allSuperTypes.joinToCode(","))
        }
        if (hasNoBody) {
          codeWriter.emit(" {\n}")
          return
        }
        codeWriter.emit(" {\n")
      } else {
        codeWriter.emitKdoc(kdocWithConstructorParameters())
        codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
        codeWriter.emitAnnotations(annotationSpecs, false)
        codeWriter.emitModifiers(
          modifiers,
          if (isNestedExternal) setOf(PUBLIC, EXTERNAL) else setOf(PUBLIC),
        )
        codeWriter.emit(kind.declarationKeyword)
        if (name != null) {
          codeWriter.emitCode(" %N", this)
        }
        codeWriter.emitTypeVariables(typeVariables)

        primaryConstructor?.let {
          codeWriter.pushType(this) // avoid name collisions when emitting primary constructor
          val emittedAnnotations = it.annotations.isNotEmpty()
          val useKeyword = it.annotations.isNotEmpty() || it.modifiers.isNotEmpty()

          if (it.annotations.isNotEmpty()) {
            codeWriter.emit(" ")
            codeWriter.emitAnnotations(it.annotations, true)
          }

          if (it.modifiers.isNotEmpty()) {
            if (!emittedAnnotations) codeWriter.emit(" ")
            codeWriter.emitModifiers(it.modifiers)
          }

          if (useKeyword) {
            codeWriter.emit("constructor")
          }

          it.parameters.emit(codeWriter, forceNewLines = true) { param ->
            val property = constructorProperties[param.name]
            if (property != null) {
              property.emit(
                codeWriter,
                setOf(PUBLIC),
                withInitializer = false,
                inline = true,
                inlineAnnotations = false,
              )
              param.emitDefaultValue(codeWriter)
            } else {
              param.emit(codeWriter, emitKdoc = true, inlineAnnotations = false)
            }
          }

          codeWriter.popType()
        }

        val types = listOf(superclass).filter { it != ANY }.map {
          if (primaryConstructor != null || funSpecs.none(FunSpec::isConstructor)) {
            if (!areNestedExternal && !modifiers.contains(EXPECT)) {
              CodeBlock.of("%T(%L)", it, superclassConstructorParametersBlock)
            } else {
              CodeBlock.of("%T", it)
            }
          } else {
            CodeBlock.of("%T", it)
          }
        }
        val superTypes = types + superinterfaces.entries.map { (type, init) ->
          if (init == null) CodeBlock.of("%T", type) else CodeBlock.of("%T by %L", type, init)
        }

        if (superTypes.isNotEmpty()) {
          codeWriter.emitCode(superTypes.joinToCode(separator = ", ", prefix = " : "))
        }

        codeWriter.emitWhereBlock(typeVariables)

        if (hasNoBody) {
          codeWriter.emit("\n")
          return // Avoid unnecessary braces "{}".
        }
        codeWriter.emit(" {\n")
      }

      codeWriter.pushType(this)
      codeWriter.indent()
      var firstMember = true
      for ((key, value) in enumConstants.entries) {
        if (!firstMember) codeWriter.emit("\n")
        value.emit(codeWriter, key)
        codeWriter.emit(",")
        firstMember = false
      }
      if (isEnum) {
        if (!firstMember) {
          codeWriter.emit("\n")
        }
        if (propertySpecs.isNotEmpty() || funSpecs.isNotEmpty() || typeSpecs.isNotEmpty()) {
          codeWriter.emit(";\n")
        }
      }

      val cachedHasInitializer = hasInitializer
      var initializerEmitted = false
      fun possiblyEmitInitializer() {
        if (initializerEmitted) return
        initializerEmitted = true
        if (cachedHasInitializer) {
          if (!firstMember) codeWriter.emit("\n")
          codeWriter.emitCode(initializerBlock)
          firstMember = false
        }
      }

      // Properties and initializer block.
      for ((index, propertySpec) in propertySpecs.withIndex()) {
        // Initializer block.
        if (index == initializerIndex) {
          possiblyEmitInitializer()
        }
        if (constructorProperties.containsKey(propertySpec.name)) {
          continue
        }
        if (!firstMember) codeWriter.emit("\n")
        propertySpec.emit(codeWriter, kind.implicitPropertyModifiers(modifiers))
        firstMember = false
      }

      // One last try in case the initializer index is after all properties
      possiblyEmitInitializer()

      if (primaryConstructor != null && primaryConstructor.body.isNotEmpty()) {
        codeWriter.emit("init {\n")
        codeWriter.indent()
        codeWriter.emitCode(primaryConstructor.body)
        codeWriter.unindent()
        codeWriter.emit("}\n")
      }

      // Constructors.
      for (funSpec in funSpecs) {
        if (!funSpec.isConstructor) continue
        if (!firstMember) codeWriter.emit("\n")
        funSpec.emit(codeWriter, name, kind.implicitFunctionModifiers(modifiers + implicitModifiers), false)
        firstMember = false
      }

      // Functions.
      for (funSpec in funSpecs) {
        if (funSpec.isConstructor) continue
        if (!firstMember) codeWriter.emit("\n")
        funSpec.emit(codeWriter, name, kind.implicitFunctionModifiers(modifiers + implicitModifiers), true)
        firstMember = false
      }

      for (typeSpec in typeSpecs) {
        if (!firstMember) codeWriter.emit("\n")
        typeSpec.emit(codeWriter, null, kind.implicitTypeModifiers(modifiers + implicitModifiers), isNestedExternal = areNestedExternal)
        firstMember = false
      }

      codeWriter.unindent()
      codeWriter.popType()

      codeWriter.emit("}")
      if (enumName == null && !isAnonymousClass) {
        codeWriter.emit("\n") // If this type isn't also a value, include a trailing newline.
      }
    } finally {
      codeWriter.statementLine = previousStatementLine
    }
  }

  /** Returns the properties that can be declared inline as constructor parameters. */
  private fun constructorProperties(): Map<String, PropertySpec> {
    if (primaryConstructor == null) return emptyMap()

    // Properties added after the initializer are not permitted to be inlined into the constructor
    // due to ordering concerns.
    val range = if (hasInitializer) {
      0 until initializerIndex
    } else {
      propertySpecs.indices
    }
    val result: MutableMap<String, PropertySpec> = LinkedHashMap()
    for (propertyIndex in range) {
      val property = propertySpecs[propertyIndex]
      if (property.getter != null || property.setter != null) continue
      val parameter = primaryConstructor.parameter(property.name) ?: continue
      if (parameter.type != property.type) continue
      if (!isPropertyInitializerConstructorParameter(property, parameter)) {
        continue
      }

      result[property.name] = property.fromPrimaryConstructorParameter(parameter)
    }
    return result
  }

  /**
   * Returns true if the property can be declared inline as a constructor parameter
   */
  private fun isPropertyInitializerConstructorParameter(
    property: PropertySpec,
    parameter: ParameterSpec,
  ): Boolean {
    val parameterName = CodeBlock.of("%N", parameter).toString()
    val initializerCode = property.initializer.toString().escapeIfNecessary(validate = false)
    return parameterName == initializerCode
  }

  /**
   * Returns KDoc comments including those of primary constructor parameters.
   *
   * If the primary constructor parameter is not mapped to a property, or if the property doesn't
   * have its own KDoc - the parameter's KDoc will be attached to the parameter. Otherwise, if both
   * the parameter and the property have KDoc - the property's KDoc will be attached to the
   * property/parameter, and the parameter's KDoc will be printed in the type header.
   */
  private fun kdocWithConstructorParameters(): CodeBlock {
    if (primaryConstructor == null || primaryConstructor.parameters.isEmpty()) {
      return kdoc.ensureEndsWithNewLine()
    }
    val constructorProperties = constructorProperties()
    val parametersWithKdoc = primaryConstructor.parameters.filter { parameter ->
      val propertyKdoc = constructorProperties[parameter.name]?.kdoc ?: CodeBlock.EMPTY
      return@filter parameter.kdoc.isNotEmpty() && propertyKdoc.isNotEmpty() &&
        parameter.kdoc != propertyKdoc
    }
    return with(kdoc.ensureEndsWithNewLine().toBuilder()) {
      parametersWithKdoc.forEachIndexed { index, parameter ->
        if (index == 0 && kdoc.isNotEmpty()) add("\n")
        add("@param %L %L", parameter.name, parameter.kdoc.ensureEndsWithNewLine())
      }
      build()
    }
  }

  private val hasInitializer: Boolean get() = initializerIndex != -1 && initializerBlock.isNotEmpty()

  private val hasNoBody: Boolean
    get() {
      if (propertySpecs.isNotEmpty()) {
        val constructorProperties = constructorProperties()
        for (propertySpec in propertySpecs) {
          if (!constructorProperties.containsKey(propertySpec.name)) {
            return false
          }
        }
      }
      return enumConstants.isEmpty() &&
        initializerBlock.isEmpty() &&
        (primaryConstructor?.body?.isEmpty() ?: true) &&
        funSpecs.isEmpty() &&
        typeSpecs.isEmpty()
    }

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

  override fun hashCode(): Int = toString().hashCode()

  override fun toString(): String = buildCodeString { emit(this, null) }

  public enum class Kind(
    internal val declarationKeyword: String,
    internal val defaultImplicitPropertyModifiers: Set<KModifier>,
    internal val defaultImplicitFunctionModifiers: Set<KModifier>,
    internal val defaultImplicitTypeModifiers: Set<KModifier>,
  ) {
    CLASS("class", setOf(PUBLIC), setOf(PUBLIC), setOf()),
    OBJECT("object", setOf(PUBLIC), setOf(PUBLIC), setOf()),
    INTERFACE("interface", setOf(PUBLIC, ABSTRACT), setOf(PUBLIC, ABSTRACT), setOf()),
    ;

    internal fun implicitPropertyModifiers(modifiers: Set<KModifier>): Set<KModifier> {
      return defaultImplicitPropertyModifiers + when {
        ANNOTATION in modifiers -> emptySet()
        EXPECT in modifiers -> setOf(EXPECT)
        EXTERNAL in modifiers -> setOf(EXTERNAL)
        else -> emptySet()
      }
    }

    internal fun implicitFunctionModifiers(modifiers: Set<KModifier> = setOf()): Set<KModifier> {
      return defaultImplicitFunctionModifiers + when {
        ANNOTATION in modifiers -> setOf(ABSTRACT)
        EXPECT in modifiers -> setOf(EXPECT)
        EXTERNAL in modifiers -> setOf(EXTERNAL)
        else -> emptySet()
      }
    }

    internal fun implicitTypeModifiers(modifiers: Set<KModifier> = setOf()): Set<KModifier> {
      return defaultImplicitTypeModifiers + when {
        EXPECT in modifiers -> setOf(EXPECT)
        EXTERNAL in modifiers -> setOf(EXTERNAL)
        else -> emptySet()
      }
    }
  }

  public class Builder internal constructor(
    internal var kind: Kind,
    internal val name: String?,
    vararg modifiers: KModifier,
  ) : Taggable.Builder<Builder>, OriginatingElementsHolder.Builder<Builder>, ContextReceivable.Builder<Builder> {
    internal val kdoc = CodeBlock.builder()
    internal var primaryConstructor: FunSpec? = null
    internal var superclass: TypeName = ANY
    internal val initializerBlock = CodeBlock.builder()
    public var initializerIndex: Int = -1
    internal val isAnonymousClass get() = name == null && kind == Kind.CLASS
    internal val isExternal get() = EXTERNAL in modifiers
    internal val isEnum get() = kind == Kind.CLASS && ENUM in modifiers
    internal val isAnnotation get() = kind == Kind.CLASS && ANNOTATION in modifiers
    internal val isCompanion get() = kind == Kind.OBJECT && COMPANION in modifiers
    internal val isInlineOrValClass get() = kind == Kind.CLASS &&
      (INLINE in modifiers || VALUE in modifiers)
    internal val isSimpleClass get() = kind == Kind.CLASS && !isEnum && !isAnnotation
    internal val isFunInterface get() = kind == Kind.INTERFACE && FUN in modifiers

    override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
    override val originatingElements: MutableList<Element> = mutableListOf()

    @ExperimentalKotlinPoetApi
    override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()
    public val modifiers: MutableSet<KModifier> = mutableSetOf(*modifiers)
    public val superinterfaces: MutableMap<TypeName, CodeBlock?> = mutableMapOf()
    public val enumConstants: MutableMap<String, TypeSpec> = mutableMapOf()
    public val annotationSpecs: MutableList<AnnotationSpec> = mutableListOf()
    public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
    public val superclassConstructorParameters: MutableList<CodeBlock> = mutableListOf()
    public val propertySpecs: MutableList<PropertySpec> = mutableListOf()
    public val funSpecs: MutableList<FunSpec> = mutableListOf()
    public val typeSpecs: MutableList<TypeSpec> = mutableListOf()

    public fun addKdoc(format: String, vararg args: Any): Builder = apply {
      kdoc.add(format, *args)
    }

    public fun addKdoc(block: CodeBlock): Builder = apply {
      kdoc.add(block)
    }

    public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
      this.annotationSpecs += annotationSpecs
    }

    public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
      annotationSpecs += annotationSpec
    }

    public fun addAnnotation(annotation: ClassName): Builder =
      addAnnotation(AnnotationSpec.builder(annotation).build())

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    public fun addAnnotation(annotation: Class<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun addAnnotation(annotation: KClass<*>): Builder =
      addAnnotation(annotation.asClassName())

    public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
      check(!isAnonymousClass) { "forbidden on anonymous types." }
      this.modifiers += modifiers
    }

    public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
      check(!isAnonymousClass) { "forbidden on anonymous types." }
      this.modifiers += modifiers
    }

    public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
      this.typeVariables += typeVariables
    }

    public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
      typeVariables += typeVariable
    }

    public fun primaryConstructor(primaryConstructor: FunSpec?): Builder = apply {
      check(kind == Kind.CLASS) {
        "$kind can't have a primary constructor"
      }
      if (primaryConstructor != null) {
        require(primaryConstructor.isConstructor) {
          "expected a constructor but was ${primaryConstructor.name}"
        }

        if (isInlineOrValClass) {
          check(primaryConstructor.parameters.size == 1) {
            "value/inline classes must have 1 parameter in constructor"
          }
        }
      }
      this.primaryConstructor = primaryConstructor
    }

    public fun superclass(superclass: TypeName): Builder = apply {
      checkCanHaveSuperclass()
      check(this.superclass === ANY) { "superclass already set to ${this.superclass}" }
      this.superclass = superclass
    }

    private fun checkCanHaveSuperclass() {
      check(isSimpleClass || kind == Kind.OBJECT) {
        "only classes can have super classes, not $kind"
      }
      check(!isInlineOrValClass) {
        "value/inline classes cannot have super classes"
      }
    }

    private fun checkCanHaveInitializerBlocks() {
      check(isSimpleClass || isEnum || kind == Kind.OBJECT) {
        "$kind can't have initializer blocks"
      }
      check(EXPECT !in modifiers) {
        "expect $kind can't have initializer blocks"
      }
    }

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    public fun superclass(superclass: Type): Builder = superclass(superclass.asTypeName())

    public fun superclass(superclass: KClass<*>): Builder = superclass(superclass.asTypeName())

    public fun addSuperclassConstructorParameter(
      format: String,
      vararg args: Any,
    ): Builder = apply {
      addSuperclassConstructorParameter(CodeBlock.of(format, *args))
    }

    public fun addSuperclassConstructorParameter(codeBlock: CodeBlock): Builder = apply {
      checkCanHaveSuperclass()
      this.superclassConstructorParameters += codeBlock
    }

    public fun addSuperinterfaces(superinterfaces: Iterable<TypeName>): Builder = apply {
      this.superinterfaces.putAll(superinterfaces.map { it to null })
    }

    public fun addSuperinterface(
      superinterface: TypeName,
      delegate: CodeBlock = CodeBlock.EMPTY,
    ): Builder = apply {
      if (delegate.isEmpty()) {
        this.superinterfaces[superinterface] = null
      } else {
        require(isSimpleClass || kind == Kind.OBJECT) {
          "delegation only allowed for classes and objects (found $kind '$name')"
        }
        require(!superinterface.isNullable) {
          "expected non-nullable type but was '${superinterface.copy(nullable = false)}'"
        }
        require(this.superinterfaces[superinterface] == null) {
          "'$name' can not delegate to $superinterface by $delegate with existing declaration by " +
            "${this.superinterfaces[superinterface]}"
        }
        this.superinterfaces[superinterface] = delegate
      }
    }

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    public fun addSuperinterface(
      superinterface: Type,
      delegate: CodeBlock = CodeBlock.EMPTY,
    ): Builder = addSuperinterface(superinterface.asTypeName(), delegate)

    public fun addSuperinterface(
      superinterface: KClass<*>,
      delegate: CodeBlock = CodeBlock.EMPTY,
    ): Builder = addSuperinterface(superinterface.asTypeName(), delegate)

    public fun addSuperinterface(
      superinterface: KClass<*>,
      constructorParameterName: String,
    ): Builder = addSuperinterface(superinterface.asTypeName(), constructorParameterName)

    public fun addSuperinterface(
      superinterface: TypeName,
      constructorParameter: String,
    ): Builder = apply {
      requireNotNull(primaryConstructor) {
        "delegating to constructor parameter requires not-null constructor"
      }
      val parameter = primaryConstructor?.parameter(constructorParameter)
      requireNotNull(parameter) {
        "no such constructor parameter '$constructorParameter' to delegate to for type '$name'"
      }
      addSuperinterface(superinterface, CodeBlock.of(constructorParameter))
    }

    @JvmOverloads public fun addEnumConstant(
      name: String,
      typeSpec: TypeSpec = anonymousClassBuilder().build(),
    ): Builder = apply {
      require(name != "name" && name != "ordinal") {
        "constant with name \"$name\" conflicts with a supertype member with the same name"
      }
      enumConstants[name] = typeSpec
    }

    public fun addProperties(propertySpecs: Iterable<PropertySpec>): Builder = apply {
      propertySpecs.map(this::addProperty)
    }

    public fun addProperty(propertySpec: PropertySpec): Builder = apply {
      if (EXPECT in modifiers) {
        require(propertySpec.initializer == null) {
          "properties in expect classes can't have initializers"
        }
        require(propertySpec.getter == null && propertySpec.setter == null) {
          "properties in expect classes can't have getters and setters"
        }
      }
      if (isEnum) {
        require(propertySpec.name != "name" && propertySpec.name != "ordinal") {
          "${propertySpec.name} is a final supertype member and can't be redeclared or overridden"
        }
      }
      propertySpecs += propertySpec
    }

    public fun addProperty(name: String, type: TypeName, vararg modifiers: KModifier): Builder =
      addProperty(PropertySpec.builder(name, type, *modifiers).build())

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    public fun addProperty(name: String, type: Type, vararg modifiers: KModifier): Builder =
      addProperty(name, type.asTypeName(), *modifiers)

    public fun addProperty(name: String, type: KClass<*>, vararg modifiers: KModifier): Builder =
      addProperty(name, type.asTypeName(), *modifiers)

    public fun addProperty(name: String, type: TypeName, modifiers: Iterable<KModifier>): Builder =
      addProperty(PropertySpec.builder(name, type, modifiers).build())

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    public fun addProperty(name: String, type: Type, modifiers: Iterable<KModifier>): Builder =
      addProperty(name, type.asTypeName(), modifiers)

    public fun addProperty(name: String, type: KClass<*>, modifiers: Iterable<KModifier>): Builder =
      addProperty(name, type.asTypeName(), modifiers)

    public fun addInitializerBlock(block: CodeBlock): Builder = apply {
      checkCanHaveInitializerBlocks()
      // Set index to however many properties we have
      // All properties added after this point are declared as such, including any that initialize
      // to a constructor param.
      initializerIndex = propertySpecs.size
      initializerBlock.add("init {\n")
        .indent()
        .add(block)
        .unindent()
        .add("}\n")
    }

    public fun addFunctions(funSpecs: Iterable<FunSpec>): Builder = apply {
      funSpecs.forEach { addFunction(it) }
    }

    public fun addFunction(funSpec: FunSpec): Builder = apply {
      funSpecs += funSpec
    }

    public fun addTypes(typeSpecs: Iterable<TypeSpec>): Builder = apply {
      this.typeSpecs += typeSpecs
    }

    public fun addType(typeSpec: TypeSpec): Builder = apply {
      typeSpecs += typeSpec
    }

    @ExperimentalKotlinPoetApi
    override fun contextReceivers(receiverTypes: Iterable<TypeName>): Builder = apply {
      check(isSimpleClass) { "contextReceivers can only be applied on simple classes" }
      contextReceiverTypes += receiverTypes
    }

    public fun build(): TypeSpec {
      if (enumConstants.isNotEmpty()) {
        check(isEnum) { "$name is not an enum and cannot have enum constants" }
      }

      if (superclassConstructorParameters.isNotEmpty()) {
        checkCanHaveSuperclass()

        check(!isExternal) {
          "delegated constructor call in external class is not allowed"
        }
      }
      check(!(isExternal && funSpecs.any { it.delegateConstructor != null })) {
        "delegated constructor call in external class is not allowed"
      }

      check(!(isAnonymousClass && typeVariables.isNotEmpty())) {
        "typevariables are forbidden on anonymous types"
      }

      val isAbstract = ABSTRACT in modifiers || SEALED in modifiers || kind != Kind.CLASS ||
        !isSimpleClass
      for (funSpec in funSpecs) {
        require(isAbstract || ABSTRACT !in funSpec.modifiers) {
          "non-abstract type $name cannot declare abstract function ${funSpec.name}"
        }
        when {
          kind == Kind.INTERFACE -> {
            requireNoneOf(funSpec.modifiers, INTERNAL, PROTECTED)
            requireNoneOrOneOf(funSpec.modifiers, ABSTRACT, PRIVATE)
          }
          isAnnotation -> require(funSpec.modifiers == kind.implicitFunctionModifiers(modifiers)) {
            "annotation class $name.${funSpec.name} " +
              "requires modifiers ${kind.implicitFunctionModifiers(modifiers)}"
          }
          EXPECT in modifiers -> require(funSpec.body.isEmpty()) {
            "functions in expect classes can't have bodies"
          }
        }
      }

      if (primaryConstructor == null) {
        require(funSpecs.none { it.isConstructor } || superclassConstructorParameters.isEmpty()) {
          "types without a primary constructor cannot specify secondary constructors and " +
            "superclass constructor parameters"
        }
      }

      if (isInlineOrValClass) {
        primaryConstructor?.let {
          check(it.parameters.size == 1) {
            "value/inline classes must have 1 parameter in constructor"
          }
        }

        check(propertySpecs.size > 0) {
          "value/inline classes must have at least 1 property"
        }

        val constructorParamName = primaryConstructor?.parameters?.firstOrNull()?.name
        constructorParamName?.let { paramName ->
          val underlyingProperty = propertySpecs.find { it.name == paramName }
          requireNotNull(underlyingProperty) {
            "value/inline classes must have a single read-only (val) property parameter."
          }
          check(!underlyingProperty.mutable) {
            "value/inline classes must have a single read-only (val) property parameter."
          }
        }
        check(superclass == Any::class.asTypeName()) {
          "value/inline classes cannot have super classes"
        }
      }

      if (isFunInterface) {
        // Note: Functional interfaces can contain any number of non-abstract functions.
        val abstractFunSpecs = funSpecs.filter { ABSTRACT in it.modifiers }
        check(abstractFunSpecs.size == 1) {
          "Functional interfaces must have exactly one abstract function. Contained " +
            "${abstractFunSpecs.size}: ${abstractFunSpecs.map { it.name }}"
        }
      }

      when (typeSpecs.count { it.isCompanion }) {
        0 -> Unit
        1 -> {
          require(isSimpleClass || kind == Kind.INTERFACE || isEnum || isAnnotation) {
            "$kind types can't have a companion object"
          }
        }
        else -> {
          throw IllegalArgumentException("Multiple companion objects are present but only one is allowed.")
        }
      }

      return TypeSpec(this)
    }
  }

  public companion object {
    @JvmStatic public fun classBuilder(name: String): Builder = Builder(Kind.CLASS, name)

    @JvmStatic public fun classBuilder(className: ClassName): Builder =
      classBuilder(className.simpleName)

    @JvmStatic public fun expectClassBuilder(name: String): Builder =
      Builder(Kind.CLASS, name, EXPECT)

    @JvmStatic public fun expectClassBuilder(className: ClassName): Builder =
      expectClassBuilder(className.simpleName)

    @JvmStatic public fun valueClassBuilder(name: String): Builder =
      Builder(Kind.CLASS, name, VALUE)

    @JvmStatic public fun objectBuilder(name: String): Builder = Builder(Kind.OBJECT, name)

    @JvmStatic public fun objectBuilder(className: ClassName): Builder =
      objectBuilder(className.simpleName)

    @JvmStatic @JvmOverloads
    public fun companionObjectBuilder(name: String? = null): Builder =
      Builder(Kind.OBJECT, name, COMPANION)

    @JvmStatic public fun interfaceBuilder(name: String): Builder = Builder(Kind.INTERFACE, name)

    @JvmStatic public fun interfaceBuilder(className: ClassName): Builder =
      interfaceBuilder(className.simpleName)

    @JvmStatic public fun funInterfaceBuilder(name: String): Builder =
      Builder(Kind.INTERFACE, name, FUN)

    @JvmStatic public fun funInterfaceBuilder(className: ClassName): Builder =
      funInterfaceBuilder(className.simpleName)

    @JvmStatic public fun enumBuilder(name: String): Builder = Builder(Kind.CLASS, name, ENUM)

    @JvmStatic public fun enumBuilder(className: ClassName): Builder =
      enumBuilder(className.simpleName)

    @JvmStatic public fun anonymousClassBuilder(): Builder = Builder(Kind.CLASS, null)

    @JvmStatic public fun annotationBuilder(name: String): Builder =
      Builder(Kind.CLASS, name, ANNOTATION)

    @JvmStatic public fun annotationBuilder(className: ClassName): Builder =
      annotationBuilder(className.simpleName)
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://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.
 */
@file:JvmName("TypeVariableNames")

package com.squareup.kotlinpoet

import java.lang.reflect.Type
import java.util.Collections
import javax.lang.model.element.TypeParameterElement
import javax.lang.model.type.TypeMirror
import javax.lang.model.type.TypeVariable
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
import kotlin.reflect.KVariance

public class TypeVariableName private constructor(
  public val name: String,
  public val bounds: List<TypeName>,

  /** Either [KModifier.IN], [KModifier.OUT], or null. */
  public val variance: KModifier? = null,
  public val isReified: Boolean = false,
  nullable: Boolean = false,
  annotations: List<AnnotationSpec> = emptyList(),
  tags: Map<KClass<*>, Any> = emptyMap(),
) : TypeName(nullable, annotations, TagMap(tags)) {

  override fun copy(
    nullable: Boolean,
    annotations: List<AnnotationSpec>,
    tags: Map<KClass<*>, Any>,
  ): TypeVariableName {
    return copy(nullable, annotations, this.bounds, this.isReified, tags)
  }

  public fun copy(
    nullable: Boolean = this.isNullable,
    annotations: List<AnnotationSpec> = this.annotations.toList(),
    bounds: List<TypeName> = this.bounds.toList(),
    reified: Boolean = this.isReified,
    tags: Map<KClass<*>, Any> = this.tagMap.tags,
  ): TypeVariableName {
    return TypeVariableName(
      name,
      bounds.withoutImplicitBound(),
      variance,
      reified,
      nullable,
      annotations,
      tags,
    )
  }

  private fun List<TypeName>.withoutImplicitBound(): List<TypeName> {
    return if (size == 1) this else filterNot { it == NULLABLE_ANY }
  }

  override fun emit(out: CodeWriter) = out.emit(name)

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

    other as TypeVariableName

    if (name != other.name) return false
    if (bounds != other.bounds) return false
    if (variance != other.variance) return false
    if (isReified != other.isReified) return false

    return true
  }

  override fun hashCode(): Int {
    var result = super.hashCode()
    result = 31 * result + name.hashCode()
    result = 31 * result + bounds.hashCode()
    result = 31 * result + (variance?.hashCode() ?: 0)
    result = 31 * result + isReified.hashCode()
    return result
  }

  public companion object {
    internal fun of(
      name: String,
      bounds: List<TypeName>,
      variance: KModifier?,
    ): TypeVariableName {
      require(variance == null || variance.isOneOf(KModifier.IN, KModifier.OUT)) {
        "$variance is an invalid variance modifier, the only allowed values are in and out!"
      }
      require(bounds.isNotEmpty()) {
        "$name has no bounds"
      }
      // Strip Any? from bounds if it is present.
      return TypeVariableName(name, bounds, variance)
    }

    /** Returns type variable named `name` with `variance` and without bounds. */
    @JvmStatic
    @JvmName("get")
    @JvmOverloads
    public operator fun invoke(name: String, variance: KModifier? = null): TypeVariableName =
      of(name = name, bounds = NULLABLE_ANY_LIST, variance = variance)

    /** Returns type variable named `name` with `variance` and `bounds`. */
    @JvmStatic
    @JvmName("get")
    @JvmOverloads
    public operator fun invoke(
      name: String,
      vararg bounds: TypeName,
      variance: KModifier? = null,
    ): TypeVariableName =
      of(
        name = name,
        bounds = bounds.toList().ifEmpty(::NULLABLE_ANY_LIST),
        variance = variance,
      )

    /** Returns type variable named `name` with `variance` and `bounds`. */
    @JvmStatic
    @JvmName("get")
    @JvmOverloads
    public operator fun invoke(
      name: String,
      vararg bounds: KClass<*>,
      variance: KModifier? = null,
    ): TypeVariableName =
      of(
        name = name,
        bounds = bounds.map(KClass<*>::asTypeName).ifEmpty(::NULLABLE_ANY_LIST),
        variance = variance,
      )

    /** Returns type variable named `name` with `variance` and `bounds`. */
    @JvmStatic
    @JvmName("get")
    @JvmOverloads
    public operator fun invoke(
      name: String,
      vararg bounds: Type,
      variance: KModifier? = null,
    ): TypeVariableName =
      of(
        name = name,
        bounds = bounds.map(Type::asTypeName).ifEmpty(::NULLABLE_ANY_LIST),
        variance = variance,
      )

    /** Returns type variable named `name` with `variance` and `bounds`. */
    @JvmStatic
    @JvmName("get")
    @JvmOverloads
    public operator fun invoke(
      name: String,
      bounds: List<TypeName>,
      variance: KModifier? = null,
    ): TypeVariableName = of(name, bounds.ifEmpty(::NULLABLE_ANY_LIST), variance)

    /** Returns type variable named `name` with `variance` and `bounds`. */
    @JvmStatic
    @JvmName("getWithClasses")
    @JvmOverloads
    public operator fun invoke(
      name: String,
      bounds: Iterable<KClass<*>>,
      variance: KModifier? = null,
    ): TypeVariableName =
      of(
        name,
        bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST),
        variance,
      )

    /** Returns type variable named `name` with `variance` and `bounds`. */
    @JvmStatic
    @JvmName("getWithTypes")
    @JvmOverloads
    public operator fun invoke(
      name: String,
      bounds: Iterable<Type>,
      variance: KModifier? = null,
    ): TypeVariableName =
      of(
        name,
        bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST),
        variance,
      )

    /**
     * Make a TypeVariableName for the given TypeMirror. This form is used internally to avoid
     * infinite recursion in cases like `Enum<E extends Enum<E>>`. When we encounter such a
     * thing, we will make a TypeVariableName without bounds and add that to the `typeVariables`
     * map before looking up the bounds. Then if we encounter this TypeVariable again while
     * constructing the bounds, we can just return it from the map. And, the code that put the entry
     * in `variables` will make sure that the bounds are filled in before returning.
     */
    internal fun get(
      mirror: javax.lang.model.type.TypeVariable,
      typeVariables: MutableMap<TypeParameterElement, TypeVariableName>,
    ): TypeVariableName {
      val element = mirror.asElement() as TypeParameterElement
      var typeVariableName: TypeVariableName? = typeVariables[element]
      if (typeVariableName == null) {
        // Since the bounds field is public, we need to make it an unmodifiableList. But we control
        // the List that that wraps, which means we can change it before returning.
        val bounds = mutableListOf<TypeName>()
        val visibleBounds = Collections.unmodifiableList(bounds)
        typeVariableName = TypeVariableName(element.simpleName.toString(), visibleBounds)
        typeVariables[element] = typeVariableName
        for (typeMirror in element.bounds) {
          bounds += get(typeMirror, typeVariables)
        }
        bounds.remove(ANY)
        bounds.remove(JAVA_OBJECT)
        if (bounds.isEmpty()) {
          bounds.add(NULLABLE_ANY)
        }
      }
      return typeVariableName
    }

    /** Returns type variable equivalent to `type`.  */
    internal fun get(
      type: java.lang.reflect.TypeVariable<*>,
      map: MutableMap<Type, TypeVariableName> = mutableMapOf(),
    ): TypeVariableName {
      var result: TypeVariableName? = map[type]
      if (result == null) {
        val bounds = mutableListOf<TypeName>()
        val visibleBounds = Collections.unmodifiableList(bounds)
        result = TypeVariableName(type.name, visibleBounds)
        map[type] = result
        for (bound in type.bounds) {
          bounds += get(bound, map)
        }
        bounds.remove(ANY)
        bounds.remove(JAVA_OBJECT)
        if (bounds.isEmpty()) {
          bounds.add(NULLABLE_ANY)
        }
      }
      return result
    }

    internal val NULLABLE_ANY_LIST = listOf(NULLABLE_ANY)
    private val JAVA_OBJECT = ClassName("java.lang", "Object")
  }
}

/** Returns type variable equivalent to `mirror`. */
@DelicateKotlinPoetApi(
  message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
    " the kotlinpoet-metadata APIs instead.",
)
@JvmName("get")
public fun TypeVariable.asTypeVariableName(): TypeVariableName =
  (asElement() as TypeParameterElement).asTypeVariableName()

/** Returns type variable equivalent to `element`. */
@DelicateKotlinPoetApi(
  message = "Element APIs don't give complete information on Kotlin types. Consider using" +
    " the kotlinpoet-metadata APIs instead.",
)
@JvmName("get")
public fun TypeParameterElement.asTypeVariableName(): TypeVariableName {
  val name = simpleName.toString()
  val boundsTypeNames = bounds.map(TypeMirror::asTypeName)
    .ifEmpty(TypeVariableName.Companion::NULLABLE_ANY_LIST)
  return TypeVariableName.of(name, boundsTypeNames, variance = null)
}

public fun KTypeParameter.asTypeVariableName(): TypeVariableName {
  return TypeVariableName.of(
    name = name,
    bounds = upperBounds.map(KType::asTypeName)
      .ifEmpty(TypeVariableName.Companion::NULLABLE_ANY_LIST),
    variance = when (variance) {
      KVariance.INVARIANT -> null
      KVariance.IN -> KModifier.IN
      KVariance.OUT -> KModifier.OUT
    },
  )
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet

import com.squareup.kotlinpoet.CodeBlock.Companion.isPlaceholder
import java.util.Collections

internal object NullAppendable : Appendable {
  override fun append(charSequence: CharSequence) = this
  override fun append(charSequence: CharSequence, start: Int, end: Int) = this
  override fun append(c: Char) = this
}

internal fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V> =
  Collections.unmodifiableMap(LinkedHashMap(this))

internal fun <T> Collection<T>.toImmutableList(): List<T> =
  Collections.unmodifiableList(ArrayList(this))

internal fun <T> Collection<T>.toImmutableSet(): Set<T> =
  Collections.unmodifiableSet(LinkedHashSet(this))

internal inline fun <reified T : Enum<T>> Collection<T>.toEnumSet(): Set<T> =
  enumValues<T>().filterTo(mutableSetOf(), this::contains)

internal fun requireNoneOrOneOf(modifiers: Set<KModifier>, vararg mutuallyExclusive: KModifier) {
  val count = mutuallyExclusive.count(modifiers::contains)
  require(count <= 1) {
    "modifiers $modifiers must contain none or only one of ${mutuallyExclusive.contentToString()}"
  }
}

internal fun requireNoneOf(modifiers: Set<KModifier>, vararg forbidden: KModifier) {
  require(forbidden.none(modifiers::contains)) {
    "modifiers $modifiers must contain none of ${forbidden.contentToString()}"
  }
}

internal fun <T> T.isOneOf(t1: T, t2: T, t3: T? = null, t4: T? = null, t5: T? = null, t6: T? = null) =
  this == t1 || this == t2 || this == t3 || this == t4 || this == t5 || this == t6

internal fun <T> Collection<T>.containsAnyOf(vararg t: T) = t.any(this::contains)

// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
internal fun characterLiteralWithoutSingleQuotes(c: Char) = when {
  c == '\b' -> "\\b" // \u0008: backspace (BS)
  c == '\t' -> "\\t" // \u0009: horizontal tab (HT)
  c == '\n' -> "\\n" // \u000a: linefeed (LF)
  c == '\r' -> "\\r" // \u000d: carriage return (CR)
  c == '\"' -> "\"" // \u0022: double quote (")
  c == '\'' -> "\\'" // \u0027: single quote (')
  c == '\\' -> "\\\\" // \u005c: backslash (\)
  c.isIsoControl -> String.format("\\u%04x", c.code)
  else -> c.toString()
}

internal fun escapeCharacterLiterals(s: String) = buildString {
  for (c in s) append(characterLiteralWithoutSingleQuotes(c))
}

private val Char.isIsoControl: Boolean
  get() {
    return this in '\u0000'..'\u001F' || this in '\u007F'..'\u009F'
  }

/** Returns the string literal representing `value`, including wrapping double quotes.  */
internal fun stringLiteralWithQuotes(
  value: String,
  isInsideRawString: Boolean = false,
  isConstantContext: Boolean = false,
): String {
  if (!isConstantContext && '\n' in value) {
    val result = StringBuilder(value.length + 32)
    result.append("\"\"\"\n|")
    var i = 0
    while (i < value.length) {
      val c = value[i]
      if (value.regionMatches(i, "\"\"\"", 0, 3)) {
        // Don't inadvertently end the raw string too early
        result.append("\"\"\${'\"'}")
        i += 2
      } else if (c == '\n') {
        // Add a '|' after newlines. This pipe will be removed by trimMargin().
        result.append("\n|")
      } else if (c == '$' && !isInsideRawString) {
        // Escape '$' symbols with ${'$'}.
        result.append("\${\'\$\'}")
      } else {
        result.append(c)
      }
      i++
    }
    // If the last-emitted character wasn't a margin '|', add a blank line. This will get removed
    // by trimMargin().
    if (!value.endsWith("\n")) result.append("\n")
    result.append("\"\"\".trimMargin()")
    return result.toString()
  } else {
    val result = StringBuilder(value.length + 32)
    // using pre-formatted strings allows us to get away with not escaping symbols that would
    // normally require escaping, e.g. "foo ${"bar"} baz"
    if (isInsideRawString) result.append("\"\"\"") else result.append('"')
    for (c in value) {
      // Trivial case: single quote must not be escaped.
      if (c == '\'') {
        result.append("'")
        continue
      }
      // Trivial case: double quotes must be escaped.
      if (c == '\"' && !isInsideRawString) {
        result.append("\\\"")
        continue
      }
      // Trivial case: $ signs must be escaped.
      if (c == '$' && !isInsideRawString) {
        result.append("\${\'\$\'}")
        continue
      }
      // Default case: just let character literal do its work.
      result.append(if (isInsideRawString) c else characterLiteralWithoutSingleQuotes(c))
      // Need to append indent after linefeed?
    }
    if (isInsideRawString) result.append("\"\"\"") else result.append('"')
    return result.toString()
  }
}

internal fun CodeBlock.ensureEndsWithNewLine() = if (isEmpty()) {
  this
} else {
  with(toBuilder()) {
    val lastFormatPart = trim().formatParts.last()
    if (lastFormatPart.isPlaceholder && args.isNotEmpty()) {
      val lastArg = args.last()
      if (lastArg is String) {
        args[args.size - 1] = lastArg.trimEnd('\n') + '\n'
      }
    } else {
      formatParts[formatParts.lastIndexOf(lastFormatPart)] = lastFormatPart.trimEnd('\n')
      formatParts += "\n"
    }
    return@with build()
  }
}

private val IDENTIFIER_REGEX =
  (
    "((\\p{gc=Lu}+|\\p{gc=Ll}+|\\p{gc=Lt}+|\\p{gc=Lm}+|\\p{gc=Lo}+|\\p{gc=Nl}+)+" +
      "\\d*" +
      "\\p{gc=Lu}*\\p{gc=Ll}*\\p{gc=Lt}*\\p{gc=Lm}*\\p{gc=Lo}*\\p{gc=Nl}*)" +
      "|" +
      "(`[^\n\r`]+`)"
    )
    .toRegex()

internal val String.isIdentifier get() = IDENTIFIER_REGEX.matches(this)

// https://kotlinlang.org/docs/reference/keyword-reference.html
private val KEYWORDS = setOf(
  // Hard keywords
  "as",
  "break",
  "class",
  "continue",
  "do",
  "else",
  "false",
  "for",
  "fun",
  "if",
  "in",
  "interface",
  "is",
  "null",
  "object",
  "package",
  "return",
  "super",
  "this",
  "throw",
  "true",
  "try",
  "typealias",
  "typeof",
  "val",
  "var",
  "when",
  "while",

  // Soft keywords
  "by",
  "catch",
  "constructor",
  "delegate",
  "dynamic",
  "field",
  "file",
  "finally",
  "get",
  "import",
  "init",
  "param",
  "property",
  "receiver",
  "set",
  "setparam",
  "where",

  // Modifier keywords
  "actual",
  "abstract",
  "annotation",
  "companion",
  "const",
  "crossinline",
  "data",
  "enum",
  "expect",
  "external",
  "final",
  "infix",
  "inline",
  "inner",
  "internal",
  "lateinit",
  "noinline",
  "open",
  "operator",
  "out",
  "override",
  "private",
  "protected",
  "public",
  "reified",
  "sealed",
  "suspend",
  "tailrec",
  "value",
  "vararg",

  // These aren't keywords anymore but still break some code if unescaped. https://youtrack.jetbrains.com/issue/KT-52315
  "header",
  "impl",

  // Other reserved keywords
  "yield",
)

private const val ALLOWED_CHARACTER = '$'

private const val UNDERSCORE_CHARACTER = '_'

internal val String.isKeyword get() = this in KEYWORDS

internal val String.hasAllowedCharacters get() = this.any { it == ALLOWED_CHARACTER }

internal val String.allCharactersAreUnderscore get() = this.all { it == UNDERSCORE_CHARACTER }

// https://github.com/JetBrains/kotlin/blob/master/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmSimpleNameBacktickChecker.kt
private val ILLEGAL_CHARACTERS_TO_ESCAPE = setOf('.', ';', '[', ']', '/', '<', '>', ':', '\\')

private fun String.failIfEscapeInvalid() {
  require(!any { it in ILLEGAL_CHARACTERS_TO_ESCAPE }) {
    "Can't escape identifier $this because it contains illegal characters: " +
      ILLEGAL_CHARACTERS_TO_ESCAPE.intersect(this.toSet()).joinToString("")
  }
}

internal fun String.escapeIfNecessary(validate: Boolean = true): String = escapeIfNotJavaIdentifier()
  .escapeIfKeyword()
  .escapeIfHasAllowedCharacters()
  .escapeIfAllCharactersAreUnderscore()
  .apply { if (validate) failIfEscapeInvalid() }

private fun String.alreadyEscaped() = startsWith("`") && endsWith("`")

private fun String.escapeIfKeyword() = if (isKeyword && !alreadyEscaped()) "`$this`" else this

private fun String.escapeIfHasAllowedCharacters() = if (hasAllowedCharacters && !alreadyEscaped()) "`$this`" else this

private fun String.escapeIfAllCharactersAreUnderscore() = if (allCharactersAreUnderscore && !alreadyEscaped()) "`$this`" else this

private fun String.escapeIfNotJavaIdentifier(): String {
  return if ((
      !Character.isJavaIdentifierStart(first()) ||
        drop(1).any { !Character.isJavaIdentifierPart(it) }
      ) &&
    !alreadyEscaped()
  ) {
    "`$this`".replace(' ', '·')
  } else {
    this
  }
}

internal fun String.escapeSegmentsIfNecessary(delimiter: Char = '.') = split(delimiter)
  .filter { it.isNotEmpty() }
  .joinToString(delimiter.toString()) { it.escapeIfNecessary() }

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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
 *
 * https://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.
 */
@file:JvmName("WildcardTypeNames")

package com.squareup.kotlinpoet

import java.lang.reflect.Type
import java.lang.reflect.WildcardType
import javax.lang.model.element.TypeParameterElement
import kotlin.reflect.KClass

public class WildcardTypeName private constructor(
  outTypes: List<TypeName>,
  inTypes: List<TypeName>,
  nullable: Boolean = false,
  annotations: List<AnnotationSpec> = emptyList(),
  tags: Map<KClass<*>, Any> = emptyMap(),
) : TypeName(nullable, annotations, TagMap(tags)) {
  public val outTypes: List<TypeName> = outTypes.toImmutableList()
  public val inTypes: List<TypeName> = inTypes.toImmutableList()

  init {
    require(this.outTypes.size == 1) { "unexpected out types: $outTypes" }
  }

  override fun copy(
    nullable: Boolean,
    annotations: List<AnnotationSpec>,
    tags: Map<KClass<*>, Any>,
  ): WildcardTypeName {
    return WildcardTypeName(outTypes, inTypes, nullable, annotations, tags)
  }

  override fun emit(out: CodeWriter): CodeWriter {
    return when {
      inTypes.size == 1 -> out.emitCode("in·%T", inTypes[0])
      outTypes == STAR.outTypes -> out.emit("*")
      else -> out.emitCode("out·%T", outTypes[0])
    }
  }

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

    other as WildcardTypeName

    if (outTypes != other.outTypes) return false
    if (inTypes != other.inTypes) return false

    return true
  }

  override fun hashCode(): Int {
    var result = super.hashCode()
    result = 31 * result + outTypes.hashCode()
    result = 31 * result + inTypes.hashCode()
    return result
  }

  public companion object {
    /**
     * Returns a type that represents an unknown type that produces `outType`. For example, if
     * `outType` is `CharSequence`, this returns `out CharSequence`. If `outType` is `Any?`, this
     * returns `*`, which is shorthand for `out Any?`.
     */
    @JvmStatic public fun producerOf(outType: TypeName): WildcardTypeName =
      WildcardTypeName(listOf(outType), emptyList())

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    public fun producerOf(outType: Type): WildcardTypeName =
      producerOf(outType.asTypeName())

    @JvmStatic public fun producerOf(outType: KClass<*>): WildcardTypeName =
      producerOf(outType.asTypeName())

    /**
     * Returns a type that represents an unknown type that consumes `inType`. For example, if
     * `inType` is `String`, this returns `in String`.
     */
    @JvmStatic public fun consumerOf(inType: TypeName): WildcardTypeName =
      WildcardTypeName(listOf(ANY), listOf(inType))

    @DelicateKotlinPoetApi(
      message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
        "using the kotlinpoet-metadata APIs instead.",
    )
    @JvmStatic
    public fun consumerOf(inType: Type): WildcardTypeName =
      consumerOf(inType.asTypeName())

    @JvmStatic public fun consumerOf(inType: KClass<*>): WildcardTypeName =
      consumerOf(inType.asTypeName())

    internal fun get(
      mirror: javax.lang.model.type.WildcardType,
      typeVariables: Map<TypeParameterElement, TypeVariableName>,
    ): TypeName {
      val outType = mirror.extendsBound
      return if (outType == null) {
        val inType = mirror.superBound
        if (inType == null) {
          STAR
        } else {
          consumerOf(get(inType, typeVariables))
        }
      } else {
        producerOf(get(outType, typeVariables))
      }
    }

    internal fun get(
      wildcardName: WildcardType,
      map: MutableMap<Type, TypeVariableName>,
    ): TypeName {
      return WildcardTypeName(
        wildcardName.upperBounds.map { get(it, map = map) },
        wildcardName.lowerBounds.map { get(it, map = map) },
      )
    }
  }
}

@DelicateKotlinPoetApi(
  message = "Mirror APIs don't give complete information on Kotlin types. Consider using" +
    " the kotlinpoet-metadata APIs instead.",
)
@JvmName("get")
public fun javax.lang.model.type.WildcardType.asWildcardTypeName(): TypeName =
  WildcardTypeName.get(this, mutableMapOf())

@DelicateKotlinPoetApi(
  message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
    " the kotlinpoet-metadata APIs instead.",
)
@JvmName("get")
public fun WildcardType.asWildcardTypeName(): TypeName =
  WildcardTypeName.get(this, mutableMapOf())

/*
 * Copyright (C) 2018 Square, Inc.
 *
 * 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
 *
 * https://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.
 */
@file:JvmName("JvmAnnotations")

package com.squareup.kotlinpoet.jvm

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.FunSpec.Companion.isAccessor
import com.squareup.kotlinpoet.FunSpec.Companion.isConstructor
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName
import java.lang.reflect.Type
import kotlin.reflect.KClass

public fun FileSpec.Builder.jvmName(name: String): FileSpec.Builder = addAnnotation(
  AnnotationSpec.builder(JvmName::class)
    .useSiteTarget(FILE)
    .addMember("%S", name)
    .build(),
)

public fun FileSpec.Builder.jvmMultifileClass(): FileSpec.Builder = addAnnotation(
  AnnotationSpec.builder(JvmMultifileClass::class)
    .useSiteTarget(FILE)
    .build(),
)

public fun TypeSpec.Builder.jvmSuppressWildcards(suppress: Boolean = true): TypeSpec.Builder =
  addAnnotation(jvmSuppressWildcardsAnnotation(suppress))

private fun jvmSuppressWildcardsAnnotation(suppress: Boolean = true) =
  AnnotationSpec.builder(JvmSuppressWildcards::class)
    .apply { if (!suppress) addMember("suppress = false") }
    .build()

public fun TypeSpec.Builder.jvmInline(): TypeSpec.Builder = addAnnotation(JvmInline::class)

public fun TypeSpec.Builder.jvmRecord(): TypeSpec.Builder = addAnnotation(JvmRecord::class)

public fun FunSpec.Builder.jvmStatic(): FunSpec.Builder = apply {
  check(!name.isConstructor) { "Can't apply @JvmStatic to a constructor!" }
  addAnnotation(JvmStatic::class)
}

public fun FunSpec.Builder.jvmOverloads(): FunSpec.Builder = apply {
  check(!name.isAccessor) {
    "Can't apply @JvmOverloads to a " + if (name == FunSpec.GETTER) "getter!" else "setter!"
  }
  addAnnotation(JvmOverloads::class)
}

public fun FunSpec.Builder.jvmName(name: String): FunSpec.Builder = apply {
  check(!this.name.isConstructor) { "Can't apply @JvmName to a constructor!" }
  addAnnotation(
    AnnotationSpec.builder(JvmName::class)
      .addMember("%S", name)
      .build(),
  )
}

public fun FunSpec.Builder.throws(vararg exceptionClasses: KClass<out Throwable>): FunSpec.Builder =
  throws(exceptionClasses.map(KClass<*>::asTypeName))

public fun FunSpec.Builder.throws(vararg exceptionClasses: Type): FunSpec.Builder =
  throws(exceptionClasses.map(Type::asTypeName))

public fun FunSpec.Builder.throws(vararg exceptionClasses: TypeName): FunSpec.Builder =
  throws(exceptionClasses.toList())

public fun FunSpec.Builder.throws(exceptionClasses: Iterable<TypeName>): FunSpec.Builder =
  addAnnotation(
    AnnotationSpec.builder(Throws::class)
      .apply { exceptionClasses.forEach { addMember("%T::class", it) } }
      .build(),
  )

public fun FunSpec.Builder.jvmSuppressWildcards(suppress: Boolean = true): FunSpec.Builder = apply {
  check(!name.isConstructor) { "Can't apply @JvmSuppressWildcards to a constructor!" }
  check(!name.isAccessor) {
    "Can't apply @JvmSuppressWildcards to a " + if (name == FunSpec.GETTER) "getter!" else "setter!"
  }
  addAnnotation(jvmSuppressWildcardsAnnotation(suppress))
}

public fun FunSpec.Builder.synchronized(): FunSpec.Builder = apply {
  check(!name.isConstructor) { "Can't apply @Synchronized to a constructor!" }
  addAnnotation(Synchronized::class)
}

public fun FunSpec.Builder.strictfp(): FunSpec.Builder = addAnnotation(Strictfp::class)

public fun PropertySpec.Builder.jvmField(): PropertySpec.Builder = addAnnotation(JvmField::class)

public fun PropertySpec.Builder.jvmStatic(): PropertySpec.Builder = addAnnotation(JvmStatic::class)

public fun PropertySpec.Builder.jvmSuppressWildcards(
  suppress: Boolean = true,
): PropertySpec.Builder = addAnnotation(jvmSuppressWildcardsAnnotation(suppress))

public fun PropertySpec.Builder.transient(): PropertySpec.Builder = addAnnotation(Transient::class)

public fun PropertySpec.Builder.volatile(): PropertySpec.Builder = addAnnotation(Volatile::class)

public fun TypeName.jvmSuppressWildcards(suppress: Boolean = true): TypeName =
  copy(annotations = this.annotations + jvmSuppressWildcardsAnnotation(suppress))

public fun TypeName.jvmWildcard(): TypeName =
  copy(annotations = this.annotations + AnnotationSpec.builder(JvmWildcard::class).build())

@Suppress("DEPRECATION")
@Deprecated("'JvmDefault' is deprecated. Switch to new -Xjvm-default modes: `all` or `all-compatibility`")
public fun PropertySpec.Builder.jvmDefault(): PropertySpec.Builder =
  addAnnotation(JvmDefault::class)

@Suppress("DEPRECATION")
@Deprecated("'JvmDefault' is deprecated. Switch to new -Xjvm-default modes: `all` or `all-compatibility`")
public fun FunSpec.Builder.jvmDefault(): FunSpec.Builder = addAnnotation(JvmDefault::class)

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.tags

import com.squareup.kotlinpoet.TypeName

/**
 * This tag indicates that this [TypeName] represents a `typealias` type.
 *
 * @property [abbreviatedType] the underlying type for this alias.
 */
public class TypeAliasTag(public val abbreviatedType: TypeName)

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.javapoet

import com.squareup.javapoet.ArrayTypeName
import com.squareup.javapoet.TypeName
import com.squareup.kotlinpoet.ANY
import com.squareup.kotlinpoet.ARRAY
import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.BOOLEAN_ARRAY
import com.squareup.kotlinpoet.BYTE
import com.squareup.kotlinpoet.BYTE_ARRAY
import com.squareup.kotlinpoet.CHAR
import com.squareup.kotlinpoet.CHAR_ARRAY
import com.squareup.kotlinpoet.CHAR_SEQUENCE
import com.squareup.kotlinpoet.DOUBLE
import com.squareup.kotlinpoet.DOUBLE_ARRAY
import com.squareup.kotlinpoet.Dynamic
import com.squareup.kotlinpoet.ENUM
import com.squareup.kotlinpoet.FLOAT
import com.squareup.kotlinpoet.FLOAT_ARRAY
import com.squareup.kotlinpoet.INT
import com.squareup.kotlinpoet.INT_ARRAY
import com.squareup.kotlinpoet.LIST
import com.squareup.kotlinpoet.LONG
import com.squareup.kotlinpoet.LONG_ARRAY
import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.MAP
import com.squareup.kotlinpoet.MUTABLE_LIST
import com.squareup.kotlinpoet.MUTABLE_MAP
import com.squareup.kotlinpoet.MUTABLE_SET
import com.squareup.kotlinpoet.SET
import com.squareup.kotlinpoet.SHORT
import com.squareup.kotlinpoet.SHORT_ARRAY
import com.squareup.kotlinpoet.STAR
import com.squareup.kotlinpoet.STRING
import com.squareup.kotlinpoet.U_BYTE
import com.squareup.kotlinpoet.U_BYTE_ARRAY
import com.squareup.kotlinpoet.U_INT
import com.squareup.kotlinpoet.U_INT_ARRAY
import com.squareup.kotlinpoet.U_LONG
import com.squareup.kotlinpoet.U_LONG_ARRAY
import com.squareup.kotlinpoet.U_SHORT
import com.squareup.kotlinpoet.U_SHORT_ARRAY

@KotlinPoetJavaPoetPreview
public fun KClassName.toJClassName(boxIfPrimitive: Boolean = false): JTypeName {
  return when (copy(nullable = false)) {
    BOOLEAN -> JTypeName.BOOLEAN.boxIfPrimitive(boxIfPrimitive || isNullable)
    BYTE, U_BYTE -> JTypeName.BYTE.boxIfPrimitive(boxIfPrimitive || isNullable)
    CHAR -> JTypeName.CHAR.boxIfPrimitive(boxIfPrimitive || isNullable)
    SHORT, U_SHORT -> JTypeName.SHORT.boxIfPrimitive(boxIfPrimitive || isNullable)
    INT, U_INT -> JTypeName.INT.boxIfPrimitive(boxIfPrimitive || isNullable)
    LONG, U_LONG -> JTypeName.LONG.boxIfPrimitive(boxIfPrimitive || isNullable)
    FLOAT -> JTypeName.FLOAT.boxIfPrimitive(boxIfPrimitive || isNullable)
    DOUBLE -> JTypeName.DOUBLE.boxIfPrimitive(boxIfPrimitive || isNullable)
    ANY -> JTypeName.OBJECT
    CHAR_SEQUENCE -> PoetInterop.CN_JAVA_CHAR_SEQUENCE
    STRING -> PoetInterop.CN_JAVA_STRING
    LIST, MUTABLE_LIST -> PoetInterop.CN_JAVA_LIST
    SET, MUTABLE_SET -> PoetInterop.CN_JAVA_SET
    MAP, MUTABLE_MAP -> PoetInterop.CN_JAVA_MAP
    BOOLEAN_ARRAY -> ArrayTypeName.of(JTypeName.BOOLEAN)
    BYTE_ARRAY, U_BYTE_ARRAY -> ArrayTypeName.of(JTypeName.BYTE)
    CHAR_ARRAY -> ArrayTypeName.of(JTypeName.CHAR)
    SHORT_ARRAY, U_SHORT_ARRAY -> ArrayTypeName.of(JTypeName.SHORT)
    INT_ARRAY, U_INT_ARRAY -> ArrayTypeName.of(JTypeName.INT)
    LONG_ARRAY, U_LONG_ARRAY -> ArrayTypeName.of(JTypeName.LONG)
    FLOAT_ARRAY -> ArrayTypeName.of(JTypeName.FLOAT)
    DOUBLE_ARRAY -> ArrayTypeName.of(JTypeName.DOUBLE)
    ENUM -> PoetInterop.CN_JAVA_ENUM
    else -> {
      if (simpleNames.size == 1) {
        JClassName.get(packageName, simpleName)
      } else {
        JClassName.get(packageName, simpleNames.first(), *simpleNames.drop(1).toTypedArray())
      }
    }
  }
}

@KotlinPoetJavaPoetPreview
public fun KParameterizedTypeName.toJParameterizedOrArrayTypeName(): JTypeName {
  return when (rawType) {
    ARRAY -> {
      val componentType = typeArguments.firstOrNull()?.toJTypeName()
        ?: throw IllegalStateException("Array with no type! $this")
      ArrayTypeName.of(componentType)
    }
    else -> {
      JParameterizedTypeName.get(
        rawType.toJClassName() as JClassName,
        *typeArguments.map { it.toJTypeName(boxIfPrimitive = true) }.toTypedArray(),
      )
    }
  }
}

@KotlinPoetJavaPoetPreview
public fun KParameterizedTypeName.toJParameterizedTypeName(): JParameterizedTypeName {
  check(rawType != ARRAY) {
    "Array type! JavaPoet arrays are a custom TypeName. Use this function only for things you know are not arrays"
  }
  return toJParameterizedOrArrayTypeName() as JParameterizedTypeName
}

@KotlinPoetJavaPoetPreview
public fun KTypeVariableName.toJTypeVariableName(): JTypeVariableName {
  return JTypeVariableName.get(name, *bounds.map { it.toJTypeName(boxIfPrimitive = true) }.toTypedArray())
}

@KotlinPoetJavaPoetPreview
public fun KWildcardTypeName.toJWildcardTypeName(): JWildcardTypeName {
  return if (this == STAR) {
    JWildcardTypeName.subtypeOf(TypeName.OBJECT)
  } else if (inTypes.size == 1) {
    JWildcardTypeName.supertypeOf(inTypes[0].toJTypeName())
  } else {
    JWildcardTypeName.subtypeOf(outTypes[0].toJTypeName())
  }
}

@KotlinPoetJavaPoetPreview
public fun KTypeName.toJTypeName(boxIfPrimitive: Boolean = false): JTypeName {
  return when (this) {
    is KClassName -> toJClassName(boxIfPrimitive)
    Dynamic -> throw IllegalStateException("Not applicable in Java!")
    // TODO should we return a ParameterizedTypeName of the KFunction?
    is LambdaTypeName -> throw IllegalStateException("Not applicable in Java!")
    is KParameterizedTypeName -> toJParameterizedOrArrayTypeName()
    is KTypeVariableName -> toJTypeVariableName()
    is KWildcardTypeName -> toJWildcardTypeName()
  }
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.javapoet

import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY
import kotlin.annotation.AnnotationTarget.TYPEALIAS

/**
 * Indicates that a given API is part of the experimental KotlinPoet JavaPoet support and is
 * subject to API changes.
 */
@RequiresOptIn
@Retention(AnnotationRetention.BINARY)
@Target(CLASS, FUNCTION, PROPERTY, TYPEALIAS)
public annotation class KotlinPoetJavaPoetPreview

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.javapoet

/** Various JavaPoet and KotlinPoet representations of some common types. */
@OptIn(KotlinPoetJavaPoetPreview::class)
internal object PoetInterop {
  internal val CN_JAVA_CHAR_SEQUENCE = JClassName.get("java.lang", "CharSequence")
  internal val CN_JAVA_STRING = JClassName.get("java.lang", "String")
  internal val CN_JAVA_LIST = JClassName.get("java.util", "List")
  internal val CN_JAVA_SET = JClassName.get("java.util", "Set")
  internal val CN_JAVA_MAP = JClassName.get("java.util", "Map")
  internal val CN_JAVA_ENUM = JClassName.get("java.lang", "Enum")
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.javapoet

import com.squareup.javapoet.ArrayTypeName
import com.squareup.javapoet.TypeName
import com.squareup.kotlinpoet.ANY
import com.squareup.kotlinpoet.ARRAY
import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.BYTE
import com.squareup.kotlinpoet.BYTE_ARRAY
import com.squareup.kotlinpoet.CHAR
import com.squareup.kotlinpoet.CHAR_ARRAY
import com.squareup.kotlinpoet.CHAR_SEQUENCE
import com.squareup.kotlinpoet.DOUBLE
import com.squareup.kotlinpoet.DOUBLE_ARRAY
import com.squareup.kotlinpoet.ENUM
import com.squareup.kotlinpoet.FLOAT
import com.squareup.kotlinpoet.FLOAT_ARRAY
import com.squareup.kotlinpoet.INT
import com.squareup.kotlinpoet.INT_ARRAY
import com.squareup.kotlinpoet.LIST
import com.squareup.kotlinpoet.LONG
import com.squareup.kotlinpoet.LONG_ARRAY
import com.squareup.kotlinpoet.MAP
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.SET
import com.squareup.kotlinpoet.SHORT
import com.squareup.kotlinpoet.SHORT_ARRAY
import com.squareup.kotlinpoet.STAR
import com.squareup.kotlinpoet.STRING

@KotlinPoetJavaPoetPreview
public fun JClassName.toKClassName(): KClassName {
  return when (this) {
    JTypeName.BOOLEAN.box() -> BOOLEAN
    JTypeName.BYTE.box() -> BYTE
    JTypeName.CHAR.box() -> CHAR
    JTypeName.SHORT.box() -> SHORT
    JTypeName.INT.box() -> INT
    JTypeName.LONG.box() -> LONG
    JTypeName.FLOAT.box() -> FLOAT
    JTypeName.DOUBLE.box() -> DOUBLE
    JTypeName.OBJECT -> ANY
    PoetInterop.CN_JAVA_CHAR_SEQUENCE -> CHAR_SEQUENCE
    PoetInterop.CN_JAVA_STRING -> STRING
    PoetInterop.CN_JAVA_LIST -> LIST
    PoetInterop.CN_JAVA_SET -> SET
    PoetInterop.CN_JAVA_MAP -> MAP
    PoetInterop.CN_JAVA_ENUM -> ENUM
    else -> {
      if (simpleNames().size == 1) {
        KClassName(packageName(), simpleName())
      } else {
        KClassName(packageName(), simpleNames().first(), *simpleNames().drop(1).toTypedArray())
      }
    }
  }
}

@KotlinPoetJavaPoetPreview
public fun JParameterizedTypeName.toKParameterizedTypeName(): KParameterizedTypeName {
  return rawType.toKClassName()
    .parameterizedBy(*typeArguments.map { it.toKTypeName() }.toTypedArray())
}

@KotlinPoetJavaPoetPreview
public fun JTypeVariableName.toKTypeVariableName(): KTypeVariableName {
  return if (bounds.isEmpty()) {
    KTypeVariableName(name)
  } else {
    KTypeVariableName(name, *bounds.map { it.toKTypeName() }.toTypedArray())
  }
}

@KotlinPoetJavaPoetPreview
public fun JWildcardTypeName.toKWildcardTypeName(): KWildcardTypeName {
  return if (lowerBounds.size == 1) {
    KWildcardTypeName.consumerOf(lowerBounds.first().toKTypeName())
  } else {
    when (val upperBound = upperBounds[0]) {
      TypeName.OBJECT -> STAR
      else -> KWildcardTypeName.producerOf(upperBound.toKTypeName())
    }
  }
}

@KotlinPoetJavaPoetPreview
public fun JTypeName.toKTypeName(): KTypeName {
  return when (this) {
    is JClassName -> toKClassName()
    is JParameterizedTypeName -> toKParameterizedTypeName()
    is JTypeVariableName -> toKTypeVariableName()
    is JWildcardTypeName -> toKWildcardTypeName()
    is ArrayTypeName -> {
      when (componentType) {
        JTypeName.BYTE -> BYTE_ARRAY
        JTypeName.CHAR -> CHAR_ARRAY
        JTypeName.SHORT -> SHORT_ARRAY
        JTypeName.INT -> INT_ARRAY
        JTypeName.LONG -> LONG_ARRAY
        JTypeName.FLOAT -> FLOAT_ARRAY
        JTypeName.DOUBLE -> DOUBLE_ARRAY
        else -> ARRAY.parameterizedBy(componentType.toKTypeName())
      }
    }
    else -> when (unboxIfBoxedPrimitive()) {
      JTypeName.BOOLEAN -> BOOLEAN
      JTypeName.BYTE -> BYTE
      JTypeName.CHAR -> CHAR
      JTypeName.SHORT -> SHORT
      JTypeName.INT -> INT
      JTypeName.LONG -> LONG
      JTypeName.FLOAT -> FLOAT
      JTypeName.DOUBLE -> DOUBLE
      else -> error("Unrecognized type $this")
    }
  }
}

@OptIn(KotlinPoetJavaPoetPreview::class)
internal fun JTypeName.unboxIfBoxedPrimitive(): JTypeName {
  return if (isBoxedPrimitive) {
    unbox()
  } else {
    this
  }
}

@OptIn(KotlinPoetJavaPoetPreview::class)
internal fun JTypeName.boxIfPrimitive(extraCondition: Boolean = true): JTypeName {
  return if (extraCondition && isPrimitive && !isBoxedPrimitive) {
    box()
  } else {
    this
  }
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.javapoet

/*
 * Useful typealiases for colliding names
 */

@KotlinPoetJavaPoetPreview
public typealias KTypeName = com.squareup.kotlinpoet.TypeName

@KotlinPoetJavaPoetPreview
public typealias KClassName = com.squareup.kotlinpoet.ClassName

@KotlinPoetJavaPoetPreview
public typealias KTypeVariableName = com.squareup.kotlinpoet.TypeVariableName

@KotlinPoetJavaPoetPreview
public typealias KParameterizedTypeName = com.squareup.kotlinpoet.ParameterizedTypeName

@KotlinPoetJavaPoetPreview
public typealias KWildcardTypeName = com.squareup.kotlinpoet.WildcardTypeName

@KotlinPoetJavaPoetPreview
public typealias KTypeSpec = com.squareup.kotlinpoet.TypeSpec

@KotlinPoetJavaPoetPreview
public typealias KAnnotationSpec = com.squareup.kotlinpoet.AnnotationSpec

@KotlinPoetJavaPoetPreview
public typealias JTypeName = com.squareup.javapoet.TypeName

@KotlinPoetJavaPoetPreview
public typealias JClassName = com.squareup.javapoet.ClassName

@KotlinPoetJavaPoetPreview
public typealias JTypeVariableName = com.squareup.javapoet.TypeVariableName

@KotlinPoetJavaPoetPreview
public typealias JParameterizedTypeName = com.squareup.javapoet.ParameterizedTypeName

@KotlinPoetJavaPoetPreview
public typealias JWildcardTypeName = com.squareup.javapoet.WildcardTypeName

@KotlinPoetJavaPoetPreview
public typealias JTypeSpec = com.squareup.javapoet.TypeSpec

@KotlinPoetJavaPoetPreview
public typealias JAnnotationSpec = com.squareup.javapoet.AnnotationSpec

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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
 *
 * https://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.
 */
@file:Suppress("unused")

package com.squareup.kotlinpoet.metadata

import kotlinx.metadata.Flag
import kotlinx.metadata.Flags
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmConstructor
import kotlinx.metadata.KmFunction
import kotlinx.metadata.KmProperty
import kotlinx.metadata.KmType
import kotlinx.metadata.KmTypeParameter
import kotlinx.metadata.KmValueParameter

// Common flags for any element with flags.
@KotlinPoetMetadataPreview
public val Flags.hasAnnotations: Boolean get() = Flag.HAS_ANNOTATIONS(this)

@KotlinPoetMetadataPreview
public val Flags.isAbstract: Boolean get() = Flag.IS_ABSTRACT(this)

@KotlinPoetMetadataPreview
public val Flags.isFinal: Boolean get() = Flag.IS_FINAL(this)

@KotlinPoetMetadataPreview
public val Flags.isInternal: Boolean get() = Flag.IS_INTERNAL(this)

@KotlinPoetMetadataPreview
public val Flags.isLocal: Boolean get() = Flag.IS_LOCAL(this)

@KotlinPoetMetadataPreview
public val Flags.isOpen: Boolean get() = Flag.IS_OPEN(this)

@KotlinPoetMetadataPreview
public val Flags.isPrivate: Boolean get() = Flag.IS_PRIVATE(this)

@KotlinPoetMetadataPreview
public val Flags.isPrivate_to_this: Boolean get() = Flag.IS_PRIVATE_TO_THIS(this)

@KotlinPoetMetadataPreview
public val Flags.isProtected: Boolean get() = Flag.IS_PROTECTED(this)

@KotlinPoetMetadataPreview
public val Flags.isPublic: Boolean get() = Flag.IS_PUBLIC(this)

@KotlinPoetMetadataPreview
public val Flags.isSealed: Boolean get() = Flag.IS_SEALED(this)

// Type flags.
@KotlinPoetMetadataPreview
public val Flags.isNullableType: Boolean get() = Flag.Type.IS_NULLABLE(this)

@KotlinPoetMetadataPreview
public val Flags.isSuspendType: Boolean get() = Flag.Type.IS_SUSPEND(this)

// Class flags.
@KotlinPoetMetadataPreview
public val Flags.isAnnotationClass: Boolean get() = Flag.Class.IS_ANNOTATION_CLASS(this)

@KotlinPoetMetadataPreview
public val Flags.isClass: Boolean get() = Flag.Class.IS_CLASS(this)

@KotlinPoetMetadataPreview
public val Flags.isCompanionObjectClass: Boolean get() = Flag.Class.IS_COMPANION_OBJECT(this)

@KotlinPoetMetadataPreview
public val Flags.isDataClass: Boolean get() = Flag.Class.IS_DATA(this)

@KotlinPoetMetadataPreview
public val Flags.isEnumClass: Boolean get() = Flag.Class.IS_ENUM_CLASS(this)

@KotlinPoetMetadataPreview
public val Flags.isEnumEntryClass: Boolean get() = Flag.Class.IS_ENUM_ENTRY(this)

@KotlinPoetMetadataPreview
public val Flags.isExpectClass: Boolean get() = Flag.Class.IS_EXPECT(this)

@KotlinPoetMetadataPreview
public val Flags.isExternalClass: Boolean get() = Flag.Class.IS_EXTERNAL(this)

@KotlinPoetMetadataPreview
public val Flags.isValueClass: Boolean get() = Flag.Class.IS_VALUE(this)

@KotlinPoetMetadataPreview
public val Flags.isInnerClass: Boolean get() = Flag.Class.IS_INNER(this)

@KotlinPoetMetadataPreview
public val Flags.isObjectClass: Boolean get() = Flag.Class.IS_OBJECT(this)

@KotlinPoetMetadataPreview
public val Flags.isInterface: Boolean get() = Flag.Class.IS_INTERFACE(this)

@KotlinPoetMetadataPreview
public val Flags.isFun: Boolean get() = Flag.Class.IS_FUN(this)

@KotlinPoetMetadataPreview
public val KmClass.isAnnotation: Boolean get() = flags.isAnnotationClass

@KotlinPoetMetadataPreview
public val KmClass.isClass: Boolean get() = flags.isClass

@KotlinPoetMetadataPreview
public val KmClass.isCompanionObject: Boolean get() = flags.isCompanionObjectClass

@KotlinPoetMetadataPreview
public val KmClass.isData: Boolean get() = flags.isDataClass

@KotlinPoetMetadataPreview
public val KmClass.isEnum: Boolean get() = flags.isEnumClass

@KotlinPoetMetadataPreview
public val KmClass.isEnumEntry: Boolean get() = flags.isEnumEntryClass

@KotlinPoetMetadataPreview
public val KmClass.isExpect: Boolean get() = flags.isExpectClass

@KotlinPoetMetadataPreview
public val KmClass.isExternal: Boolean get() = flags.isExternalClass

@KotlinPoetMetadataPreview
public val KmClass.isValue: Boolean get() = flags.isValueClass

@KotlinPoetMetadataPreview
public val KmClass.isInner: Boolean get() = flags.isInnerClass

@KotlinPoetMetadataPreview
public val KmClass.isObject: Boolean get() = flags.isObjectClass

@KotlinPoetMetadataPreview
public val KmClass.isInterface: Boolean get() = flags.isInterface

@KotlinPoetMetadataPreview
public val KmClass.isFun: Boolean get() = flags.isFun

@KotlinPoetMetadataPreview
public val KmType.isSuspend: Boolean get() = flags.isSuspendType

@KotlinPoetMetadataPreview
public val KmType.isNullable: Boolean get() = flags.isNullableType

// Constructor flags.
@KotlinPoetMetadataPreview
public val Flags.isPrimaryConstructor: Boolean get() = !Flag.Constructor.IS_SECONDARY(this)

@KotlinPoetMetadataPreview
public val KmConstructor.isPrimary: Boolean get() = flags.isPrimaryConstructor

@KotlinPoetMetadataPreview
public val KmConstructor.isSecondary: Boolean get() = !isPrimary

// Function flags.
@KotlinPoetMetadataPreview
public val Flags.isDeclarationFunction: Boolean get() = Flag.Function.IS_DECLARATION(this)

@KotlinPoetMetadataPreview
public val Flags.isFakeOverrideFunction: Boolean get() = Flag.Function.IS_FAKE_OVERRIDE(this)

@KotlinPoetMetadataPreview
public val Flags.isDelegationFunction: Boolean get() = Flag.Function.IS_DELEGATION(this)

@KotlinPoetMetadataPreview
public val Flags.isSynthesizedFunction: Boolean get() = Flag.Function.IS_SYNTHESIZED(this)

@KotlinPoetMetadataPreview
public val Flags.isOperatorFunction: Boolean get() = Flag.Function.IS_OPERATOR(this)

@KotlinPoetMetadataPreview
public val Flags.isInfixFunction: Boolean get() = Flag.Function.IS_INFIX(this)

@KotlinPoetMetadataPreview
public val Flags.isInlineFunction: Boolean get() = Flag.Function.IS_INLINE(this)

@KotlinPoetMetadataPreview
public val Flags.isTailRecFunction: Boolean get() = Flag.Function.IS_TAILREC(this)

@KotlinPoetMetadataPreview
public val Flags.isExternalFunction: Boolean get() = Flag.Function.IS_EXTERNAL(this)

@KotlinPoetMetadataPreview
public val Flags.isSuspendFunction: Boolean get() = Flag.Function.IS_SUSPEND(this)

@KotlinPoetMetadataPreview
public val Flags.isExpectFunction: Boolean get() = Flag.Function.IS_EXPECT(this)

@KotlinPoetMetadataPreview
public val KmFunction.isDeclaration: Boolean get() = flags.isDeclarationFunction

@KotlinPoetMetadataPreview
public val KmFunction.isFakeOverride: Boolean get() = flags.isFakeOverrideFunction

@KotlinPoetMetadataPreview
public val KmFunction.isDelegation: Boolean get() = flags.isDelegationFunction

@KotlinPoetMetadataPreview
public val KmFunction.isSynthesized: Boolean get() = flags.isSynthesizedFunction

@KotlinPoetMetadataPreview
public val KmFunction.isOperator: Boolean get() = flags.isOperatorFunction

@KotlinPoetMetadataPreview
public val KmFunction.isInfix: Boolean get() = flags.isInfixFunction

@KotlinPoetMetadataPreview
public val KmFunction.isInline: Boolean get() = flags.isInlineFunction

@KotlinPoetMetadataPreview
public val KmFunction.isTailRec: Boolean get() = flags.isTailRecFunction

@KotlinPoetMetadataPreview
public val KmFunction.isExternal: Boolean get() = flags.isExternalFunction

@KotlinPoetMetadataPreview
public val KmFunction.isSuspend: Boolean get() = flags.isSuspendFunction

@KotlinPoetMetadataPreview
public val KmFunction.isExpect: Boolean get() = flags.isExpectFunction

// Parameter flags.
@KotlinPoetMetadataPreview
public val KmValueParameter.declaresDefaultValue: Boolean get() =
  Flag.ValueParameter.DECLARES_DEFAULT_VALUE(flags)

@KotlinPoetMetadataPreview
public val KmValueParameter.isCrossInline: Boolean get() = Flag.ValueParameter.IS_CROSSINLINE(flags)

@KotlinPoetMetadataPreview
public val KmValueParameter.isNoInline: Boolean get() = Flag.ValueParameter.IS_NOINLINE(flags)

// Property flags.
@KotlinPoetMetadataPreview
public val Flags.isFakeOverrideProperty: Boolean get() = Flag.Property.IS_FAKE_OVERRIDE(this)

@KotlinPoetMetadataPreview
public val KmProperty.hasConstant: Boolean get() = Flag.Property.HAS_CONSTANT(flags)

@KotlinPoetMetadataPreview
public val KmProperty.hasGetter: Boolean get() = Flag.Property.HAS_GETTER(flags)

@KotlinPoetMetadataPreview
public val KmProperty.hasSetter: Boolean get() = Flag.Property.HAS_SETTER(flags)

@KotlinPoetMetadataPreview
public val KmProperty.isConst: Boolean get() = Flag.Property.IS_CONST(flags)

@KotlinPoetMetadataPreview
public val KmProperty.isDeclaration: Boolean get() = Flag.Property.IS_DECLARATION(flags)

@KotlinPoetMetadataPreview
public val KmProperty.isDelegated: Boolean get() = Flag.Property.IS_DELEGATED(flags)

@KotlinPoetMetadataPreview
public val KmProperty.isDelegation: Boolean get() = Flag.Property.IS_DELEGATION(flags)

@KotlinPoetMetadataPreview
public val KmProperty.isExpect: Boolean get() = Flag.Property.IS_EXPECT(flags)

@KotlinPoetMetadataPreview
public val KmProperty.isExternal: Boolean get() = Flag.Property.IS_EXTERNAL(flags)

@KotlinPoetMetadataPreview
public val KmProperty.isFakeOverride: Boolean get() = flags.isFakeOverrideProperty

@KotlinPoetMetadataPreview
public val KmProperty.isLateinit: Boolean get() = Flag.Property.IS_LATEINIT(flags)

@KotlinPoetMetadataPreview
public val KmProperty.isSynthesized: Boolean get() = Flag.Property.IS_SYNTHESIZED(flags)

@KotlinPoetMetadataPreview
public val KmProperty.isVar: Boolean get() = Flag.Property.IS_VAR(flags)

@KotlinPoetMetadataPreview
public val KmProperty.isVal: Boolean get() = !isVar

// Property Accessor Flags
@KotlinPoetMetadataPreview
public val Flags.isPropertyAccessorExternal: Boolean
  get() = Flag.PropertyAccessor.IS_EXTERNAL(this)

@KotlinPoetMetadataPreview
public val Flags.isPropertyAccessorInline: Boolean
  get() = Flag.PropertyAccessor.IS_INLINE(this)

@KotlinPoetMetadataPreview
public val Flags.isPropertyAccessorNotDefault: Boolean
  get() = Flag.PropertyAccessor.IS_NOT_DEFAULT(this)

// TypeParameter flags.
@KotlinPoetMetadataPreview
public val KmTypeParameter.isReified: Boolean get() = Flag.TypeParameter.IS_REIFIED(flags)

// Property Accessor Flags
public enum class PropertyAccessorFlag {
  IS_EXTERNAL,
  IS_INLINE,
  IS_NOT_DEFAULT,
}

@KotlinPoetMetadataPreview
public val KmProperty.setterPropertyAccessorFlags: Set<PropertyAccessorFlag>
  get() = setterFlags.propertyAccessorFlags

@KotlinPoetMetadataPreview
public val KmProperty.getterPropertyAccessorFlags: Set<PropertyAccessorFlag>
  get() = getterFlags.propertyAccessorFlags

@KotlinPoetMetadataPreview
public val Flags.propertyAccessorFlags: Set<PropertyAccessorFlag>
  get() = setOf {
    if (Flag.PropertyAccessor.IS_EXTERNAL(this@propertyAccessorFlags)) {
      add(PropertyAccessorFlag.IS_EXTERNAL)
    }
    if (Flag.PropertyAccessor.IS_INLINE(this@propertyAccessorFlags)) {
      add(PropertyAccessorFlag.IS_INLINE)
    }
    if (Flag.PropertyAccessor.IS_NOT_DEFAULT(this@propertyAccessorFlags)) {
      add(PropertyAccessorFlag.IS_NOT_DEFAULT)
    }
  }

internal inline fun <E> setOf(body: MutableSet<E>.() -> Unit): Set<E> {
  return mutableSetOf<E>().apply(body).toSet()
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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
 *
 * https://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.
 */
@file:JvmName("KotlinPoetMetadata")

package com.squareup.kotlinpoet.metadata

import javax.lang.model.element.TypeElement
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY
import kotlin.reflect.KClass
import kotlinx.metadata.KmClass
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.Metadata

/**
 * Indicates that a given API is part of the experimental KotlinPoet metadata support. This exists
 * because kotlinx-metadata is not a stable API, and will remain in place until it is.
 */
@RequiresOptIn
@Retention(AnnotationRetention.BINARY)
@Target(CLASS, FUNCTION, PROPERTY)
public annotation class KotlinPoetMetadataPreview

/** @return a new [KmClass] representation of the Kotlin metadata for [this] class. */
@KotlinPoetMetadataPreview
public fun KClass<*>.toKmClass(): KmClass = java.toKmClass()

/** @return a new [KmClass] representation of the Kotlin metadata for [this] class. */
@KotlinPoetMetadataPreview
public fun Class<*>.toKmClass(): KmClass = readMetadata(::getAnnotation).toKmClass()

/** @return a new [KmClass] representation of the Kotlin metadata for [this] type. */
@KotlinPoetMetadataPreview
public fun TypeElement.toKmClass(): KmClass = readMetadata(::getAnnotation).toKmClass()

@KotlinPoetMetadataPreview
public fun Metadata.toKmClass(): KmClass {
  return toKotlinClassMetadata<KotlinClassMetadata.Class>()
    .toKmClass()
}

@KotlinPoetMetadataPreview
public inline fun <reified T : KotlinClassMetadata> Metadata.toKotlinClassMetadata(): T {
  val expectedType = T::class
  val metadata = readKotlinClassMetadata()
  return when (expectedType) {
    KotlinClassMetadata.Class::class -> {
      check(metadata is KotlinClassMetadata.Class)
      metadata as T
    }
    KotlinClassMetadata.FileFacade::class -> {
      check(metadata is KotlinClassMetadata.FileFacade)
      metadata as T
    }
    KotlinClassMetadata.SyntheticClass::class ->
      throw UnsupportedOperationException("SyntheticClass isn't supported yet!")
    KotlinClassMetadata.MultiFileClassFacade::class ->
      throw UnsupportedOperationException("MultiFileClassFacade isn't supported yet!")
    KotlinClassMetadata.MultiFileClassPart::class ->
      throw UnsupportedOperationException("MultiFileClassPart isn't supported yet!")
    KotlinClassMetadata.Unknown::class ->
      throw RuntimeException("Recorded unknown metadata type! $metadata")
    else -> TODO("Unrecognized KotlinClassMetadata type: $expectedType")
  }
}

/**
 * Returns the [KotlinClassMetadata] this represents. In general you should only use this function
 * when you don't know what the underlying [KotlinClassMetadata] subtype is, otherwise you should
 * use one of the more direct functions like [toKmClass].
 */
@KotlinPoetMetadataPreview
public fun Metadata.readKotlinClassMetadata(): KotlinClassMetadata {
  val metadata = KotlinClassMetadata.read(asClassHeader())
  checkNotNull(metadata) {
    "Could not parse metadata! Try bumping kotlinpoet and/or kotlinx-metadata version."
  }
  return metadata
}

private inline fun readMetadata(lookup: ((Class<Metadata>) -> Metadata?)): Metadata {
  return checkNotNull(lookup.invoke(Metadata::class.java)) {
    "No Metadata annotation found! Must be Kotlin code built with the standard library on the classpath."
  }
}

private fun Metadata.asClassHeader(): Metadata {
  return Metadata(
    kind = kind,
    metadataVersion = metadataVersion,
    data1 = data1,
    data2 = data2,
    extraString = extraString,
    packageName = packageName,
    extraInt = extraInt,
  )
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.symbol.AnnotationUseSiteTarget
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSName
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeAlias
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.ParameterizedTypeName

/** Returns an [AnnotationSpec] representation of this [KSAnnotation] instance. */
public fun KSAnnotation.toAnnotationSpec(): AnnotationSpec {
  val builder = when (val type = annotationType.resolve().unwrapTypeAlias().toTypeName()) {
    is ClassName -> AnnotationSpec.builder(type)
    is ParameterizedTypeName -> AnnotationSpec.builder(type)
    else -> error("This is never possible.")
  }
  useSiteTarget?.let { builder.useSiteTarget(it.kpAnalog) }
  // TODO support type params once they're exposed https://github.com/google/ksp/issues/753
  for (argument in arguments) {
    val member = CodeBlock.builder()
    val name = argument.name!!.getShortName()
    member.add("%N = ", name)
    addValueToBlock(argument.value!!, member)
    builder.addMember(member.build())
  }
  return builder.build()
}

private val AnnotationUseSiteTarget.kpAnalog: UseSiteTarget get() = when (this) {
  AnnotationUseSiteTarget.FILE -> UseSiteTarget.FILE
  AnnotationUseSiteTarget.PROPERTY -> UseSiteTarget.PROPERTY
  AnnotationUseSiteTarget.FIELD -> UseSiteTarget.FIELD
  AnnotationUseSiteTarget.GET -> UseSiteTarget.GET
  AnnotationUseSiteTarget.SET -> UseSiteTarget.SET
  AnnotationUseSiteTarget.RECEIVER -> UseSiteTarget.RECEIVER
  AnnotationUseSiteTarget.PARAM -> UseSiteTarget.PARAM
  AnnotationUseSiteTarget.SETPARAM -> UseSiteTarget.SETPARAM
  AnnotationUseSiteTarget.DELEGATE -> UseSiteTarget.DELEGATE
}

internal fun KSType.unwrapTypeAlias(): KSType {
  return if (this.declaration is KSTypeAlias) {
    (this.declaration as KSTypeAlias).type.resolve()
  } else {
    this
  }
}

private fun addValueToBlock(value: Any, member: CodeBlock.Builder) {
  when (value) {
    is List<*> -> {
      // Array type
      val arrayType = when (value.firstOrNull()) {
        is Boolean -> "booleanArrayOf"
        is Byte -> "byteArrayOf"
        is Char -> "charArrayOf"
        is Short -> "shortArrayOf"
        is Int -> "intArrayOf"
        is Long -> "longArrayOf"
        is Float -> "floatArrayOf"
        is Double -> "doubleArrayOf"
        else -> "arrayOf"
      }
      member.add("$arrayType(⇥⇥")
      value.forEachIndexed { index, innerValue ->
        if (index > 0) member.add(", ")
        addValueToBlock(innerValue!!, member)
      }
      member.add("⇤⇤)")
    }
    is KSType -> {
      val unwrapped = value.unwrapTypeAlias()
      val isEnum = (unwrapped.declaration as KSClassDeclaration).classKind == ClassKind.ENUM_ENTRY
      if (isEnum) {
        val parent = unwrapped.declaration.parentDeclaration as KSClassDeclaration
        val entry = unwrapped.declaration.simpleName.getShortName()
        member.add("%T.%L", parent.toClassName(), entry)
      } else {
        member.add("%T::class", unwrapped.toClassName())
      }
    }
    is KSName ->
      member.add(
        "%T.%L",
        ClassName.bestGuess(value.getQualifier()),
        value.getShortName(),
      )
    is KSAnnotation -> member.add("%L", value.toAnnotationSpec())
    else -> member.add(memberForValue(value))
  }
}

/**
 * Creates a [CodeBlock] with parameter `format` depending on the given `value` object.
 * Handles a number of special cases, such as appending "f" to `Float` values, and uses
 * `%L` for other types.
 */
internal fun memberForValue(value: Any) = when (value) {
  is Class<*> -> CodeBlock.of("%T::class", value)
  is Enum<*> -> CodeBlock.of("%T.%L", value.javaClass, value.name)
  is String -> CodeBlock.of("%S", value)
  is Float -> CodeBlock.of("%Lf", value)
  is Double -> CodeBlock.of("%L", value)
  is Char -> CodeBlock.of("'%L'", value)
  is Byte -> CodeBlock.of("$value.toByte()")
  is Short -> CodeBlock.of("$value.toShort()")
  // Int or Boolean
  else -> CodeBlock.of("%L", value)
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.squareup.kotlinpoet.ClassName

/** Returns the [ClassName] representation of this [KSClassDeclaration]. */
public fun KSClassDeclaration.toClassName(): ClassName {
  return toClassNameInternal()
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeAlias
import com.google.devtools.ksp.symbol.KSTypeArgument
import com.google.devtools.ksp.symbol.KSTypeParameter
import com.google.devtools.ksp.symbol.KSTypeReference
import com.google.devtools.ksp.symbol.Variance
import com.google.devtools.ksp.symbol.Variance.CONTRAVARIANT
import com.google.devtools.ksp.symbol.Variance.COVARIANT
import com.google.devtools.ksp.symbol.Variance.INVARIANT
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.STAR
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.tags.TypeAliasTag

/** Returns the [ClassName] representation of this [KSType] IFF it's a [KSClassDeclaration]. */
public fun KSType.toClassName(): ClassName {
  val decl = declaration
  check(decl is KSClassDeclaration) {
    "Declaration was not a KSClassDeclaration: $this"
  }
  return decl.toClassName()
}

/**
 * Returns the [TypeName] representation of this [KSType].
 *
 * @see toTypeParameterResolver
 * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
 *                          declarations can be anything with generics that child nodes declare as
 *                          defined by [KSType.arguments].
 */
public fun KSType.toTypeName(
  typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
): TypeName = toTypeName(typeParamResolver, emptyList())

internal fun KSType.toTypeName(
  typeParamResolver: TypeParameterResolver,
  typeArguments: List<KSTypeArgument>,
): TypeName {
  require(!isError) {
    "Error type '$this' is not resolvable in the current round of processing."
  }
  val type = when (val decl = declaration) {
    is KSClassDeclaration -> {
      val arguments = if (decl.classKind == ClassKind.ANNOTATION_CLASS) {
        arguments
      } else {
        typeArguments
      }

      decl.toClassName().withTypeArguments(arguments.map { it.toTypeName(typeParamResolver) })
    }
    is KSTypeParameter -> typeParamResolver[decl.name.getShortName()]
    is KSTypeAlias -> {
      var typeAlias: KSTypeAlias = decl
      var arguments = arguments

      var resolvedType: KSType
      var mappedArgs: List<KSTypeArgument>
      var extraResolver: TypeParameterResolver = typeParamResolver
      while (true) {
        resolvedType = typeAlias.type.resolve()
        mappedArgs = mapTypeArgumentsFromTypeAliasToAbbreviatedType(
          typeAlias = typeAlias,
          typeAliasTypeArguments = arguments,
          abbreviatedType = resolvedType,
        )
        extraResolver = if (typeAlias.typeParameters.isEmpty()) {
          extraResolver
        } else {
          typeAlias.typeParameters.toTypeParameterResolver(extraResolver)
        }

        typeAlias = resolvedType.declaration as? KSTypeAlias ?: break
        arguments = mappedArgs
      }

      val abbreviatedType = resolvedType
        .toTypeName(extraResolver)
        .copy(nullable = isMarkedNullable)
        .rawType()
        .withTypeArguments(mappedArgs.map { it.toTypeName(extraResolver) })

      val aliasArgs = typeArguments.map { it.toTypeName(typeParamResolver) }

      decl.toClassNameInternal()
        .withTypeArguments(aliasArgs)
        .copy(tags = mapOf(TypeAliasTag::class to TypeAliasTag(abbreviatedType)))
    }
    else -> error("Unsupported type: $declaration")
  }

  return type.copy(nullable = isMarkedNullable)
}

private fun mapTypeArgumentsFromTypeAliasToAbbreviatedType(
  typeAlias: KSTypeAlias,
  typeAliasTypeArguments: List<KSTypeArgument>,
  abbreviatedType: KSType,
): List<KSTypeArgument> {
  return abbreviatedType.arguments
    .map { typeArgument ->
      // Check if type argument is a reference to a typealias type parameter, and not an actual type.
      val typeAliasTypeParameterIndex = typeAlias.typeParameters.indexOfFirst { typeAliasTypeParameter ->
        typeAliasTypeParameter.name.asString() == typeArgument.type.toString()
      }
      if (typeAliasTypeParameterIndex >= 0) {
        typeAliasTypeArguments[typeAliasTypeParameterIndex]
      } else {
        typeArgument
      }
    }
}

/**
 * Returns a [TypeVariableName] representation of this [KSTypeParameter].
 *
 * @see toTypeParameterResolver
 * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
 *                          declarations can be anything with generics that child nodes declare as
 *                          defined by [KSType.arguments].
 */
public fun KSTypeParameter.toTypeVariableName(
  typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
): TypeVariableName {
  val typeVarName = name.getShortName()
  val typeVarBounds = bounds.map { it.toTypeName(typeParamResolver) }.toList()
  val typeVarVariance = when (variance) {
    COVARIANT -> KModifier.OUT
    CONTRAVARIANT -> KModifier.IN
    else -> null
  }
  return TypeVariableName(typeVarName, bounds = typeVarBounds, variance = typeVarVariance)
}

/**
 * Returns a [TypeName] representation of this [KSTypeArgument].
 *
 * @see toTypeParameterResolver
 * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
 *                          declarations can be anything with generics that child nodes declare as
 *                          defined by [KSType.arguments].
 */
public fun KSTypeArgument.toTypeName(
  typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
): TypeName {
  val type = this.type ?: return STAR
  return when (variance) {
    COVARIANT -> WildcardTypeName.producerOf(type.toTypeName(typeParamResolver))
    CONTRAVARIANT -> WildcardTypeName.consumerOf(type.toTypeName(typeParamResolver))
    Variance.STAR -> STAR
    INVARIANT -> type.toTypeName(typeParamResolver)
  }
}

/**
 * Returns a [TypeName] representation of this [KSTypeReference].
 *
 * @see toTypeParameterResolver
 * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
 *                          declarations can be anything with generics that child nodes declare as
 *                          defined by [KSType.arguments].
 */
public fun KSTypeReference.toTypeName(
  typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
): TypeName {
  return resolve().toTypeName(typeParamResolver, element?.typeArguments.orEmpty())
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.symbol.Modifier
import com.google.devtools.ksp.symbol.Modifier.ABSTRACT
import com.google.devtools.ksp.symbol.Modifier.ACTUAL
import com.google.devtools.ksp.symbol.Modifier.ANNOTATION
import com.google.devtools.ksp.symbol.Modifier.CROSSINLINE
import com.google.devtools.ksp.symbol.Modifier.DATA
import com.google.devtools.ksp.symbol.Modifier.ENUM
import com.google.devtools.ksp.symbol.Modifier.EXPECT
import com.google.devtools.ksp.symbol.Modifier.EXTERNAL
import com.google.devtools.ksp.symbol.Modifier.FINAL
import com.google.devtools.ksp.symbol.Modifier.FUN
import com.google.devtools.ksp.symbol.Modifier.IN
import com.google.devtools.ksp.symbol.Modifier.INFIX
import com.google.devtools.ksp.symbol.Modifier.INLINE
import com.google.devtools.ksp.symbol.Modifier.INNER
import com.google.devtools.ksp.symbol.Modifier.INTERNAL
import com.google.devtools.ksp.symbol.Modifier.LATEINIT
import com.google.devtools.ksp.symbol.Modifier.NOINLINE
import com.google.devtools.ksp.symbol.Modifier.OPEN
import com.google.devtools.ksp.symbol.Modifier.OPERATOR
import com.google.devtools.ksp.symbol.Modifier.OUT
import com.google.devtools.ksp.symbol.Modifier.OVERRIDE
import com.google.devtools.ksp.symbol.Modifier.PRIVATE
import com.google.devtools.ksp.symbol.Modifier.PROTECTED
import com.google.devtools.ksp.symbol.Modifier.PUBLIC
import com.google.devtools.ksp.symbol.Modifier.REIFIED
import com.google.devtools.ksp.symbol.Modifier.SEALED
import com.google.devtools.ksp.symbol.Modifier.SUSPEND
import com.google.devtools.ksp.symbol.Modifier.TAILREC
import com.google.devtools.ksp.symbol.Modifier.VALUE
import com.google.devtools.ksp.symbol.Modifier.VARARG
import com.squareup.kotlinpoet.KModifier

/**
 * Returns the [KModifier] representation of this [Modifier] or null if this is a Java-only
 * modifier (i.e. prefixed with `JAVA_`), which do not have obvious [KModifier] analogues.
 */
public fun Modifier.toKModifier(): KModifier? {
  return when (this) {
    PUBLIC -> KModifier.PUBLIC
    PRIVATE -> KModifier.PRIVATE
    INTERNAL -> KModifier.INTERNAL
    PROTECTED -> KModifier.PROTECTED
    IN -> KModifier.IN
    OUT -> KModifier.OUT
    OVERRIDE -> KModifier.OVERRIDE
    LATEINIT -> KModifier.LATEINIT
    ENUM -> KModifier.ENUM
    SEALED -> KModifier.SEALED
    ANNOTATION -> KModifier.ANNOTATION
    DATA -> KModifier.DATA
    INNER -> KModifier.INNER
    FUN -> KModifier.FUN
    VALUE -> KModifier.VALUE
    SUSPEND -> KModifier.SUSPEND
    TAILREC -> KModifier.TAILREC
    OPERATOR -> KModifier.OPERATOR
    INFIX -> KModifier.INFIX
    INLINE -> KModifier.INLINE
    EXTERNAL -> KModifier.EXTERNAL
    ABSTRACT -> KModifier.ABSTRACT
    FINAL -> KModifier.FINAL
    OPEN -> KModifier.OPEN
    VARARG -> KModifier.VARARG
    NOINLINE -> KModifier.NOINLINE
    CROSSINLINE -> KModifier.CROSSINLINE
    REIFIED -> KModifier.REIFIED
    EXPECT -> KModifier.EXPECT
    ACTUAL -> KModifier.ACTUAL
    else -> null
  }
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.symbol.KSFile
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.Taggable
import com.squareup.kotlinpoet.TypeAliasSpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.tag
import java.io.OutputStreamWriter
import java.nio.charset.StandardCharsets

/**
 * A simple holder class for containing originating [KSFiles][KSFile], which are used by KSP to
 * inform its incremental processing.
 *
 * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
 */
public interface OriginatingKSFiles {
  public val files: List<KSFile>
}

/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
public fun TypeSpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()

/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
public fun FunSpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()

/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
public fun PropertySpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()

/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
public fun TypeAliasSpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()

/**
 * Returns the list of all files added to the contained
 * [TypeSpecs][TypeSpec], [PropertySpecs][PropertySpec], [FunSpecs][FunSpec], or
 * [TypeAliasSpecs][TypeAliasSpec] contained in this spec.
 */
public fun FileSpec.originatingKSFiles(): List<KSFile> {
  return members
    .flatMap {
      when (it) {
        is FunSpec -> it.originatingKSFiles()
        is PropertySpec -> it.originatingKSFiles()
        is TypeSpec -> it.originatingKSFiles()
        is TypeAliasSpec -> it.originatingKSFiles()
        else -> emptyList()
      }
    }
    .distinct()
}

/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
public fun TypeAliasSpec.Builder.addOriginatingKSFile(ksFile: KSFile): TypeAliasSpec.Builder = apply {
  getOrCreateKSFilesTag().add(ksFile)
}

/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
public fun PropertySpec.Builder.addOriginatingKSFile(ksFile: KSFile): PropertySpec.Builder = apply {
  getOrCreateKSFilesTag().add(ksFile)
}

/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
public fun FunSpec.Builder.addOriginatingKSFile(ksFile: KSFile): FunSpec.Builder = apply {
  getOrCreateKSFilesTag().add(ksFile)
}

/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
public fun TypeSpec.Builder.addOriginatingKSFile(ksFile: KSFile): TypeSpec.Builder = apply {
  getOrCreateKSFilesTag().add(ksFile)
}

/**
 * Writes this [FileSpec] to a given [codeGenerator] with the given [originatingKSFiles].
 *
 * Note that if none are specified, the [originatingKSFiles] argument defaults to using
 * [FileSpec.originatingKSFiles], which will automatically resolve any files added to the
 * contained declarations.
 *
 * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
 *
 * @see FileSpec.originatingKSFiles
 * @param codeGenerator the [CodeGenerator] to write to.
 * @param aggregating flag indicating if this is an aggregating symbol processor.
 */
public fun FileSpec.writeTo(
  codeGenerator: CodeGenerator,
  aggregating: Boolean,
  originatingKSFiles: Iterable<KSFile> = originatingKSFiles(),
) {
  val dependencies = kspDependencies(aggregating, originatingKSFiles)
  writeTo(codeGenerator, dependencies)
}

/**
 * Writes this [FileSpec] to a given [codeGenerator] with the given [dependencies].
 *
 * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
 *
 * @see FileSpec.originatingKSFiles
 * @see kspDependencies
 * @param codeGenerator the [CodeGenerator] to write to.
 * @param dependencies the [Dependencies] to create a new file with.
 */
public fun FileSpec.writeTo(
  codeGenerator: CodeGenerator,
  dependencies: Dependencies,
) {
  val file = codeGenerator.createNewFile(dependencies, packageName, name)
  // Don't use writeTo(file) because that tries to handle directories under the hood
  OutputStreamWriter(file, StandardCharsets.UTF_8)
    .use(::writeTo)
}

/**
 * Returns a KSP [Dependencies] component of this [FileSpec] with the given [originatingKSFiles],
 * intended to be used in tandem with [writeTo].
 *
 * Note that if no [originatingKSFiles] are specified, the [originatingKSFiles] argument defaults
 * to using [FileSpec.originatingKSFiles], which will automatically resolve any files added to the
 * contained declarations.
 *
 * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
 *
 * @see FileSpec.originatingKSFiles
 * @see FileSpec.writeTo
 * @param aggregating flag indicating if this is an aggregating symbol processor.
 */
public fun FileSpec.kspDependencies(
  aggregating: Boolean,
  originatingKSFiles: Iterable<KSFile> = originatingKSFiles(),
): Dependencies = Dependencies(aggregating, *originatingKSFiles.toList().toTypedArray())

/**
 * A mutable [OriginatingKSFiles] instance for use with KotlinPoet Builders via [Taggable.Builder].
 */
private interface MutableOriginatingKSFiles : OriginatingKSFiles {
  override val files: MutableList<KSFile>
}

private data class MutableOriginatingKSFilesImpl(override val files: MutableList<KSFile> = mutableListOf()) : MutableOriginatingKSFiles

private fun Taggable.getKSFilesTag(): List<KSFile> {
  return tag<OriginatingKSFiles>()?.files.orEmpty()
}

private fun Taggable.Builder<*>.getOrCreateKSFilesTag(): MutableList<KSFile> {
  val holder = tags.getOrPut(
    OriginatingKSFiles::class,
    ::MutableOriginatingKSFilesImpl,
  ) as MutableOriginatingKSFiles
  return holder.files
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeParameter
import com.squareup.kotlinpoet.TypeVariableName

/**
 * A resolver for enclosing declarations' type parameters. Parent declarations can be anything with
 * generics that child nodes declare as defined by [KSType.arguments].
 *
 * This is important for resolving inherited generics on child declarations, as KSP interop
 * otherwise can't resolve them.
 *
 * In general, you want to retrieve an instance of this via [toTypeParameterResolver].
 *
 * @see toTypeParameterResolver
 */
public interface TypeParameterResolver {
  public val parametersMap: Map<String, TypeVariableName>
  public operator fun get(index: String): TypeVariableName

  public companion object {
    /**
     * An empty instance of [TypeParameterResolver], only should be used if enclosing declarations
     * are known to not have arguments, such as top-level classes.
     */
    public val EMPTY: TypeParameterResolver = object : TypeParameterResolver {
      override val parametersMap: Map<String, TypeVariableName> = emptyMap()

      override fun get(index: String): TypeVariableName = throw NoSuchElementException("No TypeParameter found for index $index")
    }
  }
}

/**
 * Returns a [TypeParameterResolver] for this list of [KSTypeParameters][KSTypeParameter] for use
 * with enclosed declarations.
 *
 * @param parent the optional parent resolver, if any. An example of this is cases where you might
 *               create a resolver for a [KSFunction] and supply a parent resolved from the
 *               enclosing [KSClassDeclaration].
 * @param sourceTypeHint an optional hint for error messages. Unresolvable parameter IDs will
 *                       include this hint in the thrown error's message.
 */
public fun List<KSTypeParameter>.toTypeParameterResolver(
  parent: TypeParameterResolver? = null,
  sourceTypeHint: String = "<unknown>",
): TypeParameterResolver {
  val parametersMap = LinkedHashMap<String, TypeVariableName>()
  val typeParamResolver = { id: String ->
    parametersMap[id]
      ?: parent?.get(id)
      ?: throw IllegalStateException(
        "No type argument found for $id! Analyzed $sourceTypeHint with known parameters " +
          "${parametersMap.keys}",
      )
  }

  val resolver = object : TypeParameterResolver {
    override val parametersMap: Map<String, TypeVariableName> = parametersMap

    override operator fun get(index: String): TypeVariableName = typeParamResolver(index)
  }

  // Fill the parametersMap. Need to do sequentially and allow for referencing previously defined params
  for (typeVar in this) {
    // Put the simple typevar in first, then it can be referenced in the full toTypeVariable()
    // replacement later that may add bounds referencing this.
    val id = typeVar.name.getShortName()
    parametersMap[id] = TypeVariableName(id)
  }

  for (typeVar in this) {
    val id = typeVar.name.getShortName()
    // Now replace it with the full version.
    parametersMap[id] = typeVar.toTypeVariableName(resolver)
  }

  return resolver
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.symbol.Visibility
import com.google.devtools.ksp.symbol.Visibility.JAVA_PACKAGE
import com.google.devtools.ksp.symbol.Visibility.LOCAL
import com.squareup.kotlinpoet.KModifier

/**
 * Returns the [KModifier] representation of this visibility or null if this is [JAVA_PACKAGE]
 * or [LOCAL] (which do not have obvious [KModifier] alternatives).
 */
public fun Visibility.toKModifier(): KModifier? {
  return when (this) {
    Visibility.PUBLIC -> KModifier.PUBLIC
    Visibility.PRIVATE -> KModifier.PRIVATE
    Visibility.PROTECTED -> KModifier.PROTECTED
    Visibility.INTERNAL -> KModifier.INTERNAL
    else -> null
  }
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.isLocal
import com.google.devtools.ksp.symbol.KSDeclaration
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName

internal fun TypeName.rawType(): ClassName {
  return findRawType() ?: throw IllegalArgumentException("Cannot get raw type from $this")
}

internal fun TypeName.findRawType(): ClassName? {
  return when (this) {
    is ClassName -> this
    is ParameterizedTypeName -> rawType
    is LambdaTypeName -> {
      var count = parameters.size
      if (receiver != null) {
        count++
      }
      val functionSimpleName = if (count >= 23) {
        "FunctionN"
      } else {
        "Function$count"
      }
      ClassName("kotlin.jvm.functions", functionSimpleName)
    }
    else -> null
  }
}

internal fun ClassName.withTypeArguments(arguments: List<TypeName>): TypeName {
  return if (arguments.isEmpty()) {
    this
  } else {
    this.parameterizedBy(arguments)
  }
}

internal fun KSDeclaration.toClassNameInternal(): ClassName {
  require(!isLocal()) {
    "Local/anonymous classes are not supported!"
  }
  val pkgName = packageName.asString()
  val typesString = checkNotNull(qualifiedName).asString().removePrefix("$pkgName.")

  val simpleNames = typesString
    .split(".")
  return ClassName(pkgName, simpleNames)
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.classinspectors

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
import com.squareup.kotlinpoet.CHAR_SEQUENCE
import com.squareup.kotlinpoet.COLLECTION
import com.squareup.kotlinpoet.COMPARABLE
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.ITERABLE
import com.squareup.kotlinpoet.LIST
import com.squareup.kotlinpoet.MAP
import com.squareup.kotlinpoet.MAP_ENTRY
import com.squareup.kotlinpoet.MUTABLE_COLLECTION
import com.squareup.kotlinpoet.MUTABLE_ITERABLE
import com.squareup.kotlinpoet.MUTABLE_LIST
import com.squareup.kotlinpoet.MUTABLE_MAP
import com.squareup.kotlinpoet.MUTABLE_MAP_ENTRY
import com.squareup.kotlinpoet.MUTABLE_SET
import com.squareup.kotlinpoet.SET
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.joinToCode
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.isConst
import com.squareup.kotlinpoet.metadata.specs.ClassInspector
import java.util.Collections
import java.util.TreeSet
import kotlinx.metadata.KmProperty
import kotlinx.metadata.isLocal
import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable

@KotlinPoetMetadataPreview
internal object ClassInspectorUtil {
  val JVM_NAME: ClassName = JvmName::class.asClassName()
  private val JVM_FIELD = JvmField::class.asClassName()
  internal val JVM_FIELD_SPEC = AnnotationSpec.builder(JVM_FIELD).build()
  internal val JVM_SYNTHETIC = JvmSynthetic::class.asClassName()
  internal val JVM_SYNTHETIC_SPEC = AnnotationSpec.builder(JVM_SYNTHETIC).build()
  internal val JAVA_DEPRECATED = java.lang.Deprecated::class.asClassName()
  private val JVM_TRANSIENT = Transient::class.asClassName()
  private val JVM_VOLATILE = Volatile::class.asClassName()
  private val IMPLICIT_FIELD_ANNOTATIONS = setOf(
    JVM_FIELD,
    JVM_TRANSIENT,
    JVM_VOLATILE,
  )
  private val NOT_NULL = NotNull::class.asClassName()
  private val NULLABLE = Nullable::class.asClassName()
  private val EXTENSION_FUNCTION_TYPE = ExtensionFunctionType::class.asClassName()
  private val KOTLIN_INTRINSIC_ANNOTATIONS = setOf(
    NOT_NULL,
    NULLABLE,
    EXTENSION_FUNCTION_TYPE,
  )

  val KOTLIN_INTRINSIC_INTERFACES: Set<ClassName> = setOf(
    CHAR_SEQUENCE,
    COMPARABLE,
    ITERABLE,
    COLLECTION,
    LIST,
    SET,
    MAP,
    MAP_ENTRY,
    MUTABLE_ITERABLE,
    MUTABLE_COLLECTION,
    MUTABLE_LIST,
    MUTABLE_SET,
    MUTABLE_MAP,
    MUTABLE_MAP_ENTRY,
  )

  private val KOTLIN_NULLABILITY_ANNOTATIONS = setOf(
    "org.jetbrains.annotations.NotNull",
    "org.jetbrains.annotations.Nullable",
  )

  fun filterOutNullabilityAnnotations(
    annotations: List<AnnotationSpec>,
  ): List<AnnotationSpec> {
    return annotations.filterNot {
      val typeName = it.typeName
      return@filterNot typeName is ClassName &&
        typeName.canonicalName in KOTLIN_NULLABILITY_ANNOTATIONS
    }
  }

  /** @return a [CodeBlock] representation of a [literal] value. */
  fun codeLiteralOf(literal: Any): CodeBlock {
    return when (literal) {
      is String -> CodeBlock.of("%S", literal)
      is Long -> CodeBlock.of("%LL", literal)
      is Float -> CodeBlock.of("%LF", literal)
      else -> CodeBlock.of("%L", literal)
    }
  }

  /**
   * Infers if [property] is a jvm field and should be annotated as such given the input
   * parameters.
   */
  fun computeIsJvmField(
    property: KmProperty,
    classInspector: ClassInspector,
    isCompanionObject: Boolean,
    hasGetter: Boolean,
    hasSetter: Boolean,
    hasField: Boolean,
  ): Boolean {
    return if (!hasGetter &&
      !hasSetter &&
      hasField &&
      !property.isConst
    ) {
      !(classInspector.supportsNonRuntimeRetainedAnnotations && !isCompanionObject)
    } else {
      false
    }
  }

  /**
   * @return a new collection of [AnnotationSpecs][AnnotationSpec] with sorting and de-duping
   *         input annotations from [body].
   */
  fun createAnnotations(
    siteTarget: UseSiteTarget? = null,
    body: MutableCollection<AnnotationSpec>.() -> Unit,
  ): Collection<AnnotationSpec> {
    val result = mutableSetOf<AnnotationSpec>()
      .apply(body)
      .filterNot { spec ->
        spec.typeName in KOTLIN_INTRINSIC_ANNOTATIONS
      }
    val withUseSiteTarget = if (siteTarget != null) {
      result.map {
        if (!(siteTarget == FIELD && it.typeName in IMPLICIT_FIELD_ANNOTATIONS)) {
          // Some annotations are implicitly only for FIELD, so don't emit those site targets
          it.toBuilder().useSiteTarget(siteTarget).build()
        } else {
          it
        }
      }
    } else {
      result
    }

    val sorted = withUseSiteTarget.toTreeSet()

    return Collections.unmodifiableCollection(sorted)
  }

  /**
   * @return a [@Throws][Throws] [AnnotationSpec] representation of a given collection of
   *         [exceptions].
   */
  fun createThrowsSpec(
    exceptions: Collection<TypeName>,
    useSiteTarget: UseSiteTarget? = null,
  ): AnnotationSpec {
    return AnnotationSpec.builder(Throws::class)
      .addMember(
        "exceptionClasses = %L",
        exceptions.map { CodeBlock.of("%T::class", it) }
          .joinToCode(prefix = "[", suffix = "]"),
      )
      .useSiteTarget(useSiteTarget)
      .build()
  }

  /**
   * Best guesses a [ClassName] as represented in Metadata's [kotlinx.metadata.ClassName], where
   * package names in this name are separated by '/' and class names are separated by '.'.
   *
   * For example: `"org/foo/bar/Baz.Nested"`.
   *
   * Local classes are prefixed with ".", but for KotlinPoetMetadataSpecs' use case we don't deal
   * with those.
   */
  fun createClassName(kotlinMetadataName: String): ClassName {
    require(!kotlinMetadataName.isLocal) {
      "Local/anonymous classes are not supported!"
    }
    // Top-level: package/of/class/MyClass
    // Nested A:  package/of/class/MyClass.NestedClass
    val simpleName = kotlinMetadataName.substringAfterLast(
      '/', // Drop the package name, e.g. "package/of/class/"
      '.', // Drop any enclosing classes, e.g. "MyClass."
    )
    val packageName = kotlinMetadataName.substringBeforeLast(
      delimiter = "/",
      missingDelimiterValue = "",
    )
    val simpleNames = kotlinMetadataName.removeSuffix(simpleName)
      .removeSuffix(".") // Trailing "." if any
      .removePrefix(packageName)
      .removePrefix("/")
      .let {
        if (it.isNotEmpty()) {
          it.split(".")
        } else {
          // Don't split, otherwise we end up with an empty string as the first element!
          emptyList()
        }
      }
      .plus(simpleName)

    return ClassName(
      packageName = packageName.replace("/", "."),
      simpleNames = simpleNames,
    )
  }

  fun Iterable<AnnotationSpec>.toTreeSet(): TreeSet<AnnotationSpec> {
    return TreeSet<AnnotationSpec>(compareBy { it.toString() }).apply {
      addAll(this@toTreeSet)
    }
  }

  private fun String.substringAfterLast(vararg delimiters: Char): String {
    val index = lastIndexOfAny(delimiters)
    return if (index == -1) this else substring(index + 1, length)
  }
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.classinspectors

import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
import com.google.auto.common.Visibility
import com.google.common.collect.LinkedHashMultimap
import com.google.common.collect.SetMultimap
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.DelicateKotlinPoetApi
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JAVA_DEPRECATED
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JVM_NAME
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.filterOutNullabilityAnnotations
import com.squareup.kotlinpoet.metadata.hasAnnotations
import com.squareup.kotlinpoet.metadata.hasConstant
import com.squareup.kotlinpoet.metadata.isAnnotation
import com.squareup.kotlinpoet.metadata.isCompanionObject
import com.squareup.kotlinpoet.metadata.isConst
import com.squareup.kotlinpoet.metadata.isDeclaration
import com.squareup.kotlinpoet.metadata.isSynthesized
import com.squareup.kotlinpoet.metadata.isValue
import com.squareup.kotlinpoet.metadata.readKotlinClassMetadata
import com.squareup.kotlinpoet.metadata.specs.ClassData
import com.squareup.kotlinpoet.metadata.specs.ClassInspector
import com.squareup.kotlinpoet.metadata.specs.ConstructorData
import com.squareup.kotlinpoet.metadata.specs.ContainerData
import com.squareup.kotlinpoet.metadata.specs.EnumEntryData
import com.squareup.kotlinpoet.metadata.specs.FieldData
import com.squareup.kotlinpoet.metadata.specs.FileData
import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier
import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.TRANSIENT
import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.VOLATILE
import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier
import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.STATIC
import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.SYNCHRONIZED
import com.squareup.kotlinpoet.metadata.specs.KM_CONSTRUCTOR_COMPARATOR
import com.squareup.kotlinpoet.metadata.specs.KM_FUNCTION_COMPARATOR
import com.squareup.kotlinpoet.metadata.specs.KM_PROPERTY_COMPARATOR
import com.squareup.kotlinpoet.metadata.specs.MethodData
import com.squareup.kotlinpoet.metadata.specs.PropertyData
import com.squareup.kotlinpoet.metadata.toKmClass
import java.util.TreeMap
import java.util.concurrent.ConcurrentHashMap
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind.INTERFACE
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.PackageElement
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.lang.model.type.TypeKind
import javax.lang.model.util.ElementFilter
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmDeclarationContainer
import kotlinx.metadata.KmPackage
import kotlinx.metadata.jvm.JvmFieldSignature
import kotlinx.metadata.jvm.JvmMethodSignature
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.fieldSignature
import kotlinx.metadata.jvm.getterSignature
import kotlinx.metadata.jvm.setterSignature
import kotlinx.metadata.jvm.signature
import kotlinx.metadata.jvm.syntheticMethodForAnnotations

private typealias ElementsModifier = javax.lang.model.element.Modifier

/**
 * An [Elements]-based implementation of [ClassInspector].
 */
@KotlinPoetMetadataPreview
public class ElementsClassInspector private constructor(
  private val elements: Elements,
  private val types: Types,
) : ClassInspector {
  private val typeElementCache = ConcurrentHashMap<ClassName, Optional<TypeElement>>()
  private val methodCache = ConcurrentHashMap<Pair<TypeElement, String>, Optional<ExecutableElement>>()
  private val variableElementCache = ConcurrentHashMap<Pair<TypeElement, String>, Optional<VariableElement>>()
  private val jvmNameType = elements.getTypeElement(JVM_NAME.canonicalName)
  private val jvmNameName = ElementFilter.methodsIn(jvmNameType.enclosedElements)
    .first { it.simpleName.toString() == "name" }

  private fun lookupTypeElement(className: ClassName): TypeElement? {
    return typeElementCache.getOrPut(className) {
      elements.getTypeElement(className.canonicalName).toOptional()
    }.nullableValue
  }

  override val supportsNonRuntimeRetainedAnnotations: Boolean = true

  override fun declarationContainerFor(className: ClassName): KmDeclarationContainer {
    val typeElement = lookupTypeElement(className)
      ?: error("No type element found for: $className.")

    val metadata = typeElement.getAnnotation(Metadata::class.java)
    return when (val kotlinClassMetadata = metadata.readKotlinClassMetadata()) {
      is KotlinClassMetadata.Class -> kotlinClassMetadata.toKmClass()
      is KotlinClassMetadata.FileFacade -> kotlinClassMetadata.toKmPackage()
      else -> TODO("Not implemented yet: ${kotlinClassMetadata.javaClass.simpleName}")
    }
  }

  override fun isInterface(className: ClassName): Boolean {
    if (className in ClassInspectorUtil.KOTLIN_INTRINSIC_INTERFACES) {
      return true
    }
    return lookupTypeElement(className)?.kind == INTERFACE
  }

  private fun TypeElement.lookupField(fieldSignature: JvmFieldSignature): VariableElement? {
    val signatureString = fieldSignature.asString()
    return variableElementCache.getOrPut(this to signatureString) {
      ElementFilter.fieldsIn(enclosedElements)
        .find { signatureString == it.jvmFieldSignature(types) }.toOptional()
    }.nullableValue
  }

  private fun lookupMethod(
    className: ClassName,
    methodSignature: JvmMethodSignature,
    elementFilter: (Iterable<Element>) -> List<ExecutableElement>,
  ): ExecutableElement? {
    return lookupTypeElement(className)?.lookupMethod(methodSignature, elementFilter)
  }

  private fun TypeElement.lookupMethod(
    methodSignature: JvmMethodSignature,
    elementFilter: (Iterable<Element>) -> List<ExecutableElement>,
  ): ExecutableElement? {
    val signatureString = methodSignature.asString()
    return methodCache.getOrPut(this to signatureString) {
      elementFilter(enclosedElements)
        .find { signatureString == it.jvmMethodSignature(types) }.toOptional()
    }.nullableValue
  }

  private fun VariableElement.jvmModifiers(isJvmField: Boolean): Set<JvmFieldModifier> {
    return modifiers.mapNotNullTo(mutableSetOf()) {
      when {
        it == ElementsModifier.TRANSIENT -> TRANSIENT
        it == ElementsModifier.VOLATILE -> VOLATILE
        !isJvmField && it == ElementsModifier.STATIC -> JvmFieldModifier.STATIC
        else -> null
      }
    }
  }

  @OptIn(DelicateKotlinPoetApi::class)
  private fun VariableElement.annotationSpecs(): List<AnnotationSpec> {
    return filterOutNullabilityAnnotations(
      annotationMirrors.map { AnnotationSpec.get(it) },
    )
  }

  private fun ExecutableElement.jvmModifiers(): Set<JvmMethodModifier> {
    return modifiers.mapNotNullTo(mutableSetOf()) {
      when (it) {
        ElementsModifier.SYNCHRONIZED -> SYNCHRONIZED
        ElementsModifier.STATIC -> STATIC
        ElementsModifier.DEFAULT -> DEFAULT
        else -> null
      }
    }
  }

  @OptIn(DelicateKotlinPoetApi::class)
  private fun ExecutableElement.annotationSpecs(): List<AnnotationSpec> {
    return filterOutNullabilityAnnotations(
      annotationMirrors.map { AnnotationSpec.get(it) },
    )
  }

  @OptIn(DelicateKotlinPoetApi::class)
  private fun ExecutableElement.exceptionTypeNames(): List<TypeName> {
    return thrownTypes.map { it.asTypeName() }
  }

  override fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData {
    val enumType = lookupTypeElement(enumClassName)
      ?: error("No type element found for: $enumClassName.")
    val enumTypeAsType = enumType.asType()
    val member = typeElementCache.getOrPut(enumClassName.nestedClass(memberName)) {
      ElementFilter.typesIn(enumType.enclosedElements)
        .asSequence()
        .filter { types.isSubtype(enumTypeAsType, it.superclass) }
        .find { it.simpleName.contentEquals(memberName) }.toOptional()
    }.nullableValue
    val declarationContainer = member?.getAnnotation(Metadata::class.java)?.toKmClass()

    val entry = ElementFilter.fieldsIn(enumType.enclosedElements)
      .find { it.simpleName.contentEquals(memberName) }
      ?: error("Could not find the enum entry for: $enumClassName")

    return EnumEntryData(
      declarationContainer = declarationContainer,
      annotations = entry.annotationSpecs(),
    )
  }

  private fun VariableElement.constantValue(): CodeBlock? {
    return constantValue?.let(ClassInspectorUtil::codeLiteralOf)
  }

  override fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean {
    return lookupMethod(className, methodSignature, ElementFilter::methodsIn) != null
  }

  /**
   * Detects whether [this] given method is overridden in [type].
   *
   * Adapted and simplified from AutoCommon's private
   * [MoreElements.getLocalAndInheritedMethods] methods implementations for detecting
   * overrides.
   */
  private fun ExecutableElement.isOverriddenIn(type: TypeElement): Boolean {
    val methodMap = LinkedHashMultimap.create<String, ExecutableElement>()
    type.getAllMethods(MoreElements.getPackage(type), methodMap)
    // Find methods that are overridden using `Elements.overrides`. We reduce the performance
    // impact by:
    //   (a) grouping methods by name, since a method cannot override another method with a
    //       different name. Since we know the target name, we just inspect the methods with
    //       that name.
    //   (b) making sure that methods in ancestor types precede those in descendant types,
    //       which means we only have to check a method against the ones that follow it in
    //       that order. Below, this means we just need to find the index of our target method
    //       and compare against only preceding ones.
    val methodList = methodMap.asMap()[simpleName.toString()]?.toList()
      ?: return false
    val signature = jvmMethodSignature(types)
    return methodList.asSequence()
      .filter { it.jvmMethodSignature(types) == signature }
      .take(1)
      .any { elements.overrides(this, it, type) }
  }

  /**
   * Add to [methodsAccumulator] the instance methods from [this] that are visible to code in
   * the package [pkg]. This means all the instance methods from [this] itself and all
   * instance methods it inherits from its ancestors, except private methods and
   * package-private methods in other packages. This method does not take overriding into
   * account, so it will add both an ancestor method and a descendant method that overrides
   * it. [methodsAccumulator] is a multimap from a method name to all of the methods with
   * that name, including methods that override or overload one another. Within those
   * methods, those in ancestor types always precede those in descendant types.
   *
   * Adapted from AutoCommon's private [MoreElements.getLocalAndInheritedMethods] methods'
   * implementations, before overridden methods are stripped.
   */
  private fun TypeElement.getAllMethods(
    pkg: PackageElement,
    methodsAccumulator: SetMultimap<String, ExecutableElement>,
  ) {
    for (superInterface in interfaces) {
      MoreTypes.asTypeElement(superInterface).getAllMethods(pkg, methodsAccumulator)
    }
    if (superclass.kind != TypeKind.NONE) {
      // Visit the superclass after superinterfaces so we will always see the implementation of a
      // method after any interfaces that declared it.
      MoreTypes.asTypeElement(superclass).getAllMethods(pkg, methodsAccumulator)
    }
    for (method in ElementFilter.methodsIn(enclosedElements)) {
      if (ElementsModifier.STATIC !in method.modifiers &&
        ElementsModifier.FINAL !in method.modifiers &&
        ElementsModifier.PRIVATE !in method.modifiers &&
        method.isVisibleFrom(pkg)
      ) {
        methodsAccumulator.put(method.simpleName.toString(), method)
      }
    }
  }

  private fun ExecutableElement.isVisibleFrom(pkg: PackageElement): Boolean {
    // We use Visibility.ofElement rather than [MoreElements.effectiveVisibilityOfElement]
    // because it doesn't really matter whether the containing class is visible. If you
    // inherit a public method then you have a public method, regardless of whether you
    // inherit it from a public class.
    return when (Visibility.ofElement(this)) {
      Visibility.PRIVATE -> false
      Visibility.DEFAULT -> MoreElements.getPackage(this) == pkg
      else -> true
    }
  }

  @OptIn(DelicateKotlinPoetApi::class)
  override fun containerData(
    declarationContainer: KmDeclarationContainer,
    className: ClassName,
    parentClassName: ClassName?,
  ): ContainerData {
    val typeElement: TypeElement = lookupTypeElement(className) ?: error("No class found for: $className.")
    val isCompanionObject = when (declarationContainer) {
      is KmClass -> {
        declarationContainer.isCompanionObject
      }
      is KmPackage -> {
        false
      }
      else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
    }

    // Should only be called if parentName has been null-checked
    val classIfCompanion by lazy(NONE) {
      if (isCompanionObject && parentClassName != null) {
        lookupTypeElement(parentClassName)
          ?: error("No class found for: $parentClassName.")
      } else {
        typeElement
      }
    }

    val propertyData = declarationContainer.properties
      .asSequence()
      .filter { it.isDeclaration }
      .filterNot { it.isSynthesized }
      .associateWithTo(TreeMap(KM_PROPERTY_COMPARATOR)) { property ->
        val isJvmField = ClassInspectorUtil.computeIsJvmField(
          property = property,
          classInspector = this,
          isCompanionObject = isCompanionObject,
          hasGetter = property.getterSignature != null,
          hasSetter = property.setterSignature != null,
          hasField = property.fieldSignature != null,
        )

        val fieldData = property.fieldSignature?.let fieldDataLet@{ fieldSignature ->
          // Check the field in the parent first. For const/static/jvmField elements, these only
          // exist in the parent and we want to check that if necessary to avoid looking up a
          // non-existent field in the companion.
          val parentModifiers = if (isCompanionObject && parentClassName != null) {
            classIfCompanion.lookupField(fieldSignature)?.jvmModifiers(isJvmField).orEmpty()
          } else {
            emptySet()
          }

          val isStatic = JvmFieldModifier.STATIC in parentModifiers

          // TODO we looked up field once, let's reuse it
          val classForOriginalField = typeElement.takeUnless {
            isCompanionObject &&
              (property.isConst || isJvmField || isStatic)
          } ?: classIfCompanion

          val field = classForOriginalField.lookupField(fieldSignature)
            ?: return@fieldDataLet FieldData.SYNTHETIC
          val constant = if (property.hasConstant) {
            val fieldWithConstant = classIfCompanion.takeIf { it != typeElement }?.let {
              if (it.kind.isInterface) {
                field
              } else {
                // const properties are relocated to the enclosing class
                it.lookupField(fieldSignature)
                  ?: return@fieldDataLet FieldData.SYNTHETIC
              }
            } ?: field
            fieldWithConstant.constantValue()
          } else {
            null
          }

          val jvmModifiers = field.jvmModifiers(isJvmField) + parentModifiers

          FieldData(
            annotations = field.annotationSpecs(),
            isSynthetic = false,
            jvmModifiers = jvmModifiers.filterNotTo(mutableSetOf()) {
              // JvmField companion objects don't need JvmStatic, it's implicit
              isCompanionObject && isJvmField && it == JvmFieldModifier.STATIC
            },
            constant = constant,
          )
        }

        val getterData = property.getterSignature?.let { getterSignature ->
          val method = classIfCompanion.lookupMethod(getterSignature, ElementFilter::methodsIn)
          method?.methodData(
            typeElement = typeElement,
            hasAnnotations = property.getterFlags.hasAnnotations,
            jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
              ?.lookupMethod(getterSignature, ElementFilter::methodsIn)
              ?: method,
          )
            ?: return@let MethodData.SYNTHETIC
        }

        val setterData = property.setterSignature?.let { setterSignature ->
          val method = classIfCompanion.lookupMethod(setterSignature, ElementFilter::methodsIn)
          method?.methodData(
            typeElement = typeElement,
            hasAnnotations = property.setterFlags.hasAnnotations,
            jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
              ?.lookupMethod(setterSignature, ElementFilter::methodsIn)
              ?: method,
            knownIsOverride = getterData?.isOverride,
          )
            ?: return@let MethodData.SYNTHETIC
        }

        val annotations = mutableListOf<AnnotationSpec>()
        if (property.flags.hasAnnotations) {
          property.syntheticMethodForAnnotations?.let { annotationsHolderSignature ->
            val method = typeElement.lookupMethod(annotationsHolderSignature, ElementFilter::methodsIn)
              ?: return@let MethodData.SYNTHETIC
            annotations += method.annotationSpecs()
              // Cover for https://github.com/square/kotlinpoet/issues/1046
              .filterNot { it.typeName == JAVA_DEPRECATED }
          }
        }

        // If a field is static in a companion object, remove the modifier and add the annotation
        // directly on the top level. Otherwise this will generate `@field:JvmStatic`, which is
        // not legal
        var finalFieldData = fieldData
        fieldData?.jvmModifiers?.let { modifiers ->
          if (isCompanionObject && JvmFieldModifier.STATIC in modifiers) {
            finalFieldData = fieldData.copy(
              jvmModifiers = fieldData.jvmModifiers
                .filterNotTo(LinkedHashSet()) { it == JvmFieldModifier.STATIC },
            )
            annotations += AnnotationSpec.builder(
              JVM_STATIC,
            ).build()
          }
        }

        PropertyData(
          annotations = annotations,
          fieldData = finalFieldData,
          getterData = getterData,
          setterData = setterData,
          isJvmField = isJvmField,
        )
      }

    val methodData = declarationContainer.functions
      .associateWithTo(TreeMap(KM_FUNCTION_COMPARATOR)) { kmFunction ->
        val signature = kmFunction.signature
        if (signature != null) {
          val method = typeElement.lookupMethod(signature, ElementFilter::methodsIn)
          method?.methodData(
            typeElement = typeElement,
            hasAnnotations = kmFunction.flags.hasAnnotations,
            jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
              ?.lookupMethod(signature, ElementFilter::methodsIn)
              ?: method,
          )
            ?: return@associateWithTo MethodData.SYNTHETIC
        } else {
          MethodData.EMPTY
        }
      }

    when (declarationContainer) {
      is KmClass -> {
        val constructorData = declarationContainer.constructors
          .associateWithTo(TreeMap(KM_CONSTRUCTOR_COMPARATOR)) { kmConstructor ->
            if (declarationContainer.isAnnotation || declarationContainer.isValue) {
              //
              // Annotations are interfaces in bytecode, but kotlin metadata will still report a
              // constructor signature
              //
              // Inline classes have no constructors at runtime
              //
              return@associateWithTo ConstructorData.EMPTY
            }
            val signature = kmConstructor.signature
            if (signature != null) {
              val constructor = typeElement.lookupMethod(signature, ElementFilter::constructorsIn)
                ?: return@associateWithTo ConstructorData.EMPTY
              ConstructorData(
                annotations = if (kmConstructor.flags.hasAnnotations) {
                  constructor.annotationSpecs()
                } else {
                  emptyList()
                },
                parameterAnnotations = constructor.parameters.indexedAnnotationSpecs(),
                isSynthetic = false,
                jvmModifiers = constructor.jvmModifiers(),
                exceptions = constructor.exceptionTypeNames(),
              )
            } else {
              ConstructorData.EMPTY
            }
          }
        return ClassData(
          declarationContainer = declarationContainer,
          className = className,
          annotations = if (declarationContainer.flags.hasAnnotations) {
            ClassInspectorUtil.createAnnotations {
              addAll(typeElement.annotationMirrors.map { AnnotationSpec.get(it) })
            }
          } else {
            emptyList()
          },
          properties = propertyData,
          constructors = constructorData,
          methods = methodData,
        )
      }
      is KmPackage -> {
        // There's no flag for checking if there are annotations, so we just eagerly check in this
        // case. All annotations on this class are file: site targets in source. This includes
        // @JvmName.
        var jvmName: String? = null
        val fileAnnotations = ClassInspectorUtil.createAnnotations(FILE) {
          addAll(
            typeElement.annotationMirrors.map {
              if (it.annotationType == jvmNameType) {
                val nameValue = requireNotNull(it.elementValues[jvmNameName]) {
                  "No name property found on $it"
                }
                jvmName = nameValue.value as String
              }
              @Suppress("DEPRECATION")
              AnnotationSpec.get(it)
            },
          )
        }
        return FileData(
          declarationContainer = declarationContainer,
          annotations = fileAnnotations,
          properties = propertyData,
          methods = methodData,
          className = className,
          jvmName = jvmName,
        )
      }
      else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
    }
  }

  private fun List<VariableElement>.indexedAnnotationSpecs(): Map<Int, Collection<AnnotationSpec>> {
    return withIndex().associate { (index, parameter) ->
      index to ClassInspectorUtil.createAnnotations { addAll(parameter.annotationSpecs()) }
    }
  }

  private fun ExecutableElement.methodData(
    typeElement: TypeElement,
    hasAnnotations: Boolean,
    jvmInformationMethod: ExecutableElement = this,
    knownIsOverride: Boolean? = null,
  ): MethodData {
    return MethodData(
      annotations = if (hasAnnotations) annotationSpecs() else emptyList(),
      parameterAnnotations = parameters.indexedAnnotationSpecs(),
      isSynthetic = false,
      jvmModifiers = jvmInformationMethod.jvmModifiers(),
      isOverride = knownIsOverride ?: isOverriddenIn(typeElement),
      exceptions = exceptionTypeNames(),
    )
  }

  public companion object {
    /** @return an [Elements]-based implementation of [ClassInspector]. */
    @JvmStatic
    @KotlinPoetMetadataPreview
    public fun create(elements: Elements, types: Types): ClassInspector {
      return ElementsClassInspector(elements, types)
    }

    private val JVM_STATIC = JvmStatic::class.asClassName()
  }
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.classinspectors

import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.NestingKind
import javax.lang.model.element.QualifiedNameable
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.lang.model.type.ArrayType
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.ErrorType
import javax.lang.model.type.ExecutableType
import javax.lang.model.type.IntersectionType
import javax.lang.model.type.NoType
import javax.lang.model.type.NullType
import javax.lang.model.type.PrimitiveType
import javax.lang.model.type.TypeKind.BOOLEAN
import javax.lang.model.type.TypeKind.BYTE
import javax.lang.model.type.TypeKind.CHAR
import javax.lang.model.type.TypeKind.DOUBLE
import javax.lang.model.type.TypeKind.FLOAT
import javax.lang.model.type.TypeKind.INT
import javax.lang.model.type.TypeKind.LONG
import javax.lang.model.type.TypeKind.SHORT
import javax.lang.model.type.TypeMirror
import javax.lang.model.type.TypeVariable
import javax.lang.model.type.UnionType
import javax.lang.model.type.WildcardType
import javax.lang.model.util.AbstractTypeVisitor8
import javax.lang.model.util.Types
import kotlinx.metadata.jvm.JvmFieldSignature
import kotlinx.metadata.jvm.JvmMethodSignature

/*
 * Adapted from
 * - https://github.com/Takhion/kotlin-metadata/blob/e6de126575ad6ca10b093129b7c30d000c9b0c37/lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/jvm/JvmDescriptorUtils.kt
 * - https://github.com/Takhion/kotlin-metadata/pull/13
 */

/**
 * For reference, see the [JVM specification, section 4.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2).
 *
 * @return the name of this [Element] in its "internal form".
 */
internal val Element.internalName: String
  get() = when (this) {
    is TypeElement -> {
      when (nestingKind) {
        NestingKind.TOP_LEVEL ->
          qualifiedName.toString().replace('.', '/')
        NestingKind.MEMBER ->
          enclosingElement.internalName + "$" + simpleName
        NestingKind.LOCAL, NestingKind.ANONYMOUS ->
          error("Unsupported nesting $nestingKind")
        null ->
          error("Unsupported, nestingKind == null")
      }
    }
    is QualifiedNameable -> qualifiedName.toString().replace('.', '/')
    else -> simpleName.toString()
  }

/**
 * @return the "field descriptor" of this type.
 * @see [JvmDescriptorTypeVisitor]
 */
@Suppress("unused")
internal val NoType.descriptor: String
  get() = "V"

/**
 * @return the "field descriptor" of this type.
 * @see [JvmDescriptorTypeVisitor]
 */
internal val DeclaredType.descriptor: String
  get() = "L" + asElement().internalName + ";"

/**
 * @return the "field descriptor" of this type.
 * @see [JvmDescriptorTypeVisitor]
 */
internal val PrimitiveType.descriptor: String
  get() = when (this.kind) {
    BYTE -> "B"
    CHAR -> "C"
    DOUBLE -> "D"
    FLOAT -> "F"
    INT -> "I"
    LONG -> "J"
    SHORT -> "S"
    BOOLEAN -> "Z"
    else -> error("Unknown primitive type $this")
  }

/**
 * @see [JvmDescriptorTypeVisitor]
 */
internal fun TypeMirror.descriptor(types: Types): String =
  accept(JvmDescriptorTypeVisitor, types)

/**
 * @return the "field descriptor" of this type.
 * @see [JvmDescriptorTypeVisitor]
 */
internal fun WildcardType.descriptor(types: Types): String =
  types.erasure(this).descriptor(types)

/**
 * @return the "field descriptor" of this type.
 * @see [JvmDescriptorTypeVisitor]
 */
internal fun TypeVariable.descriptor(types: Types): String =
  types.erasure(this).descriptor(types)

/**
 * @return the "field descriptor" of this type.
 * @see [JvmDescriptorTypeVisitor]
 */
internal fun ArrayType.descriptor(types: Types): String =
  "[" + componentType.descriptor(types)

/**
 * @return the "method descriptor" of this type.
 * @see [JvmDescriptorTypeVisitor]
 */
internal fun ExecutableType.descriptor(types: Types): String {
  val parameterDescriptors = parameterTypes.joinToString(separator = "") { it.descriptor(types) }
  val returnDescriptor = returnType.descriptor(types)
  return "($parameterDescriptors)$returnDescriptor"
}

/**
 * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`.
 *
 * Useful for comparing with [JvmMethodSignature].
 *
 * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
 */
internal fun ExecutableElement.jvmMethodSignature(types: Types): String {
  return "$simpleName${asType().descriptor(types)}"
}

/**
 * Returns the JVM signature in the form "$Name:$FieldDescriptor", for example: `"value:Ljava/lang/String;"`.
 *
 * Useful for comparing with [JvmFieldSignature].
 *
 * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
 */
internal fun VariableElement.jvmFieldSignature(types: Types): String {
  return "$simpleName:${asType().descriptor(types)}"
}

/**
 * When applied over a type, it returns either:
 * - a "field descriptor", for example: `Ljava/lang/Object;`
 * - a "method descriptor", for example: `(Ljava/lang/Object;)Z`
 *
 * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
 */
internal object JvmDescriptorTypeVisitor : AbstractTypeVisitor8<String, Types>() {
  override fun visitNoType(t: NoType, types: Types): String = t.descriptor
  override fun visitDeclared(t: DeclaredType, types: Types): String = t.descriptor
  override fun visitPrimitive(t: PrimitiveType, types: Types): String = t.descriptor

  override fun visitArray(t: ArrayType, types: Types): String = t.descriptor(types)
  override fun visitWildcard(t: WildcardType, types: Types): String = t.descriptor(types)
  override fun visitExecutable(t: ExecutableType, types: Types): String = t.descriptor(types)
  override fun visitTypeVariable(t: TypeVariable, types: Types): String = t.descriptor(types)

  override fun visitNull(t: NullType, types: Types): String = visitUnknown(t, types)

  override fun visitError(t: ErrorType, types: Types): String = visitUnknown(t, types)

  override fun visitUnion(t: UnionType, types: Types): String = visitUnknown(t, types)

  override fun visitIntersection(t: IntersectionType, types: Types): String = visitUnknown(t, types)

  override fun visitUnknown(t: TypeMirror, types: Types): String = error("Unsupported type $t")
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.classinspectors

/**
 * Simple `Optional` implementation for use in collections that don't allow `null` values.
 */
internal data class Optional<out T : Any>(val nullableValue: T?)

internal fun <T : Any> T?.toOptional(): Optional<T> = Optional(
  this,
)

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.classinspectors

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.DelicateKotlinPoetApi
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JAVA_DEPRECATED
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.filterOutNullabilityAnnotations
import com.squareup.kotlinpoet.metadata.hasAnnotations
import com.squareup.kotlinpoet.metadata.hasConstant
import com.squareup.kotlinpoet.metadata.isAnnotation
import com.squareup.kotlinpoet.metadata.isCompanionObject
import com.squareup.kotlinpoet.metadata.isConst
import com.squareup.kotlinpoet.metadata.isDeclaration
import com.squareup.kotlinpoet.metadata.isSynthesized
import com.squareup.kotlinpoet.metadata.isValue
import com.squareup.kotlinpoet.metadata.readKotlinClassMetadata
import com.squareup.kotlinpoet.metadata.specs.ClassData
import com.squareup.kotlinpoet.metadata.specs.ClassInspector
import com.squareup.kotlinpoet.metadata.specs.ConstructorData
import com.squareup.kotlinpoet.metadata.specs.ContainerData
import com.squareup.kotlinpoet.metadata.specs.EnumEntryData
import com.squareup.kotlinpoet.metadata.specs.FieldData
import com.squareup.kotlinpoet.metadata.specs.FileData
import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier
import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.TRANSIENT
import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.VOLATILE
import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier
import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.STATIC
import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.SYNCHRONIZED
import com.squareup.kotlinpoet.metadata.specs.KM_CONSTRUCTOR_COMPARATOR
import com.squareup.kotlinpoet.metadata.specs.KM_FUNCTION_COMPARATOR
import com.squareup.kotlinpoet.metadata.specs.KM_PROPERTY_COMPARATOR
import com.squareup.kotlinpoet.metadata.specs.MethodData
import com.squareup.kotlinpoet.metadata.specs.PropertyData
import com.squareup.kotlinpoet.metadata.toKmClass
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.lang.reflect.Parameter
import java.util.TreeMap
import java.util.concurrent.ConcurrentHashMap
import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmDeclarationContainer
import kotlinx.metadata.KmPackage
import kotlinx.metadata.jvm.JvmFieldSignature
import kotlinx.metadata.jvm.JvmMethodSignature
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.fieldSignature
import kotlinx.metadata.jvm.getterSignature
import kotlinx.metadata.jvm.setterSignature
import kotlinx.metadata.jvm.signature
import kotlinx.metadata.jvm.syntheticMethodForAnnotations

@KotlinPoetMetadataPreview
public class ReflectiveClassInspector private constructor(
  private val classLoader: ClassLoader?,
) : ClassInspector {

  private val classCache = ConcurrentHashMap<ClassName, Optional<Class<*>>>()
  private val methodCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Method>>()
  private val constructorCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Constructor<*>>>()
  private val fieldCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Field>>()
  private val enumCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Enum<*>>>()

  private fun lookupClass(className: ClassName): Class<*>? {
    return classCache.getOrPut(className) {
      try {
        if (classLoader == null) {
          Class.forName(className.reflectionName())
        } else {
          Class.forName(className.reflectionName(), true, classLoader)
        }
      } catch (e: ClassNotFoundException) {
        null
      }.toOptional()
    }.nullableValue
  }

  override val supportsNonRuntimeRetainedAnnotations: Boolean = false

  override fun declarationContainerFor(className: ClassName): KmDeclarationContainer {
    val clazz = lookupClass(className)
      ?: error("No type element found for: $className.")

    val metadata = clazz.getAnnotation(Metadata::class.java)
    return when (val kotlinClassMetadata = metadata.readKotlinClassMetadata()) {
      is KotlinClassMetadata.Class -> kotlinClassMetadata.toKmClass()
      is KotlinClassMetadata.FileFacade -> kotlinClassMetadata.toKmPackage()
      else -> TODO("Not implemented yet: ${kotlinClassMetadata.javaClass.simpleName}")
    }
  }

  override fun isInterface(className: ClassName): Boolean {
    if (className in ClassInspectorUtil.KOTLIN_INTRINSIC_INTERFACES) {
      return true
    }
    return lookupClass(className)?.isInterface ?: false
  }

  private fun Class<*>.lookupField(fieldSignature: JvmFieldSignature): Field? {
    return try {
      val signatureString = fieldSignature.asString()
      fieldCache.getOrPut(this to signatureString) {
        declaredFields
          .asSequence()
          .onEach { it.isAccessible = true }
          .find { signatureString == it.jvmFieldSignature }.toOptional()
      }.nullableValue
    } catch (e: ClassNotFoundException) {
      null
    }
  }

  private fun Class<*>.lookupMethod(
    methodSignature: JvmMethodSignature,
  ): Method? {
    val signatureString = methodSignature.asString()
    return methodCache.getOrPut(this to signatureString) {
      declaredMethods
        .asSequence()
        .onEach { it.isAccessible = true }
        .find { signatureString == it.jvmMethodSignature }.toOptional()
    }.nullableValue
  }

  private fun Class<*>.lookupConstructor(
    constructorSignature: JvmMethodSignature,
  ): Constructor<*>? {
    val signatureString = constructorSignature.asString()
    return constructorCache.getOrPut(this to signatureString) {
      declaredConstructors
        .asSequence()
        .onEach { it.isAccessible = true }
        .find { signatureString == it.jvmMethodSignature }.toOptional()
    }.nullableValue
  }

  private fun Field.jvmModifiers(): Set<JvmFieldModifier> {
    return mutableSetOf<JvmFieldModifier>().apply {
      if (Modifier.isTransient(modifiers)) {
        add(TRANSIENT)
      }
      if (Modifier.isVolatile(modifiers)) {
        add(VOLATILE)
      }
      if (Modifier.isStatic(modifiers)) {
        add(JvmFieldModifier.STATIC)
      }
    }
  }

  @OptIn(DelicateKotlinPoetApi::class)
  private fun Field.annotationSpecs(): List<AnnotationSpec> {
    return filterOutNullabilityAnnotations(
      declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) },
    )
  }

  @OptIn(DelicateKotlinPoetApi::class)
  private fun Constructor<*>.annotationSpecs(): List<AnnotationSpec> {
    return filterOutNullabilityAnnotations(
      declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, true) },
    )
  }

  private fun Method.jvmModifiers(): Set<JvmMethodModifier> {
    return methodJvmModifiers(modifiers, isDefault)
  }

  private fun Constructor<*>.jvmModifiers(): Set<JvmMethodModifier> {
    return methodJvmModifiers(modifiers, false)
  }

  private fun methodJvmModifiers(modifiers: Int, isDefault: Boolean): Set<JvmMethodModifier> {
    val jvmMethodModifiers = mutableSetOf<JvmMethodModifier>()
    if (Modifier.isSynchronized(modifiers)) {
      jvmMethodModifiers += SYNCHRONIZED
    }
    if (Modifier.isStatic(modifiers)) {
      jvmMethodModifiers += STATIC
    }
    if (isDefault) {
      jvmMethodModifiers += DEFAULT
    }
    return jvmMethodModifiers
  }

  @OptIn(DelicateKotlinPoetApi::class)
  private fun Method.annotationSpecs(): List<AnnotationSpec> {
    return filterOutNullabilityAnnotations(
      declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) },
    )
  }

  @OptIn(DelicateKotlinPoetApi::class)
  private fun Parameter.annotationSpecs(): List<AnnotationSpec> {
    return filterOutNullabilityAnnotations(
      declaredAnnotations.map { AnnotationSpec.get(it, includeDefaultValues = true) },
    )
  }

  private fun Method.exceptionTypeNames(): List<TypeName> {
    return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() }
  }

  private fun Constructor<*>.exceptionTypeNames(): List<TypeName> {
    return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() }
  }

  override fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData {
    val clazz = lookupClass(enumClassName)
      ?: error("No class found for: $enumClassName.")
    check(clazz.isEnum) {
      "Class must be an enum but isn't: $clazz"
    }
    val enumEntry = enumCache.getOrPut(clazz to memberName) {
      clazz.enumConstants
        .asSequence()
        .map { it as Enum<*> }
        .find { it.name == memberName }
        .toOptional()
    }.nullableValue
    checkNotNull(enumEntry) {
      "Could not find $memberName on $enumClassName"
    }
    return EnumEntryData(
      declarationContainer = if (enumEntry.javaClass == clazz) {
        // For simple enums with no class bodies, the entry class will be the same as the original
        // class.
        null
      } else {
        enumEntry.javaClass.getAnnotation(Metadata::class.java)?.toKmClass()
      },
      annotations = clazz.getField(enumEntry.name).annotationSpecs(),
    )
  }

  private fun Field.constantValue(): CodeBlock? {
    if (!Modifier.isStatic(modifiers)) {
      return null
    }
    return get(null) // Constant means we can do a static get on it.
      .let(ClassInspectorUtil::codeLiteralOf)
  }

  private fun JvmMethodSignature.isOverriddenIn(clazz: Class<*>): Boolean {
    val signatureString = asString()
    val classPackage = clazz.`package`.name
    val interfaceMethods = clazz.interfaces.asSequence()
      .flatMap { it.methods.asSequence() }
    val superClassMethods = clazz.superclass?.methods.orEmpty().asSequence()
    return interfaceMethods.plus(superClassMethods)
      .filterNot { Modifier.isFinal(it.modifiers) }
      .filterNot { Modifier.isStatic(it.modifiers) }
      .filterNot { Modifier.isPrivate(it.modifiers) }
      .filter {
        Modifier.isPublic(it.modifiers) ||
          Modifier.isProtected(it.modifiers) ||
          // Package private
          it.declaringClass.`package`.name == classPackage
      }
      .map { it.jvmMethodSignature }
      .any { it == signatureString }
  }

  override fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean {
    return lookupClass(className)?.lookupMethod(methodSignature) != null
  }

  @OptIn(DelicateKotlinPoetApi::class)
  override fun containerData(
    declarationContainer: KmDeclarationContainer,
    className: ClassName,
    parentClassName: ClassName?,
  ): ContainerData {
    val targetClass = lookupClass(className) ?: error("No class found for: $className.")
    val isCompanionObject: Boolean = when (declarationContainer) {
      is KmClass -> {
        declarationContainer.isCompanionObject
      }
      is KmPackage -> {
        false
      }
      else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
    }

    // Should only be called if parentName has been null-checked
    val classIfCompanion by lazy(NONE) {
      if (isCompanionObject && parentClassName != null) {
        lookupClass(parentClassName)
          ?: error("No class found for: $parentClassName.")
      } else {
        targetClass
      }
    }

    val propertyData = declarationContainer.properties
      .asSequence()
      .filter { it.isDeclaration }
      .filterNot { it.isSynthesized }
      .associateWithTo(TreeMap(KM_PROPERTY_COMPARATOR)) { property ->
        val isJvmField = ClassInspectorUtil.computeIsJvmField(
          property = property,
          classInspector = this,
          isCompanionObject = isCompanionObject,
          hasGetter = property.getterSignature != null,
          hasSetter = property.setterSignature != null,
          hasField = property.fieldSignature != null,
        )

        val fieldData = property.fieldSignature?.let { fieldSignature ->
          // Check the field in the parent first. For const/static/jvmField elements, these only
          // exist in the parent and we want to check that if necessary to avoid looking up a
          // non-existent field in the companion.
          val parentModifiers = if (isCompanionObject && parentClassName != null) {
            classIfCompanion.lookupField(fieldSignature)?.jvmModifiers().orEmpty()
          } else {
            emptySet()
          }

          val isStatic = JvmFieldModifier.STATIC in parentModifiers

          // TODO we looked up field once, let's reuse it
          val classForOriginalField = targetClass.takeUnless {
            isCompanionObject &&
              (property.isConst || isJvmField || isStatic)
          } ?: classIfCompanion

          val field = classForOriginalField.lookupField(fieldSignature)
            ?: error("No field $fieldSignature found in $classForOriginalField.")
          val constant = if (property.hasConstant) {
            val fieldWithConstant = classIfCompanion.takeIf { it != targetClass }?.let {
              if (it.isInterface) {
                field
              } else {
                // const properties are relocated to the enclosing class
                it.lookupField(fieldSignature)
                  ?: error("No field $fieldSignature found in $it.")
              }
            } ?: field
            fieldWithConstant.constantValue()
          } else {
            null
          }

          val jvmModifiers = field.jvmModifiers() + parentModifiers

          // For static, const, or JvmField fields in a companion object, the companion
          // object's field is marked as synthetic to hide it from Java, but in this case
          // it's a false positive for this check in kotlin.
          val isSynthetic = field.isSynthetic &&
            !(
              isCompanionObject &&
                (property.isConst || isJvmField || JvmFieldModifier.STATIC in jvmModifiers)
              )

          FieldData(
            annotations = field.annotationSpecs(),
            isSynthetic = isSynthetic,
            jvmModifiers = jvmModifiers.filterNotTo(mutableSetOf()) {
              // JvmField companion objects don't need JvmStatic, it's implicit
              isCompanionObject && isJvmField && it == JvmFieldModifier.STATIC
            },
            constant = constant,
          )
        }

        val getterData = property.getterSignature?.let { getterSignature ->
          val method = classIfCompanion.lookupMethod(getterSignature)
          method?.methodData(
            clazz = targetClass,
            signature = getterSignature,
            hasAnnotations = property.getterFlags.hasAnnotations,
            jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }
              ?.lookupMethod(getterSignature) ?: method,
          )
            ?: error("No getter method $getterSignature found in $classIfCompanion.")
        }

        val setterData = property.setterSignature?.let { setterSignature ->
          val method = classIfCompanion.lookupMethod(setterSignature)
          method?.methodData(
            clazz = targetClass,
            signature = setterSignature,
            hasAnnotations = property.setterFlags.hasAnnotations,
            jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }
              ?.lookupMethod(setterSignature) ?: method,
            knownIsOverride = getterData?.isOverride,
          )
            ?: error("No setter method $setterSignature found in $classIfCompanion.")
        }

        val annotations = mutableListOf<AnnotationSpec>()
        if (property.flags.hasAnnotations) {
          property.syntheticMethodForAnnotations?.let { annotationsHolderSignature ->
            targetClass.lookupMethod(annotationsHolderSignature)?.let { method ->
              annotations += method.annotationSpecs()
                // Cover for https://github.com/square/kotlinpoet/issues/1046
                .filterNot { it.typeName == JAVA_DEPRECATED }
            }
          }
        }

        PropertyData(
          annotations = annotations,
          fieldData = fieldData,
          getterData = getterData,
          setterData = setterData,
          isJvmField = isJvmField,
        )
      }

    val methodData = declarationContainer.functions
      .associateWithTo(TreeMap(KM_FUNCTION_COMPARATOR)) { kmFunction ->
        val signature = kmFunction.signature
        if (signature != null) {
          val method = targetClass.lookupMethod(signature)
          method?.methodData(
            clazz = targetClass,
            signature = signature,
            hasAnnotations = kmFunction.flags.hasAnnotations,
            jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }?.lookupMethod(signature)
              ?: method,
          )
            ?: error("No method $signature found in $targetClass.")
        } else {
          MethodData.EMPTY
        }
      }

    when (declarationContainer) {
      is KmClass -> {
        val classAnnotations = if (declarationContainer.flags.hasAnnotations) {
          ClassInspectorUtil.createAnnotations {
            addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) })
          }
        } else {
          emptyList()
        }
        val constructorData = declarationContainer.constructors
          .associateWithTo(TreeMap(KM_CONSTRUCTOR_COMPARATOR)) { kmConstructor ->
            if (declarationContainer.isAnnotation || declarationContainer.isValue) {
              //
              // Annotations are interfaces in reflection, but kotlin metadata will still report a
              // constructor signature
              //
              // Inline classes have no constructors at runtime
              //
              return@associateWithTo ConstructorData.EMPTY
            }
            val signature = kmConstructor.signature
            if (signature != null) {
              val constructor = targetClass.lookupConstructor(signature)
                ?: error("No constructor $signature found in $targetClass.")
              ConstructorData(
                annotations = if (kmConstructor.flags.hasAnnotations) {
                  constructor.annotationSpecs()
                } else {
                  emptyList()
                },
                parameterAnnotations = constructor.parameters.indexedAnnotationSpecs(),
                isSynthetic = constructor.isSynthetic,
                jvmModifiers = constructor.jvmModifiers(),
                exceptions = constructor.exceptionTypeNames(),
              )
            } else {
              ConstructorData.EMPTY
            }
          }
        return ClassData(
          declarationContainer = declarationContainer,
          className = className,
          annotations = classAnnotations,
          properties = propertyData,
          constructors = constructorData,
          methods = methodData,
        )
      }
      is KmPackage -> {
        // There's no flag for checking if there are annotations, so we just eagerly check in this
        // case. All annotations on this class are file: site targets in source. This does not
        // include @JvmName since it does not have RUNTIME retention. In practice this doesn't
        // really matter, but it does mean we can't know for certain if the file should be called
        // FooKt.kt or Foo.kt.
        val fileAnnotations = ClassInspectorUtil.createAnnotations(FILE) {
          addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) })
        }
        return FileData(
          declarationContainer = declarationContainer,
          annotations = fileAnnotations,
          properties = propertyData,
          methods = methodData,
          className = className,
        )
      }
      else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
    }
  }

  private fun Array<Parameter>.indexedAnnotationSpecs(): Map<Int, Collection<AnnotationSpec>> {
    return withIndex().associate { (index, parameter) ->
      index to ClassInspectorUtil.createAnnotations { addAll(parameter.annotationSpecs()) }
    }
  }

  private fun Method.methodData(
    clazz: Class<*>,
    signature: JvmMethodSignature,
    hasAnnotations: Boolean,
    jvmInformationMethod: Method = this,
    knownIsOverride: Boolean? = null,
  ): MethodData {
    return MethodData(
      annotations = if (hasAnnotations) annotationSpecs() else emptyList(),
      parameterAnnotations = parameters.indexedAnnotationSpecs(),
      isSynthetic = isSynthetic,
      jvmModifiers = jvmInformationMethod.jvmModifiers(),
      isOverride = knownIsOverride ?: signature.isOverriddenIn(clazz),
      exceptions = exceptionTypeNames(),
    )
  }

  public companion object {
    @JvmStatic
    @KotlinPoetMetadataPreview
    public fun create(classLoader: ClassLoader? = null): ClassInspector {
      return ReflectiveClassInspector(classLoader)
    }

    private val Class<*>.descriptor: String
      get() {
        return when {
          isPrimitive -> when (kotlin) {
            Byte::class -> "B"
            Char::class -> "C"
            Double::class -> "D"
            Float::class -> "F"
            Int::class -> "I"
            Long::class -> "J"
            Short::class -> "S"
            Boolean::class -> "Z"
            Void::class -> "V"
            else -> throw RuntimeException("Unrecognized primitive $this")
          }
          isArray -> name.replace('.', '/')
          else -> "L$name;".replace('.', '/')
        }
      }

    private val Method.descriptor: String
      get() = parameterTypes.joinToString(
        separator = "",
        prefix = "(",
        postfix = ")${returnType.descriptor}",
      ) { it.descriptor }

    /**
     * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`.
     *
     * Useful for comparing with [JvmMethodSignature].
     *
     * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
     */
    private val Method.jvmMethodSignature: String get() = "$name$descriptor"

    private val Constructor<*>.descriptor: String
      get() = parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")V") { it.descriptor }

    /**
     * Returns the JVM signature in the form "<init>$MethodDescriptor", for example: `"<init>(Ljava/lang/Object;)V")`.
     *
     * Useful for comparing with [JvmMethodSignature].
     *
     * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
     */
    private val Constructor<*>.jvmMethodSignature: String get() = "<init>$descriptor"

    /**
     * Returns the JVM signature in the form "$Name:$FieldDescriptor", for example: `"value:Ljava/lang/String;"`.
     *
     * Useful for comparing with [JvmFieldSignature].
     *
     * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
     */
    private val Field.jvmFieldSignature: String get() = "$name:${type.descriptor}"
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmDeclarationContainer
import kotlinx.metadata.jvm.JvmMethodSignature

/** A basic interface for looking up JVM information about a given Class. */
@KotlinPoetMetadataPreview
public interface ClassInspector {

  /**
   * Indicates if this [ClassInspector] supports [AnnotationRetention.RUNTIME]-retained annotations.
   * This is used to indicate if manual inference of certain non-RUNTIME-retained annotations should
   * be done, such as [JvmName].
   */
  public val supportsNonRuntimeRetainedAnnotations: Boolean

  /**
   * Creates a new [ContainerData] instance for a given [declarationContainer].
   *
   * @param declarationContainer the source [KmDeclarationContainer] to read from.
   * @param className the [ClassName] of the target class to to read from.
   * @param parentClassName the parent [ClassName] name if [declarationContainer] is nested, inner,
   * or is a companion object.
   */
  public fun containerData(
    declarationContainer: KmDeclarationContainer,
    className: ClassName,
    parentClassName: ClassName?,
  ): ContainerData

  /**
   * Looks up other declaration containers, such as for nested members. Note that this class would
   * always be Kotlin, so Metadata can be relied on for this.
   *
   * @param className The [ClassName] representation of the class.
   * @return the read [KmDeclarationContainer] from its metadata. If no class or facade
   *         file was found, this should throw an exception.
   */
  public fun declarationContainerFor(className: ClassName): KmDeclarationContainer

  /**
   * Looks up a class and returns whether or not it is an interface. Note that this class can be
   * Java or Kotlin, so Metadata should not be relied on for this.
   *
   * @param className The [ClassName] representation of the class.
   * @return whether or not it is an interface.
   */
  public fun isInterface(className: ClassName): Boolean

  /**
   * Looks up the enum entry on a given enum given its member name.
   *
   * @param enumClassName The [ClassName] representation of the enum class.
   * @param memberName The simple member name.
   * @return the [EnumEntryData]
   */
  public fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData

  /**
   * Looks up if a given [methodSignature] within [className] exists.
   *
   * @param className The [ClassName] representation of the class.
   * @param methodSignature The method signature to check.
   * @return whether or not the method exists.
   */
  public fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean
}

/**
 * Creates a new [ContainerData] instance for a given [className].
 *
 * @param className the [ClassName] of the target class to to read from.
 * @param parentClassName the parent [ClassName] name if [className] is nested, inner, or is a
 *        companion object.
 */
@KotlinPoetMetadataPreview
public fun ClassInspector.containerData(
  className: ClassName,
  parentClassName: ClassName?,
): ContainerData {
  return containerData(declarationContainerFor(className), className, parentClassName)
}

/**
 * Looks up other classes, such as for nested members. Note that this class would always be
 * Kotlin, so Metadata can be relied on for this.
 *
 * @param className The [ClassName] representation of the class.
 * @return the read [KmClass] from its metadata. If no class was found, this should throw
 *         an exception.
 */
@KotlinPoetMetadataPreview
public fun ClassInspector.classFor(className: ClassName): KmClass {
  val container = declarationContainerFor(className)
  check(container is KmClass) {
    "Container is not a class! Was ${container.javaClass.simpleName}"
  }
  return container
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil

/**
 * Represents relevant information on a constructor used for [ClassInspector]. Should only be
 * associated with constructors of a [ClassData].
 *
 * @param annotations declared annotations on this constructor.
 * @property parameterAnnotations a mapping of parameter indices to annotations on them.
 * @property isSynthetic indicates if this constructor is synthetic or not.
 * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this constructor.
 * @property exceptions list of exceptions thrown by this constructor.
 */
@KotlinPoetMetadataPreview
public data class ConstructorData(
  private val annotations: List<AnnotationSpec>,
  val parameterAnnotations: Map<Int, Collection<AnnotationSpec>>,
  val isSynthetic: Boolean,
  val jvmModifiers: Set<JvmMethodModifier>,
  val exceptions: List<TypeName>,
) {

  /**
   * A collection of all annotations on this constructor, including any derived from [jvmModifiers],
   * [isSynthetic], and [exceptions].
   */
  val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations {
    addAll(annotations)
    if (isSynthetic) {
      add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
    }
    addAll(jvmModifiers.mapNotNull { it.annotationSpec() })
    exceptions.takeIf { it.isNotEmpty() }
      ?.let {
        add(ClassInspectorUtil.createThrowsSpec(it))
      }
  }

  public companion object {
    public val EMPTY: ConstructorData = ConstructorData(
      annotations = emptyList(),
      parameterAnnotations = emptyMap(),
      isSynthetic = false,
      jvmModifiers = emptySet(),
      exceptions = emptyList(),
    )
  }
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmConstructor
import kotlinx.metadata.KmDeclarationContainer
import kotlinx.metadata.KmFunction
import kotlinx.metadata.KmPackage
import kotlinx.metadata.KmProperty

/**
 * Represents relevant information on a declaration container used for [ClassInspector]. Can only
 * ever be applied on a Kotlin type (i.e. is annotated with [Metadata]).
 *
 * @property declarationContainer the [KmDeclarationContainer] as parsed from the class's
 *           [@Metadata][Metadata] annotation.
 * @property annotations declared annotations on this class.
 * @property properties the mapping of [declarationContainer]'s properties to parsed [PropertyData].
 * @property methods the mapping of [declarationContainer]'s methods to parsed [MethodData].
 */
@KotlinPoetMetadataPreview
public interface ContainerData {
  public val declarationContainer: KmDeclarationContainer
  public val annotations: Collection<AnnotationSpec>
  public val properties: Map<KmProperty, PropertyData>
  public val methods: Map<KmFunction, MethodData>
}

/**
 * Represents relevant information on a Kotlin class used for [ClassInspector]. Can only ever be
 * applied on a class and not file facades.
 *
 * @property declarationContainer the [KmClass] as parsed from the class's
 *           [@Metadata][Metadata] annotation.
 * @property className the KotlinPoet [ClassName] of the class.
 * @property constructors the mapping of [declarationContainer]'s constructors to parsed
 * [ConstructorData].
 */
@KotlinPoetMetadataPreview
public data class ClassData(
  override val declarationContainer: KmClass,
  val className: ClassName,
  override val annotations: Collection<AnnotationSpec>,
  override val properties: Map<KmProperty, PropertyData>,
  val constructors: Map<KmConstructor, ConstructorData>,
  override val methods: Map<KmFunction, MethodData>,
) : ContainerData

/**
 * Represents relevant information on a file facade used for [ClassInspector].
 *
 * @property declarationContainer the [KmClass] as parsed from the class's
 *           [@Metadata][Metadata] annotation.
 * @property className the KotlinPoet [ClassName] of the underlying facade class in JVM.
 * @property jvmName the `@JvmName` of the class or null if it does not have a custom name.
 *           Default will try to infer from the [className].
 */
@KotlinPoetMetadataPreview
public data class FileData(
  override val declarationContainer: KmPackage,
  override val annotations: Collection<AnnotationSpec>,
  override val properties: Map<KmProperty, PropertyData>,
  override val methods: Map<KmFunction, MethodData>,
  val className: ClassName,
  val jvmName: String? =
    if (!className.simpleName.endsWith("Kt")) className.simpleName else null,
) : ContainerData {

  /**
   * The file name of the container, defaults to [className]'s simple name + "Kt". If a [jvmName] is
   * specified, it will always defer to that.
   */
  val fileName: String = jvmName ?: className.simpleName.removeSuffix("Kt")
}

/**
 * Represents relevant information on a Kotlin enum entry.
 *
 * @property declarationContainer the [KmClass] as parsed from the entry's
 * [@Metadata][Metadata] annotation.
 * @property annotations the annotations for the entry
 */
@KotlinPoetMetadataPreview
public data class EnumEntryData(
  val declarationContainer: KmClass?,
  val annotations: Collection<AnnotationSpec>,
)

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil

/**
 * Represents relevant information on a field used for [ClassInspector]. Should only be
 * associated with a [PropertyData].
 *
 * @param annotations declared annotations on this field.
 * @property isSynthetic indicates if this field is synthetic or not.
 * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this field.
 * @property constant the constant value of this field, if available. Note that this is does not
 *           strictly imply that the associated property is `const`.
 */
@KotlinPoetMetadataPreview
public data class FieldData(
  private val annotations: List<AnnotationSpec>,
  val isSynthetic: Boolean,
  val jvmModifiers: Set<JvmFieldModifier>,
  val constant: CodeBlock?,
) {

  /**
   * A collection of all annotations on this method, including any derived from [jvmModifiers]
   * and [isSynthetic].
   */
  val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations(
    FIELD,
  ) {
    addAll(annotations)
    if (isSynthetic) {
      add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
    }
    addAll(jvmModifiers.mapNotNull(JvmFieldModifier::annotationSpec))
  }

  public companion object {
    public val SYNTHETIC: FieldData = FieldData(
      annotations = emptyList(),
      isSynthetic = true,
      jvmModifiers = emptySet(),
      constant = null,
    )
  }
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview

/** Modifiers that are annotations in Kotlin but modifier keywords in bytecode. */
@KotlinPoetMetadataPreview
public enum class JvmFieldModifier : JvmModifier {
  STATIC {
    override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
      JvmStatic::class.asClassName(),
    ).build()
  },
  TRANSIENT {
    override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
      Transient::class.asClassName(),
    ).build()
  },
  VOLATILE {
    override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
      Volatile::class.asClassName(),
    ).build()
  },
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview

/** Modifiers that are annotations or implicit in Kotlin but modifier keywords in bytecode. */
@KotlinPoetMetadataPreview
public enum class JvmMethodModifier : JvmModifier {
  STATIC {
    override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
      JvmStatic::class.asClassName(),
    ).build()
  },
  SYNCHRONIZED {
    override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
      Synchronized::class.asClassName(),
    ).build()
  },
  DEFAULT,
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview

/**
 * Represents a JVM modifier that is represented as an annotation in Kotlin but as a modifier in
 * bytecode. Examples include annotations such as [@JvmStatic][JvmStatic] or
 * [@JvmSynthetic][JvmSynthetic].
 *
 * This API is considered read-only and should not be implemented outside of KotlinPoet.
 */
@KotlinPoetMetadataPreview
public interface JvmModifier {
  public fun annotationSpec(): AnnotationSpec? {
    return null
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.STAR
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
import com.squareup.kotlinpoet.metadata.isNullable
import com.squareup.kotlinpoet.metadata.isPrimary
import com.squareup.kotlinpoet.metadata.isReified
import com.squareup.kotlinpoet.metadata.isSuspend
import com.squareup.kotlinpoet.tags.TypeAliasTag
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmClassifier.Class
import kotlinx.metadata.KmClassifier.TypeAlias
import kotlinx.metadata.KmClassifier.TypeParameter
import kotlinx.metadata.KmConstructor
import kotlinx.metadata.KmFlexibleTypeUpperBound
import kotlinx.metadata.KmFunction
import kotlinx.metadata.KmProperty
import kotlinx.metadata.KmType
import kotlinx.metadata.KmTypeParameter
import kotlinx.metadata.KmTypeProjection
import kotlinx.metadata.KmVariance
import kotlinx.metadata.KmVariance.IN
import kotlinx.metadata.KmVariance.INVARIANT
import kotlinx.metadata.KmVariance.OUT
import kotlinx.metadata.jvm.annotations
import kotlinx.metadata.jvm.signature

/**
 * `true` if this is an extension type (i.e. String.() -> Unit vs (String) -> Unit).
 *
 * See details: https://discuss.kotlinlang.org/t/announcing-kotlinx-metadata-jvm-library-for-reading-modifying-metadata-of-kotlin-jvm-class-files/7980/27
 */
public val KmType.isExtensionType: Boolean get() {
  return annotations.any { it.className == "kotlin/ExtensionFunctionType" }
}

@KotlinPoetMetadataPreview
internal val KmClass.primaryConstructor: KmConstructor?
  get() = constructors.find { it.isPrimary }

internal fun KmVariance.toKModifier(): KModifier? {
  return when (this) {
    IN -> KModifier.IN
    OUT -> KModifier.OUT
    INVARIANT -> null
  }
}

@KotlinPoetMetadataPreview
internal fun KmTypeProjection.toTypeName(
  typeParamResolver: TypeParameterResolver,
): TypeName {
  val typename = type?.toTypeName(typeParamResolver) ?: STAR
  return when (variance) {
    IN -> WildcardTypeName.consumerOf(typename)
    OUT -> WildcardTypeName.producerOf(typename)
    INVARIANT -> typename
    null -> STAR
  }
}

/**
 * Converts a given [KmType] into a KotlinPoet representation, attempting to give a correct
 * "source" representation. This includes converting [functions][kotlin.Function] and `suspend`
 * types to appropriate [lambda representations][LambdaTypeName].
 */
@KotlinPoetMetadataPreview
internal fun KmType.toTypeName(
  typeParamResolver: TypeParameterResolver,
): TypeName {
  val argumentList = arguments.map { it.toTypeName(typeParamResolver) }
  val type: TypeName = when (val valClassifier = classifier) {
    is TypeParameter -> {
      typeParamResolver[valClassifier.id]
    }
    is KmClassifier.Class -> {
      flexibleTypeUpperBound?.toTypeName(typeParamResolver)?.let { return it }
      outerType?.toTypeName(typeParamResolver)?.let { return it }
      var finalType: TypeName = ClassInspectorUtil.createClassName(valClassifier.name)
      if (argumentList.isNotEmpty()) {
        val finalTypeString = finalType.toString()
        if (finalTypeString.startsWith("kotlin.Function")) {
          // It's a lambda type!
          finalType = if (finalTypeString == "kotlin.FunctionN") {
            TODO("unclear how to express this one since it has arity")
          } else {
            val (parameters, returnType) = if (isSuspend) {
              // Coroutines always adds an `Any?` return type, but we kind of just want the
              // source representation, so we trick it here and ignore the last.
              argumentList.dropLast(2).toTypedArray() to argumentList.dropLast(1).last().let {
                // Coroutines makes these a `Continuation<T>` of the type, so we want the parameterized type
                check(it is ParameterizedTypeName)
                it.typeArguments[0]
              }
            } else {
              argumentList.dropLast(1).toTypedArray() to argumentList.last()
            }
            val lambdaType = if (isExtensionType) {
              // Extension function type! T.(). First parameter is actually the receiver.
              LambdaTypeName.get(
                receiver = parameters[0],
                parameters = parameters.drop(1).toTypedArray(),
                returnType = returnType,
              )
            } else {
              LambdaTypeName.get(
                receiver = null,
                parameters = parameters,
                returnType = returnType,
              )
            }
            lambdaType.copy(suspending = isSuspend)
          }
        } else {
          finalType = (finalType as ClassName).parameterizedBy(argumentList)
        }
      }
      finalType
    }
    is TypeAlias -> {
      ClassInspectorUtil.createClassName(valClassifier.name)
    }
  }

  val annotations = ClassInspectorUtil.createAnnotations {
    for (annotation in annotations) {
      add(annotation.toAnnotationSpec())
    }
  }.toList()
  val finalType = type.copy(nullable = isNullable, annotations = annotations)
  return abbreviatedType?.let {
    // This is actually an alias! The "abbreviated type" is the alias and how it's actually
    // represented in source. So instead - we'll return the abbreviated type but store the "real"
    // type in tags for reference.
    val abbreviatedTypeName = it.toTypeName(typeParamResolver)
    abbreviatedTypeName.copy(
      tags = mapOf(TypeAliasTag::class to TypeAliasTag(finalType)),
    )
  } ?: finalType
}

@KotlinPoetMetadataPreview
internal fun KmTypeParameter.toTypeVariableName(
  typeParamResolver: TypeParameterResolver,
): TypeVariableName {
  val finalVariance = variance.toKModifier()
  val typeVariableName = TypeVariableName(
    name = name,
    bounds = upperBounds.map { it.toTypeName(typeParamResolver) },
    variance = finalVariance,
  )
  val annotations = ClassInspectorUtil.createAnnotations {
    for (annotation in annotations) {
      add(annotation.toAnnotationSpec())
    }
  }.toList()
  return typeVariableName.copy(
    reified = isReified,
    tags = mapOf(KmTypeParameter::class to this),
    annotations = annotations,
  )
}

@KotlinPoetMetadataPreview
private fun KmFlexibleTypeUpperBound.toTypeName(
  typeParamResolver: TypeParameterResolver,
): TypeName {
  // TODO tag typeFlexibilityId somehow?
  return WildcardTypeName.producerOf(type.toTypeName(typeParamResolver))
}

internal interface TypeParameterResolver {
  val parametersMap: Map<Int, TypeVariableName>
  operator fun get(index: Int): TypeVariableName

  companion object {
    val EMPTY = object : TypeParameterResolver {
      override val parametersMap: Map<Int, TypeVariableName> = emptyMap()

      override fun get(index: Int): TypeVariableName = throw NoSuchElementException("No type parameters!")
    }
  }
}

@KotlinPoetMetadataPreview
internal fun List<KmTypeParameter>.toTypeParameterResolver(
  fallback: TypeParameterResolver? = null,
): TypeParameterResolver {
  val parametersMap = LinkedHashMap<Int, TypeVariableName>()
  val typeParamResolver = { id: Int ->
    parametersMap[id]
      ?: fallback?.get(id)
      ?: throw IllegalStateException("No type argument found for $id!")
  }

  val resolver = object : TypeParameterResolver {
    override val parametersMap: Map<Int, TypeVariableName> = parametersMap

    override operator fun get(index: Int): TypeVariableName = typeParamResolver(index)
  }

  // Fill the parametersMap. Need to do sequentially and allow for referencing previously defined params
  for (typeParam in this) {
    // Put the simple typevar in first, then it can be referenced in the full toTypeVariable()
    // replacement later that may add bounds referencing this.
    parametersMap[typeParam.id] = TypeVariableName(typeParam.name)
  }

  for (typeParam in this) {
    // Now replace it with the full version.
    parametersMap[typeParam.id] = typeParam.toTypeVariableName(resolver)
  }

  return resolver
}

internal val KM_PROPERTY_COMPARATOR = Comparator<KmProperty> { o1, o2 ->
  // No need to check fields, getters, etc as properties must have distinct names
  o1.name.compareTo(o2.name)
}

internal val KM_FUNCTION_COMPARATOR = Comparator<KmFunction> { o1, o2 ->
  var result = o1.name.compareTo(o2.name)
  if (result != 0) return@Comparator result

  val signature1 = o1.signature
  val signature2 = o2.signature
  if (signature1 != null && signature2 != null) {
    result = signature1.asString().compareTo(signature2.asString())
    if (result != 0) return@Comparator result
  }

  // Fallback - calculate signature
  val manualSignature1 = o1.computeSignature()
  val manualSignature2 = o2.computeSignature()
  manualSignature1.compareTo(manualSignature2)
}

internal val KM_CONSTRUCTOR_COMPARATOR = Comparator<KmConstructor> { o1, o2 ->
  val signature1 = o1.signature
  val signature2 = o2.signature
  if (signature1 != null && signature2 != null) {
    val result = signature1.asString().compareTo(signature2.asString())
    if (result != 0) return@Comparator result
  }

  // Fallback - calculate signature
  val manualSignature1 = o1.computeSignature()
  val manualSignature2 = o2.computeSignature()
  manualSignature1.compareTo(manualSignature2)
}

// Computes a simple signature string good enough for hashing
private fun KmFunction.computeSignature(): String {
  return "$name(${valueParameters.joinToString(",") { it.type.simpleName }})${returnType.simpleName}"
}

private fun KmConstructor.computeSignature(): String {
  return "$<init>(${valueParameters.joinToString(",") { it.type.simpleName }})"
}

private val KmType?.simpleName: String get() {
  if (this == null) return "void"
  return when (val c = classifier) {
    is Class -> c.name
    is TypeParameter -> "Object"
    is TypeAlias -> c.name
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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
 *
 * https://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.
 */
@file:JvmName("KotlinPoetMetadataSpecs")

package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.ANY
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.DelicateKotlinPoetApi
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.FunSpec.Builder
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.KModifier.ABSTRACT
import com.squareup.kotlinpoet.KModifier.CONST
import com.squareup.kotlinpoet.KModifier.CROSSINLINE
import com.squareup.kotlinpoet.KModifier.DATA
import com.squareup.kotlinpoet.KModifier.EXPECT
import com.squareup.kotlinpoet.KModifier.EXTERNAL
import com.squareup.kotlinpoet.KModifier.FINAL
import com.squareup.kotlinpoet.KModifier.INFIX
import com.squareup.kotlinpoet.KModifier.INLINE
import com.squareup.kotlinpoet.KModifier.INNER
import com.squareup.kotlinpoet.KModifier.INTERNAL
import com.squareup.kotlinpoet.KModifier.LATEINIT
import com.squareup.kotlinpoet.KModifier.NOINLINE
import com.squareup.kotlinpoet.KModifier.OPEN
import com.squareup.kotlinpoet.KModifier.OPERATOR
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.KModifier.PROTECTED
import com.squareup.kotlinpoet.KModifier.PUBLIC
import com.squareup.kotlinpoet.KModifier.SEALED
import com.squareup.kotlinpoet.KModifier.SUSPEND
import com.squareup.kotlinpoet.KModifier.TAILREC
import com.squareup.kotlinpoet.KModifier.VALUE
import com.squareup.kotlinpoet.KModifier.VARARG
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeAliasSpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.UNIT
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag
import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_EXTERNAL
import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_INLINE
import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_NOT_DEFAULT
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createAnnotations
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createClassName
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.toTreeSet
import com.squareup.kotlinpoet.metadata.declaresDefaultValue
import com.squareup.kotlinpoet.metadata.hasAnnotations
import com.squareup.kotlinpoet.metadata.hasGetter
import com.squareup.kotlinpoet.metadata.hasSetter
import com.squareup.kotlinpoet.metadata.isAbstract
import com.squareup.kotlinpoet.metadata.isAnnotation
import com.squareup.kotlinpoet.metadata.isClass
import com.squareup.kotlinpoet.metadata.isCompanionObject
import com.squareup.kotlinpoet.metadata.isConst
import com.squareup.kotlinpoet.metadata.isCrossInline
import com.squareup.kotlinpoet.metadata.isData
import com.squareup.kotlinpoet.metadata.isDeclaration
import com.squareup.kotlinpoet.metadata.isDelegated
import com.squareup.kotlinpoet.metadata.isDelegation
import com.squareup.kotlinpoet.metadata.isEnum
import com.squareup.kotlinpoet.metadata.isEnumEntry
import com.squareup.kotlinpoet.metadata.isExpect
import com.squareup.kotlinpoet.metadata.isExternal
import com.squareup.kotlinpoet.metadata.isFinal
import com.squareup.kotlinpoet.metadata.isFun
import com.squareup.kotlinpoet.metadata.isInfix
import com.squareup.kotlinpoet.metadata.isInline
import com.squareup.kotlinpoet.metadata.isInner
import com.squareup.kotlinpoet.metadata.isInterface
import com.squareup.kotlinpoet.metadata.isInternal
import com.squareup.kotlinpoet.metadata.isLateinit
import com.squareup.kotlinpoet.metadata.isNoInline
import com.squareup.kotlinpoet.metadata.isObject
import com.squareup.kotlinpoet.metadata.isOpen
import com.squareup.kotlinpoet.metadata.isOperator
import com.squareup.kotlinpoet.metadata.isPrimary
import com.squareup.kotlinpoet.metadata.isPrivate
import com.squareup.kotlinpoet.metadata.isProtected
import com.squareup.kotlinpoet.metadata.isPublic
import com.squareup.kotlinpoet.metadata.isReified
import com.squareup.kotlinpoet.metadata.isSealed
import com.squareup.kotlinpoet.metadata.isSuspend
import com.squareup.kotlinpoet.metadata.isSynthesized
import com.squareup.kotlinpoet.metadata.isTailRec
import com.squareup.kotlinpoet.metadata.isVal
import com.squareup.kotlinpoet.metadata.isValue
import com.squareup.kotlinpoet.metadata.isVar
import com.squareup.kotlinpoet.metadata.propertyAccessorFlags
import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
import com.squareup.kotlinpoet.metadata.toKmClass
import com.squareup.kotlinpoet.tag
import java.util.Locale
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.PackageElement
import javax.lang.model.element.TypeElement
import kotlin.reflect.KClass
import kotlinx.metadata.Flags
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmConstructor
import kotlinx.metadata.KmFunction
import kotlinx.metadata.KmPackage
import kotlinx.metadata.KmProperty
import kotlinx.metadata.KmType
import kotlinx.metadata.KmTypeAlias
import kotlinx.metadata.KmValueParameter
import kotlinx.metadata.jvm.JvmMethodSignature
import kotlinx.metadata.jvm.getterSignature
import kotlinx.metadata.jvm.jvmInternalName
import kotlinx.metadata.jvm.setterSignature
import kotlinx.metadata.jvm.signature

/** @return a [TypeSpec] ABI representation of this [KClass]. */
@KotlinPoetMetadataPreview
public fun KClass<*>.toTypeSpec(
  classInspector: ClassInspector? = null,
): TypeSpec = java.toTypeSpec(classInspector)

/** @return a [TypeSpec] ABI representation of this [KClass]. */
@OptIn(DelicateKotlinPoetApi::class)
@KotlinPoetMetadataPreview
public fun Class<*>.toTypeSpec(
  classInspector: ClassInspector? = null,
): TypeSpec = toKmClass().toTypeSpec(classInspector, asClassName())

/** @return a [TypeSpec] ABI representation of this [TypeElement]. */
@OptIn(DelicateKotlinPoetApi::class)
@KotlinPoetMetadataPreview
public fun TypeElement.toTypeSpec(
  classInspector: ClassInspector? = null,
): TypeSpec = toKmClass().toTypeSpec(classInspector, asClassName())

/** @return a [FileSpec] ABI representation of this [KClass]. */
@KotlinPoetMetadataPreview
public fun KClass<*>.toFileSpec(
  classInspector: ClassInspector? = null,
): FileSpec = java.toFileSpec(classInspector)

/** @return a [FileSpec] ABI representation of this [KClass]. */
@KotlinPoetMetadataPreview
public fun Class<*>.toFileSpec(
  classInspector: ClassInspector? = null,
): FileSpec = FileSpec.get(`package`.name, toTypeSpec(classInspector))

/** @return a [FileSpec] ABI representation of this [TypeElement]. */
@KotlinPoetMetadataPreview
public fun TypeElement.toFileSpec(
  classInspector: ClassInspector? = null,
): FileSpec = FileSpec.get(
  packageName = packageName,
  typeSpec = toTypeSpec(classInspector),
)

/** @return a [TypeSpec] ABI representation of this [KmClass]. */
@KotlinPoetMetadataPreview
public fun KmClass.toTypeSpec(
  classInspector: ClassInspector?,
  className: ClassName = createClassName(name),
): TypeSpec {
  return toTypeSpec(classInspector, className, null)
}

/** @return a [FileSpec] ABI representation of this [KmClass]. */
@KotlinPoetMetadataPreview
public fun KmClass.toFileSpec(
  classInspector: ClassInspector?,
  className: ClassName = createClassName(name),
): FileSpec {
  return FileSpec.get(
    packageName = className.packageName,
    typeSpec = toTypeSpec(classInspector, className),
  )
}

/** @return a [FileSpec] ABI representation of this [KmPackage]. */
@KotlinPoetMetadataPreview
public fun KmPackage.toFileSpec(
  classInspector: ClassInspector?,
  className: ClassName,
): FileSpec {
  val fileData = classInspector?.containerData(className, null)
  check(fileData is FileData?) {
    "Unexpected container data type: ${fileData?.javaClass}"
  }
  val fileName = fileData?.fileName ?: className.simpleName
  return FileSpec.builder(className.packageName, fileName)
    .apply {
      fileData?.let { data ->
        data.jvmName?.let { name ->
          addAnnotation(
            AnnotationSpec.builder(ClassInspectorUtil.JVM_NAME)
              .addMember("name = %S", name)
              .build(),
          )
        }
        val fileAnnotations = createAnnotations(FILE) {
          addAll(data.annotations.filterNot { it.typeName == METADATA })
        }
        for (fileAnnotation in fileAnnotations) {
          addAnnotation(fileAnnotation)
        }
      }
      for (function in functions) {
        val methodData = fileData?.methods?.get(function)
        addFunction(
          function.toFunSpec(
            classInspector = classInspector,
            containerData = fileData,
            methodData = methodData,
            isInInterface = false,
          ),
        )
      }
      for (property in properties) {
        val propertyData = fileData?.properties?.get(property)
        addProperty(
          property.toPropertySpec(
            classInspector = classInspector,
            containerData = fileData,
            propertyData = propertyData,
            isInInterface = false,
          ),
        )
      }
      for (alias in typeAliases) {
        addTypeAlias(alias.toTypeAliasSpec())
      }
    }
    .build()
}

private const val NOT_IMPLEMENTED = "throw·NotImplementedError(\"Stub!\")"

@KotlinPoetMetadataPreview
private fun KmClass.toTypeSpec(
  classInspector: ClassInspector?,
  className: ClassName,
  parentClassName: ClassName?,
): TypeSpec {
  val classTypeParamsResolver = typeParameters.toTypeParameterResolver()
  val jvmInternalName = name.jvmInternalName
  val simpleName = className.simpleName
  val classData = classInspector?.containerData(className, parentClassName)
  check(classData is ClassData?) {
    "Unexpected container data type: ${classData?.javaClass}"
  }

  val builder = when {
    isAnnotation -> TypeSpec.annotationBuilder(simpleName)
    isCompanionObject -> TypeSpec.companionObjectBuilder(companionObjectName(simpleName))
    isEnum -> TypeSpec.enumBuilder(simpleName)
    isExpect -> TypeSpec.expectClassBuilder(simpleName)
    isObject -> TypeSpec.objectBuilder(simpleName)
    isInterface -> {
      if (classData?.declarationContainer?.isFun == true) {
        TypeSpec.funInterfaceBuilder(simpleName)
      } else {
        TypeSpec.interfaceBuilder(simpleName)
      }
    }
    isEnumEntry -> TypeSpec.anonymousClassBuilder()
    else -> TypeSpec.classBuilder(simpleName)
  }

  classData?.annotations
    ?.filterNot {
      it.typeName == METADATA || it.typeName in JAVA_ANNOTATION_ANNOTATIONS
    }
    ?.let(builder::addAnnotations)

  if (isEnum) {
    enumEntries.forEach { entryName ->
      val typeSpec = if (classInspector != null) {
        val entry = classInspector.enumEntry(className, entryName)
        entry.declarationContainer
          ?.let { enumEntryClass ->
            val entryClassName = className.nestedClass(entryName)
            enumEntryClass.toTypeSpec(classInspector, entryClassName, parentClassName = className)
          }
          ?: TypeSpec.anonymousClassBuilder()
            .addAnnotations(entry.annotations)
            .build()
      } else {
        TypeSpec.anonymousClassBuilder()
          .addKdoc(
            "No ClassInspector was available during metadata parsing, so this entry may not be reflected accurately if it has a class body.",
          )
          .build()
      }
      builder.addEnumConstant(entryName, typeSpec)
    }
  }

  if (!isEnumEntry) {
    visibilityFrom(flags) { builder.addModifiers(it) }
    builder.addModifiers(
      *flags.modalities
        .filterNot { it == FINAL } // Default
        .filterNot { isInterface && it == ABSTRACT } // Abstract is a default on interfaces
        .toTypedArray(),
    )
    if (isData) {
      builder.addModifiers(DATA)
    }
    if (isExternal) {
      builder.addModifiers(EXTERNAL)
    }
    if (isValue) {
      builder.addModifiers(VALUE)
    }
    if (isInner) {
      builder.addModifiers(INNER)
    }
    builder.addTypeVariables(typeParameters.map { it.toTypeVariableName(classTypeParamsResolver) })
    // If we have an inspector, we can check exactly which "supertype" is an interface vs
    // class. Without a handler though, we have to best-effort guess. Usually, the flow is:
    // - First element of a non-interface type is the superclass (can be `Any`)
    // - First element of an interface type is the first superinterface
    val superClassFilter = classInspector?.let { handler ->
      { type: KmType ->
        !handler.isInterface(createClassName((type.classifier as KmClassifier.Class).name))
      }
    } ?: { true }
    val superClass = supertypes.asSequence()
      .filter { it.classifier is KmClassifier.Class }
      .find(superClassFilter)
    if (superClass != null && !isEnum && !isInterface && !isAnnotation) {
      superClass.toTypeName(classTypeParamsResolver).takeIf { it != ANY }
        ?.let(builder::superclass)
    }
    builder.addSuperinterfaces(
      supertypes.asSequence()
        .filterNot { it == superClass }
        .map { it.toTypeName(classTypeParamsResolver) }
        .filterNot { it == ANY }
        .asIterable(),
    )
    val primaryConstructorParams = mutableMapOf<String, ParameterSpec>()
    if (isClass || isAnnotation || isEnum) {
      primaryConstructor?.let {
        it.toFunSpec(classTypeParamsResolver, classData?.constructors?.get(it) ?: return@let)
          .also { spec ->
            val finalSpec = if (isEnum && spec.annotations.isEmpty()) {
              // Metadata specifies the constructor as private, but that's implicit so we can omit it
              spec.toBuilder().apply { modifiers.remove(PRIVATE) }.build()
            } else {
              spec
            }
            builder.primaryConstructor(finalSpec)
            primaryConstructorParams.putAll(spec.parameters.associateBy { it.name })
          }
      }
      constructors.filter { !it.isPrimary }.takeIf { it.isNotEmpty() }?.let { secondaryConstructors ->
        builder.addFunctions(
          secondaryConstructors
            .mapNotNull { kmConstructor ->
              classData?.constructors?.get(kmConstructor)?.let { kmConstructor to it }
            }
            .map { (kmConstructor, constructorData) ->
              kmConstructor.toFunSpec(classTypeParamsResolver, constructorData)
            },
        )
      }
    }
    builder.addProperties(
      properties
        .asSequence()
        .filter { it.isDeclaration }
        .filterNot { it.isSynthesized }
        .map { it to classData?.properties?.get(it) }
        .map { (property, propertyData) ->
          property.toPropertySpec(
            typeParamResolver = classTypeParamsResolver,
            isConstructorParam = property.name in primaryConstructorParams,
            classInspector = classInspector,
            containerData = classData,
            propertyData = propertyData,
          )
        }
        .asIterable(),
    )
    companionObject?.let { objectName ->
      val companionType = if (classInspector != null) {
        val companionClassName = className.nestedClass(objectName)
        classInspector.classFor(companionClassName)
          .toTypeSpec(classInspector, companionClassName, parentClassName = className)
      } else {
        TypeSpec.companionObjectBuilder(companionObjectName(objectName))
          .addKdoc(
            "No ClassInspector was available during metadata parsing, so this companion object's API/contents may not be reflected accurately.",
          )
          .build()
      }
      builder.addType(companionType)
    }
  }
  builder.addFunctions(
    functions
      .asSequence()
      .filter { it.isDeclaration }
      .filterNot { it.isDelegation }
      .filterNot { it.isSynthesized }
      .map { it to classData?.methods?.get(it) }
      .map { (func, methodData) ->
        func.toFunSpec(classTypeParamsResolver, classInspector, classData, methodData)
          .toBuilder()
          .apply {
            // For interface methods, remove any body and mark the methods as abstract
            fun isKotlinDefaultInterfaceMethod(): Boolean {
              classInspector?.let { handler ->
                func.signature?.let { signature ->
                  val suffix = signature.desc.removePrefix("(")
                  return handler.methodExists(
                    className.nestedClass("DefaultImpls"),
                    signature.copy(
                      desc = "(L$jvmInternalName;$suffix",
                    ),
                  )
                }
              }
              return false
            }
            // For interface methods, remove any body and mark the methods as abstract
            // IFF it doesn't have a default interface body.
            if (isInterface &&
              annotations.none { it.typeName == JVM_DEFAULT } &&
              (methodData?.jvmModifiers?.contains(DEFAULT) == false) &&
              !isKotlinDefaultInterfaceMethod()
            ) {
              addModifiers(ABSTRACT)
              clearBody()
            } else if (ABSTRACT in modifiers) {
              // Remove bodies for abstract functions
              clearBody()
            }
            if (methodData?.isSynthetic == true) {
              addKdoc(
                "Note: Since this is a synthetic function, some JVM information " +
                  "(annotations, modifiers) may be missing.",
              )
            }
          }
          .build()
      }
      .asIterable(),
  )

  for (it in nestedClasses) {
    val nestedClassName = className.nestedClass(it)
    val nestedClass = classInspector?.classFor(nestedClassName)
    val nestedType = if (nestedClass != null) {
      if (nestedClass.isCompanionObject) {
        // We handle these separately
        continue
      } else {
        nestedClass.toTypeSpec(classInspector, nestedClassName, parentClassName = className)
      }
    } else {
      TypeSpec.classBuilder(it)
        .addKdoc(
          "No ClassInspector was available during metadata parsing, so this nested class's API/contents may not be reflected accurately.",
        )
        .build()
    }
    builder.addType(nestedType)
  }

  return builder
    .tag(this)
    .build()
}

private fun companionObjectName(name: String): String? {
  return if (name == "Companion") null else name
}

@KotlinPoetMetadataPreview
private fun KmConstructor.toFunSpec(
  typeParamResolver: TypeParameterResolver,
  constructorData: ConstructorData?,
): FunSpec {
  return FunSpec.constructorBuilder()
    .apply {
      addAnnotations(constructorData?.allAnnotations.orEmpty())
      visibilityFrom(flags) { addModifiers(it) }
      addParameters(
        this@toFunSpec.valueParameters.mapIndexed { index, param ->
          param.toParameterSpec(
            typeParamResolver,
            constructorData?.takeIf { it != ConstructorData.EMPTY }
              ?.parameterAnnotations
              ?.get(index)
              .orEmpty(),
          )
        },
      )
      if (!isPrimary) {
        // TODO How do we know when to add callSuperConstructor()?
      }
    }
    .tag(this)
    .build()
}

@KotlinPoetMetadataPreview
private val ContainerData.isInterface: Boolean get() {
  return declarationContainer.let { container ->
    container is KmClass && container.isInterface
  }
}

@KotlinPoetMetadataPreview
private fun KmFunction.toFunSpec(
  classTypeParamsResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
  classInspector: ClassInspector? = null,
  containerData: ContainerData? = null,
  methodData: MethodData? = null,
  isInInterface: Boolean = containerData?.isInterface ?: false,
): FunSpec {
  val typeParamsResolver = typeParameters.toTypeParameterResolver(
    fallback = classTypeParamsResolver,
  )
  val mutableAnnotations = mutableListOf<AnnotationSpec>()
  if (classInspector != null && containerData != null) {
    signature?.let { signature ->
      if (!containerData.isInterface) {
        // Infer if JvmName was used
        // We skip interface types for this because they can't have @JvmName.
        signature.jvmNameAnnotation(name)?.let { jvmNameAnnotation ->
          mutableAnnotations += jvmNameAnnotation
        }
      }
    }
  }
  val anyReified = typeParameters.any { it.isReified }
  val isInFacade = containerData is FileData
  val annotations = mutableAnnotations
    .plus(methodData?.allAnnotations(containsReifiedTypeParameter = anyReified).orEmpty())
    .filterNot { isInFacade && it.typeName == JVM_STATIC }
    .toTreeSet()
  return FunSpec.builder(name)
    .apply {
      addAnnotations(annotations)
      visibilityFrom(flags) { addModifiers(it) }
      val isOverride = methodData?.isOverride == true
      addModifiers(
        flags.modalities
          .filterNot { it == FINAL && !isOverride } // Final is the default
          .filterNot { it == OPEN && isOverride } // Overrides are implicitly open
          .filterNot { it == OPEN && isInInterface }, // interface methods are implicitly open
      )
      if (valueParameters.isNotEmpty()) {
        addParameters(
          valueParameters.mapIndexed { index, param ->
            param.toParameterSpec(
              typeParamsResolver,
              // This can be empty if the element is synthetic
              methodData?.parameterAnnotations?.get(index).orEmpty(),
            )
          },
        )
      }
      if (typeParameters.isNotEmpty()) {
        addTypeVariables(typeParameters.map { it.toTypeVariableName(typeParamsResolver) })
      }
      if (methodData?.isOverride == true) {
        addModifiers(KModifier.OVERRIDE)
      }
      if (isOperator) {
        addModifiers(OPERATOR)
      }
      if (isInfix) {
        addModifiers(INFIX)
      }
      if (isInline) {
        addModifiers(INLINE)
      }
      if (isTailRec) {
        addModifiers(TAILREC)
      }
      if (isExternal) {
        addModifiers(EXTERNAL)
      }
      if (isExpect) {
        addModifiers(EXPECT)
      }
      if (isSuspend) {
        addModifiers(SUSPEND)
      }
      val returnTypeName = this@toFunSpec.returnType.toTypeName(typeParamsResolver)
      if (returnTypeName != UNIT) {
        returns(returnTypeName)
        if (!flags.isAbstract) {
          addStatement(NOT_IMPLEMENTED)
        }
      }
      receiverParameterType?.toTypeName(typeParamsResolver)?.let { receiver(it) }
    }
    .tag(this)
    .build()
}

@KotlinPoetMetadataPreview
private fun KmValueParameter.toParameterSpec(
  typeParamResolver: TypeParameterResolver,
  annotations: Collection<AnnotationSpec>,
): ParameterSpec {
  val paramType = varargElementType ?: type
  return ParameterSpec.builder(name, paramType.toTypeName(typeParamResolver))
    .apply {
      addAnnotations(annotations)
      if (varargElementType != null) {
        addModifiers(VARARG)
      }
      if (isCrossInline) {
        addModifiers(CROSSINLINE)
      }
      if (isNoInline) {
        addModifiers(NOINLINE)
      }
      if (declaresDefaultValue) {
        defaultValue(NOT_IMPLEMENTED)
      }
    }
    .tag(this)
    .build()
}

@KotlinPoetMetadataPreview
private fun KmProperty.toPropertySpec(
  typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
  isConstructorParam: Boolean = false,
  classInspector: ClassInspector? = null,
  containerData: ContainerData? = null,
  propertyData: PropertyData? = null,
  isInInterface: Boolean = containerData?.isInterface ?: false,
): PropertySpec {
  val isOverride = propertyData?.isOverride ?: false
  val returnTypeName = returnType.toTypeName(typeParamResolver)
  val mutableAnnotations = mutableListOf<AnnotationSpec>()
  if (containerData != null && propertyData != null) {
    if (hasGetter) {
      getterSignature?.let { getterSignature ->
        if (!containerData.isInterface &&
          !flags.isOpen &&
          !flags.isAbstract
        ) {
          // Infer if JvmName was used
          // We skip interface types or open/abstract properties because they can't have @JvmName.
          // For annotation properties, kotlinc puts JvmName annotations by default in
          // bytecode but they're implicit in source, so we expect the simple name for
          // annotation types.
          val expectedMetadataName = if (containerData is ClassData &&
            containerData.declarationContainer.isAnnotation
          ) {
            name
          } else {
            "get${name.safeCapitalize(Locale.US)}"
          }
          getterSignature.jvmNameAnnotation(
            metadataName = expectedMetadataName,
            useSiteTarget = UseSiteTarget.GET,
          )?.let { jvmNameAnnotation ->
            mutableAnnotations += jvmNameAnnotation
          }
        }
      }
    }
    if (hasSetter) {
      setterSignature?.let { setterSignature ->
        if (containerData is ClassData &&
          !containerData.declarationContainer.isAnnotation &&
          !containerData.declarationContainer.isInterface &&
          classInspector?.supportsNonRuntimeRetainedAnnotations == false &&
          !flags.isOpen &&
          !flags.isAbstract
        ) {
          // Infer if JvmName was used
          // We skip annotation types for this because they can't have vars.
          // We skip interface types or open/abstract properties because they can't have @JvmName.
          setterSignature.jvmNameAnnotation(
            metadataName = "set${name.safeCapitalize(Locale.US)}",
            useSiteTarget = UseSiteTarget.SET,
          )?.let { jvmNameAnnotation ->
            mutableAnnotations += jvmNameAnnotation
          }
        }
      }
    }
  }
  return PropertySpec.builder(name, returnTypeName)
    .apply {
      // If a property annotation doesn't have a custom site target and is used in a constructor
      // we have to add the property: site target to it.

      val isInFacade = containerData is FileData
      val finalAnnotations = mutableAnnotations
        .plus(propertyData?.allAnnotations.orEmpty())
        .filterNot { (isConst || isInFacade) && it.typeName == JVM_STATIC }
        .map {
          if (isConstructorParam && it.useSiteTarget == null) {
            // TODO Ideally don't do this if the annotation use site is only field?
            //  e.g. JvmField. It's technically fine, but redundant on parameters as it's
            //  automatically applied to the property for these annotation types.
            //  This is another thing ClassInspector *could* tell us
            it.toBuilder().useSiteTarget(UseSiteTarget.PROPERTY).build()
          } else {
            it
          }
        }
        .toTreeSet()
      addAnnotations(finalAnnotations)
      visibilityFrom(flags) { addModifiers(it) }
      addModifiers(
        flags.modalities
          .filterNot { it == FINAL && !isOverride } // Final is the default
          .filterNot { it == OPEN && isOverride } // Overrides are implicitly open
          .filterNot { it == OPEN && isInInterface } // Interface properties implicitly open
          .filterNot { it == ABSTRACT && isInInterface }, // Interface properties implicitly abstract
      )
      if (isOverride) {
        addModifiers(KModifier.OVERRIDE)
      }
      if (isConst) {
        addModifiers(CONST)
      }
      if (isVar) {
        mutable(true)
      } else if (isVal) {
        mutable(false)
      }
      if (isDelegated) {
        // Placeholders for these are tricky
        addKdoc("Note: delegation is ABI stub only and not guaranteed to match source code.")
        if (isVal) {
          delegate("%M { %L }", MemberName("kotlin", "lazy"), NOT_IMPLEMENTED) // Placeholder
        } else {
          if (returnTypeName.isNullable) {
            delegate(
              "%T.observable(null) { _, _, _ -> }",
              ClassName("kotlin.properties", "Delegates"),
            )
          } else {
            delegate("%T.notNull()", ClassName("kotlin.properties", "Delegates")) // Placeholder
          }
        }
      }
      if (isExpect) {
        addModifiers(EXPECT)
      }
      if (isExternal) {
        addModifiers(EXTERNAL)
      }
      if (isLateinit) {
        addModifiers(LATEINIT)
      }
      if (isConstructorParam || (!isDelegated && !isLateinit)) {
        val constant = propertyData?.fieldData?.constant
        when {
          constant != null -> initializer(constant)
          isConstructorParam -> initializer(name)
          returnTypeName.isNullable -> initializer("null")
          flags.isAbstract || isInInterface -> {
            // No-op, don't emit an initializer for abstract or interface properties
          }
          else -> initializer(NOT_IMPLEMENTED)
        }
      }
      // Delegated properties have setters/getters defined for some reason, ignore here
      // since the delegate handles it
      // vals with initialized constants have a getter in bytecode but not a body in kotlin source
      val modifierSet = modifiers.toSet()
      if (hasGetter && !isDelegated && !flags.isAbstract) {
        propertyAccessor(
          modifierSet,
          getterFlags,
          FunSpec.getterBuilder().addStatement(NOT_IMPLEMENTED),
          isOverride,
        )?.let(::getter)
      }
      if (hasSetter && !isDelegated && !flags.isAbstract) {
        propertyAccessor(modifierSet, setterFlags, FunSpec.setterBuilder(), isOverride)?.let(::setter)
      }
    }
    .tag(this)
    .build()
}

@KotlinPoetMetadataPreview
private fun propertyAccessor(
  propertyModifiers: Set<KModifier>,
  flags: Flags,
  functionBuilder: Builder,
  isOverride: Boolean,
): FunSpec? {
  val visibility = flags.visibility
  if (visibility == PUBLIC || visibility !in propertyModifiers) {
    // This is redundant and just a stub
    // For annotations on this accessor, we declare them on the property with site target instead
    return null
  }
  val modalities = flags.modalities
    .filterNot { it == FINAL && !isOverride }
    .filterNot { it == OPEN && isOverride }
  val propertyAccessorFlags = flags.propertyAccessorFlags
  return if (modalities.isNotEmpty() || propertyAccessorFlags.isNotEmpty()) {
    functionBuilder
      .apply {
        addModifiers(visibility)
        addModifiers(modalities)
        addModifiers(*propertyAccessorFlags.toKModifiersArray())
      }
      .build()
  } else {
    null
  }
}

private fun Set<PropertyAccessorFlag>.toKModifiersArray(): Array<KModifier> {
  return mapNotNull {
    when (it) {
      IS_EXTERNAL -> EXTERNAL
      IS_INLINE -> INLINE
      IS_NOT_DEFAULT -> null // Gracefully skip over these
    }
  }.toTypedArray()
}

@KotlinPoetMetadataPreview
private fun KmTypeAlias.toTypeAliasSpec(): TypeAliasSpec {
  val typeParamResolver = typeParameters.toTypeParameterResolver()
  return TypeAliasSpec.builder(name, underlyingType.toTypeName(typeParamResolver))
    .apply {
      visibilityFrom(flags) {
        addModifiers(it)
      }
      if (flags.hasAnnotations) {
        val annotationSpecs = this@toTypeAliasSpec.annotations
          .map { it.toAnnotationSpec() }
        addAnnotations(annotationSpecs)
      }
    }
    .addTypeVariables(typeParamResolver.parametersMap.values)
    .build()
}

private fun JvmMethodSignature.jvmNameAnnotation(
  metadataName: String,
  useSiteTarget: UseSiteTarget? = null,
): AnnotationSpec? {
  return if (name == metadataName) {
    null
  } else {
    return AnnotationSpec.builder(JvmName::class)
      .addMember("name = %S", name)
      .useSiteTarget(useSiteTarget)
      .build()
  }
}

private val JAVA_ANNOTATION_ANNOTATIONS = setOf(
  java.lang.annotation.Retention::class.asClassName(),
  java.lang.annotation.Target::class.asClassName(),
)

@KotlinPoetMetadataPreview
private val Flags.visibility: KModifier
  get() = when {
    isInternal -> INTERNAL
    isPrivate -> PRIVATE
    isProtected -> PROTECTED
    isPublic -> PUBLIC
    else -> {
      // IS_PRIVATE_TO_THIS or IS_LOCAL, so just default to public
      PUBLIC
    }
  }

@KotlinPoetMetadataPreview
private fun visibilityFrom(flags: Flags, body: (KModifier) -> Unit) {
  val modifierVisibility = flags.visibility
  if (modifierVisibility != PUBLIC) {
    body(modifierVisibility)
  }
}

private fun String.safeCapitalize(locale: Locale): String {
  return replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() }
}

@KotlinPoetMetadataPreview
private val Flags.modalities: Set<KModifier>
  get() = setOf {
    if (isFinal) {
      add(FINAL)
    }
    if (isOpen) {
      add(OPEN)
    }
    if (isAbstract) {
      add(ABSTRACT)
    }
    if (isSealed) {
      add(SEALED)
    }
  }

private inline fun <E> setOf(body: MutableSet<E>.() -> Unit): Set<E> {
  return mutableSetOf<E>().apply(body).toSet()
}

private val METADATA = Metadata::class.asClassName()

@Suppress("DEPRECATION")
private val JVM_DEFAULT = JvmDefault::class.asClassName()
private val JVM_STATIC = JvmStatic::class.asClassName()

@PublishedApi
internal val Element.packageName: String
  get() {
    var element = this
    while (element.kind != ElementKind.PACKAGE) {
      element = element.enclosingElement
    }
    return (element as PackageElement).toString()
  }

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil

/**
 * Represents relevant information on a method used for [ClassInspector]. Should only be
 * associated with methods of a [ClassData] or [PropertyData].
 *
 * @param annotations declared annotations on this method.
 * @property parameterAnnotations a mapping of parameter indices to annotations on them.
 * @property isSynthetic indicates if this method is synthetic or not.
 * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this method.
 * @property isOverride indicates if this method overrides one in a supertype.
 * @property exceptions list of exceptions thrown by this method.
 */
@KotlinPoetMetadataPreview
public data class MethodData(
  private val annotations: List<AnnotationSpec>,
  val parameterAnnotations: Map<Int, Collection<AnnotationSpec>>,
  val isSynthetic: Boolean,
  val jvmModifiers: Set<JvmMethodModifier>,
  val isOverride: Boolean,
  val exceptions: List<TypeName>,
) {

  /**
   * A collection of all annotations on this method, including any derived from [jvmModifiers],
   * [isSynthetic], and [exceptions].
   *
   * @param useSiteTarget an optional [UseSiteTarget] that all annotations on this method should
   *        use.
   * @param containsReifiedTypeParameter an optional boolean indicating if any type parameters on
   *        this function are `reified`, which are implicitly synthetic.
   */
  public fun allAnnotations(
    useSiteTarget: UseSiteTarget? = null,
    containsReifiedTypeParameter: Boolean = false,
  ): Collection<AnnotationSpec> {
    return ClassInspectorUtil.createAnnotations(
      useSiteTarget,
    ) {
      addAll(annotations)
      if (isSynthetic && !containsReifiedTypeParameter) {
        add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
      }
      addAll(jvmModifiers.mapNotNull(JvmMethodModifier::annotationSpec))
      exceptions.takeIf { it.isNotEmpty() }
        ?.let {
          add(ClassInspectorUtil.createThrowsSpec(it, useSiteTarget))
        }
    }
  }

  public companion object {
    public val SYNTHETIC: MethodData = MethodData(
      annotations = emptyList(),
      parameterAnnotations = emptyMap(),
      isSynthetic = true,
      jvmModifiers = emptySet(),
      isOverride = false,
      exceptions = emptyList(),
    )
    public val EMPTY: MethodData = MethodData(
      annotations = emptyList(),
      parameterAnnotations = emptyMap(),
      isSynthetic = false,
      jvmModifiers = emptySet(),
      isOverride = false,
      exceptions = emptyList(),
    )
  }
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.GET
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.SET
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil

/**
 * Represents relevant information on a property used for [ClassInspector]. Should only be
 * associated with properties of a [ClassData].
 *
 * @param annotations declared annotations on this property.
 * @property fieldData associated [FieldData] with this property, if any.
 * @property getterData associated getter (as [MethodData]) with this property, if any.
 * @property setterData associated setter (as [MethodData]) with this property, if any.
 * @property isJvmField indicates if this property should be treated as a jvm field.
 */
@KotlinPoetMetadataPreview
public data class PropertyData(
  private val annotations: List<AnnotationSpec>,
  val fieldData: FieldData?,
  val getterData: MethodData?,
  val setterData: MethodData?,
  val isJvmField: Boolean,
) {
  /** Indicates if this property overrides another from a supertype. */
  val isOverride: Boolean = (getterData?.isOverride ?: false) || (setterData?.isOverride ?: false)

  /**
   * A collection of all annotations on this property including declared ones and any derived from
   * [fieldData], [getterData], [setterData], and [isJvmField].
   */
  val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations {
    // Don't add annotations that are already defined on the parent
    val higherScopedAnnotations = annotations.associateBy { it.typeName }
    val fieldAnnotations = fieldData?.allAnnotations.orEmpty()
      .filterNot { it.typeName in higherScopedAnnotations }
      .associateByTo(LinkedHashMap()) { it.typeName }
    val getterAnnotations = getterData?.allAnnotations(GET).orEmpty()
      .filterNot { it.typeName in higherScopedAnnotations }
      .associateByTo(LinkedHashMap()) { it.typeName }

    val finalTopAnnotations = annotations.toMutableList()

    // If this is a val, and annotation is on both getter and field, we can move it to just the
    // regular annotations
    if (setterData == null && !isJvmField) {
      val sharedAnnotations = getterAnnotations.keys.intersect(fieldAnnotations.keys)
      for (sharedAnnotation in sharedAnnotations) {
        // Add it to the top-level annotations without a site-target
        finalTopAnnotations += getterAnnotations.getValue(sharedAnnotation).toBuilder()
          .useSiteTarget(null)
          .build()

        // Remove from field and getter
        fieldAnnotations.remove(sharedAnnotation)
        getterAnnotations.remove(sharedAnnotation)
      }
    }

    addAll(finalTopAnnotations)
    addAll(fieldAnnotations.values)
    addAll(getterAnnotations.values)
    addAll(
      setterData?.allAnnotations(SET).orEmpty()
        .filterNot { it.typeName in higherScopedAnnotations },
    )
    if (isJvmField) {
      add(ClassInspectorUtil.JVM_FIELD_SPEC)
    }
  }
}

/*
 * Copyright (C) 2021 Square, Inc.
 *
 * 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
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.kotlinpoet.metadata.specs

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.joinToCode
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createClassName
import com.squareup.kotlinpoet.tag
import kotlinx.metadata.KmAnnotation
import kotlinx.metadata.KmAnnotationArgument
import kotlinx.metadata.KmAnnotationArgument.AnnotationValue
import kotlinx.metadata.KmAnnotationArgument.ArrayValue
import kotlinx.metadata.KmAnnotationArgument.BooleanValue
import kotlinx.metadata.KmAnnotationArgument.ByteValue
import kotlinx.metadata.KmAnnotationArgument.CharValue
import kotlinx.metadata.KmAnnotationArgument.DoubleValue
import kotlinx.metadata.KmAnnotationArgument.EnumValue
import kotlinx.metadata.KmAnnotationArgument.FloatValue
import kotlinx.metadata.KmAnnotationArgument.IntValue
import kotlinx.metadata.KmAnnotationArgument.KClassValue
import kotlinx.metadata.KmAnnotationArgument.LongValue
import kotlinx.metadata.KmAnnotationArgument.ShortValue
import kotlinx.metadata.KmAnnotationArgument.StringValue
import kotlinx.metadata.KmAnnotationArgument.UByteValue
import kotlinx.metadata.KmAnnotationArgument.UIntValue
import kotlinx.metadata.KmAnnotationArgument.ULongValue
import kotlinx.metadata.KmAnnotationArgument.UShortValue

@KotlinPoetMetadataPreview
internal fun KmAnnotation.toAnnotationSpec(): AnnotationSpec {
  val cn = createClassName(className)
  return AnnotationSpec.builder(cn)
    .apply {
      arguments.forEach { (name, arg) ->
        addMember("%L = %L", name, arg.toCodeBlock())
      }
    }
    .tag(this)
    .build()
}

@OptIn(ExperimentalUnsignedTypes::class)
@KotlinPoetMetadataPreview
internal fun KmAnnotationArgument.toCodeBlock(): CodeBlock {
  return when (this) {
    is ByteValue -> CodeBlock.of("%L", value)
    is CharValue -> CodeBlock.of("'%L'", value)
    is ShortValue -> CodeBlock.of("%L", value)
    is IntValue -> CodeBlock.of("%L", value)
    is LongValue -> CodeBlock.of("%LL", value)
    is FloatValue -> CodeBlock.of("%LF", value)
    is DoubleValue -> CodeBlock.of("%L", value)
    is BooleanValue -> CodeBlock.of("%L", value)
    is UByteValue -> CodeBlock.of("%Lu", value)
    is UShortValue -> CodeBlock.of("%Lu", value)
    is UIntValue -> CodeBlock.of("%Lu", value)
    is ULongValue -> CodeBlock.of("%Lu", value)
    is StringValue -> CodeBlock.of("%S", value)
    is KClassValue -> CodeBlock.of("%T::class", createClassName(className))
    is EnumValue -> CodeBlock.of("%T.%L", createClassName(enumClassName), enumEntryName)
    is AnnotationValue -> CodeBlock.of("%L", annotation.toAnnotationSpec())
    is ArrayValue -> elements.map { it.toCodeBlock() }.joinToCode(", ", "[", "]")
  }
}