#!/usr/bin/env python3
"""Version bump utility — single command to bump version across all locations.

Usage:
    python3 scripts/docs/version_bump.py <major|minor|patch> [--dry-run] [--deploy]
    python3 scripts/docs/version_bump.py set <version> [--dry-run] [--deploy]
    python3 scripts/docs/version_bump.py show

Flags:
    --dry-run     Show what would change without writing files
    --skip-roadmap  Skip ROADMAP.md update
    --deploy      Print deploy reminder after version bump

Locations updated (skips any that don't exist):
    1. VERSION                        (single source of truth)
    2. pyproject.toml                 (version = "X.Y.Z")
    3. package.json                   ("version": "X.Y.Z")
    4. CHANGELOG.md                   ([Unreleased] -> [X.Y.Z] - YYYY-MM-DD)
    5. ROADMAP.md                     (marks active phase as Complete with version)
    6. CLAUDE.md                      (version reference)
    7. docs/**/*.md                   (verified_version in YAML frontmatter)
    8. MEMORY.md                      (**Version**: X.Y.Z in Claude memory file)
"""

import json
import re
import sys
from datetime import date
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]

VERSION_FILE = ROOT / "VERSION"
CHANGELOG = ROOT / "CHANGELOG.md"
ROADMAP = ROOT / "ROADMAP.md"
CLAUDE_MD = ROOT / "CLAUDE.md"
DOCS_DIR = ROOT / "docs"


def _find_file(names: list[str]) -> Path | None:
    """Find a file by trying multiple relative paths from ROOT."""
    for name in names:
        p = ROOT / name
        if p.exists():
            return p
    return None


def read_current_version() -> str:
    if VERSION_FILE.exists():
        return VERSION_FILE.read_text().strip()
    return "0.1.0"


def parse_version(v: str) -> tuple[int, int, int]:
    parts = v.split(".")
    if len(parts) != 3:
        raise ValueError(f"Invalid version: {v}")
    return int(parts[0]), int(parts[1]), int(parts[2])


def bump_version(current: str, bump_type: str) -> str:
    major, minor, patch = parse_version(current)
    if bump_type == "major":
        return f"{major + 1}.0.0"
    elif bump_type == "minor":
        return f"{major}.{minor + 1}.0"
    elif bump_type == "patch":
        return f"{major}.{minor}.{patch + 1}"
    raise ValueError(f"Unknown bump type: {bump_type}")


def update_version_file(new_version: str, dry_run: bool) -> None:
    if dry_run:
        print(f"  [dry-run] VERSION: {read_current_version()} -> {new_version}")
        return
    VERSION_FILE.write_text(new_version + "\n")
    print(f"  VERSION: -> {new_version}")


def update_toml(new_version: str, dry_run: bool) -> None:
    """Update version in pyproject.toml, Cargo.toml, or similar."""
    toml = _find_file(["pyproject.toml", "Cargo.toml"])
    if not toml:
        return
    content = toml.read_text()
    updated = re.sub(
        r'^(version\s*=\s*")[^"]*(")',
        rf'\g<1>{new_version}\2',
        content, count=1, flags=re.MULTILINE,
    )
    if updated == content:
        return
    if dry_run:
        print(f"  [dry-run] {toml.name}: -> {new_version}")
        return
    toml.write_text(updated)
    print(f"  {toml.name}: -> {new_version}")


def update_package_json(new_version: str, dry_run: bool) -> None:
    pj = _find_file(["package.json"])
    if not pj:
        return
    data = json.loads(pj.read_text())
    old_v = data.get("version", "?")
    if dry_run:
        print(f"  [dry-run] package.json: {old_v} -> {new_version}")
        return
    data["version"] = new_version
    pj.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
    print(f"  package.json: -> {new_version}")


def update_changelog(new_version: str, dry_run: bool) -> None:
    if not CHANGELOG.exists():
        return
    content = CHANGELOG.read_text()
    today = date.today().isoformat()
    replacement = f"## [Unreleased]\n\n---\n\n## [{new_version}] - {today}"
    updated = re.sub(r'^## \[Unreleased\].*$', replacement, content, count=1, flags=re.MULTILINE)
    if updated == content:
        print("  CHANGELOG.md: no [Unreleased] section found, skipping")
        return
    if dry_run:
        print(f"  [dry-run] CHANGELOG.md: [Unreleased] -> [{new_version}] - {today}")
        return
    CHANGELOG.write_text(updated)
    print(f"  CHANGELOG.md: [Unreleased] -> [{new_version}] - {today}")


def update_roadmap(new_version: str, dry_run: bool) -> None:
    """Update active phase in ROADMAP.md to Complete with new version."""
    if not ROADMAP.exists():
        return
    content = ROADMAP.read_text(encoding="utf-8")
    today = date.today().isoformat()

    # Find the first phase NOT marked as Complete (the active phase)
    phase_match = re.search(
        r'^(## Phase \d+[A-Z]?:.+)\n\n> \*\*Status:\s*(?!Complete).*$',
        content, re.MULTILINE,
    )

    if phase_match:
        status_line_start = phase_match.end() - len(phase_match.group().split("\n")[-1])
        status_line_end = phase_match.end()
        new_status = f"> **Status: Complete** | Version {new_version} ({today})"
        if dry_run:
            print(f"  [dry-run] ROADMAP.md: active phase -> Complete | Version {new_version}")
            return
        content = content[:status_line_start] + new_status + content[status_line_end:]
        ROADMAP.write_text(content, encoding="utf-8")
        print(f"  ROADMAP.md: active phase -> Complete | Version {new_version} ({today})")
    else:
        print("  ROADMAP.md: no active phase found, skipping")


def update_claude_md(new_version: str, dry_run: bool) -> None:
    if not CLAUDE_MD.exists():
        return
    content = CLAUDE_MD.read_text()
    updated = re.sub(r'\bv\d+\.\d+\.\d+\b', f"v{new_version}", content)
    if updated == content:
        return
    if dry_run:
        print(f"  [dry-run] CLAUDE.md: -> v{new_version}")
        return
    CLAUDE_MD.write_text(updated)
    print(f"  CLAUDE.md: -> v{new_version}")


def update_doc_frontmatter(new_version: str, dry_run: bool) -> None:
    if not DOCS_DIR.exists():
        return
    count = 0
    for md in sorted(DOCS_DIR.rglob("*.md")):
        content = md.read_text(encoding="utf-8")
        if not content.startswith("---"):
            continue
        updated = re.sub(
            r'^(verified_version:\s*)\S+',
            rf'\g<1>{new_version}',
            content, count=1, flags=re.MULTILINE,
        )
        if updated != content:
            count += 1
            if not dry_run:
                md.write_text(updated, encoding="utf-8")
    prefix = "[dry-run] " if dry_run else ""
    print(f"  {prefix}docs/**/*.md: {count} file(s) frontmatter -> {new_version}")


def update_memory_md(new_version: str, dry_run: bool) -> None:
    """Update version line in Claude memory MEMORY.md file."""
    # Try common memory directory patterns
    memory_dirs = [
        Path.home() / ".claude" / "projects" / f"-{str(ROOT).replace('/', '-').lstrip('-')}" / "memory",
        Path.home() / ".claude" / "projects" / ROOT.name / "memory",
    ]
    for memory_dir in memory_dirs:
        memory_file = memory_dir / "MEMORY.md"
        if memory_file.exists():
            content = memory_file.read_text(encoding="utf-8")
            today = date.today().isoformat()
            # Match pattern: **Version**: X.Y.Z (YYYY-MM-DD) or just **Version**: X.Y.Z
            updated = re.sub(
                r'(\*\*Version\*\*:\s*)\d+\.\d+\.\d+(?:\s*\(\d{4}-\d{2}-\d{2}\))?',
                rf'\g<1>{new_version} ({today})',
                content, count=1,
            )
            if updated != content:
                if dry_run:
                    print(f"  [dry-run] MEMORY.md: -> {new_version} ({today})")
                else:
                    memory_file.write_text(updated, encoding="utf-8")
                    print(f"  MEMORY.md: -> {new_version} ({today})")
                return
    print("  MEMORY.md: NOT FOUND, skipping")


def show_versions() -> None:
    print("Current versions:")
    print(f"  VERSION:       {read_current_version()}")

    toml = _find_file(["pyproject.toml", "Cargo.toml"])
    if toml:
        m = re.search(r'^version\s*=\s*"([^"]*)"', toml.read_text(), re.MULTILINE)
        print(f"  {toml.name}: {m.group(1) if m else '?'}")

    pj = _find_file(["package.json"])
    if pj:
        data = json.loads(pj.read_text())
        print(f"  package.json:  {data.get('version', '?')}")

    if CHANGELOG.exists():
        for line in CHANGELOG.read_text().splitlines():
            if line.startswith("## ["):
                print(f"  CHANGELOG.md:  {line.strip()}")
                break

    if ROADMAP.exists():
        m = re.search(r'> \*\*Status: Complete\*\* \| Version (\S+)', ROADMAP.read_text())
        if m:
            print(f"  ROADMAP.md:    {m.group(1)}")

    if CLAUDE_MD.exists():
        m = re.search(r'\bv(\d+\.\d+\.\d+)\b', CLAUDE_MD.read_text())
        if m:
            print(f"  CLAUDE.md:     v{m.group(1)}")

    if DOCS_DIR.exists():
        versions: dict[str, int] = {}
        for md in DOCS_DIR.rglob("*.md"):
            content = md.read_text(encoding="utf-8")
            m = re.search(r'^verified_version:\s*(\S+)', content, re.MULTILINE)
            if m:
                v = m.group(1)
                versions[v] = versions.get(v, 0) + 1
        if versions:
            summary = ", ".join(f"{v} ({n})" for v, n in sorted(versions.items()))
            print(f"  docs/**/*.md:  {summary}")


def main() -> None:
    if len(sys.argv) < 2:
        print("Usage: version_bump.py <major|minor|patch|show> [--dry-run] [--deploy]")
        print("       version_bump.py set <version> [--dry-run] [--deploy]")
        sys.exit(1)

    action = sys.argv[1]
    dry_run = "--dry-run" in sys.argv
    skip_roadmap = "--skip-roadmap" in sys.argv
    deploy = "--deploy" in sys.argv

    if action == "show":
        show_versions()
        return

    current = read_current_version()

    if action == "set":
        if len(sys.argv) < 3:
            print("Usage: version_bump.py set <version>")
            sys.exit(1)
        new_version = sys.argv[2]
        parse_version(new_version)
    elif action in ("major", "minor", "patch"):
        new_version = bump_version(current, action)
    else:
        print(f"Unknown action: {action}")
        sys.exit(1)

    print(f"{'[DRY RUN] ' if dry_run else ''}Bumping version: {current} -> {new_version}")
    update_version_file(new_version, dry_run)
    update_toml(new_version, dry_run)
    update_package_json(new_version, dry_run)
    update_changelog(new_version, dry_run)
    if not skip_roadmap:
        update_roadmap(new_version, dry_run)
    else:
        print("  ROADMAP.md: skipped (--skip-roadmap)")
    update_claude_md(new_version, dry_run)
    update_doc_frontmatter(new_version, dry_run)
    update_memory_md(new_version, dry_run)
    print(f"\n{'Would update' if dry_run else 'Updated'} all version references to {new_version}")

    if deploy:
        print("\n  Deploy reminder:")
        print("  - Rebuild: docker compose build && docker compose up -d")
        print("  - Or run your project's deploy command (make dev, make prod, etc.)")
        print("  - Verify: check logs for errors after restart")


if __name__ == "__main__":
    main()
