//! The printer module.

use std::{
    fmt::{self, Write},
    io,
    sync::{
        Arc,
        atomic::{AtomicU64, AtomicUsize, Ordering},
        mpsc::{self, Receiver, Sender},
    },
    thread::{self, JoinHandle},
    time::Duration,
};

use parking_lot::{Condvar, Mutex};
use tracing::error;

use crate::{ansi::AnsiStripper, typewriter::VisibleCharsIterator};

/// A shared buffer that can be written to.
pub type SharedBuffer = Arc<Mutex<String>>;

/// A centralized printer that handles output to out/err/tty via a background
/// thread.
#[derive(Debug)]
pub struct Printer {
    /// The sender channel for print commands.
    tx: Sender<Command>,

    /// The output format.
    format: OutputFormat,

    /// Whether a TTY writer is available for interactive prompts.
    has_tty: bool,

    /// The worker thread handle.
    worker_handle: Arc<Mutex<Option<JoinHandle<()>>>>,

    /// Shared with the background worker to interrupt typewriter sleeps.
    ///
    /// Set by `flush_instant()` before sending the `FlushInstant` command, so
    /// even a currently-running typewriter task wakes up immediately.
    /// Cleared by the worker after processing `FlushInstant`.
    delay_control: Arc<DelayControl>,
}

impl Clone for Printer {
    fn clone(&self) -> Self {
        Self {
            tx: self.tx.clone(),
            format: self.format,
            has_tty: self.has_tty,
            worker_handle: self.worker_handle.clone(),
            delay_control: self.delay_control.clone(),
        }
    }
}

impl Printer {
    /// Create a new printer with the given writers and format.
    pub fn new<O, E>(
        out: O,
        err: E,
        tty: Option<Box<dyn io::Write + Send>>,
        format: OutputFormat,
    ) -> Self
    where
        O: io::Write + Send + 'static,
        E: io::Write + Send + 'static,
    {
        let has_tty = tty.is_some();
        let (tx, rx) = mpsc::channel();
        let delay_control = Arc::new(DelayControl {
            skip: Mutex::new(false),
            wake: Condvar::new(),
            pending_chars: AtomicUsize::new(0),
            max_latency_nanos: AtomicU64::new(0),
            drain_snapshot: AtomicUsize::new(0),
        });

        let handle = {
            let delay_control = delay_control.clone();
            thread::spawn(move || {
                let mut worker = Worker {
                    out: Sink::new(out, format),
                    err: Sink::new(err, format),
                    tty,
                    rx,
                    delay_control,
                };
                worker.run();
            })
        };

        Self {
            tx,
            format,
            has_tty,
            worker_handle: Arc::new(Mutex::new(Some(handle))),
            delay_control,
        }
    }

    /// Create a new printer that writes to the terminal (stdout/stderr).
    ///
    /// If `/dev/tty` (Unix) or `CONOUT$` (Windows) is available, it is opened
    /// for interactive prompt output.
    /// This allows prompts to render on the terminal even when stdout and
    /// stderr are redirected.
    #[must_use]
    pub fn terminal(format: OutputFormat) -> Self {
        let tty = open_tty().map(|f| Box::new(f) as Box<dyn io::Write + Send>);
        Self::new(io::stdout(), io::stderr(), tty, format)
    }

    /// Create a new printer that silently discards all output.
    ///
    /// Useful when output should be suppressed entirely, e.g. when tool call
    /// rendering is disabled.
    /// The printer still spawns a background thread, but writes go to
    /// [`io::sink()`].
    #[must_use]
    pub fn sink() -> Self {
        Self::new(io::sink(), io::sink(), None, OutputFormat::Text)
    }

    /// Create a new printer that writes to memory buffers.
    #[must_use]
    pub fn memory(format: OutputFormat) -> (Self, SharedBuffer, SharedBuffer) {
        let out = Arc::new(Mutex::default());
        let err = Arc::new(Mutex::default());

        let out_w = SharedVec(out.clone());
        let err_w = SharedVec(err.clone());

        (Self::new(out_w, err_w, None, format), out, err)
    }

    /// Returns the output format.
    #[must_use]
    pub const fn format(&self) -> OutputFormat {
        self.format
    }

    /// Returns `true` if pretty printing is enabled (ANSI colors, unicode
    /// decorations, syntax highlighting).
    #[must_use]
    pub const fn pretty_printing_enabled(&self) -> bool {
        self.format.is_pretty()
    }
    /// Print content.
    ///
    /// In JSON mode, the content is wrapped in an NDJSON envelope:
    /// `{"message":"..."}\n`.
    pub fn print<P: Printable>(&self, p: P) {
        let mut task = p.into_task();
        if self.format.is_json() {
            task = self.wrap_json(task);
            task.content.push('\n');
        }

        self.send(Command::Print(task));
    }

    /// Print content followed by a newline.
    ///
    /// In JSON mode, the content is wrapped in an NDJSON envelope:
    /// `{"message":"..."}\n`.
    pub fn println<P: Printable>(&self, p: P) {
        let mut task = p.into_task();
        if self.format.is_json() {
            task = self.wrap_json(task);
        }

        task.content.push('\n');
        self.send(Command::Print(task));
    }

    /// Print pre-formatted content followed by a newline, without JSON
    /// wrapping.
    pub fn println_raw<P: Printable>(&self, p: P) {
        let mut task = p.into_task();
        task.content.push('\n');
        self.send(Command::Print(task));
    }

    /// Print error.
    pub fn eprint<P: Printable>(&self, p: P) {
        let mut task = p.into_task();
        task.target = PrintTarget::Err;
        self.send(Command::Print(task));
    }

    /// Print error followed by a newline.
    pub fn eprintln<P: Printable>(&self, p: P) {
        let mut task = p.into_task();
        task.content.push('\n');
        task.target = PrintTarget::Err;
        self.send(Command::Print(task));
    }

    /// Wrap a print task's content in an NDJSON envelope.
    ///
    /// ANSI escapes are stripped before serialization.
    /// This must happen here rather than in the worker because `serde_json`
    /// would encode ESC bytes as `\u001b`, making them invisible to the
    /// worker's `strip_ansi_escapes` pass.
    fn wrap_json(&self, mut task: PrintTask) -> PrintTask {
        let stripped = strip_ansi_escapes::strip_str(&task.content);
        let msg = stripped.trim_end_matches('\n');
        let json = serde_json::json!({ "message": msg });

        task.content = if self.format.is_json_pretty() {
            serde_json::to_string_pretty(&json).unwrap_or_else(|_| json.to_string())
        } else {
            serde_json::to_string(&json).unwrap_or_else(|_| json.to_string())
        };

        task
    }

    /// Get a writer that prints to the `out` stream.
    #[must_use]
    pub const fn out_writer(&self) -> PrinterWriter<'_> {
        PrinterWriter {
            printer: self,
            target: PrintTarget::Out,
        }
    }

    /// Get a writer that prints to the `err` stream.
    #[must_use]
    pub const fn err_writer(&self) -> PrinterWriter<'_> {
        PrinterWriter {
            printer: self,
            target: PrintTarget::Err,
        }
    }

    /// Get a writer for interactive prompt output.
    ///
    /// Prefers the TTY (`/dev/tty`) if available, falling back to `out`.
    /// This ensures prompts always render somewhere visible.
    #[must_use]
    pub const fn prompt_writer(&self) -> PrinterWriter<'_> {
        PrinterWriter {
            printer: self,
            target: if self.has_tty {
                PrintTarget::Tty
            } else {
                PrintTarget::Out
            },
        }
    }

    /// Configure the bounded-latency controller's budget.
    ///
    /// When `max_latency` is non-zero, the worker shrinks the per-character
    /// typewriter delay below the cap supplied by each `Print` task as the
    /// queue of pending characters grows, keeping printed output within
    /// `max_latency` of what the source has already emitted.
    /// Passing `Duration::ZERO` (the default) disables the controller and
    /// restores the static per-character delay behavior.
    pub fn set_max_latency(&self, max_latency: Duration) {
        let nanos = u64::try_from(max_latency.as_nanos()).unwrap_or(u64::MAX);
        self.delay_control
            .max_latency_nanos
            .store(nanos, Ordering::Relaxed);
    }

    /// Signal that the current typewriter producer is done emitting.
    ///
    /// Captures the current pending-character count as a floor on the
    /// controller's denominator so the per-character delay never grows back up
    /// as the queue drains.
    /// Any subsequent typewriter `Print` task clears the snapshot, returning
    /// the controller to live mode.
    ///
    /// No-op when no typewriter content is pending or when the controller is
    /// disabled.
    pub fn mark_typewriter_drained(&self) {
        let pending = self.delay_control.pending_chars.load(Ordering::Relaxed);
        if pending > 0 {
            self.delay_control
                .drain_snapshot
                .store(pending, Ordering::Relaxed);
        }
    }

    /// Track typewriter-visible characters before enqueueing a task.
    ///
    /// Increments the pending counter by the visible-character count of the
    /// task's content, and clears any drain snapshot so the bounded-latency
    /// controller treats the producer as active again.
    /// No-op for non-typewriter tasks.
    fn track_pending(&self, task: &PrintTask) {
        if !matches!(task.mode, PrintMode::Typewriter(_)) {
            return;
        }

        let count = VisibleCharsIterator::new(&task.content)
            .filter(|&(_, visible)| visible)
            .count();
        if count == 0 {
            return;
        }

        self.delay_control
            .pending_chars
            .fetch_add(count, Ordering::Relaxed);
        self.delay_control
            .drain_snapshot
            .store(0, Ordering::Relaxed);
    }

    /// Block until all currently queued print tasks are finished.
    pub fn flush(&self) {
        let (tx, rx) = mpsc::channel();
        if self.tx.send(Command::Flush(tx)).is_ok() {
            let _ = rx.recv();
        }
    }

    /// Flush all pending print tasks instantly, ignoring typewriter delays.
    ///
    /// Drains queued `Print` commands and writes their content immediately,
    /// then blocks until complete.
    /// Useful before showing interactive prompts (e.g., interrupt menus) to
    /// ensure all buffered output is visible without typewriter lag.
    pub fn flush_instant(&self) {
        // Set the flag and wake the worker so a currently-running
        // typewriter sleep returns immediately. The worker clears
        // the flag after draining.
        {
            *self.delay_control.skip.lock() = true;
        }
        self.delay_control.wake.notify_all();

        let (tx, rx) = mpsc::channel();
        if self.tx.send(Command::FlushInstant(tx)).is_ok() {
            let _ = rx.recv();
        }
    }

    /// Stop the background thread and wait for it to finish.
    pub fn shutdown(&self) {
        drop(self.tx.send(Command::Shutdown));
        let mut guard = self.worker_handle.lock();
        if let Some(handle) = guard.take() {
            drop(handle.join());
        }
    }

    /// Send a command to the background thread, printing an error if it fails.
    ///
    /// `Print` commands carrying typewriter mode also update the pending-
    /// character counter so the bounded-latency controller has the queue depth
    /// it needs.
    fn send(&self, command: Command) {
        if let Command::Print(task) = &command {
            self.track_pending(task);
        }

        if let Err(command) = self.tx.send(command) {
            error!(?command, "Failed to send command");
        }
    }
}

/// A writer wrapper for [`Printer`] that implements [`fmt::Write`].
#[derive(Debug, Clone, Copy)]
pub struct PrinterWriter<'a> {
    /// The printer to write to.
    printer: &'a Printer,

    /// The target output stream.
    target: PrintTarget,
}

impl fmt::Write for PrinterWriter<'_> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let task = PrintTask {
            content: s.to_owned(),
            mode: PrintMode::Instant,
            target: self.target,
        };

        self.printer
            .tx
            .send(Command::Print(task))
            .map_err(|_| fmt::Error)?;

        Ok(())
    }
}

impl io::Write for PrinterWriter<'_> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let s = str::from_utf8(buf).map_err(io::Error::other)?;

        self.write_str(s)
            .map_err(io::Error::other)
            .map(|()| s.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        let (tx, rx) = mpsc::channel();
        self.printer
            .tx
            .send(Command::Flush(tx))
            .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "printer shutdown"))?;

        rx.recv()
            .map_err(|_| io::Error::other("failed to receive flush signal"))
    }
}

/// The worker thread that processes print tasks.
struct Worker<O, E> {
    /// The `out` writer; strips ANSI escapes for non-pretty formats.
    out: Sink<O>,

    /// The `err` writer; strips ANSI escapes for non-pretty formats.
    err: Sink<E>,

    /// The TTY writer for interactive prompts (`/dev/tty`).
    tty: Option<Box<dyn io::Write + Send>>,

    /// The receiver for print commands.
    rx: Receiver<Command>,

    /// Shared with the [`Printer`] to interrupt typewriter sleeps.
    delay_control: Arc<DelayControl>,
}

impl<O: io::Write, E: io::Write> Worker<O, E> {
    /// Run the worker thread.
    fn run(&mut self) {
        while let Ok(cmd) = self.rx.recv() {
            match cmd {
                Command::Print(task) => self.process_task(&task),
                Command::Flush(tx) => {
                    // We don't need to do anything specific to flush out/err
                    // because we flush after every write in `process_task`. We
                    // just need to signal that we processed all messages prior
                    // to this one.
                    let _ = tx.send(());
                }
                Command::FlushInstant(tx) => {
                    self.drain_instant();
                    *self.delay_control.skip.lock() = false;
                    let _ = tx.send(());
                }
                Command::Shutdown => break,
            }
        }
    }

    /// Drain all pending commands, printing instantly.
    ///
    /// Processes queued `Print` tasks as `Instant` (ignoring typewriter
    /// delays).
    /// Honors `Flush` (signals the sender) and `Shutdown` (stops the worker) if
    /// encountered during the drain.
    fn drain_instant(&mut self) {
        while let Ok(cmd) = self.rx.try_recv() {
            match cmd {
                Command::Print(task) => self.process_task_instant(&task),
                Command::Flush(tx) => {
                    let _ = tx.send(());
                }
                Command::FlushInstant(tx) => {
                    // Nested FlushInstant: we're already draining, just signal.
                    let _ = tx.send(());
                }
                Command::Shutdown => break,
            }
        }
    }

    /// Process a print task instantly, ignoring typewriter delays.
    fn process_task_instant(&mut self, task: &PrintTask) {
        let writer: &mut dyn io::Write = match task.target {
            PrintTarget::Out => &mut self.out,
            PrintTarget::Err => &mut self.err,
            PrintTarget::Tty => match self.tty.as_mut() {
                Some(w) => w.as_mut(),
                None => return,
            },
        };

        let _err = writer.write_all(task.content.as_bytes());
        let _err = writer.flush();

        // A typewriter task drained instantly still contributed to the
        // pending counter at enqueue time; release its share now so the
        // bounded-latency controller doesn't stay biased toward an empty
        // queue.
        if matches!(task.mode, PrintMode::Typewriter(_)) {
            let count = visible_char_count(&task.content);
            release_pending(&self.delay_control, count);
        }
    }

    /// Process a single print task.
    fn process_task(&mut self, task: &PrintTask) {
        let PrintTask {
            content,
            mode,
            target,
        } = task;

        let writer: &mut dyn io::Write = match target {
            PrintTarget::Out => &mut self.out,
            PrintTarget::Err => &mut self.err,
            PrintTarget::Tty => match self.tty.as_mut() {
                Some(w) => w.as_mut(),
                None => return,
            },
        };

        match mode {
            PrintMode::Instant => {
                let _err = write!(writer, "{content}");
                let _err = writer.flush();
            }
            PrintMode::Typewriter(delay) => {
                if delay.is_zero() || *self.delay_control.skip.lock() {
                    let _err = write!(writer, "{content}");
                    let _err = writer.flush();
                    // The whole content was emitted in one shot; release
                    // its visible-char share from the pending counter.
                    let count = visible_char_count(content);
                    release_pending(&self.delay_control, count);
                    return;
                }

                // Accumulate per-character delays into batches before
                // blocking. The bounded-latency controller can request
                // delays well below the OS scheduler's tick (notably
                // ~15.6ms on Windows), and a sub-tick sleep still costs
                // a full tick of wall clock. Without batching, a burst
                // of 200 chars at 1ms each takes ~3.1s on Windows
                // instead of the 200ms the controller asked for —
                // silently violating the configured `max_latency`.
                let mut credit = Duration::ZERO;
                for (c, visible) in VisibleCharsIterator::new(content) {
                    let _err = write!(writer, "{c}");
                    let _err = writer.flush();

                    if !visible {
                        continue;
                    }

                    let effective = self.delay_control.effective_delay(*delay);
                    credit = credit.saturating_add(effective);

                    if credit >= SLEEP_GRANULARITY {
                        let mut skip = self.delay_control.skip.lock();
                        if !*skip {
                            // Interruptible sleep: returns early if
                            // flush_instant() notifies the condvar.
                            self.delay_control.wake.wait_for(&mut skip, credit);
                        }
                        drop(skip);
                        credit = Duration::ZERO;
                    }

                    release_pending(&self.delay_control, 1);
                }
            }
        }
    }
}

/// A per-stream output sink.
///
/// Wraps the underlying writer, stripping ANSI escape sequences for non-pretty
/// formats and passing bytes through untouched for pretty output.
/// Each stream owns its own sink so escape-sequence state never bleeds between
/// `out` and `err`.
enum Sink<W> {
    /// Pass bytes through unchanged; pretty formats keep their ANSI.
    Raw(W),

    /// Strip ANSI escape sequences before writing.
    Strip(Box<AnsiStripper<W>>),
}

impl<W: io::Write> Sink<W> {
    /// Build a sink for `inner`, stripping ANSI unless `format` is pretty.
    fn new(inner: W, format: OutputFormat) -> Self {
        if format.is_pretty() {
            Self::Raw(inner)
        } else {
            Self::Strip(Box::new(AnsiStripper::new(inner)))
        }
    }
}

impl<W: io::Write> io::Write for Sink<W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match self {
            Self::Raw(writer) => writer.write(buf),
            Self::Strip(writer) => writer.write(buf),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match self {
            Self::Raw(writer) => writer.flush(),
            Self::Strip(writer) => writer.flush(),
        }
    }
}

/// A shared buffer that can be written to.
struct SharedVec(SharedBuffer);

impl Write for SharedVec {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.0.lock().write_str(s)
    }
}

impl io::Write for SharedVec {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.0
            .lock()
            .write_str(String::from_utf8_lossy(buf).as_ref())
            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid UTF-8"))?;

        Ok(buf.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

/// The output format controls how content is rendered.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OutputFormat {
    /// Plain text.
    /// No ANSI colors, no unicode decorations.
    #[default]
    Text,

    /// Pretty text.
    /// ANSI colors, unicode decorations, syntax highlighting.
    TextPretty,

    /// Compact JSON.
    /// Each print call becomes an NDJSON line.
    Json,

    /// Pretty-printed JSON.
    /// Indented, but no ANSI colors.
    JsonPretty,
}

impl OutputFormat {
    /// Whether this format uses ANSI colors and unicode decorations.
    #[must_use]
    pub const fn is_pretty(self) -> bool {
        matches!(self, Self::TextPretty)
    }

    /// Whether this format produces JSON output.
    #[must_use]
    pub const fn is_json(self) -> bool {
        matches!(self, Self::Json | Self::JsonPretty)
    }

    /// Whether JSON output should be indented.
    #[must_use]
    pub const fn is_json_pretty(self) -> bool {
        matches!(self, Self::JsonPretty)
    }
}

/// The mode of printing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrintMode {
    /// Print instantly.
    Instant,

    /// Print with a typewriter effect.
    Typewriter(Duration),
}

/// The target output stream.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrintTarget {
    /// Output stream (stdout) — assistant responses, structured data.
    Out,

    /// Error stream (stderr) — chrome, progress, status.
    Err,

    /// TTY stream (`/dev/tty`) — interactive prompts.
    ///
    /// Bypasses stdout/stderr redirections so prompts always render on the
    /// terminal.
    /// Content is always written with ANSI escapes intact (never stripped) and
    /// is never JSON-wrapped.
    Tty,
}

#[derive(Debug, Clone)]
/// A task to be printed.
pub struct PrintTask {
    /// The content to print.
    pub content: String,

    /// The mode of printing.
    pub mode: PrintMode,

    /// The target output stream.
    pub target: PrintTarget,
}

impl Default for PrintTask {
    fn default() -> Self {
        Self {
            content: String::new(),
            mode: PrintMode::Instant,
            target: PrintTarget::Out,
        }
    }
}

/// Trait for types that can be converted into a print task.
pub trait Printable {
    /// Convert into a print task.
    fn into_task(self) -> PrintTask;
}

impl Printable for String {
    fn into_task(self) -> PrintTask {
        PrintTask {
            content: self,
            ..Default::default()
        }
    }
}

impl Printable for &String {
    fn into_task(self) -> PrintTask {
        PrintTask {
            content: self.to_owned(),
            ..Default::default()
        }
    }
}

impl Printable for &str {
    fn into_task(self) -> PrintTask {
        PrintTask {
            content: self.to_owned(),
            ..Default::default()
        }
    }
}

impl Printable for PrintTask {
    fn into_task(self) -> PrintTask {
        self
    }
}

/// Wrapper struct to enable `.typewriter(duration)` syntax.
pub struct Typewriter<T>(pub T, pub Duration);

impl<T: Printable> Printable for Typewriter<T> {
    fn into_task(self) -> PrintTask {
        let mut task = self.0.into_task();
        task.mode = PrintMode::Typewriter(self.1);
        task
    }
}

/// Extension trait to add `.typewriter()` to strings and other printables.
pub trait PrintableExt: Sized + Printable {
    /// Print with a typewriter effect.
    fn typewriter(self, delay: Duration) -> Typewriter<Self> {
        Typewriter(self, delay)
    }

    /// Print to `err` instead of `out`.
    fn to_err(self) -> PrintTask {
        let mut task = self.into_task();
        task.target = PrintTarget::Err;
        task
    }
}

impl<T: Printable> PrintableExt for T {}

/// A command to be executed by the printer worker.
#[derive(Debug)]
enum Command {
    /// Print a task.
    Print(PrintTask),

    /// Flush the printer.
    ///
    /// The sender is used to signal the flush is complete.
    Flush(mpsc::Sender<()>),

    /// Flush all pending print tasks instantly, ignoring typewriter delays.
    ///
    /// Drains all queued `Print` commands and writes their content immediately
    /// (as `PrintMode::Instant`), then signals completion.
    /// Other commands (`Flush`, `Shutdown`) encountered during the drain are
    /// still honored.
    FlushInstant(mpsc::Sender<()>),

    /// Shutdown the printer.
    Shutdown,
}

/// Shared state for interruptible typewriter delays and bounded-latency pacing.
///
/// Bundles three concerns shared between [`Printer`] and the background worker:
///
/// 1. [`Printer::flush_instant`] interruption: `skip` + `wake` allow a sleeping
///    per-character delay to be cancelled immediately.
/// 2. Bounded-latency pacing: `pending_chars` (typewriter-visible characters
///    still queued or being emitted) plus `max_latency_nanos` (the configured
///    budget) let the worker adapt its per-character delay to queue depth.
/// 3. Drain mode: when the producer signals via
///    [`Printer::mark_typewriter_drained`], `drain_snapshot` captures the
///    pending count at that moment so the controller never slows back up as the
///    queue empties.
///    Any subsequent typewriter enqueue resets the snapshot to `0` (i.e. live
///    mode resumes).
#[derive(Debug)]
struct DelayControl {
    /// When `true`, the worker skips all typewriter delays.
    skip: Mutex<bool>,

    /// Notified when `skip` transitions to `true`.
    wake: Condvar,

    /// Count of typewriter-visible characters queued but not yet emitted.
    ///
    /// Incremented by the printer at enqueue time, decremented by the worker
    /// after each visible character is written.
    pending_chars: AtomicUsize,

    /// Bounded-latency budget in nanoseconds.
    /// `0` disables the controller.
    max_latency_nanos: AtomicU64,

    /// Captured `pending_chars` at the moment
    /// [`Printer::mark_typewriter_drained`] was called.
    /// `0` means "not in drain mode".
    /// When non-zero, the worker uses `max(drain_snapshot, pending_chars)` as
    /// the divisor so the per-char delay never grows as the queue shrinks.
    drain_snapshot: AtomicUsize,
}

impl DelayControl {
    /// Compute the effective per-character delay for the typewriter loop.
    ///
    /// Returns `cap` directly when the controller is disabled or the queue is
    /// empty.
    /// Otherwise returns `min(cap, max_latency / denom)`, where `denom =
    /// max(drain_snapshot, pending_chars)` so that drain mode prevents the
    /// delay from growing as the queue empties.
    fn effective_delay(&self, cap: Duration) -> Duration {
        let max_latency_nanos = self.max_latency_nanos.load(Ordering::Relaxed);
        if max_latency_nanos == 0 {
            return cap;
        }

        let pending = self.pending_chars.load(Ordering::Relaxed);
        let drain = self.drain_snapshot.load(Ordering::Relaxed);
        let denom = drain.max(pending);
        if denom == 0 {
            return cap;
        }

        let computed_nanos = max_latency_nanos / denom as u64;
        cap.min(Duration::from_nanos(computed_nanos))
    }
}

/// Minimum batch size for typewriter per-character delays.
///
/// `Condvar::wait_for` is bounded below by the OS scheduler's timer resolution.
/// On Linux/macOS that's well under 1ms, so per-character sleeps are honored as
/// requested and the typewriter looks smooth.
/// On Windows the default tick is ~15.6ms, so any sub-tick request still parks
/// the worker for a full tick — without batching, a 200- character burst at
/// 1ms each takes ~3.1s instead of the 200ms the bounded-latency controller
/// asked for, silently violating the configured `max_latency`.
///
/// The worker accumulates effective per-character delays and only parks once
/// the accumulated credit reaches this threshold.
/// Setting it just below the Windows tick lets a single batched sleep cover the
/// same tick the OS was already going to schedule us on.
/// On non-Windows platforms the threshold is small enough that any practical
/// per-character delay hits it on the first character, so the loop behaves
/// identically to the original per-character sleep.
///
/// A better-but-more-complex fix exists for Windows specifically:
/// `std::thread::sleep` has used `CreateWaitableTimerExW` with
/// `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION` since Rust 1.75 (Win 10 1803+), so
/// true sub-millisecond per-character pacing is possible.
/// Reaching it from here means dropping the `Condvar`-based wake and polling
/// the `skip` flag between short `thread::sleep` calls — trading immediate
/// `flush_instant` wake-up for up-to-poll-interval wake-up latency, plus extra
/// syscall overhead from re-creating the waitable timer on every sleep.
/// We've taken the simpler batching fix here on the assumption that ~10ms
/// bursts on Windows are acceptable for a typewriter effect.
/// Revisit if real-world output on Windows looks visibly chunkier than the
/// Linux/macOS baseline.
#[cfg(windows)]
const SLEEP_GRANULARITY: Duration = Duration::from_millis(10);

/// See the `cfg(windows)` variant for the rationale.
/// On non-Windows platforms the scheduler's timer resolution is already sub-ms,
/// so the threshold is effectively just "any non-zero delay".
#[cfg(not(windows))]
const SLEEP_GRANULARITY: Duration = Duration::from_nanos(1);

/// Count the visible (non-control, non-ANSI-escape) characters in a string.
///
/// Mirrors what the typewriter loop actually delays on, so the bounded- latency
/// controller's pending counter tracks the same units the worker emits.
fn visible_char_count(s: &str) -> usize {
    VisibleCharsIterator::new(s)
        .filter(|&(_, visible)| visible)
        .count()
}

/// Release `count` characters' worth of pending budget on the controller.
///
/// Saturating subtract so a torn read or an off-by-one between `track_pending`
/// and the worker can never wrap the counter around to `usize::MAX`.
/// Once the queue actually drains, the next typewriter enqueue resets the
/// counter via `track_pending` anyway.
fn release_pending(delay_control: &DelayControl, count: usize) {
    if count == 0 {
        return;
    }
    let _ =
        delay_control
            .pending_chars
            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
                Some(current.saturating_sub(count))
            });
}

/// Opens the controlling terminal for writing.
///
/// Returns `None` if no controlling terminal is available (e.g., CI, Docker
/// without `-t`, or detached processes).
#[cfg(unix)]
fn open_tty() -> Option<std::fs::File> {
    std::fs::OpenOptions::new()
        .write(true)
        .open("/dev/tty")
        .ok()
}

/// Opens the console output device for writing.
#[cfg(windows)]
fn open_tty() -> Option<std::fs::File> {
    std::fs::OpenOptions::new().write(true).open("CONOUT$").ok()
}

/// No TTY support on this platform.
#[cfg(not(any(unix, windows)))]
fn open_tty() -> Option<std::fs::File> {
    None
}

#[cfg(test)]
#[path = "printer_tests.rs"]
mod tests;
