"""textual dashboard for absolutelyright.

one screen, no scrolling walls of text: a sidebar of sections and a detail
pane. number keys narrow the time window without leaving the app.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any, ClassVar

from textual import work
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Horizontal, Vertical
from textual.widgets import (
    ContentSwitcher,
    DataTable,
    Digits,
    Footer,
    Label,
    ListItem,
    ListView,
    Sparkline,
    Static,
)
from textual_plotext import PlotextPlot

from absolutelyright import _stats
from absolutelyright._records import DEFAULT_CLAUDE_DIR, Session, load_sessions

SECTIONS = (
    "overview",
    "sessions",
    "prompts",
    "corrections",
    "vibe",
    "projects",
    "tools",
    "hours",
)

TERRACOTTA = "#d97757"
TERRACOTTA_RGB = (217, 119, 87)


def _compact(n: int | float) -> str:
    for threshold, suffix in ((1_000_000_000, "b"), (1_000_000, "m"), (1_000, "k")):
        if n >= threshold:
            return f"{n / threshold:.1f}{suffix}"
    return str(round(n))


class AbsolutelyApp(App[None]):
    """analytics over your claude code sessions, one screen at a time."""

    TITLE = "absolutely right."

    CSS = f"""
    Screen {{
        background: #16120e;
        color: #e8dcc8;
    }}

    #hero {{
        height: 4;
        padding: 1 2 0 2;
        background: #1f1913;
        border-bottom: solid {TERRACOTTA};
    }}

    #title {{
        text-style: bold;
        color: {TERRACOTTA};
    }}

    #stats {{
        color: #97896f;
    }}

    #main {{
        height: 1fr;
    }}

    #sidebar {{
        width: 20;
        background: #1f1913;
        border-right: solid #3a2e22;
    }}

    #sidebar ListView {{
        height: 1fr;
        padding: 1;
        background: #1f1913;
    }}

    #sidebar ListItem {{
        padding: 0 1;
        background: #1f1913;
        color: #97896f;
    }}

    #sidebar ListItem.--highlight {{
        background: {TERRACOTTA};
        color: #16120e;
        text-style: bold;
    }}

    #detail {{
        width: 1fr;
        padding: 1 2;
    }}

    DataTable {{
        height: 1fr;
        background: #16120e;
    }}

    DataTable > .datatable--header {{
        background: #16120e;
        color: {TERRACOTTA};
        text-style: bold;
    }}

    DataTable > .datatable--cursor {{
        background: #3a2e22;
    }}

    .muted {{
        color: #97896f;
        padding: 0 0 1 0;
    }}

    #overview {{
        padding: 1 2;
    }}

    #digits-row {{
        height: 6;
    }}

    .stat {{
        width: 1fr;
        height: 6;
    }}

    .stat Digits {{
        color: {TERRACOTTA};
        width: auto;
    }}

    .stat Label {{
        color: #97896f;
    }}

    #spark-caption {{
        padding: 1 0 0 0;
        color: #97896f;
    }}

    Sparkline {{
        height: 3;
        margin: 0 0 1 0;
    }}

    Sparkline > .sparkline--max-color {{
        color: {TERRACOTTA};
    }}

    Sparkline > .sparkline--min-color {{
        color: #3a2e22;
    }}

    #vibe Horizontal {{
        height: 1fr;
    }}

    .vibe-col {{
        width: 1fr;
        padding: 0 2 0 0;
    }}

    .vibe-col > Label {{
        text-style: bold;
        color: {TERRACOTTA};
        padding: 0 0 1 0;
    }}

    PlotextPlot {{
        height: 1fr;
    }}
    """

    BINDINGS: ClassVar = [
        Binding("q", "quit", "quit"),
        Binding("7", "window(7)", "week"),
        Binding("3", "window(30)", "month"),
        Binding("a", "window(None)", "all"),
    ]

    def __init__(
        self, claude_dir: Path = DEFAULT_CLAUDE_DIR, days: int | None = None
    ) -> None:
        super().__init__()
        self.claude_dir = claude_dir
        self.days = days
        self.sessions: list[Session] = []

    def compose(self) -> ComposeResult:
        with Vertical(id="hero"):
            yield Static("absolutely right.", id="title")
            yield Static("loading transcripts…", id="stats")
        with Horizontal(id="main"):
            with Vertical(id="sidebar"):
                yield ListView(
                    *[ListItem(Label(name), id=f"nav-{name}") for name in SECTIONS],
                    id="nav",
                )
            with ContentSwitcher(initial="overview", id="detail"):
                with Vertical(id="overview"):
                    with Horizontal(id="digits-row"):
                        for stat in ("prompts", "interrupts", "prs", "hours-active"):
                            with Vertical(classes="stat", id=f"stat-{stat}"):
                                yield Digits("0", id=f"digits-{stat}")
                                yield Label(stat.replace("-", " "))
                    yield Static("", id="spark-caption")
                    yield Sparkline([0], summary_function=max, id="spark")
                    yield Static("", id="model-mix", classes="muted")
                    yield Static("", id="token-line", classes="muted")
                yield DataTable(id="sessions", cursor_type="row", zebra_stripes=True)
                yield DataTable(id="prompts", cursor_type="row", zebra_stripes=True)
                with Vertical(id="corrections"):
                    yield Label("", id="corrections-rate", classes="muted")
                    yield DataTable(
                        id="corrections-table", cursor_type="row", zebra_stripes=True
                    )
                with Vertical(id="vibe"), Horizontal():
                    with Vertical(classes="vibe-col"):
                        yield Label("you said")
                        yield DataTable(id="vibe-you", cursor_type="row")
                    with Vertical(classes="vibe-col"):
                        yield Label("claude said")
                        yield DataTable(id="vibe-claude", cursor_type="row")
                yield DataTable(id="projects", cursor_type="row", zebra_stripes=True)
                yield DataTable(id="tools", cursor_type="row", zebra_stripes=True)
                yield PlotextPlot(id="hours")
        yield Footer()

    def on_mount(self) -> None:
        self.query_one("#sessions", DataTable).add_columns(
            "when", "title", "project", "active", "prompts", "stops"
        )
        self.query_one("#prompts", DataTable).add_columns("count", "prompt")
        self.query_one("#corrections-table", DataTable).add_columns("project", "prompt")
        self.query_one("#vibe-you", DataTable).add_columns("word", "count")
        self.query_one("#vibe-claude", DataTable).add_columns("tic", "count")
        self.query_one("#projects", DataTable).add_columns(
            "project", "sessions", "prompts", "stops", "output tokens"
        )
        self.query_one("#tools", DataTable).add_columns("count", "tool")
        self._load()

    def on_list_view_highlighted(self, event: ListView.Highlighted) -> None:
        if event.item is not None and event.item.id is not None:
            self.query_one(
                "#detail", ContentSwitcher
            ).current = event.item.id.removeprefix("nav-")

    def action_window(self, days: int | None) -> None:
        self.days = days
        self.query_one("#stats", Static).update("reloading…")
        self._load()

    @work(thread=True, exclusive=True)
    def _load(self) -> None:
        sessions = load_sessions(self.claude_dir, days=self.days)
        self.call_from_thread(self._render, sessions)

    def _render(self, sessions: list[Session]) -> None:
        self.sessions = sessions
        stats = _stats.overview(sessions)
        isms = dict(_stats.claudeisms(sessions))

        window = f"last {self.days}d" if self.days else "all local history"
        span = (
            f"   {stats['first'][:10]} → {stats['last'][:10]}" if stats["first"] else ""
        )
        right = isms.get("you're absolutely right", 0)
        self.query_one("#stats", Static).update(
            f"{window}{span}   ·   claude was absolutely right {right} times"
        )

        self._render_overview(sessions, stats)

        table = self.query_one("#sessions", DataTable)
        table.clear()
        for row in _stats.session_rows(sessions):
            table.add_row(
                row["when"],
                row["title"][:60],
                row["project"],
                f"{row['activeMinutes']}m",
                str(row["prompts"]),
                str(row["interrupts"]),
            )

        prompts = self.query_one("#prompts", DataTable)
        prompts.clear()
        for text, count in _stats.repeated_prompts(sessions, top=50, min_count=2):
            prompts.add_row(str(count), text.replace("\n", " ")[:120])

        corrections = _stats.corrections(sessions)
        total = len(_stats.all_prompts(sessions))
        rate = 100 * len(corrections) / total if total else 0
        self.query_one("#corrections-rate", Label).update(
            f"{len(corrections)} of {total} prompts ({rate:.1f}%) open with no / wait / stop / actually"
        )
        ctable = self.query_one("#corrections-table", DataTable)
        ctable.clear()
        for p in corrections:
            ctable.add_row(p.project, p.text.splitlines()[0][:120])

        you = self.query_one("#vibe-you", DataTable)
        you.clear()
        for word, count in _stats.vibe(sessions):
            you.add_row(word, str(count))
        claude = self.query_one("#vibe-claude", DataTable)
        claude.clear()
        for tic, count in _stats.claudeisms(sessions):
            claude.add_row(tic, str(count))

        projects = self.query_one("#projects", DataTable)
        projects.clear()
        for row in _stats.by_project(sessions):
            projects.add_row(
                row["project"],
                str(row["sessions"]),
                str(row["prompts"]),
                str(row["interrupts"]),
                f"{row['outputTokens']:,}",
            )

        tools = self.query_one("#tools", DataTable)
        tools.clear()
        for name, count in _stats.tool_counts(sessions):
            tools.add_row(str(count), name)

        self._render_hours(sessions)

    def _render_overview(self, sessions: list[Session], stats: dict[str, Any]) -> None:
        hours_active = sum(s.active_seconds for s in sessions) / 3600
        self.query_one("#digits-prompts", Digits).update(_compact(stats["prompts"]))
        self.query_one("#digits-interrupts", Digits).update(
            _compact(stats["interrupts"])
        )
        self.query_one("#digits-prs", Digits).update(_compact(stats["prsShipped"]))
        self.query_one("#digits-hours-active", Digits).update(f"{hours_active:.0f}")

        days = min(self.days or 30, 30)
        self.query_one("#spark-caption", Static).update(
            f"prompts per day · last {days} days"
        )
        spark = self.query_one("#spark", Sparkline)
        spark.data = _stats.daily_counts(sessions, days=days)

        mix = _stats.model_mix(sessions)
        total_turns = sum(n for _, n in mix) or 1
        mix_text = "   ".join(
            f"{name.removeprefix('claude-')} {100 * n / total_turns:.0f}%"
            for name, n in mix[:4]
        )
        self.query_one("#model-mix", Static).update(f"models   {mix_text}")
        self.query_one("#token-line", Static).update(
            f"tokens   {_compact(stats['outputTokens'])} out"
            f" · {_compact(stats['cacheReadTokens'])} cache read"
            f" · {stats['sessions']} sessions across {stats['projects']} projects"
        )

    def _render_hours(self, sessions: list[Session]) -> None:
        histogram = _stats.hour_histogram(sessions)
        plot = self.query_one("#hours", PlotextPlot)
        plt = plot.plt
        plt.clear_data()
        hours = list(range(24))
        plt.bar(
            [f"{h:02d}" for h in hours],
            [histogram.get(h, 0) for h in hours],
            color=TERRACOTTA_RGB,
            marker="hd",
        )
        plt.title("prompts by local hour")
        plot.refresh()


def run(claude_dir: Path = DEFAULT_CLAUDE_DIR, days: int | None = None) -> None:
    AbsolutelyApp(claude_dir=claude_dir, days=days).run()
