#!/usr/bin/env python3

import json
import re
import sys
from pathlib import Path

PACKAGE_DIR = Path(__file__).resolve().parent
SYNC_GUIDE_START = "<!-- SKILL_SYNC_WORKFLOW_GUIDE_START -->"
SYNC_GUIDE_END = "<!-- SKILL_SYNC_WORKFLOW_GUIDE_END -->"
SKILL_MANAGED_START = "<!-- MIXER_SYNC_WORKFLOW_GUIDE_START -->"
SKILL_MANAGED_END = "<!-- MIXER_SYNC_WORKFLOW_GUIDE_END -->"


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)
    _write_runtime_notice(mixer_dir, PACKAGE_DIR)

    print("Sync complete.")


def _write_runtime_notice(mixer_dir: Path, package_dir: Path) -> None:
    mixer_dir.mkdir(parents=True, exist_ok=True)
    source_readme = package_dir / "README.md"
    if not source_readme.is_file():
        raise FileNotFoundError(f"Missing runtime README source: {source_readme}")
    (mixer_dir / "README.md").write_text(source_readme.read_text())


def _extract_sync_guide(overview_path: Path) -> str:
    """Extract sync guide block from workflow overview markers."""
    content = overview_path.read_text()
    pattern = re.compile(
        rf"{re.escape(SYNC_GUIDE_START)}\s*(.*?)\s*{re.escape(SYNC_GUIDE_END)}",
        re.DOTALL,
    )
    match = pattern.search(content)
    if not match:
        raise RuntimeError(
            f"Missing sync guide markers in {overview_path}: "
            f"{SYNC_GUIDE_START} ... {SYNC_GUIDE_END}"
        )
    return match.group(1).strip()


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)

    skill_content = source_skill.read_text().rstrip()
    existing_block = re.compile(
        rf"\n*{re.escape(SKILL_MANAGED_START)}.*?{re.escape(SKILL_MANAGED_END)}\n*",
        re.DOTALL,
    )
    skill_without_old = existing_block.sub("\n\n", skill_content).rstrip()

    overview_path = package_dir / "workflows" / "doc.md"
    final_content = skill_without_old
    if overview_path.exists():
        guide = _extract_sync_guide(overview_path)
        managed_block = f"{SKILL_MANAGED_START}\n{guide}\n{SKILL_MANAGED_END}\n"
        final_content = f"{skill_without_old}\n\n{managed_block}".rstrip()

    target_skill.write_text(final_content + "\n")
    print(f"  Skill synced: {source_skill} -> {target_skill}")


def _find_modules(root: Path) -> list[Path]:
    """Find all top-level directories containing .mix/."""
    modules = []
    for child in sorted(root.iterdir()):
        if not child.is_dir():
            continue
        if (child / ".mix").is_dir():
            modules.append(child)
    return modules


def _scan_module(directory: Path, rel_prefix: str) -> dict:
    """Recursively scan a module directory into a settings tree node."""
    node = {}
    mix_sub = directory / ".mix"
    if mix_sub.is_dir():
        for file_path in sorted(mix_sub.glob("*.md")):
            node[file_path.stem] = f"./{rel_prefix}/.mix/{file_path.name}"

    children = {}
    for child in sorted(directory.iterdir()):
        if not child.is_dir():
            continue
        child_node = _scan_module(child, f"{rel_prefix}/{child.name}")
        has_docs = any(key != "children" for key in child_node)
        has_children = bool(child_node.get("children"))
        if has_docs or has_children:
            children[child.name] = child_node

    node["children"] = children
    return node


def sync_settings(root: Path, mixer_dir: Path):
    """Scan project modules and generate settings.json."""
    module_dirs = _find_modules(root)
    if not module_dirs:
        raise FileNotFoundError(
            f"No modules found in {root}. "
            "Expected at least one directory containing a .mix/ subdirectory."
        )

    modules = {}
    for module_dir in module_dirs:
        name = module_dir.name
        modules[name] = _scan_module(module_dir, name)

    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)
    settings["modules"] = modules

    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 = [directory.name for directory in module_dirs]
    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)
