//! Terminal session entity and supporting value objects.

use serde::{Deserialize, Serialize};

use crate::error::DomainError;
use crate::ids::{AgentId, NodeId, SessionId};
use crate::project::ProjectPath;

/// Dimensions of a pseudo-terminal, in character cells.
///
/// Invariant: `rows > 0 && cols > 0`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PtySize {
    /// Number of rows.
    pub rows: u16,
    /// Number of columns.
    pub cols: u16,
}

impl PtySize {
    /// Builds a validated PTY size.
    ///
    /// # Errors
    /// Returns [`DomainError::InvalidPtySize`] if either dimension is zero.
    pub fn new(rows: u16, cols: u16) -> Result<Self, DomainError> {
        if rows == 0 || cols == 0 {
            return Err(DomainError::InvalidPtySize { rows, cols });
        }
        Ok(Self { rows, cols })
    }
}

/// What a terminal session is running.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "type")]
pub enum SessionKind {
    /// A plain shell terminal.
    Plain,
    /// A terminal launched for a specific agent.
    #[serde(rename_all = "camelCase")]
    Agent {
        /// The agent driving this session.
        agent_id: AgentId,
    },
}

/// Lifecycle status of a terminal session.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "state")]
pub enum SessionStatus {
    /// Spawn requested, not yet confirmed running.
    Starting,
    /// Running.
    Running,
    /// Exited with a code.
    Exited {
        /// Process exit code.
        code: i32,
    },
}

/// A terminal session hosted by a layout leaf cell.
///
/// Invariants:
/// - `pty_size` is valid (`rows>0 && cols>0`),
/// - "at most one active session per leaf" is a *layout* invariant enforced in
///   [`crate::layout`], not here (a session does not own the leaf).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TerminalSession {
    /// Stable identifier.
    pub id: SessionId,
    /// Layout leaf hosting this session.
    pub node_id: NodeId,
    /// Working directory.
    pub cwd: ProjectPath,
    /// What the session runs.
    pub kind: SessionKind,
    /// Current terminal size.
    pub pty_size: PtySize,
    /// Lifecycle status.
    pub status: SessionStatus,
}

impl TerminalSession {
    /// Builds a terminal session (in `Starting` state).
    #[must_use]
    pub fn starting(
        id: SessionId,
        node_id: NodeId,
        cwd: ProjectPath,
        kind: SessionKind,
        pty_size: PtySize,
    ) -> Self {
        Self {
            id,
            node_id,
            cwd,
            kind,
            pty_size,
            status: SessionStatus::Starting,
        }
    }
}
