package com.correx.infrastructure.tools

/**
 * Produces a unified-diff-style string from old and new content.
 * Uses LCS (Longest Common Subsequence) to find minimal edits.
 * Context lines: 3 before and after each hunk.
 */
object DiffUtil {

    private const val CONTEXT_LINES = 3

    fun unifiedDiff(oldContent: String, newContent: String, filePath: String? = null): String {
        if (oldContent == newContent) return ""

        val oldLines = oldContent.lines()
        val newLines = newContent.lines()

        val edits = computeEdits(oldLines, newLines)
        if (edits.isEmpty()) return ""

        return formatHunks(edits, filePath ?: "file")
    }

    // --- LCS-based edit computation ---

    private enum class Op { EQUAL, DELETE, INSERT }

    private data class Edit(val op: Op, val text: String)

    private fun computeEdits(oldLines: List<String>, newLines: List<String>): List<Edit> {
        val m = oldLines.size
        val n = newLines.size

        // LCS dynamic programming table
        val dp = Array(m + 1) { IntArray(n + 1) }
        for (i in 1..m) {
            for (j in 1..n) {
                dp[i][j] = if (oldLines[i - 1] == newLines[j - 1]) {
                    dp[i - 1][j - 1] + 1
                } else {
                    maxOf(dp[i - 1][j], dp[i][j - 1])
                }
            }
        }

        // Backtrack through LCS table to build edit list
        val result = mutableListOf<Edit>()
        var i = m
        var j = n
        val stack = ArrayDeque<Edit>()
        while (i > 0 || j > 0) {
            when {
                i > 0 && j > 0 && oldLines[i - 1] == newLines[j - 1] -> {
                    stack.addFirst(Edit(Op.EQUAL, newLines[j - 1]))
                    i--
                    j--
                }

                j > 0 && (i == 0 || dp[i][j - 1] >= dp[i - 1][j]) -> {
                    stack.addFirst(Edit(Op.INSERT, newLines[j - 1]))
                    j--
                }

                i > 0 -> {
                    stack.addFirst(Edit(Op.DELETE, oldLines[i - 1]))
                    i--
                }
            }
        }
        result.addAll(stack)
        return result
    }

    // --- Hunk formatting ---

    private fun formatHunks(edits: List<Edit>, filePath: String): String {
        val hunks = buildList {
            var i = 0
            while (i < edits.size) {
                if (edits[i].op != Op.EQUAL) {
                    // Find start of hunk: go back CONTEXT_LINES equal lines
                    val hunkStart = maxOf(findHunkStart(edits, i), 0)
                    // Find end of hunk: go forward CONTEXT_LINES equal lines after last change
                    val hunkEnd = findHunkEnd(edits, i)
                    add(Triple(hunkStart, hunkEnd, edits.subList(hunkStart, hunkEnd)))
                    i = hunkEnd
                } else {
                    i++
                }
            }
        }

        if (hunks.isEmpty()) return ""

        val sb = StringBuilder()
        sb.appendLine("--- a/$filePath")
        sb.appendLine("+++ b/$filePath")

        for ((start, end, hunkEdits) in hunks) {
            val oldStart = countOldLinesBefore(edits, start) + 1
            val oldCount = countOpInRange(hunkEdits, Op.EQUAL) + countOpInRange(hunkEdits, Op.DELETE)
            val newStart = countNewLinesBefore(edits, start) + 1
            val newCount = countOpInRange(hunkEdits, Op.EQUAL) + countOpInRange(hunkEdits, Op.INSERT)

            sb.appendLine("@@ -$oldStart,$oldCount +$newStart,$newCount @@")
            for (edit in hunkEdits) {
                when (edit.op) {
                    Op.EQUAL -> sb.appendLine(" ${edit.text}")
                    Op.DELETE -> sb.appendLine("-${edit.text}")
                    Op.INSERT -> sb.appendLine("+${edit.text}")
                }
            }
        }

        return sb.toString().trimEnd()
    }

    private fun findHunkStart(edits: List<Edit>, changeIdx: Int): Int {
        var idx = changeIdx
        var ctx = CONTEXT_LINES
        while (idx > 0 && ctx > 0) {
            idx--
            if (edits[idx].op == Op.EQUAL) ctx--
        }
        return idx
    }

    private fun findHunkEnd(edits: List<Edit>, changeIdx: Int): Int {
        var idx = changeIdx
        var ctx = CONTEXT_LINES
        while (idx < edits.size && (edits[idx].op != Op.EQUAL || ctx > 0)) {
            if (edits[idx].op == Op.EQUAL) ctx--
            idx++
        }
        return idx
    }

    private fun countOldLinesBefore(edits: List<Edit>, upTo: Int): Int =
        edits.subList(0, upTo).count { it.op != Op.INSERT }

    private fun countNewLinesBefore(edits: List<Edit>, upTo: Int): Int =
        edits.subList(0, upTo).count { it.op != Op.DELETE }

    private fun countOpInRange(edits: List<Edit>, op: Op): Int =
        edits.count { it.op == op }
}
