"""aggregations over parsed sessions."""

from __future__ import annotations

import re
from collections import Counter
from datetime import UTC, date, datetime, timedelta
from typing import Any

from absolutelyright._records import Prompt, Session

CORRECTION_RE = re.compile(
    r"^(no|nope|wait|stop|actually|wrong|not that|undo|revert|that's not)\b", re.I
)

MAX_REPEAT_LEN = 200  # longer prompts are never literal repeats worth surfacing


def all_prompts(sessions: list[Session]) -> list[Prompt]:
    return [p for s in sessions for p in s.prompts]


def overview(sessions: list[Session]) -> dict[str, Any]:
    prompts = all_prompts(sessions)
    timestamps = sorted(p.timestamp for p in prompts if p.timestamp)
    return {
        "sessions": len(sessions),
        "prompts": len(prompts),
        "interrupts": sum(s.interrupts for s in sessions),
        "toolCalls": sum(sum(s.tool_calls.values()) for s in sessions),
        "outputTokens": sum(s.output_tokens for s in sessions),
        "cacheReadTokens": sum(s.cache_read_tokens for s in sessions),
        "prsShipped": len({url for s in sessions for url in s.pr_urls}),
        "projects": len({s.project for s in sessions}),
        "first": timestamps[0].isoformat() if timestamps else None,
        "last": timestamps[-1].isoformat() if timestamps else None,
    }


def repeated_prompts(
    sessions: list[Session], top: int = 15, min_count: int = 2
) -> list[tuple[str, int]]:
    counts = Counter(
        p.text.lower() for p in all_prompts(sessions) if len(p.text) <= MAX_REPEAT_LEN
    )
    return [(text, n) for text, n in counts.most_common(top) if n >= min_count]


def corrections(sessions: list[Session]) -> list[Prompt]:
    return [p for p in all_prompts(sessions) if CORRECTION_RE.match(p.text)]


def by_project(sessions: list[Session]) -> list[dict[str, Any]]:
    rows: dict[str, dict[str, Any]] = {}
    for s in sessions:
        row = rows.setdefault(
            s.project,
            {
                "project": s.project,
                "sessions": 0,
                "prompts": 0,
                "interrupts": 0,
                "outputTokens": 0,
            },
        )
        row["sessions"] += 1
        row["prompts"] += len(s.prompts)
        row["interrupts"] += s.interrupts
        row["outputTokens"] += s.output_tokens
    return sorted(rows.values(), key=lambda r: r["prompts"], reverse=True)


def by_parent(sessions: list[Session]) -> list[tuple[str, int]]:
    """prompts grouped by the first path segment of each project (forge, org, ...)."""
    totals: Counter[str] = Counter()
    for s in sessions:
        totals[s.project.split("/")[0]] += len(s.prompts)
    return totals.most_common()


def tool_counts(sessions: list[Session]) -> list[tuple[str, int]]:
    totals: Counter[str] = Counter()
    for s in sessions:
        totals.update(s.tool_calls)
    return totals.most_common()


def hour_histogram(sessions: list[Session]) -> dict[int, int]:
    """prompts by local hour of day."""
    hours: Counter[int] = Counter()
    for p in all_prompts(sessions):
        if p.timestamp:
            hours[p.timestamp.astimezone().hour] += 1
    return dict(sorted(hours.items()))


def weekday_histogram(sessions: list[Session]) -> list[tuple[str, int]]:
    """prompts by local weekday, monday first."""
    days = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
    counts: Counter[int] = Counter()
    for p in all_prompts(sessions):
        if p.timestamp:
            counts[p.timestamp.astimezone().weekday()] += 1
    return [(days[i], counts.get(i, 0)) for i in range(7)]


def word_counts(
    sessions: list[Session], who: str, top: int = 50
) -> list[tuple[str, int]]:
    """raw word frequencies — no stop words, no curation. who: 'you' | 'claude'."""
    totals: Counter[str] = Counter()
    for s in sessions:
        totals.update(s.your_words if who == "you" else s.claude_words)
    return totals.most_common(top)


def distinctive_words(
    sessions: list[Session], who: str, top: int = 40, min_count: int = 10
) -> list[tuple[str, int, float]]:
    """words used at a higher rate by one side than the other. the opposite
    corpus is the baseline — no stop-word list needed, function words score
    ~1x and drop out. returns (word, count, rate_ratio)."""
    mine: Counter[str] = Counter()
    theirs: Counter[str] = Counter()
    for s in sessions:
        mine.update(s.your_words if who == "you" else s.claude_words)
        theirs.update(s.claude_words if who == "you" else s.your_words)
    n_mine, n_theirs = sum(mine.values()) or 1, sum(theirs.values()) or 1
    scored = [
        (w, c, (c / n_mine) / ((theirs.get(w, 0) + 0.5) / n_theirs))
        for w, c in mine.items()
        if c >= min_count
    ]
    scored.sort(key=lambda t: t[2], reverse=True)
    return scored[:top]


def phrase_counts(
    sessions: list[Session], who: str, n: int = 3, top: int = 50
) -> list[tuple[str, int]]:
    """raw n-word sequence frequencies, verbatim. n in PHRASE_LENGTHS."""
    totals: Counter[str] = Counter()
    for s in sessions:
        totals.update((s.your_phrases if who == "you" else s.claude_phrases)[n])
    return totals.most_common(top)


def phrase_children(
    sessions: list[Session], who: str, min_count: int = 2, per_node: int = 50
) -> dict[tuple[str, ...], list[tuple[str, int]]]:
    """prefix-tree view of the n-gram counts: maps each word prefix to its
    observed continuations, ranked. () maps to root words. built purely from
    the raw counters — min_count only hides the singleton long tail."""
    maps: dict[tuple[str, ...], Counter[str]] = {}

    def add(prefix: tuple[str, ...], word: str, count: int) -> None:
        maps.setdefault(prefix, Counter())[word] += count

    for s in sessions:
        words = s.your_words if who == "you" else s.claude_words
        phrases = s.your_phrases if who == "you" else s.claude_phrases
        for word, count in words.items():
            add((), word, count)
        for counter in phrases.values():
            for phrase, count in counter.items():
                tokens = tuple(phrase.split(" "))
                add(tokens[:-1], tokens[-1], count)

    return {
        prefix: [(w, c) for w, c in counter.most_common(per_node) if c >= min_count]
        for prefix, counter in maps.items()
    }


def absolutely_right(sessions: list[Session]) -> int:
    """the namesake: how many times claude said it, straight from the phrase counts."""
    return sum(s.claude_phrases[3]["you're absolutely right"] for s in sessions)


def images_pasted(sessions: list[Session]) -> int:
    return sum(s.images for s in sessions)


def model_mix(sessions: list[Session]) -> list[tuple[str, int]]:
    totals: Counter[str] = Counter()
    for s in sessions:
        totals.update(s.models)
    return totals.most_common()


def slash_counts(sessions: list[Session]) -> list[tuple[str, int]]:
    totals: Counter[str] = Counter()
    for s in sessions:
        totals.update(s.slash_commands)
    return totals.most_common()


def shipped(sessions: list[Session]) -> list[dict[str, str]]:
    """unique PRs opened, most recent project first."""
    epoch = datetime.min.replace(tzinfo=UTC)
    seen: dict[str, str] = {}
    for s in sorted(
        sessions, key=lambda s: s.last_ts or s.first_ts or epoch, reverse=True
    ):
        for url in sorted(s.pr_urls):
            seen.setdefault(url, s.project)
    return [{"url": url, "project": project} for url, project in seen.items()]


def humanize_when(ts: datetime) -> str:
    """'today 04:30', 'yesterday 23:10', or 'tue jun 09'."""
    local = ts.astimezone()
    delta = (date.today() - local.date()).days
    if delta == 0:
        return f"today {local:%H:%M}"
    if delta == 1:
        return f"yesterday {local:%H:%M}"
    return local.strftime("%a %b %d").lower()


def session_rows(sessions: list[Session]) -> list[dict[str, Any]]:
    """one row per session for the sessions browser, most recent first."""
    rows = []
    for s in sessions:
        if s.first_ts is None:
            continue
        rows.append(
            {
                "when": humanize_when(s.first_ts),
                "title": s.title or "(untitled)",
                "project": s.project,
                "activeMinutes": round(s.active_seconds / 60),
                "prompts": len(s.prompts),
                "interrupts": s.interrupts,
                "outputTokens": s.output_tokens,
                "_sort": s.first_ts,
            }
        )
    rows.sort(key=lambda r: r["_sort"], reverse=True)
    for row in rows:
        del row["_sort"]
    return rows


def daily_counts(sessions: list[Session], days: int = 30) -> list[int]:
    """prompts per day for the trailing window, oldest first (sparkline food)."""
    today = date.today()
    counts: Counter[date] = Counter()
    for p in all_prompts(sessions):
        if p.timestamp:
            counts[p.timestamp.astimezone().date()] += 1
    return [counts.get(today - timedelta(days=i), 0) for i in range(days - 1, -1, -1)]
