"""Hourglass Timer TUI — animated ASCII hourglass with falling sand."""

import signal
import time

from rich.console import Console
from rich.live import Live
from rich.text import Text

from .input import RawInput
from .notify import play_bell, send_notification
from .themes import DARK, Theme

# Sand capacities per row (top-to-bottom of top bulb, then bottom bulb)
TOP_WIDTHS = [21, 21, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3]
BOT_WIDTHS = [3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 21, 21]
TOTAL_SAND = sum(TOP_WIDTHS)  # 162

# 3-frame falling grain cycle
NECK_FRAMES = [
    ("·", " ", " "),
    (" ", "·", " "),
    (" ", " ", "·"),
]

# 5-line tall ASCII art digits
BIG_DIGITS = {
    "0": ["█████", "█   █", "█   █", "█   █", "█████"],
    "1": ["  █  ", " ██  ", "  █  ", "  █  ", "█████"],
    "2": ["█████", "    █", "█████", "█    ", "█████"],
    "3": ["█████", "    █", "█████", "    █", "█████"],
    "4": ["█   █", "█   █", "█████", "    █", "    █"],
    "5": ["█████", "█    ", "█████", "    █", "█████"],
    "6": ["█████", "█    ", "█████", "█   █", "█████"],
    "7": ["█████", "    █", "   █ ", "  █  ", "  █  "],
    "8": ["█████", "█   █", "█████", "█   █", "█████"],
    "9": ["█████", "█   █", "█████", "    █", "█████"],
    ":": ["     ", "  █  ", "     ", "  █  ", "     "],
}

BIG_DIGIT_HEIGHT = 5
BIG_DIGIT_GAP = 1


def render_big_time(remaining_secs: float) -> list[str]:
    """Render MM:SS as large block-character text."""
    mins = int(remaining_secs) // 60
    secs = int(remaining_secs) % 60
    time_str = f"{mins:02d}:{secs:02d}"

    rows = [""] * BIG_DIGIT_HEIGHT
    for i, ch in enumerate(time_str):
        glyph = BIG_DIGITS[ch]
        for row in range(BIG_DIGIT_HEIGHT):
            if i > 0:
                rows[row] += " " * BIG_DIGIT_GAP
            rows[row] += glyph[row]
    return rows


def compute_sand(progress: float) -> tuple[list[int], list[int], bool]:
    """Return (top_fills, bot_fills, grain_active) for a given progress 0→1."""
    sand_fallen = int(progress * TOTAL_SAND)
    sand_fallen = max(0, min(TOTAL_SAND, sand_fallen))

    top_fills = list(TOP_WIDTHS)
    to_remove = sand_fallen
    for i in range(len(top_fills) - 1, -1, -1):
        if to_remove <= 0:
            break
        take = min(top_fills[i], to_remove)
        top_fills[i] -= take
        to_remove -= take

    bot_fills = [0] * len(BOT_WIDTHS)
    to_add = sand_fallen
    for i in range(len(bot_fills) - 1, -1, -1):
        if to_add <= 0:
            break
        give = min(BOT_WIDTHS[i], to_add)
        bot_fills[i] = give
        to_add -= give

    grain_active = 0 < progress < 1
    return top_fills, bot_fills, grain_active


def format_sand(width: int, fill: int) -> str:
    """Return a string of `fill` sand chars centered in `width` space."""
    sand = "░" * fill
    pad = width - fill
    left = pad // 2
    right = pad - left
    return " " * left + sand + " " * right


def build_frame(progress: float, remaining_secs: float, frame_idx: int,
                term_width: int = 80, term_height: int = 24,
                theme: Theme = DARK, label: str | None = None,
                round_info: str | None = None,
                paused: bool = False) -> Text:
    """Build one frame of the hourglass as a Rich Text object."""
    top_fills, bot_fills, grain_active = compute_sand(progress)

    BULB_W = 21

    if grain_active and not paused:
        neck_top, neck_mid, neck_bot = NECK_FRAMES[frame_idx % len(NECK_FRAMES)]
    else:
        neck_top, neck_mid, neck_bot = " ", " ", " "

    raw_lines: list[str] = []
    styled_lines: list[list[tuple[str, str]]] = []

    def add_line(parts: list[tuple[str, str]]):
        raw = "".join(content for content, _ in parts)
        raw_lines.append(raw)
        styled_lines.append(parts)

    # Label above hourglass
    if label:
        add_line([(label, theme.label)])
        add_line([("", "")])

    # Top cap
    add_line([("╔" + "═" * BULB_W + "╗", theme.cap)])

    # Top bulb rows
    for i in range(3):
        inner = format_sand(BULB_W, top_fills[i])
        add_line([("║", theme.frame), (inner, theme.sand), ("║", theme.frame)])

    # Top taper rows
    for i in range(3, len(TOP_WIDTHS)):
        w = TOP_WIDTHS[i]
        pad = (BULB_W - w) // 2
        inner = format_sand(w, top_fills[i])
        add_line([
            (" " * pad + "╲", theme.taper),
            (inner, theme.sand),
            ("╱", theme.taper),
        ])

    # Neck
    neck_pad = (BULB_W - 1) // 2
    add_line([(" " * neck_pad + "╲", theme.taper), (neck_top, theme.grain), ("╱", theme.taper)])
    add_line([(" " * neck_pad + "│", theme.taper), (neck_mid, theme.grain), ("│", theme.taper)])
    add_line([(" " * neck_pad + "╱", theme.taper), (neck_bot, theme.grain), ("╲", theme.taper)])

    # Bottom taper rows
    for i in range(len(BOT_WIDTHS) - 3):
        w = BOT_WIDTHS[i]
        pad = (BULB_W - w) // 2
        inner = format_sand(w, bot_fills[i])
        add_line([
            (" " * pad + "╱", theme.taper),
            (inner, theme.sand),
            ("╲", theme.taper),
        ])

    # Bottom bulb rows
    for i in range(len(BOT_WIDTHS) - 3, len(BOT_WIDTHS)):
        inner = format_sand(BULB_W, bot_fills[i])
        add_line([("║", theme.frame), (inner, theme.sand), ("║", theme.frame)])

    # Bottom cap
    add_line([("╚" + "═" * BULB_W + "╝", theme.cap)])

    # Big timer
    big_time_rows = render_big_time(remaining_secs)
    add_line([("", "")])
    for row in big_time_rows:
        add_line([(row, theme.timer)])

    # Round info below timer
    if round_info:
        add_line([("", "")])
        add_line([(round_info, theme.round_info)])

    # Paused indicator
    if paused:
        add_line([("", "")])
        add_line([("PAUSED (space to resume)", theme.paused)])

    # Centering
    max_content_width = max(len(line) for line in raw_lines)
    total_height = len(raw_lines)
    top_pad = max(0, (term_height - total_height) // 2)

    text = Text()
    if top_pad > 0:
        text.append("\n" * top_pad)

    for i, (raw, parts) in enumerate(zip(raw_lines, styled_lines)):
        if i > 0:
            text.append("\n")
        left_pad = max(0, (term_width - max_content_width) // 2)
        if left_pad > 0:
            text.append(" " * left_pad)
        for content, style in parts:
            if content or style:
                text.append(content, style=style)

    return text


def build_small_frame(remaining_secs: float) -> Text:
    """Fallback for small terminals: just show the countdown."""
    mins = int(remaining_secs) // 60
    secs = int(remaining_secs) % 60
    text = Text()
    text.append(f"  ⏳ {mins:02d}:{secs:02d}", style="bold bright_white")
    return text


def run_timer(duration_secs: float, label: str | None = None,
              theme: Theme | None = None, silent: bool = False,
              notify: bool = True, round_info: str | None = None) -> str:
    """Run the hourglass timer. Returns 'completed' or 'interrupted'."""
    if theme is None:
        theme = DARK

    console = Console()
    interrupted = False

    def handle_sigint(sig, frame):
        nonlocal interrupted
        interrupted = True

    signal.signal(signal.SIGINT, handle_sigint)

    start = time.monotonic()
    frame_idx = 0
    paused = False
    pause_offset = 0.0
    pause_start = 0.0

    try:
        with RawInput() as raw_input, Live(
            console=console,
            refresh_per_second=8,
            screen=True,
            transient=True,
        ) as live:
            while not interrupted:
                # Check for key input
                key = raw_input.get_key(timeout=0.0)
                if key == " ":
                    if paused:
                        pause_offset += time.monotonic() - pause_start
                        paused = False
                    else:
                        pause_start = time.monotonic()
                        paused = True

                if paused:
                    elapsed = pause_start - start - pause_offset
                else:
                    elapsed = time.monotonic() - start - pause_offset

                remaining = max(0.0, duration_secs - elapsed)
                progress = min(1.0, elapsed / duration_secs) if duration_secs > 0 else 1.0

                size = console.size
                if size.width < 30 or size.height < 38:
                    frame = build_small_frame(remaining)
                else:
                    frame = build_frame(
                        progress, remaining,
                        frame_idx if not paused else frame_idx,
                        size.width, size.height,
                        theme=theme, label=label,
                        round_info=round_info, paused=paused,
                    )

                live.update(frame)
                if not paused:
                    frame_idx += 1

                if progress >= 1.0:
                    break

                time.sleep(0.125)

            # Completion
            if not interrupted and progress >= 1.0:
                if not silent:
                    play_bell(console)

                if notify:
                    title = label or "Hourglass"
                    send_notification(title, "Time's up!")

                for _ in range(3):
                    size = console.size
                    msg = "TIME'S UP!"
                    h_pad = " " * max(0, (size.width - len(msg)) // 2)
                    v_pad = "\n" * max(0, size.height // 2)
                    text = Text()
                    text.append(v_pad + h_pad + msg, style="bold red on white")
                    live.update(text)
                    time.sleep(0.4)

                    size = console.size
                    live.update(build_frame(
                        1.0, 0, frame_idx,
                        size.width, size.height,
                        theme=theme, label=label,
                        round_info=round_info,
                    ))
                    time.sleep(0.4)
                    frame_idx += 1

    except KeyboardInterrupt:
        interrupted = True

    signal.signal(signal.SIGINT, signal.SIG_DFL)

    if interrupted:
        console.print("\n  [bold yellow]Timer interrupted.[/bold yellow]\n")
        return "interrupted"

    console.print("\n  [bold green]Timer complete![/bold green]\n")
    return "completed"
