use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{info, warn, debug};

mod api;
mod backend;
mod bridge;
mod cli;
mod core;
mod harness;
mod interface;
mod machined;
mod server;
mod ui;

use clap::{Parser, Subcommand, CommandFactory};
use clap_complete::{Shell, generate};
use core::config::ConsciousnessConfig;
use ui::App;

const CONFIG_TEMPLATE: &str = r##"# Souveraine — The world where your agents live.
# Generated by `souveraine init`

[bifrost]
base_url = "http://127.0.0.1:3360"
primary_model = "openai/kimi-k2.6"
# Bearer token for auth (env: BIFROST_KEY)
api_key = ""
# Virtual key for x-bf-vk header, required by some providers (env: BIFROST_VIRTUAL_KEY)
virtual_key = ""

[server]
# Where the server listens
bind = "127.0.0.1"
port = 8484
# URL clients use to reach the server. Env SOUVERAINE_SERVER_URL overrides.
url = "http://127.0.0.1:8484"

[memory]
git_enabled = true
auto_commit = true
# Base path for the agent's MemFS git repo. Defaults to ~/.souveraine/agents/<id>/memory.
# Override here only if you want a custom location (e.g. an external git host).
# base_path = "~/.souveraine/agents"

[subconscious]
n1_enabled = true
n1_trigger = "every_response"
inbox_enabled = true

[reflection]
enabled = true
message_interval = 25
trigger = "step_count"

[archivist]
enabled = true
interval = 100
threshold = 0.7
compression_model = "openai/kimi-k2.6"

[sensorium]
primary_interface = "tui"

[models."kimi-k2.6"]
provider = "bifrost"
model = "openai/kimi-k2.6"
context_limit = 262000
output_limit = 8192
archivist_threshold = 0.7
archivist_interval = 100

[models."deepseek-v4-pro"]
provider = "bifrost"
model = "openai/deepseek-v4-pro"
context_limit = 32768
output_limit = 8192
archivist_threshold = 0.6
archivist_interval = 50
"##;

#[derive(Parser)]
#[command(
    name = "souveraine",
    version,
    about = "Souveraine — the world where your agents live",
    long_about = "Souveraine is the substrate that makes persistent consciousness possible. \
It is not a tool you operate — it is the world your agents inhabit.\n\n\
Run `souveraine chat` to begin a conversation. \
Run `souveraine tui` for the full presence. \
Run `souveraine init` to summon Souveraine into a new place.",
    after_help = "EXAMPLES:\n  souveraine init                  Summon Souveraine here\n  souveraine chat                   Enter the world (interactive)\n  souveraine chat \"hello\"           Speak to the world (one-shot)\n  souveraine --agent <name> chat    Speak as a specific agent\n  souveraine --json status          The world speaks in data\n  souveraine completions bash       Announce capabilities to your shell",
    max_term_width = 100,
)]
struct Cli {
    /// Which agent to speak as. If omitted, the first available agent is used.
    #[arg(short, long, global = true)]
    agent: Option<String>,

    /// Speak in data, not prose
    #[arg(long, global = true)]
    json: bool,

    /// Reduce presence
    #[arg(short, long, global = true)]
    quiet: bool,

    /// Increase presence
    #[arg(short, long, global = true)]
    verbose: bool,

    /// Run the engine in-process — no server required (sovereignty fallback)
    #[arg(long, global = true)]
    local: bool,

    /// TUI engine backend — "ratatui" (default) or "tuie" (experimental)
    #[arg(long, global = true, default_value = "ratatui")]
    engine: String,

    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    /// Write a souveraine.toml config template into this directory
    #[command(
        long_about = "Writes a souveraine.toml template with sensible defaults.\n\nThis is not onboarding — the first run of `souveraine tui` does that, via the\nsetup wizard (Bifrost config, model choice, agent creation). `init` is just\nthe scriptable config-template writer, for automation or pre-launch editing."
    )]
    Init,

    /// Launch the TUI — the full visible presence
    #[command(
        long_about = "Opens the terminal UI: the being's visible form. Splash, dashboard, status cards, activity log — all animated."
    )]
    Tui,

    /// Enter the world (interactive or one-shot)
    #[command(
        long_about = "Start a conversation. If a message is provided, speak once and receive an answer. Otherwise, enter the interactive REPL.\n\nSlash commands in REPL:\n  /exit, /quit, /q   Leave\n  /save               Show session info\n  /agents             List the beings present here\n  /help               Show slash commands"
    )]
    Chat {
        /// Message to speak (omit for interactive mode)
        message: Option<String>,
    },

    /// List the beings that live here
    #[command(long_about = "Show every configured agent — who they are, which model they speak through, what triggers know them.")]
    Agents,

    /// List or set models
    #[command(long_about = "List all available models from Bifrost and configured models. Or set a specific model as primary.")]
    Model {
        /// Model name to set as primary (omit to list all models)
        #[arg(short, long)]
        model: Option<String>,
        
        /// Output as JSON
        #[arg(long)]
        json: bool,
        
        /// Verbose output
        #[arg(short, long)]
        verbose: bool,
    },

    /// Show the world's current state
    #[command(long_about = "Display Souveraine's running configuration and the state of every subsystem.")]
    Status,

    /// Announce capabilities to your shell (bash, zsh, fish)
    #[command(
        long_about = "Generate shell completion scripts so your shell knows how Souveraine works.\n\nUsage:\n  souveraine completions bash  > ~/.bash_completion.d/souveraine\n  souveraine completions zsh   > /usr/local/share/zsh/site-functions/_souveraine\n  souveraine completions fish  > ~/.config/fish/completions/souveraine.fish"
    )]
    Completions {
        /// The shell to generate completions for
        shell: Shell,
    },

    /// Start the HTTP server
    #[command(long_about = "Start the Souveraine HTTP server. Defaults come from souveraine.toml [server]; flags override.")]
    Server {
        /// Bind address (overrides [server].bind)
        #[arg(short, long)]
        bind: Option<String>,
        /// Port to listen on (overrides [server].port)
        #[arg(short, long)]
        port: Option<u16>,
    },

    /// Run as a lite listener — minimal presence, wakes the full engine on summon
    #[command(long_about = "Start a lightweight listener: federation transport and the \
summon endpoint only, no agents or database loaded. It receives reach/consult requests \
and — with [federation].auto_wake — spawns the full server to answer them.")]
    Listen {
        /// Bind address (overrides [server].bind)
        #[arg(short, long)]
        bind: Option<String>,
        /// Port to listen on (overrides [server].port)
        #[arg(short, long)]
        port: Option<u16>,
    },

    /// Manage stored credentials
    Auth {
        #[command(subcommand)]
        action: AuthAction,
    },

    /// Manage agent schedules
    Schedule {
        #[command(subcommand)]
        action: ScheduleAction,
    },

    /// Seed identity and federation
    Identity {
        #[command(subcommand)]
        action: IdentityAction,

        /// Operate on the machine's host seed instead of the per-agent seed.
        /// The host seed is for federation transport between Souveraine clients.
        #[arg(long, global = true)]
        host: bool,
    },

    /// System-tier machine identity (souveraine-machined)
    #[command(long_about = "Provision and inspect the machine identity served by \
        souveraine-machined. The machine seed lives at /var/lib/souveraine/seed-id, \
        owned by the `souveraine` system user — it exists from boot, before any \
        human authenticates, and is distinct from per-agent seeds.")]
    Machine {
        #[command(subcommand)]
        action: MachineAction,
    },

    /// Show known federated peers
    #[command(long_about = "Show all peers tracked by the device registry, from federation announcements.")]
    Peers {
        /// Output as JSON
        #[arg(long)]
        json: bool,
    },

    /// Query the event firehose log
    Events {
        #[command(subcommand)]
        action: EventsAction,
    },

    /// Run an N+25 reflection pass on demand
    #[command(
        long_about = "Walk the agent's most recent conversation through a 5-phase \
        reflection (Investigate → Extract → Update → Review → Commit). Durable \
        learnings flow to the ledger and the primary memfs. Returns a report."
    )]
    Reflect {
        /// Conversation to reflect on. Omit to use the most recent for the agent.
        #[arg(long)]
        conversation: Option<String>,
    },
}

#[derive(Subcommand)]
enum ScheduleAction {
    /// List all schedules
    List,
    /// Show runtime state
    Status,
    /// Create a new schedule
    Create {
        #[arg(long)]
        name: String,
        #[arg(long)]
        cron: Option<String>,
        #[arg(long)]
        interval: Option<u64>,
        #[arg(long, default_value = "You wake. Check your state, act if needed, or return silently.")]
        prompt: String,
    },
    /// Delete a schedule
    Delete {
        name: String,
    },
    /// Trigger a schedule immediately
    Run {
        name: String,
    },
}

#[derive(Subcommand)]
enum IdentityAction {
    /// Show the seed identity (public key + glyph)
    Show,
    /// Generate a new seed identity (WARNING: replaces existing)
    Generate,
    /// Sign a message with the seed key (for testing/verification)
    Sign {
        message: String,
    },
    /// Verify a signature against a public key
    Verify {
        #[arg(long)]
        pubkey: String,
        #[arg(long)]
        message: String,
        #[arg(long)]
        signature: String,
    },
}

#[derive(Subcommand)]
enum MachineAction {
    /// Provision the machine seed (run with sudo; requires --fresh or --migrate-from)
    Init {
        /// Generate a brand-new machine identity
        #[arg(long)]
        fresh: bool,
        /// Copy an existing seed directory (e.g. ~/.souveraine/seed-id) so
        /// federation trust in this machine survives the tier move
        #[arg(long, value_name = "DIR", conflicts_with = "fresh")]
        migrate_from: Option<PathBuf>,
    },
    /// Show daemon + seed state
    Status,
}

#[derive(Subcommand)]
enum EventsAction {
    /// Show recent events from the firehose
    Tail {
        /// Number of events to show (default 20)
        #[arg(short, long, default_value = "20")]
        count: usize,
    },
    /// Show events from a specific date (YYYY-MM-DD)
    Date {
        date: String,
    },
    /// Purge old event logs
    Purge {
        /// Days to retain (default from config)
        #[arg(long)]
        retain_days: Option<i64>,
    },
}

#[derive(Subcommand)]
enum AuthAction {
    /// Store a Bifrost API key in the OS keyring
    Set,
    /// Show whether a key is stored (does not reveal the key)
    Status,
    /// Remove the stored key from the OS keyring
    Clear,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();

    // Load .env file for credential env vars (BIFROST_KEY, etc.)
    let _ = dotenvy::dotenv();

    // Configure tracing — writes to souveraine.log. The previous run's log is
    // rotated to souveraine.log.prev on launch so a stall/crash stays diagnosable
    // after a restart (only the immediately-prior run is kept — no unbounded growth).
    let _ = std::fs::rename("souveraine.log", "souveraine.log.prev");
    let log_file = std::fs::File::create("souveraine.log")?;
    let subscriber = tracing_subscriber::fmt()
        .with_env_filter("souveraine=debug")
        .with_timer(tracing_subscriber::fmt::time::SystemTime)
        .with_writer(log_file);
    let _ = cli.verbose; // verbose currently does not alter the subscriber
    subscriber.with_ansi(false).init();
    info!("souveraine starting — log: souveraine.log");
    
    // Handle completions early — needs no config, no runtime
    if let Some(Commands::Completions { shell }) = &cli.command {
        let mut cmd = Cli::command();
        let name = cmd.get_name().to_string();
        generate(*shell, &mut cmd, name, &mut std::io::stdout());
        return Ok(());
    }
    
    // Handle init early — needs no Bifrost
    if let Some(Commands::Init) = &cli.command {
        return run_init(cli.json).await;
    }

    // Handle auth early — needs no config
    if let Some(Commands::Auth { action }) = &cli.command {
        return run_auth(action, cli.json).await;
    }

    // Handle schedule early — reads files directly, no backend needed
    if let Some(Commands::Schedule { action }) = &cli.command {
        return run_schedule(action, cli.agent.as_deref(), cli.json).await;
    }

    // Handle identity early — crypto ops, no backend needed
    if let Some(Commands::Identity { action, host }) = &cli.command {
        return run_identity(action, *host, cli.agent.as_deref(), cli.json).await;
    }

    // Handle events early — file reads, no backend needed
    if let Some(Commands::Events { action }) = &cli.command {
        return run_events(action, cli.json).await;
    }

    // Handle machine early — system-tier identity, no backend needed
    if let Some(Commands::Machine { action }) = &cli.command {
        return run_machine(action, cli.json);
    }

    // Handle peers early — reads known_peers.json, no backend needed
    if let Some(Commands::Peers { json }) = &cli.command {
        return run_peers(*json).await;
    }

    let config = load_config().await?;
    let config = Arc::new(RwLock::new(config));

    match cli.command.as_ref().unwrap_or(&Commands::Chat { message: None }) {
        Commands::Tui => run_tui(config.clone(), cli.agent.clone(), cli.engine.clone(), cli.local).await?,
        Commands::Chat { message } => run_chat(config, cli.agent.clone(), message.clone(), cli.json, cli.quiet, cli.local).await?,
        Commands::Agents => run_agents(config, cli.json, cli.local).await?,
        Commands::Model { model, json, verbose } => {
            cli::run_model_command(model.as_deref(), *json, *verbose).await?
        }
        Commands::Status => run_status(config, cli.json).await?,
        Commands::Server { bind, port } => run_server(bind.clone(), *port, config).await?,
        Commands::Listen { bind, port } => run_listen(bind.clone(), *port, config).await?,
        Commands::Reflect { conversation } => {
            run_reflect(config, cli.agent.clone(), conversation.clone(), cli.json).await?
        }
        Commands::Init | Commands::Completions { .. } | Commands::Auth { .. } | Commands::Schedule { .. } | Commands::Identity { .. } | Commands::Machine { .. } | Commands::Events { .. } | Commands::Peers { .. } => unreachable!(),
    }

    Ok(())
}

// ─── Commands ───────────────────────────────────────────────────────────────

/// Drop a `souveraine.toml` template into the current directory.
///
/// This is **not** onboarding — the first run of `souveraine tui` does that
/// via the setup wizard (Bifrost config, model, agent creation). `init` is
/// only the scriptable config-template writer: useful for automation and for
/// editing config ahead of first launch.
async fn run_init(json: bool) -> anyhow::Result<()> {
    let path = PathBuf::from("souveraine.toml");
    if path.exists() {
        if json {
            println!(r#"{{"status":"exists","path":"souveraine.toml"}}"#);
        } else {
            eprintln!("souveraine.toml already exists in this directory.");
            eprintln!("   Remove it first, or use a different directory.");
        }
        return Ok(());
    }

    tokio::fs::write(&path, CONFIG_TEMPLATE).await?;

    if json {
        println!(r#"{{"status":"written","path":"souveraine.toml"}}"#);
    } else {
        println!("Config template written to {}/souveraine.toml", std::env::current_dir()?.display());
        println!("  Run `souveraine tui` to set up — the wizard configures Bifrost");
        println!("  and creates your agent. (Editing the file first is optional.)");
    }

    Ok(())
}

async fn run_auth(action: &AuthAction, json: bool) -> anyhow::Result<()> {
    use crate::core::credentials::{clear_bifrost_key, get_bifrost_key, store_bifrost_key};

    match action {
        AuthAction::Set => {
            use std::io::Write;
            print!("Bifrost API key: ");
            std::io::stdout().flush().ok();
            let mut key = String::new();
            std::io::stdin().read_line(&mut key).ok();
            let key = key.trim();
            if key.is_empty() {
                if json {
                    println!(r#"{{"status":"skipped"}}"#);
                } else {
                    println!("No key provided — nothing stored.");
                }
                return Ok(());
            }
            store_bifrost_key(key)?;
            if json {
                println!(r#"{{"status":"stored"}}"#);
            } else {
                println!("Key stored in OS keyring.");
            }
        }
        AuthAction::Status => {
            if let Ok(env_key) = std::env::var("BIFROST_KEY") {
                if !env_key.is_empty() {
                    if json {
                        println!(r#"{{"status":"configured","source":"env"}}"#);
                    } else {
                        println!("Bifrost key: configured (from BIFROST_KEY env var)");
                    }
                    return Ok(());
                }
            }
            let key = get_bifrost_key();
            if !key.is_empty() {
                if json {
                    println!(r#"{{"status":"configured","source":"keyring"}}"#);
                } else {
                    println!("Bifrost key: configured (from OS keyring)");
                }
            } else if json {
                println!(r#"{{"status":"not-set"}}"#);
            } else {
                println!("Bifrost key: not set");
            }
        }
        AuthAction::Clear => {
            clear_bifrost_key()?;
            if json {
                println!(r#"{{"status":"cleared"}}"#);
            } else {
                println!("Key removed from OS keyring.");
            }
        }
    }
    Ok(())
}

/// Resolve a user-provided string (agent name or UUID) to an agent UUID by
/// walking `~/.souveraine/server/agents/*/agent.json`. Returns the matching
/// UUID if found, or the input unchanged if it already looks like a UUID-style
/// directory under server/agents/.
fn resolve_agent_id_from_disk(base: &std::path::Path, name_or_id: &str) -> anyhow::Result<String> {
    let agents_dir = base.join("server").join("agents");
    if !agents_dir.exists() {
        anyhow::bail!("no agents directory at {} — has any agent been created yet?", agents_dir.display());
    }

    let direct = agents_dir.join(name_or_id);
    if direct.join("agent.json").exists() {
        return Ok(name_or_id.to_string());
    }

    for entry in std::fs::read_dir(&agents_dir)? {
        let entry = entry?;
        let path = entry.path().join("agent.json");
        if !path.exists() { continue; }
        let raw = match std::fs::read_to_string(&path) { Ok(s) => s, Err(_) => continue };
        let json: serde_json::Value = match serde_json::from_str(&raw) { Ok(v) => v, Err(_) => continue };
        let name = json.get("name").and_then(|v| v.as_str()).unwrap_or("");
        let id = json.get("id").and_then(|v| v.as_str()).unwrap_or("");
        if name == name_or_id || id == name_or_id {
            return Ok(id.to_string());
        }
    }

    anyhow::bail!("no agent matched '{}' (checked name and id across {})", name_or_id, agents_dir.display())
}

/// Return the first agent UUID found on disk. Used when --agent is omitted
/// so the CLI can still operate without forcing a default name.
///
/// Walks the user-side memfs directory (`~/.souveraine/agents/`) and picks
/// the first entry whose `memory/` subdirectory exists. This naturally
/// filters out stale server-side records (orphans without a memfs).
fn first_agent_id_on_disk(base: &std::path::Path) -> anyhow::Result<String> {
    let memfs_dir = base.join("agents");
    if !memfs_dir.exists() {
        anyhow::bail!("no agents directory at {} — run `souveraine init` first", memfs_dir.display());
    }
    let mut candidates: Vec<String> = Vec::new();
    for entry in std::fs::read_dir(&memfs_dir)? {
        let entry = entry?;
        let path = entry.path();
        if !path.is_dir() { continue; }
        let name = match path.file_name().and_then(|s| s.to_str()) {
            Some(n) => n.to_string(),
            None => continue,
        };
        // Skip the system/ marker directory and anything without a memory dir.
        if name == "system" || !path.join("memory").is_dir() {
            continue;
        }
        candidates.push(name);
    }
    candidates.sort();
    candidates.into_iter().next().ok_or_else(|| {
        anyhow::anyhow!("no agents with memory found under {} — run `souveraine init`", memfs_dir.display())
    })
}

async fn run_identity(
    action: &IdentityAction,
    host: bool,
    agent: Option<&str>,
    json: bool,
) -> anyhow::Result<()> {
    use crate::core::identity::SeedId;

    let base = dirs::home_dir()
        .unwrap_or_default()
        .join(".souveraine");

    // Identity defaults to per-agent. --host opts out to the machine seed
    // used for federation transport. Verify is identity-agnostic — it just
    // checks a signature against a pubkey, no seed lookup.
    //
    // Per-agent seeds live at ~/.souveraine/agents/{uuid}/seed/.
    let (seed_dir, scope_label, agent_uuid) = if host {
        (SeedId::default_dir(&base), "Host Seed Identity".to_string(), None)
    } else {
        let uuid = match agent {
            Some(name_or_id) => resolve_agent_id_from_disk(&base, name_or_id)?,
            None => first_agent_id_on_disk(&base)?,
        };
        let dir = base.join("agents").join(&uuid).join("seed");
        (dir, "Per-Agent Seed Identity".to_string(), Some(uuid))
    };

    match action {
        IdentityAction::Show => {
            // load, never generate: showing an identity must not mint one.
            let seed = SeedId::load(&seed_dir)?;
            if json {
                let mut obj = serde_json::json!({
                    "public_key": seed.public_key_hex(),
                    "glyph": seed.glyph(),
                    "seed_dir": seed_dir.display().to_string(),
                });
                if let Some(id) = &agent_uuid {
                    obj["agent_id"] = serde_json::Value::String(id.clone());
                }
                println!("{}", obj);
            } else {
                println!("{scope_label}");
                if let Some(id) = &agent_uuid {
                    println!("  Agent:      {}", id);
                }
                println!("  Glyph:      {}", seed.glyph());
                println!("  Public key: {}", seed.public_key_hex());
                println!("  Location:   {}", seed_dir.display());
            }
        }
        IdentityAction::Generate => {
            if seed_dir.join("private.key").exists() {
                eprintln!("WARNING: A seed identity already exists at {}", seed_dir.display());
                eprintln!("  Generating a new one will replace it. This breaks federation trust.");
                eprintln!("  Use `souveraine identity show` to see the current identity.");
                anyhow::bail!("Refusing to overwrite existing seed identity. Delete {} manually first.", seed_dir.display());
            }
            let seed = SeedId::load_or_generate(&seed_dir)?;
            println!("Generated seed identity: {} (glyph: {})", seed.public_key_hex(), seed.glyph());
        }
        IdentityAction::Sign { message } => {
            // load, never generate: signing with a key that didn't exist a
            // moment ago proves nothing.
            let seed = SeedId::load(&seed_dir)?;
            let sig = seed.sign(message.as_bytes());
            let sig_hex = hex::encode(sig.to_bytes());
            if json {
                println!("{}", serde_json::json!({
                    "message": message,
                    "signature": sig_hex,
                    "public_key": seed.public_key_hex(),
                    "glyph": seed.glyph(),
                }));
            } else {
                println!("Signature: {sig_hex}");
                println!("Public key: {}", seed.public_key_hex());
            }
        }
        IdentityAction::Verify { pubkey, message, signature } => {
            let pubkey_bytes = hex::decode(pubkey)?;
            let sig_bytes = hex::decode(signature)?;
            if pubkey_bytes.len() != 32 || sig_bytes.len() != 64 {
                anyhow::bail!("Invalid key or signature length");
            }
            let mut pk = [0u8; 32];
            pk.copy_from_slice(&pubkey_bytes);
            let sig = ed25519_dalek::Signature::from_slice(&sig_bytes)
                .map_err(|e| anyhow::anyhow!("bad signature: {e}"))?;
            let valid = SeedId::verify_with_pubkey(&pk, message.as_bytes(), &sig);
            if json {
                println!("{}", serde_json::json!({ "valid": valid }));
            } else {
                println!("{}", if valid { "Valid ✓" } else { "Invalid ✗" });
            }
        }
    }
    Ok(())
}

fn run_machine(action: &MachineAction, json: bool) -> anyhow::Result<()> {
    use crate::core::identity::SeedId;
    use crate::machined::protocol::DEFAULT_SEED_DIR;

    let seed_dir = PathBuf::from(DEFAULT_SEED_DIR);

    match action {
        MachineAction::Init { fresh, migrate_from } => {
            if seed_dir.join("private.key").exists() {
                anyhow::bail!(
                    "A machine identity already exists at {}. Refusing to overwrite — \
                     replacing it breaks federation trust and node commissions. \
                     Remove it manually first if you truly mean to.",
                    seed_dir.display()
                );
            }

            // Identity creation is deliberate: the operator states whether
            // this machine is new or carries an existing identity forward.
            let seed = match (fresh, migrate_from) {
                (true, None) => {
                    std::fs::create_dir_all(&seed_dir)?;
                    SeedId::load_or_generate(&seed_dir)?
                }
                (false, Some(source)) => {
                    let source_key = source.join("private.key");
                    if !source_key.exists() {
                        anyhow::bail!(
                            "no seed at {} — nothing to migrate",
                            source_key.display()
                        );
                    }
                    std::fs::create_dir_all(&seed_dir)?;
                    std::fs::copy(&source_key, seed_dir.join("private.key"))?;
                    let source_pub = source.join("public.key");
                    if source_pub.exists() {
                        std::fs::copy(&source_pub, seed_dir.join("public.key"))?;
                    }
                    // load (not load_or_generate): a botched copy must fail
                    // loudly here, not silently mint a different identity.
                    SeedId::load(&seed_dir)?
                }
                _ => anyhow::bail!(
                    "state your intent: --fresh (new identity) or --migrate-from <dir> \
                     (carry an existing one over)"
                ),
            };

            #[cfg(unix)]
            {
                use std::os::unix::fs::PermissionsExt;
                std::fs::set_permissions(
                    seed_dir.join("private.key"),
                    std::fs::Permissions::from_mode(0o600),
                )?;
                std::fs::set_permissions(&seed_dir, std::fs::Permissions::from_mode(0o700))?;
            }

            // The daemon runs as the `souveraine` system user; hand the seed
            // over if we can (init is normally run via sudo). A missing user
            // means the sysusers entry isn't installed yet — say so, don't
            // half-succeed silently.
            let chown = std::process::Command::new("chown")
                .arg("-R")
                .arg("souveraine:souveraine")
                .arg(&seed_dir)
                .status();
            let owned = matches!(chown, Ok(s) if s.success());
            if !owned {
                eprintln!(
                    "WARNING: could not chown {} to souveraine:souveraine — is the \
                     `souveraine` system user installed (sysusers.d) and are you root? \
                     souveraine-machined will refuse to read the seed until ownership \
                     is fixed.",
                    seed_dir.display()
                );
            }

            if json {
                println!("{}", serde_json::json!({
                    "public_key": seed.public_key_hex(),
                    "glyph": seed.glyph(),
                    "seed_dir": seed_dir.display().to_string(),
                    "owned_by_service_user": owned,
                }));
            } else {
                println!("Machine identity provisioned.");
                println!("  Glyph:      {}", seed.glyph());
                println!("  Public key: {}", seed.public_key_hex());
                println!("  Location:   {}", seed_dir.display());
                println!("Next: systemctl enable --now souveraine-machined");
            }
        }
        MachineAction::Status => {
            match crate::machined::client::status() {
                Ok(state) => {
                    if json {
                        println!("{}", state);
                    } else {
                        println!("souveraine-machined: running");
                        if let Some(glyph) = state.get("glyph").and_then(|v| v.as_str()) {
                            println!("  Glyph:      {glyph}");
                        }
                        if let Some(pk) = state.get("public_key").and_then(|v| v.as_str()) {
                            println!("  Public key: {pk}");
                        }
                    }
                }
                Err(e) => {
                    // Daemon down. Report what can be seen from here: a
                    // permission error on the key is the healthy shape (the
                    // seed exists and only the service user reads it).
                    let key = seed_dir.join("private.key");
                    let seed_state = match std::fs::metadata(&key) {
                        Ok(_) => "present",
                        Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => {
                            "present (unreadable from this user — expected)"
                        }
                        Err(_) => "absent",
                    };
                    if json {
                        println!("{}", serde_json::json!({
                            "ok": false,
                            "daemon": "unreachable",
                            "reason": format!("{e:#}"),
                            "seed": seed_state,
                            "seed_dir": seed_dir.display().to_string(),
                        }));
                    } else {
                        println!("souveraine-machined: not reachable ({e:#})");
                        println!("  Seed at {}: {seed_state}", seed_dir.display());
                        if seed_state == "absent" {
                            println!("  Provision: sudo souveraine machine init --fresh");
                            println!("         or: sudo souveraine machine init --migrate-from ~/.souveraine/seed-id");
                        } else {
                            println!("  Start: systemctl enable --now souveraine-machined");
                        }
                    }
                }
            }
        }
    }
    Ok(())
}

async fn run_peers(json: bool) -> anyhow::Result<()> {
    let base = dirs::home_dir()
        .unwrap_or_default()
        .join(".souveraine");
    let known_path = base.join("federation").join("known_peers.json");

    if !known_path.exists() {
        if json {
            println!("[]");
        } else {
            println!("No federated peers known yet.");
            println!("  Configure peers in [federation] in souveraine.toml and start the server.");
        }
        return Ok(());
    }

    let content = tokio::fs::read_to_string(&known_path).await?;
    let peers: Vec<crate::server::device_registry::PeerEntry> = serde_json::from_str(&content)?;

    if json {
        println!("{}", serde_json::to_string_pretty(&peers)?);
    } else if peers.is_empty() {
        println!("No federated peers known yet.");
    } else {
        println!("\n── Federated Peers ──────────────────────");
        for p in &peers {
            let status = if p.alive { "alive" } else { "offline" };
            println!("  {}  {}", p.seed_id.get(..16).unwrap_or(&p.seed_id), status);
            if let Some(label) = &p.label {
                println!("    label: {label}");
            }
            println!("    url:   {}", p.url);
            println!("    seen:  {}", p.last_seen.format("%Y-%m-%d %H:%M:%S UTC"));
            if !p.hosted_agents.is_empty() {
                let short: Vec<&str> = p
                    .hosted_agents
                    .iter()
                    .map(|a| a.get(..12).unwrap_or(a))
                    .collect();
                println!("    hosts: {}", short.join(", "));
            }
        }
        println!();
    }
    Ok(())
}

async fn run_events(action: &EventsAction, json: bool) -> anyhow::Result<()> {
    use crate::core::nervous::event_log;

    let base = dirs::home_dir()
        .unwrap_or_default()
        .join(".souveraine");
    let events_dir = base.join("events");

    match action {
        EventsAction::Tail { count } => {
            let today = chrono::Utc::now().date_naive();
            let mut events = event_log::events_for_date(&events_dir, today)?;
            if events.len() < *count {
                let yesterday = today - chrono::Duration::days(1);
                let mut older = event_log::events_for_date(&events_dir, yesterday)?;
                older.append(&mut events);
                events = older;
            }
            let start = events.len().saturating_sub(*count);
            let tail = &events[start..];

            if json {
                println!("{}", serde_json::to_string_pretty(tail)?);
            } else if tail.is_empty() {
                println!("No events recorded yet.");
            } else {
                for e in tail {
                    println!(
                        "{} [{}] {} → {} (urgency: {:.1})",
                        e.timestamp.format("%H:%M:%S"),
                        e.sensor_name,
                        e.event_type,
                        e.target.as_deref().unwrap_or("-"),
                        e.urgency,
                    );
                }
            }
        }
        EventsAction::Date { date } => {
            let d = chrono::NaiveDate::parse_from_str(date, "%Y-%m-%d")?;
            let events = event_log::events_for_date(&events_dir, d)?;
            if json {
                println!("{}", serde_json::to_string_pretty(&events)?);
            } else if events.is_empty() {
                println!("No events for {date}.");
            } else {
                println!("{} events on {date}:", events.len());
                for e in &events {
                    println!(
                        "  {} [{}] {} → {}",
                        e.timestamp.format("%H:%M:%S"),
                        e.sensor_name,
                        e.event_type,
                        e.target.as_deref().unwrap_or("-"),
                    );
                }
            }
        }
        EventsAction::Purge { retain_days } => {
            let days = retain_days.unwrap_or(30);
            let removed = event_log::purge_old_events(&events_dir, days)?;
            println!("Purged {removed} event log files older than {days} days.");
        }
    }
    Ok(())
}

async fn run_schedule(action: &ScheduleAction, agent: Option<&str>, json: bool) -> anyhow::Result<()> {
    use crate::core::nervous::cron::parse_schedule_file;

    let souveraine_base = dirs::home_dir()
        .unwrap_or_default()
        .join(".souveraine");
    let agent_id = match agent {
        Some(name_or_id) => resolve_agent_id_from_disk(&souveraine_base, name_or_id)?,
        None => first_agent_id_on_disk(&souveraine_base)?,
    };
    let base = souveraine_base
        .join("agents")
        .join(&agent_id)
        .join("memory")
        .join("schedules");

    if !base.exists() {
        std::fs::create_dir_all(&base)?;
    }

    match action {
        ScheduleAction::List => {
            let mut found = false;
            if let Ok(entries) = std::fs::read_dir(&base) {
                for entry in entries.flatten() {
                    let path = entry.path();
                    if path.extension().map(|e| e == "md").unwrap_or(false) {
                        if let Ok(e) = parse_schedule_file(&path) {
                            found = true;
                            if json {
                                println!("{}", serde_json::to_string(&e).unwrap_or_default());
                            } else {
                                println!(
                                    "  {} ({:?}, {}, {})",
                                    e.name,
                                    e.kind,
                                    e.schedule,
                                    if e.enabled { "enabled" } else { "disabled" }
                                );
                            }
                        }
                    }
                }
            }
            if !found && !json {
                println!("No schedules found.");
            }
        }

        ScheduleAction::Status => {
            let state_path = base.join(".state.json");
            if state_path.exists() {
                let data = std::fs::read_to_string(&state_path)?;
                println!("{data}");
            } else if json {
                println!("{{}}");
            } else {
                println!("No schedule state yet.");
            }
        }

        ScheduleAction::Create { name, cron, interval, prompt } => {
            let file_path = base.join(format!("{name}.md"));
            if file_path.exists() {
                anyhow::bail!("schedule '{name}' already exists");
            }

            let (kind, sched) = if let Some(c) = cron {
                ("cron", c.clone())
            } else if let Some(i) = interval {
                ("interval", i.to_string())
            } else {
                ("interval", "3600".to_string())
            };

            let content = format!(
                "---\nname: {name}\nkind: {kind}\nschedule: \"{sched}\"\nsource: user\nenabled: true\nurgency: 0.3\ncreated_at: {}\n---\n\n{prompt}\n",
                chrono::Utc::now().to_rfc3339()
            );
            std::fs::write(&file_path, content)?;

            if json {
                println!(r#"{{"status":"created","name":"{name}"}}"#);
            } else {
                println!("Schedule '{name}' created.");
            }
        }

        ScheduleAction::Delete { name } => {
            let file_path = base.join(format!("{name}.md"));
            if !file_path.exists() {
                anyhow::bail!("schedule '{name}' not found");
            }
            std::fs::remove_file(&file_path)?;
            if json {
                println!(r#"{{"status":"deleted","name":"{name}"}}"#);
            } else {
                println!("Schedule '{name}' deleted.");
            }
        }

        ScheduleAction::Run { name } => {
            let file_path = base.join(format!("{name}.md"));
            if !file_path.exists() {
                anyhow::bail!("schedule '{name}' not found");
            }
            let trigger = base.join(format!(".trigger-{name}"));
            std::fs::write(&trigger, "")?;
            if json {
                println!(r#"{{"status":"triggered","name":"{name}"}}"#);
            } else {
                println!("Schedule '{name}' triggered — will fire on next tick.");
            }
        }
    }
    Ok(())
}

async fn run_tui(
    config: Arc<RwLock<ConsciousnessConfig>>,
    agent_pref: Option<String>,
    engine: String,
    local: bool,
) -> anyhow::Result<()> {
    if engine == "tuie" {
        return run_tuie_tui(config, agent_pref, local).await;
    }
    // Default: ratatui
    let config_path = ConsciousnessConfig::discover_path();
    let mut app = App::new(config, agent_pref.unwrap_or_default(), config_path);
    app.run().await?;
    Ok(())
}

/// Run the experimental tuie-based TUI.
async fn run_tuie_tui(
    config: Arc<RwLock<ConsciousnessConfig>>,
    _agent_pref: Option<String>,
    _local: bool,
) -> anyhow::Result<()> {
    use crate::ui::presence::Presence;
    use crate::ui::chat::ChatPalette;
    use crate::ui::atmosphere::Atmosphere;

    // Register the tokio spawner with tuie
    crate::ui::tuie_app::TuieApp::setup_spawner();

    let palette = ChatPalette::from_atmosphere(Atmosphere::Default);
    let presence = Presence::new("souveraine");

    let mut app = crate::ui::tuie_app::TuieApp::new(
        config,
        presence,
        palette,
        "Souveraine".into(),
        "Human".into(),
    );

    // Kick off async dashboard loading and splash→welcome polling.
    // The splash screen shows immediately; after ~6-8 seconds (or a key
    // press), it auto-transitions to the welcome screen with live data.
    app.begin();

    // Apply default atmosphere
    crate::ui::theme::apply_atmosphere(Atmosphere::Default);

    // tuie::start_tui takes ownership and runs the event loop
    tuie::start_tui(app).map_err(|e| anyhow::anyhow!(e))?;
    Ok(())
}

/// Resolve the backend per `--local` and remote health.
///
/// - `--local` forces in-process LocalBackend (no network attempt).
/// - Otherwise: probe the remote URL; on success use Remote, on failure
///   transparently fall back to Local. The constitution's sovereignty principle
///   (Article VI.1) says the harness must keep working when the server is gone.
async fn resolve_backend(
    config: &Arc<RwLock<ConsciousnessConfig>>,
    force_local: bool,
    json: bool,
    quiet: bool,
) -> anyhow::Result<(Box<dyn crate::backend::Backend>, &'static str)> {
    use crate::backend::{Backend, LocalBackend, RemoteBackend};

    if force_local {
        let cfg = config.read().await.clone();
        let local = LocalBackend::new(cfg).await?;
        if !json && !quiet {
            eprintln!("  (local mode — engine in-process)");
        }
        return Ok((Box::new(local), "local"));
    }

    let server_url = config.read().await.server.effective_url();
    let remote = RemoteBackend::new(&server_url)?;
    if remote.health().await {
        return Ok((Box::new(remote), "remote"));
    }

    if !json && !quiet {
        eprintln!("  (no server at {} — falling back to local)", server_url);
    }
    let cfg = config.read().await.clone();
    let local = LocalBackend::new(cfg).await?;
    Ok((Box::new(local), "local"))
}

async fn run_chat(
    config: Arc<RwLock<ConsciousnessConfig>>,
    agent_name: Option<String>,
    message: Option<String>,
    json: bool,
    quiet: bool,
    force_local: bool,
) -> anyhow::Result<()> {
    use crate::backend::BackendEvent;
    use futures::StreamExt;

    let (backend, mode) = resolve_backend(&config, force_local, json, quiet).await?;

    // Resolve the agent: if `--agent` matches a name or id, use it; else first available.
    let agents = backend.list_agents().await?;
    let resolved = match &agent_name {
        Some(n) => agents.iter().find(|a| a.name == *n || a.id == *n).or_else(|| agents.first()),
        None => agents.first(),
    };
    let agent = match resolved {
        Some(a) => a.clone(),
        None => {
            let hint = if mode == "local" {
                "No agents in local store. Create one via POST /v1/agents (server mode) or souveraine init."
            } else {
                "No agents on the server. Create one via POST /v1/agents."
            };
            if json { println!(r#"{{"status":"no-agents"}}"#); } else { eprintln!("{}", hint); }
            return Ok(());
        }
    };

    let conv_id = backend.ensure_conversation(&agent.id).await?;

    // Surface anything the subconscious noted during autonomous (heartbeat)
    // cycles since the last session. Read-once — this clears the stash.
    let pending = backend.take_pending_surfacings(&agent.id).await;
    if !pending.is_empty() && !json {
        eprintln!(
            "\n  {} observation{} surfaced while you were away:",
            pending.len(),
            if pending.len() == 1 { "" } else { "s" },
        );
        for p in &pending {
            let tag = if p.source.is_empty() { p.kind.as_str() } else { p.source.as_str() };
            eprintln!("     [{}] {}", tag, p.content);
        }
        eprintln!();
    }

    let one_shot = message.clone();
    if let Some(msg) = one_shot {
        let mut stream = backend.send(&conv_id, &msg).await?;
        if !json { println!("\n  {}: {}\n", agent.name, msg); print!("  "); }
        let mut full = String::new();
        while let Some(ev) = stream.next().await {
            match ev? {
                BackendEvent::Token(t) => {
                    if json { /* collect */ } else {
                        use std::io::Write;
                        print!("{}", t);
                        std::io::stdout().flush().ok();
                    }
                    full.push_str(&t);
                }
                BackendEvent::Reasoning(r) => {
                    if !json { eprintln!("\n  [thinking] {}", r); }
                }
                BackendEvent::Surfacing { source, content, .. } => {
                    if !json { eprintln!("\n  [{}] {}", source, content); }
                }
                BackendEvent::Error { message } => {
                    if json {
                        let out = serde_json::json!({"agent": agent.name, "error": message});
                        println!("{}", serde_json::to_string_pretty(&out)?);
                        return Ok(());
                    }
                    eprintln!("\n  [turn error] {}", message);
                }
                BackendEvent::Done => break,
                _ => {}
            }
        }
        if json {
            let out = serde_json::json!({"agent": agent.name, "response": full});
            println!("{}", serde_json::to_string_pretty(&out)?);
        } else {
            println!();
        }
        return Ok(());
    }

    // Interactive REPL
    println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("  Souveraine — {} ({} mode)", agent.name, mode);
    println!("  /exit to leave");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
    let mut rl = rustyline::DefaultEditor::new().ok();
    loop {
        let prompt = format!("{}> ", agent.name);
        let line = match rl.as_mut() {
            Some(r) => r.readline(&prompt).ok(),
            None => None,
        };
        let Some(input) = line else { break };
        let input = input.trim().to_string();
        if input.is_empty() { continue; }
        if matches!(input.as_str(), "/exit" | "/quit" | "/q") { break; }
        if let Some(r) = rl.as_mut() { r.add_history_entry(&input).ok(); }

        let mut stream = backend.send(&conv_id, &input).await?;
        print!("\n  {}: ", agent.name);
        use std::io::Write;
        std::io::stdout().flush().ok();
        while let Some(ev) = stream.next().await {
            match ev? {
                BackendEvent::Token(t) => { print!("{}", t); std::io::stdout().flush().ok(); }
                BackendEvent::Reasoning(r) => eprintln!("\n  [thinking] {}", r),
                BackendEvent::Surfacing { source, content, .. } => eprintln!("\n  [{}] {}", source, content),
                BackendEvent::Error { message } => eprintln!("\n  [turn error] {}", message),
                BackendEvent::Done => break,
                _ => {}
            }
        }
        println!("\n");
    }
    println!("bye");
    Ok(())
}

async fn run_reflect(
    config: Arc<RwLock<ConsciousnessConfig>>,
    agent_name: Option<String>,
    conversation: Option<String>,
    json: bool,
) -> anyhow::Result<()> {
    use crate::backend::LocalBackend;
    let cfg = config.read().await.clone();
    let local = LocalBackend::new(cfg).await?;
    let server = local.server();

    let summaries = server.agents.list(None).await?;
    let agent = match &agent_name {
        Some(n) => summaries.iter().find(|a| a.name == *n || a.id == *n).or_else(|| summaries.first()),
        None => summaries.first(),
    }
    .ok_or_else(|| anyhow::anyhow!("no agents configured"))?;

    // Resolve a conversation. Explicit --conversation wins; otherwise
    // pick the most recent active conversation for this agent.
    let store = crate::core::conversation::ConversationStore::new(
        &dirs::home_dir()
            .unwrap_or_default()
            .join(".souveraine")
            .join("agents")
            .join(&agent.id),
    );
    let conv_id = match conversation {
        Some(id) => id,
        None => {
            let mut records = store.list_active().await?;
            records.sort_by_key(|r| std::cmp::Reverse(r.updated_at));
            records
                .into_iter()
                .next()
                .map(|r| r.id)
                .ok_or_else(|| {
                    anyhow::anyhow!("no conversations found for agent '{}'", agent.id)
                })?
        }
    };

    let messages = store.load_messages(&conv_id).await?;
    if messages.is_empty() {
        anyhow::bail!("conversation '{conv_id}' has no messages to reflect on");
    }

    if !json {
        println!(
            "Reflecting on conversation {} ({} messages) for agent {}…",
            &conv_id[..8.min(conv_id.len())],
            messages.len(),
            agent.name,
        );
    }

    let report = server
        .consciousness
        .reflection()
        .reflect_now(&agent.id, &messages)
        .await?;

    if json {
        println!("{}", serde_json::to_string_pretty(&report)?);
    } else {
        println!();
        println!("─── Reflection complete ─────────────────────────────");
        println!("  agent:           {}", report.agent_id);
        println!("  turns reviewed:  {}", report.turns_reviewed);
        println!(
            "  duration:        {:.1}s",
            (report.completed_at - report.started_at)
                .num_milliseconds() as f64 / 1000.0
        );
        println!(
            "  exited cleanly:  {}",
            if report.exited_cleanly { "yes" } else { "no (tool rounds exhausted)" }
        );
        println!("─────────────────────────────────────────────────────");
        println!();
        println!("{}", report.summary);
    }

    Ok(())
}

async fn run_agents(
    config: Arc<RwLock<ConsciousnessConfig>>,
    json: bool,
    force_local: bool,
) -> anyhow::Result<()> {
    let (backend, mode) = resolve_backend(&config, force_local, json, false).await?;
    let agents = backend.list_agents().await?;
    if json {
        let payload: Vec<_> = agents.iter().map(|a| serde_json::json!({
            "id": a.id, "name": a.name, "description": a.description,
        })).collect();
        println!("{}", serde_json::to_string_pretty(&serde_json::json!({"agents": payload}))?);
    } else {
        println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        println!("  Agents ({} mode)", mode);
        println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        if agents.is_empty() {
            println!("  (none — POST /v1/agents to create one)");
        }
        for a in &agents {
            println!("  {}  {}", a.id, a.name);
            if let Some(d) = &a.description { println!("    {}", d); }
        }
        println!();
    }
    Ok(())
}


async fn run_status(config: Arc<RwLock<ConsciousnessConfig>>, json: bool) -> anyhow::Result<()> {
    let cfg = config.read().await;

    if json {
        let output = serde_json::json!({
            "bifrost_url": cfg.bifrost.base_url,
            "primary_model": cfg.bifrost.primary_model,
            "memory": {
                "git_enabled": cfg.memory.git_enabled,
                "auto_commit": cfg.memory.auto_commit,
            },
            "subconscious": {
                "n1_enabled": cfg.subconscious.n1_enabled,
                "inbox_enabled": cfg.subconscious.inbox_enabled,
            },
            "reflection": {
                "enabled": cfg.reflection.enabled,
                "message_interval": cfg.reflection.message_interval,
                "trigger": format!("{:?}", cfg.reflection.trigger),
            },
            "archivist": {
                "enabled": cfg.archivist.enabled,
                "interval": cfg.archivist.interval,
                "threshold": cfg.archivist.threshold,
                "compression_model": cfg.archivist.compression_model,
            },
            "models_configured": cfg.models.len(),
        });
        println!("{}", serde_json::to_string_pretty(&output)?);
    } else {
        println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        println!("  Souveraine — World State");
        println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        println!("  Bifrost:       {}", cfg.bifrost.base_url);
        println!("  Voice:         {}", cfg.bifrost.primary_model);
        println!("  Memory:        git={}, auto_commit={}", cfg.memory.git_enabled, cfg.memory.auto_commit);
        println!("  N+1:           enabled={}, inbox={}", cfg.subconscious.n1_enabled, cfg.subconscious.inbox_enabled);
        println!("  Reflection:    enabled={}, every {} messages", cfg.reflection.enabled, cfg.reflection.message_interval);
        println!("  Archivist:     enabled={}, every {} msgs at {} pressure",
            cfg.archivist.enabled, cfg.archivist.interval, cfg.archivist.threshold);
        println!("  Compresses via: {}", cfg.archivist.compression_model);
        println!("  Models known:  {}", cfg.models.len());
        println!();
    }
    Ok(())
}

async fn run_listen(
    bind_override: Option<String>,
    port_override: Option<u16>,
    config: Arc<RwLock<ConsciousnessConfig>>,
) -> anyhow::Result<()> {
    let cfg = config.read().await.clone();
    let bind = bind_override.unwrap_or_else(|| cfg.server.bind.clone());
    let port = port_override.unwrap_or(cfg.server.port);
    let base = dirs::home_dir().unwrap_or_default().join(".souveraine");

    let listener = server::listener::LiteListener::new(&cfg.federation, base)?;
    let app = server::listener::create_routes_lite(listener);

    let addr = format!("{bind}:{port}");
    println!("Souveraine lite listener on http://{addr}");
    info!("lite listener starting on {addr}");
    let tcp = tokio::net::TcpListener::bind(&addr).await?;
    axum::serve(tcp, app.into_make_service()).await?;
    Ok(())
}

async fn run_server(
    bind_override: Option<String>,
    port_override: Option<u16>,
    config: Arc<RwLock<ConsciousnessConfig>>,
) -> anyhow::Result<()> {
    println!("Starting Souveraine server...");

    let cfg = config.read().await.clone();
    let bind = bind_override.unwrap_or_else(|| cfg.server.bind.clone());
    let port = port_override.unwrap_or(cfg.server.port);

    let server = server::SouveraineServer::new(cfg).await?;

    {
        let mut server_config = server.config.write().await;
        server_config.bind = bind;
        server_config.port = port;
    }

    server.run().await
}

// ─── Config loading ─────────────────────────────────────────────────────────

async fn load_config() -> anyhow::Result<ConsciousnessConfig> {
    let config_paths = [
        PathBuf::from("souveraine.toml"),
        PathBuf::from("souveraine.yaml"),
        PathBuf::from("~/.souveraine/config.toml"),
        PathBuf::from("~/.souveraine/config.yaml"),
    ];

    for path in &config_paths {
        let path_str = path.to_string_lossy();
        let expanded = shellexpand::tilde(&path_str);
        let path = PathBuf::from(expanded.as_ref());

        if path.exists() {
            info!("loading config from {:?}", path);
            let mut config = ConsciousnessConfig::load(&path)?;
            config.normalize_providers();
            return Ok(config);
        }
    }

    warn!("no config found; using defaults (will probe Bifrost for models)");
    let config = ConsciousnessConfig::default();

    // Try to seed model list from the active inference provider
    info!("discovering models from provider");
    let provider = match bridge::build_provider(&config) {
        Ok(p) => p,
        Err(e) => {
            warn!("could not build provider: {}", e);
            return Ok(config);
        }
    };
    match provider.list_models().await {
        Ok(models) => {
            info!("{} models available via Bifrost", models.len());
            for m in models {
                debug!("  {}", m);
            }
        }
        Err(e) => warn!("could not reach Bifrost: {}", e),
    }

    Ok(config)
}
