plugins {
	id 'java'
	id 'maven-publish'
	id 'java-gradle-plugin'
	id 'idea'
	id 'eclipse'
	id 'groovy'
	id 'checkstyle'
	id 'jacoco'
	id 'codenarc'
	alias(libs.plugins.kotlin) apply false // Delay this so we can perform magic 🪄 first.
	alias(libs.plugins.spotless)
	alias(libs.plugins.retry)
}

/**
 * Haha this is fun :) The Kotlin gradle plugin triggers deprecation warnings for custom configurations (https://youtrack.jetbrains.com/issue/KT-60879)
 * We need to make DefaultConfiguration.isSpecialCaseOfChangingUsage think that our configurstion is a special case and not deprecated.
 * We do this by setting DefaultConfiguration.roleAtCreation to LEGACY, thus isInLegacyRole will now return true.
 *
 * Yeah I know we can just ignore the deprecation warning, but doing so wouldn't alert us to issues when testing against pre-release Gradle versions. Also this is more fun :)
 */
def brokenConfigurations = [
	"commonDecompilerRuntimeClasspath",
	"fernflowerRuntimeClasspath",
	"cfrRuntimeClasspath",
	"vineflowerRuntimeClasspath"
]

configurations.configureEach {
	if (brokenConfigurations.contains(it.name)) {
		// For some reason Gradle stops us from using Groovy magic to do this, so lets do it the boring way.
		def field = org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.class.getDeclaredField("roleAtCreation")
		field.setAccessible(true)
		field.set(it, ConfigurationRoles.LEGACY)
	}
}

// Ensure we apply the Kotlin plugin after, to allow for the above configuration to take place first
apply plugin: libs.plugins.kotlin.get().pluginId

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

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
	kotlinOptions {
		jvmTarget = "17"
	}
}

group = "dev.architectury"
def baseVersion = '1.6'

def ENV = System.getenv()
def runNumber = ENV.GITHUB_RUN_NUMBER ?: "9999"

def isSnapshot = ENV.PR_NUM != null

if (!isSnapshot) {
	version = baseVersion + "." + runNumber
} else {
	version = baseVersion + "-PR." + ENV.PR_NUM + "." + runNumber
}

logger.lifecycle(":building plugin v${version}")

// We must build against the version of Kotlin Gradle ships with.
def kotlinVersion = KotlinDslVersion.current().getKotlinVersion()
if (libs.versions.kotlin.get() != kotlinVersion) {
	throw new IllegalStateException("Requires Kotlin version: ${kotlinVersion}")
}

repositories {
	mavenCentral()
	maven { url "https://maven.fabricmc.net/" }
	maven { url "https://maven.architectury.dev/" }
	maven {
		url "https://maven.minecraftforge.net/"
		content {
			excludeGroupByRegex "org\\.eclipse\\.?.*"
		}
	}
}

configurations {
	bootstrap {
		transitive false
	}
	compileClasspath.extendsFrom bootstrap
	runtimeClasspath.extendsFrom bootstrap
	testRuntimeClasspath.extendsFrom bootstrap
}

configurations.all {
	resolutionStrategy {
		// I am sorry, for now
		// failOnNonReproducibleResolution()
	}
}

sourceSets {
	commonDecompiler {
		java {
			srcDir("src/decompilers/common")
		}
	}
	fernflower {
		java {
			srcDir("src/decompilers/fernflower")
		}
	}
	cfr {
		java {
			srcDir("src/decompilers/cfr")
		}
	}
	vineflower {
		java {
			srcDir("src/decompilers/vineflower")
		}
	}
}

dependencies {
	implementation gradleApi()

	bootstrap project(":bootstrap")

	// libraries
	implementation libs.commons.io
	implementation libs.gson
	implementation libs.guava
	implementation libs.bundles.asm

	// game handling utils
	implementation (libs.fabric.stitch) {
		exclude module: 'mercury'
		exclude module: 'enigma'
	}

	// tinyfile management
	implementation libs.fabric.tiny.remapper
	implementation libs.fabric.access.widener
	implementation libs.fabric.mapping.io
	implementation (libs.fabric.lorenz.tiny) {
		transitive = false
	}
	implementation "dev.architectury:refmap-remapper:1.0.5"

	implementation libs.fabric.loom.nativelib

	// decompilers
	fernflowerCompileOnly runtimeLibs.fernflower
	fernflowerCompileOnly libs.fabric.mapping.io

	cfrCompileOnly runtimeLibs.cfr
	cfrCompileOnly libs.fabric.mapping.io

	vineflowerCompileOnly runtimeLibs.vineflower
	vineflowerCompileOnly libs.fabric.mapping.io

	fernflowerApi sourceSets.commonDecompiler.output
	cfrApi sourceSets.commonDecompiler.output
	vineflowerApi sourceSets.commonDecompiler.output

	implementation sourceSets.commonDecompiler.output
	implementation sourceSets.fernflower.output
	implementation sourceSets.cfr.output
	implementation sourceSets.vineflower.output

	// source code remapping
	implementation libs.fabric.mercury

	// Kotlin
	implementation(libs.kotlin.metadata) {
		transitive = false
	}

	// Kapt integration
	compileOnly libs.kotlin.gradle.plugin

	// Forge patches
	implementation libs.forge.installer.tools
	implementation libs.lorenz
	implementation libs.mcinjector
	implementation libs.opencsv
	implementation libs.forge.diffpatch
	implementation libs.datafixerupper
	implementation libs.at

	// Forge mods.toml parsing
	implementation libs.night.config.toml

	// Testing
	testImplementation(gradleTestKit())
	testImplementation(testLibs.spock) {
		exclude module: 'groovy-all'
	}
	testImplementation testLibs.junit.jupiter.engine
	testRuntimeOnly testLibs.junit.platform.launcher
	testImplementation (testLibs.javalin) {
		exclude group: 'org.jetbrains.kotlin'
	}
	testImplementation testLibs.mockito
	testImplementation testLibs.java.debug

	compileOnly runtimeLibs.jetbrains.annotations
	testCompileOnly runtimeLibs.jetbrains.annotations

	testCompileOnly (testLibs.mixin) {
		transitive = false
	}
}

jar {
	manifest {
		attributes 'Implementation-Version': project.version
	}

	from configurations.bootstrap.collect { it.isDirectory() ? it : zipTree(it) }
	from sourceSets.commonDecompiler.output.classesDirs
	from sourceSets.cfr.output.classesDirs
	from sourceSets.fernflower.output.classesDirs
	from sourceSets.vineflower.output.classesDirs
}

base {
	archivesName = project.name
}

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

java {
	withSourcesJar()
	sourceCompatibility = JavaVersion.VERSION_17
	targetCompatibility = JavaVersion.VERSION_17
}

spotless {
	lineEndings = com.diffplug.spotless.LineEnding.UNIX

	java {
		licenseHeaderFile(rootProject.file("HEADER")).yearSeparator("-")
		targetExclude("**/loom/util/DownloadUtil.java", "**/loom/util/FileSystemUtil.java")
		targetExclude("**/dev/architectury/**")
	}

	groovy {
		importOrder('java', 'javax', '', 'net.fabricmc', '\\#')
		licenseHeaderFile(rootProject.file("HEADER")).yearSeparator("-")
		greclipse()
	}

	groovyGradle {
		target 'src/**/*.gradle', '*.gradle'
		greclipse()

		targetExclude(
				// These files use a @MAPPINGS@ token which is not valid Groovy
				'**/projects/forge/simple/build.gradle',
				'**/projects/neoforge/simple/build.gradle'
				)
	}

	kotlin {
		licenseHeaderFile(rootProject.file("HEADER")).yearSeparator("-")
		targetExclude("**/build.gradle.kts")
		targetExclude("src/test/resources/projects/*/**")
		ktlint()
	}
}

checkstyle {
	configFile = file('checkstyle.xml')
	toolVersion = libs.versions.checkstyle.get()
}

// Workaround https://github.com/gradle/gradle/issues/27035
configurations.checkstyle {
	resolutionStrategy.capabilitiesResolution.withCapability("com.google.collections:google-collections") {
		select("com.google.guava:guava:0")
	}
}

codenarc {
	toolVersion = libs.versions.codenarc.get()
	configFile = file("codenarc.groovy")
}

gradlePlugin {
	plugins {
		fabricLoom {
			id = 'dev.architectury.loom'
			implementationClass = 'net.fabricmc.loom.bootstrap.LoomGradlePluginBootstrap'
		}
	}
}

jacoco {
	toolVersion = libs.versions.jacoco.get()
}

// Run to get test coverage.
jacocoTestReport {
	dependsOn test
	reports {
		xml.required = false
		csv.required = false
		html.outputLocation = file("${layout.buildDirectory.get().asFile}/jacocoHtml")
	}
}

test {
	maxHeapSize = "2560m"
	jvmArgs "-XX:+HeapDumpOnOutOfMemoryError"
	useJUnitPlatform()

	// Forward system prop onto tests.
	if (System.getProperty("fabric.loom.test.homeDir")) {
		systemProperty "fabric.loom.test.homeDir", System.getProperty("fabric.loom.test.homeDir")
	}


	if (ENV.CI) {
		retry {
			maxRetries = 3
		}
	}
}


import org.gradle.api.internal.artifacts.configurations.ConfigurationRoles
import org.gradle.launcher.cli.KotlinDslVersion
import org.gradle.util.GradleVersion
import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.Node

publishing {
	publications {
		if (isSnapshot) return

			// Also publish a snapshot so people can use the latest version if they wish
			snapshot(MavenPublication) { publication ->
				groupId project.group
				artifactId project.base.archivesName.get()
				version baseVersion + '-SNAPSHOT'

				from components.java
			}

		// Manually crate the plugin marker for snapshot versions
		snapshotPlugin(MavenPublication) {
			groupId 'dev.architectury.loom'
			artifactId 'dev.architectury.loom.gradle.plugin'
			version baseVersion + '-SNAPSHOT'

			pom.withXml {
				// Based off org.gradle.plugin.devel.plugins.MavenPluginPublishPlugin
				Element root = asElement()
				Document document = root.getOwnerDocument()
				Node dependencies = root.appendChild(document.createElement('dependencies'))
				Node dependency = dependencies.appendChild(document.createElement('dependency'))
				Node groupId = dependency.appendChild(document.createElement('groupId'))
				groupId.setTextContent(project.group)
				Node artifactId = dependency.appendChild(document.createElement('artifactId'))
				artifactId.setTextContent(project.archivesBaseName)
				Node version = dependency.appendChild(document.createElement('version'))
				version.setTextContent(baseVersion + '-SNAPSHOT')
			}
		}
	}

	repositories {
		if (ENV.MAVEN_PASS != null) {
			maven {
				url = "https://deploy.shedaniel.me/"
				credentials {
					username = "shedaniel"
					password = ENV.MAVEN_PASS
				}
			}
		}
	}
}

// Need to tweak this file to pretend we are compatible with j8 so the bootstrap will run.
tasks.withType(GenerateModuleMetadata) {
	doLast {
		def file = outputFile.get().asFile

		def metadata = new groovy.json.JsonSlurper().parseText(file.text)

		metadata.variants.each {
			it.attributes["org.gradle.jvm.version"] = 8
		}

		file.text = groovy.json.JsonOutput.toJson(metadata)
	}
}

// A task to output a json file with a list of all the test to run
task writeActionsTestMatrix() {
	doLast {
		def testMatrix = []
		file('src/test/groovy/net/fabricmc/loom/test/integration').traverse {
			if (it.name.endsWith("Test.groovy")) {
				if (it.name.endsWith("ReproducibleBuildTest.groovy")) {
					// This test gets a special case to run across all os's
					return
				}

				if (it.name.endsWith("DebugLineNumbersTest.groovy")) {
					// Known flakey test
					return
				}

				if (it.name.endsWith("FabricAPITest.groovy")) {
					// Arch: Disabled as it hangs
					return
				}

				if (it.name.endsWith("NeoForge1206Test.groovy") || it.name.endsWith("Forge1206Test.groovy")) {
					// Arch: The 1.20.6 tests require Java 21
					return
				}

				def className = it.path.toString().replace(".groovy", "")
				className = className.substring(className.lastIndexOf("integration/") + "integration/".length()).replace('/', '.')

				testMatrix.add("net.fabricmc.loom.test.integration.${className}")
			}
		}

		// Run all the unit tests together
		testMatrix.add("net.fabricmc.loom.test.unit.*")

		// Kotlin tests
		testMatrix.add("net.fabricmc.loom.test.kotlin.*")

		def json = groovy.json.JsonOutput.toJson(testMatrix)
		def output = file("build/test_matrix.json")
		output.parentFile.mkdir()
		output.text = json
	}
}

tasks.named('wrapper') {
	distributionType = Wrapper.DistributionType.ALL
}

/**
 * Run this task to download the gradle sources next to the api jar, you may need to manually attach the sources jar
 */
task downloadGradleSources() {
	doLast {
		// Awful hack to find the gradle api location
		def gradleApiFile = project.configurations.detachedConfiguration(dependencies.gradleApi()).files.stream()
				.find {
					it.name.startsWith("gradle-api")
				}

		def gradleApiSources = new File(gradleApiFile.absolutePath.replace(".jar", "-sources.jar"))
		def url = "https://services.gradle.org/distributions/gradle-${GradleVersion.current().getVersion()}-src.zip"

		gradleApiSources.delete()

		println("Downloading (${url}) to (${gradleApiSources})")
		gradleApiSources << new URL(url).newInputStream()
	}
}

tasks.withType(GenerateModuleMetadata) {
	enabled = false
}

task printActionsTestName(type: PrintActionsTestName) {
}

/**
 * Replaces invalid characters in test names for GitHub Actions artifacts.
 */
class PrintActionsTestName extends DefaultTask {
	@Input
	@Option(option = "name", description = "The test name")
	String testName

	@TaskAction
	def run() {
		def sanitised = testName.replace('*', '_')
		new File(System.getenv().GITHUB_OUTPUT) << "\ntest=$sanitised"
	}
}

apply from: rootProject.file('gradle/versions.gradle')
