"""Interactive REPL and one-shot mode for the Keiro CLI.

Handles three invocation patterns:

- ``keiro`` — interactive streaming chat REPL
- ``keiro "prompt"`` — one-shot: print response, exit
- ``echo ctx | keiro "prompt"`` — pipe stdin as context

When stdout is not a TTY (piped to another program), output is raw text
with no decorations, colors, or latency display.
"""

from __future__ import annotations

import sys
import threading
import time
from typing import Iterator

from keiro.cli.animations import _SPINNER_FRAMES, _shimmer_ansi, animations_enabled


def _is_tty_out() -> bool:
    return sys.stdout.isatty()


def _is_tty_in() -> bool:
    return sys.stdin.isatty()


def _read_stdin_context() -> str | None:
    """Read piped stdin content. Returns None if stdin is a TTY."""
    if _is_tty_in():
        return None
    try:
        return sys.stdin.read()
    except (OSError, UnicodeDecodeError):
        return None


def _spinner_loop(stop: threading.Event, start: float) -> None:
    """Write shimmer-animated spinner frames to stdout until *stop* is set.

    Combines a braille spinner character, accumulating dots, a shimmer
    highlight sweep across the label, and an elapsed counter after 2 s.
    """
    frame = 0
    dot_cycle = 0.6  # seconds per full dot cycle (1 → 2 → 3 → 1)
    shimmer_cycle = 2.0  # seconds per full shimmer sweep
    while not stop.wait(0.06):
        char = _SPINNER_FRAMES[frame % len(_SPINNER_FRAMES)]
        elapsed = time.monotonic() - start

        # Accumulating dots: Thinking. → Thinking.. → Thinking...
        dot_count = int((elapsed % dot_cycle) / dot_cycle * 3) + 1
        dots = "." * dot_count + " " * (3 - dot_count)
        label = f"Thinking{dots}"

        if elapsed > 2.0:
            label += f" ({elapsed:.0f}s)"

        shimmer_phase = (elapsed % shimmer_cycle) / shimmer_cycle
        styled = _shimmer_ansi(label, shimmer_phase)

        sys.stdout.write(f"\r\033[2m{char}\033[0m {styled}\033[K")
        sys.stdout.flush()
        frame += 1


def _stream_response(
    chunks: Iterator[str],
    *,
    decorated: bool,
) -> tuple[str, float, float | None]:
    """Consume a streaming response, printing chunks as they arrive.

    When running in a TTY, shows a spinner while waiting for the first
    chunk (during EB1 candidate execution), then prints tokens as they
    stream from the judge synthesis.

    Args:
        chunks: Iterator of text chunks from the API.
        decorated: If True, show spinner and formatting.

    Returns:
        Tuple of (full response text, elapsed seconds, TTFT seconds or None).
    """
    start = time.monotonic()
    full_text: list[str] = []
    iterator = iter(chunks)
    ttft: float | None = None

    # Phase 1: Wait for first chunk with optional spinner.
    show_spinner = decorated and animations_enabled()
    stop: threading.Event | None = None
    spinner_thread: threading.Thread | None = None

    if show_spinner:
        stop = threading.Event()
        spinner_thread = threading.Thread(
            target=_spinner_loop,
            args=(stop, start),
            daemon=True,
            name="keiro-thinking",
        )
        spinner_thread.start()

    try:
        first_chunk = next(iterator, None)
    finally:
        if stop is not None:
            stop.set()
        if spinner_thread is not None:
            spinner_thread.join(timeout=1.0)
        if show_spinner:
            sys.stdout.write("\r\033[K")
            sys.stdout.flush()

    # Phase 2: Stream response tokens.
    if first_chunk is not None:
        ttft = time.monotonic() - start
        full_text.append(first_chunk)
        sys.stdout.write(first_chunk)
        sys.stdout.flush()

    for chunk in iterator:
        full_text.append(chunk)
        sys.stdout.write(chunk)
        sys.stdout.flush()

    elapsed = time.monotonic() - start

    if decorated:
        sys.stdout.write("\n")
        sys.stdout.flush()

    return "".join(full_text), elapsed, ttft


def _print_latency(elapsed: float, *, ttft: float | None = None) -> None:
    """Print latency in dim text, optionally with TTFT."""
    parts: list[str] = []
    if ttft is not None and ttft >= 1.0:
        parts.append(f"first token {ttft:.1f}s")
    if elapsed < 1.0:
        parts.append(f"{elapsed * 1000:.0f}ms")
    else:
        parts.append(f"{elapsed:.1f}s")
    label = " | ".join(parts)
    sys.stdout.write(f"\033[2m{label}\033[0m\n")
    sys.stdout.flush()


def one_shot(prompt: str, *, model: str, context: str | None = None) -> int:
    """Run a single prompt, stream the response, and exit.

    Args:
        prompt: User prompt text.
        model: Model identifier.
        context: Optional piped context to prepend as a system message.

    Returns:
        Exit code.
    """
    from keiro.model_api import ModelsAPI

    decorated = _is_tty_out()
    api = ModelsAPI()

    try:
        kwargs: dict[str, object] = {}
        if context:
            kwargs["instructions"] = context

        chunks = api.responses_stream(model, input=prompt, **kwargs)
        _, elapsed, ttft = _stream_response(chunks, decorated=decorated)

        if decorated:
            _print_latency(elapsed, ttft=ttft)
        else:
            # Ensure trailing newline for piped output.
            sys.stdout.write("\n")
            sys.stdout.flush()
    except KeyboardInterrupt:
        sys.stdout.write("\n")
        return 130
    except Exception as exc:
        sys.stderr.write(f"error: {exc}\n")
        return 1
    finally:
        api.close()

    return 0


def repl(*, model: str) -> int:
    """Run an interactive streaming chat REPL.

    Args:
        model: Default model identifier.

    Returns:
        Exit code.
    """
    from keiro.model_api import ModelsAPI

    api = ModelsAPI()

    _print_welcome(model)

    try:
        while True:
            try:
                prompt = input(f"\033[1m>\033[0m ")
            except EOFError:
                sys.stdout.write("\n")
                break

            prompt = prompt.strip()
            if not prompt:
                continue

            if prompt.lower() in ("exit", "quit", "/q"):
                break

            sys.stdout.write("\n")
            try:
                chunks = api.responses_stream(model, input=prompt)
                _, elapsed, ttft = _stream_response(chunks, decorated=True)
                _print_latency(elapsed, ttft=ttft)
            except Exception as exc:
                sys.stderr.write(f"error: {exc}\n")
            sys.stdout.write("\n")

    except KeyboardInterrupt:
        sys.stdout.write("\n")
    finally:
        api.close()

    return 0


def _print_welcome(model: str) -> None:
    """Print a minimal welcome banner."""
    sys.stdout.write(f"\033[1mkeiro\033[0m \033[2m({model})\033[0m\n")
    sys.stdout.write("\033[2mType a prompt. Ctrl-C or 'exit' to quit.\033[0m\n\n")
    sys.stdout.flush()
