import org.gradle.internal.os.OperatingSystem
import javax.inject.Inject

// ----------------------- Publishing (kept from your original) -----------------------
publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
            artifact tasks.shadowJar
            groupId    = project.group ?: 'de.peeeq'
            artifactId = 'wurstscript'
            version    = project.version
        }
    }
    repositories {
        mavenLocal()
        // or: maven { url = uri("${buildDir}/repo") }
    }
}

// ----------------------- Common providers/locations ---------------------------------
def os = OperatingSystem.current()
def arch = System.getProperty("os.arch") // "amd64"/"x86_64"/"aarch64"

def plat = os.isWindows() ? "win-x64"
        : os.isMacOsX() ? (arch == "aarch64" ? "macos-arm64" : "macos-x64")
        : "linux-x64"

// Capture project version at configuration time (avoid Task.project at execution)
def versionString = project.version.toString()

// Allow adding modules if reflection/ServiceLoader pulls them in:
//   ./gradlew jlinkRuntime25 -PextraJdkModules=java.desktop,jdk.crypto.ec
def extraJdkModulesProvider = providers.gradleProperty("extraJdkModules").orElse("")

def fatJar = tasks.named('shadowJar').flatMap { it.archiveFile }

def jlinkWorkDir  = layout.buildDirectory.dir("jlink")
def modulesRawTxt  = jlinkWorkDir.map { it.file("modules.raw.txt") }
def modulesTxt     = jlinkWorkDir.map { it.file("modules.txt") }
def jreImageDir    = layout.buildDirectory.dir("jre-wurst-25")

def distRoot    = layout.buildDirectory.dir("dist/slim-${plat}")
def releasesDir = layout.buildDirectory.dir("releases")
def appCdsArchive = distRoot.map { it.file("wurst-runtime/wurst-lsp.jsa") }

// ----------------------- Toolchain / tool paths (providers) -------------------------
def toolchainSvc = extensions.getByType(JavaToolchainService)
def launcherProvider = toolchainSvc.launcherFor(java.toolchain)

// Prefer JAVA_HOME if set, otherwise toolchain
def javaHomeProvider =
        providers.environmentVariable("JAVA_HOME")
                .map { new File(it) }   // <-- no file()
                .orElse(launcherProvider.map { it.metadata.installationPath.asFile })

def jdepsPathProvider = javaHomeProvider.map { home ->
    new File(home, "bin/jdeps${os.isWindows() ? '.exe' : ''}").absolutePath
}
def jlinkPathProvider = javaHomeProvider.map { home ->
    new File(home, "bin/jlink${os.isWindows() ? '.exe' : ''}").absolutePath
}
def jmodsDirProvider = javaHomeProvider.map { home ->
    new File(home, "jmods").absolutePath
}

// ----------------------- Tasks: jdeps → jlink → assemble → package ------------------

// 1) Run jdeps and capture raw module deps

abstract class JdepsRawTask extends DefaultTask {
    @Inject abstract ExecOperations getExecOps()

    @InputFile
    abstract RegularFileProperty getInputJar()

    @Input
    abstract Property<String> getJdepsPath()

    @OutputFile
    abstract RegularFileProperty getOutFile()

    @TaskAction
    void runJdeps() {
        outFile.get().asFile.parentFile.mkdirs()

        def outBuf = new ByteArrayOutputStream()
        execOps.exec {
            executable = jdepsPath.get()
            args "--multi-release", "25",
                    "--ignore-missing-deps",
                    "--print-module-deps",
                    "--ignore-missing-deps",
                    inputJar.get().asFile.absolutePath
            standardOutput = outBuf
        }
        outFile.get().asFile.text = outBuf.toString("UTF-8").trim() + "\n"
    }
}

def fatJarProvider = tasks.named("shadowJar").flatMap { it.archiveFile } // Provider<RegularFile>

tasks.register("jdepsRaw", JdepsRawTask) {
    group = "distribution"
    description = "Runs jdeps and writes raw module deps to a file."
    dependsOn("shadowJar")

    // IMPORTANT: RegularFileProperty expects a Provider<RegularFile>
    inputJar.set(fatJarProvider)

    jdepsPath.set(jdepsPathProvider) // Provider<String>
    outFile.set(modulesRawTxt)       // Provider<RegularFile>
}


// 1b) Normalize jdeps output into modules.txt (add jdwp + extra modules)
tasks.register("jdepsModules") {
    description = "Detects required JDK modules for the fat compiler.jar via jdeps"
    group = "distribution"

    dependsOn("jdepsRaw")

    inputs.file(modulesRawTxt)
    inputs.property("extraJdkModules", extraJdkModulesProvider)
    outputs.file(modulesTxt)

    // capture provider values at configuration time (avoid Task.project at execution)
    def extraMods = extraJdkModulesProvider.get().toString().trim()

    doLast {
        def raw = modulesRawTxt.get().asFile.text.trim()
        def detected = raw.isEmpty() ? "java.base" : raw

        def jdwpModule = "jdk.jdwp.agent"
        if (!detected.split(",").contains(jdwpModule)) {
            detected = detected + "," + jdwpModule
        }
        if (!extraMods.isEmpty()) {
            detected = detected + "," + extraMods
        }

        modulesTxt.get().asFile.parentFile.mkdirs()
        modulesTxt.get().asFile.text = detected
        logger.lifecycle("[jdeps] Using modules: ${detected}")
    }
}

// 2) Build slim runtime with jlink (output dir must not exist)
tasks.register("jlinkRuntime25", Exec) {
    description = "Builds a slim Java 25 runtime image containing only the needed modules"
    group = "distribution"

    dependsOn("jdepsModules")

    inputs.file(modulesTxt)
    inputs.property("javaHome", javaHomeProvider)
    inputs.property("jmodsDir", jmodsDirProvider)
    outputs.dir(jreImageDir)

    doFirst {
        def mods = modulesTxt.get().asFile.text.trim()
        if (!mods) throw new GradleException("No modules detected for jlink.")

        def outDir = jreImageDir.get().asFile
        if (outDir.exists()) {
            outDir.deleteDir() // no project access
        }
        outDir.parentFile.mkdirs()

        logger.lifecycle("[jlink] Using: ${jlinkPathProvider.get()}")
        logger.lifecycle("[jlink] JAVA_HOME: ${javaHomeProvider.get()}")
        logger.lifecycle("[jlink] jmods: ${jmodsDirProvider.get()}")
        logger.lifecycle("[jlink] Modules: ${mods}")

        executable = jlinkPathProvider.get()
        args "--verbose",
                "--module-path", jmodsDirProvider.get(),
                "--add-modules", mods,
                "--no-header-files",
                "--no-man-pages",
                "--strip-debug",
                "--compress=zip-9",
                "--output", outDir.absolutePath
    }
}

// 3) Assemble folder layout: jre + compiler.jar (no manifest)
tasks.register("assembleSlimCompilerDist", Copy) {
    description = "Assembles dist folder with JRE and compiler.jar (no manifest)."
    group = "distribution"

    if (os.isMacOsX()) {
        // On macOS, skip jlink entirely. Copying the original Temurin JDK preserves
        // Apple's codesignatures on dylibs/executables, which jlink would invalidate.
        // jmods/ (jlink inputs, ~150 MB) and include/ (C headers) are dev-only.
        dependsOn("shadowJar")
        from(javaHomeProvider) {
            into("wurst-runtime")
            exclude("jmods/**", "include/**")
        }
    } else {
        dependsOn("jlinkRuntime25", "shadowJar")
        from(jreImageDir) { into("wurst-runtime") }
    }

    from(fatJar) { into("wurst-compiler") }
    into(distRoot)

    doLast {
        logger.lifecycle("[dist] Folder ready at: ${distRoot.get().asFile}")
    }
}

// Add launch scripts
tasks.named("assembleSlimCompilerDist", Copy) { t ->
    if (os.isWindows()) {
        from("../Wurstpack/wurstscript/wurstscript.cmd") { into(".") }
        from("../Wurstpack/wurstscript/wurstscript")     { into(".") }
        from("../Wurstpack/wurstscript/grill.cmd")       { into(".") }
        from("../Wurstpack/wurstscript/grill")           { into(".") }
    } else {
        from("../Wurstpack/wurstscript/wurstscript") { into(".") }
        from("../Wurstpack/wurstscript/grill")       { into(".") }
    }
}

tasks.register("generateAppCdsArchive", Exec) {
    description = "Best-effort AppCDS archive generation for language-server startup."
    group = "distribution"
    dependsOn("assembleSlimCompilerDist")

    inputs.file(fatJar)
    inputs.dir(distRoot)
    outputs.file(appCdsArchive)
    ignoreExitValue = true

    doFirst {
        def distDir = distRoot.get().asFile
        def archiveFile = appCdsArchive.get().asFile
        def runtimeJava = new File(distDir, "wurst-runtime/bin/java${os.isWindows() ? '.exe' : ''}")
        def compilerJar = new File(distDir, "wurst-compiler/${fatJar.get().asFile.name}")

        archiveFile.parentFile.mkdirs()
        if (archiveFile.exists()) {
            archiveFile.delete()
        }

        executable = runtimeJava.absolutePath
        args "-Xshare:auto",
            "-XX:ArchiveClassesAtExit=${archiveFile.absolutePath}",
            "-jar", compilerJar.absolutePath,
            "-languageServerAppCdsTrain"

        logger.lifecycle("[appcds] Training LS startup with ${runtimeJava.absolutePath}")
    }

    doLast {
        def archiveFile = appCdsArchive.get().asFile
        if (executionResult.get().exitValue != 0 || !archiveFile.exists()) {
            logger.warn("[appcds] Archive generation did not succeed on ${plat}; continuing without shipped AppCDS archive.")
        } else {
            logger.lifecycle("[appcds] Wrote ${archiveFile.absolutePath}")
        }
    }
}

// 4) Package ZIP on all platforms
tasks.register("packageSlimCompilerDistZip", Zip) {
    description = "Packages slim dist as a ZIP archive (all platforms)."
    group = "distribution"
    dependsOn("generateAppCdsArchive")

    from(distRoot)
    destinationDirectory.set(releasesDir)
    archiveFileName.set("wurst-compiler-${versionString}-${plat}.zip")
}

// OS-aware convenience wrapper now just points to ZIP
tasks.register("packageSlimCompilerDist") {
    description = "Packages slim dist for the current platform (ZIP on all OSes)."
    group = "distribution"
    dependsOn("packageSlimCompilerDistZip")
}
