plugins {
    id 'java'
    id 'eclipse'
    id 'application'
    id 'maven-publish'
    id 'signing'
    id 'pmd'
    id 'jacoco'
    id 'info.solidsoft.pitest' version '1.19.0'
    id 'me.champeau.jmh' version '0.7.2'
    id 'org.owasp.dependencycheck' version '12.2.1'
    id 'org.cyclonedx.bom' version '3.2.4'
    id 'com.palantir.git-version' version '4.0.0'
}

group = 'org.egothor'
version = gitVersion(prefix:'release@')

def benchmarkReportsDirectory = layout.buildDirectory.dir('reports/jmh')
def sbomReportsDirectory = layout.buildDirectory.dir('reports/sbom')

def nvdApiKey = providers.gradleProperty('nvdApiKey')
        .orElse(providers.environmentVariable('NVD_API_KEY'))
        .orNull
def dependencyCheckSuppressionFile = rootProject.file('dependency-suppression.xml')

apply from: 'gradle/maven-pom.gradle'

configurations {
    mockitoAgent
}

java {
    withSourcesJar()
    withJavadocJar()

    sourceCompatibility = JavaVersion.VERSION_21
    targetCompatibility = JavaVersion.VERSION_21
}

tasks.withType(AbstractArchiveTask).configureEach {
    preserveFileTimestamps = false
    reproducibleFileOrder = true
}

jacoco {
    toolVersion = '0.8.14'
}

pmd {
    consoleOutput = true
    toolVersion = '7.20.0'
    sourceSets = [sourceSets.main]
    ruleSetFiles = files(rootProject.file(".ruleset"))
}

dependencyLocking {
    lockAllConfigurations()

    lockMode = LockMode.STRICT
}

dependencies {
    jmhImplementation sourceSets.main.output

    testImplementation platform(libs.junit.bom)
    testImplementation libs.junit.jupiter
    testRuntimeOnly libs.junit.platform.launcher

    testImplementation libs.mockito.core
    testImplementation libs.mockito.junit.jupiter
    testImplementation libs.jqwik

    mockitoAgent(libs.mockito.core) {
        transitive = false
    }
}

dependencyCheck {
    failBuildOnCVSS = 7.0
    failOnError = true
    autoUpdate = true
    formats = ['HTML', 'JSON']
    outputDirectory = layout.buildDirectory.dir('reports/dependency-check').get().asFile.absolutePath

    /*
     * Keep the scan focused on actual Java dependency inputs used by this project.
     * testRuntimeClasspath is included intentionally because the current external
     * dependency surface is primarily test-scoped.
     */
    scanConfigurations = ['runtimeClasspath', 'testRuntimeClasspath', 'mockitoAgent']
    skipTestGroups = false

    analyzers {
        experimentalEnabled = false
        centralEnabled = true
    }

    nvd {
        apiKey = nvdApiKey
        delay = nvdApiKey != null ? 3500 : 8000
        validForHours = 4
    }

    if (dependencyCheckSuppressionFile.exists()) {
        suppressionFile = dependencyCheckSuppressionFile.absolutePath
        failBuildOnUnusedSuppressionRule = true
    }
}

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

    doFirst {
        jvmArgs "-javaagent:${configurations.mockitoAgent.singleFile}"
    }

    /*
     * Bundled dictionary integration tests compile and reload large real-world
     * stemming dictionaries, including large language resources such as es_es.
     * The default Gradle test executor heap is too small for this workload.
     */
    minHeapSize = '1g'
    maxHeapSize = '4g'

    finalizedBy(tasks.named('jacocoTestReport'))

    reports {
        junitXml.required = true
        html.required = true
    }
}

tasks.withType(Pmd).configureEach {
    reports {
        xml.required = true
        html.required = true
    }
}

tasks.named('jacocoTestReport', JacocoReport) {
    dependsOn(tasks.named('test'))

    classDirectories.setFrom(
            files(sourceSets.main.output).asFileTree.matching {
                exclude 'org/egothor/stemmer/StemmerKnowledgeExperiment*'
                exclude 'org/egothor/stemmer/DiacriticStripper*'
            }
    )

    reports {
        xml.required = true
        csv.required = false
        html.required = true
    }
}

tasks.named('check') {
    dependsOn(tasks.named('jacocoTestReport'))
    // no-default, only on-demand: dependsOn(tasks.named('dependencyCheckAnalyze'))
}

allprojects {
    tasks.matching { it.name == 'cyclonedxDirectBom' }.configureEach {
        includeConfigs = ['runtimeClasspath', 'compileClasspath']
        skipConfigs = ['testRuntimeClasspath', 'testCompileClasspath', 'jmh.*', 'mockitoAgent']
        includeBomSerialNumber = true
        includeLicenseText = false
        includeMetadataResolution = true
        includeBuildSystem = true
    }
}

tasks.named('cyclonedxBom') {
    includeBomSerialNumber = true
    includeLicenseText = false
    includeBuildSystem = true
    jsonOutput.set(sbomReportsDirectory.map { it.file('radixor-sbom.json') })
    xmlOutput.set(sbomReportsDirectory.map { it.file('radixor-sbom.xml') })
}

pitest {
    pitestVersion = '1.22.1'
    junit5PluginVersion = '1.2.3'

    targetClasses = [
        'org.egothor.stemmer.*',
        'org.egothor.stemmer.trie.*'
    ]
    targetTests = [
        'org.egothor.stemmer.*Test',
        'org.egothor.stemmer.trie.*Test'
    ]

    excludedClasses = [
        'org.egothor.stemmer.Compile*',
        'org.egothor.stemmer.StemmerPatchTrieLoader*',
        'org.egothor.stemmer.StemmerKnowledgeExperiment*',
        'org.egothor.stemmer.StemmerKnowledgeExperimentCli*'
    ]
    excludedTestClasses = [
        'org.egothor.stemmer.CompileIntegrationTest',
        'org.egothor.stemmer.StemmerPatchTrieLoaderTest',
        'org.egothor.stemmer.StemmerKnowledgeExperimentTest'
    ]
    outputFormats = ['XML', 'HTML']
    timestampedReports = false
    exportLineCoverage = true
    failWhenNoMutations = true
    threads = Math.max(1, Runtime.runtime.availableProcessors().intdiv(2))
}

application {
    mainClass = 'org.egothor.stemmer.Compile'
    applicationName = 'radixor'
    executableDir = 'bin'
}

tasks.register('stemmerKnowledgeExperiment', JavaExec) {
    group = 'application'
    description = 'Runs the stemmer knowledge evaluation experiment.'
    classpath = sourceSets.main.runtimeClasspath
    mainClass = 'org.egothor.stemmer.StemmerKnowledgeExperimentCli'
}

distributions {
    main {
        distributionBaseName = 'radixor'

        contents {
            from('README.md') {
                into ''
            }

            from('LICENSE') {
                into ''
            }

            from('LICENSE-stemmer-data') {
                into ''
            }

            from('docs') {
                into 'docs'
                include '**/*.md'
            }

            from(layout.buildDirectory.dir('generated/release-notes')) {
                into ''
                include 'CHANGELOG.md'
            }
        }
    }
}

tasks.named('startScripts') {
    applicationName = 'radixor'
}

tasks.named('distZip', Zip) {
    archiveBaseName = 'radixor'
    archiveClassifier = 'bin'
}

tasks.named('distTar') {
    enabled = false
}

jmh {
    jmhVersion = '1.37'
    warmupIterations = 3
    iterations = 5
    fork = 1
    benchmarkMode = ['avgt']
    timeUnit = 'ns'
    resultFormat = 'CSV'
    resultsFile = benchmarkReportsDirectory.map { it.file('jmh-results.csv').asFile }.get()
    humanOutputFile = benchmarkReportsDirectory.map { it.file('jmh-results.txt').asFile }.get()
    duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE
}

tasks.named('jmh') {
    group = 'verification'
    description = 'Runs JMH benchmarks for the Radixor algorithmic core and Snowball comparison suite.'
}

tasks.register('regressionArtifactGenerator', JavaExec) {
    group = 'verification'
    description = 'Generates deterministic compiled trie regression artifacts.'

    classpath = sourceSets.test.runtimeClasspath
    mainClass = 'org.egothor.stemmer.RegressionArtifactGenerator'

    if (project.hasProperty('regressionInput')) {
        args '--input', project.property('regressionInput').toString()
    }
    if (project.hasProperty('regressionOutput')) {
        args '--output', project.property('regressionOutput').toString()
    }
    if (project.hasProperty('regressionStoreOriginal')) {
        args '--store-original', project.property('regressionStoreOriginal').toString()
    }
    if (project.hasProperty('regressionReductionMode')) {
        args '--reduction-mode', project.property('regressionReductionMode').toString()
    }
}

tasks.register('printDependencyCheckNvdConfig') {
    doLast {
        System.out.println("NVD API key present: " + (nvdApiKey != null && !nvdApiKey.isBlank()))
    }
}

tasks.named('dependencyCheckAnalyze') {
    dependsOn(tasks.named('printDependencyCheckNvdConfig'))
}

javadoc {
    failOnError = false

    options.addStringOption('Xdoclint:all,-missing', '-quiet')
    options.addBooleanOption('html5', true)
    options.tags('apiNote:a:API Note:')
    options.tags('implSpec:a:Implementation Requirements:')
    options.tags('implNote:a:Implementation Note:')
    options.tags('param')
    options.tags('return')
    options.tags('throws')
    options.tags('since')
    options.tags('version')
    options.tags('serialData')
    options.tags('factory')
    options.tags('see')

    options.use = true
    options.author = true
    options.version = true
    options.windowTitle = 'Radixor - Egothor Stemmer'
    options.docTitle = 'Radixor - Egothor Stemmer API'
    options.overview = file('src/main/javadoc/overview.html')
    options.bottom = """
        <div class="legal-copy">
            &copy; 2026 Egothor
            <br/>
            Licensed under <a href="https://github.com/leogalambos/Radixor/blob/main/LICENSE">BSD-3-Clause</a>
        </div>
    """
    options.links('https://docs.oracle.com/en/java/javase/21/docs/api/')
    options.group('Core Stemming API', 'org.egothor.stemmer')
    options.group('Trie Infrastructure', 'org.egothor.stemmer.trie')

    source = sourceSets.main.allJava
}

apply from: 'gradle/snowball-benchmarks.gradle'

gradle.taskGraph.whenReady { taskGraph ->
    def banner = """
\u001B[34m

8888888888 .d8888b.   .d88888b. 88888888888 888    888  .d88888b.  8888888b.
888       d88P  Y88b d88P" "Y88b    888     888    888 d88P" "Y88b 888   Y88b
888       888    888 888     888    888     888    888 888     888 888    888
8888888   888        888     888    888     8888888888 888     888 888   d88P
888       888  88888 888     888    888     888    888 888     888 8888888P"
888       888    888 888     888    888     888    888 888     888 888 T88b
888       Y88b  d88P Y88b. .d88P    888     888    888 Y88b. .d88P 888  T88b
8888888888 "Y8888P88  "Y88888P"     888     888    888  "Y88888P"  888   T88b

\u001B[36m
 Project : ${project.name}
 Version : ${project.version}
\u001B[0m
"""
    println banner
}
