# /// script
# requires-python = ">=3.9"
# dependencies = [
#   "mpbuild",
#   "mpflash",
#   "click"
# ]
# ///

from __future__ import annotations

from pathlib import Path
import sys

import click
import mpbuild.build as mpb  # type: ignore
import mpflash.basicgit as git
from mpbuild.board_database import Board, Database  # type: ignore
from mpflash.config import config as mpflash_config
from mpflash.custom import add_custom_firmware
from mpflash.versions import get_preview_mp_version, get_stable_mp_version
import mpflash.db.core  # noqa: F401  # initializes the peewee database connection
from mpflash.logger import log

ROOT = Path(__file__).resolve().parent


def copy_firmware(board: Board, variant: str | None, version: str, build: str, mpy_dir: Path, fw_path: Path, register: bool = True):
    """Copy the built firmware to the destination directory."""
    # find the build directory
    if board.physical_board:
        # all other ports
        if variant:
            build_dir = mpy_dir / f"ports/{board.port.name}/build-{board.name}-{variant}"
        else:
            build_dir = mpy_dir / f"ports/{board.port.name}/build-{board.name}"
    else:
        build_dir = mpy_dir / f"ports/{board.port.name}/build-{variant}"
    # what is the resulting name of the firmware files?
    fw_name = {
        "webassembly": "micropython.*",
        "unix": "micropython",
        "windows": "micropython.exe",
    }.get(board.port.name, "firmware.*")
    # create the destination directory
    if board.port.name in {"unix", "windows"}:
        # just a single file
        if register:
            add_custom_firmware(
                fw_path=build_dir / fw_name,
                force=True,
                description="Stand Alone build using mpbuild",
                custom=True,
            )
    elif board.port.name == "webassembly":
        # all webassembly binaries need to be in a single folder
        # Create a zip file with the firmware files
        import shutil
        import zipfile

        zip_name = f"{board.name}-{variant}-{version}"
        zip_path = build_dir / zip_name
        # Create a zip file with only the micropython.mjs and micropython.wasm files
        with zipfile.ZipFile(zip_path.with_suffix(".zip"), "w") as zf:
            for pattern in ["micropython.mjs", "micropython.wasm"]:
                for file in build_dir.glob(pattern):
                    zf.write(file, arcname=file.name)
        # copy the zip to the destination fw_path
        fw_path.mkdir(parents=True, exist_ok=True)
        dest_zip = fw_path / zip_path.with_suffix(".zip").name
        shutil.copy2(zip_path.with_suffix(".zip"), dest_zip)
        # add to mpflash list of custom firmwares using the build-dir path so
        # port_and_boardid_from_path can extract port/board_id via the
        # "/ports/{port}/build-{board_id}/" pattern in the path
        if register:
            add_custom_firmware(
                fw_path=zip_path.with_suffix(".zip"),
                force=True,
                description="Built using mpbuild",
                custom=True,
            )


def build_sa_port(
    port: str,
    version: str,
    mpy_dir: Path,
    fw_path: Path,
    variant: str | None = None,
    extras: list[str] | None = None,
    register: bool = True,
):
    """Build a single stand-alone port."""
    print("=" * 60)
    build_nr = ""
    if "preview" in version:
        ok = git.checkout_tag("master", mpy_dir)
        if describe := git.get_git_describe(mpy_dir):
            parts = describe.split("-", 3)
            if len(parts) >= 3:
                build_nr = parts[2]
    else:
        ok = git.checkout_tag(version, mpy_dir)
    if not ok:
        print(f"Failed to checkout {version} in {mpy_dir}")
        return False

    print(git.get_git_describe(mpy_dir))
    # un-cached database
    db = Database(mpy_dir)
    print(f"boards found {len(db.boards.keys())}")

    print(f"Building {version}, build {build_nr}")
    print("=" * 60)

    if port not in db.boards.keys():
        print(f"Board '{port}' not found for version '{version}'")
        return False

    _board = db.boards[port]
    if not variant:
        variant = None

    try:
        mpb.clean_board(board=_board.name, variant=variant, mpy_dir=str(mpy_dir))
        mpb.build_board(board=_board.name, variant=variant, mpy_dir=mpy_dir, extra_args=extras or [])
    except SystemExit as e:
        print(f"Failed to build {port} {variant} {version}: {e}")
        return False
    # for unix ports - mark the firmware as executable
    if _board.port.name in {"unix"}:
        for fw_file in fw_path.glob("micropython*"):
            fw_file.chmod(fw_file.stat().st_mode | 0o111)
    copy_firmware(fw_path=fw_path, board=_board, variant=variant, version=version, build=build_nr, mpy_dir=mpy_dir, register=register)
    return True


# Default variant per port — webassembly uses 'pyscript', everything else 'standard'
_PORT_DEFAULT_VARIANT: dict[str, str] = {
    "webassembly": "pyscript",
}


@click.command()
@click.argument("port")
@click.option(
    "--variant",
    "-v",
    default=None,
    show_default=True,
    help="Build variant (default: 'pyscript' for webassembly, 'standard' for all other ports).",
)
@click.option("--version", default=None, help="MicroPython version tag (default: stable).")
@click.option("--extra", "-e", default="", show_default=True, help="Extra build arguments.")
@click.option("--fw-path", default="./firmware", show_default=True, type=click.Path(), help="Destination folder for firmware files.")
@click.option("--register/--no-register", default=True, show_default=True, help="Register the firmware with mpflash after building.")
def main(port: str, variant: str | None, version: str | None, extra: str, fw_path: str, register: bool):
    """Build a stand-alone MicroPython binary for PORT and register it with mpflash."""
    # mpflash resolves firmware_folder from MPFLASH_FIRMWARE env var or platform default.
    # Set MPFLASH_FIRMWARE in your shell to override (e.g. on WSL pointing to Windows Downloads).
    print(f"Firmware folder: {mpflash_config.firmware_folder}")

    # Apply per-port variant default when the user didn't specify one
    if variant is None:
        variant = _PORT_DEFAULT_VARIANT.get(port, "standard")
    print(f"Using variant: {variant}")

    if version is None or version == "stable":
        version = get_stable_mp_version()
        print(f"Using stable version: {version}")
    elif version == "preview":
        version = get_preview_mp_version()
        print(f"Using preview version: {version}")

    mpy_dir = ROOT / "repos/micropython"
    mpy_dir = mpy_dir.resolve().absolute()
    assert mpy_dir.exists(), f"Micropython repo not found in {mpy_dir}"

    if sys.platform == "win32":
        # Builds only on linux / WSL
        log.error("Building stand-alone ports is not supported on Windows. Use WSL or Linux.")
        raise SystemExit(1)

    extras = extra.split() if extra else []

    success = build_sa_port(
        port=port,
        version=version,
        mpy_dir=mpy_dir,
        fw_path=Path(fw_path),
        variant=variant or None,
        extras=extras,
        register=register,
    )
    if not success:
        raise SystemExit(1)


if __name__ == "__main__":
    main()
