#!/usr/bin/env python3

import json
import re
import shutil
from pathlib import Path


BUILD_ARTIFACTS = (
    "bootloader.bin",
    "partitions.bin",
    "firmware.bin",
    "firmware.elf",
    "firmware.map",
)

MANIFEST_PARTS = (
    {"path": "bootloader.bin", "offset": 4096},
    {"path": "partitions.bin", "offset": 32768},
    {"path": "boot_app0.bin", "offset": 57344},
    {"path": "firmware.bin", "offset": 65536},
)

VERSION_PATTERN = re.compile(r'Eggduino-Firmware V([^"\\]+)')


def extract_firmware_version(header_text: str) -> str:
    match = VERSION_PATTERN.search(header_text)
    if not match:
        raise ValueError("Could not extract firmware version from include/EggDuino.h")
    return match.group(1).strip()


def build_manifest(version: str) -> dict:
    return {
        "name": "EggDuino ESP32 Firmware",
        "version": version,
        "new_install_prompt_erase": True,
        "builds": [
            {
                "chipFamily": "ESP32",
                "parts": list(MANIFEST_PARTS),
            }
        ],
    }


def package_bundle(build_dir: Path, boot_app0_path: Path, header_path: Path, output_dir: Path) -> None:
    build_dir = Path(build_dir)
    boot_app0_path = Path(boot_app0_path)
    header_path = Path(header_path)
    output_dir = Path(output_dir)

    if output_dir.exists():
        shutil.rmtree(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    for artifact_name in BUILD_ARTIFACTS:
        artifact_path = build_dir / artifact_name
        if not artifact_path.is_file():
            raise FileNotFoundError(f"Missing build artifact: {artifact_path}")
        shutil.copy2(artifact_path, output_dir / artifact_name)

    if not boot_app0_path.is_file():
        raise FileNotFoundError(f"Missing boot_app0.bin: {boot_app0_path}")
    shutil.copy2(boot_app0_path, output_dir / "boot_app0.bin")

    version = extract_firmware_version(header_path.read_text(encoding="utf-8"))
    manifest_path = output_dir / "manifest.json"
    manifest_path.write_text(
        json.dumps(build_manifest(version), indent=4) + "\n",
        encoding="utf-8",
    )


def _get_boot_app0_path(env) -> Path:
    framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif32")
    if not framework_dir:
        raise FileNotFoundError("PlatformIO framework-arduinoespressif32 package is not installed")

    boot_app0_path = Path(framework_dir) / "tools" / "partitions" / "boot_app0.bin"
    if not boot_app0_path.is_file():
        raise FileNotFoundError(f"Could not find boot_app0.bin in {boot_app0_path.parent}")
    return boot_app0_path


def _package_platformio_bundle(target, source, env) -> None:
    del target, source
    project_dir = Path(env.subst("$PROJECT_DIR"))
    package_bundle(
        build_dir=Path(env.subst("$BUILD_DIR")),
        boot_app0_path=_get_boot_app0_path(env),
        header_path=project_dir / "include" / "EggDuino.h",
        output_dir=project_dir / "firmware",
    )
    print(f"Packaged firmware bundle to {project_dir / 'firmware'}")


if "Import" in globals():
    Import("env")
    env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", _package_platformio_bundle)
