#!/usr/bin/env python3

import json
import sys
from pathlib import Path

PACKAGE_DIR = Path(__file__).resolve().parent


def sync(project_root: str | None = None):
    root = Path(project_root).resolve() if project_root else Path.cwd().resolve()
    mixer_dir = root / ".mixer"

    sync_settings(root, mixer_dir)
    sync_skill(root, PACKAGE_DIR)

    print("Sync complete.")


def sync_skill(root: Path, package_dir: Path):
    """Sync canonical package SKILL.md into project .claude skill path."""
    source_skill = package_dir / "SKILL.md"
    if not source_skill.is_file():
        raise FileNotFoundError(f"Missing canonical skill file: {source_skill}")

    target_skill = root / ".claude" / "skills" / "mixer" / "SKILL.md"
    target_skill.parent.mkdir(parents=True, exist_ok=True)

    target_skill.write_text(source_skill.read_text())
    print(f"  Skill synced: {source_skill} -> {target_skill}")


_SKIP_DIRS = {
    ".git", ".mixer", ".claude", "node_modules", "__pycache__",
    ".venv", "venv", "dist", "build", ".tox", ".mypy_cache",
}


def _scan_doc_tree(directory: Path, root: Path, seen_names: dict[str, Path]) -> dict:
    """Recursively scan a directory for _*.md module doc files, building a hierarchical tree.

    Each node is keyed by the name derived from the filename (e.g. _plan.md -> plan).
    Nodes contain a 'doc' path and 'children' dict. Raises on duplicate names.
    """
    result = {}

    doc_files = sorted(directory.glob("_*.md"))
    node_name = None
    node = {}

    if len(doc_files) > 1:
        raise ValueError(
            f"Multiple _*.md module doc files found in {directory}: "
            + ", ".join(f.name for f in doc_files)
        )

    if doc_files:
        doc_file = doc_files[0]
        node_name = doc_file.stem[1:]  # e.g. _plan.md -> plan
        if not node_name:
            raise ValueError(f"Invalid module doc filename: {doc_file} (name would be empty)")
        if node_name in seen_names:
            raise ValueError(
                f"Duplicate module name '{node_name}' found in "
                f"{doc_file} and {seen_names[node_name]}"
            )
        seen_names[node_name] = doc_file
        rel = "./" + str(doc_file.relative_to(root))
        node["doc"] = rel

    children = {}
    for child in sorted(directory.iterdir()):
        if not child.is_dir():
            continue
        if child.name.startswith(".") or child.name in _SKIP_DIRS:
            continue
        child_result = _scan_doc_tree(child, root, seen_names)
        children.update(child_result)

    if node_name:
        node["children"] = children
        result[node_name] = node
    else:
        result.update(children)

    return result


def _merge_modules(existing: dict, scanned: dict) -> dict:
    """Merge scanned module tree into existing, preserving user-added keys.

    Scanned entries update ``doc`` and recurse into ``children``, but any
    extra keys the user added to an existing node are kept.  Modules that
    no longer appear in the scan are also kept so user data is never lost.
    """
    merged = dict(existing)
    for name, node in scanned.items():
        if name in existing and isinstance(existing[name], dict):
            old = dict(existing[name])
            old["doc"] = node.get("doc", old.get("doc"))
            old_children = old.get("children", {})
            new_children = node.get("children", {})
            old["children"] = _merge_modules(
                old_children if isinstance(old_children, dict) else {},
                new_children if isinstance(new_children, dict) else {},
            )
            merged[name] = old
        else:
            merged[name] = node
    return merged


def sync_settings(root: Path, mixer_dir: Path):
    """Scan project for _*.md module doc files and generate settings.json."""
    seen_names: dict[str, Path] = {}
    modules = {}
    for child in sorted(root.iterdir()):
        if not child.is_dir():
            continue
        if child.name.startswith(".") or child.name in _SKIP_DIRS:
            continue
        child_result = _scan_doc_tree(child, root, seen_names)
        modules.update(child_result)

    if not modules:
        raise FileNotFoundError(
            f"No modules found in {root}. "
            "Expected at least one _*.md module doc file."
        )

    mixer_dir.mkdir(parents=True, exist_ok=True)
    settings_path = mixer_dir / "settings.json"
    existing: dict = {}
    if settings_path.is_file():
        try:
            loaded = json.loads(settings_path.read_text())
            if isinstance(loaded, dict):
                existing = loaded
        except json.JSONDecodeError:
            existing = {}

    settings = dict(existing)

    # Merge scanned modules without discarding user-added keys on existing entries.
    existing_modules = settings.get("modules", {})
    if not isinstance(existing_modules, dict):
        existing_modules = {}
    merged_modules = _merge_modules(existing_modules, modules)
    settings["modules"] = merged_modules

    # Preserve the user's linear configuration — only fill in missing defaults.
    linear = settings.get("linear")
    if not isinstance(linear, dict):
        linear = {}
    linear.setdefault("team_prefix", "")
    linear.setdefault("team_id", "")
    settings["linear"] = linear

    content = json.dumps(settings, indent=2) + "\n"
    settings_path.write_text(content)

    names = sorted(seen_names.keys())
    print(f"  Settings generated (modules: {', '.join(names)})")


if __name__ == "__main__":
    project_root = None
    for arg in sys.argv[1:]:
        if arg.startswith("--project_root="):
            project_root = arg.split("=", 1)[1]
    sync(project_root=project_root)
