#!/usr/bin/env python3
"""Regenerate changelog/INDEX.md from frontmatter across all buckets.

Usage:
  build_index.py [--changelog-dir path] [--stdout]
"""
import argparse
import sys
from collections import defaultdict
from pathlib import Path
from urllib.parse import urlparse

sys.path.insert(0, str(Path(__file__).parent))
from _common import as_list, parse_frontmatter, resolve_changelog_dir  # noqa: E402

BUCKETS = ["changes", "decisions", "phases", "backlog"]
BUCKET_TO_TYPE = {
    "changes": "change",
    "decisions": "decision",
    "phases": "phase",
    "backlog": "backlog",
}
STATUS_ORDER = {
    "change": ["draft", "shipped"],
    "decision": ["proposed", "accepted", "superseded"],
    "phase": ["active", "completed"],
    "backlog": ["in-progress", "active", "proposed", "deferred", "done", "dropped"],
}
VALID_PRIORITIES = ["low", "medium", "high", "critical"]


def walk_bucket(cdir, bucket, include_archive=False):
    bdir = cdir / bucket
    entries = []
    if bdir.exists():
        for path in sorted(bdir.rglob("*.md")):
            if "/archive/" in str(path):
                continue
            if path.name in ("INDEX.md", "README.md", "AGENTS.md"):
                continue
            meta, _ = parse_frontmatter(path.read_text())
            if not meta.get("type"):
                continue
            entries.append({"path": str(path.relative_to(cdir)), "meta": meta})
    if include_archive:
        archive = cdir / "archive" / bucket
        if archive.exists():
            for path in sorted(archive.rglob("*.md")):
                meta, _ = parse_frontmatter(path.read_text())
                if not meta.get("type"):
                    continue
                entries.append({
                    "path": str(path.relative_to(cdir)),
                    "meta": meta,
                    "archived": True,
                })
    return entries

def _format_artifact_link(artifact):
    parsed = urlparse(artifact)
    if parsed.scheme and parsed.netloc:
        label = parsed.netloc or artifact
        return f"[{label}]({artifact})"
    label = Path(artifact).name or artifact
    return f"[{label}]({artifact})"


def format_entry_line(e, bucket):
    meta = e["meta"]
    tags = meta.get("tags") or []
    tagstr = " " + " ".join(f"`{t}`" for t in tags) if tags else ""
    archived = " _(archived)_" if e.get("archived") else ""
    title = meta.get("title") or meta.get("id") or e["path"]
    hints = []
    if bucket == "backlog":
        priority = meta.get("priority")
        if priority:
            hints.append(f"priority `{priority}`")
        owner = meta.get("owner")
        if owner:
            owners = ", ".join(str(x) for x in as_list(owner))
            hints.append(f"owner `{owners}`")
        depends_on = meta.get("depends_on")
        if depends_on:
            deps = ", ".join(f"`{x}`" for x in as_list(depends_on))
            hints.append(f"depends on {deps}")
        artifacts = meta.get("artifacts")
        if artifacts:
            links = ", ".join(_format_artifact_link(x) for x in as_list(artifacts))
            hints.append(f"artifacts {links}")
    hintstr = ""
    if hints:
        hintstr = " — " + "; ".join(hints)
    return (
        f"- [{title}]({e['path']}) — `{meta.get('date', '?')}`"
        f"{tagstr}{hintstr}{archived}"
    )


def main():
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument("--changelog-dir")
    p.add_argument("--stdout", action="store_true")
    args = p.parse_args()

    cdir = resolve_changelog_dir(args.changelog_dir)

    all_entries = {}
    id_map = {}
    for bucket in BUCKETS:
        all_entries[bucket] = walk_bucket(cdir, bucket, include_archive=True)
        for e in all_entries[bucket]:
            eid = e["meta"].get("id")
            if eid:
                id_map[eid] = (bucket, e["path"])

    lines = [
        "# Changelog Index",
        "",
        "_Generated by `build_index.py`. Do not edit by hand._",
        "",
    ]

    for bucket in BUCKETS:
        entries = [e for e in all_entries[bucket] if not e.get("archived")]
        archived = [e for e in all_entries[bucket] if e.get("archived")]
        if not entries and not archived:
            continue
        type_key = BUCKET_TO_TYPE[bucket]
        lines.append(f"## {bucket}")
        lines.append("")
        by_status = defaultdict(list)
        for e in entries:
            by_status[e["meta"].get("status", "unknown")].append(e)

        order = STATUS_ORDER.get(type_key, [])
        statuses = list(order) + [s for s in by_status if s not in order]
        for s in statuses:
            if s not in by_status:
                continue
            lines.append(f"### {s}")
            lines.append("")
            items = sorted(
                by_status[s],
                key=lambda x: (x["meta"].get("updated") or x["meta"].get("date") or ""),
                reverse=True,
            )
            for e in items:
                lines.append(format_entry_line(e, bucket))
            lines.append("")

        if archived:
            lines.append("### archived")
            lines.append("")
            items = sorted(
                archived,
                key=lambda x: (x["meta"].get("updated") or x["meta"].get("date") or ""),
                reverse=True,
            )
            for e in items:
                lines.append(format_entry_line(e, bucket))
            lines.append("")

    issues = []
    for bucket in BUCKETS:
        for e in all_entries[bucket]:
            meta = e["meta"]
            required = ["title", "id", "date", "type", "status"]
            for f in required:
                if f not in meta:
                    issues.append(f"{e['path']}: missing required field `{f}`")
            expected_id = Path(e["path"]).stem
            if meta.get("id") and meta["id"] != expected_id:
                issues.append(
                    f"{e['path']}: id `{meta['id']}` does not match filename `{expected_id}`"
                )
            t = meta.get("type")
            if t in STATUS_ORDER and meta.get("status") not in STATUS_ORDER[t]:
                issues.append(
                    f"{e['path']}: status `{meta.get('status')}` not valid for type `{t}`"
                )
            priority = meta.get("priority")
            if priority and priority not in VALID_PRIORITIES:
                issues.append(
                    f"{e['path']}: priority `{priority}` not valid "
                    f"(expected one of {', '.join(VALID_PRIORITIES)})"
                )
            refs = meta.get("refs") or {}
            if isinstance(refs, dict):
                for rkey, rvals in refs.items():
                    if not isinstance(rvals, list):
                        rvals = [rvals]
                    for v in rvals:
                        if v and v not in id_map:
                            issues.append(
                                f"{e['path']}: refs.{rkey} -> `{v}` not found"
                            )
            depends_on = meta.get("depends_on")
            if depends_on:
                for dep in as_list(depends_on):
                    if dep and dep not in id_map:
                        issues.append(
                            f"{e['path']}: depends_on -> `{dep}` not found"
                        )

    if issues:
        lines.append("## Validation Issues")
        lines.append("")
        for issue in issues:
            lines.append(f"- {issue}")
        lines.append("")

    output = "\n".join(lines)
    if args.stdout:
        sys.stdout.write(output)
    else:
        target = cdir / "INDEX.md"
        target.write_text(output)
        print(f"wrote {target}")
        if issues:
            print(f"{len(issues)} validation issue(s) — see INDEX.md")


if __name__ == "__main__":
    main()
