package com.correx.core.config

import kotlinx.serialization.Serializable

@Serializable
data class CorrexConfig(
    val server: ServerConfig = ServerConfig(),
    val tui: TuiConfig = TuiConfig(),
    val cli: CliConfig = CliConfig(),
    val tools: ToolsConfig = ToolsConfig(),
    val providers: List<ProviderConfig> = emptyList(),
    val router: RouterConfig = RouterConfig(),
    val models: List<ModelConfig> = emptyList(),
    val modelsSettings: ModelsSettings = ModelsSettings(),
    val artifacts: List<ArtifactKindConfig> = emptyList(),
    val project: ProjectConfig = ProjectConfig(),
)

/**
 * Project-scoped, cross-session memory. When [enabled], the decision journal is distilled
 * to durable per-repo memory at session end and retrieved (top-[memoryK]) at session start.
 * [root] is the repo root key; empty means the current working directory.
 */
@Serializable
data class ProjectConfig(
    val enabled: Boolean = false,
    val root: String = "",
    val memoryK: Int = 5,
    val maxDepth: Int = 4,
    val ignoreGlobs: List<String> = DEFAULT_IGNORES,
    val injectTopK: Int = 30,
) {
    companion object {
        val DEFAULT_IGNORES: List<String> = listOf(
            ".git", "node_modules", "build", "target", ".gradle", "dist", ".idea", ".opencode",
        )
    }
}

/**
 * A user-declared artifact kind. [schemaPath] points to a JSON file holding the
 * kind's JSON schema (object type, typed properties, required, additionalProperties).
 * A path-to-file (not inline TOML) is used so schemas can nest arbitrarily and be
 * parsed with a real JSON parser. [llmEmitted] kinds are validated against this
 * schema, never trusted.
 */
@Serializable
data class ArtifactKindConfig(
    val id: String,
    val schemaPath: String,
    val llmEmitted: Boolean = false,
)

@Serializable
data class ServerConfig(
    val host: String = "localhost",
    val port: Int = 8080,
)

@Serializable
data class TuiConfig(
    val theme: String = "dark",
    val sessionListLimit: Int = 5,
)

@Serializable
data class CliConfig(
    val defaultOutput: String = "human",
)

@Serializable
data class ToolsConfig(
    val sandboxRoot: String = "",
    val workingDir: String = "",
    val shellAllowedExecutables: List<String> = emptyList(),
    val defaultSystemPromptPath: String = "~/.config/correx/prompts/default_system.md",
    val shellEnabled: Boolean = true,
    val fileReadEnabled: Boolean = true,
    val fileWriteEnabled: Boolean = true,
    val fileEditEnabled: Boolean = true,
    val workspaceRoot: String = "",
    val privilegedLocations: List<String> = DEFAULT_PRIVILEGED_LOCATIONS,
    val interpreterExecutables: List<String> = DEFAULT_INTERPRETERS,
    val networkAllowedHosts: List<String> = emptyList(),
    val networkDeniedHosts: List<String> = emptyList(),
    val allowedWorkspaceRoots: List<String> = emptyList(),
) {
    companion object {
        val DEFAULT_PRIVILEGED_LOCATIONS: List<String> = listOf(
            "/etc", "/usr", "/bin", "/sbin", "/boot", "/lib", "/lib64",
            "/sys", "/proc", "/dev", "/root",
        )
        val DEFAULT_INTERPRETERS: List<String> = listOf(
            "python", "python2", "python3", "bash", "sh", "zsh", "fish",
            "node", "deno", "ruby", "perl", "php", "Rscript", "lua", "groovy",
        )
    }
}

@Serializable
data class ProviderConfig(
    val id: String,
    val type: String,
    val modelId: String,
    val modelPath: String = "",
    val url: String = "http://127.0.0.1:10000",
    val capabilities: Map<String, Double> = emptyMap(),
)

@Serializable
data class RouterConfig(
    val embedder: EmbedderConfig = EmbedderConfig(),
    val l3: L3Config = L3Config(),
    val conversationKeepLast: Int = 6,
    val retrievalK: Int = 5,
    val tokenBudget: Int = 4096,
    val generation: GenerationSettings = GenerationSettings(),
    val narration: NarrationSettings = NarrationSettings(),
)

/** Sampling/length knobs for router chat + steering replies. */
@Serializable
data class GenerationSettings(
    val temperature: Double = 0.7,
    val topP: Double = 0.9,
    val maxTokens: Int = 512,
)

/** Router narration knobs. Larger maxTokens than chat so reasoning models can
 *  finish "thinking" and still emit the line; maxPerRun caps narrations per run. */
@Serializable
data class NarrationSettings(
    val temperature: Double = 0.7,
    val topP: Double = 0.9,
    val maxTokens: Int = 1024,
    val maxPerRun: Int = 100,
)

@Serializable
data class EmbedderConfig(
    val backend: String = "noop",
    val dimension: Int = 1536,
    val url: String? = null,
    val modelId: String? = null,
)

@Serializable
data class L3Config(
    val backend: String = "in_memory",
    val persistPath: String? = null,
    val pythonExecutable: String = "python3",
    val scriptPath: String? = null,
    val dim: Int = 1536,
    val bitWidth: Int = 4,
)

@Serializable
data class ModelConfig(
    val id: String,
    val modelPath: String,
    val contextSize: Int = 8192,
    val params: Map<String, String> = emptyMap(),
    val capabilities: Map<String, Double> = emptyMap(),
)

@Serializable
data class ModelsSettings(
    val defaultModel: String? = null,
    val llamaServerBin: String = "llama-server",
    val host: String = "127.0.0.1",
    val port: Int = 10000,
)
