#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# build-emulator.sh — Emulator-target build script (MiSTer Minimig, UAE, Amiberry)
#
# Creates bootable HDF images for Amiga emulators, selected via --emu-profile:
#   mister   — MiSTer FPGA Minimig core (PPP networking, SHARE:, wheel driver,
#              official MiSTer RTG driver)
#   uae      — UAE-family emulators: FS-UAE, WinUAE, vAmiga (uaegfx P96 RTG,
#              networking via the host's bsdsocket.library emulation)
#   amiberry — Amiberry on Raspberry Pi / Linux (currently identical to uae)
# Image layout: Pure RDB with Amiga partitions (no FAT boot partition).
# No genet/wifipi hardware (emulators provide their own NIC).
#
# Sources shared functions from lib/common.sh and partition_layout.sh.
# ---------------------------------------------------------------------------

set -euo pipefail

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

SCRIPT_VERSION="${AMIGA_IMAGER_APP_VERSION:-dev}"
echo "==> Amiga-Imager emulator 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-emulator.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}"
shift || true
KICK_ROM=""

# Positional arguments:
#   New MiSTer format: <img> [<iso>] <assets_dir> [flags...]
#   Legacy format:     <img> <kick.rom|-> [<iso>] <assets_dir> [flags...]
# MiSTer gets Kickstart from the core configuration, so a ROM is never consumed
# by this build script. Keep accepting the old positional slot for compatibility.
if [[ $# -gt 0 ]]; then
  _mister_first_pos_lc="$(printf "%s" "${1:-}" | tr '[:upper:]' '[:lower:]')"
  case "$_mister_first_pos_lc" in
    ""|"-"|"none"|"null"|*.rom|*.bin)
      KICK_ROM="${1:-}"
      shift || true
      ;;
  esac
fi

ISO_PATH=""
ASSETS_DIR=""
if [[ $# -ge 2 && "${2#--}" == "$2" ]]; then
  ISO_PATH="${1:-}"
  ASSETS_DIR="${2:?Assets folder path required}"
  shift 2 || true
else
  ASSETS_DIR="${1:?Assets folder path required}"
  shift || 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=""
PARTITION_CONTENT_MAP=""
ADF_DIR=""                 # Optional: merged ADF folder
ICON_SET="standard"        # standard | glowicons
BUILD_MANIFEST=""          # optional: path to JSON manifest written by Swift (Stage D)
BUILD_PLAN=""              # optional: path to compiled shell execution plan (Phase 2A/2B)
PACKAGES=""                # comma-separated package ids resolved by Swift
LOCALES=""                 # comma-separated locale codes
CSV_DIR=""                 # explicit InputFiles directory
BLANK_AMIGA_PARTITIONS=0   # 1 = create formatted empty Amiga partitions, skip OS copy/install
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=""
WORKBENCH_ADF=""          # first Workbench ADF discovered in ADF source
IBROWSE_DIR=""             # optional staging dir for IBrowse installer
IBROWSE_KEY=""             # optional IBrowse key file
GOVD_KEY=""                # optional GoVD registration key file
INCLUDE_IBROWSE=0          # 1=install IBrowse from staging dir
INCLUDE_WBDOCK=0           # 1=install WBDock2
INCLUDE_GOVD=0             # 1=install GoVD virtual desktop
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
ICON_PASS2_MODE="scoped"   # scoped|global for the late optional-package icon pass
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

# Emulator profile (mister | uae | amiberry)
EMU_PROFILE="mister"       # mister = MiSTer Minimig; uae/amiberry = UAE-family emulators

# MiSTer extras (mister profile only)
MISTER_SHARED_FOLDER=1     # 1 = install SHARE: shared-folder support
MISTER_MOUSE_WHEEL=1       # 1 = install WheelDriver + FreeWheel

# RTG card selection (uae/amiberry profiles; mirrors build-classic.sh)
RTG_CARD=""                # e.g. UAEGFX
RTG_CARD_DRIVER=""         # P96 .card driver filename (informational)

# 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_BLOCKSIZES=()  # bytes per filesystem block
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"
HST_IMAGER="${HST_IMAGER:-$DEFAULT_HST}"
AMIGA_TOOLS="${AMIGA_TOOLS:-}"


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

mister_p96_suffix_for_depth() {
  local depth_hex="${1:-08}"
  local depth_dec
  depth_dec=$((16#$depth_hex))
  if (( depth_dec < 16 )); then
    printf "1000"
  elif (( depth_dec == 16 )); then
    printf "1100"
  elif (( depth_dec <= 24 )); then
    printf "1200"
  else
    printf "1300"
  fi
}

mister_normalize_screen_mode_id() {
  [[ -z "${SCREEN_MODE_ID:-}" ]] && return 0

  local prefix=""
  case "${SCREEN_WIDTH:-}x${SCREEN_HEIGHT:-}" in
    640x480)   prefix="5000" ;;
    1024x768)  prefix="5001" ;;
    1280x720)  prefix="5002" ;;
    1920x1080) prefix="5003" ;;
    1600x1200) prefix="5004" ;;
    800x600)   prefix="5005" ;;
  esac

  if [[ -z "$prefix" ]]; then
    echo "==> WARN: MiSTer RTG: unsupported preset ${SCREEN_WIDTH:-?}x${SCREEN_HEIGHT:-?}; falling back to 1280x720"
    prefix="5002"
    SCREEN_WIDTH=1280
    SCREEN_HEIGHT=720
  fi

  local suffix normalized
  suffix="$(mister_p96_suffix_for_depth "${WB_DEPTH_HEX:-08}")"
  normalized="$(printf "%s%s" "$prefix" "$suffix" | tr '[:lower:]' '[:upper:]')"
  if [[ "$SCREEN_MODE_ID" != "$normalized" ]]; then
    echo "==> MiSTer RTG: normalizing ModeID $SCREEN_MODE_ID -> $normalized for official MiSTer_RTG.lha"
    SCREEN_MODE_ID="$normalized"
  fi
}

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
SYS:C/SysVars >NIL:
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"
  chmod +x "$tmp_wb"  # Amiga 's' bit: required for WBStartup script execution
  fs_copy_file "$tmp_wb" "$sys_root/WBStartup/OnetimeRunWB"

  # 2) Startup-Sequence fallback:
  #    System detection keys on $UAEMAJOR (UAE) and the PiStorm device probes.
  #    FS-UAE frequently does NOT set $UAEMAJOR, and on real MiSTer hardware no
  #    PiStorm probe matches — either way $System is left empty. Default it to
  #    the system this image was actually BUILT for so first-boot logic (RTG
  #    mode, WBDock, locale, WBPattern) runs under the correct path:
  #      uae/amiberry -> UAE   (mirrors build-pistorm.sh's UAE fallback)
  #      mister       -> MiSTer
  #    Previously this always defaulted to MiSTer, so UAE images on FS-UAE booted
  #    the MiSTer path: native chipset instead of uaegfx RTG, no dock, stock
  #    first-boot locale/pattern requesters.
  local _fb_system="MiSTer"
  case "$EMU_PROFILE" in
    uae|amiberry) _fb_system="UAE" ;;
  esac
  local tmp_ss="$STAGE/Startup-Sequence.mister"
  if fs_pull_file "$sys_root/S/Startup-sequence" "$tmp_ss"; then
    if ! grep -q 'Amiga Imager fallback: default System=' "$tmp_ss"; then
      awk -v fb="$_fb_system" '
        BEGIN { inserted = 0 }
        {
          if (inserted == 0 && $0 ~ /^IF \$KICKVER EQ 47/) {
            print "; Amiga Imager fallback: default System=" fb " when detection did not set it"
            print "IF \"$System\" EQ \"\""
            print "  SETENV System \"" fb "\""
            print "  SETENV SYSTEM \"" fb "\""
            print "ENDIF"
            print ""
            inserted = 1
          }
          print
        }
      ' "$tmp_ss" > "$tmp_ss.new" && mv "$tmp_ss.new" "$tmp_ss"
      make_lf_only "$tmp_ss"
      chmod +x "$tmp_ss"  # Amiga 's' bit: Startup-sequence is an AmigaDOS script
      fs_copy_file "$tmp_ss" "$sys_root/S/Startup-sequence"
    fi
  fi

  # 3) Disable MiSTer PPP autostart.
  #    PPP over the MiSTer UART is timing-sensitive and a hidden background
  #    dialer can leave the interface/serial device allocated before the user
  #    clicks Ethernet/GoOnline. Keep networking as an explicit WBDock action.
  local tmp_us="$STAGE/User-startup.mister-network"
  local us_name=""
  for us_try in "User-startup" "user-startup" "User-Startup"; do
    if fs_pull_file "$sys_root/S/$us_try" "$tmp_us"; then
      us_name="$us_try"
      break
    fi
  done
  if [[ -n "$us_name" ]] && grep -Eq '^[[:space:]]*Execute[[:space:]]+S:Network-Startup' "$tmp_us"; then
    awk '
      /^[[:space:]]*Execute[[:space:]]+S:Network-Startup[[:space:]]*$/ {
        print "  ; MiSTer: start PPP manually from WBDock Ethernet"
        print "  ; Execute S:Network-Startup"
        next
      }
      { print }
    ' "$tmp_us" > "$tmp_us.new" && mv "$tmp_us.new" "$tmp_us"
    make_lf_only "$tmp_us"
    chmod +x "$tmp_us"  # Amiga 's' bit: User-startup is an AmigaDOS script
    fs_copy_file "$tmp_us" "$sys_root/S/$us_name"
  fi

  # 4) 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"
    # Check if specific codec files exist (dispatch-aware — works with both engines)
    local has_codecs=0
    if fs_dir "$sys_root/Programs/Ibrowse/codecs/gif.codec" || \
       fs_dir "$sys_root/Programs/Ibrowse/codecs/jpeg.codec" || \
       fs_dir "$sys_root/Programs/Ibrowse/codecs/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"
    chmod +x "$tmp_ib"  # Amiga 's' bit: required for Execute in T:RunTimeScript
    fs_copy_file "$tmp_ib" "$sys_root/S/OneTimeRun/Ibrowse"
  fi
}

normalize_mister_startup_conditionals() {
  local sys_root="$1"
  local tmp_ss="$STAGE/Startup-Sequence.mister-normalize"

  if fs_pull_file "$sys_root/S/Startup-sequence" "$tmp_ss"; then
    awk '
      {
        line = $0
        sub(/^IF \$SYSTEM EQ "UAE"$/, "IF \"$SYSTEM\" EQ \"UAE\"", line)
        sub(/^IF NOT \$SYSTEM EQ "UAE"$/, "IF NOT \"$SYSTEM\" EQ \"UAE\"", line)
        sub(/^IF \$FIRSTTIMEBOOT EQ "TRUE"$/, "IF \"$FIRSTTIMEBOOT\" EQ \"TRUE\"", line)
        sub(/^IF NOT \$FIRSTTIMEBOOT EQ "TRUE"$/, "IF NOT \"$FIRSTTIMEBOOT\" EQ \"TRUE\"", line)
        sub(/^IF \$REBOOT EQ "TRUE"$/, "IF \"$REBOOT\" EQ \"TRUE\"", line)
        print line
      }
    ' "$tmp_ss" > "$tmp_ss.new" && mv "$tmp_ss.new" "$tmp_ss"

    if ! grep -q 'MiSTer: clear FIRSTTIMEBOOT after CLI one-time scripts' "$tmp_ss"; then
      awk '
        /^[[:space:]]*IF "\$REBOOT" EQ "TRUE"[[:space:]]*$/ {
          print "; MiSTer: clear FIRSTTIMEBOOT after CLI one-time scripts"
          print "IF NOT \"$SYSTEM\" EQ \"UAE\""
          print "  ECHO \"FALSE\" > SYS:PREFS/ENV-ARCHIVE/FIRSTTIMEBOOT"
          print "ENDIF"
          print ""
        }
        { print }
      ' "$tmp_ss" > "$tmp_ss.new" && mv "$tmp_ss.new" "$tmp_ss"
    fi

    make_lf_only "$tmp_ss"
    chmod +x "$tmp_ss"  # Amiga 's' bit: Startup-sequence is an AmigaDOS script
    fs_copy_file "$tmp_ss" "$sys_root/S/Startup-sequence"
  fi

  local tmp_wb="$STAGE/OnetimeRunWB.mister-normalize"
  if fs_pull_file "$sys_root/WBStartup/OnetimeRunWB" "$tmp_wb"; then
    awk '
      {
        line = $0
        sub(/^IF \$FIRSTTIMEBOOTWB EQ "TRUE"$/, "IF \"$FIRSTTIMEBOOTWB\" EQ \"TRUE\"", line)
        print line
      }
    ' "$tmp_wb" > "$tmp_wb.new" && mv "$tmp_wb.new" "$tmp_wb"
    make_lf_only "$tmp_wb"
    chmod +x "$tmp_wb"  # Amiga 's' bit: required for WBStartup script execution
    fs_copy_file "$tmp_wb" "$sys_root/WBStartup/OnetimeRunWB"
  fi
}

reorder_mister_post_loadwb_services() {
  local sys_root="$1"
  local tmp_ss="$STAGE/Startup-Sequence.mister-postloadwb"
  local ss_name=""

  for ss_try in "Startup-Sequence" "Startup-sequence" "startup-sequence"; do
    if fs_pull_file "$sys_root/S/$ss_try" "$tmp_ss"; then
      ss_name="$ss_try"
      break
    fi
  done
  [[ -z "$ss_name" ]] && return 0

  awk '
    BEGIN {
      capture = ""
      saw_iprefs = 0
      wbdock_block = ""
    }

    {
      if (capture == "net_old") {
        if ($0 ~ /^[[:space:]]*ENDIF[[:space:]]*$/) capture = ""
        next
      }
      if (capture == "net_new") {
        capture = ""
        next
      }
      if (capture == "wbdock") {
        wbdock_block = wbdock_block $0 ORS
        if ($0 ~ /^[[:space:]]*ENDIF[[:space:]]*$/) capture = ""
        next
      }

      if ($0 ~ /^; MiSTer: delayed PPP autostart \(skip on first boot, run after Workbench\)$/) {
        capture = "net_old"
        next
      }
      if ($0 ~ /^; MiSTer: delayed PPP autostart \(run after Workbench\)$/) {
        capture = "net_new"
        next
      }
      if ($0 ~ /^; Amiga Imager: delayed WBDock launch \(skip on first boot, runs after reboot\)$/) {
        capture = "wbdock"
        wbdock_block = $0 ORS
        next
      }

      if ($0 ~ /^[[:space:]]*Run[[:space:]]+>NIL:[[:space:]]+IPrefs[[:space:]]*$/) {
        saw_iprefs = 1
      }

      if ($0 ~ /^[[:space:]]*EndCLI/ && saw_iprefs) {
        if (wbdock_block != "") {
          printf "%s", wbdock_block
          print ""
        }
      }

      print
    }
  ' "$tmp_ss" > "$tmp_ss.new"

  if ! cmp -s "$tmp_ss" "$tmp_ss.new"; then
    mv "$tmp_ss.new" "$tmp_ss"
    make_lf_only "$tmp_ss"
    chmod +x "$tmp_ss"  # Amiga 's' bit: Startup-sequence is an AmigaDOS script
    fs_copy_file "$tmp_ss" "$sys_root/S/$ss_name"
  else
    rm -f "$tmp_ss.new"
  fi
}

install_mister_shared_folder_support() {
  local sys_root="$1"
  local share_url="https://raw.githubusercontent.com/MiSTer-devel/Minimig-AGA_MiSTer/MiSTer/extra/MiSTer_share.lha"
  local share_lha=""
  local share_dir="$STAGE/mister-share-extract"
  local cand

  if [[ -n "${AMINET_DIR:-}" && -d "${AMINET_DIR:-}" ]]; then
    for cand in "$AMINET_DIR"/MiSTer_share.lha "$AMINET_DIR"/mister_share.lha; do
      [[ -f "$cand" ]] && { share_lha="$cand"; break; }
    done
  fi
  if [[ -z "$share_lha" ]]; then
    for cand in \
      "$ASSETS_DIR/AmigaFiles/LocalAmigaPackages/MiSTer_share.lha" \
      "$ASSETS_DIR/LocalAmigaPackages/MiSTer_share.lha"; do
      [[ -f "$cand" ]] && { share_lha="$cand"; break; }
    done
  fi
  if [[ -z "$share_lha" ]]; then
    share_lha="$STAGE/MiSTer_share.lha"
    echo "==> MiSTer shared folder: downloading MiSTer_share.lha"
    if ! curl -fsSL --connect-timeout 15 --max-time 120 "$share_url" -o "$share_lha" 2>/dev/null; then
      echo "==> WARN: MiSTer shared folder: failed to download MiSTer_share.lha"
      return 0
    fi
  else
    echo "==> MiSTer shared folder: using $(basename "$share_lha")"
  fi

  rm -rf "$share_dir" >/dev/null 2>&1 || true
  mkdir -p "$share_dir"
  if ! extract_lha_to_host "$share_lha" "$share_dir"; then
    echo "==> WARN: MiSTer shared folder: failed to extract $(basename "$share_lha")"
    return 0
  fi

  local share_fs="" dummy_device="" mountlist_src=""
  for cand in "$share_dir/L/MiSTerFileSystem" "$share_dir/l/MiSTerFileSystem"; do
    [[ -f "$cand" ]] && { share_fs="$cand"; break; }
  done
  for cand in "$share_dir/DEVS/dummy.device" "$share_dir/Devs/dummy.device" "$share_dir/devs/dummy.device"; do
    [[ -f "$cand" ]] && { dummy_device="$cand"; break; }
  done
  for cand in "$share_dir/DEVS/MountList" "$share_dir/Devs/MountList" "$share_dir/devs/MountList"; do
    [[ -f "$cand" ]] && { mountlist_src="$cand"; break; }
  done

  if [[ -z "$share_fs" || -z "$dummy_device" || -z "$mountlist_src" ]]; then
    echo "==> WARN: MiSTer shared folder: archive missing expected files"
    return 0
  fi

  write_uaem_with_perms "----rwed" "$share_fs"
  write_uaem_with_perms "----rwed" "$dummy_device"
  fs_copy_file "$share_fs" "$sys_root/L/MiSTerFileSystem" --uaemetadata UaeMetafile
  fs_copy_file "$dummy_device" "$sys_root/Devs/dummy.device" --uaemetadata UaeMetafile

  local mountlist_work="$STAGE/MiSTerShare-MountList"
  if fs_pull_file "$sys_root/Devs/MountList" "$mountlist_work"; then
    if ! grep -qi '^[[:space:]]*SHARE:' "$mountlist_work" 2>/dev/null; then
      {
        printf "\n"
        cat "$mountlist_src"
      } >> "$mountlist_work"
    fi
  else
    cp -f "$mountlist_src" "$mountlist_work"
  fi
  make_lf_only "$mountlist_work"
  fs_copy_file "$mountlist_work" "$sys_root/Devs/MountList"

  local dosdriver="$STAGE/MiSTerShare-DOSDriver"
  awk '
    /^[[:space:]]*#/ { next }
    /^[[:space:]]*SHARE:[[:space:]]*/ {
      sub(/^[[:space:]]*SHARE:[[:space:]]*/, "")
      if (length($0) > 0) print
      next
    }
    { print }
  ' "$mountlist_src" > "$dosdriver"
  make_lf_only "$dosdriver"
  fs_copy_file "$dosdriver" "$sys_root/Devs/DOSDrivers/SHARE"

  # Prepare a Disk.info for the mounted SHARE: volume. The real Disk.info must
  # live inside the Linux-side shared folder, so the boot script copies this
  # template after mounting if the user has not already provided one. Use the
  # same geometry as PiStorm's SD0/EMU68BOOT icon.
  local share_icon_base="$STAGE/MiSTerShareDiskBase.info"
  local share_icon_to_copy=""
  if fs_pull_file "$sys_root/Prefs/Env-Archive/Sys/def_harddisk.info" "$share_icon_base" >/dev/null 2>&1 \
    || fs_pull_file "$sys_root/Disk.info" "$share_icon_base" >/dev/null 2>&1; then
    share_icon_to_copy="$(hst_icon_update_file "$share_icon_base" "1" "15" "220" "50" "50" "500" "160")"
    ensure_img_dir "$sys_root/Storage"
    ensure_img_dir "$sys_root/Storage/MiSTerShare"
    fs_copy_file "$share_icon_to_copy" "$sys_root/Storage/MiSTerShare/Disk.info"
    echo "==> MiSTer shared folder: staged SHARE: Disk.info @ 15,220"
  else
    echo "==> WARN: MiSTer shared folder: could not stage SHARE: Disk.info template"
  fi

  local mount_script="$STAGE/MountMiSTerShare"
  cat > "$mount_script" <<'MISTERSHARE'
; Amiga Imager: mount MiSTer Linux shared folder
FailAt 30
IF EXISTS C:Mount
  IF EXISTS L:MiSTerFileSystem
    IF EXISTS DEVS:dummy.device
      C:Mount SHARE: >NIL:
    ENDIF
  ENDIF
ENDIF
Wait 1
IF EXISTS SHARE:
  IF EXISTS SYS:Storage/MiSTerShare/Disk.info
    IF NOT EXISTS ENVARC:MiSTerShareIconPlaced
      C:Copy SYS:Storage/MiSTerShare/Disk.info SHARE:Disk.info QUIET
      Echo "TRUE" >ENVARC:MiSTerShareIconPlaced
    ELSE
      IF NOT EXISTS SHARE:Disk.info
        C:Copy SYS:Storage/MiSTerShare/Disk.info SHARE:Disk.info QUIET
      ENDIF
    ENDIF
  ENDIF
ENDIF
MISTERSHARE
  make_lf_only "$mount_script"
  write_uaem_with_perms "-s--rwed" "$mount_script"
  fs_copy_file "$mount_script" "$sys_root/S/MountMiSTerShare" --uaemetadata UaeMetafile

  local mount_overlay="$STAGE/mister-share-startup.overlay"
  cat > "$mount_overlay" <<'MISTERSHAREOVERLAY'
IF EXISTS SYS:S/MountMiSTerShare
  Run >NIL: Execute SYS:S/MountMiSTerShare
ENDIF
MISTERSHAREOVERLAY
  make_lf_only "$mount_overlay"
  inject_overlay_into_script "$sys_root" "S/Startup-Sequence" "$mount_overlay" inject_before "EndCLI" "MiSTerShare"

  echo "==> MiSTer shared folder: installed SHARE: support (Linux folder: /media/fat/Amiga/shared)"
}

install_mister_wheel_support() {
  local sys_root="$1"
  local wheel_url="https://raw.githubusercontent.com/MiSTer-devel/Minimig-AGA_MiSTer/MiSTer/releases/WheelDriverAkiko.adf"
  local wheel_adf=""
  local wheel_dir="$STAGE/mister-wheel-extract"
  local cand

  if [[ -n "${ADF_SRC:-}" && -d "${ADF_SRC:-}" ]]; then
    for cand in "$ADF_SRC"/WheelDriverAkiko.adf "$ADF_SRC"/WheelDriverAkiko.ADF; do
      [[ -f "$cand" ]] && { wheel_adf="$cand"; break; }
    done
  fi
  if [[ -z "$wheel_adf" ]]; then
    for cand in \
      "$ASSETS_DIR/AmigaFiles/LocalAmigaPackages/WheelDriverAkiko.adf" \
      "$ASSETS_DIR/LocalAmigaPackages/WheelDriverAkiko.adf"; do
      [[ -f "$cand" ]] && { wheel_adf="$cand"; break; }
    done
  fi
  if [[ -z "$wheel_adf" ]]; then
    wheel_adf="$STAGE/WheelDriverAkiko.adf"
    echo "==> MiSTer wheel: downloading WheelDriverAkiko.adf"
    if ! curl -fsSL --connect-timeout 15 --max-time 120 "$wheel_url" -o "$wheel_adf" 2>/dev/null; then
      echo "==> WARN: MiSTer wheel: failed to download WheelDriverAkiko.adf"
      return 0
    fi
  else
    echo "==> MiSTer wheel: using $(basename "$wheel_adf")"
  fi

  rm -rf "$wheel_dir" >/dev/null 2>&1 || true
  mkdir -p "$wheel_dir"
  # Native extractor first (keeps the native path free of hst-imager calls);
  # hst-imager remains the fallback for ADF DOS types it cannot read.
  if ! { [[ -n "${AMIGA_TOOLS:-}" && -x "${AMIGA_TOOLS:-}" ]] \
         && "$AMIGA_TOOLS" adf extract "$wheel_adf" "$wheel_dir" >/dev/null 2>&1; } \
    && ! "$HST_IMAGER" fs extract "$wheel_adf" "$wheel_dir" --recursive --force >/dev/null 2>&1 \
    && ! "$HST_IMAGER" fs extract "$wheel_adf" "$wheel_dir" --recursive >/dev/null 2>&1 \
    && ! "$HST_IMAGER" fs copy "$wheel_adf" "$wheel_dir" --recursive --force >/dev/null 2>&1 \
    && ! "$HST_IMAGER" fs copy "$wheel_adf" "$wheel_dir" --recursive >/dev/null 2>&1; then
    echo "==> WARN: MiSTer wheel: failed to extract $(basename "$wheel_adf")"
    return 0
  fi

  local wheel_driver="" freewheel="" set_akiko=""
  wheel_driver="$(find "$wheel_dir" -type f -iname "WheelDriver" ! -iname "*.info" 2>/dev/null | head -n1 || true)"
  freewheel="$(find "$wheel_dir" -type f -iname "FreeWheel" ! -iname "*.info" 2>/dev/null | head -n1 || true)"
  set_akiko="$(find "$wheel_dir" -type f -iname "SetAkiko" ! -iname "*.info" 2>/dev/null | head -n1 || true)"

  if [[ -z "$wheel_driver" || -z "$freewheel" ]]; then
    echo "==> WARN: MiSTer wheel: WheelDriverAkiko.adf missing WheelDriver or FreeWheel"
    return 0
  fi

  write_uaem_with_perms "----rwed" "$wheel_driver"
  write_uaem_with_perms "----rwed" "$freewheel"
  fs_copy_file "$wheel_driver" "$sys_root/C/WheelDriver" --uaemetadata UaeMetafile
  fs_copy_file "$freewheel" "$sys_root/C/FreeWheel" --uaemetadata UaeMetafile
  if [[ -n "$set_akiko" && -f "$set_akiko" ]]; then
    write_uaem_with_perms "----rwed" "$set_akiko"
    fs_copy_file "$set_akiko" "$sys_root/C/SetAkiko" --uaemetadata UaeMetafile
  fi

  local wheel_script="$STAGE/StartMiSTerWheel"
  cat > "$wheel_script" <<'MISTERWHEEL'
; Amiga Imager: start MiSTer mouse wheel support
FailAt 30
IF EXISTS C:WheelDriver
  Run >NIL: C:WheelDriver
ENDIF
Wait 1
IF EXISTS C:FreeWheel
  Run >NIL: C:FreeWheel
ENDIF
MISTERWHEEL
  make_lf_only "$wheel_script"
  write_uaem_with_perms "-s--rwed" "$wheel_script"
  fs_copy_file "$wheel_script" "$sys_root/S/StartMiSTerWheel" --uaemetadata UaeMetafile

  local wheel_overlay="$STAGE/mister-wheel-startup.overlay"
  cat > "$wheel_overlay" <<'MISTERWHEELOVERLAY'
IF EXISTS SYS:S/StartMiSTerWheel
  Run >NIL: Execute SYS:S/StartMiSTerWheel
ENDIF
MISTERWHEELOVERLAY
  make_lf_only "$wheel_overlay"
  inject_overlay_into_script "$sys_root" "S/Startup-Sequence" "$wheel_overlay" inject_before "EndCLI" "MiSTerWheel"

  echo "==> MiSTer wheel: installed WheelDriver + FreeWheel startup support"
}


# ===========================================================================
# 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;;
    --partition-content-map) PARTITION_CONTENT_MAP="${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;;
    --packages-dir)     AMINET_DIR="${2:?}"; IBROWSE_DIR="${2:?}"; shift 2;;  # unified staging dir (Aminet + direct + user packages)
    --aminet-rules-csv) AMINET_RULES_CSV="${2:?}"; shift 2;;
    --download-only)    shift 2;;  # removed — no longer used
    --debug)            DEBUG=1; shift 1;;
    --debug-export-dir) DEBUG_EXPORT_DIR="${2:?}"; shift 2;;
    --hst-amiga)        shift 2;;
    --ibrowse-dir)      IBROWSE_DIR="${2:?}"; shift 2;;
    --ibrowse-key)      IBROWSE_KEY="${2:?}"; shift 2;;
    --govd-key)         GOVD_KEY="${2:?}"; shift 2;;
    --build-manifest)   BUILD_MANIFEST="${2:?}"; shift 2;;
    --build-plan)       BUILD_PLAN="${2:?}"; shift 2;;
    --icon-pass2-mode)  ICON_PASS2_MODE="${2:?}"; shift 2;;
    --blank-amiga-partitions) BLANK_AMIGA_PARTITIONS="${2:?}"; shift 2;;
    --include-wbdock|--include-igame|--include-lumiftp|--include-amigaamp|--include-riva|--include-bass|--include-fotaq|--include-lure) shift;; # removed in Stage C; IDs now travel via --packages
    --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)        partition_layout_add_spec "${2:?}"; 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;;
    # ${2?} not ${2:?}: the app sends --screen-mode-id "" for RTG-disabled
    # emulator builds (empty = native chipset, skip P96); :? rejects empty.
    --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;;
    --emu-profile)        EMU_PROFILE="${2:?}"; shift 2;;
    --rtg-card)           RTG_CARD="${2:?}"; shift 2;;
    --rtg-card-driver)    RTG_CARD_DRIVER="${2:?}"; shift 2;;
    --mister-shared-folder) MISTER_SHARED_FOLDER="${2:?}"; shift 2;;
    --mister-mouse-wheel) MISTER_MOUSE_WHEEL="${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

load_build_plan "${BUILD_PLAN:-}"

# Derive INCLUDE_* flags from PACKAGES list (all package IDs travel via --packages since Stage C)
# MiSTer profile is 68000: bass/fotaq/lure/riva are never in PACKAGES there (excluded by Swift);
# uae/amiberry profiles run 68020+ and may carry the full package set.
package_is_selected "ibrowse"  && INCLUDE_IBROWSE=1  || true
package_is_selected "wbdock"   && INCLUDE_WBDOCK=1   || true
package_is_selected "govd"     && INCLUDE_GOVD=1     || true

# Stage D: log resolved build manifest (written by Swift, additive/informational only)
if [[ -n "${BUILD_MANIFEST:-}" && -f "${BUILD_MANIFEST:-}" ]]; then
  echo "==> [manifest] Resolved build plan:"
  cat "$BUILD_MANIFEST"
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
# -----------------------------------------------------------------------------
[[ -d "$ASSETS_DIR" ]] || die "Assets dir missing: $ASSETS_DIR"
# hst-imager is only needed for the deprecated DISK_ENGINE=hst fallback; the
# default native engine (amigadiskkit) requires no external tool.
if [[ "${DISK_ENGINE:-hst}" == "hst" ]]; then
  [[ -x "$HST_IMAGER" ]] || die "hst-imager not found/executable: $HST_IMAGER"
fi

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
  if [[ "$BLANK_AMIGA_PARTITIONS" -eq 1 && "$HST_FS" == "Pfs3" ]]; then
    :
  elif [[ "$BLANK_AMIGA_PARTITIONS" -eq 1 ]]; then
    die "Blank FFS images need --adf-dir or an ISO path so FastFileSystem can be extracted. Use PFS3 to build without media."
  else
    die "No input media available. Provide --adf-dir (preferred) or an ISO path as 3rd positional argument."
  fi
fi

case "$ICON_SET" in
  standard|glowicons) ;;
  *) die "Invalid --icon-set. Use: standard | glowicons" ;;
esac
case "$EMU_PROFILE" in
  mister|uae|amiberry) ;;
  *) die "Invalid --emu-profile. Use: mister | uae | amiberry" ;;
esac
case "$MISTER_SHARED_FOLDER" in
  0|1) ;;
  *) die "Invalid --mister-shared-folder. Use: 0 | 1" ;;
esac
case "$MISTER_MOUSE_WHEEL" in
  0|1) ;;
  *) die "Invalid --mister-mouse-wheel. Use: 0 | 1" ;;
esac

if [[ "$EMU_PROFILE" == "mister" ]]; then
  # The official MiSTer_RTG.lha ships a compact board-local ModeID table;
  # normalize ModeIDs from older callers. UAE-family profiles use standard
  # P96 uaegfx ModeIDs and must not be rewritten.
  mister_normalize_screen_mode_id
else
  # UAE-family emulators provide networking via the host's bsdsocket.library
  # emulation — never install an Amiga-side stack (defense in depth for
  # headless callers; the app already forces --network-stack none).
  if [[ "$NETWORK_STACK" != "none" ]]; then
    echo "==> $EMU_PROFILE: forcing --network-stack none (host emulates bsdsocket.library)"
    NETWORK_STACK="none"
  fi
fi

# --- N-partition support: populate partition arrays from --partition args or legacy fallback ---
partition_layout_prepare "DH" 1

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

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 (emulator HDF, profile: ${EMU_PROFILE}):"
echo "    Total: ${TOTAL_GB} GB (${DISK_SIZE_BYTES} bytes)"
echo "    RTG card: ${RTG_CARD:-<profile default>}"
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]} block=${PART_BLOCKSIZES[$_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 "    Blank partitions: ${BLANK_AMIGA_PARTITIONS}"
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"

    # Native extractor first (keeps the native path free of hst-imager calls);
    # hst-imager remains the fallback for ADF DOS types it cannot read.
    if ! { [[ -n "${AMIGA_TOOLS:-}" && -x "${AMIGA_TOOLS:-}" ]] \
           && "$AMIGA_TOOLS" adf extract "$adf" "$adf_out" >/dev/null 2>&1; } \
      && ! "$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"
mkdir -p "$(dirname "$IMG")"
if [[ -e "$IMG" ]]; then
  echo "==> Removing existing image: $IMG"
  rm -f "$IMG"
fi

if [[ "${DISK_ENGINE:-hst}" == "amigadiskkit" ]]; then
  # Native engine: blank file + RDB creation happen together in step 3 (rdb-build).
  # Compute USABLE_BYTES from disk size now so partition_layout_resolve_bytes can run.
  echo "==> 1) [amigadiskkit] HDF image (${TOTAL_GB} GB) — creation deferred to step 3"
  echo "==> PROGRESS 2/10 Initializing RDB (deferred)"
  echo "==> 2) [amigadiskkit] RDB init deferred — native engine creates image + RDB atomically"
  _native_pure_rdb_usable_bytes "$DISK_SIZE_BYTES"
  RDB="$IMG/rdb"
else
  # hst-imager path: blank + rdb init + register filesystem handlers
  echo "==> 1) Create blank HDF image (${TOTAL_GB} GB)"
  run "$HST_IMAGER" blank "$IMG" "$DISK_SIZE_BYTES"
  echo "==> PROGRESS 2/10 Initializing RDB"
  echo "==> 2) Initialize RDB on HDF"
  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"
    if partition_layout_uses_fs "Pfs3"; then
      run "$HST_IMAGER" rdb fs add "$IMG" "$PFS3_AIO" PDS3
    fi
  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
fi

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

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

if [[ "${DISK_ENGINE:-hst}" != "amigadiskkit" ]]; then
  # hst-imager path: read RDB info for geometry + usable space
  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)"
  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"
fi

partition_layout_resolve_bytes "$USABLE_BYTES"
partition_layout_log_resolved_bytes "$RDB_BYTES" "$USABLE_BYTES"

# 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

if [[ "${DISK_ENGINE:-hst}" == "amigadiskkit" ]]; then
  # -------------------------------------------------------------------------
  # Native engine: create blank image + RDB + partitions in one rdb-build call
  # -------------------------------------------------------------------------
  echo "==> [amigadiskkit] Building RDB partition layout"
  _native_build_part_args "$NUM_PARTS"
  run "$AMIGA_TOOLS" disk rdb-build "$IMG" "$DISK_SIZE_BYTES" "${NATIVE_PART_ARGS[@]}"

  # Register filesystem handlers (image exists only after rdb-build).
  # Mirrors the hst-imager branch above; rdb-fs-add is idempotent.
  if [[ "$HST_FS" == "Pfs3" ]]; then
    run "$AMIGA_TOOLS" disk rdb-fs-add "$IMG" "$PFS3_AIO" PDS3
  else
    if [[ "${NEEDS_DOS7_FS_REGISTRATION:-0}" == "1" ]]; then ensure_rdb_has_dos7_fs; fi
    if partition_layout_uses_fs "Pfs3"; then
      run "$AMIGA_TOOLS" disk rdb-fs-add "$IMG" "$PFS3_AIO" PDS3
    fi
  fi

  # Resolve partition numbers from the native-written RDB (native rdb-info;
  # numbers equal PART-chain order, which rdb-build/reinit wrote in our order)
  native_resolve_part_pns

  # Format each partition (native FFS formatter — no Bug 1/2)
  for _i in $(seq 0 $((NUM_PARTS - 1))); do
    run "$AMIGA_TOOLS" disk rdb-format "$IMG" "${PART_NAMES[$_i]}" "${PART_LABELS[$_i]}"
    echo "==> Partition ${PART_NAMES[$_i]} (${PART_LABELS[$_i]}) post-format: OK (native engine)"
  done
else
  # -------------------------------------------------------------------------
  # hst-imager path: delete + add + format + verify
  # -------------------------------------------------------------------------
  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

  # 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 "${PART_BLOCKSIZES[$_i]}" --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.
  # 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 fs_dir "$_vpath"; 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

  # Bug 1 guard: explicitly verify boot partition FFS boot block checksum.
  # See build-pistorm.sh for full explanation. For MiSTer (pure RDB, no MBR),
  # the partition start byte from hst-imager info "$IMG" is the absolute offset.
  _bug1_pn="${PART_PNS[0]}"
  _bug1_slice_start=$("$HST_IMAGER" info "$IMG" 2>/dev/null \
    | awk -F'|' -v pn="$_bug1_pn" '/RDB/ {
        n=$4; gsub(/[[:space:]]+/, "", n)
        if (n == pn) {
          s=$6; gsub(/[[:space:]]+/, "", s)
          if (s ~ /^[0-9]+$/) { print s; exit }
        }
      }')
  if [[ -n "$_bug1_slice_start" ]]; then
    _bug1_blk=$(( _bug1_slice_start / 512 ))
    _bug1_cksum=$(dd if="$IMG" bs=512 skip="$_bug1_blk" count=1 2>/dev/null \
      | dd bs=1 skip=4 count=4 2>/dev/null \
      | od -A n -t x1 | tr -d ' \n')
    _bug1_rootptr=$(dd if="$IMG" bs=512 skip="$_bug1_blk" count=1 2>/dev/null \
      | dd bs=1 skip=8 count=4 2>/dev/null \
      | od -A n -t x1 | tr -d ' \n')
    if [[ "$_bug1_cksum" == "00000000" && "$_bug1_rootptr" == "00000000" ]]; then
      echo "==> ERROR: Boot partition ${PART_NAMES[0]} (${PART_LABELS[0]}) has a corrupt boot block." >&2
      echo "==>   FFS checksum and root-block pointer are both zero — hst-imager Bug 1." >&2
      echo "==>   DOS3 partitions that exceed 2 GiB (including a default 2 GiB DH0 after" >&2
      echo "==>   cylinder-alignment) are silently corrupted by hst-imager rdb part format." >&2
      echo "==>   AmigaOS shows 'Not a DOS disk in device ${PART_NAMES[0]}' on first boot." >&2
      echo "==>   Fix: Settings → Debug → Disk Engine → Native (recommended)." >&2
      echo "==>   Alt: reduce ${PART_NAMES[0]} size to 1.8 GiB or below in the UI." >&2
      die "Boot block corruption (hst-imager Bug 1) in ${PART_NAMES[0]}"
    fi
  fi
fi

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

# Register partition name map for amigadiskkit engine dispatch (see common.sh DISK_ENGINE)
DISK_ENGINE_IMG="$IMG"
DISK_ENGINE_ROOT_BASE="$PARTITION_ROOT_BASE"
DISK_ENGINE_PART_NAMES=()
for _disk_i in "${!PART_NAMES[@]}"; do DISK_ENGINE_PART_NAMES[${PART_PNS[$_disk_i]}]="${PART_NAMES[$_disk_i]}"; done

# 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="$PARTITION_ROOT_BASE/${PART_PNS[$_transfer_idx]}"
  else
    echo "==> WARN: transfer partition '$TRANSFER_PARTITION' not found; using ${PART_NAMES[0]}" >&2
  fi
fi

apply_partition_content_map() {
  [[ -n "$PARTITION_CONTENT_MAP" ]] || return 0
  [[ -f "$PARTITION_CONTENT_MAP" ]] || die "Partition content map missing: $PARTITION_CONTENT_MAP"

  echo "==> Applying partition content map"

  local _map_name _map_path _target_root _idx
  while IFS=$'\t' read -r _map_name _map_path || [[ -n "${_map_name}${_map_path}" ]]; do
    [[ -n "$_map_name" ]] || continue
    [[ "${_map_name#\#}" != "$_map_name" ]] && continue
    [[ -n "$_map_path" ]] || continue
    [[ -d "$_map_path" ]] || die "Partition content directory missing for $_map_name: $_map_path"

    _target_root=""
    for _idx in $(seq 0 $((NUM_PARTS - 1))); do
      if [[ "${PART_NAMES[$_idx]}" == "$_map_name" ]]; then
        _target_root="$PARTITION_ROOT_BASE/${PART_PNS[$_idx]}"
        break
      fi
    done

    [[ -n "$_target_root" ]] || die "Partition content target not found in image: $_map_name"
    echo "==> Copying $(basename "$_map_path") -> $_map_name"
    fs_copy "$_map_path/" "$_target_root/" --recursive --makedir
  done < "$PARTITION_CONTENT_MAP"
}

if [[ "$BLANK_AMIGA_PARTITIONS" -eq 1 ]]; then
  apply_partition_content_map
  echo "==> PROGRESS 4/10 Skipping AmigaOS population (blank partitions mode)"
  echo "==> Blank partitions mode: formatted partitions created and assigned content copied; skipping AmigaOS setup"
  echo "==> PROGRESS 10/10 Complete"
  echo "==> Build complete: $IMG"
  exit 0
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 the first Workbench ADF, matching the Classic and
  # PiStorm flows. Capturing detect_workbench_version() output would poison
  # DETECTED_WB_VERSION with its human-readable log line.
  for _wb in "$ADF_SRC"/Workbench*.adf "$ADF_SRC"/Workbench*.ADF; do
    if [[ -f "$_wb" ]]; then
      WORKBENCH_ADF="$_wb"
      break
    fi
  done
  detect_workbench_version
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"

# -----------------------------------------------------------------------------
# OS 3.9 CD: direct filesystem extraction (no ADFs). Mirrors build-classic.sh's
# inline implementation. The call below dates back to the modular split, but
# the function was never defined anywhere — OS 3.9 full-ISO mode on this
# script died with "command not found" until this definition was added.
# -----------------------------------------------------------------------------
extract_39_iso_to_host() {
  local iso_root="$1"
  local sys_host="$2"
  local wb35_dir="$iso_root/OS-Version3.9/Workbench3.5"
  local wb39_dir="$iso_root/OS-Version3.9/Workbench3.9"
  if [[ ! -d "$wb35_dir" ]]; then
    echo "ERROR: OS 3.9 CD missing expected directory: OS-Version3.9/Workbench3.5" >&2
    return 1
  fi

  echo "==> Copying Workbench 3.5 base files to host staging"
  cp -R "$wb35_dir"/* "$sys_host/" || return 1

  if [[ -d "$wb39_dir" ]]; then
    echo "==> Overlaying Workbench 3.9 updated files"
    cp -R "$wb39_dir"/* "$sys_host/" || return 1
  fi

  # The 3.9 CD ships Reboot in OS-Version3.9/Install-Tools (NOT the installed C/),
  # so a 3.9 install otherwise has no C:Reboot and the first-boot OnetimeRunWB
  # Sys:c/reboot (after the locale step) silently no-ops. Install it to SYS:C.
  if [[ -f "$iso_root/OS-Version3.9/Install-Tools/Reboot" ]]; then
    mkdir -p "$sys_host/C"
    cp -f "$iso_root/OS-Version3.9/Install-Tools/Reboot" "$sys_host/C/Reboot"
  fi

  DETECTED_WB_VERSION="3.9"
  echo "==> Workbench version detected: 3.9 (from OS 3.9 CD ISO structure)"
  return 0
}

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"
      # Native extractor first (keeps the native path free of hst-imager
      # calls, same pattern as find_ffs_in_adf_dir); hst-imager remains the
      # fallback for ADF DOS types amiga-tools cannot read.
      { [[ -n "${AMIGA_TOOLS:-}" && -x "${AMIGA_TOOLS:-}" ]] \
          && "$AMIGA_TOOLS" adf extract "$_adf" "$_out" >/dev/null 2>&1; } \
        || "$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) — extract_lha_to_host is native-first
# (system lha → amiga-tools → hst fallback), matching build-classic.sh.
if [[ -n "${BOING_BAG_1:-}" && -f "$BOING_BAG_1" ]]; then
  echo "==> Extracting Boing Bag 1: $BOING_BAG_1"
  extract_lha_to_host "$BOING_BAG_1" "$SYS_HOST" || true
fi
if [[ -n "${BOING_BAG_2:-}" && -f "$BOING_BAG_2" ]]; then
  echo "==> Extracting Boing Bag 2: $BOING_BAG_2"
  extract_lha_to_host "$BOING_BAG_2" "$SYS_HOST" || true
fi

# OS 3.9 ISO mode: Boing Bag archives unpack into BoingBag*/ subdirs —
# flatten them onto SYS_HOST (mirrors build-classic.sh).
if [[ "$IS_39_ISO" -eq 1 ]]; then
  for bb_dir in "$SYS_HOST"/BoingBag* "$SYS_HOST"/boingbag*; do
    if [[ -d "$bb_dir" ]]; then
      cp -R "$bb_dir"/* "$SYS_HOST/" 2>/dev/null || true
      rm -rf "$bb_dir"
    fi
  done
fi

# Locale fallback from the 3.9 CD root: it ships the full language set
# (deutsch.language + ~20 others) under /Locale, not Workbench3.9 — without this
# the first-boot locale picker shows only English. (Mirrors build-classic.sh:1279.)
if [[ "$IS_39_ISO" -eq 1 && -d "$ISO_ROOT/Locale" ]]; then
  mkdir -p "$SYS_HOST/Locale" >/dev/null 2>&1 || true
  cp -R "$ISO_ROOT/Locale/"* "$SYS_HOST/Locale/" 2>/dev/null || 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

ensure_backdrop_presets "$SYS_HOST" "${ASSETS_DIR:-}"
install_rtg_backdrop_png "$SYS_HOST" "${ADF_SRC:-}" "$SCREEN_WIDTH" "$SCREEN_HEIGHT" "$DETECTED_WB_VERSION"

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
  fs_dir "$SYS_ROOT/Prefs/Presets/Backdrops/pattern.iff" || {
    # pattern.iff not on image — try copying default_pal.iff as pattern.iff
    if fs_dir "$SYS_ROOT/Prefs/Presets/Backdrops/default_pal.iff"; 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 ! fs_dir "$_p"; 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 — the bundled file defines Emu68/VideoCore boards. The MiSTer
            # profile installs the MiSTer_RTG.lha version instead; UAE-family
            # profiles use the uaegfx board's dynamic mode registration.
            echo "==> NOTE: Skipping bundled Picasso96Settings (PiStorm-specific)"
            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"

# --- 5d2) Install HD Startup-Sequence (all emulator profiles) ---
# Must happen BEFORE overlay injection so the OneTimeRun/UAEGFX/Iconlib overlays
# apply to the HD version. The base Workbench ADF ships Startup-Sequence_KickDisk_LD
# — a minimal kickstart-disk boot script ("no HappyENV here") that does NOT set up
# the hard-drive datatype/ENV environment. Without the real HD Startup-Sequence,
# datatypes.library never works, so PNG/akPNG/akGIF/akJFIF all fail ("Unknown data
# type" / "can't load picture") for the Workbench backdrop, IBrowse GUI images, any
# datatype-loaded picture (built-in ILBM still works, which made it look partial).
# The Update 3.2.x ADF ships Startup-HardDrive (the canonical HD boot script the
# official installer uses — proper ROM CheckInstall, ENVARC, AddDataTypes). Install
# it unmodified, exactly like build-classic.sh does. Verified by comparison: a
# Classic image (HD SS) and this emulator image (KickDisk SS) have byte-identical
# datatypes, yet PNG only works on the HD SS.
case "$DETECTED_WB_VERSION" in
  3.2*|3.9*)
    _hd_ss=""
    for _upd_adf in "$ADF_SRC"/Update*.adf "$ADF_SRC"/update*.adf; do
      [[ -f "$_upd_adf" ]] || continue
      _upd_tmp="$STAGE/update-extract-ss.$$"
      rm -rf "$_upd_tmp" && mkdir -p "$_upd_tmp"
      extract_adf_to_host "$_upd_adf" "$_upd_tmp" >/dev/null 2>&1 || continue
      for _hd_cand in "$_upd_tmp"/Update/Startup-HardDrive "$_upd_tmp"/Update/Startup-HardDrive.Z; do
        if [[ -f "$_hd_cand" ]]; then
          if [[ "$_hd_cand" == *.Z ]]; then
            uncompress -f "$_hd_cand" 2>/dev/null || true
            _hd_cand="${_hd_cand%.Z}"
          fi
          [[ -f "$_hd_cand" ]] && { _hd_ss="$_hd_cand"; break 2; }
        fi
      done
      rm -rf "$_upd_tmp"
    done
    if [[ -n "$_hd_ss" && -f "$_hd_ss" ]]; then
      chmod +x "$_hd_ss"  # Amiga 's' bit: Startup-Sequence is an AmigaDOS script
      fs_copy_file "$_hd_ss" "$SYS_ROOT/S/Startup-Sequence"
      echo "==> $EMU_PROFILE: Installed HD Startup-Sequence (Startup-HardDrive) — replaces KickDisk_LD"
      rm -rf "$STAGE/update-extract-ss.$$" 2>/dev/null || true
    else
      echo "==> WARN: HD Startup-Sequence not found in Update ADFs; keeping base (KickDisk) Startup-Sequence"
    fi
    ;;
esac

# --- 5e) Inject script overlays (Windows parity) ---
# The Startup-Sequence_UAEGFX overlay performs the boot-time Videocore/Uaegfx
# monitor swap. uae/amiberry NEED it (it moves the Uaegfx monitor into
# Devs:Monitors when $System EQ "UAE" so LoadMonDrvs registers the uaegfx board).
# Only the MiSTer profile must skip it — MiSTer ships its own RTG driver and the
# swap would move/delete the MiSTer monitor at boot.
_mister_overlay_dir="$STAGE/mister-overlay-S"
rm -rf "$_mister_overlay_dir" 2>/dev/null || true
cp -R "$SYS_PAYLOAD/S" "$_mister_overlay_dir"
if [[ "$EMU_PROFILE" == "mister" ]]; then
  rm -f "$_mister_overlay_dir/Startup-Sequence_UAEGFX"
fi
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"

if [[ "$BACKDROP_ENABLED" == "1" && "$_RTG_BACKDROP_PNG_INSTALLED" == "1" ]]; then
  # With the HD Startup-Sequence (see 5d2) datatypes work, so the PNG backdrop
  # loads. Point WBPattern at the installed resolution backdrop.
  write_wbpattern_backdrop "$SYS_ROOT" "Sys:Prefs/Presets/Backdrops/backdrop.png"
elif [[ "$BACKDROP_ENABLED" == "1" && "$DETECTED_WB_VERSION" == "3.9" ]]; then
  # AmigaOS 3.9 ships JPEG wallpapers (no resolution-PNG set like the 3.2 Backdrops
  # ADF). Point WBPattern at AOS.jpg — a BASELINE JPEG the OS jpeg.datatype can
  # decode. The Sterne*.jpg wallpapers (and the 1024x768 OS3.5-Backdrop) are
  # PROGRESSIVE JPEGs, which the datatype CANNOT load ("can't load picture").
  write_wbpattern_backdrop "$SYS_ROOT" "Sys:Prefs/Presets/Backdrops/AOS.jpg"
else
  # No PNG backdrop: write empty WBPattern.prefs (FORM+PREF+PRHD, no PTRN) so
  # IPrefs does not attempt the stock default_pal.iff PTRN (avoids a requester,
  # e.g. on the 68000 MiSTer Minimig core where ilbm.datatype may fail).
  _empty_wbp="$STAGE/emulator-empty-wbpattern.prefs"
  printf 'FORM\x00\x00\x00\x12PREFPRHD\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"
fi

# 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 ! fs_dir "$SYS_ROOT/System/CLICon" \
   && ! fs_dir "$SYS_ROOT/System/CLIcon"; then
  if fs_dir "$SYS_ROOT/System/CLI"; then
    local _cli_tmp="$STAGE/.cli-copy-$$.$RANDOM"
    if fs_pull_file "$SYS_ROOT/System/CLI" "$_cli_tmp" 2>/dev/null; then
      fs_copy_file "$_cli_tmp" "$SYS_ROOT/System/CLIcon" 2>/dev/null || true
    fi; rm -f "$_cli_tmp" 2>/dev/null || 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"

# codesets.library — required by LumiFTP and LumiWeather for filename encoding.
if package_is_selected "lumiftp" || package_is_selected "lumiweather"; then
  download_and_install_codesets_library "$SYS_ROOT"
fi

# icon.library 46.4 (if selected)
if package_is_selected "IconLib"; then
  if ! fs_dir "$SYS_ROOT/Libs/icon.library"; then
    download_and_install_iconlib "$SYS_ROOT"
  fi
fi

# AHI prefs binary alias (AHI -> AHI_BGUI or AHI_MUI)
_ahi_prefs_src=""
if fs_dir "$SYS_ROOT/Libs/muimaster.library"; then
  _ahi_prefs_src="AHI_MUI"
else
  _ahi_prefs_src="AHI_BGUI"
  if ! fs_dir "$SYS_ROOT/Libs/bgui.library"; then
    if fs_dir "$SYS_ROOT/Programs/AHI/BGUI/bgui.library"; 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 fs_dir "$SYS_ROOT/Prefs/$_ahi_prefs_src"; 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 ! fs_dir "$SYS_ROOT/System/CLI"; then
  echo "==> WARN: SYS:System/CLI missing (Shell icon will fail)." >&2
fi
if ! fs_dir "$SYS_ROOT/Fonts"; then
  echo "==> WARN: SYS:Fonts missing (Font prefs will fail)." >&2
fi
if ! fs_dir "$SYS_ROOT/Tools"; 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 ! fs_dir "$SYS_ROOT/S"; then
  die "Failed to create/access SYS:S at $SYS_ROOT/S"
fi
if ! fs_dir "$SYS_ROOT/S/OneTimeRunWB"; 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 fs_dir "$SYS_ROOT/Prefs/Env-Archive/Sys"; then
  # Copy (best-effort) using pull+push for dispatch compatibility.
  _lp_tmp="$STAGE/.locale-prefs-$$.$RANDOM"
  if fs_pull_file "$SYS_ROOT/Prefs/Presets/Locale.prefs" "$_lp_tmp" 2>/dev/null; then
    fs_copy_file "$_lp_tmp" "$SYS_ROOT/Prefs/Env-Archive/Sys/Locale.prefs" 2>/dev/null || true
  fi; rm -f "$_lp_tmp" 2>/dev/null || true
  _lpi_tmp="$STAGE/.locale-prefs-info-$$.$RANDOM"
  if fs_pull_file "$SYS_ROOT/Prefs/Presets/Locale.prefs.info" "$_lpi_tmp" 2>/dev/null; then
    fs_copy_file "$_lpi_tmp" "$SYS_ROOT/Prefs/Env-Archive/Sys/Locale.prefs.info" 2>/dev/null || true
  fi; rm -f "$_lpi_tmp" 2>/dev/null || true
fi

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

if [[ -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

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

    if [[ -d "$SRC" ]]; then
      echo "  -> staging package: $pkg"
      stage_onetimerunwb_payload_filtered "$SRC" "$DST"
    fi
    # Non-existing IDs are Aminet/external packages handled elsewhere — silently skip.
  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

    # IBrowse/AmiSSL/WBDock have dedicated installers; skip them here.
    is_dedicated_external_archive "$lha_base_check" && continue

    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.
  # Skipped when --packages-dir merged the staging dirs (the glob above already has it).
  if [[ -n "${IBROWSE_DIR:-}" && -d "${IBROWSE_DIR:-}" && "${IBROWSE_DIR}" != "${AMINET_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

    # ScummVM requires 68020+ — MiSTer Minimig is 68000-baseline, skip.
    if echo "$lha_base" | grep -Eqi "^ScummVM_AGA_060\.lha$|^ScummVM_RTG_060\.lha$|^scummvm-.*-rtg\.lha$"; then
      echo "  -> skipping $lha_base (requires 68020+; MiSTer Minimig is 68000)"
      continue
    fi

    # AmigaAMP — explicit installer handles the AmigaAMP3 archive layout directly.
    if is_amigaamp_archive "$lha_base"; then
      if install_pkg_amigaamp "$SYS_ROOT" "$lha_file" "$lha_base"; then
        AMINET_BUILDTIME+=("$lha_base")
        continue
      fi
      echo "  -> WARN: explicit AmigaAMP install failed, falling back to CSV mapping"
    fi

    # rc-ftpd — explicit installer (multi-destination: Programs/, Libs/, ENVARC/).
    if is_rcftpd_archive "$lha_base"; then
      if install_pkg_rcftpd "$SYS_ROOT" "$lha_file" "$lha_base"; then
        AMINET_BUILDTIME+=("$lha_base")
        continue
      fi
      echo "  -> WARN: explicit rc-ftpd install failed, falling back to CSV mapping"
    fi

    # GoVD — explicit installer + optional key file injection.
    if is_govd_archive "$lha_base"; then
      if install_pkg_govd "$SYS_ROOT" "$lha_file" "$lha_base" "${GOVD_KEY:-}"; then
        AMINET_BUILDTIME+=("$lha_base")
        continue
      fi
      echo "  -> WARN: explicit GoVD install failed, falling back to CSV mapping"
    fi

    # Solas, LumiWeather, LumiPass, LumiReg — simple archive extractors.
    _simple_app_done=0
    for _fn in solas lumiweather lumipass lumireg; do
      _fn_pred="is_${_fn}_archive"
      _fn_inst="install_pkg_${_fn}"
      if "$_fn_pred" "$lha_base"; then
        if "$_fn_inst" "$SYS_ROOT" "$lha_file" "$lha_base"; then
          AMINET_BUILDTIME+=("$lha_base")
          _simple_app_done=1
          break
        fi
        echo "  -> WARN: explicit ${_fn} install failed, falling back to CSV mapping"
      fi
    done
    # continue the outer package loop — without this the archive was re-fed
    # to the CSV path and misreported as "no usable CSV/file match"
    if [[ "$_simple_app_done" -eq 1 ]]; then
      continue
    fi

    # User-defined packages (Settings → custom download URL): plan-driven generic install
    if install_pkg_user_archive "$SYS_ROOT" "$lha_file" "$lha_base"; then
      AMINET_BUILDTIME+=("$lha_base")
      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 fs_dir "$SYS_ROOT/Programs/IMP3" \
     || fs_dir "$SYS_ROOT/C/IMP3"; 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 fs_dir "$SYS_ROOT/Programs/IMP3" \
     || fs_dir "$SYS_ROOT/C/IMP3"; 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"

# Official MiSTer Minimig extras (mister profile only):
# - MiSTer_share.lha provides SHARE: Linux shared-folder access.
# - WheelDriverAkiko.adf provides WheelDriver + FreeWheel for USB mouse wheels.
if [[ "$EMU_PROFILE" == "mister" ]]; then
  if [[ "$MISTER_SHARED_FOLDER" == "1" ]]; then
    install_mister_shared_folder_support "$SYS_ROOT"
  else
    echo "==> MiSTer shared folder: disabled by settings"
  fi
  if [[ "$MISTER_MOUSE_WHEEL" == "1" ]]; then
    install_mister_wheel_support "$SYS_ROOT"
  else
    echo "==> MiSTer wheel: disabled by settings"
  fi
else
  echo "==> $EMU_PROFILE: MiSTer hardware extras (SHARE:, wheel) not applicable"
fi

# -----------------------------------------------------------------------------
# RTG, per profile:
#   mister       — Picasso96 via the official MiSTer card driver (MiSTer_RTG.lha)
#   uae/amiberry — Picasso96 via the standard uaegfx board (card + monitor ship
#                  in the P96 archive itself; the standard UAE setup)
# -----------------------------------------------------------------------------
if [[ -n "$SCREEN_MODE_ID" && "$EMU_PROFILE" == "mister" ]]; then
  echo "==> MiSTer RTG: Screen mode requested (official MiSTer driver)"
  echo "==> MiSTer RTG: requires MiSTer OSD CPU = 68020 or better"

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

  # MiSTer.card (from MiSTer_RTG.lha, Oct 2020) targets the iComp P96 v41+
  # card driver ABI, not the 1999 Aminet shareware v40 ABI. Using rtg.library
  # v40 with a v41+ card driver results in zero RTG screen modes (card init
  # fails silently, no modes registered, IPrefs can't switch to RTG).
  # Trust the user's iComp archive (v43) as-is — same policy as Classic RTG.
  export P96_TRUST_ARCHIVE_ABI=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)"
    fs_delete "$SYS_ROOT/Devs/Monitors/uaegfx"
    fs_delete "$SYS_ROOT/Devs/Monitors/uaegfx.info"

    # 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

  # MiSTer_RTG.lha ships a compact board-local Picasso96Settings table. The
  # Swift caller normally passes the matching ModeID; the script normalization
  # above keeps older callers safe. Write the user's selected resolution so RTG
  # is pre-configured before first boot.
  write_screenmode_prefs "$SYS_ROOT" "$SCREEN_MODE_ID" "$WB_DEPTH_HEX"

  # Make monitor registration explicit on MiSTer.
  # The generated WB3.2 Startup-Sequence does not call LoadMonDrvs itself, and
  # MiSTer no longer uses the old UAEGFX/Videocore swap overlay path.
  #
  # Register the monitor only after User-startup has established the Picasso96:
  # assign. The official MiSTer monitor opens "picasso96/rtg.library", so the
  # assign must resolve to the actual Libs/Picasso96 runtime drawer before
  # LoadMonDrvs executes. Keep Programs/Picasso96 as an added path so the
  # shareware tools still resolve under the same logical assign.
  #
  # Keep the proven project-wide IPrefs flow: LoadWB first, then Run >NIL:
  # IPrefs before EndCLI. IPrefs reads the active RTG screenmode.prefs and
  # applies WBPattern/WBConfig after the MiSTer monitor has registered modes.
  _mister_rtg_overlay="$STAGE/mister-rtg-startup.overlay"
  cat > "$_mister_rtg_overlay" <<'MISTERRTG'
IF EXISTS SYS:Libs/Picasso96
  Assign Picasso96: SYS:Libs/Picasso96
  IF EXISTS SYS:Programs/Picasso96
    Assign ADD Picasso96: SYS:Programs/Picasso96
  ENDIF
ELSE
  IF EXISTS SYS:Programs/Picasso96
    Assign Picasso96: SYS:Programs/Picasso96
  ENDIF
ENDIF

IF EXISTS C:LoadMonDrvs
  C:LoadMonDrvs >NIL:
ENDIF
MISTERRTG
  make_lf_only "$_mister_rtg_overlay"
  inject_overlay_into_script "$SYS_ROOT" "S/Startup-Sequence" "$_mister_rtg_overlay" inject_before "LoadWB" "MiSTerRTG"
elif [[ -n "$SCREEN_MODE_ID" ]]; then
  echo "==> $EMU_PROFILE RTG: Picasso96 uaegfx (card: ${RTG_CARD:-UAEGFX}, driver: ${RTG_CARD_DRIVER:-uaegfx.card})"
  echo "==> $EMU_PROFILE RTG: enable a P96/RTG board (e.g. Zorro III) in the emulator configuration"

  # uae/amiberry use the same proven Videocore/Uaegfx monitor swap as
  # PiStorm/Classic: stage_p96_icomp_archive installs the Videocore monitor into
  # Devs:Monitors and the Uaegfx monitor into Storage:Monitors, then the
  # Startup-Sequence_UAEGFX overlay moves Uaegfx into Devs:Monitors at boot when
  # $System EQ "UAE" so LoadMonDrvs registers the uaegfx board.
  # Do NOT set SKIP_VIDEOCORE_MONITOR here: it skips monitor staging entirely,
  # leaving Devs:Monitors empty so RTG never activates (Workbench stays in native
  # chipset mode and the WBPattern backdrop/pattern pictures fail to load).
  # UAE-family emulators implement the modern uaegfx board ABI; trust the
  # user's archive as-is — same policy as Classic RTG and MiSTer.
  export P96_TRUST_ARCHIVE_ABI=1

  echo "==> Staging Picasso96 for $EMU_PROFILE RTG"
  stage_p96_icomp_archive "$SYS_ROOT"

  # The uaegfx board (uaegfx-z3) generates its own mode list internally and UAE
  # ignores any Devs/Picasso96Settings we install (picasso96_noautomodes is off),
  # so we cannot choose the board's ModeIDs — we must MATCH them. $SCREEN_MODE_ID
  # for uae/amiberry is computed in MainViewModel.resolvedRTGModeIDHex via
  # ScreenModeConfig.uaeModeID, which mirrors UAE's AssignModeID
  # (0x50001000 | mi_id<<16; mi[] is the WinUAE/FS-UAE/Amiberry picasso96
  # resolution table). Seeding that exact ModeID makes first boot select the
  # GUI-chosen resolution (the resolution-sized backdrop then renders correctly).
  write_screenmode_prefs "$SYS_ROOT" "$SCREEN_MODE_ID" "$WB_DEPTH_HEX"

  # write_screenmode_prefs tags ScreenModeChipset=RTG, but the shared
  # Startup-Sequence_UAEGFX overlay reads "RTG" as "VideoCore RTG" and falls back
  # to the NATIVE screenmode under UAE ("Videocore Mode selected. Using Native
  # screenmode.") — correct only for a PiStorm image booted on UAE. On a native
  # uae/amiberry build the RTG *is* uaegfx, so that native USE reverts the screen
  # to chipset/RGB on every boot (and overrides a manually-saved uaegfx mode).
  # Tag it non-"RTG" so the overlay takes its ELSE branch and applies the ACTIVE
  # screenmode.prefs (the uaegfx mode) — not the build-time screenmode.prefs.User
  # snapshot. A user "Save" in Prefs/ScreenMode rewrites ENVARC: screenmode.prefs
  # but never .User, so applying .User at boot would revert a manually-saved mode
  # every reboot. Applying the active prefs keeps user changes persistent (same
  # policy as build-classic.sh's ClassicRTGScreenMode overlay); IPrefs then keeps it.
  # (CheckScreenModeandChipset.rexx is PiStorm-only — not run on UAE — so this
  # tag is consumed only by the UAEGFX overlay.)
  _smc="$STAGE/ScreenModeChipset.uaegfx"
  printf "UAEGFX\n" > "$_smc"; make_lf_only "$_smc"
  fs_copy_file "$_smc" "$SYS_ROOT/Prefs/Env-Archive/ScreenModeChipset"
  rm -f "$_smc"
  echo "==> $EMU_PROFILE RTG: ScreenModeChipset=UAEGFX (apply uaegfx screenmode, not native fallback)"

  # The generated WB3.2 Startup-Sequence does not call LoadMonDrvs itself
  # (same situation as the MiSTer profile). Establish the Picasso96: assign,
  # then register monitors before LoadWB; IPrefs applies the RTG prefs after.
  _uae_rtg_overlay="$STAGE/uae-rtg-startup.overlay"
  cat > "$_uae_rtg_overlay" <<'UAERTG'
IF EXISTS SYS:Libs/Picasso96
  Assign Picasso96: SYS:Libs/Picasso96
  IF EXISTS SYS:Programs/Picasso96
    Assign ADD Picasso96: SYS:Programs/Picasso96
  ENDIF
ELSE
  IF EXISTS SYS:Programs/Picasso96
    Assign Picasso96: SYS:Programs/Picasso96
  ENDIF
ENDIF

IF EXISTS C:LoadMonDrvs
  C:LoadMonDrvs >NIL:
ENDIF
UAERTG
  make_lf_only "$_uae_rtg_overlay"
  inject_overlay_into_script "$SYS_ROOT" "S/Startup-Sequence" "$_uae_rtg_overlay" inject_before "LoadWB" "UAERTG"
else
  echo "==> $EMU_PROFILE: No RTG mode selected (native chipset)"
fi

# -----------------------------------------------------------------------------
# Register datatypes at boot (all emulator profiles) — safety net.
# The HD Startup-Sequence installed in 5d2 already calls AddDataTypes (that's what
# registers the DEVS:DataTypes/ descriptors so PNG/akPNG/akGIF/akJFIF images load;
# without it everything but built-in ILBM fails: "Unknown data type" /
# "can't load picture" for the backdrop, IBrowse GUI images, etc.). Only inject
# our own call if the active Startup-Sequence lacks one — i.e. the HD SS install
# fell back to the KickDisk base SS, which has no AddDataTypes.
_adt_check="$STAGE/.ss-adt-check.$$"
if fs_pull_file "$SYS_ROOT/S/Startup-Sequence" "$_adt_check" 2>/dev/null && \
   grep -qi 'AddDataTypes' "$_adt_check" 2>/dev/null; then
  echo "==> AddDataTypes: skipped (Startup-Sequence already calls it)"
else
  _adt_overlay="$STAGE/adddatatypes-startup.overlay"
  cat > "$_adt_overlay" <<'ADDDATATYPES'
IF EXISTS C:AddDataTypes
  C:AddDataTypes REFRESH QUIET
ENDIF
ADDDATATYPES
  make_lf_only "$_adt_overlay"
  inject_overlay_into_script "$SYS_ROOT" "S/Startup-Sequence" "$_adt_overlay" inject_before "LoadWB" "AddDataTypes"
  rm -f "$_adt_overlay"
fi
rm -f "$_adt_check" 2>/dev/null || true

# 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 dialer config: S:PPP-Configurations/PPP ---
  # Match Roadshow's documented PPP workflow:
  #   AddNetInterface PPP
  #   ppp_dialer S:PPP-Configurations/PPP
  #
  # MiSTer talks to serial.device over its UART->PPP bridge. The documented
  # working MiSTer setup is: OSD UART Mode = PPP, Baud = 115200, RTS/CTS = ON,
  # 8N1, Nullmodem (MiSTer FPGA Bible / mister-devel docs). RTS/CTS MUST be on:
  # the MiSTer virtual UART uses hardware flow control and only forwards data
  # once RTS is asserted, so rtscts=no leaves the peer silent (matches the
  # "never worked on any MiSTer/any stack" report). nullmodem=yes still ignores
  # carrier/DCD; checkcarrier=no keeps the link from depending on a DCD line.
  echo "==> MiSTer: Writing PPP dialer configuration (115200 baud, RTS/CTS, nullmodem)"
  ensure_img_dir "$SYS_ROOT/S/PPP-Configurations"
  _ppp_config="$STAGE/mister-ppp"
  cat > "$_ppp_config" <<'PPPSERIAL'
interface=PPP
device=serial.device
speed=115200
rtscts=yes
checkcarrier=no
nullmodem=yes
# Diagnostics (disabled): to trace a failed link, uncomment the two lines below.
# This logs PPP negotiation to a volatile RAM: file, cleared each boot.
# FRAMESIN/FRAMESOUT reveal whether the MiSTer side ever answers our LCP
# Configure-Requests (silent peer = UART/pppd not running; our own frames echoed
# back = "Link is looped back"). Valid keywords (from ppp-serial.device):
# LINK AUTH IPCP FRAMESIN FRAMESOUT DATAIN.
#log=LINK,AUTH,IPCP,FRAMESIN,FRAMESOUT
#logfile=RAM:Roadshow-PPP.log
PPPSERIAL
  make_lf_only "$_ppp_config"
  fs_copy_file "$_ppp_config" "$SYS_ROOT/S/PPP-Configurations/PPP"
  rm -f "$_ppp_config"

  # --- 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"

  # Keep S:Network-Startup as a manual helper only. The WBDock launcher is
  # named Ethernet for the user-facing MiSTer network path, while Roadshow still
  # uses the documented PPP interface internally.
  echo "==> MiSTer: Writing S:Network-Startup (manual PPP helper)"
  _net_startup="$STAGE/mister-network-startup"
  cat > "$_net_startup" <<'NETSTARTUP'
; MiSTer PPP network startup (manual helper generated by Amiga Imager)
FailAt 30
IF EXISTS C:RemoveNetInterface
  C:RemoveNetInterface PPP >NIL:
  Wait 1
ENDIF
C:AddNetInterface PPP
IF WARN
  Echo "AddNetInterface PPP failed. Check DEVS:NetInterfaces/PPP and DEVS:Networks/ppp-serial.device."
ELSE
  Run >NIL: C:ppp_dialer S:PPP-Configurations/PPP
ENDIF
NETSTARTUP
  make_lf_only "$_net_startup"
  chmod +x "$_net_startup"  # Amiga 's' bit: Network-Startup is an AmigaDOS script
  fs_copy_file "$_net_startup" "$SYS_ROOT/S/Network-Startup"
  rm -f "$_net_startup"

  # Create a manual Roadshow launcher for WBDock / IconX.
  # MiSTer uses a PPP tunnel over the core UART to reach the MiSTer host's
  # Ethernet/Wi-Fi. The launcher is named "Ethernet" because that is the
  # user-facing network path from Workbench.
  _ethernet_online="$STAGE/Ethernet"
  cat > "$_ethernet_online" <<'ETHERNETONLINE'
; Amiga Imager: Ethernet Online (MiSTer PPP / Roadshow)
FailAt 30
Echo "Resetting Roadshow PPP interface..."
IF EXISTS C:RemoveNetInterface
  C:RemoveNetInterface PPP >NIL:
  Wait 1
ENDIF
Echo "Preparing Roadshow PPP interface..."
C:AddNetInterface PPP
IF WARN
  Echo "AddNetInterface PPP failed. Check DEVS:NetInterfaces/PPP and RAM:Roadshow-PPP.log."
ELSE
  Echo "Starting MiSTer Ethernet link via PPP. Press Ctrl+C in this shell to disconnect."
  Echo "If the link does not come up, check RAM:Roadshow-PPP.log for the PPP negotiation trace."
  C:ppp_dialer S:PPP-Configurations/PPP
ENDIF
ETHERNETONLINE
  make_lf_only "$_ethernet_online"
  chmod +x "$_ethernet_online"  # Amiga 's' bit: Ethernet is an AmigaDOS script
  fs_copy_file "$_ethernet_online" "$SYS_ROOT/S/Ethernet"
  rm -f "$_ethernet_online"

  _go_online="$STAGE/GoOnline"
  cat > "$_go_online" <<'GOONLINE'
; Amiga Imager: GoOnline compatibility wrapper
FailAt 30
Execute SYS:S/Ethernet
GOONLINE
  make_lf_only "$_go_online"
  chmod +x "$_go_online"  # Amiga 's' bit: GoOnline is an AmigaDOS script
  fs_copy_file "$_go_online" "$SYS_ROOT/S/GoOnline"
  rm -f "$_go_online"

  # Project icons so WBDock can launch S:Ethernet via IconX and older configs
  # can still launch S:GoOnline.
  _go_info="$STAGE/GoOnline.info"
  _eth_info="$STAGE/Ethernet.info"
  _go_info_src="$ASSETS_DIR/AmigaFiles/System/WBStartup/OnetimeRunWB.info"
  if [[ -f "$_go_info_src" ]]; then
    cp -f "$_go_info_src" "$_go_info"
    cp -f "$_go_info_src" "$_eth_info"
    fs_copy_file "$_go_info" "$SYS_ROOT/S/GoOnline.info"
    fs_copy_file "$_eth_info" "$SYS_ROOT/S/Ethernet.info"
    rm -f "$_go_info"
    rm -f "$_eth_info"
  elif fs_pull_file "$SYS_ROOT/WBStartup/OnetimeRunWB.info" "$_go_info" >/dev/null 2>&1; then
    cp -f "$_go_info" "$_eth_info"
    fs_copy_file "$_go_info" "$SYS_ROOT/S/GoOnline.info"
    fs_copy_file "$_eth_info" "$SYS_ROOT/S/Ethernet.info"
    rm -f "$_go_info"
    rm -f "$_eth_info"
  fi

  WBDOCK_NETWORK_ENTRY="SYS:S/Ethernet"
  echo "==> MiSTer: PPP networking configured (set OSD: UART=PPP, Baud=115200)"
elif [[ "$EMU_PROFILE" != "mister" ]]; then
  echo "==> $EMU_PROFILE: No Amiga-side network stack (host emulates bsdsocket.library)"
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"
chmod +x "$TMP_RUNALL"  # Amiga 's' bit: required for Execute in OnetimeRunWB
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"
chmod +x "$TMP_WB_OTR"  # Amiga 's' bit: required for WBStartup script execution
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)
# js_variant=none: MiSTer Minimig is 68000, no FPU — javascript.library not needed.
# -----------------------------------------------------------------------------
install_pkg_ibrowse "$SYS_ROOT" "$IBROWSE_DIR" "${IBROWSE_KEY:-}" "none"
install_ibrowse_key_file "$SYS_ROOT" "${IBROWSE_KEY:-}" "${IBROWSE_DIR:-}"

# MiSTer: replace ILBM toolbar images with PNG equivalents.
# images/ contains 5-bit planar ILBM files; ilbm.datatype fails on Minimig
# (likely a 68000/68020 ABI or rendering issue). images-8bit/ contains
# 256-color PNG files loaded by akPNG.datatype which is confirmed working.
# Standard IBrowse installer does the same: installs images-8bit/ content to
# images/ when the user chooses 256-colour images at install time.
if (( INCLUDE_IBROWSE )); then
  _ib_img_tmp="$STAGE/mister-ib-png-$$"
  rm -rf "$_ib_img_tmp" 2>/dev/null || true
  mkdir -p "$_ib_img_tmp"
  if fs_pull_file "$SYS_ROOT/Programs/Ibrowse/images-8bit" "$_ib_img_tmp" 2>/dev/null; then
    _ib_replaced=0
    for _ib_png in "$_ib_img_tmp"/*; do
      [[ -f "$_ib_png" ]] || continue
      _ib_name="$(basename "$_ib_png")"
      fs_copy_file "$_ib_png" "$SYS_ROOT/Programs/Ibrowse/images/$_ib_name"
      _ib_replaced=$(( _ib_replaced + 1 ))
    done
    echo "==> MiSTer: Replaced $_ib_replaced IBrowse toolbar images (ILBM→PNG) in images/"
  else
    echo "==> MiSTer: WARN: images-8bit/ not found — toolbar images remain ILBM"
  fi
  rm -rf "$_ib_img_tmp" 2>/dev/null || true
fi

# WBDock2
install_pkg_wbdock "$SYS_ROOT" "$IBROWSE_DIR"

# Phase 5: post-install sweep — runs plan actions for any package not yet covered
sweep_plan_post_actions "$SYS_ROOT"

# NewMeter position + startup DELAY (seconds). DELAY makes NewMeter open AFTER
# IPrefs has switched to the RTG screen, so it positions on the RTG screen instead
# of the ~640px native screen. The default 5s proved too short under the HD
# Startup-Sequence first-boot flow (NewMeter opened before the RTG switch settled),
# so use 10s for the 020+ UAE family and 25s for the slow 68000 MiSTer Minimig core
# (20s still landed on the native screen on real MiSTer hardware, 2026-06-16).
if package_is_selected "newmeter"; then
  _nm_delay=10
  [[ "$EMU_PROFILE" == "mister" ]] && _nm_delay=25
  configure_newmeter_position "$SYS_ROOT" "$_nm_delay"
fi

# Reuse the proven shared RTG boot path for the final prefs application.
# MiSTer now seeds the RTG mode before LoadWB via MiSTerRTG above, and IPrefs
# remains responsible for WBPattern/backdrop and the normal Workbench prefs pass.
if [[ -n "$SCREEN_MODE_ID" ]]; then
  relocate_iprefs_after_loadwb "$SYS_ROOT"
fi

# MiSTer-specific startup scripts have several injected overlays by this point.
# Normalize their conditionals last so quoted checks and first-boot flag writes
# survive all later startup modifications.
normalize_mister_startup_conditionals "$SYS_ROOT"
reorder_mister_post_loadwb_services "$SYS_ROOT"

# --- 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_pass2
fi

# Auto-arrange Programs icons without curated CSV positions (custom/user packages)
normalize_unpositioned_program_icons "$SYS_ROOT"

verify_programs_entries "$SYS_ROOT"

# -----------------------------------------------------------------------------
# Sanity check
# -----------------------------------------------------------------------------
echo "==> PROGRESS 9/10 Running sanity checks"
echo "==> Sanity check (key dirs on SYS:)"
diag_fs_dir "$SYS_ROOT" || true
diag_fs_dir "$SYS_ROOT/S" --recursive || true
diag_fs_dir "$SYS_ROOT/WBStartup" --recursive || true
diag_fs_dir "$SYS_ROOT/S/OneTimeRunWB" --recursive || true
diag_rdb_info
amigadiskkit_inspect_rdb "$IMG"

# 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 fs_dir "$SYS_ROOT/System/CLI"; 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 fs_dir "$SYS_ROOT/Prefs/Font"; then
  echo "==> WARN: Prefs/Font is a directory (should be a file)"
elif fs_dir "$SYS_ROOT/Prefs/Font.Z"; 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

patch_runtime_partition_references "$SYS_ROOT"

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