package dev.kaizen.app.chat

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import dev.kaizen.app.ui.shape.KaizenShapes
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import dev.kaizen.app.ui.theme.KaizenSemanticColors
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.kaizen.app.haptics.rememberHaptics
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import dev.kaizen.app.ui.icon.KaizenIcons

// ── Block types ────────────────────────────────────────────────────────────────

private sealed interface MdBlock {
    data class Paragraph(val text: String) : MdBlock
    data class Heading(val level: Int, val text: String) : MdBlock
    data class CodeFence(val lang: String, val code: String) : MdBlock
    data class ListBlock(val items: List<ListItem>) : MdBlock
    data class Item(val text: String, val ordered: Boolean, val index: Int) : MdBlock
    data object HorizontalRule : MdBlock
    data class Blockquote(val text: String) : MdBlock
    data class Table(val headers: List<String>, val rows: List<List<String>>) : MdBlock
}

private data class ListItem(val text: String, val ordered: Boolean, val index: Int, val checked: Boolean? = null)

// ── Pre-compiled patterns ──────────────────────────────────────────────────────

private val HR_PATTERN = Regex("^\\s*([-*_])\\s*\\1\\s*\\1[\\s\\-*_]*$")
private val BQ_PATTERN = Regex("^\\s*>\\s?(.*)")
private val HEADING_PATTERN = Regex("^(#{1,3})\\s+(.+)")
private val UL_PATTERN = Regex("^\\s*[-*+]\\s+(.*)")
private val OL_PATTERN = Regex("^\\s*(\\d+)[.)]\\s+(.*)")
private val TASK_PATTERN = Regex("^\\[([xX ])]\\s+(.*)")
private val TABLE_ROW_PATTERN = Regex("^\\|(.+)\\|\\s*$")
private val TABLE_SEP_PATTERN = Regex("^\\|[\\s:]*-{2,}[\\s:]*(?:\\|[\\s:]*-{2,}[\\s:]*)*\\|\\s*$")
private val FENCE_START = "```"
private val PARA_BREAK_PATTERNS = listOf(
    Regex("^#{1,3}\\s+"),
    Regex("^\\s*[-*+]\\s+"),
    Regex("^\\s*\\d+[.)]\\s+"),
    Regex("^\\s*>\\s?"),
    Regex("^\\|.+\\|\\s*$"),
)

// ── Parser ─────────────────────────────────────────────────────────────────────

private fun parseBlocks(src: String): List<MdBlock> {
    val blocks = mutableListOf<MdBlock>()
    val lines = src.lines()
    var i = 0
    while (i < lines.size) {
        val line = lines[i]

        if (HR_PATTERN.matches(line)) {
            blocks.add(MdBlock.HorizontalRule)
            i++
            continue
        }

        val bqMatch = BQ_PATTERN.matchEntire(line)
        if (bqMatch != null) {
            val bqLines = mutableListOf(bqMatch.groupValues[1])
            i++
            while (i < lines.size) {
                val m = BQ_PATTERN.matchEntire(lines[i])
                if (m != null) { bqLines.add(m.groupValues[1]); i++ } else break
            }
            blocks.add(MdBlock.Blockquote(bqLines.joinToString("\n")))
            continue
        }

        if (line.trimStart().startsWith(FENCE_START)) {
            val lang = line.trimStart().removePrefix(FENCE_START).trim()
            val codeLines = mutableListOf<String>()
            i++
            while (i < lines.size && !lines[i].trimStart().startsWith(FENCE_START)) {
                codeLines.add(lines[i]); i++
            }
            if (i < lines.size) i++
            blocks.add(MdBlock.CodeFence(lang, codeLines.joinToString("\n")))
            continue
        }

        // Table: header row, separator row, then body rows
        val tableMatch = TABLE_ROW_PATTERN.matchEntire(line)
        if (tableMatch != null && i + 1 < lines.size && TABLE_SEP_PATTERN.matches(lines[i + 1])) {
            val headers = tableMatch.groupValues[1].split("|").map { it.trim() }
            i += 2 // skip header + separator
            val rows = mutableListOf<List<String>>()
            while (i < lines.size) {
                val rowMatch = TABLE_ROW_PATTERN.matchEntire(lines[i])
                if (rowMatch != null) {
                    rows.add(rowMatch.groupValues[1].split("|").map { it.trim() })
                    i++
                } else break
            }
            blocks.add(MdBlock.Table(headers, rows))
            continue
        }

        val headingMatch = HEADING_PATTERN.matchEntire(line)
        if (headingMatch != null) {
            blocks.add(MdBlock.Heading(headingMatch.groupValues[1].length, headingMatch.groupValues[2]))
            i++
            continue
        }

        val ulMatch = UL_PATTERN.matchEntire(line)
        if (ulMatch != null) {
            val items = mutableListOf<ListItem>()
            while (i < lines.size) {
                val m = UL_PATTERN.matchEntire(lines[i]) ?: break
                val content = m.groupValues[1]
                val taskMatch = TASK_PATTERN.matchEntire(content)
                if (taskMatch != null) {
                    val checked = taskMatch.groupValues[1] != " "
                    items.add(ListItem(taskMatch.groupValues[2], ordered = false, index = items.size + 1, checked = checked))
                } else {
                    items.add(ListItem(content, ordered = false, index = items.size + 1))
                }
                i++
            }
            blocks.add(MdBlock.ListBlock(items))
            continue
        }

        val olMatch = OL_PATTERN.matchEntire(line)
        if (olMatch != null) {
            val items = mutableListOf<ListItem>()
            while (i < lines.size) {
                val m = OL_PATTERN.matchEntire(lines[i]) ?: break
                items.add(ListItem(m.groupValues[2], ordered = true, index = m.groupValues[1].toInt()))
                i++
            }
            blocks.add(MdBlock.ListBlock(items))
            continue
        }

        if (line.isBlank()) { i++; continue }

        val paraLines = mutableListOf(line)
        i++
        while (i < lines.size) {
            val next = lines[i]
            if (next.isBlank()
                || next.trimStart().startsWith(FENCE_START)
                || HR_PATTERN.matches(next)
                || PARA_BREAK_PATTERNS.any { it.containsMatchIn(next) }
            ) break
            paraLines.add(next)
            i++
        }
        blocks.add(MdBlock.Paragraph(paraLines.joinToString(" ")))
    }
    return blocks
}

// ── Inline markdown ────────────────────────────────────────────────────────────

private fun inlineMarkdown(
    text: String,
    baseColor: Color,
    codeColor: Color,
    codeBg: Color,
    linkColor: Color = Color(0xFF3B82F6),
): AnnotatedString {
    return buildAnnotatedString {
        var pos = 0
        val src = text
        while (pos < src.length) {
            // Link: [text](url)
            if (src[pos] == '[') {
                val closeBracket = src.indexOf(']', pos + 1)
                if (closeBracket > pos && closeBracket + 1 < src.length && src[closeBracket + 1] == '(') {
                    val closeParen = src.indexOf(')', closeBracket + 2)
                    if (closeParen > closeBracket) {
                        val linkText = src.substring(pos + 1, closeBracket)
                        val url = src.substring(closeBracket + 2, closeParen)
                        pushLink(LinkAnnotation.Url(url, TextLinkStyles(SpanStyle(color = linkColor, textDecoration = TextDecoration.Underline))))
                        append(linkText)
                        pop()
                        pos = closeParen + 1
                        continue
                    }
                }
            }
            // Inline code: `...`
            if (src[pos] == '`' && pos + 1 < src.length) {
                val end = src.indexOf('`', pos + 1)
                if (end > pos) {
                    withStyle(SpanStyle(fontFamily = FontFamily.Monospace, color = codeColor, background = codeBg, fontSize = 14.sp)) {
                        append(" ")
                        append(src.substring(pos + 1, end))
                        append(" ")
                    }
                    pos = end + 1
                    continue
                }
            }
            // Strikethrough: ~~...~~
            if (pos + 1 < src.length && src[pos] == '~' && src[pos + 1] == '~') {
                val end = src.indexOf("~~", pos + 2)
                if (end > pos) {
                    withStyle(SpanStyle(textDecoration = TextDecoration.LineThrough, color = baseColor.copy(alpha = 0.5f))) {
                        append(src.substring(pos + 2, end))
                    }
                    pos = end + 2
                    continue
                }
            }
            // Bold+italic: ***...*** or ___...___
            if (pos + 2 < src.length && src.substring(pos, pos + 3).let { it == "***" || it == "___" }) {
                val marker = src.substring(pos, pos + 3)
                val end = src.indexOf(marker, pos + 3)
                if (end > pos) {
                    withStyle(SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic)) {
                        append(src.substring(pos + 3, end))
                    }
                    pos = end + 3
                    continue
                }
            }
            // Bold: **...** or __...__
            if (pos + 1 < src.length && src.substring(pos, pos + 2).let { it == "**" || it == "__" }) {
                val marker = src.substring(pos, pos + 2)
                val end = src.indexOf(marker, pos + 2)
                if (end > pos) {
                    withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
                        append(src.substring(pos + 2, end))
                    }
                    pos = end + 2
                    continue
                }
            }
            // Italic: *...* or _..._
            if ((src[pos] == '*' || src[pos] == '_') && pos + 1 < src.length && src[pos + 1] != ' ') {
                val marker = src[pos]
                val end = src.indexOf(marker, pos + 1)
                if (end > pos && end > pos + 1) {
                    withStyle(SpanStyle(fontStyle = FontStyle.Italic)) {
                        append(src.substring(pos + 1, end))
                    }
                    pos = end + 1
                    continue
                }
            }
            append(src[pos])
            pos++
        }
    }
}

// ── Syntax highlighting ────────────────────────────────────────────────────────

private val KEYWORDS = mapOf(
    "kotlin" to setOf("fun", "val", "var", "class", "object", "interface", "if", "else", "when", "for", "while", "return", "import", "package", "private", "public", "internal", "protected", "override", "suspend", "data", "sealed", "enum", "companion", "null", "true", "false", "is", "in", "as", "this", "super", "try", "catch", "finally", "throw", "by", "init", "constructor", "typealias", "abstract", "open", "annotation", "inline", "operator", "infix", "tailrec", "reified", "crossinline", "noinline", "const", "lateinit", "break", "continue", "do"),
    "java" to setOf("class", "public", "private", "protected", "static", "final", "void", "int", "long", "double", "float", "boolean", "char", "byte", "short", "if", "else", "for", "while", "return", "new", "import", "package", "try", "catch", "throw", "throws", "extends", "implements", "interface", "abstract", "null", "true", "false", "this", "super", "switch", "case", "default", "break", "continue", "do", "enum", "instanceof", "synchronized", "volatile", "transient", "native", "strictfp"),
    "javascript" to setOf("function", "const", "let", "var", "if", "else", "for", "while", "return", "import", "export", "default", "class", "new", "this", "try", "catch", "throw", "async", "await", "null", "undefined", "true", "false", "switch", "case", "break", "continue", "do", "typeof", "instanceof", "in", "of", "from", "yield", "delete", "void", "with", "debugger", "finally", "extends", "super", "static", "get", "set"),
    "typescript" to setOf("function", "const", "let", "var", "if", "else", "for", "while", "return", "import", "export", "default", "class", "new", "this", "try", "catch", "throw", "async", "await", "null", "undefined", "true", "false", "switch", "case", "break", "continue", "do", "typeof", "instanceof", "in", "of", "from", "yield", "type", "interface", "enum", "namespace", "module", "declare", "as", "is", "keyof", "readonly", "extends", "implements", "super", "static", "get", "set", "abstract", "private", "protected", "public", "override"),
    "python" to setOf("def", "class", "if", "elif", "else", "for", "while", "return", "import", "from", "as", "try", "except", "raise", "with", "pass", "break", "continue", "lambda", "yield", "del", "global", "nonlocal", "assert", "in", "not", "and", "or", "is", "None", "True", "False", "async", "await", "finally", "self"),
    "bash" to setOf("if", "then", "else", "elif", "fi", "for", "while", "do", "done", "case", "esac", "function", "return", "exit", "echo", "export", "source", "local", "readonly", "declare", "unset", "shift", "set", "true", "false", "in", "select", "until", "trap", "break", "continue"),
    "sh" to setOf("if", "then", "else", "elif", "fi", "for", "while", "do", "done", "case", "esac", "function", "return", "exit", "echo", "export", "source", "local", "readonly", "declare", "unset", "shift", "set", "true", "false", "in", "select", "until", "trap", "break", "continue"),
    "sql" to setOf("SELECT", "FROM", "WHERE", "INSERT", "UPDATE", "DELETE", "CREATE", "ALTER", "DROP", "TABLE", "INDEX", "INTO", "VALUES", "SET", "JOIN", "LEFT", "RIGHT", "INNER", "OUTER", "ON", "AND", "OR", "NOT", "NULL", "IS", "IN", "AS", "ORDER", "BY", "GROUP", "HAVING", "LIMIT", "OFFSET", "UNION", "ALL", "DISTINCT", "EXISTS", "BETWEEN", "LIKE", "CASE", "WHEN", "THEN", "ELSE", "END", "PRIMARY", "KEY", "FOREIGN", "REFERENCES", "CASCADE", "DEFAULT", "CONSTRAINT", "UNIQUE", "CHECK", "TRUE", "FALSE", "BEGIN", "COMMIT", "ROLLBACK", "GRANT", "REVOKE", "WITH"),
    "css" to setOf("color", "background", "border", "margin", "padding", "display", "position", "width", "height", "font", "text", "flex", "grid", "align", "justify", "overflow", "opacity", "transform", "transition", "animation", "content", "cursor", "visibility", "z-index"),
    "html" to setOf("div", "span", "p", "a", "img", "input", "button", "form", "table", "tr", "td", "th", "ul", "ol", "li", "h1", "h2", "h3", "h4", "h5", "h6", "head", "body", "html", "link", "meta", "script", "style", "section", "header", "footer", "nav", "main", "article", "aside"),
    "json" to setOf("true", "false", "null"),
    "yaml" to setOf("true", "false", "null", "yes", "no"),
    "xml" to emptySet(),
    "rust" to setOf("fn", "let", "mut", "const", "if", "else", "match", "for", "while", "loop", "return", "use", "mod", "pub", "crate", "self", "super", "struct", "enum", "impl", "trait", "type", "where", "as", "in", "ref", "move", "async", "await", "true", "false", "Some", "None", "Ok", "Err", "unsafe", "extern", "dyn", "static", "macro_rules"),
    "go" to setOf("func", "var", "const", "if", "else", "for", "range", "return", "import", "package", "type", "struct", "interface", "map", "chan", "go", "defer", "select", "case", "switch", "break", "continue", "default", "nil", "true", "false", "fallthrough"),
    "swift" to setOf("func", "let", "var", "class", "struct", "enum", "protocol", "if", "else", "for", "while", "return", "import", "switch", "case", "break", "continue", "guard", "defer", "do", "try", "catch", "throw", "throws", "async", "await", "nil", "true", "false", "self", "Self", "init", "deinit", "extension", "subscript", "where", "in", "is", "as", "typealias", "associatedtype", "private", "public", "internal", "open", "fileprivate", "static", "override", "mutating", "weak", "unowned", "lazy", "final", "required", "convenience", "optional", "some", "any"),
    "c" to setOf("auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "int", "long", "register", "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "NULL", "true", "false", "inline", "restrict"),
    "cpp" to setOf("auto", "break", "case", "char", "class", "const", "continue", "default", "delete", "do", "double", "else", "enum", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "nullptr", "operator", "private", "protected", "public", "register", "return", "short", "signed", "sizeof", "static", "struct", "switch", "template", "this", "throw", "true", "try", "typedef", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "while", "override", "final", "constexpr", "noexcept", "decltype", "thread_local", "static_assert"),
    "ruby" to setOf("def", "class", "module", "if", "elsif", "else", "unless", "while", "until", "for", "do", "end", "return", "yield", "begin", "rescue", "ensure", "raise", "require", "include", "extend", "attr_reader", "attr_writer", "attr_accessor", "self", "nil", "true", "false", "and", "or", "not", "in", "then", "when", "case", "lambda", "proc", "block_given?", "defined?", "super", "private", "protected", "public"),
    "php" to setOf("function", "class", "if", "else", "elseif", "for", "foreach", "while", "do", "switch", "case", "break", "continue", "return", "echo", "print", "new", "try", "catch", "throw", "finally", "public", "private", "protected", "static", "abstract", "interface", "extends", "implements", "namespace", "use", "require", "include", "null", "true", "false", "var", "const", "array", "isset", "unset", "empty"),
    "toml" to setOf("true", "false"),
    "dockerfile" to setOf("FROM", "RUN", "CMD", "EXPOSE", "ENV", "ADD", "COPY", "ENTRYPOINT", "VOLUME", "USER", "WORKDIR", "ARG", "LABEL", "STOPSIGNAL", "HEALTHCHECK", "SHELL", "ONBUILD", "AS"),
)

private fun highlightCode(code: String, lang: String, isDark: Boolean): AnnotatedString {
    val keywords = KEYWORDS[lang.lowercase()] ?: KEYWORDS["javascript"] ?: emptySet()
    val kwColor = if (isDark) Color(0xFFFF79C6) else Color(0xFFD63384)
    val strColor = if (isDark) Color(0xFFF1FA8C) else Color(0xFF22863A)
    val commentColor = if (isDark) Color(0xFF6272A4) else Color(0xFF6A737D)
    val numColor = if (isDark) Color(0xFFBD93F9) else Color(0xFF005CC5)
    val baseColor = if (isDark) Color(0xFFF8F8F2) else Color(0xFF24292E)

    return buildAnnotatedString {
        var pos = 0
        while (pos < code.length) {
            // Block comment: /* ... */
            if (pos + 1 < code.length && code[pos] == '/' && code[pos + 1] == '*') {
                val end = code.indexOf("*/", pos + 2).let { if (it < 0) code.length else it + 2 }
                withStyle(SpanStyle(color = commentColor, fontStyle = FontStyle.Italic)) {
                    append(code.substring(pos, end))
                }
                pos = end
                continue
            }
            // Line comment: //
            if (pos + 1 < code.length && code[pos] == '/' && code[pos + 1] == '/') {
                val end = code.indexOf('\n', pos).let { if (it < 0) code.length else it }
                withStyle(SpanStyle(color = commentColor, fontStyle = FontStyle.Italic)) {
                    append(code.substring(pos, end))
                }
                pos = end
                continue
            }
            // Hash comment
            if (code[pos] == '#' && (lang.lowercase() in setOf("python", "bash", "sh", "yaml", "ruby", "toml", "dockerfile"))) {
                val end = code.indexOf('\n', pos).let { if (it < 0) code.length else it }
                withStyle(SpanStyle(color = commentColor, fontStyle = FontStyle.Italic)) {
                    append(code.substring(pos, end))
                }
                pos = end
                continue
            }
            // HTML/XML comment: <!-- ... -->
            if (pos + 3 < code.length && code.substring(pos, pos + 4) == "<!--") {
                val end = code.indexOf("-->", pos + 4).let { if (it < 0) code.length else it + 3 }
                withStyle(SpanStyle(color = commentColor, fontStyle = FontStyle.Italic)) {
                    append(code.substring(pos, end))
                }
                pos = end
                continue
            }
            // String literals
            if (code[pos] == '"' || code[pos] == '\'') {
                val quote = code[pos]
                var end = pos + 1
                while (end < code.length && code[end] != quote) {
                    if (code[end] == '\\' && end + 1 < code.length) end++
                    end++
                }
                if (end < code.length) end++
                withStyle(SpanStyle(color = strColor)) {
                    append(code.substring(pos, end))
                }
                pos = end
                continue
            }
            // Template/backtick strings
            if (code[pos] == '`' && lang.lowercase() in setOf("javascript", "typescript", "js", "ts")) {
                var end = pos + 1
                while (end < code.length && code[end] != '`') {
                    if (code[end] == '\\' && end + 1 < code.length) end++
                    end++
                }
                if (end < code.length) end++
                withStyle(SpanStyle(color = strColor)) {
                    append(code.substring(pos, end))
                }
                pos = end
                continue
            }
            // Numbers
            if (code[pos].isDigit() && (pos == 0 || !code[pos - 1].isLetterOrDigit())) {
                var end = pos
                while (end < code.length && (code[end].isDigit() || code[end] == '.' || code[end] == 'x' || code[end] == 'f' || code[end] == 'L')) end++
                withStyle(SpanStyle(color = numColor)) {
                    append(code.substring(pos, end))
                }
                pos = end
                continue
            }
            // Words (potential keywords)
            if (code[pos].isLetter() || code[pos] == '_' || code[pos] == '@') {
                var end = pos
                while (end < code.length && (code[end].isLetterOrDigit() || code[end] == '_')) end++
                val word = code.substring(pos, end)
                val isCaseInsensitiveKw = lang.lowercase() == "sql"
                if (word in keywords || (isCaseInsensitiveKw && word.uppercase() in keywords)) {
                    withStyle(SpanStyle(color = kwColor, fontWeight = FontWeight.SemiBold)) { append(word) }
                } else {
                    withStyle(SpanStyle(color = baseColor)) { append(word) }
                }
                pos = end
                continue
            }
            withStyle(SpanStyle(color = baseColor)) { append(code[pos]) }
            pos++
        }
    }
}

// ── Render ─────────────────────────────────────────────────────────────────────

@Composable
fun MarkdownText(text: String, streaming: Boolean = false, modifier: Modifier = Modifier) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val blocks = remember(text) { parseBlocks(text) }

    val codeBg = if (isDark) Color(0xFF1A1E2A) else Color(0xFFF3F4F6)
    val codeBorder = if (isDark) Color.White.copy(alpha = 0.08f) else Color.Black.copy(alpha = 0.06f)
    val inlineCodeBg = if (isDark) Color(0xFF1E2433) else Color(0xFFEBEDF0)
    val inlineCodeColor = if (isDark) Color(0xFFE06C75) else Color(0xFFD63384)
    val linkColor = if (isDark) KaizenSemanticColors.linkDark else KaizenSemanticColors.linkLight
    val langBadgeColor = cs.onSurfaceVariant.copy(alpha = 0.5f)
    val tableBorder = if (isDark) Color.White.copy(alpha = 0.10f) else Color.Black.copy(alpha = 0.08f)
    val tableHeaderBg = if (isDark) Color.White.copy(alpha = 0.05f) else Color.Black.copy(alpha = 0.03f)

    val cursor = if (streaming) " ▌" else ""

    Column(modifier) {
        blocks.forEachIndexed { idx, block ->
            val isLast = idx == blocks.lastIndex
            when (block) {
                is MdBlock.Heading -> {
                    if (idx > 0) Spacer(Modifier.height(if (block.level == 1) 24.dp else 20.dp))
                    val rendered = inlineMarkdown(block.text + if (isLast) cursor else "", cs.onBackground, inlineCodeColor, inlineCodeBg, linkColor)
                    ClickableMarkdownText(rendered, cs.onBackground,
                        fontSize = when (block.level) { 1 -> 26.sp; 2 -> 22.sp; else -> 18.sp },
                        fontWeight = when (block.level) { 1 -> FontWeight.W600; 2 -> FontWeight.W600; else -> FontWeight.W500 },
                        lineHeight = when (block.level) { 1 -> 34.sp; 2 -> 30.sp; else -> 26.sp },
                    )
                    Spacer(Modifier.height(10.dp))
                }

                is MdBlock.CodeFence -> {
                    if (idx > 0) Spacer(Modifier.height(12.dp))
                    Box(
                        Modifier
                            .fillMaxWidth()
                            .clip(RoundedCornerShape(10.dp))
                            .background(codeBg)
                            .border(1.dp, codeBorder, RoundedCornerShape(10.dp))
                    ) {
                        Column {
                            Row(
                                Modifier
                                    .fillMaxWidth()
                                    .padding(start = 12.dp, end = 6.dp, top = 6.dp),
                                verticalAlignment = Alignment.CenterVertically,
                            ) {
                                Text(
                                    if (block.lang.isNotBlank()) block.lang else "code",
                                    color = langBadgeColor,
                                    fontSize = 11.sp,
                                    fontWeight = FontWeight.Medium,
                                )
                                Spacer(Modifier.weight(1f))
                                CopyButton(block.code)
                            }
                            Box(
                                Modifier
                                    .fillMaxWidth()
                                    .horizontalScroll(rememberScrollState())
                                    .padding(horizontal = 12.dp, vertical = 6.dp)
                            ) {
                                Text(
                                    text = highlightCode(block.code + if (isLast) cursor else "", block.lang, isDark),
                                    fontFamily = FontFamily.Monospace,
                                    fontSize = 13.sp,
                                    lineHeight = 19.sp,
                                )
                            }
                        }
                    }
                    Spacer(Modifier.height(12.dp))
                }

                is MdBlock.Table -> {
                    if (idx > 0) Spacer(Modifier.height(12.dp))
                    Box(
                        Modifier
                            .fillMaxWidth()
                            .horizontalScroll(rememberScrollState())
                            .clip(RoundedCornerShape(8.dp))
                            .border(1.dp, tableBorder, RoundedCornerShape(8.dp))
                    ) {
                        Column {
                            // Header
                            Row(Modifier.background(tableHeaderBg)) {
                                block.headers.forEach { cell ->
                                    Box(
                                        Modifier
                                            .width(IntrinsicSize.Max)
                                            .defaultMinSize(minWidth = 80.dp)
                                            .border(0.5.dp, tableBorder)
                                            .padding(horizontal = 10.dp, vertical = 8.dp)
                                    ) {
                                        Text(
                                            text = cell,
                                            color = cs.onBackground,
                                            fontSize = 13.sp,
                                            fontWeight = FontWeight.SemiBold,
                                            maxLines = 3,
                                            overflow = TextOverflow.Ellipsis,
                                        )
                                    }
                                }
                            }
                            // Body rows
                            block.rows.forEach { row ->
                                Row {
                                    row.forEachIndexed { cellIdx, cell ->
                                        Box(
                                            Modifier
                                                .width(IntrinsicSize.Max)
                                                .defaultMinSize(minWidth = 80.dp)
                                                .border(0.5.dp, tableBorder)
                                                .padding(horizontal = 10.dp, vertical = 7.dp)
                                        ) {
                                            val rendered = inlineMarkdown(cell, cs.onBackground, inlineCodeColor, inlineCodeBg, linkColor)
                                            Text(
                                                text = rendered,
                                                color = cs.onBackground,
                                                fontSize = 13.sp,
                                                lineHeight = 18.sp,
                                                maxLines = 5,
                                                overflow = TextOverflow.Ellipsis,
                                            )
                                        }
                                    }
                                }
                            }
                        }
                    }
                    Spacer(Modifier.height(12.dp))
                }

                is MdBlock.ListBlock -> {
                    if (idx > 0) Spacer(Modifier.height(10.dp))
                    block.items.forEachIndexed { itemIdx, item ->
                        Row(Modifier.fillMaxWidth().padding(start = 4.dp, bottom = 8.dp)) {
                            if (item.checked != null) {
                                val checkColor = if (item.checked) linkColor else cs.onSurfaceVariant.copy(alpha = 0.4f)
                                val checkIcon = if (item.checked) KaizenIcons.Check else KaizenIcons.Square
                                Icon(checkIcon, null, tint = checkColor, modifier = Modifier.size(18.dp).padding(top = 4.dp))
                                Spacer(Modifier.width(8.dp))
                            } else {
                                val bullet = if (item.ordered) "${item.index}." else "•"
                                Text(
                                    bullet,
                                    color = cs.onSurfaceVariant.copy(alpha = 0.7f),
                                    fontSize = 16.sp,
                                    fontWeight = FontWeight.W300,
                                    modifier = Modifier.width(if (item.ordered) 24.dp else 18.dp).padding(top = 1.dp),
                                )
                            }
                            val appendCursor = isLast && itemIdx == block.items.lastIndex
                            val rendered = inlineMarkdown(item.text + if (appendCursor) cursor else "", cs.onBackground, inlineCodeColor, inlineCodeBg, linkColor)
                            ClickableMarkdownText(rendered, cs.onBackground,
                                fontSize = 16.sp, lineHeight = 26.sp, fontWeight = FontWeight.W300,
                                modifier = Modifier.weight(1f),
                            )
                        }
                    }
                    Spacer(Modifier.height(10.dp))
                }

                is MdBlock.HorizontalRule -> {
                    if (idx > 0) Spacer(Modifier.height(14.dp))
                    Box(
                        Modifier
                            .fillMaxWidth()
                            .height(1.dp)
                            .background(cs.onSurfaceVariant.copy(alpha = 0.20f))
                    )
                    Spacer(Modifier.height(14.dp))
                }

                is MdBlock.Blockquote -> {
                    if (idx > 0) Spacer(Modifier.height(14.dp))
                    Row(
                        Modifier
                            .fillMaxWidth()
                            .height(IntrinsicSize.Min)
                            .padding(start = 4.dp)
                    ) {
                        Box(
                            Modifier
                                .width(3.dp)
                                .fillMaxHeight()
                                .clip(RoundedCornerShape(1.5.dp))
                                .background(cs.primary.copy(alpha = 0.45f))
                        )
                        Spacer(Modifier.width(12.dp))
                        val rendered = inlineMarkdown(block.text + if (isLast) cursor else "", cs.onSurfaceVariant, inlineCodeColor, inlineCodeBg, linkColor)
                        ClickableMarkdownText(rendered, cs.onSurfaceVariant,
                            fontSize = 15.sp, lineHeight = 24.sp,
                            fontStyle = FontStyle.Italic,
                            modifier = Modifier.weight(1f),
                        )
                    }
                    Spacer(Modifier.height(12.dp))
                }

                is MdBlock.Paragraph -> {
                    if (idx > 0) Spacer(Modifier.height(12.dp))
                    val rendered = inlineMarkdown(block.text + if (isLast) cursor else "", cs.onBackground, inlineCodeColor, inlineCodeBg, linkColor)
                    ClickableMarkdownText(rendered, cs.onBackground,
                        fontSize = 16.sp, lineHeight = 26.sp, fontWeight = FontWeight.W300,
                    )
                }

                is MdBlock.Item -> {}
            }
        }
    }
}

// ── Clickable text (handles link taps via LinkAnnotation + LocalUriHandler) ────

@Composable
private fun ClickableMarkdownText(
    text: AnnotatedString,
    color: Color,
    fontSize: androidx.compose.ui.unit.TextUnit = 16.sp,
    lineHeight: androidx.compose.ui.unit.TextUnit = 26.sp,
    fontWeight: FontWeight? = null,
    fontStyle: FontStyle? = null,
    modifier: Modifier = Modifier,
) {
    Text(
        text = text,
        color = color,
        fontSize = fontSize,
        lineHeight = lineHeight,
        fontWeight = fontWeight,
        fontStyle = fontStyle,
        modifier = modifier,
    )
}

// ── Copy button ────────────────────────────────────────────────────────────────

@Composable
private fun CopyButton(code: String) {
    val clipboard = LocalClipboard.current
    val haptics = rememberHaptics()
    val scope = rememberCoroutineScope()
    var copied by remember { mutableStateOf(false) }
    val cs = MaterialTheme.colorScheme

    Box(
        Modifier
            .size(28.dp)
            .clip(KaizenShapes.circle)
            .clickable {
                haptics.tick()
                copied = true
                scope.launch {
                    clipboard.setClipEntry(
                        androidx.compose.ui.platform.ClipEntry(
                            android.content.ClipData.newPlainText("code", code)
                        )
                    )
                    delay(1500)
                    copied = false
                }
            },
        contentAlignment = Alignment.Center,
    ) {
        Icon(
            imageVector = if (copied) KaizenIcons.Check else KaizenIcons.Copy,
            contentDescription = "Kopieren",
            tint = if (copied) KaizenSemanticColors.successBright else cs.onSurfaceVariant.copy(alpha = 0.5f),
            modifier = Modifier.size(14.dp),
        )
    }
}
