//! Agent pool types — `AgentPool`, `StoryAgent`, and related data structures.
use crate::slog;
use crate::worktree::WorktreeInfo;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::{Mutex, broadcast};

use super::super::{AgentEvent, AgentInfo, AgentStatus, CompletionReport};

/// Build the composite key used to track agents in the pool.
pub(super) fn composite_key(story_id: &str, agent_name: &str) -> String {
    format!("{story_id}:{agent_name}")
}

/// RAII guard that removes a pending agent entry from the pool on drop.
///
/// Created after inserting a `Pending` entry into the agent HashMap.
/// If `start_agent` succeeds (the agent process is spawned and status
/// transitions to `Running`), call [`disarm`](Self::disarm) to prevent
/// cleanup.  If any intermediate step fails and the guard is dropped
/// without being disarmed, the pending entry is removed so it cannot
/// block future auto-assign dispatches.
pub(super) struct PendingGuard {
    pub(super) agents: Arc<Mutex<HashMap<String, StoryAgent>>>,
    pub(super) key: String,
    pub(super) armed: bool,
}

impl PendingGuard {
    pub(super) fn new(agents: Arc<Mutex<HashMap<String, StoryAgent>>>, key: String) -> Self {
        Self {
            agents,
            key,
            armed: true,
        }
    }

    /// Prevent the guard from cleaning up the entry (call after
    /// successful spawn).
    pub(super) fn disarm(&mut self) {
        self.armed = false;
    }
}

impl Drop for PendingGuard {
    fn drop(&mut self) {
        if !self.armed {
            return;
        }
        if let Ok(mut agents) = self.agents.try_lock()
            && agents
                .get(&self.key)
                .is_some_and(|a| a.status == AgentStatus::Pending)
        {
            agents.remove(&self.key);
            slog!(
                "[agents] Cleaned up leaked Pending entry for '{}'",
                self.key
            );
        }
    }
}

pub(super) struct StoryAgent {
    pub(super) agent_name: String,
    pub(super) status: AgentStatus,
    pub(super) worktree_info: Option<WorktreeInfo>,
    pub(super) session_id: Option<String>,
    pub(super) tx: broadcast::Sender<AgentEvent>,
    pub(super) task_handle: Option<tokio::task::JoinHandle<()>>,
    /// Accumulated events for polling via get_agent_output.
    pub(super) event_log: Arc<std::sync::Mutex<Vec<AgentEvent>>>,
    /// Set when the agent calls report_completion.
    pub(super) completion: Option<CompletionReport>,
    /// Project root, stored for pipeline advancement after completion.
    pub(super) project_root: Option<PathBuf>,
    /// UUID identifying the log file for this session.
    pub(super) log_session_id: Option<String>,
    /// Set to `true` when the agent calls `report_merge_failure`.
    /// Prevents the pipeline from blindly advancing to `5_done/` after a
    /// failed merge: the server-owned gate check runs in the feature-branch
    /// worktree (which compiles fine) and returns `gates_passed=true` even
    /// though the code was never squash-merged onto master.
    pub(super) merge_failure_reported: bool,
    /// Set to `true` by the merge runner when the squash merge succeeds and
    /// `story_archived: true` (bug 1008).  Lets the exit handler in
    /// `spawn.rs` recognise a clean successful exit and skip the
    /// transient-respawn logic entirely.
    pub(super) merge_success_reported: bool,
    /// Set when a rate-limit event was received for this agent.
    /// Carries the expiry time so the scheduler can decide when to retry.
    pub(super) throttled: Option<crate::agents::AgentExecution>,
    /// Set when the watchdog terminates the agent for exceeding a limit.
    pub(super) termination_reason: Option<crate::agents::TerminationReason>,
    /// Passive event accumulator scoped to this session's project.
    ///
    /// Created at session start by subscribing to the pool's
    /// [`crate::service::status::StatusBroadcaster`].  On restart a fresh
    /// buffer is constructed, effectively clearing the previous subscription.
    /// Events accumulate without invoking any model calls or side effects.
    #[allow(dead_code)]
    pub(super) status_buffer: Option<crate::service::status::buffer::StatusEventBuffer>,
}

/// Build an `AgentInfo` snapshot from a `StoryAgent` map entry.
pub(super) fn agent_info_from_entry(story_id: &str, agent: &StoryAgent) -> AgentInfo {
    AgentInfo {
        story_id: story_id.to_string(),
        agent_name: agent.agent_name.clone(),
        status: agent.status.clone(),
        session_id: agent.session_id.clone(),
        worktree_path: agent
            .worktree_info
            .as_ref()
            .map(|wt| wt.path.to_string_lossy().to_string()),
        base_branch: agent
            .worktree_info
            .as_ref()
            .map(|wt| wt.base_branch.clone()),
        completion: agent.completion.clone(),
        log_session_id: agent.log_session_id.clone(),
        throttled: agent.throttled.as_ref().map(|e| match e {
            crate::agents::AgentExecution::Throttled { until } => *until,
        }),
        termination_reason: agent.termination_reason.clone(),
    }
}
