#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# partition_layout.sh — Shared partition spec parsing / validation helpers.
#
# Sourced by: build-pistorm.sh, build-classic.sh, build-emulator.sh
#
# Expects common.sh to already be sourced for die() and part_fs_to_dostype().
# Operates on the caller-owned PART_* arrays and layout globals.
# ---------------------------------------------------------------------------

_partition_layout_trim() {
  local value="${1-}"
  value="${value#"${value%%[![:space:]]*}"}"
  value="${value%"${value##*[![:space:]]}"}"
  printf '%s' "$value"
}

_partition_layout_normalize_fs() {
  local raw
  raw="$(_partition_layout_trim "${1-}")"
  raw="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
  case "$raw" in
    ""|ffs) printf 'Ffs' ;;
    ffsintl) printf 'FfsIntl' ;;
    ffsdc) printf 'FfsDC' ;;
    pfs3) printf 'Pfs3' ;;
    *) return 1 ;;
  esac
}

_partition_layout_default_block_size_for_fs() {
  local fs
  fs="$(_partition_layout_normalize_fs "${1-}")" || return 1
  case "$fs" in
    Ffs|FfsIntl|FfsDC) printf '2048' ;;
    Pfs3) printf '512' ;;
    *) return 1 ;;
  esac
}

_partition_layout_normalize_size() {
  local raw lowered
  raw="$(_partition_layout_trim "${1-}")"
  lowered="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
  if [[ "$lowered" == "remaining" ]]; then
    printf 'remaining'
    return 0
  fi
  [[ "$raw" =~ ^[0-9]+$ ]] || return 1
  printf '%s' "$raw"
}

_partition_layout_normalize_bootable() {
  local raw
  raw="$(_partition_layout_trim "${1-}")"
  [[ -z "$raw" ]] && raw="0"
  case "$raw" in
    0|1) printf '%s' "$raw" ;;
    *) return 1 ;;
  esac
}

_partition_layout_normalize_priority() {
  local raw
  raw="$(_partition_layout_trim "${1-}")"
  [[ -z "$raw" ]] && raw="0"
  [[ "$raw" =~ ^-?[0-9]+$ ]] || return 1
  printf '%s' "$raw"
}

_partition_layout_normalize_block_size() {
  local raw fs normalized_fs
  raw="$(_partition_layout_trim "${1-}")"
  normalized_fs="$(_partition_layout_normalize_fs "${2-}")" || return 1

  if [[ -z "$raw" ]]; then
    _partition_layout_default_block_size_for_fs "$normalized_fs"
    return 0
  fi

  [[ "$raw" =~ ^[0-9]+$ ]] || return 1
  case "$normalized_fs:$raw" in
    Ffs:512|Ffs:1024|Ffs:2048|Ffs:4096|FfsIntl:512|FfsIntl:1024|FfsIntl:2048|FfsIntl:4096|FfsDC:512|FfsDC:1024|FfsDC:2048|FfsDC:4096|Pfs3:512|Pfs3:1024)
      printf '%s' "$raw"
      ;;
    *)
      return 1
      ;;
  esac
}

_partition_layout_validate_name() {
  local name="${1-}"
  [[ -n "$name" ]] || return 1
  [[ "$name" != *:* ]] || return 1
  [[ "$name" != *[[:space:]]* ]] || return 1
}

_partition_layout_validate_label() {
  local label="${1-}"
  [[ -n "$label" ]] || return 1
  [[ "$label" != *:* ]] || return 1
}

partition_layout_add_spec() {
  local spec="${1:?}"
  local name="" label="" size="" fs="" block_size="" bootable="" priority="" extra=""
  local fs_raw="" size_raw="" block_size_raw="" bootable_raw="" priority_raw=""
  local field5="" field6="" field7=""

  IFS=':' read -r name label size fs field5 field6 field7 extra <<< "$spec"
  [[ -z "${extra:-}" ]] || die "Malformed --partition spec '$spec'. Expected name:label:size:fs:block-size:bootable:priority."

  name="$(_partition_layout_trim "$name")"
  label="$(_partition_layout_trim "$label")"
  size_raw="$size"
  fs_raw="$fs"
  if [[ -n "${field7:-}" ]]; then
    block_size_raw="$field5"
    bootable_raw="$field6"
    priority_raw="$field7"
  else
    block_size_raw=""
    bootable_raw="$field5"
    priority_raw="$field6"
  fi

  _partition_layout_validate_name "$name" || die "Invalid partition device name '${name:-<empty>}' in --partition spec '$spec'."
  _partition_layout_validate_label "$label" || die "Invalid partition label '${label:-<empty>}' in --partition spec '$spec'."

  size="$(_partition_layout_normalize_size "$size_raw")" \
    || die "Invalid partition size '${size_raw:-<empty>}' in --partition spec '$spec'. Use a MiB integer or 'remaining'."
  fs="$(_partition_layout_normalize_fs "$fs_raw")" \
    || die "Invalid partition filesystem '${fs_raw:-<empty>}' in --partition spec '$spec'. Use Ffs | FfsIntl | FfsDC | Pfs3."
  block_size="$(_partition_layout_normalize_block_size "$block_size_raw" "$fs")" \
    || die "Invalid partition block size '${block_size_raw:-<empty>}' in --partition spec '$spec' for ${fs}. Use 512 | 1024 | 2048 | 4096 as supported by the filesystem."
  bootable="$(_partition_layout_normalize_bootable "$bootable_raw")" \
    || die "Invalid partition bootable value '${bootable_raw:-<empty>}' in --partition spec '$spec'. Use 0 or 1."
  priority="$(_partition_layout_normalize_priority "$priority_raw")" \
    || die "Invalid partition boot priority '${priority_raw:-<empty>}' in --partition spec '$spec'. Use an integer."

  PART_NAMES+=("$name")
  PART_LABELS+=("$label")
  PART_SIZES+=("$size")
  PART_FSTYPES+=("$fs")
  PART_BLOCKSIZES+=("$block_size")
  PART_BOOTABLE+=("$bootable")
  PART_BOOTPRI+=("$priority")
}

partition_layout_use_legacy_defaults() {
  local prefix="${1:?}"
  local allow_boot_remaining="${2:-0}"

  PART_NAMES=("${prefix}0")
  PART_LABELS=("Workbench")
  if [[ "$allow_boot_remaining" == "1" && "${DH0_MIB:-0}" -le 0 ]]; then
    PART_SIZES=("remaining")
  else
    PART_SIZES=("${DH0_MIB:-0}")
  fi
  PART_FSTYPES=("${AMIGA_FS:-Ffs}")
  PART_BLOCKSIZES=("$(_partition_layout_default_block_size_for_fs "${AMIGA_FS:-Ffs}")")
  PART_BOOTABLE=("1")
  PART_BOOTPRI=("1")

  if [[ "${DH1_MIB:-0}" -gt 0 ]]; then
    PART_NAMES+=("${prefix}1")
    PART_LABELS+=("Work")
    PART_SIZES+=("remaining")
    PART_FSTYPES+=("${AMIGA_FS:-Ffs}")
    PART_BLOCKSIZES+=("$(_partition_layout_default_block_size_for_fs "${AMIGA_FS:-Ffs}")")
    PART_BOOTABLE+=("0")
    PART_BOOTPRI+=("0")
  fi
}

partition_layout_validate() {
  local count="${#PART_NAMES[@]}"
  local count_labels="${#PART_LABELS[@]}"
  local count_sizes="${#PART_SIZES[@]}"
  local count_fstypes="${#PART_FSTYPES[@]}"
  local count_blocksizes="${#PART_BLOCKSIZES[@]}"
  local count_bootable="${#PART_BOOTABLE[@]}"
  local count_bootpri="${#PART_BOOTPRI[@]}"
  local remaining_count=0
  local seen_names="|"
  local index upper_name name label size fs block_size bootable priority

  [[ "$count" -gt 0 ]] || die "No Amiga partitions defined."
  [[ "$count" -eq "$count_labels" && "$count" -eq "$count_sizes" && "$count" -eq "$count_fstypes" && "$count" -eq "$count_blocksizes" && "$count" -eq "$count_bootable" && "$count" -eq "$count_bootpri" ]] \
    || die "Partition layout arrays are inconsistent. This is an internal script bug."

  for ((index = 0; index < count; index++)); do
    name="$(_partition_layout_trim "${PART_NAMES[$index]}")"
    label="$(_partition_layout_trim "${PART_LABELS[$index]}")"
    size="$(_partition_layout_normalize_size "${PART_SIZES[$index]}")" \
      || die "Invalid partition size '${PART_SIZES[$index]}' for ${name:-partition $((index + 1))}. Use a MiB integer or 'remaining'."
    fs="$(_partition_layout_normalize_fs "${PART_FSTYPES[$index]}")" \
      || die "Invalid filesystem '${PART_FSTYPES[$index]}' for ${name:-partition $((index + 1))}. Use Ffs | FfsIntl | FfsDC | Pfs3."
    block_size="$(_partition_layout_normalize_block_size "${PART_BLOCKSIZES[$index]}" "$fs")" \
      || die "Invalid block size '${PART_BLOCKSIZES[$index]}' for ${name:-partition $((index + 1))} (${fs})."
    bootable="$(_partition_layout_normalize_bootable "${PART_BOOTABLE[$index]}")" \
      || die "Invalid bootable flag '${PART_BOOTABLE[$index]}' for ${name:-partition $((index + 1))}. Use 0 or 1."
    priority="$(_partition_layout_normalize_priority "${PART_BOOTPRI[$index]}")" \
      || die "Invalid boot priority '${PART_BOOTPRI[$index]}' for ${name:-partition $((index + 1))}. Use an integer."

    _partition_layout_validate_name "$name" || die "Partition $((index + 1)) has an invalid device name '${name:-<empty>}'."
    _partition_layout_validate_label "$label" || die "Partition ${name:-$((index + 1))} has an invalid label '${label:-<empty>}'."

    upper_name="$(printf '%s' "$name" | tr '[:lower:]' '[:upper:]')"
    [[ "$seen_names" != *"|$upper_name|"* ]] || die "Duplicate partition name '$name' specified."
    seen_names="${seen_names}${upper_name}|"

    if [[ "$size" == "remaining" ]]; then
      remaining_count=$((remaining_count + 1))
    elif [[ "$size" -lt 256 ]]; then
      die "Partition $name must be at least 256 MiB."
    fi

    PART_NAMES[$index]="$name"
    PART_LABELS[$index]="$label"
    PART_SIZES[$index]="$size"
    PART_FSTYPES[$index]="$fs"
    PART_BLOCKSIZES[$index]="$block_size"
    PART_BOOTABLE[$index]="$bootable"
    PART_BOOTPRI[$index]="$priority"
  done

  (( remaining_count <= 1 )) || die "Multiple 'remaining' partitions specified (${remaining_count}). Only one partition may use remaining space."
  [[ "${PART_BOOTABLE[0]}" == "1" ]] || die "First partition ${PART_NAMES[0]} must be bootable."

  NUM_PARTS="$count"
}

partition_layout_apply_primary_fs() {
  case "${PART_FSTYPES[0]}" in
    Pfs3)
      HST_FS="Pfs3"
      AMIGA_FS="Pfs3"
      ;;
    FfsDC)
      HST_FS="DOS7"
      AMIGA_FS="FfsDC"
      ;;
    *)
      HST_FS="$(part_fs_to_dostype "${PART_FSTYPES[0]}" "0")"
      AMIGA_FS="Ffs"
      ;;
  esac
}

partition_layout_prepare() {
  local prefix="${1:?}"
  local allow_boot_remaining="${2:-0}"

  if [[ ${#PART_NAMES[@]} -eq 0 ]]; then
    partition_layout_use_legacy_defaults "$prefix" "$allow_boot_remaining"
  fi

  partition_layout_validate
  partition_layout_apply_primary_fs
}

partition_layout_uses_fs() {
  local wanted="$(_partition_layout_normalize_fs "${1:?}")" || return 1
  local index
  for ((index = 0; index < ${#PART_FSTYPES[@]}; index++)); do
    [[ "${PART_FSTYPES[$index]}" == "$wanted" ]] && return 0
  done
  return 1
}

partition_layout_resolve_bytes() {
  local usable_bytes="${1:?}"
  local fixed_total=0
  local remaining_idx=-1
  local remaining_count=0
  local min_partition_bytes=$((256 * 1024 * 1024))
  local index bytes remain overflow_mib

  [[ "$usable_bytes" -gt 0 ]] || die "Computed usable RDB space invalid"

  PART_BYTES=()
  for ((index = 0; index < NUM_PARTS; index++)); do
    if [[ "${PART_SIZES[$index]}" == "remaining" ]]; then
      remaining_idx="$index"
      remaining_count=$((remaining_count + 1))
      PART_BYTES+=("0")
    else
      bytes=$(( ${PART_SIZES[$index]} * 1024 * 1024 ))
      fixed_total=$(( fixed_total + bytes ))
      PART_BYTES+=("$bytes")
    fi
  done

  (( remaining_count <= 1 )) || die "Multiple 'remaining' partitions specified (${remaining_count}). Only one partition may use remaining space."

  if [[ "$remaining_idx" -ge 0 ]]; then
    remain=$(( usable_bytes - fixed_total ))
    if (( remain < min_partition_bytes )); then
      die "Remaining space for ${PART_NAMES[$remaining_idx]} is only $(( remain / 1024 / 1024 )) MiB (< 256 MiB). Fixed partitions total $(( fixed_total / 1024 / 1024 )) MiB; usable RDB = $(( usable_bytes / 1024 / 1024 )) MiB. Increase image size or reduce fixed partition sizes."
    fi
    PART_BYTES[$remaining_idx]="$remain"
  elif (( fixed_total > usable_bytes )); then
    overflow_mib=$(( (fixed_total - usable_bytes + 1024 * 1024 - 1) / 1024 / 1024 ))
    die "Fixed partitions exceed usable RDB space by ${overflow_mib} MiB. Increase image size or reduce partition sizes."
  fi

  PART_FIXED_TOTAL_BYTES="$fixed_total"
}

partition_layout_log_resolved_bytes() {
  local total_bytes="${1:?}"
  local usable_bytes="${2:?}"
  local index

  echo "==> RDB sizing: total=${total_bytes}B usable=${usable_bytes}B fixed=${PART_FIXED_TOTAL_BYTES:-0}B partitions=${NUM_PARTS}"
  for ((index = 0; index < NUM_PARTS; index++)); do
    echo "    ${PART_NAMES[$index]}: ${PART_BYTES[$index]}B (requested: ${PART_SIZES[$index]}, fs=${PART_FSTYPES[$index]}, block-size=${PART_BLOCKSIZES[$index]})"
  done
}
