#!/usr/bin/env python3
"""
Skills Sync -- Manifest-based seeding of bundled skills into ~/.hermes/skills/.

On fresh install: copies all bundled skills from the repo's skills/ directory
into ~/.hermes/skills/ and records every skill name in a manifest file.

On update: copies only NEW bundled skills (names not in the manifest) so that
user deletions are permanent and user modifications are never overwritten.

The manifest lives at ~/.hermes/skills/.bundled_manifest and is a simple
newline-delimited list of skill names that have been offered to the user.
"""

import json
import logging
import os
import shutil
from pathlib import Path
from typing import List, Tuple

logger = logging.getLogger(__name__)


HERMES_HOME = Path(os.getenv("HERMES_HOME", Path.home() / ".hermes"))
SKILLS_DIR = HERMES_HOME / "skills"
MANIFEST_FILE = SKILLS_DIR / ".bundled_manifest"


def _get_bundled_dir() -> Path:
    """Locate the bundled skills/ directory in the repo."""
    return Path(__file__).parent.parent / "skills"


def _read_manifest() -> set:
    """Read the set of skill names already offered to the user."""
    if not MANIFEST_FILE.exists():
        return set()
    try:
        return set(
            line.strip()
            for line in MANIFEST_FILE.read_text(encoding="utf-8").splitlines()
            if line.strip()
        )
    except (OSError, IOError):
        return set()


def _write_manifest(names: set):
    """Write the manifest file."""
    MANIFEST_FILE.parent.mkdir(parents=True, exist_ok=True)
    MANIFEST_FILE.write_text(
        "\n".join(sorted(names)) + "\n",
        encoding="utf-8",
    )


def _discover_bundled_skills(bundled_dir: Path) -> List[Tuple[str, Path]]:
    """
    Find all SKILL.md files in the bundled directory.
    Returns list of (skill_name, skill_directory_path) tuples.
    """
    skills = []
    if not bundled_dir.exists():
        return skills

    for skill_md in bundled_dir.rglob("SKILL.md"):
        path_str = str(skill_md)
        if "/.git/" in path_str or "/.github/" in path_str or "/.hub/" in path_str:
            continue
        skill_dir = skill_md.parent
        skill_name = skill_dir.name
        skills.append((skill_name, skill_dir))

    return skills


def _compute_relative_dest(skill_dir: Path, bundled_dir: Path) -> Path:
    """
    Compute the destination path in SKILLS_DIR preserving the category structure.
    e.g., bundled/skills/mlops/axolotl -> ~/.hermes/skills/mlops/axolotl
    """
    rel = skill_dir.relative_to(bundled_dir)
    return SKILLS_DIR / rel


def sync_skills(quiet: bool = False) -> dict:
    """
    Sync bundled skills into ~/.hermes/skills/ using the manifest.

    - Skills whose names are already in the manifest are skipped (even if deleted by user).
    - New skills (not in manifest) are copied to SKILLS_DIR and added to the manifest.

    Returns:
        dict with keys: copied (list of names), skipped (int), total_bundled (int)
    """
    bundled_dir = _get_bundled_dir()
    if not bundled_dir.exists():
        return {"copied": [], "skipped": 0, "total_bundled": 0}

    SKILLS_DIR.mkdir(parents=True, exist_ok=True)
    manifest = _read_manifest()
    bundled_skills = _discover_bundled_skills(bundled_dir)
    copied = []
    skipped = 0

    for skill_name, skill_src in bundled_skills:
        if skill_name in manifest:
            skipped += 1
            continue

        dest = _compute_relative_dest(skill_src, bundled_dir)
        try:
            if dest.exists():
                # Skill dir exists (maybe user created one with same name) -- don't overwrite
                skipped += 1
            else:
                dest.parent.mkdir(parents=True, exist_ok=True)
                shutil.copytree(skill_src, dest)
                copied.append(skill_name)
                if not quiet:
                    print(f"  + {skill_name}")
        except (OSError, IOError) as e:
            if not quiet:
                print(f"  ! Failed to copy {skill_name}: {e}")

        manifest.add(skill_name)

    # Also copy DESCRIPTION.md files for categories (if not already present)
    for desc_md in bundled_dir.rglob("DESCRIPTION.md"):
        rel = desc_md.relative_to(bundled_dir)
        dest_desc = SKILLS_DIR / rel
        if not dest_desc.exists():
            try:
                dest_desc.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(desc_md, dest_desc)
            except (OSError, IOError) as e:
                logger.debug("Could not copy %s: %s", desc_md, e)

    _write_manifest(manifest)

    return {
        "copied": copied,
        "skipped": skipped,
        "total_bundled": len(bundled_skills),
    }


if __name__ == "__main__":
    print("Syncing bundled skills into ~/.hermes/skills/ ...")
    result = sync_skills(quiet=False)
    print(f"\nDone: {len(result['copied'])} new, {result['skipped']} skipped, "
          f"{result['total_bundled']} total bundled.")
