//! Shared recording of a single tool-call outcome.
//!
//! The persist-then-emit tail of a tool call (`ExecutionOutcome` → DB row +
//! `ToolDone`/`ToolError`/`ToolCancelled` event) was copy-pasted into both the live
//! loop (`run_agent_turn`) and `resume_pending_tools`. `record_tool_outcome` is the
//! single implementation both call.

use serde_json::Value;
use tracing::{debug, info, warn};

use crate::chat_event_bus::ToolCallEvent;
use crate::db::chat_llm_tools;
use crate::tools::{is_file_write_tool, ExecutionOutcome};

use super::ChatSessionHandler;
use super::emitter::TurnEmitter;

/// Whether the enclosing loop should keep going after an outcome is recorded.
pub(super) enum RecordFlow {
    /// Continue with the next tool call / round.
    Continue,
    /// The tool was cancelled by the user — the caller must end the turn.
    Abort,
}

impl ChatSessionHandler {
    /// Persists one tool-call outcome and emits the matching lifecycle event.
    /// Returns [`RecordFlow::Abort`] for a user cancellation (the caller ends the
    /// turn), [`RecordFlow::Continue`] otherwise.
    ///
    /// When `accumulate` is `Some` (the live turn), the call is also appended to the
    /// turn's `ToolCallEvent` list for the chat-event bus, and a `FileChanged` event
    /// is emitted for a successful file-write tool. `resume_pending_tools` passes
    /// `None`: it neither accumulates nor re-emits `FileChanged`.
    pub(super) async fn record_tool_outcome(
        &self,
        tool_call_id: i64,
        tool_name:    &str,
        args:         &Value,
        outcome:      ExecutionOutcome,
        em:           &TurnEmitter<'_>,
        accumulate:   Option<&mut Vec<ToolCallEvent>>,
    ) -> anyhow::Result<RecordFlow> {
        let pool = &self.db;
        match outcome {
            ExecutionOutcome::Completed(result) => {
                let wire = result.to_wire();
                let kind = result.kind();
                debug!(session_id = self.session_id, tool = %tool_name, tool_call_id, result_len = wire.len(), "tool done");
                chat_llm_tools::complete(pool, tool_call_id, &wire, kind).await?;
                if let Some(acc) = accumulate {
                    if is_file_write_tool(tool_name)
                        && let Some(p) = args["path"].as_str()
                    {
                        em.file_changed(crate::approval::normalize_path(p)).await;
                    }
                    acc.push(ToolCallEvent {
                        name:      tool_name.to_string(),
                        arguments: Some(serde_json::to_string(args).unwrap_or_default()),
                        result:    Some(wire.clone()),
                        status:    "done".to_string(),
                    });
                }
                em.tool_done(tool_call_id, wire, kind.to_string()).await;
                Ok(RecordFlow::Continue)
            }
            ExecutionOutcome::Failed(msg) => {
                warn!(session_id = self.session_id, tool = %tool_name, tool_call_id, error = %msg, "tool failed");
                chat_llm_tools::fail(pool, tool_call_id, &msg).await?;
                if let Some(acc) = accumulate {
                    acc.push(ToolCallEvent {
                        name:      tool_name.to_string(),
                        arguments: Some(serde_json::to_string(args).unwrap_or_default()),
                        result:    Some(msg.clone()),
                        status:    "failed".to_string(),
                    });
                }
                em.tool_error(tool_call_id, msg).await;
                Ok(RecordFlow::Continue)
            }
            ExecutionOutcome::Cancelled => {
                // A /stop hit this tool mid-flight. Record it as cancelled (not
                // failed); the sticky token cancels the rest of the loop by
                // construction, so the caller just ends the turn.
                info!(session_id = self.session_id, tool = %tool_name, tool_call_id, "tool cancelled by user");
                chat_llm_tools::cancel(pool, tool_call_id, "Cancelled by user.").await?;
                em.tool_cancelled(tool_call_id).await;
                Ok(RecordFlow::Abort)
            }
        }
    }
}
