#!/usr/bin/env python3
"""One-time migration: rename Granola meeting files to new naming convention.

Old pattern: {Title}_{id_prefix}.notes.md / .transcript.md
New pattern: {id_prefix}_{Title}.granola.notes.md / .granola.transcript.md

Also deduplicates files where emoji and non-emoji variants coexist
(e.g. "🗓️_Title_abc.notes.md" alongside "Title_abc.notes.md").

Usage:
    python scripts/migrate_filenames.py --dry-run   # preview renames + dedup
    python scripts/migrate_filenames.py              # execute renames + dedup

Delete this script after successful migration.
"""

from __future__ import annotations

import argparse
import re
import sys
import unicodedata
from collections import defaultdict
from pathlib import Path

import yaml


def _strip_emojis(text: str) -> str:
    """Strip emoji characters (So), variation selectors, and ZWJ."""
    return "".join(
        c for c in text if unicodedata.category(c) != "So" and c not in ("\u200d", "\ufe0f")
    )


def _sanitize_title(title: str) -> str:
    """Sanitize a title for use in filenames (matches sync logic)."""
    stripped = _strip_emojis(title)
    sanitized = re.sub(r"\s*/\s*", "___", stripped)
    sanitized = re.sub(r"[^\w\-]", "_", sanitized)
    sanitized = sanitized.replace("___", "\x00")
    sanitized = re.sub(r"_+", "_", sanitized)
    sanitized = sanitized.replace("\x00", "___")
    return sanitized.strip("_")


def _parse_frontmatter(text: str) -> dict:
    """Extract frontmatter from markdown text."""
    m = re.match(r"^---\s*\n(.*?)\n---\s*\n", text, re.DOTALL)
    if not m:
        return {}
    try:
        fm = yaml.safe_load(m.group(1))
    except yaml.YAMLError:
        return {}
    return fm if isinstance(fm, dict) else {}


def _make_new_basename(title: str, granola_id: str, calendar_uid: str | None = None) -> str:
    """Compute new basename: {uid_prefix}_{Sanitized_Title}."""
    sanitized = _sanitize_title(title)
    uid = calendar_uid if calendar_uid else granola_id
    uid_prefix = uid[:8] if len(uid) >= 8 else uid
    return f"{uid_prefix}_{sanitized}"


# Keys for grouping: (parent_dir, granola_id_prefix, suffix_type)
GroupKey = tuple[str, str, str]


def _collect_files(meetings_dir: Path) -> dict[GroupKey, list[Path]]:
    """Scan all old-pattern files, group by (dir, granola_id[:8], suffix_type)."""
    groups: dict[GroupKey, list[Path]] = defaultdict(list)

    for md_file in sorted(meetings_dir.rglob("*.md")):
        name = md_file.name

        # Skip files already in new format
        if ".granola." in name or ".notion." in name:
            continue

        if name.endswith(".notes.md"):
            suffix_type = "notes"
        elif name.endswith(".transcript.md"):
            suffix_type = "transcript"
        else:
            continue

        try:
            text = md_file.read_text(encoding="utf-8")
        except (OSError, UnicodeDecodeError):
            continue

        fm = _parse_frontmatter(text)
        granola_id = str(fm.get("granola_id", ""))
        if not granola_id:
            continue

        id_prefix = granola_id[:8] if len(granola_id) >= 8 else granola_id
        key: GroupKey = (str(md_file.parent), id_prefix, suffix_type)
        groups[key].append(md_file)

    return dict(groups)


def _pick_best(files: list[Path]) -> Path:
    """Pick the best file from a group of duplicates.

    Prefers: largest file first, then shortest filename (no emoji cruft).
    """
    return max(files, key=lambda f: (f.stat().st_size, -len(f.name)))


def migrate(meetings_dir: Path, dry_run: bool = True) -> dict[str, int]:
    """Walk meetings/, deduplicate, and rename to new pattern."""
    counts = {"renamed": 0, "deduped": 0, "skipped_new": 0, "errors": 0}

    # Count already-new files
    for md_file in meetings_dir.rglob("*.md"):
        if ".granola." in md_file.name or ".notion." in md_file.name:
            counts["skipped_new"] += 1

    groups = _collect_files(meetings_dir)

    for (_parent_dir, _id_prefix, suffix_type), files in sorted(groups.items()):
        # Pick the best file if duplicates exist
        best = _pick_best(files)
        duplicates = [f for f in files if f != best]

        # Read frontmatter from the best file
        try:
            text = best.read_text(encoding="utf-8")
        except (OSError, UnicodeDecodeError):
            counts["errors"] += 1
            continue

        fm = _parse_frontmatter(text)
        granola_id = str(fm.get("granola_id", ""))
        if not granola_id:
            counts["errors"] += 1
            continue

        calendar_uid_raw = fm.get("calendar_uid")
        calendar_uid = str(calendar_uid_raw) if calendar_uid_raw else None
        title = str(fm.get("title", "Untitled"))

        new_suffix = f".granola.{suffix_type}.md"
        new_base = _make_new_basename(title, granola_id, calendar_uid)
        new_name = f"{new_base}{new_suffix}"
        new_path = best.parent / new_name

        # Report duplicates
        for dup in duplicates:
            rel_dup = dup.relative_to(meetings_dir)
            if dry_run:
                print(f"  DELETE (dup) {rel_dup}")
            else:
                dup.unlink()
            counts["deduped"] += 1

        # Rename best → new
        if new_path != best:
            rel_old = best.relative_to(meetings_dir)
            rel_new = new_path.relative_to(meetings_dir)
            if dry_run:
                print(f"  {rel_old} -> {rel_new}")
            else:
                best.rename(new_path)
            counts["renamed"] += 1
        else:
            counts["skipped_new"] += 1

    return counts


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--dry-run", action="store_true", help="Preview renames without executing")
    parser.add_argument(
        "--meetings-dir",
        type=Path,
        default=Path(__file__).resolve().parent.parent / "memory" / "meetings",
        help="Path to memory/meetings/ directory",
    )
    args = parser.parse_args()

    if not args.meetings_dir.exists():
        print(f"Error: {args.meetings_dir} does not exist", file=sys.stderr)
        sys.exit(1)

    mode = "DRY RUN" if args.dry_run else "LIVE"
    print(f"Migration ({mode}): {args.meetings_dir}")
    print()

    counts = migrate(args.meetings_dir, dry_run=args.dry_run)

    print()
    print(f"Renamed:        {counts['renamed']}")
    print(f"Deduped:        {counts['deduped']}")
    print(f"Already new:    {counts['skipped_new']}")
    print(f"Errors:         {counts['errors']}")

    if args.dry_run and (counts["renamed"] > 0 or counts["deduped"] > 0):
        print()
        print("Run without --dry-run to execute.")


if __name__ == "__main__":
    main()
