plugins {
    id 'java'
    id 'fabric-loom' apply false
}

/**
 * Compatibility buckets, not Minecraft versions.
 *
 * Every project under :platform is compiled against one representative version
 * and declares -- in its own gradle.properties -- the full range of Minecraft
 * versions that the resulting jar actually runs on. The grouping comes from
 * measured API breakpoints, documented in docs/PORTING.md.
 */
ext.platformProjects = { subprojects.findAll { it.path.startsWith(':platform:') } }
ext.sharedProjects = { subprojects.findAll { it.path.startsWith(':shared:') } }

subprojects {
    apply plugin: 'java'

    group = rootProject.mod_group
    version = rootProject.mod_version

    repositories {
        maven { name = 'Fabric'; url = 'https://maven.fabricmc.net/' }
        mavenCentral()
    }

    dependencies {
        compileOnly "org.projectlombok:lombok:${rootProject.lombok_version}"
        annotationProcessor "org.projectlombok:lombok:${rootProject.lombok_version}"

        testCompileOnly "org.projectlombok:lombok:${rootProject.lombok_version}"
        testAnnotationProcessor "org.projectlombok:lombok:${rootProject.lombok_version}"
        testImplementation platform("org.junit:junit-bom:${rootProject.junit_version}")
        testImplementation 'org.junit.jupiter:junit-jupiter'
        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    }

    tasks.withType(JavaCompile).configureEach {
        options.encoding = 'UTF-8'
    }

    tasks.withType(Test).configureEach {
        useJUnitPlatform()
    }
}

// ---------------------------------------------------------------------------
// Shared modules: zero Minecraft on the classpath.
//
// These compile to Java 17 bytecode because 1.20-1.20.4 run on a Java 17 JVM.
// That is a real constraint on the code, not just a flag: no virtual threads
// and no Java 21 pattern matching in shared code.
// ---------------------------------------------------------------------------
configure(sharedProjects()) {
    // java-library so a module can re-export what its consumers legitimately
    // need: :shared:ui hands RenderBridge types straight to platform code.
    apply plugin: 'java-library'

    java {
        toolchain { languageVersion = JavaLanguageVersion.of(21) }
    }
    tasks.withType(JavaCompile).configureEach {
        options.release = rootProject.shared_java as int
    }

    dependencies {
        // Gson ships inside Minecraft's own dependency set on every supported
        // version, so we compile against it but never bundle it. See
        // docs/PORTING.md for the version range this has to stay within.
        compileOnly "com.google.code.gson:gson:${rootProject.gson_api_version}"
        compileOnly "org.slf4j:slf4j-api:${rootProject.slf4j_api_version}"

        testImplementation "com.google.code.gson:gson:${rootProject.gson_api_version}"
        testImplementation "org.slf4j:slf4j-api:${rootProject.slf4j_api_version}"
        testRuntimeOnly "org.slf4j:slf4j-simple:${rootProject.slf4j_api_version}"
    }
}

// ---------------------------------------------------------------------------
// Platform modules: the only code allowed to touch Minecraft types.
// ---------------------------------------------------------------------------
configure(platformProjects()) {
    apply plugin: 'fabric-loom'

    def mcVersion = project.property('minecraft_version')
    def mcJava = project.property('mc_java') as int
    def deobfuscated = project.property('deobfuscated').toBoolean()

    // Minecraft 26.x ships already deobfuscated, so Loom has nothing to remap.
    // It still insists on a mappings artifact carrying a 'named' namespace, so
    // we hand it a header-only tiny-v2 file: three namespaces, zero rows, which
    // Loom reads as "every name maps to itself".
    //
    // This is written during configuration rather than by a task because Loom
    // resolves the mappings configuration while it configures the project --
    // a task output would not exist yet. It is 47 bytes and deterministic.
    def identityMappings = null
    if (deobfuscated) {
        identityMappings = layout.buildDirectory
                .file("identity-mappings/identity-mappings-${mcVersion}.jar").get().asFile
        if (!identityMappings.exists()) {
            identityMappings.parentFile.mkdirs()
            new java.util.zip.ZipOutputStream(identityMappings.newOutputStream()).withCloseable { zip ->
                zip.putNextEntry(new java.util.zip.ZipEntry('mappings/mappings.tiny'))
                zip.write("tiny\t2\t0\tofficial\tintermediary\tnamed\n".getBytes('UTF-8'))
                zip.closeEntry()
            }
        }
    }

    java {
        // javac can always read older bytecode, so one toolchain covers every
        // bucket except 26.x, whose Minecraft classes are Java 25.
        toolchain { languageVersion = JavaLanguageVersion.of(Math.max(mcJava, 21)) }
        withSourcesJar()
    }

    // The compatibility layer. platform/common holds the adapter code that is
    // textually identical on all nine buckets -- the Fabric entrypoint, the
    // ClientBridge assembly, and the two seams (I18n, KeyboardHandler) that have
    // not moved since 1.20. It is a source root rather than a project because it
    // imports net.minecraft, so it has to be compiled once per bucket against
    // that bucket's Minecraft; there is no single jar it could produce.
    //
    // Everything it calls that *does* differ lives in dev.photosync.platform.impl,
    // which each bucket supplies from its own src/main/java under the same
    // package. Common code names those classes directly and the bucket's source
    // root satisfies them -- so the invariant half is written once, and the half
    // that churns is a per-bucket file the compiler checks.
    sourceSets.main.java.srcDir rootProject.file('platform/common/src/main/java')
    sourceSets.main.resources.srcDir rootProject.file('platform/common/src/main/resources')

    tasks.withType(JavaCompile).configureEach {
        options.release = mcJava
    }

    dependencies {
        minecraft "com.mojang:minecraft:${mcVersion}"

        if (deobfuscated) {
            mappings project.files(identityMappings)
        } else {
            mappings loom.officialMojangMappings()
        }

        modImplementation "net.fabricmc:fabric-loader:${project.property('loader_version')}"
        modImplementation "net.fabricmc.fabric-api:fabric-api:${project.property('fabric_api_version')}"

        // Shared code is compiled once per bucket's bytecode target and folded
        // into the platform jar below. No shading, no jar-in-jar: these are our
        // own classes and they carry no third-party dependencies.
        rootProject.sharedProjects().each { implementation it }
    }

    loom {
        runs {
            client {
                client()
                ideConfigGenerated = true
            }
        }
    }

    processResources {
        def tokens = [
                mod_id            : rootProject.mod_id,
                mod_name          : rootProject.mod_name,
                mod_version       : rootProject.mod_version,
                mod_description   : rootProject.mod_description,
                mod_license       : rootProject.mod_license,
                mod_sources       : rootProject.mod_sources,
                minecraft_range   : project.property('minecraft_range'),
                loader_version    : project.property('loader_version'),
                java_version      : String.valueOf(mcJava),
                // Mixin refuses to apply a class whose bytecode is newer than the
                // declared level, and platform classes are compiled at the
                // bucket's own level -- so this has to follow mc_java.
                mixin_compat      : "JAVA_${mcJava}",
        ]
        inputs.properties(tokens)
        filesMatching(['fabric.mod.json', '*.mixins.json']) { expand(tokens) }
    }

    jar {
        rootProject.sharedProjects().each { from it.sourceSets.main.output }

        manifest {
            attributes(
                    'Implementation-Title': rootProject.mod_name,
                    'Implementation-Version': rootProject.mod_version,
                    'Minecraft-Version': mcVersion,
                    'Minecraft-Range': project.property('minecraft_range')
            )
        }
    }

    // Give every bucket's artifact a distinguishable name.
    base.archivesName = "${rootProject.mod_id}-${mcVersion}"
}

// Convenience: build every bucket's shippable jar in one go.
tasks.register('buildAllPlatforms') {
    group = 'build'
    description = 'Builds the remapped mod jar for every compatibility bucket.'
    dependsOn platformProjects().collect { "${it.path}:build" }
}
