#!/usr/bin/env bash
#
# Build EVERY image this household needs, from one CoreSystemConfig.json.
#
# This is the normal way to use tools/. The individual builders still exist and still
# work, but rebuilding one image on its own is the unusual case: the images are a set
# that has to agree with itself, and the whole reason a kiosk knows the container
# host's address is that both were generated in the same pass from the same file.
# Building the set is therefore the default, and building one is the exception.
#
#   sudo -E tools/build-all.sh              # the core pair + every configured kiosk
#   sudo -E tools/build-all.sh --core       # just the twinned pair
#   sudo -E tools/build-all.sh --kiosks     # just the kiosks
#   sudo -E tools/build-all.sh --dry-run    # validate + list what would be built
#
# Every image is stamped with the same pair ID, so a drawer full of USB sticks can be
# checked against each other later: same ID means they were built from the same
# config and agree on every address and token.
#
# WHAT COMES OUT CONTAINS SECRETS. Wi-Fi PSK, service tokens, MQTT and HA credentials
# are burnt into these images — that is the point (nothing to configure post-install),
# and it makes every ISO a credential. .gitignore keeps them out of the repo; wiping
# old USB sticks is on you.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/coreconfig.sh
source "${SCRIPT_DIR}/lib/coreconfig.sh"

MODE="all"
DRY_RUN="false"
for arg in "$@"; do
  case "$arg" in
    --core)    MODE="core" ;;
    --kiosks)  MODE="kiosks" ;;
    --dry-run) DRY_RUN="true" ;;
    -h|--help) sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
    *) core_die "Unknown argument '$arg'. Try --help." ;;
  esac
done

core_load
[[ "$DRY_RUN" == "true" ]] || core_require_root "$@"

PAIR_ID="$(core_pair_id)"

# Which kiosks are configured, and which builder each needs.
mapfile -t KIOSK_ROWS < <(python3 - "$CORE_CONFIG_PATH" <<'PY'
import json, sys
for k in json.load(open(sys.argv[1])).get("kiosks", []):
    print(f"{k['type']}\t{k['hostname']}\t{k.get('friendly_name','')}")
PY
)

# Audio endpoints, with the architecture each one needs. Both architectures are built
# when both are configured — they're two different toolchains producing two different
# artifacts (an amd64 ISO and an arm64 .img), not one image that runs on both.
mapfile -t AUDIO_ROWS < <(python3 - "$CORE_CONFIG_PATH" <<'PY'
import json, sys
for a in json.load(open(sys.argv[1])).get("audio_endpoints", []):
    print(f"{a['arch']}\t{a['hostname']}\t{a.get('friendly_name','')}")
PY
)

cat <<EOF

============================================================================
 SmartestHome — build all
============================================================================
  Config    : ${CORE_CONFIG_PATH}
  Pair ID   : ${PAIR_ID}
  Subnet    : ${CORE_SUBNET_PREFIX}.0/24

  Core pair (twinned — each knows the other's address by derivation):
    container host  ${CORE_CONTAINER_HOST_IP}  ${CORE_CONTAINER_HOST_NAME}
    LLM host        ${CORE_LLM_HOST_IP}  ${CORE_LLM_HOST_NAME}
    the link        ${CORE_OLLAMA_HOST}

  Kiosks + audio endpoints (all pointed at ${CORE_CONTAINER_HOST_IP} by derivation):
EOF
if [[ ${#KIOSK_ROWS[@]} -eq 0 && ${#AUDIO_ROWS[@]} -eq 0 ]]; then
  echo "    (none configured)"
fi
for row in "${KIOSK_ROWS[@]:-}"; do
  [[ -n "$row" ]] || continue
  IFS=$'\t' read -r ktype khost kname <<< "$row"
  printf '    %-18s %-24s %s\n' "$ktype" "$khost" "$kname"
done
for row in "${AUDIO_ROWS[@]:-}"; do
  [[ -n "$row" ]] || continue
  IFS=$'\t' read -r aarch ahost aname <<< "$row"
  printf '    %-18s %-24s %s\n' "audio/${aarch}" "$ahost" "$aname"
done
echo "
  Building  : ${MODE}$([[ "$DRY_RUN" == "true" ]] && echo "  (DRY RUN — nothing will be built)")
============================================================================"

if [[ "$DRY_RUN" == "true" ]]; then
  echo "
Config is valid and the above is what would be built. Re-run without --dry-run.
"
  exit 0
fi

BUILT=()
FAILED=()

run_build() {
  local label="$1"; shift
  core_log "Building ${label}"
  # One image failing must not abandon the rest: an ISO build is long, and losing an
  # hour of successful builds because the last one hit a mirror timeout would be a
  # poor trade. Failures are collected and reported together at the end.
  if "$@"; then
    BUILT+=("$label")
  else
    core_warn "${label} FAILED — continuing with the rest"
    FAILED+=("$label")
  fi
}

if [[ "$MODE" == "all" || "$MODE" == "core" ]]; then
  run_build "container host" "${SCRIPT_DIR}/build-container-host-iso.sh"
  run_build "LLM host"       "${SCRIPT_DIR}/build-llm-host-iso.sh"
fi

if [[ "$MODE" == "all" || "$MODE" == "kiosks" ]]; then
  for row in "${KIOSK_ROWS[@]}"; do
    IFS=$'\t' read -r ktype khost _ <<< "$row"
    builder="${SCRIPT_DIR}/build-${ktype}-iso.sh"
    if [[ ! -x "$builder" ]]; then
      core_warn "No builder for kiosk type '${ktype}' (${builder}) — skipping ${khost}"
      FAILED+=("${ktype}/${khost} (no builder)")
      continue
    fi
    run_build "${ktype} — ${khost}" "$builder" "$khost"
  done

  # Both architectures now produce ONE PRE-NAMED IMAGE PER ROOM, so every device in
  # the household is identified by the build rather than by something typed in
  # afterwards. arm64 used to be the exception (one generic .img, hostname set in
  # Raspberry Pi Imager at flash time); baking it in costs a full rpi-image-gen run per
  # room, which is the tradeoff `build.arm64_prebake: false` exists to undo.
  arm64_generic_done="false"
  for row in "${AUDIO_ROWS[@]:-}"; do
    [[ -n "$row" ]] || continue
    IFS=$'\t' read -r aarch ahost _ <<< "$row"
    case "$aarch" in
      amd64)
        run_build "audio endpoint (amd64) — ${ahost}" \
          "${SCRIPT_DIR}/build-audio-endpoint-iso-amd64.sh" "$ahost"
        ;;
      arm64)
        if [[ "$CORE_ARM64_PREBAKE" != "true" ]]; then
          # Generic mode: one image for every arm64 room, named in Pi Imager.
          if [[ "$arm64_generic_done" == "true" ]]; then
            core_log "arm64 generic image already built — ${ahost} flashes the same .img (name it in Pi Imager)"
            continue
          fi
          run_build "audio endpoint (arm64, generic image)" \
            "${SCRIPT_DIR}/build-audio-endpoint-image-arm64.sh" "$ahost"
          arm64_generic_done="true"
        else
          run_build "audio endpoint (arm64) — ${ahost}" \
            "${SCRIPT_DIR}/build-audio-endpoint-image-arm64.sh" "$ahost"
        fi
        ;;
      *)
        core_warn "Unknown audio endpoint arch '${aarch}' — skipping ${ahost}"
        FAILED+=("audio/${ahost} (bad arch)")
        ;;
    esac
  done
fi

OUTPUT_DIR="${CORE_REPO_ROOT}/${CORE_BUILD_OUTPUT_DIR}"

echo "
============================================================================
 Build all — done (pair ${PAIR_ID})
============================================================================"
if [[ ${#BUILT[@]} -gt 0 ]]; then
  echo "  Built:"
  for b in "${BUILT[@]}"; do echo "    ✓ $b"; done
fi
if [[ ${#FAILED[@]} -gt 0 ]]; then
  echo "  FAILED:"
  for f in "${FAILED[@]}"; do echo "    ✗ $f"; done
fi
echo "
  Images in: ${OUTPUT_DIR}
"
ls -lh "$OUTPUT_DIR" 2>/dev/null | tail -n +2 | awk '{printf "    %-52s %s\n", $9, $5}' || true
cat <<EOF

  Install the container host FIRST — the kiosks and the LLM host are all
  clients of it, and every one of them was built expecting it at
  ${CORE_CONTAINER_HOST_IP}.

  Two things still need a human, because neither exists at build time:
    1. HA_TOKEN  — a Long-Lived Access Token from Home Assistant's own UI, which
       can't be created until HA is running. Put it in CoreSystemConfig.json and
       rebuild, or edit identity.env on the container host.
    2. TRUSTED_ENTITY_PREFIXES — the real entity_id prefixes your Private BLE
       Device setup produces. The default is a guess and it is the highest-risk
       unknown in Phase 6.
EOF

[[ ${#FAILED[@]} -eq 0 ]]
