#!/usr/bin/env python3
"""Walidator konformancji OKF v0.1 (§9) dla tego bundle'a.

Sprawdza:
  1. Każdy nie-zarezerwowany plik .md ma parsowalny blok frontmattera YAML.
  2. Każdy frontmatter ma niepuste pole `type`.
  3. Pliki zarezerwowane (index.md, log.md) nie mają frontmattera —
     z jedynym wyjątkiem: root index.md może mieć wyłącznie `okf_version` (§11).

Tylko biblioteka standardowa: minimalny parser YAML wystarczający dla
frontmatterów w tym bundle'u (klucze skalarne, listy inline, listy blokowe).
Uruchomienie: python3 check_okf.py [katalog-bundle'a]
"""

from __future__ import annotations

import sys
from pathlib import Path

RESERVED = {"index.md", "log.md"}


def split_frontmatter(text: str) -> tuple[str | None, str]:
    """Zwraca (blok frontmattera lub None, reszta dokumentu)."""
    if not text.startswith("---\n"):
        return None, text
    end = text.find("\n---", 3)
    if end == -1:
        raise ValueError("blok frontmattera otwarty '---', ale nigdy nie zamknięty")
    return text[4:end], text[end + 4 :]


def parse_yaml(block: str) -> dict[str, object]:
    """Minimalny parser mapy YAML najwyższego poziomu."""
    out: dict[str, object] = {}
    key: str | None = None
    for lineno, raw in enumerate(block.splitlines(), start=2):
        line = raw.rstrip()
        if not line.strip() or line.lstrip().startswith("#"):
            continue
        if line.startswith((" ", "\t")):
            item = line.strip()
            if not item.startswith("- "):
                raise ValueError(f"linia {lineno}: nieoczekiwane wcięcie: {line!r}")
            if key is None:
                raise ValueError(f"linia {lineno}: element listy bez klucza")
            out.setdefault(key, [])
            if not isinstance(out[key], list):
                out[key] = []
            out[key].append(scalar(item[2:]))  # type: ignore[union-attr]
            continue
        if ":" not in line:
            raise ValueError(f"linia {lineno}: brak ':' w {line!r}")
        key, _, value = line.partition(":")
        key = key.strip()
        if not key:
            raise ValueError(f"linia {lineno}: pusty klucz w {line!r}")
        out[key] = scalar(value.strip())
    return out


def scalar(value: str) -> object:
    value = value.strip()
    if not value:
        return ""
    if value.startswith("[") and value.endswith("]"):
        inner = value[1:-1].strip()
        return [scalar(v) for v in inner.split(",")] if inner else []
    if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
        return value[1:-1]
    return value


def check(root: Path) -> list[str]:
    errors: list[str] = []
    files = sorted(p for p in root.rglob("*.md") if ".git" not in p.parts)
    if not files:
        return [f"brak plików .md w {root}"]

    for path in files:
        rel = path.relative_to(root).as_posix()
        text = path.read_text(encoding="utf-8")
        try:
            block, _ = split_frontmatter(text)
        except ValueError as exc:
            errors.append(f"{rel}: {exc}")
            continue

        if path.name in RESERVED:
            is_root_index = rel == "index.md"
            if block is None:
                continue
            if not is_root_index:
                errors.append(
                    f"{rel}: plik zarezerwowany nie może mieć frontmattera (§6/§11)"
                )
                continue
            try:
                fm = parse_yaml(block)
            except ValueError as exc:
                errors.append(f"{rel}: niepoprawny YAML we frontmatterze — {exc}")
                continue
            extra = set(fm) - {"okf_version"}
            if extra:
                errors.append(
                    f"{rel}: root index.md może mieć wyłącznie okf_version, "
                    f"znaleziono też: {', '.join(sorted(extra))} (§11)"
                )
            elif not str(fm.get("okf_version", "")).strip():
                errors.append(f"{rel}: puste okf_version (§11)")
            continue

        if block is None:
            errors.append(f"{rel}: brak bloku frontmattera YAML (§9.1)")
            continue
        try:
            fm = parse_yaml(block)
        except ValueError as exc:
            errors.append(f"{rel}: niepoprawny YAML we frontmatterze — {exc}")
            continue
        if not str(fm.get("type", "")).strip():
            errors.append(f"{rel}: brak niepustego pola `type` (§9.2)")

    return errors


def main() -> int:
    root = Path(sys.argv[1] if len(sys.argv) > 1 else Path(__file__).parent).resolve()
    files = sorted(p for p in root.rglob("*.md") if ".git" not in p.parts)
    errors = check(root)

    print(f"Bundle: {root}")
    print(f"Sprawdzono plików .md: {len(files)}")
    concepts = [p for p in files if p.name not in RESERVED]
    reserved = [p for p in files if p.name in RESERVED]
    print(f"  koncepty: {len(concepts)}, zarezerwowane: {len(reserved)}")
    print()

    if errors:
        print(f"NIEZGODNE z OKF v0.1 — {len(errors)} problem(ów):")
        for err in errors:
            print(f"  ✗ {err}")
        return 1

    print("ZGODNE z OKF v0.1 (§9): frontmatter parsowalny, `type` niepusty,")
    print("pliki zarezerwowane bez frontmattera (poza okf_version w root index.md).")
    return 0


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