package com.correx.core.validation.artifact

/**
 * Deterministic, pure text repairs for near-miss LLM artifact output. Each is `(String) -> String`
 * and safe to apply to already-valid JSON (idempotent / pass-through). The pipeline applies them in
 * order, re-parsing after each, and stops at the first that yields parseable JSON.
 *
 * Motivated by weak local models that emit prose-wrapped JSON, code-fenced JSON, trailing commas,
 * or code-as-content — see docs/specs/2026-07-04-artifact-emission-pipeline.md.
 *
 * ponytail: JSON-only ladder. json5/yaml/toml/xml deferred — no artifact kind emits them and each
 * pulls a new dep. Add a parser to the pipeline when a kind actually needs it.
 */
object ArtifactRepairs {

    val ladder: List<(String) -> String> = listOf(
        ::stripCodeFences,
        ::stripSurroundingProse,
        ::removeTrailingCommas,
        ::quoteUnquotedKeys,
        ::fixCommonEscapes,
    )

    /** Drop a markdown code fence anywhere in the text, keeping the first fenced block's body. */
    fun stripCodeFences(text: String): String {
        val open = text.indexOf("```")
        if (open < 0) return text
        val bodyStart = text.indexOf('\n', open).let { if (it < 0) return text else it + 1 }
        val close = text.indexOf("```", bodyStart)
        if (close < 0) return text
        return text.substring(bodyStart, close).trim()
    }

    /**
     * Keep only the first balanced `{...}` (or `[...]`) object, discarding surrounding prose.
     * String-aware so braces inside quoted values don't confuse the matcher. This is the workhorse
     * for prose-wrapped JSON and code-as-content.
     */
    fun stripSurroundingProse(text: String): String {
        val start = text.indexOfFirst { it == '{' || it == '[' }
        if (start < 0) return text
        val open = text[start]
        val close = if (open == '{') '}' else ']'
        var depth = 0
        var inString = false
        var escaped = false
        for (i in start until text.length) {
            val c = text[i]
            when {
                escaped -> escaped = false
                c == '\\' && inString -> escaped = true
                c == '"' -> inString = !inString
                inString -> {}
                c == open -> depth++
                c == close -> {
                    depth--
                    if (depth == 0) return text.substring(start, i + 1)
                }
            }
        }
        return text
    }

    /** Remove commas that immediately precede a closing `}` or `]` (ignoring whitespace). */
    fun removeTrailingCommas(text: String): String =
        Regex(""",(\s*[}\]])""").replace(text) { it.groupValues[1] }

    /** Quote bare identifier keys: `{ key: 1 }` → `{ "key": 1 }`. Skips already-quoted keys. */
    fun quoteUnquotedKeys(text: String): String =
        Regex("""([{,]\s*)([A-Za-z_][A-Za-z0-9_]*)(\s*):""")
            .replace(text) { "${it.groupValues[1]}\"${it.groupValues[2]}\"${it.groupValues[3]}:" }

    /** Normalize smart quotes and collapse doubled backslashes that break JSON string escapes. */
    fun fixCommonEscapes(text: String): String =
        text.replace('“', '"').replace('”', '"')
            .replace('‘', '\'').replace('’', '\'')
            .replace("\\\\", "\\")
}
