#!/usr/bin/env bash
# provision_macmesh_node.sh — take a blank Heltec V4 to a finished MacMesh node.
#
# Three unattended esptool writes, no per-board configuration, no serial session:
#
#   0x000000  mesh-loader merged image  loader + Meshtastic + both filesystems
#   0x0A0000  MacMesh MeshCore (ota_0)  your fork: UART GPIO47/48 @57600 + BLE
#   0xB50000  Meshtastic filesystem     Rev8 serial config, US, hop limit 7
#
# Why the third write exists: Meshtastic's Serial Module pins are runtime config
# with no build-time flag (userPrefs.jsonc has no serial keys), and Meshtastic's
# USB serial API does not work under a dual-boot loader — so it cannot be
# configured the normal way. Writing its LittleFS directly sidesteps both.
#
# Identity: nothing here carries one. The Meshtastic filesystem image has
# security.private_key stripped, and MeshCore generates its keypair on first
# boot from the SX1262 hardware RNG. Every board provisioned with these exact
# images still ends up a distinct node. See docs/software-dualboot.md.
#
# Using the node: reset boots the loader, which waits ~2s and then boots the
# last-used firmware. Press PRG during those 2s to boot the other one.
#
# Usage: scripts/provision_macmesh_node.sh [-p PORT] [-y]

set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
VENV_PY="$SCRIPT_DIR/.heltec-flash-cache/venv/bin/python3"
D="$REPO_ROOT/firmware/dualboot"

LOADER="$D/mesh-loader/firmware-merged-heltec_v4.bin"
# Ours, from firmware/dualboot/macmesh-loader: an Espressif second-stage
# bootloader (Apache-2.0) and a partition table generated from our own CSV.
# Written over the merged image's copies so the only third-party code left on a
# finished board is the Meshtastic application itself.
OWN_BOOTLOADER="$D/macmesh-loader/.pio/build/heltec_v4/bootloader.bin"
OWN_PARTITIONS="$D/macmesh-loader/.pio/build/heltec_v4/partitions.bin"
OWN_LOADER="$D/macmesh-loader/.pio/build/heltec_v4/firmware.bin"
MESHCORE="$D/meshcore-macmesh-meshloader.bin"
MT_FS="$D/meshtastic-fs-meshloader.bin"

PORT=""; ASSUME_YES=0
while [ $# -gt 0 ]; do
    case "$1" in
        -p|--port) PORT="$2"; shift 2 ;;
        -y|--yes) ASSUME_YES=1; shift ;;
        -h|--help) sed -n '2,26p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
        *) echo "unknown arg: $1" >&2; exit 1 ;;
    esac
done

log()  { echo "==> $*"; }
die()  { echo "error: $*" >&2; exit 1; }

for f in "$LOADER" "$MESHCORE" "$MT_FS" "$OWN_BOOTLOADER" "$OWN_PARTITIONS" "$OWN_LOADER"; do
    [ -f "$f" ] || die "missing image: $f"
done
[ -x "$VENV_PY" ] || die "toolchain venv not found; run scripts/flash_heltec_v4_dualboot.sh once to create it"

(cd "$D" && shasum -a 256 -c provisioning-images.sha256 >/dev/null 2>&1) \
    && log "provisioning images match their recorded checksums" \
    || echo "warning: image checksums not verified" >&2

detect_port() {
    [ -n "$PORT" ] && { echo "$PORT"; return; }
    local c=(); for p in /dev/cu.usbmodem* /dev/ttyACM* /dev/ttyUSB*; do [ -e "$p" ] && c+=("$p"); done
    case "${#c[@]}" in
        0) die "no USB serial device found" ;;
        1) echo "${c[0]}" ;;
        *) die "multiple serial devices (${c[*]}); pass -p" ;;
    esac
}

# Retries across the V4's USB re-enumeration. After every write the board
# re-enumerates, and the next esptool call reliably hits one of
# "port is busy", "Device not configured", or a vanished device node for a few
# seconds. Ten attempts with a 5s gap covers it; fewer used to silently lose a
# whole write.
esp() {
    local attempt out p
    for attempt in $(seq 1 10); do
        p="$(detect_port 2>/dev/null || true)"
        if [ -n "$p" ]; then
            out="$("$VENV_PY" -m esptool --chip esp32s3 --port "$p" --baud 921600 "$@" 2>&1)" && { echo "$out"; return 0; }
        fi
        sleep 5
    done
    echo "${out:-no serial port appeared}" >&2
    return 1
}

# Write, then prove it landed. esptool reports success per-chunk, and a write
# that never ran at all leaves whatever the previous image put there -- which
# looks fine unless you check. Refusing to finish without a verify is the whole
# point when the same images go onto a batch of boards.
write_verified() {  # offset image label
    local addr="$1" img="$2" lbl="$3"
    log "$lbl -> $addr"
    esp write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB "$addr" "$img" >/dev/null \
        || die "failed to write $lbl at $addr"
    local out
    out="$(esp verify_flash "$addr" "$img")" || die "could not verify $lbl at $addr"
    echo "$out" | grep -qi "verify OK" || die "$lbl at $addr does not match the image after writing"
    log "  verified"
}

log "Identifying board"
info="$(esp flash_id)" || die "could not reach the board. Hold PRG while plugging in USB, then retry."
echo "$info" | sed -n 's/^\(Chip is\|MAC\|Detected flash size\).*/  &/p'
echo "$info" | grep -q "ESP32-S3" || die "not an ESP32-S3"
echo "$info" | grep -q "Detected flash size: 16MB" || die "board does not report 16MB flash; this layout needs all 16MB"
MAC="$(echo "$info" | sed -n 's/^MAC: //p' | tr -d ': ' | head -1)"

if [ "$ASSUME_YES" -ne 1 ]; then
    echo
    echo "About to ERASE and provision the board on $(detect_port) (MAC $MAC)."
    echo "Any existing node identity on it will be destroyed."
    read -r -p "Continue? [y/N] " r; case "$r" in [yY]|[yY][eE][sS]) ;; *) die "aborted" ;; esac
fi

log "Erasing flash"
esp erase_flash >/dev/null
# Order matters: the merged loader image spans 0x0-0xC50000, so it contains its
# own stock MeshCore and its own blank Meshtastic filesystem. Writes 2 and 3
# overwrite those regions afterwards. Verifying 0x0 against the merged image
# would therefore fail by design -- only the two regions we own are checked.
log "1/6  base image (supplies the Meshtastic application; the rest is overwritten below)"
esp write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0x0 "$LOADER" >/dev/null \
    || die "failed to write the loader image at 0x0"
write_verified 0x0      "$OWN_BOOTLOADER" "2/6  bootloader (ours)"
write_verified 0x8000   "$OWN_PARTITIONS" "3/6  partition table (ours)"
write_verified 0x20000  "$OWN_LOADER"     "4/6  MacMesh Loader (ours)"
write_verified 0xa0000  "$MESHCORE"       "5/6  MacMesh MeshCore (ota_0)"
write_verified 0xb50000 "$MT_FS"          "6/6  Meshtastic config filesystem"

log "Provisioned board $MAC"
echo
echo "Reset the board. It boots the loader, waits ~2s, then boots a firmware;"
echo "press PRG during those 2s to switch between MeshCore and Meshtastic."
echo "Both talk to the Macintosh client on the header UART (GPIO47/48)."
