//! Generic **PTY ↔ outbound stream sink** bridge infrastructure.
//!
//! ARCHITECTURE §2 decides that high-frequency PTY byte streams travel over a
//! per-session stream channel rather than global events, for throughput and
//! isolation. This module provides the desktop-facing plumbing that adapts the
//! shared backend sink abstraction to Tauri IPC.
//!
//! Design:
//! - The frontend opens a terminal and passes a [`tauri::ipc::Channel`] for that
//!   session. The adapter wraps it as a sink and registers it in [`PtyBridge`].
//! - Whatever produces output calls [`PtyBridge::send_output`], which forwards
//!   the bytes through the current sink. Bytes are sent as-is; the frontend
//!   xterm wrapper consumes them.
//! - [`PtyBridge::unregister`] tears the channel down on terminal close.

use std::sync::Arc;

use backend::stream::OutputBridge;
use tauri::ipc::Channel;

use domain::ids::SessionId;

use crate::stream::TauriChannelSink;

/// A chunk of PTY output bytes destined for a specific session's channel.
///
/// Sent as a raw byte vector; serde encodes it for the IPC channel. Kept as a
/// distinct type so the wire shape can evolve (e.g. add a sequence number for
/// backpressure handling) without touching call sites.
pub type PtyChunk = Vec<u8>;

/// Registry mapping live terminal sessions to their output sink.
///
/// Thread-safe; cloned `Arc<PtyBridge>` is held in [`crate::state::AppState`].
#[derive(Default)]
pub struct PtyBridge {
    inner: OutputBridge<SessionId, PtyChunk>,
}

impl PtyBridge {
    /// Creates an empty bridge.
    #[must_use]
    pub fn new() -> Self {
        Self {
            inner: OutputBridge::new(),
        }
    }

    /// Registers (or replaces) the output channel for a session and returns the
    /// **generation** of this registration. Each call for a session bumps the
    /// generation, so the caller's pump thread can later tear down *only its own*
    /// registration via [`unregister_if`](Self::unregister_if).
    pub fn register(&self, session: SessionId, channel: Channel<PtyChunk>) -> u64 {
        self.inner
            .register(session, Arc::new(TauriChannelSink::new(channel)))
    }

    /// Removes a session's channel unconditionally (terminal explicitly closed).
    pub fn unregister(&self, session: &SessionId) {
        self.inner.unregister(session);
    }

    /// Removes a session's channel **only if** `gen` is still the current
    /// generation. A pump thread calls this when its output stream ends: if the
    /// session has since been re-attached (newer generation), this is a no-op, so
    /// the dying thread never unregisters the live channel that superseded it.
    pub fn unregister_if(&self, session: &SessionId, gen: u64) {
        self.inner.unregister_if(session, gen);
    }

    /// Forwards a chunk of output bytes to a session's channel.
    ///
    /// Returns `true` if the chunk was delivered, `false` if no channel is
    /// registered for the session (e.g. already closed). In L3 the PTY adapter's
    /// output stream drives this.
    pub fn send_output(&self, session: &SessionId, chunk: PtyChunk) -> bool {
        self.inner.send_output(session, chunk)
    }

    /// Number of currently-registered sessions (handy for tests/diagnostics).
    #[must_use]
    pub fn active_sessions(&self) -> usize {
        self.inner.active_sessions()
    }
}
