import random
import time

import pyautogui
from rich.segment import Segment
from rich.style import Style
from textual.app import App, ComposeResult
from textual.containers import Horizontal, Vertical
from textual.strip import Strip
from textual.timer import Timer
from textual.widget import Widget
from textual.widgets import Button, Footer, Static
from textual.worker import Worker, WorkerState, get_current_worker

pyautogui.FAILSAFE = False

ASCII_TITLE = r"""
       __ __________ ______   ___
      / /  _/ ____/ ____/ /   /   |
 __  / // // / __/ / __/ /   / /| |
/ /_/ // // /_/ / /_/ / /___/ ___ |
\____/___/\____/\____/_____/_/  |_|
"""

CHARS = "abcdefghijklmnopqrstuvwxyz0123456789@#$%&*"


class MatrixRain(Widget):
    """Matrix-style falling character rain."""

    DEFAULT_CSS = """
    MatrixRain {
        width: 1fr;
        height: 1fr;
    }
    """

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
        self._columns: list[dict] | None = None
        self._frame = 0

    def on_mount(self) -> None:
        self.set_interval(1 / 15, self._tick)

    def on_resize(self) -> None:
        self._columns = None

    def _tick(self) -> None:
        self._frame += 1
        cols = self._ensure_columns()
        for col in cols:
            if self._frame % col["speed"] == 0:
                col["pos"] += 1
                if col["pos"] - col["length"] > self.size.height:
                    col["pos"] = -random.randint(0, self.size.height)
                    col["speed"] = random.randint(1, 3)
                    col["length"] = random.randint(4, max(5, self.size.height // 2))
        self.refresh()

    def _ensure_columns(self) -> list[dict]:
        if self._columns is None or len(self._columns) != self.size.width:
            self._columns = [
                {
                    "pos": random.randint(-self.size.height, self.size.height),
                    "speed": random.randint(1, 3),
                    "length": random.randint(4, max(5, self.size.height // 2)),
                    "chars": [random.choice(CHARS) for _ in range(max(1, self.size.height))],
                }
                for _ in range(self.size.width)
            ]
        return self._columns

    def render_line(self, y: int) -> Strip:
        columns = self._ensure_columns()
        segments: list[Segment] = []
        for col in columns:
            head = col["pos"]
            tail = head - col["length"]
            if y == head:
                ch = random.choice(CHARS)
                segments.append(Segment(ch, Style(color="bright_white", bold=True)))
            elif tail < y < head:
                distance = head - y
                ratio = distance / max(1, col["length"])
                if ratio < 0.3:
                    color = "bright_green"
                elif ratio < 0.6:
                    color = "green"
                else:
                    color = "color(22)"
                ch = col["chars"][y % len(col["chars"])]
                if random.random() < 0.05:
                    ch = random.choice(CHARS)
                    col["chars"][y % len(col["chars"])] = ch
                segments.append(Segment(ch, Style(color=color)))
            else:
                segments.append(Segment(" "))
        return Strip(segments, self.size.width)


class JigglaApp(App):
    CSS = """
    Screen {
        align: center middle;
        layers: base overlay;
    }

    #home {
        width: auto;
        height: auto;
        align: center middle;
    }

    #title {
        text-align: center;
        color: $accent;
        margin-bottom: 1;
    }

    #rain {
        layer: base;
        display: none;
    }

    #overlay {
        layer: overlay;
        width: 100%;
        height: 100%;
        align: center middle;
        background: transparent;
    }

    #overlay-box {
        width: auto;
        height: auto;
        align: center middle;
        background: $surface 60%;
        padding: 2 4;
        border: heavy $accent;
    }

    #status {
        width: 100%;
        text-align: center;
        color: ansi_bright_green;
        text-style: bold;
        margin-bottom: 1;
    }

    #timer {
        width: 100%;
        text-align: center;
        color: ansi_green;
        margin-bottom: 1;
    }

    .buttons {
        width: auto;
        height: auto;
        align: center middle;
    }

    Button {
        margin: 1 2;
        min-width: 16;
    }
    """

    BINDINGS = [
        ("q", "quit", "Quit"),
    ]

    def __init__(self) -> None:
        super().__init__()
        self._jiggle_worker: Worker | None = None
        self._timer_interval: Timer | None = None
        self._start_time: float = 0.0

    def compose(self) -> ComposeResult:
        # Home screen
        with Vertical(id="home"):
            yield Static(ASCII_TITLE, id="title")
            with Horizontal(classes="buttons"):
                yield Button("Start", id="start", variant="success")
                yield Button("Exit", id="exit", variant="error")
        # Running screen — matrix rain + floating overlay
        yield MatrixRain(id="rain")
        with Vertical(id="overlay"):
            with Vertical(id="overlay-box"):
                yield Static("Jiggling...", id="status")
                yield Static("0:00", id="timer")
                with Horizontal(classes="buttons"):
                    yield Button("Stop", id="stop", variant="warning")
        yield Footer()

    def on_mount(self) -> None:
        self.query_one("#rain").display = False
        self.query_one("#overlay").display = False

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "start":
            self._show_running()
        elif event.button.id == "stop":
            self._show_home()
        elif event.button.id == "exit":
            self.exit()

    def _show_running(self) -> None:
        self.query_one("#home").display = False
        self.query_one("#rain").display = True
        self.query_one("#overlay").display = True
        self._start_time = time.monotonic()
        self._jiggle_worker = self.run_worker(self._jiggle, thread=True, exclusive=True)
        self._timer_interval = self.set_interval(1, self._update_timer)

    def _show_home(self) -> None:
        if self._jiggle_worker is not None:
            self._jiggle_worker.cancel()
            self._jiggle_worker = None
        if self._timer_interval is not None:
            self._timer_interval.stop()
            self._timer_interval = None
        self.query_one("#rain").display = False
        self.query_one("#overlay").display = False
        self.query_one("#home").display = True
        self.query_one("#timer", Static).update("0:00")

    def _update_timer(self) -> None:
        if self._jiggle_worker is None or self._jiggle_worker.state != WorkerState.RUNNING:
            return
        elapsed = int(time.monotonic() - self._start_time)
        minutes, seconds = divmod(elapsed, 60)
        self.query_one("#timer", Static).update(f"{minutes}:{seconds:02d}")

    def _jiggle(self) -> None:
        """Runs in a thread worker. Jiggles mouse 1px left and back every ~2s."""
        worker = get_current_worker()
        while not worker.is_cancelled:
            current_x, current_y = pyautogui.position()
            pyautogui.moveTo(current_x - 1, current_y)
            time.sleep(0.1)
            pyautogui.moveTo(current_x, current_y)
            for _ in range(20):
                if worker.is_cancelled:
                    return
                time.sleep(0.1)


def main() -> None:
    app = JigglaApp()
    app.run()


if __name__ == "__main__":
    main()
