//! hagent-acp — an Agent Client Protocol (stdio) frontend for Hadrian's
//! `/v1/responses` API.
//!
//! This is `hagent`'s server-side agent exposed over ACP instead of the Ratatui
//! TUI: it reuses `hagent-core` (the HTTP/SSE client, the `Conversation` state
//! machine, MCP OAuth, skill bootstrap, config, logging) unchanged and only
//! translates between the `StreamEvent` wire protocol and ACP `SessionUpdate`s.
//!
//! **stdout is the JSON-RPC channel** — all logging goes to stderr (see
//! [`init_tracing`]). Never `println!`/`print!` from this crate.

mod agent;
mod oauth;
mod permission;
mod session;
mod translate;

use std::path::PathBuf;
use std::sync::Arc;

use anyhow::{Context, Result};
use clap::Parser;
use hagent_core::config::{FileConfig, Overrides, default_config_path, resolve};

use crate::agent::AgentState;

#[derive(Parser, Debug)]
#[command(name = "hagent-acp", version, about)]
struct Cli {
    /// Hadrian base URL (default http://localhost:8080/api).
    #[arg(long, env = "HAGENT_BASE_URL")]
    base_url: Option<String>,
    /// API key (gw_...). Prefer the HAGENT_API_KEY env var.
    #[arg(long, env = "HAGENT_API_KEY", hide_env_values = true)]
    api_key: Option<String>,
    /// Model to route to.
    #[arg(long, env = "HAGENT_MODEL")]
    model: Option<String>,
    /// Path to config.toml (default ~/.config/hagent/config.toml).
    #[arg(long)]
    config: Option<PathBuf>,
    /// Server-side tools to declare, comma-separated: shell, web_search,
    /// file_search, mcp. Overrides the config file. Without this (and no config
    /// entry) all tools are enabled; use `--tools none` to declare none.
    #[arg(long, value_delimiter = ',')]
    tools: Option<Vec<String>>,
    /// Reasoning effort: minimal, low, medium, high, xhigh, max — or off.
    #[arg(long, env = "HAGENT_REASONING_EFFORT")]
    reasoning_effort: Option<String>,
    /// Reasoning summary verbosity: auto, concise, detailed — or off.
    #[arg(long, env = "HAGENT_REASONING_SUMMARY")]
    reasoning_summary: Option<String>,
    /// Disable per-conversation logging (it's on by default).
    #[arg(long = "no-log-conversations", conflicts_with = "log_conversations")]
    no_log_conversations: bool,
    /// Force per-conversation logging on (overrides a config that disabled it).
    #[arg(long)]
    log_conversations: bool,
    /// Base directory for conversation logs (default: cache dir /conversations).
    #[arg(long, env = "HAGENT_LOG_DIR")]
    log_dir: Option<PathBuf>,
    /// Write the conversation log WITHOUT redacting secrets — for debugging auth.
    #[arg(long, env = "HAGENT_DEBUG")]
    debug: bool,
    /// Never upload the file-editing skill on `session/new` (it's offered by
    /// default when the shell tool is on and no skill is cached for this server).
    #[arg(long = "no-bootstrap")]
    no_bootstrap: bool,
    /// Present the model picker as a flat (ungrouped) list instead of grouping
    /// by provider. Use this for clients that don't render grouped select
    /// options (e.g. JetBrains). Grouped is the default.
    #[arg(long = "flat-model-list", env = "HAGENT_FLAT_MODEL_LIST")]
    flat_model_list: bool,
}

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

    let (settings, config_path) = build_settings(&cli).context("resolving settings")?;
    tracing::info!(base_url = %settings.base_url, model = %settings.model, "hagent-acp starting");

    let state = Arc::new(AgentState::new(
        settings,
        config_path,
        !cli.no_bootstrap,
        !cli.flat_model_list,
    ));

    // Drive the ACP connection over stdio until the client disconnects.
    agent::run(state)
        .await
        .map_err(|e| anyhow::anyhow!(e.to_string()))
        .context("ACP connection")
}

/// Route tracing to **stderr** — stdout carries the ACP JSON-RPC stream and any
/// stray byte there corrupts it. Level via `HAGENT_LOG` (default `info`).
fn init_tracing() {
    use tracing_subscriber::EnvFilter;
    let _ = tracing_subscriber::fmt()
        .with_writer(std::io::stderr)
        .with_ansi(false)
        .with_env_filter(
            EnvFilter::try_from_env("HAGENT_LOG").unwrap_or_else(|_| EnvFilter::new("info")),
        )
        .try_init();
}

/// Resolve the `Settings` template the same way `hagent` does: layer the config
/// file (plus any `./.mcp.json` servers) under the CLI/env flags. Returns the
/// resolved settings and the config path (for skill-id/extension persistence).
fn build_settings(cli: &Cli) -> Result<(hagent_core::Settings, Option<PathBuf>)> {
    let config_path = cli.config.clone().or_else(default_config_path);
    let file = match &config_path {
        Some(p) => FileConfig::load(p).context("loading config file")?,
        None => None,
    };

    // Merge ./.mcp.json (Claude-Code project scope) with config [[mcp_servers]];
    // config.toml wins on a label conflict — same as hagent.
    let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    let mcp_json = hagent_core::mcp::load_mcp_json(&cwd).context("loading .mcp.json")?;
    let file = if mcp_json.is_empty() {
        file
    } else {
        let mut f = file.unwrap_or_default();
        f.mcp_servers = hagent_core::mcp::merge_servers(mcp_json, f.mcp_servers);
        Some(f)
    };

    let log_conversations = if cli.no_log_conversations {
        Some(false)
    } else if cli.log_conversations {
        Some(true)
    } else {
        None
    };
    // CLI flags carry env values (via clap `env`), so they are the highest layer.
    let flags = Overrides {
        base_url: cli.base_url.clone(),
        api_key: cli.api_key.clone(),
        model: cli.model.clone(),
        tools: cli.tools.clone(),
        reasoning_effort: cli.reasoning_effort.clone(),
        reasoning_summary: cli.reasoning_summary.clone(),
        log_conversations,
        log_dir: cli
            .log_dir
            .clone()
            .map(|p| p.to_string_lossy().into_owned()),
        debug: cli.debug.then_some(true),
    };
    let settings = resolve(file, Overrides::default(), flags)?;
    Ok((settings, config_path))
}
