#!/usr/bin/env python3
"""ORCH-011 (ADR-001 D4): сборка `.pptx` из слайдо-источника витрины.

Источник истины — `docs/overview/presentation.md` (машинно-парсимая
слайдо-структура: `## Слайд N: Заголовок` + тезисы `- ...` + опциональная
строка `> Визуал: ...`). Скрипт собирает редактируемую PowerPoint-презентацию
в тёмном дизайне (D-1 Владельца): тёмный фон, светлый текст, один акцентный
цвет, системные шрифты с полной кириллицей.

Канон (D4/D5):
- запуск ТОЛЬКО вне рантайма конвейера (host/dev venv, явный запуск человеком —
  паттерн ORCH-009); `python-pptx` НЕ входит в requirements*/Dockerfile (NFR-2);
- `parse_slides` — чистая stdlib-функция БЕЗ импорта pptx: её импортирует
  `tests/test_system_docs.py` (один парсер = один источник истины о формате);
- рендерер импортирует pptx ЛЕНИВО внутри `build_pptx`;
- дефолтный выход — `build/orchestrator-overview.pptx` (в `.gitignore`;
  собранный бинарь в git НЕ коммитится — D5).

Процедура запуска (канон «команда + Проверка:») — `docs/overview/presentation.md`,
раздел «Как собрать .pptx».
"""

from __future__ import annotations

import argparse
import re
from dataclasses import dataclass, field
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_SOURCE = REPO_ROOT / "docs" / "overview" / "presentation.md"
DEFAULT_OUTPUT = REPO_ROOT / "build" / "orchestrator-overview.pptx"

# Тёмная тема (D4): фон ~#1F1F2E, светлый текст, один акцент, приглушённый серый.
DARK_BG = "1F1F2E"
TEXT_MAIN = "F2F2F7"
ACCENT = "8AB4F8"
TEXT_MUTED = "9A9AAD"
FONT_NAME = "Calibri"  # системный шрифт с полной кириллицей (D4)

_SLIDE_RE = re.compile(r"^##\s+Слайд\s+(\d+)\s*:\s*(.+?)\s*$")
_BULLET_RE = re.compile(r"^-\s+(.+?)\s*$")
_VISUAL_RE = re.compile(r"^>\s*Визуал\s*:\s*(.+?)\s*$")
_ANY_HEADING_RE = re.compile(r"^#{1,6}\s+")


@dataclass
class Slide:
    """Один слайд источника: номер, заголовок, тезисы, подпись визуала."""

    number: int
    title: str
    bullets: list[str] = field(default_factory=list)
    visual: str | None = None


def parse_slides(text: str) -> list[Slide]:
    """Разобрать слайдо-источник в список :class:`Slide` (чистая, stdlib-only).

    Формат (D4): слайд открывается строкой ``## Слайд N: Заголовок``; его тезисы —
    строки ``- ...``; опциональная подпись визуала — ``> Визуал: ...``. Любой
    другой markdown-заголовок (например, раздел «Как собрать .pptx») завершает
    текущий слайд — служебные разделы источника в слайды не попадают.
    """
    slides: list[Slide] = []
    current: Slide | None = None
    for line in text.splitlines():
        m = _SLIDE_RE.match(line)
        if m:
            current = Slide(number=int(m.group(1)), title=m.group(2))
            slides.append(current)
            continue
        if _ANY_HEADING_RE.match(line):
            current = None  # служебный раздел — не слайд
            continue
        if current is None:
            continue
        bullet = _BULLET_RE.match(line)
        if bullet:
            current.bullets.append(bullet.group(1))
            continue
        visual = _VISUAL_RE.match(line)
        if visual:
            current.visual = visual.group(1)
    return slides


def build_pptx(slides: list[Slide], output: Path) -> None:
    """Собрать `.pptx` в тёмном дизайне из распарсенных слайдов.

    Импорт `pptx` — ленивый (D4): без установленного `python-pptx` модуль
    остаётся импортируемым (нужно тестам), а сборка честно подсказывает
    `pip install python-pptx`. Текст пишется настоящими редактируемыми
    run'ами — кириллица не растрируется, слайды правятся руками.
    """
    from pptx import Presentation
    from pptx.dml.color import RGBColor
    from pptx.util import Inches, Pt

    prs = Presentation()
    prs.slide_width = Inches(13.333)  # 16:9
    prs.slide_height = Inches(7.5)
    blank_layout = prs.slide_layouts[6]

    for slide_def in slides:
        slide = prs.slides.add_slide(blank_layout)
        slide.background.fill.solid()
        slide.background.fill.fore_color.rgb = RGBColor.from_string(DARK_BG)

        title_box = slide.shapes.add_textbox(
            Inches(0.6), Inches(0.45), prs.slide_width - Inches(1.2), Inches(1.2)
        )
        title_tf = title_box.text_frame
        title_tf.word_wrap = True
        run = title_tf.paragraphs[0].add_run()
        run.text = slide_def.title
        run.font.size = Pt(34)
        run.font.bold = True
        run.font.name = FONT_NAME
        run.font.color.rgb = RGBColor.from_string(ACCENT)

        body_box = slide.shapes.add_textbox(
            Inches(0.8), Inches(1.85), prs.slide_width - Inches(1.6), Inches(4.4)
        )
        body_tf = body_box.text_frame
        body_tf.word_wrap = True
        for i, bullet in enumerate(slide_def.bullets):
            para = body_tf.paragraphs[0] if i == 0 else body_tf.add_paragraph()
            run = para.add_run()
            run.text = f"•  {bullet}"
            run.font.size = Pt(20)
            run.font.name = FONT_NAME
            run.font.color.rgb = RGBColor.from_string(TEXT_MAIN)
            para.space_after = Pt(10)

        if slide_def.visual:
            cap_box = slide.shapes.add_textbox(
                Inches(0.8), Inches(6.55), prs.slide_width - Inches(1.6), Inches(0.6)
            )
            cap_tf = cap_box.text_frame
            cap_tf.word_wrap = True
            run = cap_tf.paragraphs[0].add_run()
            run.text = f"Визуал: {slide_def.visual}"
            run.font.size = Pt(13)
            run.font.italic = True
            run.font.name = FONT_NAME
            run.font.color.rgb = RGBColor.from_string(TEXT_MUTED)

    output.parent.mkdir(parents=True, exist_ok=True)
    prs.save(str(output))


def main(argv: list[str] | None = None) -> int:
    """CLI: распарсить источник, собрать `.pptx`, напечатать число слайдов."""
    parser = argparse.ArgumentParser(
        description="Сборка docs/overview/presentation.md -> .pptx (тёмный дизайн, ORCH-011 D4)."
    )
    parser.add_argument(
        "--source",
        type=Path,
        default=DEFAULT_SOURCE,
        help=f"слайдо-источник (default: {DEFAULT_SOURCE.relative_to(REPO_ROOT)})",
    )
    parser.add_argument(
        "--out",
        type=Path,
        default=DEFAULT_OUTPUT,
        help=f"выходной .pptx (default: {DEFAULT_OUTPUT.relative_to(REPO_ROOT)})",
    )
    args = parser.parse_args(argv)

    if not args.source.is_file():
        print(f"ОШИБКА: источник не найден: {args.source}")
        return 1
    slides = parse_slides(args.source.read_text(encoding="utf-8"))
    if not slides:
        print(f"ОШИБКА: в {args.source} не найдено ни одного слайда (формат: '## Слайд N: ...')")
        return 1
    try:
        build_pptx(slides, args.out)
    except ImportError:
        print(
            "ОШИБКА: python-pptx не установлен. Сборка выполняется в одноразовом "
            "dev-venv ВНЕ прод-образа (NFR-2): pip install python-pptx"
        )
        return 1
    print(f"Собрано слайдов: {len(slides)} → {args.out}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
