"""Typewriter-effect label widget for the ALiCE TUI.

When *WordStar mode* is active the widget adds a blinking block cursor,
variable keystroke speed (simulating mechanical key travel), and an
optional per-character "clack" flash on the status bar.
"""

from __future__ import annotations

import random
from typing import TYPE_CHECKING

from textual.message import Message
from textual.reactive import reactive
from textual.widgets import Static

if TYPE_CHECKING:
    from textual.timer import Timer


class Typewriter(Static):
    """Reveals text one character at a time, like an old terminal printer.

    Parameters
    ----------
    text:
        The full string to reveal.
    speed:
        Base seconds between characters.  In WordStar mode the actual
        interval jitters ±40 % to mimic uneven keystrokes.
    wordstar:
        Enable enhanced retro mode (variable speed, cursor blink, clack
        flash messages).
    """

    DEFAULT_CSS = """
    Typewriter {
        color: #FFB000;
    }
    """

    # ── Textual message posted when the reveal finishes ───────────────
    class Done(Message):
        """Posted when the typewriter finishes revealing its text."""

    # ── reactive: visible cursor character ────────────────────────────
    cursor_visible: reactive[bool] = reactive(True)

    def __init__(
        self,
        text: str,
        *,
        speed: float = 0.03,
        wordstar: bool = False,
        id: str | None = None,
        classes: str | None = None,
    ) -> None:
        super().__init__("", id=id, classes=classes)
        self._full_text = text
        self._base_speed = speed
        self._wordstar = wordstar
        self._pos = 0
        self._timer: Timer | None = None
        self._cursor_timer: Timer | None = None
        self._done = False

    # ── lifecycle ─────────────────────────────────────────────────────

    def on_mount(self) -> None:
        self._pos = 0
        self._done = False
        self.update("")
        self._schedule_next()
        if self._wordstar:
            self._cursor_timer = self.set_interval(0.5, self._blink_cursor)

    # ── reveal logic ──────────────────────────────────────────────────

    def _schedule_next(self) -> None:
        """Schedule the next character reveal with optional jitter."""
        if self._wordstar:
            # Simulate uneven mechanical keystrokes
            jitter = random.uniform(0.6, 1.4)  # noqa: S311
            delay = self._base_speed * jitter
            # Extra pause after punctuation (carriage-return feel)
            if self._pos > 0 and self._full_text[self._pos - 1] in ".!?\n":
                delay += random.uniform(0.08, 0.20)  # noqa: S311
        else:
            delay = self._base_speed
        self._timer = self.set_timer(delay, self._reveal)

    def _reveal(self) -> None:
        self._pos += 1
        cursor = "█" if self.cursor_visible else " "
        self.update(self._full_text[: self._pos] + cursor)

        if self._pos >= len(self._full_text):
            self._finish()
            return

        self._schedule_next()

    def _blink_cursor(self) -> None:
        """Toggle the block cursor on/off for that classic CRT feel."""
        if self._done:
            return
        self.cursor_visible = not self.cursor_visible
        cursor = "█" if self.cursor_visible else " "
        self.update(self._full_text[: self._pos] + cursor)

    def _finish(self) -> None:
        self._done = True
        if self._timer is not None:
            self._timer.stop()
        if self._cursor_timer is not None:
            self._cursor_timer.stop()
        self.update(self._full_text)
        self.post_message(self.Done())
