# Typoena firmware — common commands.
# Recipes source ~/export-esp.sh themselves (LIBCLANG_PATH + Xtensa GCC),
# so no per-shell setup is needed before calling just.

# Load firmware/.env (Wi-Fi creds for the network spikes) into recipe env.
# Absent .env is fine — the editor build ignores TW_* entirely.
set dotenv-load := true

esp_env := ". ~/export-esp.sh &&"
elf := "target/xtensa-esp32s3-espidf/release/firmware"
elf_wifi := "target/xtensa-esp32s3-espidf/release/wifi_tls"
elf_sd := "target/xtensa-esp32s3-espidf/release/sd_fat"
elf_git := "target/xtensa-esp32s3-espidf/release/git_smoke"
elf_git_push := "target/xtensa-esp32s3-espidf/release/git_push"
elf_git_sync := "target/xtensa-esp32s3-espidf/release/git_sync"

# Custom partition table (adds the `storage` FAT partition for the git working
# copy). Only the git-push flash applies it — the editor flash keeps the default
# single-app layout, so this change is scoped to Spike 7's finish.
partition_table := justfile_directory() + "/partitions.csv"

# Spike 7 Path 2 — env for the git2/libgit2 build. LIBGIT2_SRC points at the
# vendored libgit2 submodule (v1.9.4, matches libgit2-sys 0.18.5). pkgconfig/
# holds fake .pc files so libgit2-sys + libz-sys run in system mode and the
# symbols come from the esp-idf libgit2 component. Only the git recipes set this,
# so the editor build never compiles libgit2.
libgit2_src := justfile_directory() + "/components/libgit2/vendor"
git_env := "LIBGIT2_SRC=" + libgit2_src + " LIBGIT2_NO_VENDOR=1 PKG_CONFIG_ALLOW_CROSS=1 PKG_CONFIG_LIBDIR=" + justfile_directory() + "/pkgconfig"

# list recipes
default:
    @just --list

# compile (release)
build:
    {{esp_env}} cargo build --release --bin firmware

# build + flash + open serial monitor
flash:
    {{esp_env}} cargo run --release --bin firmware

# serial monitor only, with decoded backtraces
monitor:
    espflash monitor --elf {{elf}}

# Spike 6 — build the Wi-Fi + TLS spike (needs TW_WIFI_SSID / TW_WIFI_PASS in .env)
build-wifi:
    {{esp_env}} cargo build --release --bin wifi_tls

# Spike 6 — flash + monitor the Wi-Fi + TLS spike
flash-wifi:
    {{esp_env}} cargo run --release --bin wifi_tls

# serial monitor for the Wi-Fi spike, with decoded backtraces
monitor-wifi:
    espflash monitor --elf {{elf_wifi}}

# Spike 3 — build the SD/FAT spike (no .env needed)
build-sd:
    {{esp_env}} cargo build --release --bin sd_fat

# Spike 3 — flash + monitor the SD/FAT spike
flash-sd:
    {{esp_env}} cargo run --release --bin sd_fat

# serial monitor for the SD spike, with decoded backtraces
monitor-sd:
    espflash monitor --elf {{elf_sd}}

# Spike 7 Path 2 — build the git2/libgit2 smoke (git2 safe API on device)
build-git:
    {{esp_env}} {{git_env}} cargo build --release --bin git_smoke --features git

# Spike 7 Path 2 — flash + monitor the git2/libgit2 smoke
flash-git:
    {{esp_env}} {{git_env}} cargo run --release --bin git_smoke --features git

# serial monitor for the git smoke, with decoded backtraces
monitor-git:
    espflash monitor --elf {{elf_git}}

# Spike 7 finish — build the on-device git push (Wi-Fi + SNTP + flash-FAT + libgit2)
build-git-push:
    {{esp_env}} {{git_env}} cargo build --release --bin git_push --features git

# Spike 7 finish — flash + monitor. espflash applies the custom partition table
# (so the `storage` FAT partition exists) and sets 16 MB flash. Uses espflash
# directly, not `cargo run`, so the table is applied only to this binary.
flash-git-push: build-git-push
    espflash flash --monitor --partition-table {{partition_table}} --flash-size 16mb {{elf_git_push}}

# serial monitor for the git push, with decoded backtraces
monitor-git-push:
    espflash monitor --elf {{elf_git_push}}

# Milestone #2A — build the persistent-clone publish cycle (clone + fast-forward push)
build-git-sync:
    {{esp_env}} {{git_env}} cargo build --release --bin git_sync --features git

# Milestone #2A — flash + monitor. Applies the same custom partition table as
# git-push (the `storage` FAT partition holds the persistent clone).
flash-git-sync: build-git-sync
    espflash flash --monitor --partition-table {{partition_table}} --flash-size 16mb {{elf_git_sync}}

# serial monitor for the git sync, with decoded backtraces
monitor-git-sync:
    espflash monitor --elf {{elf_git_sync}}

# detect board, print chip/MAC/flash size
info:
    espflash board-info

# list USB serial devices
ports:
    @ls /dev/cu.usb* 2>/dev/null || echo "no USB serial device found"

# Pre-seed an SD card with a full clone of the notes repo, copied from a
# computer (the decision in docs/notes/git-sync-images-and-repo-size.md). The
# device never cold-clones 566 MB over Wi-Fi + mbedTLS; a laptop copies the
# clone onto the card via a reader, and the device only ever takes the `open` +
# fast-forward path. Dest on the card is a top-level `repo/`, matching the
# on-device /sd/repo the editor opens (roadmap v0.1).
#
#   just seed-sd ~/code/notes          # auto-detect the one removable card
#   just seed-sd ~/code/notes MYSD     # or name /Volumes/<name> if >1 card
#
# Everything the repo's .gitignore ignores is excluded (node_modules is 3.9 GB,
# gitignored, and never in .git) — the device needs .git + the checkout
# (~720 MB), not the JS deps or any local secrets like firmware/.env. Copying
# (not a fresh `git clone` of the local path) preserves origin → GitHub so
# on-device fetch/push still work; the recipe warns if origin isn't a remote.
sd_repo_dir := "repo"

seed-sd repo_src sd_volume="":
    #!/usr/bin/env bash
    set -euo pipefail

    # Resolve + validate the source clone.
    src="{{repo_src}}"
    src="${src%/}"                       # strip trailing slash
    [ -d "$src/.git" ] || { echo "error: '$src' is not a git repo (no .git/)"; exit 1; }

    origin="$(git -C "$src" remote get-url origin 2>/dev/null || true)"
    case "$origin" in
        https://*|git@*|ssh://*|git://*)
            echo "source origin: $origin" ;;
        "")
            echo "warning: '$src' has no 'origin' remote — the device can't fetch/push after seeding" ;;
        *)
            echo "warning: origin is '$origin' (looks like a local path, not a remote) —"
            echo "         the device fetch/push will fail; set origin to the GitHub URL first" ;;
    esac
    # Opportunistic cross-check against TW_REMOTE_URL (loaded from .env if present).
    if [ -n "${TW_REMOTE_URL:-}" ] && [ -n "$origin" ] && [ "$origin" != "${TW_REMOTE_URL}" ]; then
        echo "warning: origin ($origin) != TW_REMOTE_URL (${TW_REMOTE_URL})"
    fi

    # Detect the target card. Prefer an explicit /Volumes/<name>; else auto-detect
    # exactly one ejectable + external volume (refuse on 0 or >1 — a wrong guess
    # here means rsync --delete wipes the wrong disk's repo/).
    want="{{sd_volume}}"
    if [ -n "$want" ]; then
        vol="/Volumes/$want"
        [ -d "$vol" ] || { echo "error: '$vol' is not mounted"; exit 1; }
    else
        cands=()
        for v in /Volumes/*; do
            [ -d "$v" ] || continue
            info="$(diskutil info "$v" 2>/dev/null || true)"
            if echo "$info" | grep -qiE 'Ejectable:[[:space:]]+Yes' \
               && echo "$info" | grep -qiE 'Device Location:[[:space:]]+External|Removable Media:[[:space:]]+(Removable|Yes)'; then
                cands+=("$v")
            fi
        done
        case "${#cands[@]}" in
            0) echo "error: no removable card detected under /Volumes — insert an SD card (FAT32)"; exit 1 ;;
            1) vol="${cands[0]}" ;;
            *) echo "error: multiple removable volumes — name one as the 2nd arg (just seed-sd <src> <name>):"
               printf '  %s\n' "${cands[@]#/Volumes/}"; exit 1 ;;
        esac
    fi

    # The device won't mount a non-FAT card (esp_vfs_fat, format_if_mount_failed=false).
    fs="$(diskutil info "$vol" 2>/dev/null | grep -iE 'File System Personality' | sed 's/.*:[[:space:]]*//' || true)"
    case "$fs" in
        *FAT32*|*MS-DOS*) : ;;
        *) echo "warning: '$vol' is '$fs', not FAT32 — the device may fail to mount it" ;;
    esac

    dest="$vol/{{sd_repo_dir}}"
    echo "seeding: $src  ->  $dest"
    mkdir -p "$dest"

    # Build the exclude list from git's own ignore resolution (the repo's
    # .gitignore, plus .git/info/exclude and any global excludes). --directory
    # collapses a fully-ignored dir (node_modules/, 3.9 GB) to one line, so the
    # list stays tiny. Driving off git — not rsync's own dir-merge — means we
    # never list a tracked file or anything under .git/, so a .gitignore pattern
    # like `logs/` or `*.pack` can't silently corrupt the copied clone.
    ignore_list="$(mktemp)"
    trap 'rm -f "$ignore_list"' EXIT
    git -C "$src" -c core.quotePath=false ls-files \
        --others --ignored --exclude-standard --directory --no-empty-directory \
        > "$ignore_list"
    echo "excluding $(wc -l < "$ignore_list" | tr -d ' ') gitignored path(s) (incl. node_modules, .env)"

    # -rt (no perms/owner/symlinks — meaningless on FAT), --modify-window=1 for
    # FAT's 2 s timestamp granularity, --delete to mirror (re-runs stay clean),
    # -P for progress on the ~700 MB copy. Scoped to repo/, never the card root.
    rsync -rtP --delete --modify-window=1 --exclude-from="$ignore_list" "$src/" "$dest/"

    # Flush and eject so the card can go straight into Typoena.
    sync
    echo "seeded ok — ejecting $vol"
    if diskutil eject "$vol" >/dev/null 2>&1; then
        echo "✅ card ejected — remove it and insert into Typoena"
    else
        echo "⚠️  eject failed (a file may still be open) — eject '$vol' from Finder before removing"
    fi
