//! drive — design-as-stubbed Anthropic-API driver for `kei-spawn drive`.
//!
//! The `drive` subcommand is the future one-call replacement for the current
//! two-step dance (`kei-spawn spawn` → orchestrator pastes Agent invocation).
//! Wiring it to a live Anthropic HTTP endpoint is a breaking change (adds
//! `reqwest` + tokio + a secrets contract), so v0.1 ships a stub: the
//! pipeline, types, and trait are defined; the HTTP impl returns
//! `NotImplemented` via `ManualDriver`.
//!
//! Exit-code contract (mirrors `kei-runtime::InvokeError::NotImplemented`):
//!   - 64 (EX_USAGE range) when the driver yields `NotImplemented`
//!   - 1 on spawn failure (same as `kei-spawn spawn`)
//!   - 0 only when a real driver returns Ok (HttpDriver future path)
//!
//! Constructor Pattern: one trait + two zero-state impls + one helper fn.

use serde::Serialize;

/// Success envelope for a future `HttpDriver` (and the contract
/// `ManualDriver` deliberately never fulfils).
#[derive(Debug, Clone, Serialize)]
pub struct AgentResult {
    pub agent_id: String,
    pub transcript: String,
    pub finish_reason: String,
}

/// Errors surfaced from driver invocation. `NotImplemented` is retained as
/// the v0.1 escape hatch; `Transport` is reserved for the HTTP impl.
#[derive(Debug)]
pub enum DriveError {
    NotImplemented { reason: String },
    Transport { message: String },
}

impl std::fmt::Display for DriveError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::NotImplemented { reason } => {
                write!(f, "kei-spawn drive: {reason}")
            }
            Self::Transport { message } => {
                write!(f, "kei-spawn drive transport: {message}")
            }
        }
    }
}

impl std::error::Error for DriveError {}

/// Abstraction over "how does an agent invocation actually happen."
///
/// v0.1 has one impl: `ManualDriver` (prints instructions, returns
/// `NotImplemented`). Future: `HttpDriver` backed by `reqwest` +
/// `KEI_ANTHROPIC_KEY` + POST `https://api.anthropic.com/v1/messages`.
pub trait AnthropicDriver {
    fn invoke(
        &self,
        prompt: &str,
        subagent_type: &str,
        isolation: Option<&str>,
    ) -> Result<AgentResult, DriveError>;
}

/// v0.1 driver — returns `NotImplemented` unconditionally.
///
/// Intentional: lets `kei-spawn drive` ship a complete CLI surface
/// (help, argument parsing, JSON emission) before the HTTP dep is taken.
pub struct ManualDriver;

impl AnthropicDriver for ManualDriver {
    fn invoke(
        &self,
        _prompt: &str,
        _subagent_type: &str,
        _isolation: Option<&str>,
    ) -> Result<AgentResult, DriveError> {
        Err(DriveError::NotImplemented {
            reason: not_implemented_message(),
        })
    }
}

/// Placeholder for the future HTTP-backed driver.
///
/// Deliberately kept dep-free: adding `reqwest` + tokio here would force a
/// breaking change on every consumer of `kei-spawn` today. When the HTTP
/// impl lands, this struct gains fields (`api_key`, `endpoint`, `client`)
/// and the `invoke` body is replaced.
pub struct HttpDriver;

impl AnthropicDriver for HttpDriver {
    fn invoke(
        &self,
        _prompt: &str,
        _subagent_type: &str,
        _isolation: Option<&str>,
    ) -> Result<AgentResult, DriveError> {
        Err(DriveError::NotImplemented {
            reason: "HttpDriver not wired in v0.1 — add reqwest + tokio in a dedicated PR"
                .to_string(),
        })
    }
}

/// Canonical stderr message for the v0.1 stub. Kept as a fn so both the
/// driver impl and the CLI layer emit the exact same string (and so tests
/// can assert on one fixture).
pub fn not_implemented_message() -> String {
    "HTTP Anthropic-API integration not yet wired; use spawn then manual \
     Agent-tool invocation (see printed instructions)"
        .to_string()
}

/// Drive helper — orchestrator-facing entry that dispatches to a driver.
///
/// Kept thin on purpose: the real work (prepare + ledger fork) happens in
/// `spawn_from_task`. `drive` only layers the driver call on top.
pub fn drive_with<D: AnthropicDriver>(
    driver: &D,
    prompt: &str,
    subagent_type: &str,
    isolation: Option<&str>,
) -> Result<AgentResult, DriveError> {
    driver.invoke(prompt, subagent_type, isolation)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn manual_driver_returns_not_implemented() {
        let d = ManualDriver;
        let err = d.invoke("p", "code-implementer", Some("worktree")).unwrap_err();
        match err {
            DriveError::NotImplemented { reason } => {
                assert!(reason.contains("HTTP"), "reason: {reason}");
            }
            other => panic!("expected NotImplemented, got {other}"),
        }
    }

    #[test]
    fn http_driver_also_not_implemented_in_v01() {
        let d = HttpDriver;
        assert!(matches!(
            d.invoke("p", "x", None),
            Err(DriveError::NotImplemented { .. })
        ));
    }
}
