"""Coherence validator — checks skill references, recipes, and stale names."""

import re
import sys
from pathlib import Path


def _find_repo_root() -> Path:
    """Find the golem-mcp repo root (has pyproject.toml + commands/)."""
    cwd = Path.cwd()
    for parent in [cwd, *cwd.parents]:
        if (parent / "pyproject.toml").exists() and (parent / "commands").exists():
            return parent
    # Fallback: assume script is in tools/
    return Path(__file__).parent.parent


def _read_text(path: Path) -> str:
    if path.exists():
        return path.read_text(encoding="utf-8")
    return ""


def _extract_skill_refs(content: str) -> set[str]:
    """Extract skill filename references like `godot-plan.md`, `skills/godot-debug.md` from markdown content."""
    refs = set()
    # Match backtick-quoted bare filenames: `godot-plan.md`
    refs |= set(re.findall(r"`([\w-]+\.md)`", content))
    # Match backtick-quoted skill paths: `.claude/skills/godot-debug.md` → extract `godot-debug.md`
    for m in re.finditer(r"`(?:\.claude/)?skills/([\w-]+\.md)`", content):
        refs.add(m.group(1))
    return refs


def _extract_all_md_refs(content: str) -> set[str]:
    """Extract all .md file references from content."""
    return set(re.findall(r"`([a-zA-Z0-9_-]+\.md)`", content))


def check_commands_reference_skills(root: Path) -> list[str]:
    """Check that every skill referenced in commands/*.md exists in skills/."""
    errors = []
    skills_dir = root / "skills"
    skill_files = {f.name for f in skills_dir.glob("*.md")} if skills_dir.exists() else set()

    commands_dir = root / "commands"
    if not commands_dir.exists():
        return ["commands/ directory not found"]

    for cmd in commands_dir.glob("*.md"):
        content = _read_text(cmd)
        refs = _extract_skill_refs(content)
        for ref in refs:
            # Only check refs that could be skills (not INDEX.md, gdscript_pitfalls.md, etc.)
            if ref in skill_files or ref.replace(".md", "") in {
                "godot-plan", "godot-sequence", "godot-simulate", "godot-gate",
                "godot-architecture", "godot-scene", "godot-composition",
                "godot-behavior", "godot-juice", "godot-vfx", "godot-audio",
                "godot-camera", "godot-save", "godot-debug", "godot-test",
                "godot-theme", "godot-design-audit", "godot-status", "godot",
            }:
                if ref not in skill_files:
                    errors.append(f"commands/{cmd.name} references `{ref}` but skills/{ref} not found")
    return errors


def _extract_slash_refs(content: str) -> set[str]:
    """Extract slash-command style refs like /godot-scene → godot-scene.md."""
    refs = set()
    for m in re.finditer(r"(?:^|[\s`|])/(\w[\w-]*)\b", content, re.MULTILINE):
        name = m.group(1)
        refs.add(f"{name}.md")
    return refs


def check_skills_referenced_by_commands(root: Path) -> list[str]:
    """Check that every file in skills/ is referenced by at least one command."""
    errors = []
    skills_dir = root / "skills"
    if not skills_dir.exists():
        return ["skills/ directory not found"]

    skill_files = {f.name for f in skills_dir.glob("*.md")}
    # Router and index are structural, not directly invoked by commands
    skip = {"godot.md", "INDEX.md"}

    # Collect all skill refs from all commands (backtick + slash-command style)
    commands_dir = root / "commands"
    if not commands_dir.exists():
        return ["commands/ directory not found"]

    all_refs = set()
    for cmd in commands_dir.glob("*.md"):
        content = _read_text(cmd)
        all_refs |= _extract_skill_refs(content)
        all_refs |= _extract_slash_refs(content)

    for skill in sorted(skill_files):
        if skill in skip:
            continue
        if skill not in all_refs:
            errors.append(f"skills/{skill} is not referenced by any command in commands/")
    return errors


def check_recipes_index(root: Path) -> list[str]:
    """Check that every recipe in INDEX.md has a corresponding file."""
    errors = []
    index_path = root / "recipes" / "INDEX.md"
    if not index_path.exists():
        return ["recipes/INDEX.md not found"]

    content = _read_text(index_path)
    recipes_dir = root / "recipes"

    for m in re.finditer(r"`(\w[\w-]*\.md)`", content):
        filename = m.group(1)
        if filename == "INDEX.md":
            continue
        if not (recipes_dir / filename).exists():
            errors.append(f"recipes/INDEX.md references `{filename}` but recipes/{filename} not found")
    return errors


def check_claude_md_references(root: Path) -> list[str]:
    """Check that reference files cited in CLAUDE.md exist."""
    errors = []
    claude_md = root / "CLAUDE.md"
    if not claude_md.exists():
        return []

    content = _read_text(claude_md)

    # Check references/ files
    for m in re.finditer(r"references/(\w[\w-]*\.md)", content):
        ref = m.group(1)
        if not (root / "references" / ref).exists():
            errors.append(f"CLAUDE.md references `references/{ref}` but file not found")

    # Check recipes/ files
    for m in re.finditer(r"recipes/(\w[\w-]*\.md)", content):
        ref = m.group(1)
        if not (root / "recipes" / ref).exists():
            errors.append(f"CLAUDE.md references `recipes/{ref}` but file not found")

    # Check rules/ files
    for m in re.finditer(r"rules/(\w[\w-]*\.md)", content):
        ref = m.group(1)
        if not (root / "rules" / ref).exists():
            errors.append(f"CLAUDE.md references `rules/{ref}` but file not found")

    # Check skills/ files
    for m in re.finditer(r"skills/(\w[\w-]*\.md)", content):
        ref = m.group(1)
        if not (root / "skills" / ref).exists():
            errors.append(f"CLAUDE.md references `skills/{ref}` but file not found")

    # Check commands/ files
    for m in re.finditer(r"commands/(\w[\w-]*\.md)", content):
        ref = m.group(1)
        if not (root / "commands" / ref).exists():
            errors.append(f"CLAUDE.md references `commands/{ref}` but file not found")

    return errors


def check_stale_godot_refs(root: Path) -> list[str]:
    """Check for slash command references that point to non-existent skills or commands."""
    errors = []
    # Match slash commands: preceded by backtick, space, or start of line
    # Avoids false positives on filesystem paths like ~/.local/share/godot/
    ref_pattern = re.compile(r"(?:^|[\s`|])\/(\w[\w-]*)\b", re.MULTILINE)

    # Build set of valid targets (skill and command names without .md)
    valid_targets = set()
    for directory in ["commands", "skills"]:
        dir_path = root / directory
        if dir_path.exists():
            for md in dir_path.glob("*.md"):
                valid_targets.add(md.stem)

    for directory in ["commands", "skills"]:
        dir_path = root / directory
        if not dir_path.exists():
            continue
        for md in dir_path.glob("*.md"):
            content = _read_text(md)
            matches = ref_pattern.findall(content)
            # Only check refs that look like godot skills/commands (godot-* or known command names)
            stale = sorted({
                m for m in matches
                if m not in valid_targets and (m.startswith("godot") or m in valid_targets)
            })
            if stale:
                errors.append(f"{directory}/{md.name} contains stale references: {', '.join('/' + s for s in stale)}")
    return errors


def check_hooks_exist(root: Path) -> list[str]:
    """Check that hook files referenced in installer exist."""
    errors = []
    hooks_dir = root / "hooks"
    if not hooks_dir.exists():
        return ["hooks/ directory not found"]

    expected = [
        "check_connection.py",
        "block_tscn_edit.py",
        "gdscript_lint.py",
        "compact_context.py",
        "pre_compact.py",
    ]
    for hook in expected:
        if not (hooks_dir / hook).exists():
            errors.append(f"hooks/{hook} not found (expected by installer)")
    return errors


def main() -> None:
    root = _find_repo_root()
    all_errors = []

    checks = [
        ("Commands → Skills references", check_commands_reference_skills),
        ("Skills referenced by commands", check_skills_referenced_by_commands),
        ("Recipes INDEX.md → files", check_recipes_index),
        ("CLAUDE.md → reference files", check_claude_md_references),
        ("Stale /godot-xxx references", check_stale_godot_refs),
        ("Hooks exist", check_hooks_exist),
    ]

    for name, check_fn in checks:
        errors = check_fn(root)
        if errors:
            print(f"\n✗ {name}:", file=sys.stderr)
            for err in errors:
                print(f"  - {err}", file=sys.stderr)
            all_errors.extend(errors)
        else:
            print(f"✓ {name}", file=sys.stderr)

    if all_errors:
        print(f"\n{len(all_errors)} issue(s) found.", file=sys.stderr)
        sys.exit(1)
    else:
        print("\nAll checks passed.", file=sys.stderr)
        sys.exit(0)


if __name__ == "__main__":
    main()
