// This file is a config-field registry: many one-line pure helpers (per-section copy shims and
// per-type value parsers) by design. The function-count heuristic is a false positive here.
@file:Suppress("TooManyFunctions")

package com.correx.core.config

/** Raised when a config patch references an unknown/non-editable key or a malformed value. */
class ConfigEditError(message: String) : Exception(message)

enum class ConfigFieldType { BOOL, INT, LONG, DOUBLE, STRING, ENUM }

/**
 * One operator-editable config field. [getString] renders the current value as a string for the
 * snapshot; [withString] returns a new [CorrexConfig] with the parsed value, throwing
 * [ConfigEditError] on bad input. [options] is populated only for [ConfigFieldType.ENUM].
 */
class EditableField(
    val key: String,
    val type: ConfigFieldType,
    val options: List<String> = emptyList(),
    val getString: (CorrexConfig) -> String,
    val withString: (CorrexConfig, String) -> CorrexConfig,
)

/**
 * The allowlist of config fields the TUI may edit, plus the patch applier. Security-sensitive
 * fields (sandbox/workspace roots, shell-allowed executables, privileged locations, network
 * allow/deny lists, provider/model wiring) are deliberately absent: [apply] rejects any key not
 * in [fields], so they can never be patched. This registry is the single source of truth shared
 * by the patch applier and the protocol snapshot.
 */
object EditableConfig {

    private val THEME_OPTIONS = listOf("dark", "light", "soft-blue")
    private val OUTPUT_OPTIONS = listOf("human", "json")

    val fields: List<EditableField> = listOf(
        EditableField(
            "server.host", ConfigFieldType.STRING,
            getString = { it.server.host },
            withString = { c, v -> srv(c) { it.copy(host = v) } },
        ),
        EditableField(
            "server.port", ConfigFieldType.INT,
            getString = { it.server.port.toString() },
            withString = { c, v -> srv(c) { it.copy(port = int("server.port", v)) } },
        ),
        EditableField(
            "tui.theme", ConfigFieldType.ENUM, options = THEME_OPTIONS,
            getString = { it.tui.theme },
            withString = { c, v -> tui(c) { it.copy(theme = enum("tui.theme", v, THEME_OPTIONS)) } },
        ),
        EditableField(
            "tui.session_list_limit", ConfigFieldType.INT,
            getString = { it.tui.sessionListLimit.toString() },
            withString = { c, v -> tui(c) { it.copy(sessionListLimit = int("tui.session_list_limit", v)) } },
        ),
        EditableField(
            "cli.default_output", ConfigFieldType.ENUM, options = OUTPUT_OPTIONS,
            getString = { it.cli.defaultOutput },
            withString = { c, v -> cli(c) { it.copy(defaultOutput = enum("cli.default_output", v, OUTPUT_OPTIONS)) } },
        ),
        EditableField(
            "tools.shell_enabled", ConfigFieldType.BOOL,
            getString = { it.tools.shellEnabled.toString() },
            withString = { c, v -> tls(c) { it.copy(shellEnabled = bool("tools.shell_enabled", v)) } },
        ),
        EditableField(
            "tools.file_read_enabled", ConfigFieldType.BOOL,
            getString = { it.tools.fileReadEnabled.toString() },
            withString = { c, v -> tls(c) { it.copy(fileReadEnabled = bool("tools.file_read_enabled", v)) } },
        ),
        EditableField(
            "tools.file_write_enabled", ConfigFieldType.BOOL,
            getString = { it.tools.fileWriteEnabled.toString() },
            withString = { c, v -> tls(c) { it.copy(fileWriteEnabled = bool("tools.file_write_enabled", v)) } },
        ),
        EditableField(
            "tools.file_edit_enabled", ConfigFieldType.BOOL,
            getString = { it.tools.fileEditEnabled.toString() },
            withString = { c, v -> tls(c) { it.copy(fileEditEnabled = bool("tools.file_edit_enabled", v)) } },
        ),
        EditableField(
            "tools.default_system_prompt_path", ConfigFieldType.STRING,
            getString = { it.tools.defaultSystemPromptPath },
            withString = { c, v -> tls(c) { it.copy(defaultSystemPromptPath = v) } },
        ),
        EditableField(
            "router.conversation_keep_last", ConfigFieldType.INT,
            getString = { it.talkie.conversationKeepLast.toString() },
            withString = { c, v -> rtr(c) { it.copy(conversationKeepLast = int("router.conversation_keep_last", v)) } },
        ),
        EditableField(
            "router.retrieval_k", ConfigFieldType.INT,
            getString = { it.talkie.retrievalK.toString() },
            withString = { c, v -> rtr(c) { it.copy(retrievalK = int("router.retrieval_k", v)) } },
        ),
        EditableField(
            "router.token_budget", ConfigFieldType.INT,
            getString = { it.talkie.tokenBudget.toString() },
            withString = { c, v -> rtr(c) { it.copy(tokenBudget = int("router.token_budget", v)) } },
        ),
        EditableField(
            "router.generation.temperature", ConfigFieldType.DOUBLE,
            getString = { it.talkie.generation.temperature.toString() },
            withString = { c, v -> gen(c) { it.copy(temperature = dbl("router.generation.temperature", v)) } },
        ),
        EditableField(
            "router.generation.top_p", ConfigFieldType.DOUBLE,
            getString = { it.talkie.generation.topP.toString() },
            withString = { c, v -> gen(c) { it.copy(topP = dbl("router.generation.top_p", v)) } },
        ),
        EditableField(
            "router.generation.max_tokens", ConfigFieldType.INT,
            getString = { it.talkie.generation.maxTokens.toString() },
            withString = { c, v -> gen(c) { it.copy(maxTokens = int("router.generation.max_tokens", v)) } },
        ),
        EditableField(
            "router.narration.temperature", ConfigFieldType.DOUBLE,
            getString = { it.talkie.narration.temperature.toString() },
            withString = { c, v -> nar(c) { it.copy(temperature = dbl("router.narration.temperature", v)) } },
        ),
        EditableField(
            "router.narration.top_p", ConfigFieldType.DOUBLE,
            getString = { it.talkie.narration.topP.toString() },
            withString = { c, v -> nar(c) { it.copy(topP = dbl("router.narration.top_p", v)) } },
        ),
        EditableField(
            "router.narration.max_tokens", ConfigFieldType.INT,
            getString = { it.talkie.narration.maxTokens.toString() },
            withString = { c, v -> nar(c) { it.copy(maxTokens = int("router.narration.max_tokens", v)) } },
        ),
        EditableField(
            "router.narration.max_per_run", ConfigFieldType.INT,
            getString = { it.talkie.narration.maxPerRun.toString() },
            withString = { c, v -> nar(c) { it.copy(maxPerRun = int("router.narration.max_per_run", v)) } },
        ),
        EditableField(
            "router.narration.model_id", ConfigFieldType.STRING,
            getString = { it.talkie.narration.modelId.orEmpty() },
            withString = { c, v -> nar(c) { it.copy(modelId = v.trim().ifBlank { null }) } },
        ),
        EditableField(
            "personalization.enabled", ConfigFieldType.BOOL,
            getString = { it.personalization.enabled.toString() },
            withString = { c, v -> per(c) { it.copy(enabled = bool("personalization.enabled", v)) } },
        ),
        EditableField(
            "personalization.learn", ConfigFieldType.BOOL,
            getString = { it.personalization.learn.toString() },
            withString = { c, v -> per(c) { it.copy(learn = bool("personalization.learn", v)) } },
        ),
        EditableField(
            "project.enabled", ConfigFieldType.BOOL,
            getString = { it.project.enabled.toString() },
            withString = { c, v -> prj(c) { it.copy(enabled = bool("project.enabled", v)) } },
        ),
        EditableField(
            "project.memory_k", ConfigFieldType.INT,
            getString = { it.project.memoryK.toString() },
            withString = { c, v -> prj(c) { it.copy(memoryK = int("project.memory_k", v)) } },
        ),
        EditableField(
            "project.max_depth", ConfigFieldType.INT,
            getString = { it.project.maxDepth.toString() },
            withString = { c, v -> prj(c) { it.copy(maxDepth = int("project.max_depth", v)) } },
        ),
        EditableField(
            "project.inject_top_k", ConfigFieldType.INT,
            getString = { it.project.injectTopK.toString() },
            withString = { c, v -> prj(c) { it.copy(injectTopK = int("project.inject_top_k", v)) } },
        ),
        EditableField(
            "orchestration.stage_timeout_ms", ConfigFieldType.LONG,
            getString = { it.orchestration.stageTimeoutMs.toString() },
            withString = { c, v -> orc(c) { it.copy(stageTimeoutMs = lng("orchestration.stage_timeout_ms", v)) } },
        ),
        EditableField(
            "orchestration.journal_compaction_token_threshold", ConfigFieldType.INT,
            getString = { it.orchestration.journalCompactionTokenThreshold.toString() },
            withString = { c, v -> orc(c) { it.copy(journalCompactionTokenThreshold = int(JCT_KEY, v)) } },
        ),
        EditableField(
            "orchestration.resume_abandoned_max_age_minutes", ConfigFieldType.LONG,
            getString = { it.orchestration.resumeAbandonedMaxAgeMinutes.toString() },
            withString = { c, v ->
                orc(c) {
                    it.copy(
                        resumeAbandonedMaxAgeMinutes = lng("orchestration.resume_abandoned_max_age_minutes", v)
                    )
                }
            },
        ),
    )

    private const val JCT_KEY = "orchestration.journal_compaction_token_threshold"

    /** Keys that are persisted and editable but only take effect after a server restart. */
    val restartRequiredKeys: Set<String> = setOf("server.host", "server.port")

    private val byKey: Map<String, EditableField> = fields.associateBy { it.key }

    /**
     * Folds [patch] (key → string value) onto [cfg]. Returns the updated config, or a failure
     * carrying a [ConfigEditError] when a key is unknown/non-editable or a value is malformed.
     */
    fun apply(cfg: CorrexConfig, patch: Map<String, String>): Result<CorrexConfig> = runCatching {
        patch.entries.fold(cfg) { acc, (key, value) ->
            val field = byKey[key] ?: throw ConfigEditError("unknown or non-editable key: $key")
            field.withString(acc, value)
        }
    }
}

// --- file-private helpers (kept out of the object so it stays a thin registry) ---

// Section-scoped copy helpers keep each field's withString a single readable line.
private fun srv(c: CorrexConfig, f: (ServerConfig) -> ServerConfig) = c.copy(server = f(c.server))
private fun tui(c: CorrexConfig, f: (TuiConfig) -> TuiConfig) = c.copy(tui = f(c.tui))
private fun cli(c: CorrexConfig, f: (CliConfig) -> CliConfig) = c.copy(cli = f(c.cli))
private fun tls(c: CorrexConfig, f: (ToolsConfig) -> ToolsConfig) = c.copy(tools = f(c.tools))
private fun rtr(c: CorrexConfig, f: (TalkieConfig) -> TalkieConfig) = c.copy(talkie = f(c.talkie))
private fun gen(c: CorrexConfig, f: (GenerationSettings) -> GenerationSettings) =
    rtr(c) { it.copy(generation = f(it.generation)) }
private fun nar(c: CorrexConfig, f: (NarrationSettings) -> NarrationSettings) =
    rtr(c) { it.copy(narration = f(it.narration)) }
private fun per(c: CorrexConfig, f: (PersonalizationConfig) -> PersonalizationConfig) =
    c.copy(personalization = f(c.personalization))
private fun prj(c: CorrexConfig, f: (ProjectConfig) -> ProjectConfig) = c.copy(project = f(c.project))
private fun orc(c: CorrexConfig, f: (OrchestrationKnobs) -> OrchestrationKnobs) =
    c.copy(orchestration = f(c.orchestration))

private fun int(key: String, v: String): Int =
    v.trim().toIntOrNull() ?: throw ConfigEditError("$key must be an integer, got '$v'")

private fun lng(key: String, v: String): Long =
    v.trim().toLongOrNull() ?: throw ConfigEditError("$key must be an integer, got '$v'")

private fun dbl(key: String, v: String): Double =
    v.trim().toDoubleOrNull() ?: throw ConfigEditError("$key must be a number, got '$v'")

private fun bool(key: String, v: String): Boolean = when (v.trim().lowercase()) {
    "true" -> true
    "false" -> false
    else -> throw ConfigEditError("$key must be true or false, got '$v'")
}

private fun enum(key: String, v: String, options: List<String>): String =
    if (v in options) v else throw ConfigEditError("$key must be one of ${options.joinToString("|")}, got '$v'")
