//! Claude Code runtime — launches Claude Code CLI sessions as agent backends.
use std::sync::{Arc, Mutex};

use tokio::sync::broadcast;

use crate::agent_log::AgentLogWriter;
use crate::io::watcher::WatcherEvent;
use crate::slog_warn;

use super::{AgentEvent, AgentRuntime, RuntimeContext, RuntimeResult, RuntimeStatus};

/// Agent runtime that spawns the `claude` CLI in a PTY and streams JSON events.
///
/// This is the default runtime (`runtime = "claude-code"` in project.toml).
/// It wraps the existing PTY-based execution logic, preserving all streaming,
/// token tracking, and inactivity timeout behaviour.
pub struct ClaudeCodeRuntime {
    watcher_tx: broadcast::Sender<WatcherEvent>,
}

impl ClaudeCodeRuntime {
    /// Create a new Claude Code runtime with a shared event channel.
    pub fn new(watcher_tx: broadcast::Sender<WatcherEvent>) -> Self {
        Self { watcher_tx }
    }
}

impl AgentRuntime for ClaudeCodeRuntime {
    async fn start(
        &self,
        ctx: RuntimeContext,
        tx: broadcast::Sender<AgentEvent>,
        event_log: Arc<Mutex<Vec<AgentEvent>>>,
        log_writer: Option<Arc<Mutex<AgentLogWriter>>>,
    ) -> Result<RuntimeResult, String> {
        let eager_record = ctx
            .model
            .as_ref()
            .map(|m| (ctx.project_root.clone(), m.as_str().to_string()));
        let pty_result = super::super::pty::run_agent_pty_streaming(
            &ctx.story_id,
            &ctx.agent_name,
            &ctx.command,
            &ctx.args,
            &ctx.prompt,
            &ctx.cwd,
            &tx,
            &event_log,
            log_writer.clone(),
            ctx.inactivity_timeout_secs,
            self.watcher_tx.clone(),
            ctx.session_id_to_resume.as_deref(),
            eager_record.clone(),
        )
        .await;

        match pty_result {
            Ok(result) => Ok(RuntimeResult {
                // Abort+no-session: CLI crashed (e.g. SIGABRT) before emitting its
                // first "system" event.  Detected by: non-zero exit AND no session.
                aborted_signal: !result.exit_ok && result.session_id.is_none(),
                exit_ok: result.exit_ok,
                session_id: result.session_id,
                token_usage: result.token_usage,
                rate_limit_exit: result.rate_limit_exit,
                rate_limit_reset_at: result.rate_limit_reset_at,
            }),
            Err(e) if ctx.session_id_to_resume.is_some() && ctx.fresh_prompt.is_some() => {
                // Resume failed — fall back to a fresh session without --resume.
                slog_warn!(
                    "[agents] Resume failed for {}:{}, retrying without --resume: {}",
                    ctx.story_id,
                    ctx.agent_name,
                    e
                );
                let fresh = ctx.fresh_prompt.unwrap();
                let fallback_result = super::super::pty::run_agent_pty_streaming(
                    &ctx.story_id,
                    &ctx.agent_name,
                    &ctx.command,
                    &ctx.args,
                    &fresh,
                    &ctx.cwd,
                    &tx,
                    &event_log,
                    log_writer,
                    ctx.inactivity_timeout_secs,
                    self.watcher_tx.clone(),
                    None, // no --resume on fallback
                    eager_record,
                )
                .await?;
                Ok(RuntimeResult {
                    aborted_signal: !fallback_result.exit_ok
                        && fallback_result.session_id.is_none(),
                    exit_ok: fallback_result.exit_ok,
                    session_id: fallback_result.session_id,
                    token_usage: fallback_result.token_usage,
                    rate_limit_exit: fallback_result.rate_limit_exit,
                    rate_limit_reset_at: fallback_result.rate_limit_reset_at,
                })
            }
            Err(e) => Err(e),
        }
    }

    fn stop(&self) {
        // Stopping is handled externally by the pool via kill_child_for_key().
    }

    fn get_status(&self) -> RuntimeStatus {
        // Lifecycle status is tracked by the pool; the runtime itself is stateless.
        RuntimeStatus::Idle
    }
}
