#!/usr/bin/env -S bash -c 'exec "$BASH" "$(dirname "$0")/_hermes-python" "$0" "$@"'
"""Generate the bootstrap pin fragments inside the installers.

The installers bootstrap uv (and, on Windows, git) BEFORE any checkout
exists, so they cannot read pm/lock.json at run time: they are fetched
standalone (`curl | sh`, `irm | iex`) and there is no JSON parser they can
rely on that early (no jq guarantee; python is what uv installs on Windows).
Instead, this script derives a plain-data fragment from pm/lock.json — the
SAME authority the pm package manager uses for every managed tool — and
splices it between markers in each installer. The bytes are stored, the
truth is derived, and the drift test (tests/scripts/test_bootstrap_pins_fragment.py)
fails when they disagree.

Run after bumping a bootstrapped tool in pm/lock.json:

    python3 scripts/gen-bootstrap-pins.py          # rewrite fragments
    python3 scripts/gen-bootstrap-pins.py --check  # exit 1 on drift
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[1]
PINS_PATH = REPO_ROOT / "pm" / "lock.json"

# Standalone installers cannot read the shared mirror layout before checkout.
sys.path.insert(0, str(REPO_ROOT))
from pm.artifact_mirror import mirror_url  # noqa: E402

BEGIN_MARK = "# --- BEGIN GENERATED: bootstrap pins (scripts/gen-bootstrap-pins.py) ---"
END_MARK = "# --- END GENERATED: bootstrap pins ---"

_POSIX_TARGETS = ("linux-x64", "linux-arm64", "linux-x64-musl", "linux-arm64-musl", "darwin-x64", "darwin-arm64")
_WINDOWS_TARGETS = ("win32-x64", "win32-arm64")


def _load_lock() -> dict:
    return json.loads(PINS_PATH.read_text(encoding="utf-8-sig"))


def _load_uv_pin() -> dict:
    entry = _load_lock()["packages"]["uv"]
    for target in _POSIX_TARGETS + _WINDOWS_TARGETS:
        _validate_artifact("uv", entry, target)
    return entry


def _load_git_pin() -> dict:
    # Windows targets only. install.ps1 bootstraps the pinned git-for-windows
    # artifact because Windows needs it to clone. macOS and Linux use the
    # machine's git, so the pin table declares those targets as reasoned gaps
    # and there is nothing for the sh installers to stage.
    entry = _load_lock()["packages"]["git"]
    for target in _WINDOWS_TARGETS:
        _validate_artifact("git", entry, target)
    return entry


def _validate_artifact(tool: str, entry: dict, target: str) -> None:
    spec = entry["artifacts"][target]  # KeyError on a missing target is the point
    if spec.get("missing"):
        # The table says upstream ships nothing here, but this generator was
        # told to emit that target. One of the two is wrong, and a KeyError
        # further down would not say which.
        raise ValueError(
            f"{tool} pin for {target}: the table declares a gap "
            f"({spec['missing']}), so there is nothing to generate"
        )
    if not spec["url"].startswith("https://"):
        raise ValueError(f"{tool} pin for {target}: url must be https")
    if len(spec["sha256"]) != 64:
        raise ValueError(f"{tool} pin for {target}: sha256 must be 64 hex chars")


def _sh_fragment(uv: dict) -> str:
    lines = [
        BEGIN_MARK,
        "# Derived from pm/lock.json. DO NOT EDIT BY HAND:",
        "# run scripts/gen-bootstrap-pins.py after a pin bump.",
        f'UV_PIN_VERSION="{uv["version"]}"',
        "",
        "# Sets UV_PIN_URL + UV_PIN_SHA256 for a <os>-<arch> target key.",
        "uv_bootstrap_pin() {",
        '    case "$1" in',
    ]
    for target in _POSIX_TARGETS:
        entry = uv["artifacts"][target]
        lines += [
            f"        {target})",
            f'            UV_PIN_URL="{entry["url"]}"',
            f'            UV_PIN_MIRROR="{mirror_url(entry["sha256"])}"',
            f'            UV_PIN_SHA256="{entry["sha256"]}"',
            "            ;;",
        ]
    lines += [
        "        *)",
        '            UV_PIN_URL=""',
        '            UV_PIN_SHA256=""',
        "            return 1",
        "            ;;",
        "    esac",
        "}",
        END_MARK,
    ]
    return "\n".join(lines)


def _ps1_fragment(uv: dict, git: dict) -> str:
    lines = [
        BEGIN_MARK,
        "# Derived from pm/lock.json. DO NOT EDIT BY HAND:",
        "# run scripts/gen-bootstrap-pins.py after a pin bump.",
        f'$script:UvPinVersion = "{uv["version"]}"',
        "$script:UvPinFiles = @{",
    ]
    for target in _WINDOWS_TARGETS:
        entry = uv["artifacts"][target]
        lines += [
            f'    "{target}" = @{{',
            f'        Url    = "{entry["url"]}"',
            f'        MirrorUrl = "{mirror_url(entry["sha256"])}"',
            f'        Sha256 = "{entry["sha256"]}"',
            "    }",
        ]
    lines += [
        "}",
        "",
        f'$script:GitPinVersion = "{git["version"]}"',
        "$script:GitPinFiles = @{",
    ]
    for target in _WINDOWS_TARGETS:
        entry = git["artifacts"][target]
        lines += [
            f'    "{target}" = @{{',
            f'        Url    = "{entry["url"]}"',
            f'        MirrorUrl = "{mirror_url(entry["sha256"])}"',
            f'        Sha256 = "{entry["sha256"]}"',
            "    }",
        ]
    lines += [
        "}",
        END_MARK,
    ]
    return "\n".join(lines)


def _splice(path: Path, fragment: str, check: bool) -> bool:
    """Replace the marked block in *path*. Returns True when up to date."""
    raw = path.read_bytes()
    had_bom = raw.startswith(b"\xef\xbb\xbf")
    text = raw.decode("utf-8-sig")
    begin = text.index(BEGIN_MARK)  # ValueError on missing marker is the point
    end = text.index(END_MARK) + len(END_MARK)
    # Match the file's line endings (install.ps1 is CRLF) so the generated
    # block does not leave the script with mixed newlines.
    newline = "\r\n" if "\r\n" in text else "\n"
    fragment = fragment.replace("\n", newline)
    updated = text[:begin] + fragment + text[end:]
    if updated == text:
        return True
    if not check:
        path.write_bytes((b"\xef\xbb\xbf" if had_bom else b"") + updated.encode("utf-8"))
    return False


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--check",
        action="store_true",
        help="exit 1 when the committed fragments drift from pm/lock.json",
    )
    args = parser.parse_args()

    uv = _load_uv_pin()
    git = _load_git_pin()
    results = {
        "scripts/install.sh": _splice(
            REPO_ROOT / "scripts" / "install.sh", _sh_fragment(uv), args.check
        ),
        "scripts/install.ps1": _splice(
            REPO_ROOT / "scripts" / "install.ps1", _ps1_fragment(uv, git), args.check
        ),
        # setup-hermes.sh deliberately holds NO fragment: it runs after a
        # checkout exists, so it reads the pin straight from pm/lock.json
        # at run time (the pm.sh-derived bootstrap). The fragment exists
        # only for the curl|sh / irm|iex installers that bootstrap BEFORE
        # any checkout exists.
    }
    stale = [name for name, fresh in results.items() if not fresh]
    if args.check and stale:
        print(
            "bootstrap pin fragments drifted from pm/lock.json in: "
            f"{', '.join(stale)}\nrun: python3 scripts/gen-bootstrap-pins.py",
            file=sys.stderr,
        )
        return 1
    for name in stale:
        print(f"updated {name}")
    if not stale:
        print("fragments up to date")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
