#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# build-mister.sh — MiSTer FPGA (Minimig) platform build script
#
# Creates bootable HDF images for the MiSTer FPGA Minimig Amiga core.
# Image layout: Pure RDB with Amiga partitions (no FAT boot partition).
# Optional RTG via UAEGFX (Picasso96) and optional networking (Roadshow).
# No genet/wifipi hardware (MiSTer uses its own NIC).
#
# Sources shared functions from lib/common.sh.
# ---------------------------------------------------------------------------

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/lib/common.sh"

SCRIPT_VERSION="2026-05-06-mister-v0.81"
echo "==> Amiga-Imager MiSTer build script version: ${SCRIPT_VERSION}" >&2

# Safety: this script requires bash (arrays are used).
if [[ -z "${BASH_VERSION:-}" ]]; then
  echo "ERROR: This script must be run with bash (e.g. 'bash build-mister.sh ...')." >&2
  exit 2
fi
if [[ ${BASH_VERSINFO[0]} -lt 3 ]]; then
  echo "ERROR: bash 3.x+ required (macOS /bin/bash is fine)." >&2
  exit 2
fi

# Maximum parallel ADF extractions (tunable via environment; default 4)
MAX_PARALLEL_ADF="${MAX_PARALLEL_ADF:-4}"
if ! [[ "$MAX_PARALLEL_ADF" =~ ^[1-9][0-9]*$ ]]; then
  echo "==> WARN: MAX_PARALLEL_ADF='$MAX_PARALLEL_ADF' is not a valid positive integer; using default 4" >&2
  MAX_PARALLEL_ADF=4
fi

# ---------------------------------------------------------------------------
# MiSTer Minimig build pipeline
# ---------------------------------------------------------------------------
# - Pure RDB HDF image (no MBR, no FAT boot partition)
# - Optional RTG via UAEGFX/Picasso96
# - Optional networking via Roadshow
# - No Emu68 boot bundle, no config.txt, no cmdline.txt
# - No genet/wifipi hardware, no Unicam
# ---------------------------------------------------------------------------

echo "==> USING SCRIPT: $0"

IMG="${1:?Output .hdf path required}"
KICK_ROM="${2:?Kickstart ROM path required}"

# Positional arguments (same format as PiStorm):
#   <img> <kick.rom> [<iso>] <assets_dir> [flags...]
ISO_PATH=""
ASSETS_DIR=""
if [[ $# -ge 4 && "${4#--}" == "$4" ]]; then
  ISO_PATH="${3:-}"
  ASSETS_DIR="${4:?Assets folder path required}"
  shift 4 || true
else
  ASSETS_DIR="${3:?Assets folder path required}"
  shift 3 || true
fi

# Treat "-" / "none" as explicit "no ISO".
case "$(printf "%s" "${ISO_PATH:-}" | tr '[:upper:]' '[:lower:]')" in
  ""|"-"|"none"|"null") ISO_PATH="" ;;
esac

# -----------------------------------------------------------------------------
# Defaults (GUI should override)
# -----------------------------------------------------------------------------
TOTAL_GB=2
DH0_MIB=0                # 0 = use all available space
DH1_MIB=0
AMIGA_FS="Ffs"            # Ffs | FfsDC | Pfs3
PFS3_AIO=""
TRANSFER_DIR=""
TRANSFER_PARTITION=""
ADF_DIR=""                 # Optional: merged ADF folder
ICON_SET="standard"        # standard | glowicons
PACKAGES=""                # comma-separated ids
LOCALES=""                 # comma-separated locale codes
CSV_DIR=""                 # explicit InputFiles directory
AMINET_DIR=""              # staging dir with pre-downloaded .lha files from Aminet
AMINET_RULES_CSV=""        # optional CSV override for Aminet install rules
AMINET_RULES_CSV_RESOLVED=""
DOWNLOAD_ONLY="1"          # 1=ignore Assets programs/packages
IBROWSE_DIR=""             # optional staging dir for IBrowse installer
IBROWSE_KEY=""             # optional IBrowse key file
INCLUDE_WBDOCK=0           # 1=install WBDock2
INCLUDE_IGAME=0            # 1=add iGame entry to WBDock config
INCLUDE_LUMIFTP=0          # 1=add LumiFTP entry to WBDock config
INCLUDE_AMIGAAMP=0         # 1=add AmigaAMP entry to WBDock config
WHDLOAD_KICKSTARTS_DIR=""  # optional folder with Kickstart ROM files for WHDLoad
IMP3_REG=""                # optional IMP3 registration code
IMP3_REG_FILE=""           # optional path to imp3.reg file
BOING_BAG_1=""             # optional: path to Boing Bag 1 .lha
BOING_BAG_2=""             # optional: path to Boing Bag 2 .lha
APPLY_ICON_POSITIONS="1"   # 1|0 apply icon positions from CSV at build time
DETECTED_WB_VERSION="3.2"  # auto-detected from ADF filename
BACKDROP_ENABLED="1"       # WBConfig.prefs backdrop: "1"=enabled, "0"=disabled

# RTG (optional — MiSTer uses UAEGFX, not VideoCore)
SCREEN_WIDTH=""
SCREEN_HEIGHT=""
SCREEN_FPS=""
SCREEN_INTERLACE=""
SCREEN_MODE_ID=""          # P96 ModeID hex (empty = native chipset only)
WB_DEPTH=""
WB_DEPTH_HEX=""
P96_ICOMP_ARCHIVE=""       # optional iComp Picasso96 .lha

# Networking (optional — Roadshow)
NETWORK_STACK="none"       # none | roadshowDemo | roadshowFull
ROADSHOW_VARIANT=""
ROADSHOW_ARCHIVE=""

DEBUG=0
DEBUG_EXPORT_DIR=""

# N-partition support
PART_NAMES=()
PART_LABELS=()
PART_SIZES=()       # numeric MiB or "remaining"
PART_FSTYPES=()     # Ffs | FfsIntl | FfsDC | Pfs3
PART_BOOTABLE=()    # "1" or "0"
PART_BOOTPRI=()     # integer

# Selected icon ADFs (resolved during ADF classification)
SELECTED_ICONSET_ADFS=()

DEFAULT_HST="$HOME/Library/Application Support/Amiga-Imager/Tools/hst-imager"
DEFAULT_HST_AMIGA="$HOME/Library/Application Support/Amiga-Imager/Tools/hst-amiga"
HST_IMAGER="${HST_IMAGER:-$DEFAULT_HST}"
HST_AMIGA="${HST_AMIGA:-$DEFAULT_HST_AMIGA}"
AMIGA_TOOLS="${AMIGA_TOOLS:-}"


# ===========================================================================
# MiSTer-specific functions
# ===========================================================================

harden_first_boot_scripts_mister() {
  local sys_root="$1"

  echo "==> Hardening first-boot scripts (MiSTer Minimig)"

  # 1) OnetimeRunWB launcher:
  #    Run only known scripts (localesettings/RunAll) so package payload
  #    files (.lha, metadata) are never executed as scripts.
  #    MiSTer has no EMU68INFO — use reboot command instead.
  local tmp_wb="$STAGE/OnetimeRunWB.harden"
  cat > "$tmp_wb" <<'__ONETIMERUNWB_HARDEN__'
; Amiga Imager hardening: execute only known first-boot scripts, silently.
WAIT 1
IF $FIRSTTIMEBOOTWB EQ "TRUE"
   ECHO "DELETE >NIL: SYS:WBStartup/OneTimeRunWB.info ALL FORCE *nDELETE >NIL: SYS:WBStartup/OneTimeRunWB ALL FORCE" to S:OneTimeRun/DeleteOneTimeRunWB
   ; Restore disabled WBStartup entries early so they are not lost if optional
   ; installers fail later in this script.
   COPY >NIL: "SYS:WBStartup/Disabled/#?" TO "SYS:WBStartup/" ALL FORCE CLONE

   IF EXISTS "S:OneTimeRunWB/RunAll"
      Execute "S:OneTimeRunWB/RunAll" >NIL:
      Delete "S:OneTimeRunWB/RunAll" >NIL:
   ENDIF

   IF EXISTS "S:OneTimeRunWB/localesettings"
      Execute "S:OneTimeRunWB/localesettings" >NIL:
      Delete "S:OneTimeRunWB/localesettings" >NIL:
   ENDIF
   DELETE >NIL: "SYS:WBStartup/Disabled" ALL FORCE
   Wait 1
   Sys:c/diskchange DH0:
   Sys:c/reboot
   ENDCLI
ENDIF
__ONETIMERUNWB_HARDEN__
  make_lf_only "$tmp_wb"
  fs_copy_file "$tmp_wb" "$sys_root/WBStartup/OnetimeRunWB"

  # 2) iBrowse codec patch helper: skip when iBrowse/spatch payload is not present.
  local tmp_ib="$STAGE/Ibrowse.harden"
  if fs_pull_file "$sys_root/S/OneTimeRun/Ibrowse" "$tmp_ib"; then
    echo "==> Hardening: found OneTimeRun/Ibrowse script from Assets"
    local codecs_output
    codecs_output="$("$HST_IMAGER" fs dir "$sys_root/Programs/Ibrowse/codecs" 2>/dev/null || true)"
    local has_codecs=0
    if echo "$codecs_output" | grep -q "gif\.codec\|jpeg\.codec\|png\.codec"; then
      has_codecs=1
    fi
    echo "==> Hardening: IBrowse codecs present = $has_codecs"
    if [[ "$has_codecs" -eq 1 ]]; then
      cat > "$tmp_ib" <<'__IBROWSE_HARDEN__'
; Amiga Imager hardening: patch iBrowse codecs only when prerequisites exist.
IF NOT EXISTS C:spatch
   SKIP END
ENDIF
IF NOT EXISTS SYS:Programs/Ibrowse/codecs/gif.codec
   SKIP END
ENDIF
IF NOT EXISTS SYS:Programs/Ibrowse/codecs040fpu/gif.codec.spatch
   SKIP END
ENDIF
C:spatch -oSYS:Programs/Ibrowse/codecs/gif.codec -pSYS:Programs/Ibrowse/codecs040fpu/gif.codec.spatch SYS:Programs/Ibrowse/codecs/gif.codec
C:spatch -oSYS:Programs/Ibrowse/codecs/jpeg.codec -pSYS:Programs/Ibrowse/codecs040fpu/jpeg.codec.spatch SYS:Programs/Ibrowse/codecs/jpeg.codec
C:spatch -oSYS:Programs/Ibrowse/codecs/png.codec -pSYS:Programs/Ibrowse/codecs040fpu/png.codec.spatch SYS:Programs/Ibrowse/codecs/png.codec
C:spatch -oSYS:Programs/Ibrowse/codecs/xbm.codec -pSYS:Programs/Ibrowse/codecs040fpu/xbm.codec.spatch SYS:Programs/Ibrowse/codecs/xbm.codec
C:spatch -oSYS:Programs/Ibrowse/codecs/imagedecode.module -pSYS:Programs/Ibrowse/codecs040fpu/imagedecode.module.spatch SYS:Programs/Ibrowse/codecs/imagedecode.module
DELETE SYS:Programs/Ibrowse/codecs040fpu/#? SYS:Programs/Ibrowse/codecs040fpu ALL FORCE QUIET
MAKEDIR SYS:Programs/Ibrowse/cache
LAB END
__IBROWSE_HARDEN__
    else
      echo "==> IBrowse codecs not present; neutralizing OneTimeRun/Ibrowse"
      cat > "$tmp_ib" <<'__IBROWSE_NOOP__'
; Amiga Imager: IBrowse codecs not present, skipping patch.
LAB END
__IBROWSE_NOOP__
    fi
    make_lf_only "$tmp_ib"
    fs_copy_file "$tmp_ib" "$sys_root/S/OneTimeRun/Ibrowse"
  fi
}


# ===========================================================================
# MAIN PIPELINE
# ===========================================================================
# -----------------------------------------------------------------------------
# Parse flags
# -----------------------------------------------------------------------------
while [[ $# -gt 0 ]]; do
  case "$1" in
    --total-gb)        TOTAL_GB="${2:?}"; shift 2;;
    --total-bytes)     TOTAL_BYTES_OVERRIDE="${2:?}"; shift 2;;
    --dh0-mib)          DH0_MIB="${2:?}"; shift 2;;
    --dh1-mib)          DH1_MIB="${2:?}"; shift 2;;
    --amiga-fs)         AMIGA_FS="${2:?}"; shift 2;;
    --pfs3-aio)         PFS3_AIO="${2:?}"; shift 2;;
    --hst-imager)       HST_IMAGER="${2:?}"; shift 2;;
    --amiga-tools)      AMIGA_TOOLS="${2:?}"; shift 2;;
    --transfer-dir)     TRANSFER_DIR="${2:?}"; shift 2;;
    --transfer-partition) TRANSFER_PARTITION="${2:?}"; shift 2;;
    --adf-dir)          ADF_DIR="${2:?}"; shift 2;;
    --icon-set)         ICON_SET="${2:?}"; shift 2;;
    --packages)         PACKAGES="${2:?}"; shift 2;;
    --locales)          LOCALES="${2:?}"; shift 2;;
    --csv-dir)          CSV_DIR="${2:?}"; shift 2;;
    --aminet-dir)       AMINET_DIR="${2:?}"; shift 2;;
    --aminet-rules-csv) AMINET_RULES_CSV="${2:?}"; shift 2;;
    --download-only)    DOWNLOAD_ONLY="${2:?}"; shift 2;;
    --debug)            DEBUG=1; shift 1;;
    --debug-export-dir) DEBUG_EXPORT_DIR="${2:?}"; shift 2;;
    --hst-amiga)        HST_AMIGA="${2:?}"; shift 2;;
    --ibrowse-dir)      IBROWSE_DIR="${2:?}"; shift 2;;
    --ibrowse-key)      IBROWSE_KEY="${2:?}"; shift 2;;
    --include-wbdock)   INCLUDE_WBDOCK=1; shift;;
    --include-igame)    INCLUDE_IGAME=1; shift;;
    --include-lumiftp)  INCLUDE_LUMIFTP=1; shift;;
    --include-amigaamp) INCLUDE_AMIGAAMP=1; shift;;
    --include-riva)     : ; shift;;  # RiVA requires 68040+; MiSTer is 68000 — silently ignored
    --whdload-kickstarts-dir) WHDLOAD_KICKSTARTS_DIR="${2:?}"; shift 2;;
    --imp3-reg)             IMP3_REG="${2:?}"; shift 2;;
    --imp3-reg-file)        IMP3_REG_FILE="${2:?}"; shift 2;;
    --boing-bag-1)          BOING_BAG_1="${2:?}"; shift 2;;
    --boing-bag-2)          BOING_BAG_2="${2:?}"; shift 2;;
    --backdrop-enabled) BACKDROP_ENABLED="${2:?}"; shift 2;;
    --partition)
      IFS=':' read -r _pn _pl _ps _pf _pb _pp <<< "${2:?}"
      PART_NAMES+=("$_pn")
      PART_LABELS+=("$_pl")
      PART_SIZES+=("$_ps")
      PART_FSTYPES+=("${_pf:-Ffs}")
      PART_BOOTABLE+=("${_pb:-0}")
      PART_BOOTPRI+=("${_pp:-0}")
      shift 2;;
    # RTG (optional — MiSTer uses UAEGFX)
    --screen-width)       SCREEN_WIDTH="${2:?}"; shift 2;;
    --screen-height)      SCREEN_HEIGHT="${2:?}"; shift 2;;
    --screen-fps)         SCREEN_FPS="${2:?}"; shift 2;;
    --screen-interlace)   SCREEN_INTERLACE="${2:?}"; shift 2;;
    --screen-mode-id)     SCREEN_MODE_ID="${2:?}"; shift 2;;
    --wb-depth)           WB_DEPTH="${2:?}"; shift 2;;
    --wb-depth-hex)       WB_DEPTH_HEX="${2:?}"; shift 2;;
    --p96-icomp-archive)  P96_ICOMP_ARCHIVE="${2:?}"; shift 2;;
    # Networking (optional — Roadshow)
    --network-stack)      NETWORK_STACK="${2:?}"; shift 2;;
    --roadshow-variant)   ROADSHOW_VARIANT="${2:?}"; shift 2;;
    --roadshow-archive)   ROADSHOW_ARCHIVE="${2:?}"; shift 2;;
    # Silently ignore PiStorm-only flags
    --fat-mib|--emu68-boot-url|--emu68-boot-mode|--has-floppy|--mount-emu68boot|--emu68boot-device)
      shift 2;;
    --unicam-enable|--unicam-autostart|--unicam-scaling|--unicam-brightness|--unicam-contrast)
      shift 2;;
    --unicam-sizew|--unicam-sizeh|--unicam-offsetx|--unicam-offsety)
      shift 2;;
    --wifi-ssid|--wifi-password)
      shift 2;;
    *) die "Unknown argument: $1";;
  esac
done

# Normalize download-only flag
if [[ "${DOWNLOAD_ONLY:-0}" != "1" ]]; then
  DOWNLOAD_ONLY="0"
fi
if [[ "$DOWNLOAD_ONLY" == "1" ]]; then
  PACKAGES=""
fi

# -----------------------------------------------------------------------------
# Normalize filesystem token for hst-imager
# -----------------------------------------------------------------------------
HST_FS=""
case "$AMIGA_FS" in
  Ffs|FFS)
    AMIGA_FS="Ffs"
    HST_FS="DOS3"
    ;;
  FfsDC)
    AMIGA_FS="FfsDC"
    HST_FS="DOS7"
    ;;
  Pfs3|PFS3)
    AMIGA_FS="Pfs3"
    HST_FS="Pfs3"
    ;;
  *)
    die "Unsupported filesystem: $AMIGA_FS (use Ffs | FfsDC | Pfs3)"
    ;;
esac

# -----------------------------------------------------------------------------
# Validate inputs
# -----------------------------------------------------------------------------
[[ -f "$KICK_ROM" ]] || die "Kickstart ROM missing: $KICK_ROM"
[[ -d "$ASSETS_DIR" ]] || die "Assets dir missing: $ASSETS_DIR"
[[ -x "$HST_IMAGER" ]] || die "hst-imager not found/executable: $HST_IMAGER"

if [[ -n "$ISO_PATH" ]]; then
  [[ -f "$ISO_PATH" ]] || die "ISO missing: $ISO_PATH"
fi

if [[ -n "$ADF_DIR" ]]; then
  [[ -d "$ADF_DIR" ]] || die "ADF dir missing: $ADF_DIR"
fi

if [[ -n "$AMINET_RULES_CSV" ]]; then
  [[ -f "$AMINET_RULES_CSV" ]] || die "Aminet rules CSV missing: $AMINET_RULES_CSV"
fi

if [[ -z "$ISO_PATH" && -z "$ADF_DIR" ]]; then
  die "No input media available. Provide --adf-dir (preferred) or an ISO path as 3rd positional argument."
fi

if [[ "$AMIGA_FS" == "Pfs3" ]]; then
  [[ -f "$PFS3_AIO" ]] || die "PFS3 selected but pfs3aio not found: $PFS3_AIO"
fi

case "$ICON_SET" in
  standard|glowicons) ;;
  *) die "Invalid --icon-set. Use: standard | glowicons" ;;
esac

# --- N-partition support: populate partition arrays from --partition args or legacy fallback ---
if [[ ${#PART_NAMES[@]} -eq 0 ]]; then
  # Legacy mode: build from --dh0-mib/--dh1-mib
  PART_NAMES=("DH0")
  PART_LABELS=("Workbench")
  if [[ "$DH0_MIB" -gt 0 ]]; then
    PART_SIZES=("$DH0_MIB")
  else
    PART_SIZES=("remaining")
  fi
  PART_FSTYPES=("$AMIGA_FS")
  PART_BOOTABLE=("1")
  PART_BOOTPRI=("1")
  if [[ "$DH1_MIB" -gt 0 ]]; then
    PART_NAMES+=("DH1")
    PART_LABELS+=("Work")
    PART_SIZES+=("remaining")
    PART_FSTYPES+=("$AMIGA_FS")
    PART_BOOTABLE+=("0")
    PART_BOOTPRI+=("0")
  fi
else
  # N-partition mode: override HST_FS from boot partition
  case "${PART_FSTYPES[0]}" in
    Pfs3|PFS3)
      HST_FS="Pfs3"
      AMIGA_FS="Pfs3"
      ;;
    FfsDC)
      HST_FS="DOS7"
      AMIGA_FS="FfsDC"
      ;;
    *)
      HST_FS="$(part_fs_to_dostype "${PART_FSTYPES[0]}" "0")"
      AMIGA_FS="Ffs"
      ;;
  esac
fi
NUM_PARTS=${#PART_NAMES[@]}

have_cmd curl  || die "curl is required."
have_cmd unzip || die "unzip is required."

if [[ -n "${TOTAL_BYTES_OVERRIDE:-}" && "$TOTAL_BYTES_OVERRIDE" -gt 0 ]] 2>/dev/null; then
  DISK_SIZE_BYTES="$TOTAL_BYTES_OVERRIDE"
else
  DISK_SIZE_BYTES=$(( TOTAL_GB * 1000 * 1000 * 1000 ))
fi

echo "==> Layout (MiSTer Minimig HDF):"
echo "    Total: ${TOTAL_GB} GB (${DISK_SIZE_BYTES} bytes)"
echo "    Partitions: ${NUM_PARTS}"
for _i in $(seq 0 $((NUM_PARTS - 1))); do
  echo "      ${PART_NAMES[$_i]}: label=${PART_LABELS[$_i]} size=${PART_SIZES[$_i]}MiB fs=${PART_FSTYPES[$_i]} boot=${PART_BOOTABLE[$_i]} pri=${PART_BOOTPRI[$_i]}"
done
echo "    FS:    ${AMIGA_FS} (dostype=${HST_FS})"
echo "    ADF:   ${ADF_DIR:-<auto-detect from ISO/ADF>}"
echo "    ISO:   ${ISO_PATH:-<none>}"
echo "    Icons: ${ICON_SET}"
echo "    Locales: ${LOCALES:-<all>}"
echo "    Pkgs:  ${PACKAGES:-<none>}"
echo "    Download-only: ${DOWNLOAD_ONLY}"
echo "    Aminet-dir: ${AMINET_DIR:-<none>}"
echo "    Debug: ${DEBUG} (export-dir=${DEBUG_EXPORT_DIR:-<img directory>})"
echo "    Tools: amiga-tools=${AMIGA_TOOLS:-<not set>}"

# -----------------------------------------------------------------------------
# Stage + ISO mount
# -----------------------------------------------------------------------------
STAGE="$(mktemp -d "/tmp/mister-stage.XXXXXX")"
MNT="$STAGE/mnt"
mkdir -p "$MNT"
ISO_ROOT="$STAGE/no-iso"
ISO_MOUNTED=0

cleanup() {
  export_debug_hdf || true
  if [[ "${ISO_MOUNTED:-0}" -eq 1 ]]; then
    hdiutil detach "$MNT" -force >/dev/null 2>&1 || true
  fi
  rm -rf "$STAGE" >/dev/null 2>&1 || true
}
trap cleanup EXIT

if [[ -n "$ISO_PATH" ]]; then
  echo "==> Mounting ISO via hdiutil (read-only)"
  _already_mounted="$("${AMIGA_TOOLS}" hdiutil-find-mount "$ISO_PATH" 2>/dev/null || true)"
  if [[ -n "$_already_mounted" ]]; then
    echo "==> ISO already mounted at $_already_mounted (reusing)"
    MNT="$_already_mounted"
  else
    hdiutil attach "$ISO_PATH" -readonly -nobrowse -mountpoint "$MNT" >/dev/null
    ISO_MOUNTED=1
  fi
  ISO_ROOT="$MNT"
else
  echo "==> No ISO provided; using ADF-only mode"
fi

# -----------------------------------------------------------------------------
# Find FastFileSystem (needed for DOS3/DOS7)
# -----------------------------------------------------------------------------
FFS_PATH=""

find_ffs_in_adf_dir() {
  local adf_dir="$1"
  [[ -d "$adf_dir" ]] || return 1

  local tmp_ffs_root="$STAGE/ffs-scan"
  rm -rf "$tmp_ffs_root" >/dev/null 2>&1 || true
  mkdir -p "$tmp_ffs_root"

  local -a adf_candidates=()
  local _f

  for _f in "$adf_dir"/Workbench*.adf "$adf_dir"/Workbench*.ADF "$adf_dir"/Install*.adf "$adf_dir"/Install*.ADF; do
    [[ -f "$_f" ]] && adf_candidates+=("$_f")
  done
  for _f in "$adf_dir"/*.adf "$adf_dir"/*.ADF; do
    [[ -f "$_f" ]] && adf_candidates+=("$_f")
  done
  # Deep scan: find ADFs in subdirectories too (handles user-provided trees
  # like /Volumes/AMIGA/OS3.2/Disks/Workbench.adf)
  while IFS= read -r _f; do
    [[ -n "$_f" && -f "$_f" ]] && adf_candidates+=("$_f")
  done < <(find "$adf_dir" -type f \( -iname 'Workbench*.adf' -o -iname 'Install*.adf' \) 2>/dev/null)

  # De-duplicate while preserving order.
  local -a deduped=()
  local seen="|"
  local item
  [[ ${#adf_candidates[@]} -eq 0 ]] && return 1
  for item in "${adf_candidates[@]}"; do
    if [[ "$seen" == *"|$item|"* ]]; then
      continue
    fi
    deduped+=("$item")
    seen="${seen}${item}|"
  done

  local adf
  for adf in "${deduped[@]}"; do
    local adf_out="$tmp_ffs_root/$(basename "$adf").extract"
    rm -rf "$adf_out" >/dev/null 2>&1 || true
    mkdir -p "$adf_out"

    if ! "$HST_IMAGER" fs extract "$adf" "$adf_out" --recursive --force >/dev/null 2>&1 \
      && ! "$HST_IMAGER" fs extract "$adf" "$adf_out" --recursive >/dev/null 2>&1 \
      && ! "$HST_IMAGER" fs copy "$adf" "$adf_out" --recursive --force >/dev/null 2>&1 \
      && ! "$HST_IMAGER" fs copy "$adf" "$adf_out" --recursive >/dev/null 2>&1; then
      continue
    fi

    local ffs_bin=""
    ffs_bin="$(find "$adf_out" -type f -iname 'FastFileSystem' 2>/dev/null | head -n1)"
    if [[ -n "$ffs_bin" && -f "$ffs_bin" && -s "$ffs_bin" ]]; then
      echo "$ffs_bin"
      return 0
    fi
  done
  return 1
}

if [[ "$HST_FS" != "Pfs3" ]]; then
  echo "==> Locating FastFileSystem handler..."

  # Try ISO L/ first
  if [[ -n "$ISO_PATH" ]]; then
    _iso_ffs="$(find "$ISO_ROOT" -path '*/L/FastFileSystem' -type f 2>/dev/null | head -n1 || true)"
    if [[ -n "$_iso_ffs" && -f "$_iso_ffs" ]]; then
      FFS_PATH="$_iso_ffs"
      echo "==> Found FastFileSystem from ISO: $FFS_PATH"
    fi
  fi

  # Try ADF dir
  if [[ -z "$FFS_PATH" && -n "$ADF_DIR" ]]; then
    _ffs_from_adf="$(find_ffs_in_adf_dir "$ADF_DIR" || true)"
    if [[ -n "$_ffs_from_adf" && -f "$_ffs_from_adf" ]]; then
      FFS_PATH="$_ffs_from_adf"
      echo "==> Found FastFileSystem from ADF: $FFS_PATH"
    fi
  fi

  [[ -n "$FFS_PATH" ]] || die "FastFileSystem not found in ADF directory or ISO L/. Cannot format ${HST_FS}."
fi

# -----------------------------------------------------------------------------
# 1) Create blank HDF image
# -----------------------------------------------------------------------------
echo "==> PROGRESS 1/10 Creating blank HDF image"
echo "==> 1) Create blank HDF image (${TOTAL_GB} GB)"
mkdir -p "$(dirname "$IMG")"
if [[ -e "$IMG" ]]; then
  echo "==> Removing existing image: $IMG"
  rm -f "$IMG"
fi
run "$HST_IMAGER" blank "$IMG" "$DISK_SIZE_BYTES"

# -----------------------------------------------------------------------------
# 2) Initialize RDB on the blank image
# -----------------------------------------------------------------------------
echo "==> PROGRESS 2/10 Initializing RDB"
echo "==> 2) Initialize RDB on HDF"

# hst-imager rdb init creates an RDB header on a raw disk/image.
# For pure HDF images, RDB occupies the entire file.
# Filesystem handler is added separately with rdb fs add.
run "$HST_IMAGER" rdb init "$IMG" --size "$DISK_SIZE_BYTES"

if [[ "$HST_FS" == "Pfs3" ]]; then
  run "$HST_IMAGER" rdb fs add "$IMG" "$PFS3_AIO" PDS3
else
  run "$HST_IMAGER" rdb fs add "$IMG" "$FFS_PATH" "$HST_FS"
fi

RDB="$IMG/rdb"

if ! "$HST_IMAGER" rdb info "$RDB" >/dev/null 2>&1; then
  die "RDB init failed: RDB not accessible at $RDB"
fi

# -----------------------------------------------------------------------------
# 3) Partition layout (N partitions)
# -----------------------------------------------------------------------------
echo "==> PROGRESS 3/10 Creating partitions"
echo "==> 3) Partition layout (${NUM_PARTS} partitions)"

RDB_INFO="$("$HST_IMAGER" rdb info "$RDB" --verbose 2>/dev/null || true)"
echo "$RDB_INFO" || true

RDB_BYTES="$(echo "$RDB_INFO" | sed -nE 's/.*\(([0-9]+) bytes\).*/\1/p' | head -n1)"
[[ -n "$RDB_BYTES" ]] || die "Failed to determine RDB size in bytes from: hst-imager rdb info $RDB"

UNALLOC_BYTES="$(echo "$RDB_INFO" | sed -nE 's/.*[Uu]nallocated.*\(([0-9]+) bytes\).*/\1/p' | head -n1)"

TAIL_SAFETY_BYTES=$(( 32 * 1024 * 1024 ))
HEAD_SAFETY_BYTES=$(( 4 * 1024 * 1024 ))

if [[ -n "${UNALLOC_BYTES:-}" && "${UNALLOC_BYTES:-0}" -gt 0 ]]; then
  USABLE_BYTES=$(( UNALLOC_BYTES - TAIL_SAFETY_BYTES ))
else
  USABLE_BYTES=$(( RDB_BYTES - HEAD_SAFETY_BYTES - TAIL_SAFETY_BYTES ))
fi
[[ "$USABLE_BYTES" -gt 0 ]] || die "Computed usable RDB space invalid"

# Compute byte sizes per partition (handles "remaining" keyword)
PART_BYTES=()
_fixed_total=0
_remaining_idx=-1
_remaining_count=0
for _i in $(seq 0 $((NUM_PARTS - 1))); do
  if [[ "${PART_SIZES[$_i]}" == "remaining" ]]; then
    _remaining_idx=$_i
    _remaining_count=$(( _remaining_count + 1 ))
    PART_BYTES+=("0")
  else
    _bytes=$(( ${PART_SIZES[$_i]} * 1024 * 1024 ))
    _fixed_total=$(( _fixed_total + _bytes ))
    PART_BYTES+=("$_bytes")
  fi
done
if (( _remaining_count > 1 )); then
  die "Multiple 'remaining' partitions specified ($_remaining_count). Only one partition may use remaining space."
fi

# Assign remaining space to the "remaining" partition
if [[ $_remaining_idx -ge 0 ]]; then
  _remain=$(( USABLE_BYTES - _fixed_total ))
  if (( _remain < 256 * 1024 * 1024 )); then
    die "Remaining space for ${PART_NAMES[$_remaining_idx]} is only $(( _remain / 1024 / 1024 )) MiB (< 256 MiB). Fixed partitions total $(( _fixed_total / 1024 / 1024 )) MiB; usable RDB = $(( USABLE_BYTES / 1024 / 1024 )) MiB. Increase image size or reduce fixed partition sizes."
  fi
  PART_BYTES[$_remaining_idx]=$_remain
fi

echo "==> RDB sizing: total=${RDB_BYTES}B usable=${USABLE_BYTES}B fixed=${_fixed_total}B partitions=${NUM_PARTS}"
for _i in $(seq 0 $((NUM_PARTS - 1))); do
  echo "    ${PART_NAMES[$_i]}: ${PART_BYTES[$_i]}B (requested: ${PART_SIZES[$_i]})"
done

# Clean any existing partitions (safety)
echo "==> Cleaning existing RDB partitions (robust)"
for pn in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
  "$HST_IMAGER" rdb part delete "$RDB" "$pn" >/dev/null 2>&1 || true
done

# When PFS3 is the primary filesystem, force ALL partitions to PFS3.
if [[ "$HST_FS" == "Pfs3" ]]; then
  for _i in $(seq 0 $((NUM_PARTS - 1))); do
    PART_FSTYPES[$_i]="Pfs3"
  done
  echo "==> PFS3 selected: forced all ${NUM_PARTS} partitions to PFS3"
fi

# Auto-upgrade large FFS data partitions to FfsDC (DOS7) — see common.sh helper.
upgrade_large_ffs_data_partitions

# Create N partitions
for _i in $(seq 0 $((NUM_PARTS - 1))); do
  _dostype="$(part_fs_to_dostype "${PART_FSTYPES[$_i]}" "${PART_BOOTABLE[$_i]}")"
  _boot_flag="false"
  [[ "${PART_BOOTABLE[$_i]}" == "1" ]] && _boot_flag="true"
  run "$HST_IMAGER" rdb part add "$RDB" "${PART_NAMES[$_i]}" "$_dostype" "${PART_BYTES[$_i]}" \
    --reserved 2 --block-size 512 --max-transfer 0x1fe00 --mask 0x7ffffffe \
    --bootable "$_boot_flag" --boot-priority "${PART_BOOTPRI[$_i]}"
done

# Resolve partition numbers
RDB_INFO2="$("$HST_IMAGER" rdb info "$RDB" --verbose 2>/dev/null || true)"
PART_PNS=()
for _i in $(seq 0 $((NUM_PARTS - 1))); do
  _pn="$(rdb_partnum_by_name "$RDB_INFO2" "${PART_NAMES[$_i]}")"
  [[ -n "$_pn" ]] || die "Failed to resolve partition number for ${PART_NAMES[$_i]}"
  PART_PNS+=("$_pn")
done

# Format each partition with its label
for _i in $(seq 0 $((NUM_PARTS - 1))); do
  run "$HST_IMAGER" rdb part format "$RDB" "${PART_PNS[$_i]}" "${PART_LABELS[$_i]}"
done

# Verify every partition is mountable after format (same guard as build-pistorm.sh).
# See build-pistorm.sh for explanation of the two hst-imager bugs handled here.
# For MiSTer (pure RDB HDF, no MBR) the RDB slice is the image itself, so _rdb_disk_start=0.
for _i in $(seq 0 $((NUM_PARTS - 1))); do
  _pn="${PART_PNS[$_i]}"
  _vpath="$IMG/rdb/$_pn"
  if "$HST_IMAGER" fs dir "$_vpath" >/dev/null 2>&1; then
    echo "==> Partition ${PART_NAMES[$_i]} (${PART_LABELS[$_i]}) post-format: OK"
    continue
  fi
  _slice_start=$("$HST_IMAGER" info "$IMG" 2>/dev/null \
    | awk -F'|' -v pn="$_pn" '
        /RDB/ {
          n=$4; gsub(/[[:space:]]+/, "", n)
          if (n == pn) {
            s=$6; gsub(/[[:space:]]+/, "", s)
            if (s ~ /^[0-9]+$/) { print s; exit }
          }
        }')
  if [[ -z "$_slice_start" ]]; then
    echo "==> ERROR: Partition ${PART_NAMES[$_i]} (pn $_pn, ${PART_LABELS[$_i]}) failed post-format check." >&2
    echo "==>   hst-imager fs dir failed and start offset not found in info output." >&2
    echo "==>   FS type: ${PART_FSTYPES[$_i]} → dostype $(part_fs_to_dostype "${PART_FSTYPES[$_i]}" "${PART_BOOTABLE[$_i]}")" >&2
    die "Post-format check failed for partition ${PART_NAMES[$_i]}"
  fi
  _dd_block=$(( _slice_start / 512 ))
  _dd_off=$(( _slice_start % 512 ))
  _raw_magic=$(dd if="$IMG" bs=512 skip="$_dd_block" count=1 2>/dev/null \
    | dd bs=1 skip="$_dd_off" count=4 2>/dev/null \
    | od -A n -t x1 \
    | tr -d ' \n')
  _dostype=$(part_fs_to_dostype "${PART_FSTYPES[$_i]}" "${PART_BOOTABLE[$_i]}")
  case "$_dostype" in
    DOS3) _expected_hex="444f5303" ;;
    DOS7) _expected_hex="444f5307" ;;
    PDS3) _expected_hex="50445303" ;;
    *)    _expected_hex="" ;;
  esac
  if [[ -n "$_expected_hex" && "$_raw_magic" == "$_expected_hex" ]]; then
    echo "==> Partition ${PART_NAMES[$_i]} (${PART_LABELS[$_i]}) post-format: OK (raw boot block check)"
    echo "==>   (hst-imager fs dir skipped: uint32 overflow for partition at byte $_slice_start)"
    continue
  fi
  echo "==> ERROR: Partition ${PART_NAMES[$_i]} (pn $_pn, ${PART_LABELS[$_i]}) failed post-format check." >&2
  echo "==>   FS type: ${PART_FSTYPES[$_i]} → dostype $_dostype (expected: ${_expected_hex:-unknown})" >&2
  echo "==>   Boot block at byte $_slice_start: 0x${_raw_magic}" >&2
  echo "==>   For FFS partitions > 2 GiB, upgrade_large_ffs_data_partitions should prevent this." >&2
  die "Post-format check failed for partition ${PART_NAMES[$_i]}"
done

# Primary system partition path (pure RDB — no MBR layer)
SYS_ROOT="$IMG/rdb/${PART_PNS[0]}"

# Resolve transfer target partition path
TRANSFER_ROOT="$SYS_ROOT"
if [[ -n "$TRANSFER_PARTITION" ]]; then
  _transfer_idx=""
  for _i in $(seq 0 $((NUM_PARTS - 1))); do
    if [[ "${PART_NAMES[$_i]}" == "$TRANSFER_PARTITION" ]]; then
      _transfer_idx="$_i"
      break
    fi
  done
  if [[ -n "$_transfer_idx" ]]; then
    TRANSFER_ROOT="$IMG/rdb/${PART_PNS[$_transfer_idx]}"
  else
    echo "==> WARN: transfer partition '$TRANSFER_PARTITION' not found; using ${PART_NAMES[0]}" >&2
  fi
fi

# -----------------------------------------------------------------------------
# 4) Populate SYS: from ADFs (Windows-style merge)
# -----------------------------------------------------------------------------
echo "==> PROGRESS 4/10 Extracting AmigaOS from ADFs"
echo "==> 4) Populate SYS: from ADFs (Windows-style merge)"

detect_locale_code_from_adf_name() {
  local adf_name="$1"
  local lower base tail
  lower="$(printf "%s" "$adf_name" | tr '[:upper:]' '[:lower:]')"
  base="${lower%.adf}"

  [[ "$base" == locale* ]] || return 1

  tail="${base#locale}"
  tail="${tail#[-_]}"

  case "$tail" in
    de*|german*)  echo "de"; return 0;;
    dk*|danish*)  echo "dk"; return 0;;
    es*|spanish*) echo "es"; return 0;;
    fr*|french*)  echo "fr"; return 0;;
    gb*|british*) echo "gb"; return 0;;
    it*|italian*) echo "it"; return 0;;
    nl*|dutch*)   echo "nl"; return 0;;
    no*|norweg*)  echo "no"; return 0;;
    pl*|polish*)  echo "pl"; return 0;;
    pt*|portug*)  echo "pt"; return 0;;
    se*|swedish*) echo "se"; return 0;;
    en*|englis*)  echo "en"; return 0;;
    fi*|finnish*) echo "fi"; return 0;;
    cz*|czech*)   echo "cz"; return 0;;
    ru*|russia*)  echo "ru"; return 0;;
  esac

  if [[ ${#tail} -ge 2 && ${#tail} -le 4 ]]; then
    echo "${tail:0:2}"
    return 0
  fi
  return 1
}

locale_adf_is_wanted() {
  local adf_name="$1"
  [[ -z "$LOCALES" ]] && return 0  # empty = all locales

  local code
  code="$(detect_locale_code_from_adf_name "$adf_name")" || return 0
  # Always include base locale ADFs (no language code = shared Countries)
  [[ -z "$code" ]] && return 0
  # Always include English
  [[ "$code" == "en" || "$code" == "gb" ]] && return 0

  local want
  IFS=',' read -ra _loc_arr <<< "$LOCALES"
  for want in "${_loc_arr[@]}"; do
    want="$(printf "%s" "$want" | tr '[:upper:]' '[:lower:]' | tr -d ' ')"
    [[ "$want" == "$code" ]] && return 0
  done
  return 1
}

classify_adf() {
  local adf="$1"
  local name lower
  name="$(basename "$adf")"
  lower="$(printf "%s" "$name" | tr '[:upper:]' '[:lower:]')"

  case "$lower" in
    *locale*)      echo "locale";;
    *4coloricons*) echo "icon4";;
    *glowicons*|*glow_icons*) echo "iconglow";;
    *install*|*extras*|*storage*|*classes*|*modules*) echo "patch";;
    *workbench*)   echo "base";;
    *)             echo "patch";;
  esac
}

# Determine ADF source
ADF_SRC=""
IS_39_ISO=0

if [[ -n "$ADF_DIR" && -d "$ADF_DIR" ]]; then
  ADF_SRC="$ADF_DIR"
elif [[ -n "$ISO_PATH" ]]; then
  # Check for OS3.9 ISO structure
  if [[ -d "$ISO_ROOT/OS-Version3.9" || -d "$ISO_ROOT/OS3.9" ]]; then
    IS_39_ISO=1
    ADF_SRC="$ISO_ROOT"
  else
    # Try finding ADFs within ISO
    _iso_adf_dir="$(find "$ISO_ROOT" -type d -name 'ADF' -o -type d -name 'adf' 2>/dev/null | head -n1 || true)"
    if [[ -n "$_iso_adf_dir" && -d "$_iso_adf_dir" ]]; then
      ADF_SRC="$_iso_adf_dir"
    else
      ADF_SRC="$ISO_ROOT"
    fi
  fi
fi

[[ -n "$ADF_SRC" ]] || die "No ADF source directory resolved."

# Collect and classify ADFs
ADFS=()
BASE_ADFS=()
PATCH_ADFS=()
LOCALE_ADFS=()
ICON4_ADFS=()
ICONGLOW_ADFS=()
LOCALE_SKIPPED=0

if [[ "$IS_39_ISO" -eq 1 ]]; then
  echo "==> OS 3.9 ISO detected — full ISO extraction mode"
else
  for _f in "$ADF_SRC"/*.adf "$ADF_SRC"/*.ADF; do
    [[ -f "$_f" ]] || continue
    ADFS+=("$_f")

    _class="$(classify_adf "$_f")"
    _name="$(basename "$_f")"
    case "$_class" in
      base)    BASE_ADFS+=("$_f") ;;
      patch)   PATCH_ADFS+=("$_f") ;;
      locale)
        if locale_adf_is_wanted "$_name"; then
          LOCALE_ADFS+=("$_f")
        else
          LOCALE_SKIPPED=$((LOCALE_SKIPPED + 1))
        fi
        ;;
      icon4)   ICON4_ADFS+=("$_f") ;;
      iconglow) ICONGLOW_ADFS+=("$_f") ;;
    esac
  done

  # Select icon set
  case "$ICON_SET" in
    glowicons) SELECTED_ICONSET_ADFS=("${ICONGLOW_ADFS[@]+"${ICONGLOW_ADFS[@]}"}") ;;
    *)         SELECTED_ICONSET_ADFS=("${ICON4_ADFS[@]+"${ICON4_ADFS[@]}"}") ;;
  esac

  echo "==> ADFs: total=${#ADFS[@]} base=${#BASE_ADFS[@]} patch=${#PATCH_ADFS[@]} locale=${#LOCALE_ADFS[@]} icon4=${#ICON4_ADFS[@]} glow=${#ICONGLOW_ADFS[@]} selectedIcons=${#SELECTED_ICONSET_ADFS[@]} skipped=$LOCALE_SKIPPED"

  # Detect WB version from ADF names
  for _adf in "${BASE_ADFS[@]+"${BASE_ADFS[@]}"}"; do
    _wb_ver="$(detect_workbench_version "$(basename "$_adf")" 2>/dev/null || true)"
    if [[ -n "$_wb_ver" ]]; then
      DETECTED_WB_VERSION="$_wb_ver"
      echo "==> Detected Workbench version: $DETECTED_WB_VERSION (from $(basename "$_adf"))"
      break
    fi
  done
fi

# Host staging area for ADF extraction
SYS_HOST="$STAGE/sys-host"
rm -rf "$SYS_HOST" >/dev/null 2>&1 || true
mkdir -p "$SYS_HOST"

if [[ "$IS_39_ISO" -eq 1 ]]; then
  echo "==> Extracting OS 3.9 from ISO..."
  extract_39_iso_to_host "$ISO_ROOT" "$SYS_HOST" || die "OS 3.9 ISO extraction failed."
else
  # Extract base ADFs first, then patches, then locale, then icons
  # Use parallel extraction for performance
  _adf_order=()
  _adf_order+=("${BASE_ADFS[@]+"${BASE_ADFS[@]}"}")
  _adf_order+=("${PATCH_ADFS[@]+"${PATCH_ADFS[@]}"}")
  _adf_order+=("${LOCALE_ADFS[@]+"${LOCALE_ADFS[@]}"}")
  _adf_order+=("${SELECTED_ICONSET_ADFS[@]+"${SELECTED_ICONSET_ADFS[@]}"}")

  if [[ ${#_adf_order[@]} -eq 0 ]]; then
    die "No ADFs found to extract in: $ADF_SRC"
  fi

  echo "==> Extracting ${#_adf_order[@]} ADFs to host staging..."
  _parallel_count=0
  _parallel_pids=()
  for _adf in "${_adf_order[@]}"; do
    echo "  -> $(basename "$_adf")"
    (
      _out="$SYS_HOST"
      "$HST_IMAGER" fs extract "$_adf" "$_out" --recursive --force >/dev/null 2>&1 \
        || "$HST_IMAGER" fs extract "$_adf" "$_out" --recursive >/dev/null 2>&1 \
        || "$HST_IMAGER" fs copy "$_adf" "$_out" --recursive --force >/dev/null 2>&1 \
        || "$HST_IMAGER" fs copy "$_adf" "$_out" --recursive >/dev/null 2>&1 \
        || echo "WARN: Failed to extract $(basename "$_adf")" >&2
    ) &
    _parallel_pids+=($!)
    _parallel_count=$((_parallel_count + 1))
    if [[ $_parallel_count -ge $MAX_PARALLEL_ADF ]]; then
      for _pid in "${_parallel_pids[@]}"; do
        wait "$_pid" 2>/dev/null || true
      done
      _parallel_pids=()
      _parallel_count=0
    fi
  done
  # Wait for remaining
  for _pid in "${_parallel_pids[@]+"${_parallel_pids[@]}"}"; do
    wait "$_pid" 2>/dev/null || true
  done
fi

# Boing Bags (OS 3.9 updates)
if [[ -n "${BOING_BAG_1:-}" && -f "$BOING_BAG_1" ]]; then
  echo "==> Extracting Boing Bag 1: $BOING_BAG_1"
  "$HST_IMAGER" fs extract "$BOING_BAG_1" "$SYS_HOST" --recursive --force >/dev/null 2>&1 || true
fi
if [[ -n "${BOING_BAG_2:-}" && -f "$BOING_BAG_2" ]]; then
  echo "==> Extracting Boing Bag 2: $BOING_BAG_2"
  "$HST_IMAGER" fs extract "$BOING_BAG_2" "$SYS_HOST" --recursive --force >/dev/null 2>&1 || true
fi

echo "==> Normalizing Locale drawers from extracted ADFs"
normalize_locale_drawers "$SYS_HOST" 2>/dev/null || true

# Save backdrop IFF files BEFORE sanitization removes them from root
_bd_stash="$STAGE/backdrop-stash"
mkdir -p "$_bd_stash"
for _iff in default_pal.iff default_ntsc.iff default.pal.iff default.ntsc.iff pattern.iff; do
  [[ -f "$SYS_HOST/$_iff" ]] && cp -f "$SYS_HOST/$_iff" "$_bd_stash/$_iff"
done
# Also check Prefs/Presets subdirectories
for _iff in default_pal.iff default_ntsc.iff pattern.iff; do
  [[ -f "$SYS_HOST/Prefs/Presets/Backdrops/$_iff" ]] && cp -f "$SYS_HOST/Prefs/Presets/Backdrops/$_iff" "$_bd_stash/$_iff"
  [[ -f "$SYS_HOST/Prefs/Presets/$_iff" ]] && cp -f "$SYS_HOST/Prefs/Presets/$_iff" "$_bd_stash/$_iff"
done

echo "==> Sanitizing SYS_HOST root: removing Install/Extras ADF junk"
sanitize_sys_host_root "$SYS_HOST" 2>/dev/null || true

echo "==> Sanitizing non-ASCII filenames in SYS_HOST for FFS compatibility"
sanitize_non_ascii_filenames "$SYS_HOST" 2>/dev/null || true

# Copy host staging to image partition
echo "==> Copying extracted ADFs to SYS: partition"
fs_copy "$SYS_HOST/" "$SYS_ROOT/" --recursive --makedir

# Copy backdrop presets directly to image. The Backdrops ADF is often not
# extracted during ADF classification (it's not base/patch/locale), so we
# extract it explicitly here and copy the IFF files to the image.
ensure_img_dir "$SYS_ROOT/Prefs/Presets/Backdrops"
_bd_copied=0
# Try extracting Backdrops ADF if available
_bd_adf=""
for _bd_try in "$ADF_SRC"/Backdrops*.adf "$ADF_SRC"/backdrops*.adf "$ADF_SRC"/Backdrops*.ADF; do
  [[ -f "$_bd_try" ]] && { _bd_adf="$_bd_try"; break; }
done
if [[ -n "$_bd_adf" ]]; then
  _bd_extract="$STAGE/backdrops-adf-extract"
  rm -rf "$_bd_extract" 2>/dev/null || true
  mkdir -p "$_bd_extract"
  echo "==> Extracting Backdrops ADF: $(basename "$_bd_adf")"
  extract_adf_to_host "$_bd_adf" "$_bd_extract" 2>/dev/null || true
fi
for _bd_name in default_pal.iff default_ntsc.iff pattern.iff; do
  _bd_src=""
  for _bd_cand in \
    "$_bd_stash/$_bd_name" \
    "${_bd_extract:-/nonexistent}"/"$_bd_name" \
    "${_bd_extract:-/nonexistent}"/Prefs/Presets/Backdrops/"$_bd_name" \
    "$SYS_HOST/Prefs/Presets/Backdrops/$_bd_name" \
    "$SYS_HOST/$_bd_name" \
    "$ASSETS_DIR/AmigaFiles/System/Prefs/Presets/Backdrops/$_bd_name" \
    ; do
    [[ -f "$_bd_cand" ]] && { _bd_src="$_bd_cand"; break; }
  done
  if [[ -n "$_bd_src" ]]; then
    fs_copy_file "$_bd_src" "$SYS_ROOT/Prefs/Presets/Backdrops/$_bd_name"
    _bd_copied=$((_bd_copied + 1))
  fi
done
rm -rf "${_bd_extract:-}" 2>/dev/null || true
rm -rf "$_bd_stash" 2>/dev/null || true
# Fallback: if pattern.iff is missing, use default_pal.iff as pattern.iff
if [[ "$_bd_copied" -gt 0 ]]; then
  "$HST_IMAGER" fs dir "$SYS_ROOT/Prefs/Presets/Backdrops/pattern.iff" >/dev/null 2>&1 || {
    # pattern.iff not on image — try copying default_pal.iff as pattern.iff
    if "$HST_IMAGER" fs dir "$SYS_ROOT/Prefs/Presets/Backdrops/default_pal.iff" >/dev/null 2>&1; then
      _tmp_pal="$STAGE/.pal-to-pattern"
      if fs_pull_file "$SYS_ROOT/Prefs/Presets/Backdrops/default_pal.iff" "$_tmp_pal"; then
        fs_copy_file "$_tmp_pal" "$SYS_ROOT/Prefs/Presets/Backdrops/pattern.iff"
        _bd_copied=$((_bd_copied + 1))
        echo "==> Backdrop: copied default_pal.iff as pattern.iff fallback"
      fi
      rm -f "$_tmp_pal" 2>/dev/null || true
    fi
  }
  echo "==> Backdrop presets: $_bd_copied files copied to image"
else
  echo "==> INFO: No backdrop preset files found"
fi

# -----------------------------------------------------------------------------
# 5) Overlay additions from Assets (non-destructive)
# -----------------------------------------------------------------------------
echo "==> PROGRESS 5/10 Overlaying Assets"
echo "==> 5) Overlay additions from Assets (non-destructive)"
SYS_PAYLOAD="$ASSETS_DIR/AmigaFiles/System"
if [[ ! -d "$SYS_PAYLOAD" ]]; then
  die "Missing System payload in Assets: $SYS_PAYLOAD"
fi

# --- sanity: base OS drawers should exist after ADF merge ---
for _p in \
  "$SYS_ROOT/C" \
  "$SYS_ROOT/Devs" \
  "$SYS_ROOT/Libs" \
  "$SYS_ROOT/S" \
  "$SYS_ROOT/System" \
  "$SYS_ROOT/Tools" \
  "$SYS_ROOT/Utilities" \
  "$SYS_ROOT/Prefs" \
  "$SYS_ROOT/Fonts" \
  ; do
  if ! "$HST_IMAGER" fs dir "$_p" >/dev/null 2>&1; then
    echo "==> WARN: Expected base drawer missing after ADF merge: $_p" >&2
  fi
done

# --- 5a) Overlay S: startup scripts, user-startup, etc. (file-by-file) ---
if [[ -d "$SYS_PAYLOAD/S" ]]; then
  echo "==> Overlay: System/S -> SYS:S (file-by-file)"
  ensure_img_dir "$SYS_ROOT/S"
  while IFS= read -r -d '' item; do
    base="$(basename "$item")"
    case "$base" in
      Startup-Sequence|startup-sequence)
        echo "==> NOTE: Skipping Assets S/$base to avoid clobbering AmigaOS startup scripts"
        continue
        ;;
      # Skip PiStorm-specific startup scripts
      *_Pistorm*|*_pistorm*|*_PISTORM*|*_Pist*|*_pist*|*_PIST*)
        echo "==> NOTE: Skipping PiStorm-specific script: $base"
        continue
        ;;
      # Skip VideoCore-specific startup scripts (PiStorm only)
      *_VideoCore*|*_videocore*|*_VIDEOCORE*)
        echo "==> NOTE: Skipping PiStorm-specific VideoCore script: $base"
        continue
        ;;
    esac
    if [[ -d "$item" ]]; then
      # Skip PiStorm-specific subdirectories
      case "$base" in
        OneTimeRun)
          # Filter OneTimeRun contents: skip PiStorm-specific scripts
          ensure_img_dir "$SYS_ROOT/S/$base"
          while IFS= read -r -d '' subitem; do
            subbase="$(basename "$subitem")"
            case "$subbase" in
              *[Pp]istorm*|*[Pp]ist*)
                echo "==> NOTE: Skipping PiStorm-specific OneTimeRun script: $subbase"
                continue
                ;;
            esac
            if [[ -d "$subitem" ]]; then
              fs_copy_tree_filtered "$subitem" "$SYS_ROOT/S/$base/$subbase"
            else
              if is_skippable_overlay_file "$subbase"; then continue; fi
              fs_copy_file "$subitem" "$SYS_ROOT/S/$base/$subbase"
            fi
          done < <(find "$item" -mindepth 1 -maxdepth 1 -print0)
          continue
          ;;
      esac
      fs_copy_tree_filtered "$item" "$SYS_ROOT/S/$base"
    else
      if is_skippable_overlay_file "$base"; then
        continue
      fi
      fs_copy_file "$item" "$SYS_ROOT/S/$base"
    fi
  done < <(find "$SYS_PAYLOAD/S" -mindepth 1 -maxdepth 1 -print0)
fi

# --- 5b) Skip PiStorm drawer — not applicable to MiSTer ---
# (PiStorm tools, Emu68 tools are PiStorm-specific; MiSTer doesn't need them)

# --- 5c) Overlay Programs drawer (optional apps; safe to add) ---
if [[ -d "$SYS_PAYLOAD/Programs" ]]; then
  echo "==> Overlay: System/Programs -> SYS:Programs"
  fs_copy_tree_filtered "$SYS_PAYLOAD/Programs" "$SYS_ROOT/Programs"
fi

# --- 5d) Overlay optional drawers ---
for _d in OneTimeRun OneTimeRunWB; do
  if [[ -d "$SYS_PAYLOAD/$_d" ]]; then
    echo "==> Overlay: System/$_d -> SYS:S/$_d"
    # Filter out PiStorm-specific scripts
    ensure_img_dir "$SYS_ROOT/S/$_d"
    while IFS= read -r -d '' subitem; do
      subbase="$(basename "$subitem")"
      case "$subbase" in
        *[Pp]istorm*|*[Pp]ist*)
          echo "==> NOTE: Skipping PiStorm-specific script: $subbase"
          continue
          ;;
      esac
      if [[ -d "$subitem" ]]; then
        fs_copy_tree_filtered "$subitem" "$SYS_ROOT/S/$_d/$subbase"
      else
        if is_skippable_overlay_file "$subbase"; then continue; fi
        fs_copy_file "$subitem" "$SYS_ROOT/S/$_d/$subbase"
      fi
    done < <(find "$SYS_PAYLOAD/$_d" -mindepth 1 -maxdepth 1 -print0)
  fi
done
for _d in Other Patch Support Locale; do
  if [[ -d "$SYS_PAYLOAD/$_d" ]]; then
    echo "==> Overlay: System/$_d -> SYS:$_d"
    fs_copy_tree_filtered "$SYS_PAYLOAD/$_d" "$SYS_ROOT/$_d"
  fi
done

# --- 5d.1) Overlay ADDITIONAL files from Assets into core drawers (C, Devs, Libs, Prefs) ---
for _core_dir in C Devs Libs Prefs; do
  if [[ -d "$SYS_PAYLOAD/$_core_dir" ]]; then
    echo "==> Overlay: Assets/System/$_core_dir -> SYS:$_core_dir (file-by-file additions)"
    ensure_img_dir "$SYS_ROOT/$_core_dir"
    while IFS= read -r -d '' item; do
      _base="$(basename "$item")"
      # Skip VideoCore-specific files in Devs (PiStorm only)
      if [[ "$_core_dir" == "Devs" ]]; then
        case "$_base" in
          VideoCore*|videocore*)
            echo "==> NOTE: Skipping PiStorm-specific VideoCore device file: $_base"
            continue
            ;;
          Picasso96Settings)
            # Skip — MiSTer_RTG.lha provides the correct MiSTer-specific settings
            echo "==> NOTE: Skipping bundled Picasso96Settings (MiSTer_RTG.lha version takes precedence)"
            continue
            ;;
          NetInterfaces)
            # Copy NetInterfaces but filter out PiStorm-specific interfaces
            echo "==> Overlay: Devs/NetInterfaces (filtering PiStorm interfaces)"
            ensure_img_dir "$SYS_ROOT/Devs/NetInterfaces"
            while IFS= read -r -d '' _ni; do
              _ni_base="$(basename "$_ni")"
              case "$_ni_base" in
                genet|wifipi|genet.info|wifipi.info)
                  echo "==> NOTE: Skipping PiStorm-specific interface: $_ni_base"
                  continue
                  ;;
              esac
              fs_copy_file "$_ni" "$SYS_ROOT/Devs/NetInterfaces/$_ni_base"
            done < <(find "$item" -mindepth 1 -maxdepth 1 -print0)
            continue
            ;;
        esac
      fi
      if [[ -d "$item" ]]; then
        fs_copy_tree_filtered "$item" "$SYS_ROOT/$_core_dir/$_base"
      else
        if is_skippable_overlay_file "$_base"; then
          continue
        fi
        fs_copy_file "$item" "$SYS_ROOT/$_core_dir/$_base"
      fi
    done < <(find "$SYS_PAYLOAD/$_core_dir" -mindepth 1 -maxdepth 1 -print0)
  fi
done

# Ensure core assigns (notably FONTS:) without touching Startup-Sequence.
ensure_user_startup_executes_amigaimager_assigns "$SYS_ROOT"

# --- 5e) Inject script overlays (Windows parity) ---
# Skip PiStorm-specific UAEGFX swap overlay — it moves/deletes the MiSTer monitor
# driver at boot (the VideoCore/UAEGFX swap logic is PiStorm-only).
_mister_overlay_dir="$STAGE/mister-overlay-S"
rm -rf "$_mister_overlay_dir" 2>/dev/null || true
cp -R "$SYS_PAYLOAD/S" "$_mister_overlay_dir"
rm -f "$_mister_overlay_dir/Startup-Sequence_UAEGFX"
inject_startup_and_user_startup_overlays "$SYS_ROOT" "$_mister_overlay_dir"
rm -rf "$_mister_overlay_dir" 2>/dev/null || true

# --- 5f) Binary prefs patching ---
# Write WBConfig prefs (backdrop setting).
write_wbconfig_prefs "$SYS_ROOT" "$BACKDROP_ENABLED"

# MiSTer: overwrite WBPattern.prefs with an empty prefs file to prevent
# "can't load picture" requester. MiSTer Minimig can't render the IFF
# backdrops that PiStorm handles fine. The stock WBPattern.prefs references
# Sys:Prefs/Presets/Backdrops/default_pal.iff which fails on MiSTer.
echo "==> MiSTer: Writing empty WBPattern.prefs (avoid backdrop load error)"
_empty_wbp="$STAGE/mister-empty-wbpattern.prefs"
# Minimal valid IFF PREF file with no PTRN chunks (no backdrop references)
printf 'FORM\x00\x00\x00\x0ePREFPRHD\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00' > "$_empty_wbp"
fs_copy_file "$_empty_wbp" "$SYS_ROOT/Prefs/Env-Archive/Sys/WBPattern.prefs"
rm -f "$_empty_wbp"

# Set keyboard layout based on selected locale
if [[ -n "$LOCALES" ]]; then
  _primary_keymap=""
  IFS=',' read -ra _locale_arr <<< "$LOCALES"
  for _loc in "${_locale_arr[@]}"; do
    _km="$(locale_to_keymap "$_loc")"
    if [[ -n "$_km" ]]; then
      _primary_keymap="$_km"
      break
    fi
  done
  if [[ -n "$_primary_keymap" ]]; then
    write_keymap_prefs "$SYS_ROOT" "$_primary_keymap"
  fi
fi

# --- 5g) Disk icons ---
install_disk_icons "$SYS_ROOT"
ensure_drawer_visible_in_root "$SYS_ROOT" "Programs"

# Ensure HDToolBox exists so the stock icon opens without requester.
ensure_hdtoolbox_compat "$SYS_ROOT"

# CLICon / CLIcon fallback
download_and_install_clicon "$SYS_ROOT"
if ! "$HST_IMAGER" fs dir "$SYS_ROOT/System/CLICon" >/dev/null 2>&1 \
   && ! "$HST_IMAGER" fs dir "$SYS_ROOT/System/CLIcon" >/dev/null 2>&1; then
  if "$HST_IMAGER" fs dir "$SYS_ROOT/System/CLI" >/dev/null 2>&1; then
    run "$HST_IMAGER" fs copy "$SYS_ROOT/System/CLI" "$SYS_ROOT/System/CLIcon" || true
    echo "==> Copied System/CLI -> System/CLIcon (WB3.2 LD fallback)"
  fi
fi

# mecho (enhanced echo for progress animations)
download_and_install_mecho "$SYS_ROOT"

# screennotify.library (for WBDock2 if installed)
download_and_install_screennotify "$SYS_ROOT"

# icon.library 46.4 (if selected)
if package_is_selected "IconLib"; then
  if ! "$HST_IMAGER" fs dir "$SYS_ROOT/Libs/icon.library" >/dev/null 2>&1; then
    download_and_install_iconlib "$SYS_ROOT"
  fi
fi

# AHI prefs binary alias (AHI -> AHI_BGUI or AHI_MUI)
_ahi_prefs_src=""
if "$HST_IMAGER" fs dir "$SYS_ROOT/Libs/muimaster.library" >/dev/null 2>&1; then
  _ahi_prefs_src="AHI_MUI"
else
  _ahi_prefs_src="AHI_BGUI"
  if ! "$HST_IMAGER" fs dir "$SYS_ROOT/Libs/bgui.library" >/dev/null 2>&1; then
    if "$HST_IMAGER" fs dir "$SYS_ROOT/Programs/AHI/BGUI/bgui.library" >/dev/null 2>&1; then
      _bgui_tmp="$STAGE/bgui.library.$$"
      if fs_pull_file "$SYS_ROOT/Programs/AHI/BGUI/bgui.library" "$_bgui_tmp" >/dev/null 2>&1; then
        fs_copy_file "$_bgui_tmp" "$SYS_ROOT/Libs/bgui.library"
        echo "==> Installed bgui.library to LIBS: (for AHI_BGUI)"
        rm -f "$_bgui_tmp" 2>/dev/null || true
      fi
    fi
  fi
fi
if "$HST_IMAGER" fs dir "$SYS_ROOT/Prefs/$_ahi_prefs_src" >/dev/null 2>&1; then
  _ahi_tmp="$STAGE/ahi-prefs-bin.$$"
  if fs_pull_file "$SYS_ROOT/Prefs/$_ahi_prefs_src" "$_ahi_tmp" >/dev/null 2>&1; then
    fs_copy_file "$_ahi_tmp" "$SYS_ROOT/Prefs/AHI"
    echo "==> Copied Prefs/$_ahi_prefs_src -> Prefs/AHI"
    fs_copy_file "$_ahi_tmp" "$SYS_ROOT/Prefs/AHI.MWB"
    echo "==> Copied Prefs/$_ahi_prefs_src -> Prefs/AHI.MWB"
    rm -f "$_ahi_tmp" 2>/dev/null || true
  fi
fi

# --- 5h) Tooltypes and icon positions from Windows CSVs ---
apply_tooltypes_from_package_csv
if [[ "$APPLY_ICON_POSITIONS" == "1" ]]; then
  apply_icon_positions_from_csv
else
  echo "==> Icon positioning: skipped (disabled)"
fi

# --- 5i) Set post-overlay icons borderless ---
set_coloricons_borderless_image "$SYS_ROOT" "Tools" "System" "Prefs" "Programs" "Utilities" "WBStartup" "Devs" "Storage"

# --- post-overlay sanity checks ---
if ! "$HST_IMAGER" fs dir "$SYS_ROOT/System/CLI" >/dev/null 2>&1; then
  echo "==> WARN: SYS:System/CLI missing (Shell icon will fail)." >&2
fi
if ! "$HST_IMAGER" fs dir "$SYS_ROOT/Fonts" >/dev/null 2>&1; then
  echo "==> WARN: SYS:Fonts missing (Font prefs will fail)." >&2
fi
if ! "$HST_IMAGER" fs dir "$SYS_ROOT/Tools" >/dev/null 2>&1; then
  echo "==> WARN: SYS:Tools missing (Calculator/ShowConfig etc will fail)." >&2
fi

# Persist a tiny note for debugging on the Amiga side
TMP_ADF_NOTE="$STAGE/amigaimager-adf-staged.txt"
if [[ "$IS_39_ISO" -eq 1 ]]; then
  printf "source:OS3.9-CD-ISO\nplatform:MiSTer-Minimig\n" > "$TMP_ADF_NOTE"
else
  printf "adf-staged-from:%s\nplatform:MiSTer-Minimig\n" "$ADF_SRC" > "$TMP_ADF_NOTE"
fi
make_lf_only "$TMP_ADF_NOTE"
fs_copy_file "$TMP_ADF_NOTE" "$SYS_ROOT/Install/amigaimager-adf-staged.txt"

# -----------------------------------------------------------------------------
# 6) Mandatory LhA archive helper
# -----------------------------------------------------------------------------
download_lha_from_aminet() {
  local urls=(
    "https://aminet.net/util/arc/lha_68040.lha"
    "https://aminet.net/util/arc/lha_68020.lha"
    "https://aminet.net/util/arc/lha_68k.lha"
    "http://de.aminet.net/pub/aminet/util/arc/lha_68040.lha"
    "http://de.aminet.net/pub/aminet/util/arc/lha_68020.lha"
    "http://de.aminet.net/pub/aminet/util/arc/lha_68k.lha"
  )
  local archive=""
  for url in "${urls[@]}"; do
    local base
    base="$(basename "$url")"
    archive="$STAGE/$base"
    echo "==> Downloading mandatory LhA package: $base" >&2
    if curl -fL --connect-timeout 5 --max-time 20 --retry 1 --retry-delay 1 -o "$archive" "$url" >/dev/null 2>&1; then
      echo "$archive"
      return 0
    fi
  done
  return 1
}

# -----------------------------------------------------------------------------
# 7) Optional Transfer folder -> SYS:Transfer/
# -----------------------------------------------------------------------------
if [[ -n "$TRANSFER_DIR" ]]; then
  [[ -d "$TRANSFER_DIR" ]] || die "Transfer dir missing: $TRANSFER_DIR"
  fs_copy "$TRANSFER_DIR/" "$TRANSFER_ROOT/Transfer/" --recursive --makedir
  ensure_transfer_drawer_icon "$TRANSFER_ROOT"
fi

# -----------------------------------------------------------------------------
# 8) Stage OneTimeRunWB payload
# -----------------------------------------------------------------------------
echo "==> Stage OneTimeRunWB payload (if available in Assets)"

TMP_STAGE_MARK="$STAGE/amigaimager-staged.txt"
printf "staged:%s\nplatform:mister\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$TMP_STAGE_MARK"
make_lf_only "$TMP_STAGE_MARK"

fs_mkdir_p "$SYS_ROOT/S"
fs_mkdir_p "$SYS_ROOT/S/OneTimeRunWB"

if ! "$HST_IMAGER" fs dir "$SYS_ROOT/S" >/dev/null 2>&1; then
  die "Failed to create/access SYS:S at $SYS_ROOT/S"
fi
if ! "$HST_IMAGER" fs dir "$SYS_ROOT/S/OneTimeRunWB" >/dev/null 2>&1; then
  die "Failed to create/access SYS:S/OneTimeRunWB at $SYS_ROOT/S/OneTimeRunWB"
fi

fs_copy_file "$TMP_STAGE_MARK" "$SYS_ROOT/S/amigaimager-staged.txt"

CAND_SYS_OTRWB="$ASSETS_DIR/AmigaFiles/System/OneTimeRunWB"
CAND1="$ASSETS_DIR/AmigaFiles/OneTimeRunWB"
CAND2="$ASSETS_DIR/AmigaFiles/System/S/OneTimeRunWB"

ONETIMERUNWB_SRC=""
if [[ -d "$CAND_SYS_OTRWB" ]]; then
  ONETIMERUNWB_SRC="$CAND_SYS_OTRWB"
elif [[ -d "$CAND1" ]]; then
  ONETIMERUNWB_SRC="$CAND1"
elif [[ -d "$CAND2" ]]; then
  ONETIMERUNWB_SRC="$CAND2"
fi

if [[ -n "$ONETIMERUNWB_SRC" ]]; then
  stage_onetimerunwb_payload_filtered "$ONETIMERUNWB_SRC" "$SYS_ROOT/S/OneTimeRunWB"
else
  echo "==> NOTE: No OneTimeRunWB payload found in Assets."
fi

# localesettings: ensure Locale.prefs exists
if "$HST_IMAGER" fs dir "$SYS_ROOT/Prefs/Env-Archive/Sys" >/dev/null 2>&1; then
  "$HST_IMAGER" fs copy "$SYS_ROOT/Prefs/Presets/Locale.prefs" "$SYS_ROOT/Prefs/Env-Archive/Sys/Locale.prefs" --force >/dev/null 2>&1 || true
  "$HST_IMAGER" fs copy "$SYS_ROOT/Prefs/Presets/Locale.prefs.info" "$SYS_ROOT/Prefs/Env-Archive/Sys/Locale.prefs.info" --force >/dev/null 2>&1 || true
fi

# -----------------------------------------------------------------------------
# 8a) Stage optional packages
# -----------------------------------------------------------------------------
echo "==> Stage optional packages"
PKG_ROOT="$ASSETS_DIR/AmigaFiles/Packages"

if [[ "$DOWNLOAD_ONLY" == "1" ]]; then
  echo "==> Download-only mode: skipping Assets packages"
elif [[ -n "$PACKAGES" ]]; then
  IFS=',' read -r -a PKG_LIST <<< "$PACKAGES"
  for pkg in "${PKG_LIST[@]}"; do
    pkg="$(echo "$pkg" | tr -d ' \t\n\r')"
    [[ -z "$pkg" ]] && continue

    # Skip PiStorm-specific packages (MiSTer has no VideoCore/genet/wifipi)
    case "$(printf '%s' "$pkg" | tr '[:upper:]' '[:lower:]')" in
      videocore*|emu68*|genet*|wifipi*)
        echo "  -> skipping PiStorm-specific package: $pkg"
        continue
        ;;
    esac

    SRC="$PKG_ROOT/$pkg"
    DST="$SYS_ROOT/S/OneTimeRunWB/$pkg"

    if [[ -d "$SRC" ]]; then
      echo "  -> staging package: $pkg"
      stage_onetimerunwb_payload_filtered "$SRC" "$DST"
    else
      echo "  -> WARN: package '$pkg' not found at $SRC (skipping)"
    fi
  done

  TMP_MANIFEST="$STAGE/amigaimager-packages.txt"
  printf "%s\n" "$PACKAGES" > "$TMP_MANIFEST"
  make_lf_only "$TMP_MANIFEST"
  fs_copy "$TMP_MANIFEST" "$SYS_ROOT/Install/amigaimager-packages.txt" --makedir || true
else
  echo "==> No packages selected"
fi

# -----------------------------------------------------------------------------
# 8b) Stage Aminet packages - build-time installation
# -----------------------------------------------------------------------------
if [[ -n "$AMINET_DIR" && -d "$AMINET_DIR" ]]; then
  echo "==> Install Aminet packages from: $AMINET_DIR"
  AMINET_RULES_USED="$(resolve_aminet_rules_csv || true)"
  if [[ -n "$AMINET_RULES_USED" ]]; then
    echo "==> Aminet mapping model: CSV ($(basename "$AMINET_RULES_USED"))"
  else
    echo "==> Aminet mapping model: legacy fallback (CSV rules not found)"
  fi

  declare -a AMINET_BUILDTIME=()
  declare -a AMINET_UNMAPPED=()
  declare -a AMINET_FILES=()
  LHA_READY=0

  for lha_file in "$AMINET_DIR"/*.lha "$AMINET_DIR"/*.LHA; do
    [[ -f "$lha_file" ]] || continue

    # Skip PiStorm-specific Aminet packages (VideoCore is PiStorm-only)
    lha_base_check="$(basename "$lha_file")"
    case "$(printf '%s' "$lha_base_check" | tr '[:upper:]' '[:lower:]')" in
      videocore*.lha)
        echo "  -> skipping PiStorm-specific Aminet package: $lha_base_check"
        continue
        ;;
    esac

    AMINET_FILES+=("$lha_file")
    lha_base="$(basename "$lha_file")"
    case "$(printf '%s' "$lha_base" | tr '[:upper:]' '[:lower:]')" in
      lha_68040.lha|lha_68020.lha|lha_68k.lha) LHA_READY=1 ;;
    esac
  done

  # WHDLoad is sourced from the external staging dir (whdload.de) — add it to AMINET_FILES
  # so the CSV install loop processes it alongside the Aminet packages.
  if [[ -n "${IBROWSE_DIR:-}" && -d "${IBROWSE_DIR:-}" ]]; then
    for _whdl in "$IBROWSE_DIR"/WHDLoad_usr.lha "$IBROWSE_DIR"/WHDLoad*.lha; do
      [[ -f "$_whdl" ]] || continue
      AMINET_FILES+=("$_whdl")
      echo "==> WHDLoad: adding $(basename "$_whdl") from external staging dir"
      break
    done
  fi

  # LhA is mandatory
  if [[ "${LHA_READY:-0}" -ne 1 ]]; then
    lha_archive="$(download_lha_from_aminet || true)"
    if [[ -n "${lha_archive:-}" && -f "$lha_archive" ]]; then
      echo "==> Aminet: using downloaded mandatory package $(basename "$lha_archive")"
      AMINET_FILES+=("$lha_archive")
      LHA_READY=1
    else
      echo "==> WARN: Aminet: mandatory LhA archive was not found locally and could not be downloaded."
    fi
  fi

  for lha_file in "${AMINET_FILES[@]}"; do
    lha_base="$(basename "$lha_file")"

    # mpega.library requires 68020+ — MiSTer Minimig is 68000-baseline, skip.
    if echo "$lha_base" | grep -qi "^mpega_library\.lha$"; then
      echo "  -> skipping $lha_base (requires 68020+; MiSTer Minimig is 68000)"
      continue
    fi

    # RiVA video player (m68k binary) requires 68040+ — skip on MiSTer.
    if echo "$lha_base" | grep -qi "^riva-.*\.lha$"; then
      echo "  -> skipping $lha_base (requires 68040+; MiSTer Minimig is 68000)"
      continue
    fi

    echo "  -> processing Aminet: $lha_base"
    if install_aminet_package_buildtime "$SYS_ROOT" "$lha_file"; then
      AMINET_BUILDTIME+=("$lha_base")
      case "$(printf '%s' "$lha_base" | tr '[:upper:]' '[:lower:]')" in
        lha_68040.lha|lha_68020.lha|lha_68k.lha) LHA_READY=1 ;;
      esac
    else
      AMINET_UNMAPPED+=("$lha_base")
    fi
  done

  echo "==> Aminet: ${#AMINET_BUILDTIME[@]} packages installed at build-time"

  if [[ "${#AMINET_UNMAPPED[@]}" -gt 0 ]]; then
    echo "==> Aminet: ${#AMINET_UNMAPPED[@]} packages had no usable CSV/file match and were skipped:"
    for lha_base in "${AMINET_UNMAPPED[@]}"; do
      echo "    - $lha_base"
    done
  fi

  if img_dir_has_entry_ci "$SYS_ROOT/C" "lha"; then
    LHA_READY=1
  else
    LHA_READY=0
  fi

  if [[ "${LHA_READY:-0}" -ne 1 ]]; then
    die "Mandatory package LhA missing in SYS:C. Add lha_68040.lha (or lha_68020.lha/lha_68k.lha) to --aminet-dir."
  fi
else
  echo "==> No Aminet packages to stage"
fi
normalize_roadie_install "$SYS_ROOT"
fixup_ahi_prefs "$SYS_ROOT"

# --- Post-Aminet package fixups ---
finalize_programs_entries "$SYS_ROOT"

# DOpus416: enable FORCEOPENXAD ToolType
for lha_base in "${AMINET_BUILDTIME[@]+"${AMINET_BUILDTIME[@]}"}"; do
  case "$(printf '%s' "$lha_base" | tr '[:upper:]' '[:lower:]')" in
    dopus414jrbin.lha|dopus416jrbin.lha)
      apply_dopus_tooltype_fix "$SYS_ROOT"
      ;;
  esac
done

# WHDLoadWrapper: install if WHDLoad was staged
for lha_base in "${AMINET_BUILDTIME[@]+"${AMINET_BUILDTIME[@]}"}"; do
  case "$(printf '%s' "$lha_base" | tr '[:upper:]' '[:lower:]')" in
    whdload_usr.lha|whdload-user.lha|whdload.lha)
      download_and_install_whdload_wrapper "$SYS_ROOT"
      ;;
  esac
done

# WHDLoad kickstart ROMs
if [[ -n "${WHDLOAD_KICKSTARTS_DIR:-}" && -d "${WHDLOAD_KICKSTARTS_DIR:-}" ]]; then
  install_whdload_kickstarts "$SYS_ROOT" "$WHDLOAD_KICKSTARTS_DIR"
fi

# IMP3 registration: file takes precedence over manual code
if [[ -n "${IMP3_REG_FILE:-}" && -f "$IMP3_REG_FILE" ]]; then
  if "$HST_IMAGER" fs dir "$SYS_ROOT/Programs/IMP3" >/dev/null 2>&1 \
     || "$HST_IMAGER" fs dir "$SYS_ROOT/C/IMP3" >/dev/null 2>&1; then
    fs_copy_file "$IMP3_REG_FILE" "$SYS_ROOT/S/imp3.reg"
    echo "==> IMP3: registration file copied to S:imp3.reg"
  else
    echo "==> IMP3: reg file provided but IMP3 not found; skipping"
  fi
elif [[ -n "${IMP3_REG:-}" ]]; then
  if "$HST_IMAGER" fs dir "$SYS_ROOT/Programs/IMP3" >/dev/null 2>&1 \
     || "$HST_IMAGER" fs dir "$SYS_ROOT/C/IMP3" >/dev/null 2>&1; then
    local_imp3_reg="$STAGE/imp3.reg"
    printf '%s' "$IMP3_REG" > "$local_imp3_reg"
    fs_copy_file "$local_imp3_reg" "$SYS_ROOT/S/imp3.reg"
    echo "==> IMP3: registration code written to S:imp3.reg"
  else
    echo "==> IMP3: reg code provided but IMP3 not found; skipping"
  fi
fi

echo "==> PROGRESS 6/10 Configuring RTG and network"
# -----------------------------------------------------------------------------
# MiSTer RTG: Picasso96 via UAEGFX (optional)
# -----------------------------------------------------------------------------
if [[ -n "$SCREEN_MODE_ID" ]]; then
  echo "==> MiSTer RTG: Screen mode requested (UAEGFX)"

  # Tell common.sh P96 install to skip Videocore/Uaegfx monitor — MiSTer
  # provides its own monitor via MiSTer_RTG.lha
  export SKIP_VIDEOCORE_MONITOR=1

  # Stage P96 (iComp archive → Aminet staging dir → download from Aminet)
  echo "==> Staging Picasso96 for MiSTer RTG"
  stage_p96_icomp_archive "$SYS_ROOT"

  # Download and install MiSTer-specific RTG card driver (MiSTer_RTG.lha).
  # This provides the FPGA framebuffer card driver that P96 needs to expose
  # RTG modes. Without it, only native PAL/NTSC modes are available.
  # The standard uaegfx monitor from P96 must be removed — the MiSTer card
  # driver provides its own monitor integration.
  _mister_rtg_url="https://raw.githubusercontent.com/MiSTer-devel/Minimig-AGA_MiSTer/MiSTer/extra/rtg_driver/MiSTer_RTG.lha"
  _mister_rtg_lha="$STAGE/MiSTer_RTG.lha"
  _mister_rtg_dir="$STAGE/mister-rtg-extract"
  echo "==> MiSTer RTG: Downloading MiSTer_RTG.lha card driver"
  if curl -fsSL "$_mister_rtg_url" -o "$_mister_rtg_lha" 2>/dev/null; then
    rm -rf "$_mister_rtg_dir" >/dev/null 2>&1 || true
    mkdir -p "$_mister_rtg_dir"
    extract_lha_to_host "$_mister_rtg_lha" "$_mister_rtg_dir"

    # Copy Devs/ and Libs/ contents into the image
    if [[ -d "$_mister_rtg_dir/Devs" ]]; then
      echo "==> MiSTer RTG: Installing card driver files to Devs/"
      fs_copy_tree_filtered "$_mister_rtg_dir/Devs" "$SYS_ROOT/Devs"
    fi
    if [[ -d "$_mister_rtg_dir/Libs" ]]; then
      echo "==> MiSTer RTG: Installing card driver files to Libs/"
      fs_copy_tree_filtered "$_mister_rtg_dir/Libs" "$SYS_ROOT/Libs"
    fi

    # Remove standard uaegfx monitor — MiSTer card driver provides its own
    echo "==> MiSTer RTG: Removing standard uaegfx monitor (replaced by MiSTer card driver)"
    "$HST_IMAGER" fs delete "$SYS_ROOT/Devs/Monitors/uaegfx" --force >/dev/null 2>&1 || true
    "$HST_IMAGER" fs delete "$SYS_ROOT/Devs/Monitors/uaegfx.info" --force >/dev/null 2>&1 || true

    # Install MiSTer-specific Picasso96Settings.
    # This replaces the P96 shareware settings with one that defines the
    # MiSTer board and its supported resolutions.
    if [[ -f "$_mister_rtg_dir/Devs/Picasso96Settings" ]]; then
      echo "==> MiSTer RTG: Installing MiSTer-specific Picasso96Settings"
      fs_copy_file "$_mister_rtg_dir/Devs/Picasso96Settings" "$SYS_ROOT/Devs/Picasso96Settings"
    fi

    # Also install P96 shareware uaegfx.card as a fallback — some P96 versions
    # need uaegfx.card present even when using a custom card driver.
    if [[ -f "$_mister_rtg_dir/Devs/Monitors/MiSTer.info" ]]; then
      # Change BOARDTYPE in MiSTer.info to uaegfx to match the P96 shareware
      # board registration, in case rtg.library doesn't recognize "MiSTer"
      echo "==> MiSTer RTG: Note: MiSTer.info BOARDTYPE=MiSTer (from official MiSTer_RTG.lha)"
    fi

    echo "==> MiSTer RTG: Card driver installed"
    rm -rf "$_mister_rtg_dir" "$_mister_rtg_lha" 2>/dev/null || true
  else
    echo "==> WARN: Failed to download MiSTer_RTG.lha — RTG may not work"
  fi

  # P96 ModeIDs use resolution-based $50xx prefixes that are universal across
  # all P96 cards (UAEGFX, VideoCore, CyberVision, etc.). Write screenmode.prefs
  # with the user's selected resolution so the RTG mode is pre-configured.
  write_screenmode_prefs "$SYS_ROOT" "$SCREEN_MODE_ID" "$WB_DEPTH_HEX"

  # Relocate IPrefs after LoadWB for clean RTG switch
  relocate_iprefs_after_loadwb "$SYS_ROOT"
else
  echo "==> MiSTer: No RTG mode selected (native chipset)"
fi

# IMP3 prefs: RTG variant if UAEGFX screen mode selected, native otherwise
if [[ -n "$SCREEN_MODE_ID" ]]; then
  install_imp3_prefs "$SYS_ROOT" 1
else
  install_imp3_prefs "$SYS_ROOT" 0
fi

# -----------------------------------------------------------------------------
# MiSTer Networking: Roadshow over PPP/serial (optional)
# MiSTer Minimig exposes a UART in the core. The MiSTer Linux side runs a PPP
# server (configured via /media/fat/linux/ppp_options). On the Amiga side,
# Roadshow connects via serial.device -> ppp-serial.device.
# OSD setting: UART Mode = PPP, Baud = 115200.
# -----------------------------------------------------------------------------
if [[ "$NETWORK_STACK" != "none" ]]; then
  echo "==> MiSTer: Network stack: $NETWORK_STACK (PPP over serial)"

  # Extract Roadshow BSD socket if available.
  # MiSTer Minimig is 68000 baseline — install the 68000-compatible (root)
  # variants of bsdsocket.library and ppp-*.device.
  if [[ -n "$ROADSHOW_ARCHIVE" && -f "$ROADSHOW_ARCHIVE" ]]; then
    extract_roadshow_bsdsocket "$SYS_ROOT" 0
  fi

  # --- PPP network interface: DEVS:NetInterfaces/PPP ---
  echo "==> MiSTer: Writing Roadshow PPP interface config"
  ensure_img_dir "$SYS_ROOT/Devs/NetInterfaces"
  _ppp_iface="$STAGE/mister-ppp-iface"
  cat > "$_ppp_iface" <<'PPPIFACE'
device=ppp-serial.device
requiresinitdelay=no
PPPIFACE
  make_lf_only "$_ppp_iface"
  fs_copy_file "$_ppp_iface" "$SYS_ROOT/Devs/NetInterfaces/PPP"
  rm -f "$_ppp_iface"

  # --- PPP serial config: S:PPP-Configurations/ppp-serial ---
  echo "==> MiSTer: Writing PPP serial configuration (115200 baud, nullmodem)"
  ensure_img_dir "$SYS_ROOT/S/PPP-Configurations"
  _ppp_serial="$STAGE/mister-ppp-serial"
  cat > "$_ppp_serial" <<'PPPSERIAL'
interface=ppp
device=serial.device
baud=115200
rtscts=no
checkcarrier=no
nullmodem=yes
PPPSERIAL
  make_lf_only "$_ppp_serial"
  fs_copy_file "$_ppp_serial" "$SYS_ROOT/S/PPP-Configurations/ppp-serial"
  rm -f "$_ppp_serial"

  # --- DNS: DEVS:Internet/name_resolution ---
  echo "==> MiSTer: Writing DNS name resolution (8.8.8.8)"
  ensure_img_dir "$SYS_ROOT/Devs/Internet"
  _dns="$STAGE/mister-name-resolution"
  cat > "$_dns" <<'DNSCONF'
NAMESERVER 8.8.8.8
NAMESERVER 8.8.4.4
DNSCONF
  make_lf_only "$_dns"
  fs_copy_file "$_dns" "$SYS_ROOT/Devs/Internet/name_resolution"
  rm -f "$_dns"

  # Create S:Network-Startup to activate PPP interface at boot.
  # Unlike PiStorm (where genet auto-activates), PPP needs explicit AddNetInterface.
  echo "==> MiSTer: Writing S:Network-Startup (PPP auto-connect)"
  _net_startup="$STAGE/mister-network-startup"
  cat > "$_net_startup" <<'NETSTARTUP'
; MiSTer PPP network startup (generated by Amiga Imager)
; Step 1: Register the PPP interface with Roadshow
AddNetInterface DEVS:NetInterfaces/PPP
; Step 2: Establish PPP connection via serial (auto-dial)
Wait 1
ppp_dialer S:PPP-Configurations/ppp-serial
NETSTARTUP
  make_lf_only "$_net_startup"
  fs_copy_file "$_net_startup" "$SYS_ROOT/S/Network-Startup"
  rm -f "$_net_startup"

  echo "==> MiSTer: PPP networking configured (set OSD: UART=PPP, Baud=115200)"
else
  echo "==> MiSTer: No network stack selected"
fi

# -----------------------------------------------------------------------------
# 9) Finalize RunAll
# -----------------------------------------------------------------------------
echo "==> PROGRESS 7/10 Installing optional software"
echo "==> Finalize RunAll (must be a file)"

TMP_RUNALL="$STAGE/RunAll"
{
  echo "; Amiga Imager - RunAll optional installers (MiSTer Minimig)"
  echo "FAILAT 9999"
  echo "ECHO \"Amiga Imager: Running optional installers...\""
  echo "ECHO \"Amiga Imager: RunAll start\" >T:AmigaImager-OptionalInstall.log"

  if [[ -n "$PACKAGES" ]]; then
    IFS=',' read -r -a __PKGS <<< "$PACKAGES"
    for __p in "${__PKGS[@]}"; do
      __p="$(echo "$__p" | tr -d ' \t\n\r')"
      [[ -z "$__p" ]] && continue
      # Skip PiStorm-specific packages in RunAll
      case "$(printf '%s' "$__p" | tr '[:upper:]' '[:lower:]')" in
        videocore*|emu68*|genet*|wifipi*) continue ;;
      esac
      echo ""
      echo "; Package: $__p"
      echo "IF EXISTS \"S:OneTimeRunWB/$__p/Install\""
      echo "  ECHO \"-> $__p\""
      echo "  Execute \"S:OneTimeRunWB/$__p/Install\""
      echo "  IF WARN"
      echo "    ECHO \"WARN: $__p installer returned warning/error; continuing.\""
      echo "  ENDIF"
      echo "ENDIF"
    done
  else
    echo "ECHO \"No packages selected\""
  fi

  echo ""
  echo "ECHO \"Amiga Imager: Optional installers finished.\""
  echo "ECHO \"FALSE\" >ENVARC:FIRSTTIMEBOOT"
  echo "ECHO \"FALSE\" >ENVARC:FIRSTTIMEBOOTWB"
  echo "ECHO \"FALSE\" >ENV:FIRSTTIMEBOOT"
  echo "ECHO \"FALSE\" >ENV:FIRSTTIMEBOOTWB"
} >"$TMP_RUNALL"

make_lf_only "$TMP_RUNALL"
ensure_img_dir "$SYS_ROOT/S/OneTimeRunWB"
fs_copy_file "$TMP_RUNALL" "$SYS_ROOT/S/OneTimeRunWB/RunAll"

# -----------------------------------------------------------------------------
# 10) Ensure WBStartup/OnetimeRunWB exists
# -----------------------------------------------------------------------------
echo "==> Ensure OnetimeRunWB WBStartup script exists"

ASSET_WB_OTR="$ASSETS_DIR/AmigaFiles/System/WBStartup/OnetimeRunWB"
ASSET_WB_OTR_INFO="$ASSETS_DIR/AmigaFiles/System/WBStartup/OnetimeRunWB.info"

fs_mkdir_p "$SYS_ROOT/WBStartup"

TMP_WB_OTR="$STAGE/OnetimeRunWB"
if [[ -f "$ASSET_WB_OTR" ]]; then
  cp -f "$ASSET_WB_OTR" "$TMP_WB_OTR"
else
  cat >"$TMP_WB_OTR" <<'__WB_OTR__'
WAIT 1
IF $FIRSTTIMEBOOTWB EQ "TRUE"
   ECHO "DELETE >NIL: SYS:WBStartup/OneTimeRunWB.info ALL FORCE *nDELETE >NIL: SYS:WBStartup/OneTimeRunWB ALL FORCE" to S:OneTimeRun/DeleteOneTimeRunWB
   ECHO "Amiga Imager - Performing Final Setup"
   c:ce >NIL: S:OneTimeRunWB
   ; Restore disabled WBStartup entries early so they are not lost if optional
   ; installers fail later in this script.
   COPY >NIL: "SYS:WBStartup/Disabled/#?" TO "SYS:WBStartup/" ALL FORCE CLONE
   ECHO "Running One Time Scripts"
   ECHO >T:RunTimeScript "FAILAT 21"
   LIST >>T:RunTimeScriptWB S:OneTimeRunWB PAT=~(#?.info|) LFORMAT="ECHO *"Running Script: %n*"*nEcho *"*"*nExecute *"%p%n*"*nDelete *"%p%n*"*n" ALL FILES
   EXECUTE T:RunTimeScriptWB
   DELETE >NIL: T:RunTimeScriptWB
   DELETE >NIL: "SYS:WBStartup/Disabled" ALL FORCE
   ECHO ""
   ECHO "Rebooting the Amiga in 3 seconds!"
   Wait 3
   Sys:c/diskchange DH0:
   Sys:c/reboot
   ENDCLI
ENDIF
__WB_OTR__
fi
make_lf_only "$TMP_WB_OTR"
fs_copy_file_no_makedir "$TMP_WB_OTR" "$SYS_ROOT/WBStartup/OnetimeRunWB"

# Install .info if present in Assets
if [[ -f "$ASSET_WB_OTR_INFO" ]]; then
  fs_copy_file "$ASSET_WB_OTR_INFO" "$SYS_ROOT/WBStartup/OnetimeRunWB.info"
fi

# Keep other WBStartup items disabled until first Workbench boot.
ensure_img_dir "$SYS_ROOT/WBStartup/Disabled"

# Install additional WBStartup files from Assets into Disabled/
ASSET_WB_DIR="$ASSETS_DIR/AmigaFiles/System/WBStartup"
if [[ -d "$ASSET_WB_DIR" ]]; then
  while IFS= read -r -d '' _wbf; do
    _wbname="$(basename "$_wbf")"
    case "$_wbname" in
      OnetimeRunWB|OnetimeRunWB.info|OneTimeRunWB|OneTimeRunWB.info) continue;;
    esac
    echo "==> WBStartup extra (disabled until first boot): $_wbname"
    fs_copy_file "$_wbf" "$SYS_ROOT/WBStartup/Disabled/$_wbname"
  done < <(find "$ASSET_WB_DIR" -mindepth 1 -maxdepth 1 -type f -print0)
fi

# Ensure WBStartup tools have their executables
for _wbtool in DefIcons AssignWedge; do
  _tmp_wbtool="$STAGE/wbstartup-${_wbtool}"
  if ! fs_pull_file "$SYS_ROOT/WBStartup/Disabled/$_wbtool" "$_tmp_wbtool" >/dev/null 2>&1; then
    if fs_pull_file "$SYS_ROOT/C/$_wbtool" "$_tmp_wbtool" >/dev/null 2>&1; then
      write_uaem_with_perms "----rwed" "$_tmp_wbtool"
      fs_copy_file "$_tmp_wbtool" "$SYS_ROOT/WBStartup/Disabled/$_wbtool" --uaemetadata UaeMetafile
    fi
  fi
done

# Harden first-boot scripts
harden_first_boot_scripts_mister "$SYS_ROOT"

echo "==> PROGRESS 8/10 Installing IBrowse and WBDock"
# -----------------------------------------------------------------------------
# Install IBrowse/AmiSSL (AFTER hardening)
# -----------------------------------------------------------------------------
if [[ -n "$IBROWSE_DIR" && -d "$IBROWSE_DIR" ]]; then
  install_ibrowse_from_official "$SYS_ROOT" "$IBROWSE_DIR" "${IBROWSE_KEY:-}"
fi
install_ibrowse_key_file "$SYS_ROOT" "${IBROWSE_KEY:-}" "${IBROWSE_DIR:-}"

# WBDock2
if [[ "$INCLUDE_WBDOCK" -eq 1 && -n "$IBROWSE_DIR" && -d "$IBROWSE_DIR" ]]; then
  install_wbdock "$SYS_ROOT" "$IBROWSE_DIR"
  inject_wbdock_into_startup "$SYS_ROOT"
fi

# NewMeter position
configure_newmeter_position "$SYS_ROOT"

# IPrefs relocation handled in the RTG section above (if RTG selected).

# --- Re-apply icon positions after Aminet/optional software ---
# The first pass (step 5) runs before Aminet packages create Programs/ icons.
# This second pass catches those late-arriving .info files.
if [[ "$APPLY_ICON_POSITIONS" == "1" ]]; then
  echo "==> Re-applying icon positions (post-Aminet)"
  apply_icon_positions_from_csv
fi

verify_programs_entries "$SYS_ROOT"

# -----------------------------------------------------------------------------
# Sanity check
# -----------------------------------------------------------------------------
echo "==> PROGRESS 9/10 Running sanity checks"
echo "==> Sanity check (key dirs on SYS:)"
run "$HST_IMAGER" fs dir "$SYS_ROOT" || true
run "$HST_IMAGER" fs dir "$SYS_ROOT/S" --recursive || true
run "$HST_IMAGER" fs dir "$SYS_ROOT/WBStartup" --recursive || true
run "$HST_IMAGER" fs dir "$SYS_ROOT/S/OneTimeRunWB" --recursive || true
run "$HST_IMAGER" rdb info "$RDB" --verbose || true

# File-vs-directory checks
TMP_CHECK_CLI="$STAGE/.check-System-CLI"
if fs_pull_file "$SYS_ROOT/System/CLI" "$TMP_CHECK_CLI"; then
  echo "==> OK: System/CLI is a file"
elif "$HST_IMAGER" fs dir "$SYS_ROOT/System/CLI" >/dev/null 2>&1; then
  echo "==> WARN: System/CLI is a directory (should be a file)"
else
  echo "==> WARN: System/CLI missing (Workbench Shell icon may fail)"
fi
rm -f "$TMP_CHECK_CLI" 2>/dev/null || true

TMP_CHECK_FONT="$STAGE/.check-Prefs-Font"
TMP_CHECK_FONTZ="$STAGE/.check-Prefs-FontZ"
if fs_pull_file "$SYS_ROOT/Prefs/Font" "$TMP_CHECK_FONT"; then
  echo "==> OK: Prefs/Font is a file"
elif fs_pull_file "$SYS_ROOT/Prefs/Font.Z" "$TMP_CHECK_FONTZ"; then
  echo "==> OK: Prefs/Font.Z is a file"
elif "$HST_IMAGER" fs dir "$SYS_ROOT/Prefs/Font" >/dev/null 2>&1; then
  echo "==> WARN: Prefs/Font is a directory (should be a file)"
elif "$HST_IMAGER" fs dir "$SYS_ROOT/Prefs/Font.Z" >/dev/null 2>&1; then
  echo "==> WARN: Prefs/Font.Z is a directory (should be a file)"
else
  echo "==> WARN: Prefs/Font(.Z) missing"
fi
rm -f "$TMP_CHECK_FONT" "$TMP_CHECK_FONTZ" 2>/dev/null || true

echo "==> PROGRESS 10/10 Complete"
echo "==> DONE: $IMG"
