"""Читаемый манифест требований.

Проекция состояния, а не вторая копия: файл целиком пересобирается из `state.json`,
поэтому разойтись они не могут. Рендер — чистая функция: ничего не читает с диска и
ничего не пишет, запись делает вызывающий.
"""

from __future__ import annotations

import re

from .store import REQ_STATUSES, run_title

# Формулировки взяты из панели (assets/dashboard.html, REQ_RU) дословно: файл и панель
# обязаны называть статус одинаково, иначе человек читает два разных словаря.
STATUS_RU = {
    "open": "открыто",
    "in-spec": "в спеке",
    "in-task": "в таске",
    "done": "закрыто",
    "placeholder": "нужны данные",
    "deferred": "отложено",
    "dropped": "снято",
}

DROPPED_RU = "снято владельцем"

def _flat(text: object) -> str:
    """Одна строка без переносов: многострочная цитата разрывает таблицу."""
    return re.sub(r"\s+", " ", str(text or "")).strip()


def short_title(title: object) -> str:
    """Короткая шапка манифеста: имя прогона одной строкой."""
    return run_title(str(title or ""))


def _cell(text: object) -> str:
    # Порядок замен важен: слэш экранируется ПЕРВЫМ. Иначе цитата, где слэш уже стоит
    # перед чертой (`\d+|\d+`), даст чётное число слэшей перед чертой, и рендерер GFM
    # посчитает такую черту границей ячейки — хвост уезжает в соседнюю колонку.
    return _flat(text).replace("\\", "\\\\").replace("|", r"\|")


def render(state: dict) -> str:
    """Собрать текст манифеста из состояния прогона.

    Порядок строк задан идентификатором требования, а не порядком добавления:
    иначе перегенерация «целиком» превращает каждый `git diff` в шум.
    """
    reqs = sorted(state.get("requirements") or [], key=lambda r: str(r.get("id", "")))
    title = _flat(state.get("title"))
    head = short_title(title)
    slug = _flat(state.get("slug"))

    lines = [f"# Требования — {head}", ""]
    if title and title != head:
        lines += [title, ""]
    lines += [
        f"Прогон: `{slug}`",
        "",
        "_Файл собран из `state.json`. Править его руками бесполезно: "
        "источник истины один, и это состояние._",
        "",
    ]

    if reqs:
        lines += [
            "| ID | Статус | Из брифа | Основание | Таски |",
            "| --- | --- | --- | --- | --- |",
        ]
        for r in reqs:
            status = r.get("status")
            label = DROPPED_RU if status == "dropped" else STATUS_RU.get(status, _cell(status))
            quote = _cell(r.get("quote")) or "—"
            # Заметка — это «почему требование существует». Без неё в файле остаётся
            # только «что заказано», а заказ просил читать историю решений.
            basis = _cell(r.get("note")) or "—"
            tasks = _cell(", ".join(str(t) for t in (r.get("tasks") or []))) or "—"
            lines.append(f"| {_cell(r.get('id'))} | {label} | {quote} | {basis} | {tasks} |")
        lines.append("")
    else:
        lines += ["_Требований пока нет._", ""]

    dropped = [r for r in reqs if r.get("status") == "dropped"]
    if dropped:
        lines += [f"## {DROPPED_RU.capitalize()}", ""]
        for r in dropped:
            # Пометки без основания недостаточно — ради основания заказ и просил
            # читаемую историю, поэтому пустая заметка называется вслух.
            basis = _flat(r.get("note")) or "основание не записано"
            lines.append(f"- {_flat(r.get('id'))} — {basis}")
        lines.append("")

    counted = [(s, sum(1 for r in reqs if r.get("status") == s)) for s in REQ_STATUSES]
    parts = ", ".join(f"{STATUS_RU[s]} {n}" for s, n in counted if n)
    lines += ["## Итог", ""]
    lines.append(f"Требований: {len(reqs)}" + (f" — {parts}" if parts else "") + ".")

    return "\n".join(lines).rstrip() + "\n"
