//! Shared services bundle — common state threaded through HTTP handlers and chat transports.
//!
//! `Services` bundles the fields that every transport (Matrix, Slack, Discord,
//! WhatsApp) and the HTTP/MCP layer need. A single `Arc<Services>` is
//! constructed once in `main.rs` and cloned into `AppContext` and each
//! transport's context struct.

use crate::agents::AgentPool;
use crate::chat::dispatcher::ChatDispatcher;
use crate::service::permission_router::{PendingPermReplies, ResponderRegistry};
use crate::service::status::StatusBroadcaster;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;

/// Shared state bundle constructed once at startup and cloned (via `Arc`) into
/// every context that needs access to the project root, agent pool, bot
/// identity, ambient-room set, or permission plumbing.
pub struct Services {
    /// Absolute path to the project root directory.
    pub project_root: PathBuf,
    /// Agent pool for starting, stopping, and querying coding agents.
    pub agents: Arc<AgentPool>,
    /// Display name the bot uses to identify itself (from `bot.toml`).
    pub bot_name: String,
    /// String representation of the bot's user ID (e.g. `"@timmy:hs.local"`
    /// for Matrix, `"slack-bot"` for Slack).
    pub bot_user_id: String,
    /// Set of room/channel IDs where ambient mode is active.
    pub ambient_rooms: Arc<std::sync::Mutex<HashSet<String>>>,
    /// Registry of tasks currently registered to receive forwarded MCP
    /// `prompt_permission` requests (Matrix bot, sled uplink, WS chat
    /// sessions, per-message chat transports). Replaces holding `perm_rx`'s
    /// mutex as a presence signal.
    pub permission_registry: Arc<ResponderRegistry>,
    /// Pending permission replies awaiting a plain-language (yes/no) chat
    /// reply, keyed by `request_id` with a per-location FIFO index so two
    /// concurrent requests for the same room/sender/channel don't drop each
    /// other's oneshot sender.
    pub pending_perm_replies: Arc<PendingPermReplies>,
    /// Seconds to wait for a user to respond to a permission prompt before
    /// auto-denying (fail-closed).
    pub permission_timeout_secs: u64,
    /// Project-scoped status broadcaster.
    ///
    /// Consumers (chat transports, Web UI, agent context) call
    /// [`StatusBroadcaster::subscribe`] to receive pipeline status events.
    /// The broadcaster is project-scoped: events published here are delivered
    /// only to subscribers of this instance, providing natural multi-project
    /// isolation.
    pub status: Arc<StatusBroadcaster>,
    /// Protocol-agnostic chat dispatcher shared by all transport handlers.
    ///
    /// Transport handlers call [`ChatDispatcher::submit`] instead of spawning
    /// `claude -p` directly.  The dispatcher applies a coalesce window and a
    /// per-session serial lock, preventing duplicate concurrent spawns.
    pub chat_dispatcher: Arc<ChatDispatcher>,
}

#[cfg(test)]
impl Services {
    /// Build a minimal `Services` for testing with the given project root and
    /// bot display name.
    pub fn new_test(project_root: std::path::PathBuf, bot_name: String) -> std::sync::Arc<Self> {
        let agents = std::sync::Arc::new(crate::agents::AgentPool::new_test(3000));
        std::sync::Arc::new(Self {
            project_root,
            status: agents.status_broadcaster(),
            agents,
            bot_name,
            bot_user_id: String::new(),
            ambient_rooms: std::sync::Arc::new(std::sync::Mutex::new(HashSet::new())),
            permission_registry: ResponderRegistry::new(),
            pending_perm_replies: PendingPermReplies::new(),
            permission_timeout_secs: 120,
            chat_dispatcher: std::sync::Arc::new(ChatDispatcher::new(1_500)),
        })
    }
}
