"""Pre-flight brief for /build — surfaces recipes, skills, pitfalls, and project files."""

import json
import re
import sys
from pathlib import Path


def _find_project_root() -> Path | None:
    """Walk up from cwd to find a Godot project root (has project.godot)."""
    cwd = Path.cwd()
    for parent in [cwd, *cwd.parents]:
        if (parent / "project.godot").exists():
            return parent
    return None


def _parse_recipes_index(recipes_index: Path) -> list[dict]:
    """Parse recipes/INDEX.md and return list of {file, keywords}.

    Handles 4-column format: | Concept | `file` | Dim | Keywords |
    """
    if not recipes_index.exists():
        return []

    entries = []
    for line in recipes_index.read_text(encoding="utf-8").splitlines():
        # 4 columns: | Concept | `filename.md` | Dim | keyword1, keyword2, ... |
        m = re.match(r"\|\s*(.+?)\s*\|\s*`(.+?)`\s*\|\s*\S+\s*\|\s*(.+?)\s*\|", line)
        if m and not m.group(1).startswith("---") and m.group(1).strip() != "Concept":
            concept = m.group(1).strip()
            filename = m.group(2).strip()
            keywords = [k.strip().lower() for k in m.group(3).split(",")]
            entries.append({
                "concept": concept,
                "file": filename,
                "keywords": keywords,
            })
    return entries


def _match_recipes(entries: list[dict], task_words: set[str]) -> list[dict]:
    """Return recipes whose keywords overlap with the task words."""
    matched = []
    for entry in entries:
        hits = [kw for kw in entry["keywords"] if kw in task_words]
        if hits:
            matched.append({
                "file": entry["file"],
                "concepts": hits,
            })
    return matched


def _parse_skills_index(skills_index: Path) -> list[dict]:
    """Parse skills/INDEX.md and return list of {skill, description, synonyms[]}.

    Extracts rows from tables: | Skill | Description | Synonymes |
    """
    if not skills_index.exists():
        return []

    entries = []
    for line in skills_index.read_text(encoding="utf-8").splitlines():
        # Match: | `/skill-name` | description | synonym1, synonym2, ... |
        m = re.match(r"\|\s*`(/[^`]+)`\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|", line)
        if m:
            skill = m.group(1).strip()
            description = m.group(2).strip()
            synonyms = [s.strip().lower() for s in m.group(3).split(",")]
            entries.append({
                "skill": skill,
                "description": description,
                "synonyms": synonyms,
            })
    return entries


def _match_skills(entries: list[dict], task_words: set[str]) -> list[dict]:
    """Match skill synonyms against task words. Returns [{skill, reason}]."""
    matched = []
    for entry in entries:
        hits = [s for s in entry["synonyms"] if s in task_words]
        if hits:
            matched.append({
                "skill": entry["skill"],
                "reason": ", ".join(hits),
            })
    return matched


# Domain-specific pitfall warnings — one-liners from rules/references
PITFALL_DOMAINS: dict[str, dict] = {
    "gdscript_core": {
        "keywords": {"gdscript", "script", "variable", "type", "signal", "export", "ready", "process", "node"},
        "one_liners": [
            ":= type inference — var speed := 200 = int, use 200.0 for float",
            "set_deferred on data = null in _ready() — assign sync BEFORE call_deferred(\"add_child\")",
            "Native member redefinition — @export var offset in Camera2D = Parser Error, prefix names",
        ],
    },
    "scene_editing": {
        "keywords": {"scene", "tscn", "node", "child", "instance", "instantiate", "save"},
        "one_liners": [
            "NEVER edit .tscn on disk — Godot overwrites from memory on save",
            "add_child during physics flush = crash — use call_deferred(\"add_child\", node)",
            "instantiate() without GEN_EDIT_STATE_INSTANCE = flattened nodes in parent .tscn",
            "queue_free + save_scene in same frame = node still present — use .free() in editor",
        ],
    },
    "game_feel": {
        "keywords": {"shake", "screenshake", "juice", "hitstop", "freeze", "impact", "squash", "stretch", "trail"},
        "one_liners": [
            "Camera2D shake via offset NEVER position — use follow_offset or shake_offset",
            "Engine.time_scale freeze min 0.15 — lower blocks GolemCapture",
            "Shader glow + PointLight2D = double-bubble — shader = surface, never light",
        ],
    },
    "audio": {
        "keywords": {"audio", "sound", "sfx", "music", "bus", "volume"},
        "one_liners": [
            "AudioStreamRandomizer for SFX variation — never play same sample twice",
            "Bus ducking: SFX bus → Compressor sidechain from Music bus",
        ],
    },
    "vfx": {
        "keywords": {"vfx", "particle", "shader", "glow", "dissolve", "outline", "lighting", "light"},
        "one_liners": [
            "GPUParticles2D without draw_pass = invisible — always add QuadMesh as draw_pass_1",
            "floor(TIME * speed) in shader = flickering pixels — use sin/cos",
            "CanvasModulate > (0.02, 0.02, 0.02) = visible halo edge",
            "VFX always start at 20% intensity — increase after",
        ],
    },
    "3d_skeleton": {
        "keywords": {"skeleton", "bone", "animation", "fbx", "retarget", "mesh", "3d"},
        "one_liners": [
            "MeshInstance3D.skeleton empty = silent T-pose — set NodePath(\"..\")",
            "FBX load() = rest poses not animations — use import pipeline with BoneMap",
            "AnimationPlayer root_node inherited — instanced scene may point to wrong root",
        ],
    },
    "ui": {
        "keywords": {"ui", "control", "container", "hbox", "vbox", "label", "button", "panel", "theme", "anchor"},
        "one_liners": [
            "anchors_preset does NOT reset offsets — add offset_left/top/right/bottom = 0",
            "PRESET_CENTER broken for VBoxContainer — use PRESET_FULL_RECT + alignment CENTER",
            "ColorRect fullscreen shader = white flash — set Color(0,0,0,0) in _ready()",
        ],
    },
}


def _match_domains(task_words: set[str]) -> dict[str, list[str]]:
    """Match task words against PITFALL_DOMAINS. Returns {domain: [one_liners]}."""
    matched = {}
    for domain, data in PITFALL_DOMAINS.items():
        if data["keywords"] & task_words:
            matched[domain] = data["one_liners"]
    return matched


def _parse_pitfalls(pitfalls_path: Path) -> list[dict]:
    """Parse references/gdscript_pitfalls.md — extract section headings and first-line summaries."""
    if not pitfalls_path.exists():
        return []

    sections = []
    current_heading = None

    for line in pitfalls_path.read_text(encoding="utf-8").splitlines():
        # Match ## N. Title or ## Title
        m = re.match(r"^##\s+(?:\d+\.\s+)?(.+)", line)
        if m:
            current_heading = m.group(1).strip()
            continue

        # Grab ### Symptôme or first paragraph line after heading
        if current_heading and line.strip() and not line.startswith("#"):
            # Skip markdown formatting lines
            if line.startswith("---") or line.startswith(">") or line.startswith("|"):
                if line.startswith(">"):
                    continue
                if line.startswith("---"):
                    current_heading = None
                    continue
                continue
            sections.append({
                "heading": current_heading,
                "summary": line.strip()[:120],
                "keywords": set(re.findall(r"[a-z_]+", current_heading.lower())),
            })
            current_heading = None

    return sections


def _match_pitfalls(sections: list[dict], task_words: set[str]) -> list[str]:
    """Return pitfall summaries where heading keywords overlap with task words."""
    matched = []
    for section in sections:
        if section["keywords"] & task_words:
            matched.append(f"{section['heading']} — {section['summary']}")
    return matched


def _scan_project_files(project_root: Path, task_words: set[str]) -> list[str]:
    """Scan *.gd, *.tscn, *.tres for filenames/content matching task keywords."""
    matched = []
    extensions = {".gd", ".tscn", ".tres"}

    for ext in extensions:
        for filepath in project_root.rglob(f"*{ext}"):
            # Skip addons
            rel = filepath.relative_to(project_root)
            if str(rel).startswith("addons") or str(rel).startswith("."):
                continue

            # Match on filename
            name_words = set(re.findall(r"[a-z]+", filepath.stem.lower()))
            if name_words & task_words:
                matched.append(f"res://{rel.as_posix()}")
                continue

            # Match on content (first 2000 chars for speed)
            try:
                content = filepath.read_text(encoding="utf-8", errors="ignore")[:2000].lower()
                content_words = set(re.findall(r"[a-z_]+", content))
                if len(task_words & content_words) >= 2:
                    matched.append(f"res://{rel.as_posix()}")
            except OSError:
                continue

    return sorted(set(matched))[:20]  # Cap at 20 files


def _tokenize_task(task: str) -> set[str]:
    """Split task description into lowercase keyword tokens."""
    # Split on spaces, punctuation
    words = set(re.findall(r"[a-z]+", task.lower()))
    # Remove common stop words
    stop_words = {
        "a", "an", "the", "is", "are", "was", "were", "be", "been",
        "do", "does", "did", "have", "has", "had", "will", "would",
        "can", "could", "should", "may", "might", "must", "shall",
        "to", "of", "in", "for", "on", "with", "at", "by", "from",
        "and", "or", "but", "not", "no", "if", "then", "else",
        "this", "that", "it", "its", "my", "your", "our", "their",
        "un", "une", "le", "la", "les", "de", "du", "des", "et",
        "qui", "que", "dans", "pour", "avec", "sur", "par",
        "je", "tu", "il", "nous", "vous", "ils",
        "add", "create", "make", "implement", "ajoute", "cree", "fait",
    }
    return words - stop_words


def main() -> None:
    if len(sys.argv) < 2:
        print(json.dumps({"error": "Usage: preflight.py \"task description\""}))
        sys.exit(1)

    task = " ".join(sys.argv[1:])
    task_words = _tokenize_task(task)

    if not task_words:
        print(json.dumps({"recipes": [], "pitfalls": [], "skills": [], "pitfall_warnings": [], "files": []}))
        return

    project_root = _find_project_root()

    # Locate data files — try project paths first (installed), then repo-relative
    recipes_index = None
    pitfalls_path = None
    skills_index = None

    search_bases = []
    if project_root:
        search_bases.append(project_root)
    search_bases.append(Path.cwd())

    for base in search_bases:
        if not recipes_index:
            candidate = base / "recipes" / "INDEX.md"
            if candidate.exists():
                recipes_index = candidate
        if not pitfalls_path:
            candidate = base / "references" / "gdscript_pitfalls.md"
            if candidate.exists():
                pitfalls_path = candidate
        if not skills_index:
            # Installed path: .claude/skills/INDEX.md — or repo: package/skills/INDEX.md
            for sub in ["skills", ".claude/skills", "package/skills"]:
                candidate = base / sub / "INDEX.md"
                if candidate.exists():
                    skills_index = candidate
                    break

    # Parse and match
    recipes = []
    if recipes_index:
        entries = _parse_recipes_index(recipes_index)
        recipes = _match_recipes(entries, task_words)

    pitfalls = []
    if pitfalls_path:
        sections = _parse_pitfalls(pitfalls_path)
        pitfalls = _match_pitfalls(sections, task_words)

    skills = []
    if skills_index:
        skill_entries = _parse_skills_index(skills_index)
        skills = _match_skills(skill_entries, task_words)

    # Domain-based pitfall warnings (static, no file needed)
    domain_matches = _match_domains(task_words)
    pitfall_warnings = []
    for one_liners in domain_matches.values():
        pitfall_warnings.extend(one_liners)

    files = []
    if project_root:
        files = _scan_project_files(project_root, task_words)

    result = {
        "recipes": recipes,
        "skills": skills,
        "pitfalls": pitfalls,
        "pitfall_warnings": pitfall_warnings,
        "files": files,
    }

    print(json.dumps(result, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()
