/*
 * 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.
 */

import java.util.concurrent.TimeUnit
import kotlin.system.exitProcess

fun getCommits(fromRevision: String, toRevision: String): List<Commit> {
    val cmd = "git rev-list --cherry-pick --format=medium $fromRevision...$toRevision -- ."
    val process = ProcessBuilder(*(cmd.split(" ")).toTypedArray())
        .redirectOutput(ProcessBuilder.Redirect.PIPE)
        .redirectError(ProcessBuilder.Redirect.INHERIT)
        .start()
    process.waitFor(60, TimeUnit.MINUTES)

    val commits = process.inputStream.bufferedReader().readText().split("\n\ncommit ")
    return commits.mapNotNull { commit ->
        val sanitizedCommit = commit.removePrefix("commit ").padEnd(2, '\n')
        val commitGroups = parseCommit.find(sanitizedCommit)?.groupValues ?: return@mapNotNull null
        if (commitGroups.size != 4) {
            // group 1: commit hash
            // group 2: commit message
            // group 3: title
            return@mapNotNull null
        }
        val commitId = commitGroups[1]
        val title = commitGroups[3]
        val commitMessage = commitGroups[2]
        val changeId = matchChangeId.find(commitMessage)?.groupValues?.let { it[1] }
        val relnote = matchRelnote.find(commitMessage)?.groupValues?.let { it[1] }
        val issues = matchIssue.findAll(commitMessage).mapNotNull { it.groups[1]?.value }.toList()
        return@mapNotNull Commit(commitId, title, changeId, relnote?.trim('\"'), issues)
    }
}

val parseCommit =
    Regex("^([0-9a-f]+)\\nAuthor:.*?\\nDate:.*?\\n[\\s]+((.*?)\\n[\\s\\S]+)", RegexOption.MULTILINE)
val matchRelnote =
    Regex(
        "^\\s*Relnote:\\s+(\"{3}.+\"{3}|\".+\"|[^\\n]+)$",
        setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL),
    )
val matchChangeId = Regex("Change-Id:\\s+(I[0-9a-f]+)", RegexOption.IGNORE_CASE)
val matchIssue = Regex("(?:Bug|Fixes):\\s+\\[?(\\d+|KT-\\d+)\\]?", RegexOption.IGNORE_CASE)

data class Commit(
    val commit: String,
    val title: String,
    val changeId: String?,
    val relnote: String?,
    val issues: List<String>,
)

fun commitToGitHubUrl(commit: String) = "https://github.com/JetBrains/kotlin/commit/$commit"
fun issueToBuganizerUrl(issue: String): String = "https://issuetracker.google.com/issues/$issue"
fun issueToYoutrackUrl(issue: String): String = "https://youtrack.jetbrains.com/issue/$issue"

fun Commit.asReleaseNote(): String {
    val commitLink = "[`${commit.take(7)}`](${commitToGitHubUrl(commit)})"
    val issueLinks = issues.joinToString(", ") { issue ->
        if (issue.startsWith("KT-")) {
            "[`$issue`](${issueToYoutrackUrl(issue)})"
        } else {
            "[`b/$issue`](${issueToBuganizerUrl(issue)})"
        }
    }
    val link = if (issueLinks.isEmpty()) commitLink else issueLinks
    val text = (relnote ?: title).split(" ").joinToString(" ") { if (it.startsWith('@')) "`$it`" else it }
    return "- $link $text"
}

fun main(vararg args: String) {
    if (args.isEmpty()) {
        println(
            """
        Usage: ./gradlew composeReleaseNotes --args="<from-tag> <to-tag>:"
        
        For example, to generate release notes for v2.0.0-RC2:
          ./gradlew composeReleaseNotes --args="v2.0.0-RC1 v2.0.0-RC2"
        """.trimIndent()
        )
        exitProcess(1)
    }

    val ignoreRelnotes = listOf("n/a")

    val fromRevision = args[0]
    val toRevision = args[1]

    val (fixes, features) = getCommits(fromRevision, toRevision)
        .filterDuplicates()
        .filter {
            (it.relnote != null && !ignoreRelnotes.contains(it.relnote.lowercase())) ||
                    it.issues.isNotEmpty()
        }
        .partition { it.issues.isNotEmpty() }

    println("### Compose compiler")
    println("#### New features")
    features.forEach { println(it.asReleaseNote()) }
    println("#### Fixes")
    fixes.forEach { println(it.asReleaseNote()) }
}

// Some commits may be duplicated, e.g. if a commit was cherry-picked from another branch with changes to tests.
fun List<Commit>.filterDuplicates(): List<Commit> {
    val counts = mutableMapOf<String, Int>()
    forEach {
        counts[it.title] = counts.getOrDefault(it.title, 0) + 1
    }

    return filter { counts[it.title] == 1 }
}