package com.erwan.revwatch.model.face

/**
 * Parses and serializes Face DSL text.
 *
 * Format overview:
 * ```
 * @name "Sport Arc"
 * @author "Erwan"
 * bg #111
 *
 * # Static
 * circle 50 50 45 fill:#111 stroke:#333 w:2
 * arc 50 50 40 -225 270 stroke:#444 w:3
 * ticks 50 50 38 10 4 stroke:#666 w:1.5
 *
 * # Dynamic
 * progress_arc 50 50 40 -225 270 stroke:$accent w:5 cap:round glow:8
 * needle 50 50 35 from:-225 sweep:270 w:2 color:#FFF
 *
 * # Text
 * text $value 50 42 size:16 bold color:#FFF
 * text $unit 50 55 size:6 color:#888
 *
 * # Conditional
 * if $zone==danger {
 *   circle 50 50 46 stroke:#FF6B6B w:1 alpha:0.3
 * }
 * ```
 */
object FaceParser {

    fun parse(source: String): FaceDef {
        var name = "Custom"
        var author = ""
        var bg = "#000000"
        val vars = mutableMapOf<String, String>()
        val elements = mutableListOf<FaceNode>()

        val lines = source.lines().map { it.trim() }
        var i = 0

        while (i < lines.size) {
            val line = lines[i]
            when {
                line.isBlank() || line.startsWith("#") -> {}
                line.startsWith("@name ") -> name = extractQuoted(line.substringAfter("@name ").trim())
                line.startsWith("@author ") -> author = extractQuoted(line.substringAfter("@author ").trim())
                line.startsWith("bg ") -> bg = line.substringAfter("bg ").trim()
                line.startsWith("@var ") -> {
                    val match = Regex("""^@var\s+(\$\w+)\s*=\s*(.+)$""").find(line)
                    if (match != null) {
                        vars[match.groupValues[1]] = match.groupValues[2].trim()
                    }
                }
                line.startsWith("group ") -> {
                    val (node, nextI) = parseGroup(lines, i)
                    elements.add(node)
                    i = nextI
                    continue
                }
                line.startsWith("if ") -> {
                    val (node, nextI) = parseConditional(lines, i)
                    elements.add(node)
                    i = nextI
                    continue
                }
                line.startsWith("for ") -> {
                    val (node, nextI) = parseForLoop(lines, i)
                    elements.add(node)
                    i = nextI
                    continue
                }
                else -> parseElement(line)?.let { elements.add(it) }
            }
            i++
        }

        return FaceDef(name, author, bg, vars, elements)
    }

    fun serialize(face: FaceDef): String {
        val sb = StringBuilder()
        sb.appendLine("@name \"${face.name}\"")
        if (face.author.isNotBlank()) sb.appendLine("@author \"${face.author}\"")
        sb.appendLine("bg ${face.bg}")
        for ((name, expr) in face.vars) {
            sb.appendLine("@var $name = $expr")
        }
        sb.appendLine()
        for (node in face.elements) serializeNode(sb, node, "")
        return sb.toString().trimEnd()
    }

    // ── Internal parsing ────────────────────────────────────────────

    private fun parseGroup(lines: List<String>, startIndex: Int): Pair<FaceNode.Group, Int> {
        val groupLine = lines[startIndex]
        val name = groupLine.removePrefix("group ").removeSuffix("{").removeSuffix(" ").trim()
        val children = mutableListOf<FaceNode>()
        var i = startIndex + 1

        while (i < lines.size) {
            val line = lines[i].trim()
            when {
                line == "}" -> return FaceNode.Group(name, children) to (i + 1)
                line.isBlank() || line.startsWith("#") -> {}
                line.startsWith("group ") -> {
                    val (nested, nextI) = parseGroup(lines, i)
                    children.add(nested)
                    i = nextI
                    continue
                }
                line.startsWith("if ") -> {
                    val (nested, nextI) = parseConditional(lines, i)
                    children.add(nested)
                    i = nextI
                    continue
                }
                line.startsWith("for ") -> {
                    val (nested, nextI) = parseForLoop(lines, i)
                    children.add(nested)
                    i = nextI
                    continue
                }
                else -> parseElement(line)?.let { children.add(it) }
            }
            i++
        }
        return FaceNode.Group(name, children) to i
    }

    private fun parseConditional(lines: List<String>, startIndex: Int): Pair<FaceNode.Conditional, Int> {
        val condLine = lines[startIndex]
        val condition = condLine.removePrefix("if ").removeSuffix("{").removeSuffix(" ").trim()
        val children = mutableListOf<FaceNode>()
        var i = startIndex + 1

        while (i < lines.size) {
            val line = lines[i].trim()
            when {
                line == "}" -> return FaceNode.Conditional(condition, children) to (i + 1)
                line.isBlank() || line.startsWith("#") -> {}
                line.startsWith("group ") -> {
                    val (nested, nextI) = parseGroup(lines, i)
                    children.add(nested)
                    i = nextI
                    continue
                }
                line.startsWith("if ") -> {
                    val (nested, nextI) = parseConditional(lines, i)
                    children.add(nested)
                    i = nextI
                    continue
                }
                line.startsWith("for ") -> {
                    val (nested, nextI) = parseForLoop(lines, i)
                    children.add(nested)
                    i = nextI
                    continue
                }
                else -> parseElement(line)?.let { children.add(it) }
            }
            i++
        }
        // Unterminated block — return what we have
        return FaceNode.Conditional(condition, children) to i
    }

    private fun parseForLoop(lines: List<String>, startIndex: Int): Pair<FaceNode.ForLoop, Int> {
        val header = lines[startIndex].removePrefix("for ").removeSuffix("{").removeSuffix(" ").trim()
        val tokens = tokenize(header)
        val varName = tokens.getOrElse(0) { "\$i" }
        val from = tokens.getOrElse(1) { "0" }
        val to = tokens.getOrElse(2) { "0" }
        val step = if (tokens.size >= 5 && tokens[3] == "step") tokens[4] else "1"

        val children = mutableListOf<FaceNode>()
        var i = startIndex + 1
        while (i < lines.size) {
            val line = lines[i].trim()
            when {
                line == "}" -> return FaceNode.ForLoop(varName, from, to, step, children) to (i + 1)
                line.isBlank() || line.startsWith("#") -> {}
                line.startsWith("group ") -> { val (n, ni) = parseGroup(lines, i); children.add(n); i = ni; continue }
                line.startsWith("if ") -> { val (n, ni) = parseConditional(lines, i); children.add(n); i = ni; continue }
                line.startsWith("for ") -> { val (n, ni) = parseForLoop(lines, i); children.add(n); i = ni; continue }
                else -> parseElement(line)?.let { children.add(it) }
            }
            i++
        }
        return FaceNode.ForLoop(varName, from, to, step, children) to i
    }

    private fun parseElement(line: String): FaceNode.Element? {
        val tokens = tokenize(line)
        if (tokens.isEmpty()) return null

        val type = tokens[0]
        val args = mutableListOf<String>()
        val props = mutableMapOf<String, String>()

        for (token in tokens.drop(1)) {
            when {
                // Boolean flags
                token == "bold" || token == "italic" -> props[token] = "true"
                // key:value property (but not a color like #FF0000 or $accent)
                token.contains(':') && !token.startsWith("#") && !token.startsWith("\$") -> {
                    val colonIdx = token.indexOf(':')
                    props[token.substring(0, colonIdx)] = token.substring(colonIdx + 1)
                }
                // Positional argument
                else -> args.add(token)
            }
        }

        return FaceNode.Element(type, args, props)
    }

    private fun tokenize(line: String): List<String> {
        val tokens = mutableListOf<String>()
        var i = 0
        while (i < line.length) {
            when {
                line[i].isWhitespace() -> i++
                line[i] == '"' -> {
                    val end = line.indexOf('"', i + 1)
                    if (end < 0) {
                        tokens.add("\"${line.substring(i + 1)}\"")
                        i = line.length
                    } else {
                        tokens.add("\"${line.substring(i + 1, end)}\"")
                        i = end + 1
                    }
                }
                else -> {
                    val start = i
                    var depth = 0
                    while (i < line.length && !(depth == 0 && line[i].isWhitespace())) {
                        if (line[i] == '(') depth++
                        else if (line[i] == ')') depth = maxOf(0, depth - 1)
                        i++
                    }
                    tokens.add(line.substring(start, i))
                }
            }
        }
        return tokens
    }

    // ── Serialization ───────────────────────────────────────────────

    private fun serializeNode(sb: StringBuilder, node: FaceNode, indent: String) {
        when (node) {
            is FaceNode.Group -> {
                sb.appendLine("${indent}group ${node.name} {")
                for (child in node.children) serializeNode(sb, child, "$indent  ")
                sb.appendLine("${indent}}")
            }
            is FaceNode.Element -> {
                sb.append(indent).append(node.type)
                for (a in node.args) sb.append(" $a")
                for ((k, v) in node.props) {
                    if (k == "bold" || k == "italic") sb.append(" $k")
                    else sb.append(" $k:$v")
                }
                sb.appendLine()
            }
            is FaceNode.Conditional -> {
                sb.appendLine("${indent}if ${node.condition} {")
                for (child in node.children) serializeNode(sb, child, "$indent  ")
                sb.appendLine("${indent}}")
            }
            is FaceNode.ForLoop -> {
                val stepPart = if (node.step != "1") " step ${node.step}" else ""
                sb.appendLine("${indent}for ${node.varName} ${node.from} ${node.to}$stepPart {")
                for (child in node.children) serializeNode(sb, child, "$indent  ")
                sb.appendLine("${indent}}")
            }
        }
    }

    private fun extractQuoted(s: String): String =
        if (s.startsWith('"') && s.endsWith('"')) s.substring(1, s.length - 1) else s
}
