#!/bin/bash
set -o pipefail

# TechNexion Camera Driver Modules Installation.
# This file is rendered from configs/driver-cicd.json.

APP_PRODUCT='AIE510-ONX'
APP_VERSION='JP62'
APP_DESCRIPTION='AIE510-ONX JP62'
EXTLINUX_CONF="/boot/extlinux/extlinux.conf"
BOOT_DTB_DIR='/boot/dtb'
REF_DTB_FILE='kernel_tegra234-p3768-0000+p3767-0000-nv.dtb'
MODULE_INSTALL_BASE='updates/drivers/media/i2c'
MAXIM_GMSL_INSTALL_DIR="${MODULE_INSTALL_BASE}/technexion/maxim-gmsl"
TEVS_INSTALL_DIR="${MODULE_INSTALL_BASE}/technexion/tevs"
VLS3_INSTALL_DIR="${MODULE_INSTALL_BASE}/technexion/vls3"
LEGACY_SERVICE_FILE="/etc/systemd/system/tn_cam.service"
SUPPORTS_PLATFORM=0
SUPPORTS_PHY=0
VALIDATES_L4T=0
LEGACY_MODULE_DIRS=(
  'kernel/drivers/media/i2c'
  'updates/drivers/media/i2c'
)
SUPPORTED_MODULES=(
  'vls-gm2'
)
MODULE_ALIASES=(
  '1|vls-gm2'
  'vls_gm2|vls-gm2'
)
MODULE_DESCRIPTIONS=(
  'vls-gm2|VLS-GM2: VLS-GM2 Series Cameras with VL-GM2-8CAM-RPI22 Adaptor'
)
DEFAULT_LABELS=(
  'vls-gm2|vls-gm2'
)
PHY_DEFAULT_LABELS=(
)
PHY_DESCRIPTIONS=(
  'single|Single PHY'
  'dual|Dual PHY'
)
SCRIPT_PLATFORMS=(
)
PLATFORM_LABELS=(
)
PLATFORM_ALIASES=(
)
PLATFORM_MODULES=(
)
SUPPORTED_L4T_VERSIONS=(
  '36.4.3'
)
MODULE_FILES=(
  'technexion/tevs/tevs.ko|updates/drivers/media/i2c/technexion/tevs'
  'technexion/maxim-gmsl/max9296a.ko|updates/drivers/media/i2c/technexion/maxim-gmsl'
  'technexion/maxim-gmsl/max96717.ko|updates/drivers/media/i2c/technexion/maxim-gmsl'
)
BOOT_FILES=(
  'dtbs/tegra234-p3767-camera-aie510-onx-vls-gm2-overlay.dtbo|/boot/tegra234-p3767-camera-aie510-onx-vls-gm2-overlay.dtbo'
  'dtbs/tegra234-p3767-camera-aie510-onx-vls-gm2-fsync-overlay.dtbo|/boot/tegra234-p3767-camera-aie510-onx-vls-gm2-fsync-overlay.dtbo'
)
EXTLINUX_ENTRIES=(
  '|vls-gm2|vls-gm2|auto|OVERLAYS|/boot/tegra234-p3767-camera-aie510-onx-vls-gm2-overlay.dtbo|'
  '|vls-gm2|vls-gm2-fsync|auto|OVERLAYS|/boot/tegra234-p3767-camera-aie510-onx-vls-gm2-fsync-overlay.dtbo|'
)

ARG_PLATFORM=""
ARG_MODULE=""
ARG_AUTO_YES=0
ARG_REBOOT=""
ARG_PHY_MODE=single
INTERACTIVE=0
TS=""
KERNEL_VER=""
L4T_VER=""

print_usage() {
  local module_values
  module_values=$(IFS='|'; echo "${SUPPORTED_MODULES[*]}")
  if [ "$SUPPORTS_PLATFORM" = "1" ]; then
    local platform_values
    platform_values=$(IFS='|'; echo "${SCRIPT_PLATFORMS[*]}")
    cat <<USAGE
Usage: ./tn_install.sh [--platform ${platform_values}] [--module ${module_values}] [--reboot yes|no] [--yes]

Installs ${APP_DESCRIPTION} camera driver artifacts.
Without arguments, this script runs an interactive install flow.
With arguments, it runs non-interactively for APP-driven installation.
USAGE
  else
    cat <<USAGE
Usage: ./tn_install.sh [--module ${module_values}] [--reboot yes|no] [--yes]

Installs ${APP_DESCRIPTION} camera driver artifacts.
Without arguments, this script runs an interactive install flow.
With arguments, it runs non-interactively for APP-driven installation.
USAGE
  fi
}

die() {
  printf 'ERROR: %s\n' "$*" >&2
  exit 1
}

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
}

require_root() {
  [ "$(id -u)" -eq 0 ] || die "run this script as root through the APP runner, pkexec, or sudo"
}

require_arg_value() {
  case "${2:-}" in
    ""|--*)
      print_usage >&2
      die "missing value for $1"
      ;;
  esac
}

key_for_value() {
  printf '%s' "$1" | tr '[:upper:] ' '[:lower:]_' | sed 's/-/_/g'
}

normalize_yes_no() {
  case "$1" in
    y|Y|yes|YES|true|TRUE|1)
      printf 'y\n'
      ;;
    n|N|no|NO|false|FALSE|0)
      printf 'n\n'
      ;;
    *)
      die "cannot support this yes/no option: $1"
      ;;
  esac
}

normalize_phy_mode() {
  local value=$1
  local key
  local row
  local phy
  local description

  key=$(key_for_value "$value")
  for row in "${PHY_DESCRIPTIONS[@]}"; do
    IFS='|' read -r phy description <<< "$row"
    if [ "$key" = "$(key_for_value "$phy")" ] || [ "$key" = "$(key_for_value "$description")" ]; then
      printf '%s\n' "$phy"
      return
    fi
  done

  case "$1" in
    ""|single|SINGLE|Single|s|S|1)
      printf 'single\n'
      ;;
    dual|DUAL|Dual|d|D|2)
      printf 'dual\n'
      ;;
    *)
      die "cannot support this PHY mode: $1"
      ;;
  esac
}

require_file_nonempty() {
  local path=$1

  [ -f "$path" ] || die "missing file: $path"
  [ -s "$path" ] || die "zero-byte file: $path"
}

backup_if_exists() {
  local path=$1

  if [ -e "$path" ]; then
    cp -a "$path" "$path.bak-$TS"
  fi
}

install_file_atomic() {
  local source=$1
  local target=$2
  local mode=${3:-644}
  local target_dir=${target%/*}
  local target_tmp="$target_dir/.${target##*/}.tmp-$$"

  require_file_nonempty "$source"
  mkdir -p "$target_dir"
  rm -f "$target_tmp"
  install -m "$mode" "$source" "$target_tmp"
  require_file_nonempty "$target_tmp"
  sync -f "$target_tmp" 2>/dev/null || sync
  mv "$target_tmp" "$target"
  require_file_nonempty "$target"
  sync -f "$target" 2>/dev/null || sync
}

normalize_platform() {
  local value=$1
  local key
  local row
  local alias
  local canonical
  local label

  key=$(key_for_value "$value")
  for platform in "${SCRIPT_PLATFORMS[@]}"; do
    label=$(platform_label "$platform")
    if [ "$key" = "$(key_for_value "$platform")" ] || [ "$key" = "$(key_for_value "$label")" ]; then
      printf '%s\n' "$platform"
      return
    fi
  done

  for row in "${PLATFORM_ALIASES[@]}"; do
    IFS='|' read -r alias canonical <<< "$row"
    if [ "$key" = "$(key_for_value "$alias")" ]; then
      printf '%s\n' "$canonical"
      return
    fi
  done

  die "unsupported platform for ${APP_PRODUCT} ${APP_VERSION}: $value"
}

normalize_module() {
  local value=$1
  local key
  local row
  local alias
  local canonical
  local module

  key=$(key_for_value "$value")
  for module in "${SUPPORTED_MODULES[@]}"; do
    if [ "$key" = "$(key_for_value "$module")" ] ||
      [ "$key" = "$(key_for_value "$(module_display_name "$module")")" ] ||
      [ "$key" = "$(key_for_value "$(module_description "$module")")" ]; then
      printf '%s\n' "$module"
      return
    fi
  done

  for row in "${MODULE_ALIASES[@]}"; do
    IFS='|' read -r alias canonical <<< "$row"
    if [ "$key" = "$(key_for_value "$alias")" ]; then
      printf '%s\n' "$canonical"
      return
    fi
  done

  die "unsupported module for ${APP_PRODUCT} ${APP_VERSION}: $value"
}

module_allowed_for_platform() {
  local platform=$1
  local module=$2
  local row
  local entry_platform
  local entry_module

  if [ "$SUPPORTS_PLATFORM" != "1" ]; then
    return 0
  fi

  for row in "${PLATFORM_MODULES[@]}"; do
    IFS='|' read -r entry_platform entry_module <<< "$row"
    if [ "$entry_platform" = "$platform" ] && [ "$entry_module" = "$module" ]; then
      return 0
    fi
  done

  return 1
}

allowed_modules_for_current_platform() {
  local module
  local allowed=()

  for module in "${SUPPORTED_MODULES[@]}"; do
    if module_allowed_for_platform "$ARG_PLATFORM" "$module"; then
      allowed+=("$module")
    fi
  done

  printf '%s\n' "${allowed[@]}"
}

module_description() {
  local module=$1
  local row
  local row_module
  local description

  for row in "${MODULE_DESCRIPTIONS[@]}"; do
    IFS='|' read -r row_module description <<< "$row"
    if [ "$row_module" = "$module" ]; then
      printf '%s\n' "$description"
      return
    fi
  done

  printf '%s\n' "$module"
}

platform_label() {
  local platform=$1
  local row
  local row_platform
  local label

  for row in "${PLATFORM_LABELS[@]}"; do
    IFS='|' read -r row_platform label <<< "$row"
    if [ "$row_platform" = "$platform" ]; then
      printf '%s\n' "$label"
      return
    fi
  done

  printf '%s\n' "$platform"
}

module_display_name() {
  local module=$1
  local description

  description=$(module_description "$module")
  case "$description" in
    *" - "*)
      printf '%s\n' "${description%% - *}"
      ;;
    *:*)
      printf '%s\n' "${description%%:*}"
      ;;
    *)
      printf '%s\n' "$description"
      ;;
  esac
}

module_menu_label() {
  local module=$1
  local description

  description=$(module_description "$module")
  case "$description" in
    *:*)
      printf '%s -%s\n' "${description%%:*}" "${description#*:}"
      ;;
    *)
      printf '%s\n' "$description"
      ;;
  esac
}

phy_description() {
  local phy=$1
  local row
  local row_phy
  local description

  for row in "${PHY_DESCRIPTIONS[@]}"; do
    IFS='|' read -r row_phy description <<< "$row"
    if [ "$row_phy" = "$phy" ]; then
      printf '%s\n' "$description"
      return
    fi
  done

  printf '%s\n' "$phy"
}

allowed_phy_modes_for_current_module() {
  local row
  local row_module
  local row_phy
  local row_label
  local modes=()
  local mode
  local seen

  for row in "${PHY_DEFAULT_LABELS[@]}"; do
    IFS='|' read -r row_module row_phy row_label <<< "$row"
    [ "$row_module" = "$ARG_MODULE" ] || continue
    seen=0
    for mode in "${modes[@]}"; do
      if [ "$mode" = "$row_phy" ]; then
        seen=1
        break
      fi
    done
    [ "$seen" = "1" ] || modes+=("$row_phy")
  done

  printf '%s\n' "${modes[@]}"
}

selected_camera_description() {
  local description

  description=$(module_display_name "$ARG_MODULE")
  if [ "$SUPPORTS_PHY" = "1" ]; then
    description="$description ($(phy_description "$ARG_PHY_MODE"))"
  fi
  printf '%s\n' "$description"
}

parse_args() {
  if [ "$#" -eq 0 ]; then
    INTERACTIVE=1
    return
  fi

  while [ "$#" -gt 0 ]; do
    case "$1" in
      --platform)
        if [ "$SUPPORTS_PLATFORM" != "1" ]; then
          print_usage >&2
          die "$1 is not accepted by this package installer"
        fi
        require_arg_value "$1" "${2:-}"
        ARG_PLATFORM=$(normalize_platform "$2")
        shift 2
        ;;
      --module)
        require_arg_value "$1" "${2:-}"
        ARG_MODULE=$(normalize_module "$2")
        shift 2
        ;;
      --reboot)
        require_arg_value "$1" "${2:-}"
        ARG_REBOOT=$(normalize_yes_no "$2")
        shift 2
        ;;
      --yes|-y)
        ARG_AUTO_YES=1
        shift
        ;;
      --help|-h)
        print_usage
        exit 0
        ;;
      --label|--phy)
        print_usage >&2
        die "$1 is not accepted by this package installer"
        ;;
      *)
        print_usage >&2
        die "unknown argument: $1"
        ;;
    esac
  done
}

prompt_backup_continue() {
  local choice

  cat <<WARNING
****** TechNexion Camera Driver Installation ******
This installer updates TechNexion camera drivers, camera DTBOs,
and boot entries for NVIDIA Jetson Development Kits.
Back up your image before continuing.
WARNING
  read -r -p "Continue? [Y/n] " choice
  choice=${choice:-Y}
  case "$choice" in
    y|Y|yes|YES)
      return 0
      ;;
    *)
      echo "Installation aborted."
      exit 0
      ;;
  esac
}

select_platform_interactive() {
  local choice
  local index

  if [ "$SUPPORTS_PLATFORM" != "1" ]; then
    ARG_PLATFORM=""
    return
  fi

  if [ "${#SCRIPT_PLATFORMS[@]}" -eq 1 ]; then
    ARG_PLATFORM=$(normalize_platform "${SCRIPT_PLATFORMS[0]}")
    return
  fi

  echo "Target platform:"
  index=1
  for platform in "${SCRIPT_PLATFORMS[@]}"; do
    printf '  [%d] %s\n' "$index" "$(platform_label "$platform")"
    index=$((index + 1))
  done
  read -r -p "Select platform [1-${#SCRIPT_PLATFORMS[@]}] (default: 1): " choice
  choice=${choice:-1}
  case "$choice" in
    *[!0-9]*)
      ARG_PLATFORM=$(normalize_platform "$choice")
      ;;
    *)
      [ "$choice" -ge 1 ] && [ "$choice" -le "${#SCRIPT_PLATFORMS[@]}" ] ||
        die "platform selection is out of range: $choice"
      ARG_PLATFORM=$(normalize_platform "${SCRIPT_PLATFORMS[$((choice - 1))]}")
      ;;
  esac
}

select_module_interactive() {
  local choice
  local index
  local allowed=()
  local module

  while IFS= read -r module; do
    [ -n "$module" ] && allowed+=("$module")
  done < <(allowed_modules_for_current_platform)

  [ "${#allowed[@]}" -gt 0 ] || die "no modules are configured for platform: ${ARG_PLATFORM:-default}"

  if [ "${#allowed[@]}" -eq 1 ]; then
    ARG_MODULE=$(normalize_module "${allowed[0]}")
    return
  fi

  echo "Camera module:"
  index=1
  for module in "${allowed[@]}"; do
    printf '  [%d] %s\n' "$index" "$(module_menu_label "$module")"
    index=$((index + 1))
  done
  read -r -p "Select camera module [1-${#allowed[@]}] (default: 1): " choice
  choice=${choice:-1}
  case "$choice" in
    *[!0-9]*)
      ARG_MODULE=$(normalize_module "$choice")
      ;;
    *)
      [ "$choice" -ge 1 ] && [ "$choice" -le "${#allowed[@]}" ] ||
        die "module selection is out of range: $choice"
      ARG_MODULE=$(normalize_module "${allowed[$((choice - 1))]}")
      ;;
  esac
}

select_phy_interactive() {
  local choice
  local index
  local modes=()
  local mode

  while IFS= read -r mode; do
    [ -n "$mode" ] && modes+=("$mode")
  done < <(allowed_phy_modes_for_current_module)

  if [ "${#modes[@]}" -eq 0 ]; then
    ARG_PHY_MODE="single"
    return
  fi

  if [ "${#modes[@]}" -eq 1 ]; then
    ARG_PHY_MODE=$(normalize_phy_mode "${modes[0]}")
    return
  fi

  echo "Camera PHY mode:"
  index=1
  for mode in "${modes[@]}"; do
    printf '  [%d] %s\n' "$index" "$(phy_description "$mode")"
    index=$((index + 1))
  done
  read -r -p "Select PHY mode [1-${#modes[@]}] (default: 1): " choice
  choice=${choice:-1}
  case "$choice" in
    *[!0-9]*)
      ARG_PHY_MODE=$(normalize_phy_mode "$choice")
      ;;
    *)
      [ "$choice" -ge 1 ] && [ "$choice" -le "${#modes[@]}" ] ||
        die "PHY mode selection is out of range: $choice"
      ARG_PHY_MODE=$(normalize_phy_mode "${modes[$((choice - 1))]}")
      ;;
  esac
}

prepare_install_choices() {
  local choice
  local allowed=()
  local module

  if [ "$SUPPORTS_PLATFORM" = "1" ]; then
    if [ -z "$ARG_PLATFORM" ]; then
      if [ "$INTERACTIVE" = "1" ]; then
        select_platform_interactive
      else
        ARG_PLATFORM=$(normalize_platform "${SCRIPT_PLATFORMS[0]}")
      fi
    fi
  fi

  while IFS= read -r module; do
    [ -n "$module" ] && allowed+=("$module")
  done < <(allowed_modules_for_current_platform)

  if [ -z "$ARG_MODULE" ]; then
    if [ "$INTERACTIVE" = "1" ]; then
      select_module_interactive
    elif [ "$ARG_AUTO_YES" = "1" ] && [ "${#allowed[@]}" -eq 1 ]; then
      ARG_MODULE=$(normalize_module "${allowed[0]}")
    else
      print_usage >&2
      die "--module is required for non-interactive installation"
    fi
  fi

  if ! module_allowed_for_platform "$ARG_PLATFORM" "$ARG_MODULE"; then
    die "module ${ARG_MODULE} is not supported on platform ${ARG_PLATFORM}"
  fi

  if [ "$SUPPORTS_PHY" = "1" ]; then
    if [ "$INTERACTIVE" = "1" ]; then
      select_phy_interactive
    else
      ARG_PHY_MODE="single"
    fi
  fi

  if [ "$INTERACTIVE" = "1" ]; then
    read -r -p "Reboot after installation? [y/N] " choice
    ARG_REBOOT=$(normalize_yes_no "${choice:-n}")
  elif [ -z "$ARG_REBOOT" ]; then
    ARG_REBOOT="n"
  fi
}

default_label_for_module() {
  local module=$1
  local row
  local row_module
  local row_phy
  local row_label

  if [ "$SUPPORTS_PHY" = "1" ]; then
    for row in "${PHY_DEFAULT_LABELS[@]}"; do
      IFS='|' read -r row_module row_phy row_label <<< "$row"
      if [ "$row_module" = "$module" ] && [ "$row_phy" = "$ARG_PHY_MODE" ]; then
        printf '%s\n' "$row_label"
        return
      fi
    done
  fi

  for row in "${DEFAULT_LABELS[@]}"; do
    IFS='|' read -r row_module row_label <<< "$row"
    if [ "$row_module" = "$module" ]; then
      printf '%s\n' "$row_label"
      return
    fi
  done

  die "missing default extlinux label for module: $module"
}

validate_l4t_version() {
  local supported

  if [ "$VALIDATES_L4T" != "1" ]; then
    return
  fi

  L4T_VER=$(dpkg-query -W -f='${Version}' nvidia-l4t-core 2>/dev/null | cut -d- -f1)
  [ -n "$L4T_VER" ] || die "cannot detect NVIDIA L4T version from nvidia-l4t-core"

  for supported in "${SUPPORTED_L4T_VERSIONS[@]}"; do
    case "$L4T_VER" in
      "$supported"|"$supported".*)
        return
        ;;
    esac
  done

  die "cannot support this L4T version: $L4T_VER"
}

validate_package_files() {
  local row
  local source
  local ignored

  for row in "${MODULE_FILES[@]}"; do
    IFS='|' read -r source ignored <<< "$row"
    require_file_nonempty "$source"
  done

  for row in "${BOOT_FILES[@]}"; do
    IFS='|' read -r source ignored <<< "$row"
    require_file_nonempty "$source"
  done
}

resolve_boot_path() {
  local boot_path=$1

  boot_path=${boot_path%\"}
  boot_path=${boot_path#\"}

  case "$boot_path" in
    /*)
      printf '%s\n' "$boot_path"
      ;;
    *)
      printf '/boot/%s\n' "${boot_path#./}"
      ;;
  esac
}

label_exists() {
  local label=$1
  local conf=${2:-$EXTLINUX_CONF}

  awk -v label="$label" '
    toupper($1) == "LABEL" && $2 == label { found = 1 }
    END { exit found ? 0 : 1 }
  ' "$conf"
}

get_default_label() {
  local conf=${1:-$EXTLINUX_CONF}

  awk '
    /^[[:space:]]*(#|$)/ { next }
    toupper($1) == "DEFAULT" { print $2; found = 1; exit }
    END { exit found ? 0 : 1 }
  ' "$conf" || die "cannot find DEFAULT label in $conf"
}

get_label_fdt() {
  local label=$1
  local conf=${2:-$EXTLINUX_CONF}

  awk -v label="$label" '
    toupper($1) == "LABEL" {
      if (capture) {
        exit
      }
      capture = ($2 == label)
    }
    capture && toupper($1) == "FDT" {
      print $2
      found = 1
      exit
    }
    END { exit found ? 0 : 1 }
  ' "$conf" || true
}

find_base_label() {
  local default_label

  if label_exists primary; then
    printf 'primary\n'
    return
  fi

  default_label=$(get_default_label || true)
  if [ -n "$default_label" ] && [[ "$default_label" != tn-* ]] && label_exists "$default_label"; then
    printf '%s\n' "$default_label"
    return
  fi

  awk '
    toupper($1) == "LABEL" && $2 !~ /^tn-/ {
      print $2
      found = 1
      exit
    }
    END { exit found ? 0 : 1 }
  ' "$EXTLINUX_CONF" || die "cannot find a non-TN extlinux label to clone"
}

first_boot_dtb() {
  [ -d "$BOOT_DTB_DIR" ] || return 0
  find "$BOOT_DTB_DIR" -maxdepth 1 \( -type f -o -type l \) -name '*.dtb' -printf '%f\n' | sort | sed -n '1p'
}

resolve_auto_fdt() {
  local base_label=$1
  local label_fdt
  local resolved
  local first_dtb

  if [ -n "$REF_DTB_FILE" ] && [ -s "$BOOT_DTB_DIR/$REF_DTB_FILE" ]; then
    printf '%s/%s\n' "$BOOT_DTB_DIR" "$REF_DTB_FILE"
    return
  fi

  label_fdt=$(get_label_fdt "$base_label")
  if [ -n "$label_fdt" ]; then
    resolved=$(resolve_boot_path "$label_fdt")
    if [ -s "$resolved" ]; then
      printf '%s\n' "$label_fdt"
      return
    fi
  fi

  first_dtb=$(first_boot_dtb)
  [ -n "$first_dtb" ] || return 0
  require_file_nonempty "$BOOT_DTB_DIR/$first_dtb"
  printf '%s/%s\n' "$BOOT_DTB_DIR" "$first_dtb"
}

resolve_entry_fdt() {
  local requested=$1
  local base_label=$2
  local resolved

  case "$requested" in
    ""|auto)
      resolve_auto_fdt "$base_label"
      ;;
    /*)
      resolved=$(resolve_boot_path "$requested")
      require_file_nonempty "$resolved"
      printf '%s\n' "$requested"
      ;;
    *)
      resolved="$BOOT_DTB_DIR/$requested"
      require_file_nonempty "$resolved"
      printf '%s\n' "$resolved"
      ;;
  esac
}

remove_legacy_tncam_service() {
  if [ -f "$LEGACY_SERVICE_FILE" ]; then
    echo "Remove legacy TN-CAM service..."
    if command -v systemctl >/dev/null 2>&1; then
      systemctl stop tn_cam.service 2>/dev/null || true
      systemctl disable tn_cam.service 2>/dev/null || true
    fi
    rm -f "$LEGACY_SERVICE_FILE"
    if command -v systemctl >/dev/null 2>&1; then
      systemctl daemon-reload
    fi
  fi
}

remove_legacy_tncam_modules() {
  local row
  local source
  local ignored
  local module_name
  local legacy_dir
  local target
  local seen=" "

  for row in "${MODULE_FILES[@]}"; do
    IFS='|' read -r source ignored <<< "$row"
    module_name=${source##*/}
    case "$seen" in
      *" $module_name "*)
        continue
        ;;
    esac
    seen="$seen$module_name "
    for legacy_dir in "${LEGACY_MODULE_DIRS[@]}"; do
      target="/lib/modules/$KERNEL_VER/$legacy_dir/$module_name"
      if [ -f "$target" ]; then
        echo "Remove legacy TN-CAM module: $target"
        rm -f "$target"
      fi
    done
  done
}

remove_extlinux_label() {
  local label=$1
  local source_conf=$2

  awk -v label="$label" '
    toupper($1) == "LABEL" {
      skip = ($2 == label)
    }
    !skip { print }
  ' "$source_conf"
}

cleanup_tn_extlinux_labels() {
  local tmp_conf

  tmp_conf=$(mktemp)
  awk '
    function flush_blanks() {
      for (i = 1; i <= blank_count; i++) {
        print ""
      }
      blank_count = 0
    }

    /^[[:space:]]*$/ {
      if (skip) {
        next
      }
      blank_count++
      next
    }

    /^[[:space:]]*LABEL[[:space:]]+tn-/ {
      blank_count = 0
      skip = 1
      next
    }

    /^[[:space:]]*LABEL[[:space:]]+/ {
      skip = 0
    }

    {
      if (!skip) {
        flush_blanks()
        print
      }
    }

    END {
      if (!skip) {
        flush_blanks()
      }
    }
  ' "$EXTLINUX_CONF" > "$tmp_conf"

  require_file_nonempty "$tmp_conf"
  install_file_atomic "$tmp_conf" "$EXTLINUX_CONF"
  rm -f "$tmp_conf"
}

write_extlinux_entry_block() {
  local base_label=$1
  local new_label=$2
  local fdt_path=$3
  local overlay_keyword=$4
  local overlay_path=$5
  local tmp_block

  tmp_block=$(mktemp)
  awk \
    -v base="$base_label" \
    -v new="$new_label" \
    -v fdt="$fdt_path" \
    -v overlay_keyword="$overlay_keyword" \
    -v overlay="$overlay_path" '
    function emit_insertions() {
      if (fdt != "" && !inserted_fdt) {
        print "      FDT " fdt
        inserted_fdt = 1
      }
      if (overlay != "" && !inserted_overlay) {
        print "      " overlay_keyword " " overlay
        inserted_overlay = 1
      }
    }
    function emit_block(    i, line, trimmed, fields, key) {
      inserted_fdt = 0
      inserted_overlay = 0
      for (i = 1; i <= n; i++) {
        line = lines[i]
        trimmed = line
        sub(/^[ \t]+/, "", trimmed)
        split(trimmed, fields, /[ \t]+/)
        key = toupper(fields[1])

        if (key == "LABEL") {
          sub(/LABEL[ \t]+[^ \t]+/, "LABEL " new, line)
        } else if (key == "MENU" && toupper(fields[2]) == "LABEL") {
          line = "      MENU LABEL " new
        } else if (key == "FDT" || key == "OVERLAY" || key == "OVERLAYS" || key == "FDTOVERLAYS") {
          continue
        } else if (key == "APPEND") {
          emit_insertions()
        }
        print line
      }
      emit_insertions()
    }
    toupper($1) == "LABEL" {
      if (capture) {
        exit
      }
      capture = ($2 == base)
    }
    capture {
      lines[++n] = $0
      line = $0
      sub(/^[ \t]+/, "", line)
      split(line, fields, /[ \t]+/)
      if (toupper(fields[1]) == "APPEND") {
        exit
      }
    }
    END {
      if (n == 0) {
        exit 33
      }
      emit_block()
    }
  ' "$EXTLINUX_CONF" > "$tmp_block" || {
    rm -f "$tmp_block"
    die "cannot clone extlinux label: $base_label"
  }

  printf '%s\n' "$tmp_block"
}

validate_extlinux_label() {
  local label=$1
  local conf=$2

  label_exists "$label" "$conf" || die "generated extlinux config is missing label: $label"
}

install_extlinux_entry() {
  local label=$1
  local requested_fdt=$2
  local overlay_keyword=$3
  local overlay_path=$4
  local base_label
  local fdt_path
  local tmp_block
  local tmp_conf

  base_label=$(find_base_label)
  fdt_path=$(resolve_entry_fdt "$requested_fdt" "$base_label")

  if [ -n "$overlay_path" ]; then
    [ -n "$fdt_path" ] || die "rootfs OVERLAYS require an FDT entry; missing base DTB for overlay: $overlay_path"
    require_file_nonempty "$(resolve_boot_path "$overlay_path")"
  fi

  tmp_block=$(write_extlinux_entry_block "$base_label" "tn-$label" "$fdt_path" "$overlay_keyword" "$overlay_path")
  tmp_conf=$(mktemp)
  {
    remove_extlinux_label "tn-$label" "$EXTLINUX_CONF"
    printf '\n\n'
    cat "$tmp_block"
  } > "$tmp_conf"

  validate_extlinux_label "tn-$label" "$tmp_conf"
  install_file_atomic "$tmp_conf" "$EXTLINUX_CONF"
  rm -f "$tmp_block" "$tmp_conf"
}

set_default_label() {
  local label=$1
  local tmp_conf

  label="tn-$label"
  label_exists "$label" || die "refusing to set DEFAULT to missing extlinux label: $label"
  tmp_conf=$(mktemp)
  if grep -Eq '^[[:space:]]*DEFAULT[[:space:]]+' "$EXTLINUX_CONF"; then
    sed "s/^[[:space:]]*DEFAULT[[:space:]].*/DEFAULT $label/" "$EXTLINUX_CONF" > "$tmp_conf"
  else
    {
      printf 'DEFAULT %s\n' "$label"
      cat "$EXTLINUX_CONF"
    } > "$tmp_conf"
  fi
  install_file_atomic "$tmp_conf" "$EXTLINUX_CONF"
  rm -f "$tmp_conf"
}

install_modules() {
  local row
  local source
  local target_subdir
  local target

  for row in "${MODULE_FILES[@]}"; do
    IFS='|' read -r source target_subdir <<< "$row"
    target="/lib/modules/$KERNEL_VER/$target_subdir/${source##*/}"
    backup_if_exists "$target"
    install_file_atomic "$source" "$target"
  done

  if ((${#MODULE_FILES[@]} > 0)); then
    depmod -a "$KERNEL_VER"
  fi
}

install_boot_files() {
  local row
  local source
  local target

  for row in "${BOOT_FILES[@]}"; do
    IFS='|' read -r source target <<< "$row"
    backup_if_exists "$target"
    install_file_atomic "$source" "$target"
  done
}

install_extlinux_entries() {
  local row
  local entry_platform
  local entry_module
  local label
  local fdt
  local overlay_keyword
  local overlay
  local phy_mode
  local installed=0

  require_file_nonempty "$EXTLINUX_CONF"
  backup_if_exists "$EXTLINUX_CONF"
  cleanup_tn_extlinux_labels

  for row in "${EXTLINUX_ENTRIES[@]}"; do
    IFS='|' read -r entry_platform entry_module label fdt overlay_keyword overlay phy_mode <<< "$row"
    if [ "$SUPPORTS_PLATFORM" = "1" ] && [ "$entry_platform" != "$ARG_PLATFORM" ]; then
      continue
    fi
    [ "$entry_module" = "$ARG_MODULE" ] || continue
    if [ -n "${phy_mode:-}" ] && [ "$phy_mode" != "$ARG_PHY_MODE" ]; then
      continue
    fi
    install_extlinux_entry "$label" "$fdt" "$overlay_keyword" "$overlay"
    installed=$((installed + 1))
  done

  [ "$installed" -gt 0 ] || die "no extlinux entries configured for module: $ARG_MODULE"
  set_default_label "$(default_label_for_module "$ARG_MODULE")"
}

show_summary() {
  local row
  local source
  local target_subdir
  local target
  local entry_platform
  local entry_module
  local label
  local fdt
  local overlay_keyword
  local overlay
  local phy_mode
  local module_count=0
  local boot_file_count=0
  local entry_count=0
  local default_label
  local default_overlay=""

  for row in "${MODULE_FILES[@]}"; do
    IFS='|' read -r source target_subdir <<< "$row"
    module_count=$((module_count + 1))
  done

  for row in "${BOOT_FILES[@]}"; do
    IFS='|' read -r source target <<< "$row"
    boot_file_count=$((boot_file_count + 1))
  done

  default_label=$(default_label_for_module "$ARG_MODULE")
  for row in "${EXTLINUX_ENTRIES[@]}"; do
    IFS='|' read -r entry_platform entry_module label fdt overlay_keyword overlay phy_mode <<< "$row"
    if [ "$SUPPORTS_PLATFORM" = "1" ] && [ "$entry_platform" != "$ARG_PLATFORM" ]; then
      continue
    fi
    [ "$entry_module" = "$ARG_MODULE" ] || continue
    if [ -n "${phy_mode:-}" ] && [ "$phy_mode" != "$ARG_PHY_MODE" ]; then
      continue
    fi
    entry_count=$((entry_count + 1))
    if [ "$label" = "$default_label" ]; then
      default_overlay=$overlay
    fi
  done

  echo "Installation summary:"
  echo "  Kernel: $KERNEL_VER"
  echo "  Modules installed: $module_count"
  echo "  Boot files installed: $boot_file_count"
  echo "  Default boot entry: $default_label"
  if [ -n "$default_overlay" ]; then
    echo "  Camera overlay: $default_overlay"
  fi
  echo "  Camera boot entries: $entry_count"
}

main() {
  local script_dir

  parse_args "$@"

  script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
  cd "$script_dir" || die "cannot enter installer directory: $script_dir"

  if [ "$INTERACTIVE" = "1" ]; then
    prompt_backup_continue
  else
    echo "Running non-interactive installation; prompts are skipped."
  fi

  prepare_install_choices

  for cmd in awk cat chmod cp cut date depmod find grep id install mkdir mktemp mv rm sed sort sync tr uname; do
    require_cmd "$cmd"
  done
  if [ "$VALIDATES_L4T" = "1" ]; then
    require_cmd dpkg-query
  fi

  validate_package_files
  validate_l4t_version
  require_root

  TS=$(date +%Y%m%d-%H%M%S)
  KERNEL_VER=$(uname -r)

  echo "Installing ${APP_DESCRIPTION}"
  if [ "$SUPPORTS_PLATFORM" = "1" ]; then
    echo "Selected platform: $(platform_label "$ARG_PLATFORM")"
  fi
  echo "Selected module: $(module_display_name "$ARG_MODULE")"
  if [ "$SUPPORTS_PHY" = "1" ]; then
    echo "Selected PHY mode: $(phy_description "$ARG_PHY_MODE")"
  fi

  remove_legacy_tncam_modules
  remove_legacy_tncam_service
  install_modules
  install_boot_files
  install_extlinux_entries
  show_summary

  echo "Finish Camera Driver Installation."
  if [ "$ARG_REBOOT" = "y" ]; then
    echo "Rebooting..."
    reboot
  else
    echo "Reboot the device when the user is ready."
  fi
}

main "$@"
