//! Codegen configuration — deserialized from `mizan.toml` at the consumer
//! project root. Replaces the JS substrate's `mizan.config.mjs`.
//!
//! Example:
//!
//! ```toml
//! project_id = "blazr-studio"
//! output = "src/api"
//! targets = ["react"]
//!
//! [source.fastapi]
//! module = "blazr_session.handlers"
//! cwd = "../.."
//! command = ["uv", "run", "python"]
//!
//! [rust_kernel]
//! path = "../../mizan/frontends/mizan-rust"
//! ```

use std::collections::BTreeMap;
use std::path::PathBuf;

use serde::Deserialize;


#[derive(Debug, Deserialize)]
pub struct Config {
    #[serde(default)]
    pub project_id: Option<String>,

    #[serde(default = "default_output")]
    pub output: PathBuf,

    #[serde(default = "default_targets")]
    pub targets: Vec<String>,

    #[serde(default)]
    pub source: SourceConfig,

    #[serde(default)]
    pub rust_kernel: Option<RustKernelSpec>,

    #[serde(default)]
    pub rust_crate_name: Option<String>,
}


fn default_output() -> PathBuf {
    PathBuf::from("src/api")
}


fn default_targets() -> Vec<String> {
    vec!["react".to_string()]
}


#[derive(Debug, Deserialize, Default)]
pub struct SourceConfig {
    #[serde(default)]
    pub fastapi: Option<FastapiSource>,

    #[serde(default)]
    pub django: Option<DjangoSource>,

    /// Canonical "Pydantic + Rust" DX path. The Rust crate is the IR
    /// authority; an optional `pydantic` sub-block invokes decoru as a
    /// pre-step to author Rust types from Pydantic models. Pure-Rust
    /// usage (no Pydantic) just omits the sub-block.
    #[serde(default)]
    pub rust: Option<RustSource>,

    /// `[source.script]` — generic source. Spawn an arbitrary command and
    /// read its stdout as KDL IR. Use when none of the language-specific
    /// sources fit — e.g. a Python module that walks `mizan_core.registry`
    /// for a non-Django/non-FastAPI consumer, or a custom IR emitter.
    /// Keeps mizan-codegen out of the business of knowing every possible
    /// backend language while preserving the "subprocess emits KDL"
    /// contract every other source already follows.
    #[serde(default)]
    pub script: Option<ScriptSource>,
}


#[derive(Debug, Deserialize)]
pub struct FastapiSource {
    pub module: String,

    #[serde(default)]
    pub cwd: Option<PathBuf>,

    #[serde(default)]
    pub python: Option<String>,

    #[serde(default)]
    pub command: Option<Vec<String>>,

    #[serde(default)]
    pub env: BTreeMap<String, String>,
}


#[derive(Debug, Deserialize)]
pub struct DjangoSource {
    pub manage_path: PathBuf,

    #[serde(default)]
    pub python: Option<String>,

    #[serde(default)]
    pub command: Option<Vec<String>>,

    #[serde(default)]
    pub env: BTreeMap<String, String>,
}


/// `[source.rust]` — spawn a Cargo binary that emits the Mizan IR (KDL)
/// to stdout. The binary uses `mizan_core::build_ir()` after force-linking
/// the consumer crate's `#[derive(Mizan)]` types and `#[mizan::client]`
/// functions.
#[derive(Debug, Deserialize)]
pub struct RustSource {
    /// Path to the consumer's Cargo.toml, relative to the codegen config
    /// directory. Defaults to `Cargo.toml` (i.e. config_dir/Cargo.toml).
    #[serde(default)]
    pub manifest_path: Option<PathBuf>,

    /// Name of the binary under `[[bin]]` that exports the IR. Defaults
    /// to `emit-mizan-ir` — the convention this substrate documents.
    #[serde(default = "default_rust_bin")]
    pub bin: String,

    /// Cargo features to enable when building the bin.
    #[serde(default)]
    pub features: Vec<String>,

    /// Build in release mode. Defaults to false (dev mode is faster for
    /// codegen, and the binary is throwaway).
    #[serde(default)]
    pub release: bool,

    /// Environment overrides for the cargo subprocess.
    #[serde(default)]
    pub env: BTreeMap<String, String>,

    /// Optional pre-step — invoke decoru on a Pydantic source module
    /// before running the Cargo bin. When present, the pipeline becomes:
    ///   1. python + decoru → write Rust types to `pydantic.output`
    ///   2. cargo run --bin <bin> → emit IR to stdout
    /// Omit for pure-Rust usage (hand-authored or otherwise-generated
    /// Rust types with `#[derive(Mizan)]`).
    #[serde(default)]
    pub pydantic: Option<PydanticPreStep>,
}


fn default_rust_bin() -> String {
    "emit-mizan-ir".to_string()
}


/// Pydantic → Rust pre-step. Runs an embedded Python helper that walks
/// the named module for `BaseModel` subclasses and invokes decoru's
/// `walk_pydantic_model` + `emit_rust_struct` to produce a Rust file.
#[derive(Debug, Deserialize)]
pub struct PydanticPreStep {
    /// Python module name to import (e.g. `claude_manage.schema`).
    pub module: String,

    /// Path to write the generated Rust file, relative to the codegen
    /// config directory.
    pub output: PathBuf,

    /// Working directory for the python subprocess, relative to the
    /// codegen config directory. Defaults to the config directory itself.
    /// The script prepends this to `sys.path` so the module imports.
    #[serde(default)]
    pub cwd: Option<PathBuf>,

    /// Python executable. Defaults to `python`.
    #[serde(default)]
    pub python: Option<String>,

    /// Full command override (e.g. `["uv", "run", "python"]`). Wins over
    /// `python` when present.
    #[serde(default)]
    pub command: Option<Vec<String>>,

    /// Derive macros to apply to every generated struct. The default
    /// matches the Mizan-canonical set used in `cores/rust/blazr/session`
    /// — serde + mizan_core::Mizan for end-to-end RPC participation.
    #[serde(default = "default_pydantic_derives")]
    pub derives: Vec<String>,

    /// Optional prelude inserted at the top of the generated file
    /// (typically a "// AUTO-GENERATED" warning + `use` statements for
    /// referenced types not produced by decoru itself).
    #[serde(default)]
    pub header: Option<String>,

    /// Environment overrides for the python subprocess.
    #[serde(default)]
    pub env: BTreeMap<String, String>,
}


fn default_pydantic_derives() -> Vec<String> {
    vec![
        "Debug".to_string(),
        "Clone".to_string(),
        "::serde::Serialize".to_string(),
        "::serde::Deserialize".to_string(),
        "::mizan_core::Mizan".to_string(),
    ]
}


/// `[source.script]` — generic stdout-of-arbitrary-command source.
///
/// Spawns `command` with `args`, reads its stdout, and parses it as KDL
/// Mizan IR. The same contract every other source follows; this one just
/// doesn't bake in any language-specific assumptions.
///
/// Example:
///
/// ```toml
/// [source.script]
/// command = ["uv", "run", "python", "-m", "holomorphic.emit_ir"]
/// ```
#[derive(Debug, Deserialize)]
pub struct ScriptSource {
    /// Full command vector. First entry is the program; rest are argv.
    /// Must be non-empty.
    pub command: Vec<String>,

    /// Working directory for the subprocess, relative to the codegen
    /// config directory. Defaults to the config directory itself.
    #[serde(default)]
    pub cwd: Option<PathBuf>,

    /// Environment overrides.
    #[serde(default)]
    pub env: BTreeMap<String, String>,
}


#[derive(Debug, Deserialize, Clone)]
#[serde(untagged)]
pub enum RustKernelSpec {
    Path {
        path: String,
    },
    Git {
        git: String,
        #[serde(default)]
        tag: Option<String>,
        #[serde(default)]
        rev: Option<String>,
        #[serde(default)]
        branch: Option<String>,
    },
    Version {
        version: String,
    },
}
