package com.correx.core.config

/**
 * Serializes a [CorrexConfig] back to TOML text that [ConfigLoader] reads identically:
 * the invariant is `parseToml(write(cfg)) == cfg` for every field the loader understands.
 *
 * The writer **regenerates** the whole file from the config object — it does not preserve
 * hand-written comments or formatting in an existing config.toml. This is intentional: the
 * config is edited through the TUI, which owns the file once a save happens.
 *
 * Keys mirror the snake_case names [ConfigLoader.buildConfig] reads. Nullable values and
 * empty maps are omitted (the loader reconstructs null / emptyMap from their absence). Lists
 * that the loader substitutes a default for when empty (privileged locations, interpreter
 * executables, ignore globs) are always emitted with their current contents.
 */
object CorrexConfigWriter {

    fun write(cfg: CorrexConfig): String {
        val b = StringBuilder()

        b.section("server")
        b.kv("host", str(cfg.server.host))
        b.kv("port", cfg.server.port)

        b.section("tui")
        b.kv("theme", str(cfg.tui.theme))
        b.kv("session_list_limit", cfg.tui.sessionListLimit)

        b.section("cli")
        b.kv("default_output", str(cfg.cli.defaultOutput))

        b.section("tools")
        b.kv("sandbox_root", str(cfg.tools.sandboxRoot))
        b.kv("working_dir", str(cfg.tools.workingDir))
        b.kv("workspace_root", str(cfg.tools.workspaceRoot))
        b.kv("default_system_prompt_path", str(cfg.tools.defaultSystemPromptPath))
        b.kv("privileged_locations", list(cfg.tools.privilegedLocations))
        b.kv("interpreter_executables", list(cfg.tools.interpreterExecutables))
        b.kv("network_allowed_hosts", list(cfg.tools.networkAllowedHosts))
        b.kv("network_denied_hosts", list(cfg.tools.networkDeniedHosts))
        b.kv("allowed_workspace_roots", list(cfg.tools.allowedWorkspaceRoots))

        b.section("tools.shell")
        b.kv("enabled", cfg.tools.shellEnabled)
        b.kv("allowed_executables", list(cfg.tools.shellAllowedExecutables))

        b.section("tools.file_read")
        b.kv("enabled", cfg.tools.fileReadEnabled)
        b.section("tools.file_write")
        b.kv("enabled", cfg.tools.fileWriteEnabled)
        b.section("tools.file_edit")
        b.kv("enabled", cfg.tools.fileEditEnabled)

        b.section("talkie")
        b.kv("conversation_keep_last", cfg.talkie.conversationKeepLast)
        b.kv("retrieval_k", cfg.talkie.retrievalK)
        b.kv("token_budget", cfg.talkie.tokenBudget)

        b.section("embedder")
        b.kv("backend", str(cfg.talkie.embedder.backend))
        b.kv("dimension", cfg.talkie.embedder.dimension)
        cfg.talkie.embedder.url?.let { b.kv("url", str(it)) }
        cfg.talkie.embedder.modelId?.let { b.kv("model_id", str(it)) }

        b.section("l3")
        b.kv("backend", str(cfg.talkie.l3.backend))
        cfg.talkie.l3.persistPath?.let { b.kv("persist_path", str(it)) }
        b.kv("python_executable", str(cfg.talkie.l3.pythonExecutable))
        cfg.talkie.l3.scriptPath?.let { b.kv("script_path", str(it)) }
        b.kv("dim", cfg.talkie.l3.dim)
        b.kv("bit_width", cfg.talkie.l3.bitWidth)

        b.section("talkie.generation")
        b.kv("temperature", cfg.talkie.generation.temperature)
        b.kv("top_p", cfg.talkie.generation.topP)
        b.kv("max_tokens", cfg.talkie.generation.maxTokens)

        b.section("talkie.narration")
        b.kv("temperature", cfg.talkie.narration.temperature)
        b.kv("top_p", cfg.talkie.narration.topP)
        b.kv("max_tokens", cfg.talkie.narration.maxTokens)
        b.kv("max_per_run", cfg.talkie.narration.maxPerRun)
        cfg.talkie.narration.modelId?.let { b.kv("model_id", str(it)) }

        b.section("orchestration")
        b.kv("stage_timeout_ms", cfg.orchestration.stageTimeoutMs)
        b.kv("journal_compaction_token_threshold", cfg.orchestration.journalCompactionTokenThreshold)
        b.kv("resume_abandoned_max_age_minutes", cfg.orchestration.resumeAbandonedMaxAgeMinutes)
        b.kv("compression_level", cfg.orchestration.compressionLevel)
        b.kv("token_pruner_url", cfg.orchestration.tokenPrunerUrl)
        b.kv("max_tool_rounds", cfg.orchestration.maxToolRounds)
        b.kv("read_loop_nudge_threshold", cfg.orchestration.readLoopNudgeThreshold)
        b.kv("rejection_loop_nudge_threshold", cfg.orchestration.rejectionLoopNudgeThreshold)
        b.kv("max_feedback_issues", cfg.orchestration.maxFeedbackIssues)
        b.kv("repo_map_inject_top_k", cfg.orchestration.repoMapInjectTopK)
        b.kv("repo_map_files_per_dir", cfg.orchestration.repoMapFilesPerDir)
        b.kv("docs_catalog_max", cfg.orchestration.docsCatalogMax)
        b.kv("max_clarification_rounds", cfg.orchestration.maxClarificationRounds)
        b.kv("review_block_min_confidence", cfg.orchestration.reviewBlockMinConfidence)
        b.kv("review_block_retry_cap", cfg.orchestration.reviewBlockRetryCap)
        b.kv("review_loop_max_cycles", cfg.orchestration.reviewLoopMaxCycles)
        b.kv("default_max_refinement", cfg.orchestration.defaultMaxRefinement)
        b.kv("recovery_route_budget", cfg.orchestration.recoveryRouteBudget)
        b.kv("intent_route_budget", cfg.orchestration.intentRouteBudget)

        b.section("sampling")
        b.kv("temperature", cfg.sampling.temperature)
        b.kv("top_p", cfg.sampling.topP)
        cfg.sampling.topK?.let { b.kv("top_k", it) }
        cfg.sampling.minP?.let { b.kv("min_p", it) }
        cfg.sampling.repeatPenalty?.let { b.kv("repeat_penalty", it) }

        b.section("git")
        b.kv("enabled", cfg.git.enabled)
        b.kv("remote", str(cfg.git.remote))
        b.kv("base_branch", str(cfg.git.baseBranch))
        b.kv("author", str(cfg.git.author))

        b.section("personalization")
        b.kv("enabled", cfg.personalization.enabled)
        b.kv("learn", cfg.personalization.learn)

        b.section("project")
        b.kv("enabled", cfg.project.enabled)
        b.kv("memory_k", cfg.project.memoryK)
        b.kv("max_depth", cfg.project.maxDepth)
        b.kv("inject_top_k", cfg.project.injectTopK)
        b.kv("ignore_globs", list(cfg.project.ignoreGlobs))

        b.section("models")
        cfg.modelsSettings.defaultModel?.let { b.kv("default_model", str(it)) }
        b.kv("llama_server_bin", str(cfg.modelsSettings.llamaServerBin))
        b.kv("host", str(cfg.modelsSettings.host))
        b.kv("port", cfg.modelsSettings.port)

        cfg.providers.forEach { p ->
            b.arrayTable("providers")
            b.kv("id", str(p.id))
            b.kv("type", str(p.type))
            b.kv("model_id", str(p.modelId))
            b.kv("model_path", str(p.modelPath))
            b.kv("url", str(p.url))
            if (p.capabilities.isNotEmpty()) b.kv("capabilities", caps(p.capabilities))
            if (p.apiKey.isNotEmpty()) b.kv("api_key", str(p.apiKey))
            if (p.apiKeyEnv.isNotEmpty()) b.kv("api_key_env", str(p.apiKeyEnv))
        }

        cfg.models.forEach { m ->
            b.arrayTable("models")
            b.kv("id", str(m.id))
            b.kv("model_path", str(m.modelPath))
            b.kv("context_size", m.contextSize)
            if (m.params.isNotEmpty()) b.kv("params", strMap(m.params))
            if (m.capabilities.isNotEmpty()) b.kv("capabilities", caps(m.capabilities))
        }

        cfg.artifacts.forEach { a ->
            b.arrayTable("artifacts")
            b.kv("id", str(a.id))
            b.kv("schema_path", str(a.schemaPath))
            b.kv("llm_emitted", a.llmEmitted)
        }

        return b.toString()
    }

    private fun StringBuilder.section(name: String) {
        if (isNotEmpty()) append('\n')
        append('[').append(name).append("]\n")
    }

    private fun StringBuilder.arrayTable(name: String) {
        if (isNotEmpty()) append('\n')
        append("[[").append(name).append("]]\n")
    }

    private fun StringBuilder.kv(key: String, value: Any) {
        append(key).append(" = ").append(value.toString()).append('\n')
    }

    private fun str(v: String): String = "\"" + v.replace("\\", "\\\\").replace("\"", "\\\"") + "\""

    private fun list(v: List<String>): String = "[" + v.joinToString(", ") { str(it) } + "]"

    private fun caps(m: Map<String, Double>): String =
        "{ " + m.entries.joinToString(", ") { "${it.key} = ${it.value}" } + " }"

    private fun strMap(m: Map<String, String>): String =
        "{ " + m.entries.joinToString(", ") { "${it.key} = ${str(it.value)}" } + " }"
}
