#!/usr/bin/env python3
"""Publish spec-trace to PyPI.

Usage: bin/publish [--minor|--major] [--dry-run]

Requires UV_PUBLISH_TOKEN in the environment or in .env.
Bumps the version in pyproject.toml and src/spec_trace/__init__.py, builds,
publishes, then commits and tags.
"""

from __future__ import annotations

import argparse
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path

PYPROJECT = Path("pyproject.toml")
INIT = Path("src/spec_trace/__init__.py")


def load_token_from_env_file() -> None:
    env_file = Path(".env")
    if not env_file.exists():
        return
    for raw in env_file.read_text(encoding="utf-8").splitlines():
        line = raw.strip()
        if not line or line.startswith("#") or not line.startswith("UV_PUBLISH_TOKEN="):
            continue
        os.environ.setdefault("UV_PUBLISH_TOKEN", line.split("=", 1)[1].strip().strip("\"'"))


def current_version() -> str:
    match = re.search(r'^version = "(.+?)"', PYPROJECT.read_text(encoding="utf-8"), re.MULTILINE)
    if not match:
        sys.exit("Error: could not find version in pyproject.toml")
    return match.group(1)


def bump(version: str, part: str) -> str:
    major, minor, patch = (int(x) for x in version.split("."))
    if part == "major":
        return f"{major + 1}.0.0"
    if part == "minor":
        return f"{major}.{minor + 1}.0"
    return f"{major}.{minor}.{patch + 1}"


def write_version(old: str, new: str) -> None:
    PYPROJECT.write_text(
        PYPROJECT.read_text(encoding="utf-8").replace(f'version = "{old}"', f'version = "{new}"', 1),
        encoding="utf-8",
    )
    INIT.write_text(
        INIT.read_text(encoding="utf-8").replace(f'__version__ = "{old}"', f'__version__ = "{new}"', 1),
        encoding="utf-8",
    )


def run(*command: str) -> None:
    subprocess.run(command, check=True)


def main() -> int:
    parser = argparse.ArgumentParser(description="Publish spec-trace to PyPI")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("--minor", action="store_true", help="bump the minor version")
    group.add_argument("--major", action="store_true", help="bump the major version")
    parser.add_argument("--dry-run", action="store_true", help="build only; do not publish or tag")
    args = parser.parse_args()

    if not PYPROJECT.exists():
        sys.exit("Error: run from the repository root")

    load_token_from_env_file()
    if not args.dry_run and not os.environ.get("UV_PUBLISH_TOKEN"):
        sys.exit("Error: UV_PUBLISH_TOKEN is not set (environment or .env)")

    old = current_version()
    new = bump(old, "major" if args.major else "minor" if args.minor else "patch")
    print(f"Version {old} -> {new}")

    # Gates must pass before anything is published.
    run("uv", "run", "pytest", "-q")
    run("uv", "run", "ruff", "check", ".")
    run("uv", "run", "pyright", "src/")
    run("uv", "run", "spec-trace")

    write_version(old, new)
    shutil.rmtree("dist", ignore_errors=True)
    run("uv", "build")

    if args.dry_run:
        print("Dry run: built but not published. Reverting the version bump.")
        write_version(new, old)
        return 0

    run("uv", "publish")
    run("git", "add", str(PYPROJECT), str(INIT))
    run("git", "commit", "-m", f"chore: release v{new}")
    run("git", "tag", f"v{new}")
    print(f"Published v{new}. Push with: git push && git push --tags")
    return 0


if __name__ == "__main__":
    sys.exit(main())
