"""Réutilisation des briques déjà livrées (workflow #5 · zéro duplication).

La matrice d'acceptation ne redéfinit RIEN qui existe ailleurs :

  - `parse_ci`        : parseur de `.gitea/workflows/ci.yml` de l'auditeur 4Big
                        (`qa/audit_4big/q4lib/registry.py`) — la SEULE autorité
                        sur les jobs de test réellement gated. On le réutilise
                        pour DÉRIVER l'ensemble des modules-preuve (jamais une
                        liste à la main) et prouver la couverture bijective.
  - `validate`        : validateur JSON-Schema maison du Publiciste (draft-07,
                        sous-ensemble) — la matrice est validée SANS pip, car le
                        gate CI Gitea Actions tourne sans réseau (CLAUDE.md #2).
  - `audit_sprints`   : la fenêtre de sprint de chaque module est LUE dans le
                        registre de l'auditeur 4Big (`quality_spec.json`) — une
                        seule source de vérité pour tout le mandat (anti-dérive).

Import par manipulation de `sys.path` (idiome des modules voisins :
`q4lib/deps.py`, `reglib/deps.py`) plutôt que duplication.
"""

from __future__ import annotations

import json
import os
import re
import sys

_HERE = os.path.dirname(os.path.abspath(__file__))
# qa/acceptance/acclib → 05_deliverables_mvp
_DELIVERABLES = os.path.normpath(os.path.join(_HERE, "..", "..", ".."))
# 05_deliverables_mvp → racine du dépôt (où vit 04_roadmap/).
_REPO_ROOT = os.path.normpath(os.path.join(_DELIVERABLES, ".."))
_PUB = os.path.join(_DELIVERABLES, "publiciste")
_AUDIT = os.path.join(_DELIVERABLES, "qa", "audit_4big")

for _p in (_PUB, _AUDIT):
    if _p not in sys.path:
        sys.path.insert(0, _p)

from lib import validator          # type: ignore  # noqa: E402
from q4lib import registry         # type: ignore  # noqa: E402

validate = validator.validate
parse_ci = registry.parse_ci

# Racine des livrables, exposée aux autres modules acclib.
DELIVERABLES_ROOT = _DELIVERABLES

_AUDIT_SPEC = os.path.join(_AUDIT, "quality_spec.json")


def audit_sprints() -> dict:
    """{chemin module -> sprint} tel que déclaré par le registre de l'auditeur
    4Big. Une seule source de vérité (anti-dérive · #5) ; l'auditeur ne se
    référence pas lui-même — son sprint est complété par le spec (extra)."""
    with open(_AUDIT_SPEC, encoding="utf-8") as fh:
        spec = json.load(fh)
    return {m["path"]: m["sprint"] for m in spec["modules"]}


# Ancres de traçabilité de la roadmap (structure, jamais des n° saisis à la main).
_SPRINT_HEAD = re.compile(r"##\s+SPRINT\s+\d+.*?·\s*(S\d+)\s*\(")
_METRIC_HEAD = re.compile(r"##\s+M[ée]triques\s+succ")
_DELIVERABLE = re.compile(r"\*\*DELIVERABLE")
_METRIC_BULLET = re.compile(r"^-\s*✅")


def parse_roadmap_anchors(roadmap_ref: str) -> dict:
    """DÉRIVE les ancres de traçabilité depuis la roadmap réelle (#6 · jamais une
    liste de n° de ligne saisie à la main).

    Renvoie :
      - ``deliverables`` : {sprint SX -> n° de ligne du bullet ``**DELIVERABLE**``
        appartenant au sprint SX} (le SX est LU dans le titre ``## SPRINT n … · SX (…``) ;
      - ``metrics``      : [n° de ligne] des bullets ``- ✅`` sous la section
        ``## Métriques succès MVP``, en ordre de fichier ;
      - ``path``         : le chemin résolu (pour messages).

    ``roadmap_ref`` absolu est utilisé tel quel ; relatif est résolu depuis la
    racine du dépôt. Lève ``OSError`` si la roadmap est absente/illisible — une
    référence de traçabilité vers un fichier fantôme est un mensonge (red honnête)."""
    path = roadmap_ref if os.path.isabs(roadmap_ref) \
        else os.path.join(_REPO_ROOT, roadmap_ref)
    with open(path, encoding="utf-8") as fh:
        lines = fh.read().splitlines()

    deliverables: dict[str, int] = {}
    metrics: list[int] = []
    current_sprint: str | None = None
    in_metrics = False
    for i, ln in enumerate(lines, 1):
        mh = _SPRINT_HEAD.match(ln)
        if mh:
            current_sprint, in_metrics = mh.group(1), False
            continue
        if _METRIC_HEAD.match(ln):
            current_sprint, in_metrics = None, True
            continue
        if ln.startswith("## "):          # toute autre section clôt le contexte
            current_sprint, in_metrics = None, False
            continue
        if current_sprint and _DELIVERABLE.search(ln):
            deliverables[current_sprint] = i
        if in_metrics and _METRIC_BULLET.match(ln):
            metrics.append(i)
    return {"deliverables": deliverables, "metrics": metrics, "path": path}


def artifact_exists(rel_path: str) -> bool:
    """Vrai ssi l'artefact cité (chemin relatif à 05_deliverables_mvp) existe.

    Fait de disque pur — un artefact-preuve inexistant est un mensonge de
    couverture (#6)."""
    return os.path.exists(os.path.join(DELIVERABLES_ROOT, rel_path))


__all__ = [
    "validate",
    "parse_ci",
    "audit_sprints",
    "parse_roadmap_anchors",
    "artifact_exists",
    "DELIVERABLES_ROOT",
]
