/*
 * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.generators.gradle.dsl

import org.jetbrains.kotlin.generators.arguments.getPrinterToFile
import org.jetbrains.kotlin.utils.Printer
import java.io.File

fun main() {
    generateKotlinMultiplatformSourceSetConventionsKt(::getPrinterToFile)
    generateKotlinMultiplatformSourceSetConventionsImplKt(::getPrinterToFile)
}

internal fun generateKotlinMultiplatformSourceSetConventionsKt(withPrinterToFile: (targetFile: File, Printer.() -> Unit) -> Unit) {
    val sourceFile = File(kotlinGradlePluginApiSourceRoot)
        .resolve("org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformSourceSetConventions.kt")

    val oldSource = sourceFile.readText()
    val nativeDeclarations = nativePresetEntries
        .map { PlatformSourceSetConvention(targetName = it.entityName, deprecation = it.deprecation) }
        .generatePlatformInterfaceDeclarations()
        .indented(4)

    val nonNativeDeclarations = nonNativeSourceSetConventions
        .generatePlatformInterfaceDeclarations()
        .indented(4)

    val commonDeclarations = commonSourceSetConventions
        .generateCommonInterfaceDeclarations()
        .indented(4)

    val newSource = oldSource
        .replaceRegion("Native Source Set Accessors", nativeDeclarations)
        .replaceRegion("Non-Native Source Set Accessors", nonNativeDeclarations)
        .replaceRegion("Common Source Set Accessors", commonDeclarations)

    withPrinterToFile(sourceFile) {
        println(newSource)
    }
}

internal fun generateKotlinMultiplatformSourceSetConventionsImplKt(withPrinterToFile: (targetFile: File, Printer.() -> Unit) -> Unit) {
    val sourceFile = File(kotlinGradlePluginSourceRoot)
        .resolve("org/jetbrains/kotlin/gradle/internal/dsl/KotlinMultiplatformSourceSetConventionsImpl.kt")

    val oldSource = sourceFile.readText()
    val nativeDeclarations = nativePresetEntries
        .map { PlatformSourceSetConvention(targetName = it.entityName, deprecation = it.deprecation) }
        .generatePlatformImplDeclarations()
        .indented(4)

    val nonNativeDeclarations = nonNativeSourceSetConventions
        .generatePlatformImplDeclarations()
        .indented(4)

    val commonDeclarations = commonSourceSetConventions
        .generateCommonImplDeclarations()
        .indented(4)

    val newSource = oldSource
        .replaceRegion("Native Source Set Accessors", nativeDeclarations)
        .replaceRegion("Non-Native Source Set Accessors", nonNativeDeclarations)
        .replaceRegion("Common Source Set Accessors", commonDeclarations)

    withPrinterToFile(sourceFile) {
        println(newSource)
    }
}

internal data class PlatformSourceSetConvention(
    val targetName: String,
    val deprecation: KotlinPresetEntry.Deprecation? = null,
    val experimentalAnnotation: String? = null,
)

internal data class CommonSourceSetConvention(
    val baseName: String,
    val allTargetsDescription: String,
    val sampleTarget1: String,
    val sampleTarget2: String,
)

private val commonSourceSetConventions = listOf(
    CommonSourceSetConvention("common", "all declared targets", "jvm", "iosX64"),
    CommonSourceSetConvention("native", "all declared native targets", "linuxX64", "iosX64"),
    CommonSourceSetConvention("apple", "all declared Apple targets (ios, macos, watchos, tvos)", "iosX64", "macosX64"),
    CommonSourceSetConvention("ios", "all declared iOS targets", "iosX64", "iosArm64"),
    CommonSourceSetConvention("tvos", "all declared tvOS targets", "tvosX64", "tvosArm64"),
    CommonSourceSetConvention("watchos", "all declared watchOS targets", "watchosX64", "watchosArm64"),
    CommonSourceSetConvention("macos", "all declared macOS targets", "macosX64", "macosArm64"),
    CommonSourceSetConvention("linux", "all declared Linux targets", "linuxX64", "linuxArm64"),
    CommonSourceSetConvention("ohos", "all declared Ohos targets", "ohosArm64", "ohosArm64"),
    CommonSourceSetConvention("mingw", "all declared Mingw targets", "mingwX64", "mingwX86"),
    CommonSourceSetConvention("androidNative", "all declared Android Native targets", "androidNativeX64", "androidNativeArm64"),
    CommonSourceSetConvention("web", "all declared JS and WasmJS targets", "js", "wasmJs"),
)

private val nonNativeSourceSetConventions = listOf(
    PlatformSourceSetConvention("jvm"),
    PlatformSourceSetConvention("js"),
    PlatformSourceSetConvention("wasmJs", experimentalAnnotation = "@ExperimentalWasmDsl"),
    PlatformSourceSetConvention("wasmWasi", experimentalAnnotation = "@ExperimentalWasmDsl"),
)


private fun List<PlatformSourceSetConvention>.generatePlatformInterfaceDeclarations(): String = buildString {
    appendLine()
    appendLine("// DO NOT EDIT MANUALLY! Generated by ${object {}.javaClass.enclosingClass.name}")

    for (entry in this@generatePlatformInterfaceDeclarations.sortedBy { it.targetName }) {
        fun add(compilationName: String) {
            appendLine()
            val sourceSetName = lowerCamelCaseName(entry.targetName, compilationName)
            appendLine(leafSourceSetAccessorKdoc(sourceSetName, entry.targetName, compilationName))
            val deprecation = entry.deprecation
            if (deprecation != null) {
                appendLine("@Deprecated(${deprecation.message}, level = DeprecationLevel.${deprecation.level.name})")
            }
            if (entry.experimentalAnnotation != null) {
                appendLine(entry.experimentalAnnotation)
            }
            appendLine(sourceSetInterfaceDeclaration(sourceSetName))
        }
        add("main")
        add("test")
    }
}

private fun List<CommonSourceSetConvention>.generateCommonInterfaceDeclarations(): String = buildString {
    appendLine()
    appendLine("// DO NOT EDIT MANUALLY! Generated by ${object {}.javaClass.enclosingClass.name}")

    for (entry in this@generateCommonInterfaceDeclarations) {
        fun add(compilationName: String) {
            appendLine()
            val sourceSetName = lowerCamelCaseName(entry.baseName, compilationName)
            appendLine(commonSourceSetAccessorKdoc(sourceSetName, entry.allTargetsDescription, entry.sampleTarget1, entry.sampleTarget2))
            appendLine(sourceSetInterfaceDeclaration(sourceSetName))
        }
        add("main")
        add("test")
    }
}

private fun sourceSetInterfaceDeclaration(sourceSetName: String) =
    "val NamedDomainObjectContainer<KotlinSourceSet>.$sourceSetName: NamedDomainObjectProvider<KotlinSourceSet>"

private fun sourceSetImplDeclaration(sourceSetName: String) =
    "override val NamedDomainObjectContainer<KotlinSourceSet>.${sourceSetName} by KotlinSourceSetConvention"

private fun leafSourceSetAccessorKdoc(sourceSetName: String, targetName: String, compilationName: String) = """
    /**
     * Static accessor for the $compilationName Kotlin Source Set of $targetName target.
     * Declare $targetName target to access this source set.
     * If $targetName target wasn't declared, accessing this source set will cause a runtime error during configuration time.
     *
     * Sample:
     *
     * ```kotlin
     * kotlin {
     *    $targetName() // Target is declared, $sourceSetName source set is created
     *
     *    sourceSets {
     *      $sourceSetName.dependencies {
     *          // Add $sourceSetName dependencies here
     *      }
     *    }
     * }
     * ```
     *
     * @since 2.0.20
     */
""".trimIndent()

private fun commonSourceSetAccessorKdoc(sourceSetName: String, description: String, sampleTarget1: String, sampleTarget2: String) = """
    /**
     * Static accessor for shared kotlin Source Set between $description.
     * Declare at least one of the targets mentioned above to access this source set.
     * If no targets were declared, accessing this source set will cause a runtime error during configuration time.
     *
     * Sample:
     *
     * ```kotlin
     * kotlin {
     *    $sampleTarget1()
     *    $sampleTarget2()
     *
     *    sourceSets {
     *      $sourceSetName.dependencies {
     *          // Add $sourceSetName dependencies here
     *      }
     *    }
     * }
     * ```
     *
     * @since 1.9.20
     */
""".trimIndent()

private fun List<PlatformSourceSetConvention>.generatePlatformImplDeclarations(): String = buildString {
    appendLine()
    appendLine("// DO NOT EDIT MANUALLY! Generated by ${object {}.javaClass.enclosingClass.name}")

    for (entry in this@generatePlatformImplDeclarations.sortedBy { it.targetName }) {
        fun add(compilationName: String) {
            appendLine()
            val sourceSetName = lowerCamelCaseName(entry.targetName, compilationName)
            val deprecation = entry.deprecation
            if (deprecation != null) {
                appendLine("@Deprecated(${deprecation.message}, level = DeprecationLevel.${deprecation.level.name})")
            }
            if (entry.experimentalAnnotation != null) {
                appendLine(entry.experimentalAnnotation)
            }
            appendLine(sourceSetImplDeclaration(sourceSetName))
        }
        add("main")
        add("test")
    }
}

private fun List<CommonSourceSetConvention>.generateCommonImplDeclarations(): String = buildString {
    appendLine()
    appendLine("// DO NOT EDIT MANUALLY! Generated by ${object {}.javaClass.enclosingClass.name}")

    for (entry in this@generateCommonImplDeclarations) {
        fun add(compilationName: String) {
            appendLine()
            val sourceSetName = lowerCamelCaseName(entry.baseName, compilationName)
            appendLine(sourceSetImplDeclaration(sourceSetName))
        }
        add("main")
        add("test")
    }
}
