#!/usr/bin/env python3
"""Build versioned boilerplate snapshot tarballs.

Run from the project root:

  python scripts/build_boilerplate_snapshots.py --version 2026.04.02.1
  python scripts/build_boilerplate_snapshots.py --type react_app --version 2026.04.02.1
  python scripts/build_boilerplate_snapshots.py --type wordpress_companion --version 2026.04.02.1

Version format: YYYY.MM.DD.N  where N is the daily build index (1, 2, ...).
Snapshots are written to boilerplates/snapshots/.

WordPress companion snapshots prefer the live deployable archive at
``boilerplates/wordpress_companion/p2hs-companion.tar.gz``. If that file is
missing, the script tars the remaining source tree under
``boilerplates/wordpress_companion/``.
"""
from __future__ import annotations

import argparse
import os
import shutil
import tarfile

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BOILERPLATES_DIR = os.path.join(ROOT, "boilerplates")
SNAPSHOTS_DIR = os.path.join(BOILERPLATES_DIR, "snapshots")

SCAFFOLD_TYPES = ["react_app", "nuxt_app", "html"]
WORDPRESS_COMPANION_TYPE = "wordpress_companion"
ALL_TYPES = [*SCAFFOLD_TYPES, WORDPRESS_COMPANION_TYPE]

LIVE_COMPANION_ARCHIVE = "p2hs-companion.tar.gz"

_EXCLUDE = frozenset({
    ".git", "__pycache__", ".venv", "venv",
    "dist", ".output", ".nuxt", ".next",
    "node_modules", "coverage", "build",
    "local-bak",
})


def build_snapshot(project_type: str, version: str) -> str:
    src = os.path.join(BOILERPLATES_DIR, project_type)
    if not os.path.isdir(src):
        raise ValueError(f"Source directory not found: {src}")

    os.makedirs(SNAPSHOTS_DIR, exist_ok=True)
    out = os.path.join(SNAPSHOTS_DIR, f"{project_type}@{version}.tar.gz")

    with tarfile.open(out, "w:gz") as tf:
        for dirpath, dirnames, filenames in os.walk(src):
            dirnames[:] = [d for d in dirnames if d not in _EXCLUDE]
            for filename in filenames:
                if filename.endswith(".p2hs-local.bak"):
                    continue
                full_path = os.path.join(dirpath, filename)
                arcname = os.path.relpath(full_path, src)
                tf.add(full_path, arcname=arcname)

    size_kb = os.path.getsize(out) // 1024
    print(f"  built  {os.path.relpath(out, ROOT)}  ({size_kb} KB)")
    return out


def build_wordpress_companion_snapshot(version: str) -> str:
    companion_dir = os.path.join(BOILERPLATES_DIR, WORDPRESS_COMPANION_TYPE)
    live_archive = os.path.join(companion_dir, LIVE_COMPANION_ARCHIVE)

    os.makedirs(SNAPSHOTS_DIR, exist_ok=True)
    out = os.path.join(SNAPSHOTS_DIR, f"{WORDPRESS_COMPANION_TYPE}@{version}.tar.gz")

    if os.path.isfile(live_archive):
        shutil.copy2(live_archive, out)
        size_kb = os.path.getsize(out) // 1024
        print(
            f"  built  {os.path.relpath(out, ROOT)}  ({size_kb} KB)"
            f"  from {os.path.relpath(live_archive, ROOT)}"
        )
        return out

    if not os.path.isdir(companion_dir):
        raise ValueError(f"Companion directory not found: {companion_dir}")

    source_items = [
        name
        for name in os.listdir(companion_dir)
        if name not in _EXCLUDE and name != LIVE_COMPANION_ARCHIVE
    ]
    if not source_items:
        raise ValueError(
            f"No companion source or {LIVE_COMPANION_ARCHIVE} found in {companion_dir}"
        )

    with tarfile.open(out, "w:gz") as tf:
        for item in source_items:
            full_path = os.path.join(companion_dir, item)
            if os.path.isdir(full_path):
                for dirpath, dirnames, filenames in os.walk(full_path):
                    dirnames[:] = [d for d in dirnames if d not in _EXCLUDE]
                    for filename in filenames:
                        if filename.endswith(".p2hs-local.bak"):
                            continue
                        path = os.path.join(dirpath, filename)
                        arcname = os.path.relpath(path, companion_dir)
                        tf.add(path, arcname=arcname)
            else:
                tf.add(full_path, arcname=item)

    size_kb = os.path.getsize(out) // 1024
    print(f"  built  {os.path.relpath(out, ROOT)}  ({size_kb} KB)  from source tree")
    return out


def build_snapshot_for_type(project_type: str, version: str) -> str:
    if project_type == WORDPRESS_COMPANION_TYPE:
        return build_wordpress_companion_snapshot(version)
    return build_snapshot(project_type, version)


def main() -> None:
    parser = argparse.ArgumentParser(description="Build boilerplate snapshot tarballs")
    parser.add_argument(
        "--type",
        choices=ALL_TYPES,
        metavar="TYPE",
        help=(
            f"Build only this type ({', '.join(ALL_TYPES)}). "
            f"Default: scaffold types only ({', '.join(SCAFFOLD_TYPES)})."
        ),
    )
    parser.add_argument(
        "--version",
        required=True,
        metavar="VERSION",
        help="Version string, e.g. 2026.04.02.1",
    )
    args = parser.parse_args()

    types = [args.type] if args.type else SCAFFOLD_TYPES
    print(f"Building boilerplate snapshots  version={args.version}")
    ok = 0
    for t in types:
        try:
            build_snapshot_for_type(t, args.version)
            ok += 1
        except ValueError as exc:
            print(f"  skip   {t}: {exc}")

    print(f"Done - {ok}/{len(types)} snapshots built -> {os.path.relpath(SNAPSHOTS_DIR, ROOT)}/")


if __name__ == "__main__":
    main()
