#!/usr/bin/env python3
"""Найти в речи числа и разложить их по карточкам данных.

Графика по числам ролика — единственный вид графики, который нельзя купить
готовым ассетом, и именно она выглядит дорого. Но пока план карточек писался
руками, на реальном прогоне он просто не появлялся, и слой графики падал обратно
на рисованный декор.

Поэтому план строится из транскрипта:

* **год** (четыре цифры, 1900–2099) → карточка года;
* **модель железа** (латиница с цифрами: GTX 580, RTX 4090, M2) → карточка железа;
* **два процента в пределах ~8 секунд друг от друга** → сравнение столбиками.
  Это самая ценная карточка и самая редкая: два числа, которые автор произносит
  подряд, почти всегда и есть содержание ролика.

Одиночный процент без пары намеренно **не** превращается в сравнение: столбик
без второго столбика ничего не сравнивает и читается как украшение.
"""
from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

from skill_config import emit, utf8_stdout  # noqa: E402

YEAR = re.compile(r"^(19|20)\d{2}$")
PERCENT = re.compile(r"(\d+)\s*[,.]?\s*(\d*)\s*%")
HARDWARE = re.compile(r"^[A-Za-z]{1,6}[- ]?\d{2,4}$")
MODEL_WORD = re.compile(r"^(GTX|RTX|GT|RX|M\d|A\d{2,3}|H\d{3}|TPU|CPU|GPU)$", re.I)


def clean(word: str) -> str:
    return word.strip(" ,.!?;:«»\"()—–")


def main() -> None:
    utf8_stdout()
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("timeline", help="timeline_full.json")
    parser.add_argument("--out", required=True)
    parser.add_argument("--pair-window", type=float, default=8.0,
                        help="За сколько секунд два процента считаются парой")
    parser.add_argument("--lead", type=float, default=0.35,
                        help="На сколько карточка появляется раньше произнесённого числа")
    parser.add_argument("--max-cards", type=int, default=6)
    args = parser.parse_args()

    timeline = json.loads(Path(args.timeline).read_text(encoding="utf-8"))
    words = timeline.get("words") or []
    if not words:
        raise SystemExit(f"В {args.timeline} нет слов")

    overlays: list[dict] = []
    found: dict[str, list] = {"years": [], "percents": [], "hardware": []}

    # --- проценты. Слово может быть «15» + «,3%» двумя токенами, поэтому
    # склеиваем окно из трёх подряд и ищем образец в нём.
    for index, word in enumerate(words):
        # Склейка идёт по **сырым** словам, без clean(): запятая внутри числа —
        # это десятичный разделитель, и срезав её, «15» + «,3%» превращается в
        # «153%». Убирается только точка в конце предложения.
        window = "".join(w["word"] for w in words[index:index + 3]).rstrip(".")
        match = PERCENT.match(window)
        if not match:
            continue
        value = float(f"{match.group(1)}.{match.group(2) or 0}")
        at = float(word["start"])
        if found["percents"] and at - found["percents"][-1][0] < 0.6:
            continue
        found["percents"].append((at, value))

    for index, word in enumerate(words):
        token = clean(word["word"])
        if YEAR.match(token):
            found["years"].append((float(word["start"]), token))
        if MODEL_WORD.match(token) and index + 1 < len(words):
            nxt = clean(words[index + 1]["word"])
            if nxt.isdigit():
                found["hardware"].append((float(word["start"]), f"{token.upper()} {nxt}"))
        elif HARDWARE.match(token) and any(c.isdigit() for c in token):
            found["hardware"].append((float(word["start"]), token.upper()))

    # --- сравнение: два процента внутри окна
    used_percent_times: set = set()
    for i in range(len(found["percents"]) - 1):
        first_at, first = found["percents"][i]
        second_at, second = found["percents"][i + 1]
        if second_at - first_at > args.pair_window:
            continue
        if abs(first - second) < 0.05:
            continue
        overlays.append({
            "at": round(max(0.2, first_at - args.lead), 3),
            "effect": "comparison",
            "values": [first, second],
            "why": f"{first}% против {second}% сказаны подряд — это и есть содержание",
        })
        used_percent_times.update({first_at, second_at})
        break

    for at, token in found["years"][:1]:
        overlays.append({"at": round(max(0.2, at - args.lead), 3), "effect": "year",
                         "value": token, "why": f"произнесён год {token}"})
    for at, token in found["hardware"][:1]:
        overlays.append({"at": round(max(0.2, at - args.lead), 3), "effect": "hardware",
                         "value": token, "why": f"названо железо: {token}"})

    overlays.sort(key=lambda item: item["at"])
    overlays = overlays[:args.max_cards]

    plan = {"version": "1.0", "overlays": overlays,
            "found": {"годы": [t for _a, t in found["years"]],
                      "проценты": [v for _a, v in found["percents"]],
                      "железо": [t for _a, t in found["hardware"]]},
            "note": ("Одиночный процент без пары не превращается в сравнение: "
                     "столбик без второго столбика ничего не сравнивает.")}
    Path(args.out).write_text(json.dumps(plan, ensure_ascii=False, indent=2),
                              encoding="utf-8")
    emit({"status": "ok" if overlays else "nothing_found",
          "out": args.out, "cards": len(overlays), **plan})


if __name__ == "__main__":
    main()
