#!/usr/bin/env python3
"""Aria-Brain Wikilink-Integritaets-Validator.

Prueft ob alle `[[Wikilinks]]` einer Note auf eine existierende Brain-Note
zeigen (Obsidian-Graph-Health, siehe HEARTBEAT „Dangling Links proaktiv
abarbeiten"). Dangling Links = Graph-Bruch → Aria findet die verlinkte
Begruendung spaeter nicht mehr.

Aufloesung (Obsidian-Konvention):
- `[[Note Title]]`            → irgendeine Datei `Note Title.md` im Vault
- `[[01-Projekte/Aria/Foo]]`  → pfad-relativ ab Vault-Root
- `[[Note#Heading]]`          → `#Heading` wird gestrippt
- `[[Note|Anzeigetext]]`      → `|Anzeigetext` wird gestrippt
- `![[bild.png]]`             → Asset-Embed, kein .md → uebersprungen

Usage:
    markdown_wikilinks.py --input /root/aria/brain/02-Wissen/foo.md
    cat foo.md | markdown_wikilinks.py --input - --source-path /abs/foo.md

Exit-Codes: 0 pass, 2 fail.
"""
import argparse
import os
import re
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
from _lib import emit_report, read_input

# 14.07.2026 Post-Mortem-Fix: /home/aria statt /root/aria (User-Migration)
VAULT_ROOT = Path("/home/aria/brain")
WIKILINK_RE = re.compile(r"!?\[\[([^\]]+?)\]\]")
ASSET_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp",
              ".pdf", ".canvas", ".excalidraw", ".mp4", ".mov", ".mp3"}


def build_index(root: Path) -> tuple[dict[str, list[str]], set[str]]:
    """stem(lower) -> [relpaths], und set der relpaths-ohne-.md (lower)."""
    by_stem: dict[str, list[str]] = {}
    by_relpath: set[str] = set()
    for dirpath, dirnames, filenames in os.walk(root):
        dirnames[:] = [d for d in dirnames if not d.startswith(".")]
        for fn in filenames:
            if not fn.endswith(".md"):
                continue
            full = Path(dirpath) / fn
            rel = full.relative_to(root).as_posix()
            stem = fn[:-3]
            by_stem.setdefault(stem.lower(), []).append(rel)
            by_relpath.add(rel[:-3].lower())
    return by_stem, by_relpath


def normalize_target(raw: str) -> str:
    t = raw.strip()
    t = t.split("#", 1)[0]      # Heading-Anchor weg
    t = t.split("|", 1)[0]      # Alias weg
    return t.strip()


def resolve(target: str, by_stem: dict[str, list[str]], by_relpath: set[str]) -> bool:
    t = target
    if t.endswith(".md"):
        t = t[:-3]
    key = t.lower()
    if "/" in t:
        return key in by_relpath
    return key in by_stem


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--input", default="-")
    ap.add_argument("--source-path", default=None,
                    help="Echter Pfad wenn via stdin gepiped (fuer Self-Link-Skip)")
    args = ap.parse_args()

    content = read_input(args.input)
    src = args.source_path or (args.input if args.input != "-" else None)

    if not VAULT_ROOT.exists():
        return emit_report("markdown_wikilinks", True, [],
                           details={"skipped": "vault-root fehlt"})

    by_stem, by_relpath = build_index(VAULT_ROOT)

    dangling: list[str] = []
    seen: set[str] = set()
    for m in WIKILINK_RE.finditer(content):
        is_embed = m.group(0).startswith("!")
        target = normalize_target(m.group(1))
        if not target:
            continue
        ext = os.path.splitext(target)[1].lower()
        if is_embed or ext in ASSET_EXTS:
            continue
        if target in seen:
            continue
        seen.add(target)
        if not resolve(target, by_stem, by_relpath):
            dangling.append(target)

    if dangling:
        errors = [f"Dangling-Wikilink (kein Vault-Treffer): [[{d}]]" for d in dangling]
        return emit_report("markdown_wikilinks", False, errors,
                           details={"input": src or "-", "dangling_count": len(dangling)})

    return emit_report("markdown_wikilinks", True, [],
                       details={"input": src or "-", "links_checked": len(seen)})


if __name__ == "__main__":
    sys.exit(main())
