#!/usr/bin/env python3
"""Generate src/helpers/ota/OtaTargets.h — the target_id -> env-name table for OTA-capable envs.

A target_id is sha2-256:4(pio_env_name); it travels in beacons / the .mota manifest as 4 bytes. This
table lets a node (and motatool) print the human-readable env name for a target seen over the air, WITHOUT
ever transmitting the string. The set of envs is resolved from PlatformIO (every env whose build_flags
include ENABLE_OTA and do not include DISABLE_LORA_OTA), so explicit size/safety exclusions stay out.

Run:  ./meshcore/bin/python tools/mota/gen_targets.py            # resolves via `pio project config`
      ./meshcore/bin/python tools/mota/gen_targets.py cfg.json   # or reuse a cached config dump (faster)
"""
from __future__ import annotations

import json
import os
import subprocess
import sys
from pathlib import Path

import motalib as ml

ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT / "src" / "helpers" / "ota" / "OtaTargets.h"


def resolved_config():
    if len(sys.argv) > 1 and os.path.isfile(sys.argv[1]):
        return json.load(open(sys.argv[1]))
    pio = os.environ.get("PIO", "pio")
    out = subprocess.run([pio, "project", "config", "--json-output"],
                         cwd=ROOT, capture_output=True, text=True, check=True).stdout
    return json.loads(out)


def ota_envs(cfg):
    envs = []
    for section, opts in cfg:
        if not section.startswith("env:"):
            continue
        for k, v in opts:
            if k == "build_flags":
                flags = " ".join(v) if isinstance(v, list) else str(v)
                if "ENABLE_OTA" in flags and "DISABLE_LORA_OTA" not in flags:
                    envs.append(section[4:])
                break
    return sorted(set(envs))


def main():
    envs = ota_envs(resolved_config())
    rows = [(ml.target_id_for_env(e), e) for e in envs]

    seen = {}
    for tid, e in rows:                       # sha2-256:4 over ~300 names — verify no collisions
        if tid in seen and seen[tid] != e:
            print(f"WARNING: target_id collision 0x{tid:08x}: {seen[tid]!r} vs {e!r}", file=sys.stderr)
        seen[tid] = e
    rows.sort(key=lambda r: r[1].lower())     # by env name (readable, deterministic)

    out = [
        "#pragma once",
        "#include <stdint.h>",
        "",
        "// AUTO-GENERATED by tools/mota/gen_targets.py — do not edit by hand.",
        f"// {len(rows)} OTA-capable PlatformIO envs. Maps target_id (= sha2-256:4 of the env name, LE uint32)",
        "// to the human-readable env name, so a node/tool can name a target seen over the air WITHOUT",
        "// transmitting the string in the .mota / LoRa protocol. Regenerate when the OTA env set changes.",
        "",
        "namespace mesh { namespace ota {",
        "",
        "// target_id -> env name, or nullptr if unknown. Linear scan (table is small; lookups are rare).",
        "inline const char* ota_target_env_name(uint32_t target_id) {",
        "  static const struct { uint32_t id; const char* env; } T[] = {",
    ]
    out += [f'    {{ 0x{tid:08x}, "{e}" }},' for tid, e in rows]
    out += [
        "  };",
        "  for (unsigned i = 0; i < sizeof(T) / sizeof(T[0]); i++)",
        "    if (T[i].id == target_id) return T[i].env;",
        "  return nullptr;",
        "}",
        "",
        "} } // namespace mesh::ota",
    ]
    OUT.parent.mkdir(parents=True, exist_ok=True)
    OUT.write_text("\n".join(out) + "\n")
    print(f"wrote {OUT}  ({len(rows)} OTA envs)")


if __name__ == "__main__":
    main()
