//! Terminal registry for tracking active terminal processes.
//!
//! **Status**: Implemented (TOOLS-TERM-01)
//!
//! This module provides a global registry for tracking terminal processes,
//! their output buffers, and their lifecycle state.

use super::ring_buffer::RingBuffer;
use crate::error::{ToolError, ToolResult};
use std::collections::HashMap;
use std::process::ExitStatus;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use tokio::process::Child;
use tokio::task::JoinHandle;

/// Unique identifier for a terminal.
pub type TerminalId = String;

/// State of a terminal process.
pub struct TerminalState {
    /// Process child handle
    pub process: Child,
    /// Output ring buffer (stdout + stderr interleaved)
    pub output_buffer: Arc<Mutex<RingBuffer>>,
    /// When the terminal was created
    pub start_time: Instant,
    /// Exit status (once process completes)
    pub exit_status: Option<ExitStatus>,
    /// Background task handle for output capture
    pub output_task: Option<JoinHandle<()>>,
    /// Whether the terminal has been explicitly killed
    pub killed: bool,
}

/// Global terminal registry.
///
/// This is a singleton that tracks all active terminals.
pub struct TerminalRegistry {
    terminals: Arc<Mutex<HashMap<TerminalId, TerminalState>>>,
}

impl TerminalRegistry {
    /// Create a new terminal registry.
    pub fn new() -> Self {
        Self {
            terminals: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    /// Generate a unique terminal ID.
    pub fn generate_id(&self) -> TerminalId {
        use std::sync::atomic::{AtomicU64, Ordering};
        static COUNTER: AtomicU64 = AtomicU64::new(0);
        let id = COUNTER.fetch_add(1, Ordering::SeqCst);
        format!("term-{}", id)
    }

    /// Insert a terminal into the registry.
    pub fn insert(&self, id: TerminalId, state: TerminalState) {
        let mut terminals = self.terminals.lock().unwrap();
        terminals.insert(id, state);
    }

    /// Get a reference to a terminal state.
    ///
    /// Note: This locks the entire registry. For better concurrency,
    /// consider redesigning to use individual locks per terminal.
    pub fn get_mut<F, R>(&self, id: &str, f: F) -> ToolResult<R>
    where
        F: FnOnce(&mut TerminalState) -> R,
    {
        let mut terminals = self.terminals.lock().unwrap();
        let state = terminals.get_mut(id).ok_or_else(|| {
            ToolError::TerminalNotFound {
                terminal_id: id.to_string(),
            }
        })?;
        Ok(f(state))
    }

    /// Remove a terminal from the registry.
    pub fn remove(&self, id: &str) -> ToolResult<TerminalState> {
        let mut terminals = self.terminals.lock().unwrap();
        terminals.remove(id).ok_or_else(|| ToolError::TerminalNotFound {
            terminal_id: id.to_string(),
        })
    }

    /// Check if a terminal exists.
    pub fn contains(&self, id: &str) -> bool {
        let terminals = self.terminals.lock().unwrap();
        terminals.contains_key(id)
    }

    /// Get the number of active terminals.
    pub fn len(&self) -> usize {
        let terminals = self.terminals.lock().unwrap();
        terminals.len()
    }

    /// Check if the registry is empty.
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

impl Default for TerminalRegistry {
    fn default() -> Self {
        Self::new()
    }
}

// Global registry instance
lazy_static::lazy_static! {
    static ref GLOBAL_REGISTRY: TerminalRegistry = TerminalRegistry::new();
}

/// Get the global terminal registry.
pub fn global_registry() -> &'static TerminalRegistry {
    &GLOBAL_REGISTRY
}

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

    #[test]
    fn test_generate_unique_ids() {
        let registry = TerminalRegistry::new();
        let id1 = registry.generate_id();
        let id2 = registry.generate_id();
        let id3 = registry.generate_id();

        assert_ne!(id1, id2);
        assert_ne!(id2, id3);
        assert_ne!(id1, id3);
    }

    #[test]
    fn test_registry_contains() {
        let registry = TerminalRegistry::new();
        assert!(!registry.contains("nonexistent"));
        assert_eq!(registry.len(), 0);
        assert!(registry.is_empty());
    }
}
