package com.correx.core.config

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

/**
 * Minimal hand-rolled TOML parser shared by [ProfileLoader] and [ProjectProfileLoader].
 * Handles root key-value pairs, `[section]` tables, string arrays, booleans, and numbers.
 * Does NOT handle array-of-tables (`[[…]]`) — use [ConfigLoader] for those.
 */
internal object SimpleToml {
    /** Returns (rootKeys, sections) from a simple TOML string. */
    fun parse(content: String): Pair<Map<String, Any>, Map<String, Map<String, Any>>> {
        val lines = content.trim().split("\n")
        var currentSection = ""
        val rootKeys = mutableMapOf<String, Any>()
        val sections = mutableMapOf<String, MutableMap<String, Any>>()

        var i = 0
        while (i < lines.size) {
            val trimmed = lines[i].trim()
            when {
                trimmed.isEmpty() || trimmed.startsWith("#") -> Unit
                trimmed.startsWith("[") && !trimmed.startsWith("[[") && trimmed.endsWith("]") -> {
                    currentSection = trimmed.substring(1, trimmed.length - 1).trim()
                    sections.putIfAbsent(currentSection, mutableMapOf())
                }
                else -> {
                    val eqIndex = trimmed.indexOf("=")
                    if (eqIndex > 0) {
                        val key = trimmed.substring(0, eqIndex).trim()
                        var valueStr = trimmed.substring(eqIndex + 1).trim()
                        // Multi-line array: accumulate lines until the top-level bracket closes.
                        while (valueStr.startsWith("[") && !arrayClosed(valueStr) && i + 1 < lines.size) {
                            i++
                            val next = lines[i].trim()
                            if (next.isEmpty() || next.startsWith("#")) continue
                            valueStr += " $next"
                        }
                        val parsed = parseSimpleValue(valueStr)
                        if (currentSection.isEmpty()) {
                            rootKeys[key] = parsed
                        } else {
                            sections[currentSection]?.put(key, parsed)
                        }
                    }
                }
            }
            i++
        }
        return Pair(rootKeys, sections)
    }

    /** True when every `[` opened outside quotes has been balanced by a `]`. */
    private fun arrayClosed(value: String): Boolean {
        var depth = 0
        var inQuotes = false
        var quoteChar = ' '
        var escaped = false
        for (ch in value) {
            when {
                escaped -> escaped = false
                ch == '\\' && inQuotes && quoteChar == '"' -> escaped = true
                (ch == '"' || ch == '\'') && !inQuotes -> { inQuotes = true; quoteChar = ch }
                ch == quoteChar && inQuotes -> inQuotes = false
                ch == '[' && !inQuotes -> depth++
                ch == ']' && !inQuotes -> depth--
            }
        }
        return depth <= 0
    }

    fun asString(value: Any?, default: String = ""): String =
        if (value is String) value else default

    fun asStringList(value: Any?): List<String> = when (value) {
        is List<*> -> value.filterIsInstance<String>()
        is String -> listOf(value)
        else -> emptyList()
    }

    private fun parseSimpleValue(valueStr: String): Any = when {
        valueStr.startsWith("[") && valueStr.endsWith("]") -> parseArray(valueStr)
        valueStr == "true" || valueStr == "false" -> valueStr.toBoolean()
        valueStr.toDoubleOrNull() != null ->
            if (valueStr.contains(".")) valueStr.toDouble() else valueStr.toLong()
        else -> stripQuotes(valueStr)
    }

    private fun parseArray(arrayStr: String): List<String> {
        val content = arrayStr.substring(1, arrayStr.length - 1).trim()
        if (content.isEmpty()) return emptyList()
        val result = mutableListOf<String>()
        var current = StringBuilder()
        var inQuotes = false
        var quoteChar = ' '
        var escaped = false
        for (ch in content) {
            when {
                // Keep escape sequences (e.g. \") intact so they reach stripQuotes verbatim; an
                // escaped quote must not be treated as the string's closing delimiter.
                escaped -> { current.append(ch); escaped = false }
                ch == '\\' && inQuotes && quoteChar == '"' -> { current.append(ch); escaped = true }
                (ch == '"' || ch == '\'') && !inQuotes -> { inQuotes = true; quoteChar = ch; current.append(ch) }
                ch == quoteChar && inQuotes -> { inQuotes = false; current.append(ch) }
                ch == ',' && !inQuotes -> {
                    result.add(stripQuotes(current.toString().trim()))
                    current = StringBuilder()
                }
                else -> current.append(ch)
            }
        }
        if (current.isNotEmpty()) result.add(stripQuotes(current.toString().trim()))
        return result
    }

    /**
     * Strips the surrounding quotes from [value] and, for double-quoted strings, unescapes the
     * `\\` and `\"` sequences emitted by the writers (mirrors [ProjectProfileWriter]/[CorrexConfigWriter]).
     */
    private fun stripQuotes(value: String): String {
        val isDoubleQuoted = value.startsWith("\"") && value.endsWith("\"") && value.length >= 2
        val isSingleQuoted = value.startsWith("'") && value.endsWith("'") && value.length >= 2
        return when {
            isDoubleQuoted -> unescapeDoubleQuoted(value.substring(1, value.length - 1))
            isSingleQuoted -> value.substring(1, value.length - 1)
            else -> value
        }
    }

    /** Reverses the writer's escaping: `\\` -> `\`, `\"` -> `"`; a trailing lone `\` is kept as-is. */
    private fun unescapeDoubleQuoted(value: String): String {
        if ('\\' !in value) return value
        val sb = StringBuilder(value.length)
        var i = 0
        while (i < value.length) {
            val ch = value[i]
            if (ch == '\\' && i + 1 < value.length) {
                val next = value[i + 1]
                if (next == '\\' || next == '"') {
                    sb.append(next)
                    i += 2
                    continue
                }
            }
            sb.append(ch)
            i++
        }
        return sb.toString()
    }
}

object ProfileLoader {
    fun profilePath(homeDir: String = System.getProperty("user.home")): Path =
        Paths.get(homeDir, ".config", "correx", "profile.toml")

    fun load(homeDir: String = System.getProperty("user.home")): OperatorProfile {
        val path = profilePath(homeDir)
        if (!Files.exists(path)) return OperatorProfile()

        return runCatching {
            val content = Files.readString(path)
            parseProfile(content)
        }.getOrElse { e ->
            System.err.println("Warning: Failed to parse profile at $path: ${e.message}")
            OperatorProfile()
        }
    }

    private fun parseProfile(content: String): OperatorProfile {
        val (rootKeys, sections) = SimpleToml.parse(content)
        val prefsSection = sections["preferences"] ?: emptyMap()
        val preferences = ProfilePreferences(
            approvalMode = SimpleToml.asString(prefsSection["approval_mode"], ""),
            preferredModels = SimpleToml.asStringList(prefsSection["preferred_models"]),
            conventions = SimpleToml.asStringList(prefsSection["conventions"]),
        )
        return OperatorProfile(
            about = SimpleToml.asString(rootKeys["about"], ""),
            preferences = preferences,
        )
    }
}

object ConfigLoader {
    private const val DEFAULT_SERVER_PORT = 8080
    private const val DEFAULT_SESSION_LIST_LIMIT = 5
    private const val DEFAULT_EMBEDDER_DIMENSION = 1536
    private const val DEFAULT_L3_DIM = 1536
    private const val DEFAULT_L3_BIT_WIDTH = 4
    private const val DEFAULT_GENERATION_TEMPERATURE = 0.7
    private const val DEFAULT_GENERATION_TOP_P = 0.9
    private const val DEFAULT_GENERATION_MAX_TOKENS = 512
    private const val DEFAULT_NARRATION_TEMPERATURE = 0.7
    private const val DEFAULT_NARRATION_TOP_P = 0.9
    private const val DEFAULT_NARRATION_MAX_TOKENS = 4096
    private const val DEFAULT_NARRATION_MAX_PER_RUN = 100
    private const val DEFAULT_CONVERSATION_KEEP_LAST = 6
    private const val DEFAULT_RETRIEVAL_K = 5
    private const val DEFAULT_TOKEN_BUDGET = 4096
    private const val DEFAULT_MODEL_CONTEXT_SIZE = 24_576
    private const val DEFAULT_PROJECT_MEMORY_K = 5
    private const val DEFAULT_PROJECT_MAX_DEPTH = 4
    private const val DEFAULT_PROJECT_INJECT_TOP_K = 30
    private const val DEFAULT_STAGE_TIMEOUT_MS = 180_000L
    private const val DEFAULT_JOURNAL_COMPACTION_TOKEN_THRESHOLD = 2_000
    private const val DEFAULT_RESUME_ABANDONED_MAX_AGE_MINUTES = 1_440L
    private const val DEFAULT_COMPRESSION_LEVEL = 4
    private const val DEFAULT_MAX_TOOL_ROUNDS = 30
    private const val DEFAULT_READ_LOOP_NUDGE_THRESHOLD = 3
    private const val DEFAULT_REJECTION_LOOP_NUDGE_THRESHOLD = 3
    private const val DEFAULT_MAX_FEEDBACK_ISSUES = 3
    private const val DEFAULT_REPO_MAP_INJECT_TOP_K = 30
    private const val DEFAULT_REPO_MAP_FILES_PER_DIR = 8
    private const val DEFAULT_DOCS_CATALOG_MAX = 20
    private const val DEFAULT_MAX_CLARIFICATION_ROUNDS = 3
    private const val DEFAULT_REVIEW_BLOCK_MIN_CONFIDENCE = 0.7
    private const val DEFAULT_REVIEW_BLOCK_RETRY_CAP = 20
    private const val DEFAULT_REVIEW_LOOP_MAX_CYCLES = 3
    private const val DEFAULT_MAX_REFINEMENT = 3
    private const val DEFAULT_RECOVERY_ROUTE_BUDGET = 2
    private const val DEFAULT_INTENT_ROUTE_BUDGET = 2
    private const val DEFAULT_MODELS_PORT = 10000
    private const val DEFAULT_SAMPLING_TOP_P = 1.0
    private const val DEFAULT_SAMPLING_TEMPERATURE = 0.7

    fun load(): CorrexConfig {
        val path = configPath()
        if (!Files.exists(path)) {
            return CorrexConfig()
        }

        return runCatching {
            val content = Files.readString(path)
            parseToml(content)
        }.getOrElse { e ->
            System.err.println("Warning: Failed to parse config at $path: ${e.message}")
            CorrexConfig()
        }
    }

    /** Loads and parses the config at an explicit [path], returning defaults if it is absent. */
    fun loadFrom(path: Path): CorrexConfig {
        if (!Files.exists(path)) return CorrexConfig()
        return runCatching { parseToml(Files.readString(path)) }.getOrElse { e ->
            System.err.println("Warning: Failed to parse config at $path: ${e.message}")
            CorrexConfig()
        }
    }

    fun configPath(): Path {
        val envPath = System.getenv("CORREX_CONFIG")
        return if (envPath != null) {
            Paths.get(envPath)
        } else {
            val homeDir = System.getProperty("user.home")
            Paths.get(homeDir, ".config", "correx", "config.toml")
        }
    }

    private fun parseToml(content: String): CorrexConfig {
        val lines = content.trim().split("\n")
        var currentSection = ""
        val sections = mutableMapOf<String, MutableMap<String, Any>>()
        val providers = mutableListOf<MutableMap<String, Any>>()
        val models = mutableListOf<MutableMap<String, Any>>()
        val artifacts = mutableListOf<MutableMap<String, Any>>()
        val mcpServers = mutableListOf<MutableMap<String, Any>>()
        var currentProvider: MutableMap<String, Any>? = null
        var currentModel: MutableMap<String, Any>? = null
        var currentArtifact: MutableMap<String, Any>? = null
        var currentMcp: MutableMap<String, Any>? = null

        // Flush any open array-of-table entry into its list. Called whenever a new
        // table header (array or section) starts, so the previous entry is committed.
        fun flushTables() {
            currentProvider?.let { providers.add(it) }
            currentModel?.let { models.add(it) }
            currentArtifact?.let { artifacts.add(it) }
            currentMcp?.let { mcpServers.add(it) }
            currentProvider = null
            currentModel = null
            currentArtifact = null
            currentMcp = null
        }

        for ((lineNum, line) in lines.withIndex()) {
            val trimmed = line.trim()

            when {
                trimmed.isEmpty() || trimmed.startsWith("#") -> {
                    // Skip empty lines and comments
                }
                trimmed == "[[providers]]" -> {
                    flushTables()
                    currentProvider = mutableMapOf()
                    currentSection = ""
                }
                trimmed == "[[models]]" -> {
                    flushTables()
                    currentModel = mutableMapOf()
                    currentSection = ""
                }
                trimmed == "[[artifacts]]" -> {
                    flushTables()
                    currentArtifact = mutableMapOf()
                    currentSection = ""
                }
                trimmed == "[[mcp]]" -> {
                    flushTables()
                    currentMcp = mutableMapOf()
                    currentSection = ""
                }
                trimmed.startsWith("[") && !trimmed.startsWith("[[") && trimmed.endsWith("]") -> {
                    // Parse section headers like [server] or [tools.shell]
                    flushTables()
                    currentSection = trimmed.substring(1, trimmed.length - 1).trim()
                    sections.putIfAbsent(currentSection, mutableMapOf())
                }
                else -> {
                    // Parse key=value pairs
                    val eqIndex = trimmed.indexOf("=")
                    if (eqIndex > 0) {
                        val key = trimmed.substring(0, eqIndex).trim()
                        val valueStr = trimmed.substring(eqIndex + 1).trim()
                        val parsedValue = parseValue(valueStr, lineNum + 1)

                        // Locals so smart-casting works (the vars are captured by flushTables).
                        val provider = currentProvider
                        val model = currentModel
                        val artifact = currentArtifact
                        val mcp = currentMcp
                        when {
                            provider != null -> provider[key] = parsedValue
                            model != null -> model[key] = parsedValue
                            artifact != null -> artifact[key] = parsedValue
                            mcp != null -> mcp[key] = parsedValue
                            currentSection.isNotEmpty() -> sections[currentSection]?.put(key, parsedValue)
                        }
                    }
                }
            }
        }

        // Don't forget the last array-of-table entry if file ends with one
        flushTables()

        return buildConfig(sections, providers, models, artifacts, mcpServers)
    }

    private fun parseValue(valueStr: String, lineNum: Int): Any {
        return when {
            // Inline table: { key = value, key2 = value2 }
            valueStr.startsWith("{") && valueStr.endsWith("}") -> {
                parseInlineTable(valueStr, lineNum)
            }
            // JSON-style array: ["item1", "item2"]
            valueStr.startsWith("[") && valueStr.endsWith("]") -> {
                parseArray(valueStr)
            }
            // CSV fallback for backward compat (detect by "," and "=" pattern without brackets)
            valueStr.contains(",") && valueStr.contains("=") && !valueStr.startsWith("\"") -> {
                parseCSVCapabilities(valueStr)
            }
            // Boolean
            valueStr == "true" || valueStr == "false" -> valueStr.toBoolean()
            // Number
            valueStr.toDoubleOrNull() != null -> {
                if (valueStr.contains(".")) valueStr.toDouble() else valueStr.toLong()
            }
            // String (with or without quotes)
            else -> stripQuotes(valueStr)
        }
    }

    private fun parseInlineTable(tableStr: String, lineNum: Int): Map<String, Any> {
        val result = mutableMapOf<String, Any>()
        val content = tableStr.substring(1, tableStr.length - 1).trim()
        if (content.isEmpty()) return result

        val pairs = splitTopLevel(content, ',')
        for (pair in pairs) {
            val eqIdx = pair.indexOf("=")
            if (eqIdx > 0) {
                val key = pair.substring(0, eqIdx).trim()
                val valStr = pair.substring(eqIdx + 1).trim()
                result[key] = parseValue(valStr, lineNum)
            }
        }
        return result
    }

    private fun parseArray(arrayStr: String): List<String> {
        val result = mutableListOf<String>()
        val content = arrayStr.substring(1, arrayStr.length - 1).trim()
        if (content.isEmpty()) return result

        val items = splitTopLevel(content, ',')
        for (item in items) {
            val trimmed = item.trim()
            result.add(stripQuotes(trimmed))
        }
        return result
    }

    private fun splitTopLevel(text: String, delimiter: Char): List<String> {
        val result = mutableListOf<String>()
        var current = StringBuilder()
        var inQuotes = false
        var quoteChar = ' '

        for (ch in text) {
            when {
                (ch == '"' || ch == '\'') && !inQuotes -> {
                    inQuotes = true
                    quoteChar = ch
                    current.append(ch)
                }
                ch == quoteChar && inQuotes -> {
                    inQuotes = false
                    current.append(ch)
                }
                ch == delimiter && !inQuotes -> {
                    result.add(current.toString().trim())
                    current = StringBuilder()
                }
                else -> current.append(ch)
            }
        }
        if (current.isNotEmpty()) {
            result.add(current.toString().trim())
        }
        return result
    }

    private fun parseCSVCapabilities(capsStr: String): Map<String, Double> {
        val result = mutableMapOf<String, Double>()
        capsStr.split(",").forEach { pair ->
            val parts = pair.trim().split("=")
            if (parts.size == 2) {
                val cap = parts[0].trim()
                val score = parts[1].trim().toDoubleOrNull()
                if (score != null && score >= 0.0 && score <= 1.0) {
                    result[cap] = score
                }
            }
        }
        return result
    }

    private fun stripQuotes(value: String): String {
        val isDoubleQuoted = value.startsWith("\"") && value.endsWith("\"")
        val isSingleQuoted = value.startsWith("'") && value.endsWith("'")
        return if (isDoubleQuoted || isSingleQuoted) {
            value.substring(1, value.length - 1)
        } else {
            value
        }
    }

    private fun buildConfig(
        sections: Map<String, Map<String, Any>>,
        providersList: List<Map<String, Any>> = emptyList(),
        modelsList: List<Map<String, Any>> = emptyList(),
        artifactsList: List<Map<String, Any>> = emptyList(),
        mcpList: List<Map<String, Any>> = emptyList(),
    ): CorrexConfig {
        val serverSection = sections["server"] ?: emptyMap()
        val tuiSection = sections["tui"] ?: emptyMap()
        val cliSection = sections["cli"] ?: emptyMap()
        val toolsSection = sections["tools"] ?: emptyMap()
        val toolsShellSection = sections["tools.shell"] ?: emptyMap()
        val toolsFileReadSection = sections["tools.file_read"] ?: emptyMap()
        val toolsFileWriteSection = sections["tools.file_write"] ?: emptyMap()
        val toolsFileEditSection = sections["tools.file_edit"] ?: emptyMap()
        val routerSection = sections["talkie"] ?: sections["router"] ?: emptyMap()
        // Embedder + L3 are shared infra (the orchestrator uses them for workflow repo-knowledge,
        // not just the router chat layer). Prefer the top-level [embedder]/[l3] sections; fall back
        // to the legacy [router.embedder]/[router.l3] so existing configs keep working.
        val routerEmbedderSection = sections["embedder"] ?: sections["router.embedder"] ?: emptyMap()
        val routerL3Section = sections["l3"] ?: sections["router.l3"] ?: emptyMap()
        val routerGenerationSection = sections["talkie.generation"] ?: sections["router.generation"] ?: emptyMap()
        val routerNarrationSection = sections["talkie.narration"] ?: sections["router.narration"] ?: emptyMap()
        val modelsSection = sections["models"] ?: emptyMap()

        val server = ServerConfig(
            host = asString(serverSection["host"], "localhost"),
            port = asInt(serverSection["port"], DEFAULT_SERVER_PORT),
        )

        val tui = TuiConfig(
            theme = asString(tuiSection["theme"], "dark"),
            sessionListLimit = asInt(tuiSection["session_list_limit"], DEFAULT_SESSION_LIST_LIMIT),
        )

        val cli = CliConfig(
            defaultOutput = asString(cliSection["default_output"], "human"),
        )

        // Resolve tool enable flags: prefer nested [tools.shell], [tools.file_read], etc.
        // Fall back to flat [tools] section for backward compat
        val shellEnabled = when {
            toolsShellSection.containsKey("enabled") -> asBoolean(toolsShellSection["enabled"], true)
            toolsSection.containsKey("shell_enabled") -> {
                System.err.println(
                    "Warning: 'shell_enabled' in [tools] is deprecated, use [tools.shell] enabled instead"
                )
                asBoolean(toolsSection["shell_enabled"], true)
            }
            else -> true
        }

        val fileReadEnabled = when {
            toolsFileReadSection.containsKey("enabled") -> asBoolean(toolsFileReadSection["enabled"], true)
            toolsSection.containsKey("file_read_enabled") -> {
                System.err.println(
                    "Warning: 'file_read_enabled' in [tools] is deprecated, use [tools.file_read] enabled instead"
                )
                asBoolean(toolsSection["file_read_enabled"], true)
            }
            else -> true
        }

        val fileWriteEnabled = when {
            toolsFileWriteSection.containsKey("enabled") -> asBoolean(toolsFileWriteSection["enabled"], true)
            toolsSection.containsKey("file_write_enabled") -> {
                System.err.println(
                    "Warning: 'file_write_enabled' in [tools] is deprecated, use [tools.file_write] enabled instead"
                )
                asBoolean(toolsSection["file_write_enabled"], true)
            }
            else -> true
        }

        val fileEditEnabled = when {
            toolsFileEditSection.containsKey("enabled") -> asBoolean(toolsFileEditSection["enabled"], true)
            toolsSection.containsKey("file_edit_enabled") -> {
                System.err.println(
                    "Warning: 'file_edit_enabled' in [tools] is deprecated, use [tools.file_edit] enabled instead"
                )
                asBoolean(toolsSection["file_edit_enabled"], true)
            }
            else -> true
        }

        val shellAllowedExecutables = when {
            toolsShellSection.containsKey("allowed_executables") -> {
                asStringList(toolsShellSection["allowed_executables"])
            }
            toolsSection.containsKey("shell_allowed_executables") -> {
                val val1 = toolsSection["shell_allowed_executables"]
                when {
                    val1 is List<*> -> val1.filterIsInstance<String>()
                    val1 is String -> {
                        // Backward compat: check if it's CSV or already a single item
                        if (val1.contains(",")) {
                            System.err.println(
                                "Warning: CSV format 'shell_allowed_executables' is deprecated, use JSON array instead"
                            )
                            val1.split(",").map { it.trim() }.filter { it.isNotEmpty() }
                        } else {
                            listOf(val1)
                        }
                    }
                    else -> emptyList()
                }
            }
            else -> emptyList()
        }

        val tools = ToolsConfig(
            sandboxRoot = asString(toolsSection["sandbox_root"], ""),
            workingDir = asString(toolsSection["working_dir"], ""),
            shellAllowedExecutables = shellAllowedExecutables,
            defaultSystemPromptPath = asString(
                toolsSection["default_system_prompt_path"],
                "~/.config/correx/prompts/default_system.md"
            ),
            shellEnabled = shellEnabled,
            fileReadEnabled = fileReadEnabled,
            fileWriteEnabled = fileWriteEnabled,
            fileEditEnabled = fileEditEnabled,
            workspaceRoot = asString(toolsSection["workspace_root"], ""),
            privilegedLocations = asStringList(toolsSection["privileged_locations"])
                .ifEmpty { ToolsConfig.DEFAULT_PRIVILEGED_LOCATIONS },
            interpreterExecutables = asStringList(toolsSection["interpreter_executables"])
                .ifEmpty { ToolsConfig.DEFAULT_INTERPRETERS },
            networkAllowedHosts = asStringList(toolsSection["network_allowed_hosts"]),
            networkDeniedHosts = asStringList(toolsSection["network_denied_hosts"]),
            allowedWorkspaceRoots = asStringList(toolsSection["allowed_workspace_roots"]),
        )

        val providers = providersList.mapNotNull { providerMap ->
            val id = asString(providerMap["id"]) ?: return@mapNotNull null
            val type = asString(providerMap["type"]) ?: return@mapNotNull null
            val modelId = asString(providerMap["model_id"]) ?: return@mapNotNull null
            val modelPath = asString(providerMap["model_path"], "")
            val url = asString(providerMap["url"], "http://127.0.0.1:10000")
            val capabilities = parseCapabilities(providerMap["capabilities"])

            ProviderConfig(
                id = id,
                type = type,
                modelId = modelId,
                modelPath = modelPath,
                url = url,
                capabilities = capabilities,
                apiKey = asString(providerMap["api_key"], ""),
                apiKeyEnv = asString(providerMap["api_key_env"], ""),
            )
        }

        val embedder = EmbedderConfig(
            backend = asString(routerEmbedderSection["backend"], "noop"),
            dimension = asInt(routerEmbedderSection["dimension"], DEFAULT_EMBEDDER_DIMENSION),
            url = asStringOrNull(routerEmbedderSection["url"]),
            modelId = asStringOrNull(routerEmbedderSection["model_id"]),
        )

        val l3 = L3Config(
            backend = asString(routerL3Section["backend"], "in_memory"),
            persistPath = asStringOrNull(routerL3Section["persist_path"]),
            pythonExecutable = asString(routerL3Section["python_executable"], "python3"),
            scriptPath = asStringOrNull(routerL3Section["script_path"]),
            dim = asInt(routerL3Section["dim"], DEFAULT_L3_DIM),
            bitWidth = asInt(routerL3Section["bit_width"], DEFAULT_L3_BIT_WIDTH),
        )

        val generation = GenerationSettings(
            temperature = asDouble(routerGenerationSection["temperature"], DEFAULT_GENERATION_TEMPERATURE),
            topP = asDouble(routerGenerationSection["top_p"], DEFAULT_GENERATION_TOP_P),
            maxTokens = asInt(routerGenerationSection["max_tokens"], DEFAULT_GENERATION_MAX_TOKENS),
        )

        val narration = NarrationSettings(
            temperature = asDouble(routerNarrationSection["temperature"], DEFAULT_NARRATION_TEMPERATURE),
            topP = asDouble(routerNarrationSection["top_p"], DEFAULT_NARRATION_TOP_P),
            maxTokens = asInt(routerNarrationSection["max_tokens"], DEFAULT_NARRATION_MAX_TOKENS),
            maxPerRun = asInt(routerNarrationSection["max_per_run"], DEFAULT_NARRATION_MAX_PER_RUN),
            modelId = asStringOrNull(routerNarrationSection["model_id"]),
        )

        val router = TalkieConfig(
            embedder = embedder,
            l3 = l3,
            conversationKeepLast = asInt(routerSection["conversation_keep_last"], DEFAULT_CONVERSATION_KEEP_LAST),
            retrievalK = asInt(routerSection["retrieval_k"], DEFAULT_RETRIEVAL_K),
            tokenBudget = asInt(routerSection["token_budget"], DEFAULT_TOKEN_BUDGET),
            generation = generation,
            narration = narration,
        )

        val models = modelsList.mapNotNull { modelMap ->
            val id = asString(modelMap["id"]) ?: return@mapNotNull null
            val modelPath = asString(modelMap["model_path"]) ?: return@mapNotNull null
            val contextSize = asInt(modelMap["context_size"], DEFAULT_MODEL_CONTEXT_SIZE)
            @Suppress("UNCHECKED_CAST")
            val params = (modelMap["params"] as? Map<String, Any>)
                ?.mapValues { it.value.toString() }
                ?: emptyMap()
            val capabilities = parseCapabilities(modelMap["capabilities"])

            ModelConfig(
                id = id,
                modelPath = modelPath,
                contextSize = contextSize,
                params = params,
                capabilities = capabilities,
            )
        }

        val personalizationSection = sections["personalization"] ?: emptyMap()
        val personalization = PersonalizationConfig(
            enabled = asBoolean(personalizationSection["enabled"], false),
            learn = asBoolean(personalizationSection["learn"], false),
        )

        val projectSection = sections["project"] ?: emptyMap()
        val project = ProjectConfig(
            enabled = asBoolean(projectSection["enabled"], false),
            memoryK = asInt(projectSection["memory_k"], DEFAULT_PROJECT_MEMORY_K),
            maxDepth = asInt(projectSection["max_depth"], DEFAULT_PROJECT_MAX_DEPTH),
            ignoreGlobs = asStringList(projectSection["ignore_globs"]).ifEmpty { ProjectConfig.DEFAULT_IGNORES },
            injectTopK = asInt(projectSection["inject_top_k"], DEFAULT_PROJECT_INJECT_TOP_K),
        )

        val orchestrationSection = sections["orchestration"] ?: emptyMap()
        val orchestration = OrchestrationKnobs(
            stageTimeoutMs = asLong(orchestrationSection["stage_timeout_ms"], DEFAULT_STAGE_TIMEOUT_MS),
            journalCompactionTokenThreshold = asInt(
                orchestrationSection["journal_compaction_token_threshold"],
                DEFAULT_JOURNAL_COMPACTION_TOKEN_THRESHOLD,
            ),
            resumeAbandonedMaxAgeMinutes = asLong(
                orchestrationSection["resume_abandoned_max_age_minutes"],
                DEFAULT_RESUME_ABANDONED_MAX_AGE_MINUTES,
            ),
            compressionLevel = asInt(orchestrationSection["compression_level"], DEFAULT_COMPRESSION_LEVEL),
            tokenPrunerUrl =
                asString(orchestrationSection["token_pruner_url"], "http://127.0.0.1:8199"),
            maxToolRounds = asInt(orchestrationSection["max_tool_rounds"], DEFAULT_MAX_TOOL_ROUNDS),
            readLoopNudgeThreshold =
                asInt(orchestrationSection["read_loop_nudge_threshold"], DEFAULT_READ_LOOP_NUDGE_THRESHOLD),
            rejectionLoopNudgeThreshold = asInt(
                orchestrationSection["rejection_loop_nudge_threshold"],
                DEFAULT_REJECTION_LOOP_NUDGE_THRESHOLD,
            ),
            maxFeedbackIssues = asInt(orchestrationSection["max_feedback_issues"], DEFAULT_MAX_FEEDBACK_ISSUES),
            repoMapInjectTopK = asInt(orchestrationSection["repo_map_inject_top_k"], DEFAULT_REPO_MAP_INJECT_TOP_K),
            repoMapFilesPerDir =
                asInt(orchestrationSection["repo_map_files_per_dir"], DEFAULT_REPO_MAP_FILES_PER_DIR),
            docsCatalogMax = asInt(orchestrationSection["docs_catalog_max"], DEFAULT_DOCS_CATALOG_MAX),
            maxClarificationRounds =
                asInt(orchestrationSection["max_clarification_rounds"], DEFAULT_MAX_CLARIFICATION_ROUNDS),
            reviewBlockMinConfidence =
                asDouble(orchestrationSection["review_block_min_confidence"], DEFAULT_REVIEW_BLOCK_MIN_CONFIDENCE),
            reviewBlockRetryCap = asInt(orchestrationSection["review_block_retry_cap"], DEFAULT_REVIEW_BLOCK_RETRY_CAP),
            reviewLoopMaxCycles =
                asInt(orchestrationSection["review_loop_max_cycles"], DEFAULT_REVIEW_LOOP_MAX_CYCLES),
            defaultMaxRefinement = asInt(orchestrationSection["default_max_refinement"], DEFAULT_MAX_REFINEMENT),
            recoveryRouteBudget = asInt(orchestrationSection["recovery_route_budget"], DEFAULT_RECOVERY_ROUTE_BUDGET),
            intentRouteBudget = asInt(orchestrationSection["intent_route_budget"], DEFAULT_INTENT_ROUTE_BUDGET),
        )

        val modelsSettings = ModelsSettings(
            defaultModel = asStringOrNull(modelsSection["default_model"]),
            llamaServerBin = asString(modelsSection["llama_server_bin"], "llama-server"),
            host = asString(modelsSection["host"], "127.0.0.1"),
            port = asInt(modelsSection["port"], DEFAULT_MODELS_PORT),
        )

        val artifacts = artifactsList.mapNotNull { artifactMap ->
            val id = asStringOrNull(artifactMap["id"]) ?: return@mapNotNull null
            val schemaPath = asStringOrNull(artifactMap["schema_path"]) ?: return@mapNotNull null
            ArtifactKindConfig(
                id = id,
                schemaPath = schemaPath,
                llmEmitted = asBoolean(artifactMap["llm_emitted"], false),
            )
        }

        val samplingSection = sections["sampling"] ?: emptyMap()
        val sampling = SamplingConfig(
            temperature = asDouble(samplingSection["temperature"], DEFAULT_SAMPLING_TEMPERATURE),
            topP = asDouble(samplingSection["top_p"], DEFAULT_SAMPLING_TOP_P),
            topK = samplingSection["top_k"]?.let { asInt(it) },
            minP = samplingSection["min_p"]?.let { asDouble(it) },
            repeatPenalty = samplingSection["repeat_penalty"]?.let { asDouble(it) },
        )

        val mcp = mcpList.mapNotNull { mcpMap ->
            val id = asStringOrNull(mcpMap["id"]) ?: return@mapNotNull null
            val command = asStringList(mcpMap["command"])
            if (command.isEmpty()) return@mapNotNull null
            @Suppress("UNCHECKED_CAST")
            val env = (mcpMap["env"] as? Map<String, Any>)?.mapValues { it.value.toString() } ?: emptyMap()
            McpServerConfig(
                id = id,
                command = command,
                env = env,
                tier = asString(mcpMap["tier"], "T2"),
            )
        }

        val gitSection = sections["git"] ?: emptyMap()
        val git = GitConfig(
            enabled = asBoolean(gitSection["enabled"], false),
            remote = asString(gitSection["remote"], "origin"),
            baseBranch = asString(gitSection["base_branch"], "main"),
            author = asString(gitSection["author"], ""),
        )

        return CorrexConfig(
            server = server,
            tui = tui,
            cli = cli,
            tools = tools,
            providers = providers,
            talkie = router,
            models = models,
            modelsSettings = modelsSettings,
            artifacts = artifacts,
            project = project,
            personalization = personalization,
            orchestration = orchestration,
            sampling = sampling,
            git = git,
            mcp = mcp,
        )
    }

    /** Test-only entry point to the otherwise-private TOML parser (same module). */
    internal fun parseTomlForTest(content: String): CorrexConfig = parseToml(content)

    private fun asString(value: Any?, default: String = ""): String {
        return when (value) {
            is String -> value
            else -> default
        }
    }

    private fun asStringOrNull(value: Any?): String? {
        return when (value) {
            is String -> value
            else -> null
        }
    }

    private fun asStringList(value: Any?): List<String> {
        return when (value) {
            is List<*> -> value.filterIsInstance<String>()
            is String -> if (value.contains(",")) {
                value.split(",").map { it.trim() }.filter { it.isNotEmpty() }
            } else {
                listOf(value)
            }
            else -> emptyList()
        }
    }

    private fun asInt(value: Any?, default: Int = 0): Int {
        return when (value) {
            is Int -> value
            is Long -> value.toInt()
            is String -> value.toIntOrNull() ?: default
            else -> default
        }
    }

    private fun asLong(value: Any?, default: Long): Long {
        return when (value) {
            is Long -> value
            is Int -> value.toLong()
            is Double -> value.toLong()
            is String -> value.toLongOrNull() ?: default
            else -> default
        }
    }

    private fun asDouble(value: Any?, default: Double = 0.0): Double {
        return when (value) {
            is Double -> value
            is Int -> value.toDouble()
            is Long -> value.toDouble()
            is String -> value.toDoubleOrNull() ?: default
            else -> default
        }
    }

    private fun asBoolean(value: Any?, default: Boolean = false): Boolean {
        return when (value) {
            is Boolean -> value
            is String -> value.lowercase() == "true"
            else -> default
        }
    }

    private fun parseCapabilities(value: Any?): Map<String, Double> {
        return when (value) {
            is Map<*, *> -> {
                // Inline table parsed earlier
                value.filterKeys { it is String }
                    .mapKeys { it.key as String }
                    .mapValues { (_, v) ->
                        when (v) {
                            is Double -> v
                            is Number -> v.toDouble()
                            is String -> v.toDoubleOrNull() ?: 0.0
                            else -> 0.0
                        }
                    }
                    .filter { it.value in 0.0..1.0 }
            }
            is String -> parseCSVCapabilities(value)
            else -> emptyMap()
        }
    }
}
