#!/usr/bin/env bash
set -euo pipefail

SCRIPT_NAME="$(basename "$0")"
SCRIPT_VERSION="2026-06-25-runtime-git-build"
SOURCE_REPO_URL="https://github.com/TechNexion-Vision/tn-rpi-camera-driver.git"

usage() {
  cat <<'USAGE'
Usage:
  install-tevs-native.sh --adapter rpi15 [--camera-module tevs|tevm] [options]
  install-tevs-native.sh --adapter rpi22 [--camera-module tevs|tevm] [--port auto|both|cam0|cam1] [options]

Build and install the TechNexion TEVS/TEVM camera driver directly on the
target Raspberry Pi, without rebuilding the full kernel.

Required:
  --adapter rpi15|rpi22
      rpi15: TEVS-RPI15 / 2-lane profile, used by Raspberry Pi 4 and
             Raspberry Pi 5 systems cabled for 2-lane sensors
      rpi22: TEVS-RPI22 / 4-lane profile, the default Raspberry Pi 5
             profile for 4-lane sensors

Options:
  --camera-module tevs|tevm
      Camera overlay family to install. Default: tevs.

  --port auto|both|cam0|cam1
      Camera connector for rpi22. Default: auto.
      auto/both installs both CAM/DISP overlays so one or two connected
      cameras can probe without the user knowing the connector number.
      cam0 installs dtoverlay=<camera-module>-rpi22,cam0 only.
      cam1 installs dtoverlay=<camera-module>-rpi22 only.

  --source-dir DIR
      Use or clone the TechNexion source into DIR.
      Default: a temporary directory that is removed when the script exits.

  --no-apt
      Skip apt update/install.

  --no-config
      Do not edit /boot/firmware/config.txt or /boot/config.txt.

  --clean
      Remove previously installed TechNexion camera artifacts before
      installing again.
      This removes tevs.ko/tevs.ko.xz for the running kernel, TEVS/TEVM
      boot overlays, the managed config block, and /etc/modprobe.d/tevs.conf.

  --reboot
      Reboot automatically after installation.

  -h, --help
      Show this help.

Examples:
  # Pi 4, TEVS-RPI15 / 2-lane profile
  bash install-tevs-native.sh --adapter rpi15

  # Pi 5, TEVS-RPI22 / 4-lane profile on either or both CAM/DISP connectors
  bash install-tevs-native.sh --adapter rpi22

  # Pi 5 with Raspberry Pi cable/adaptation for a 2-lane sensor
  bash install-tevs-native.sh --adapter rpi15

  # Pi 5, TEVS-RPI22 on CAM/DISP 1 only
  bash install-tevs-native.sh --adapter rpi22 --port cam1

  # Pi 5, TEVS-RPI22 on CAM/DISP 0, reboot after install
  bash install-tevs-native.sh --adapter rpi22 --port cam0 --reboot

  # Pi 5, TEVM-RPI22 on either or both CAM/DISP connectors
  bash install-tevs-native.sh --adapter rpi22 --camera-module tevm
USAGE
}

log() {
  printf '[tevs-installer] %s\n' "$*"
}

fail() {
  printf '[tevs-installer] ERROR: %s\n' "$*" >&2
  exit 1
}

require_non_empty_file() {
  local path="$1"
  local description="$2"

  [ -s "$path" ] || fail "$description is missing or empty: $path"
}

check_non_empty_file() {
  local path="$1"
  local description="$2"

  if [ -s "$path" ]; then
    return 0
  fi

  log "$description is missing or empty: $path"
  return 1
}

run_sudo() {
  if [ "$(id -u)" -eq 0 ]; then
    "$@"
  else
    sudo "$@"
  fi
}

TEVS_CONFIG_BEGIN="# BEGIN TechNexion TEVS native installer"
TEVS_CONFIG_END="# END TechNexion TEVS native installer"

update_config_txt() {
  local config_txt="$1"
  local tmp_config="$TMP_DIR/config.txt.updated"
  local block_file="$TMP_DIR/config.txt.tevs-block"
  local config_line

  run_sudo awk -v begin="$TEVS_CONFIG_BEGIN" -v end="$TEVS_CONFIG_END" '
    function is_technexion_overlay_line(line) {
      return line ~ /^[[:space:]]*dtoverlay[[:space:]]*=[[:space:]]*te(vs|vm)-rpi(15|22)([[:space:]]*(#.*)?|[[:space:]]*,.*)$/
    }

    function is_disabled_technexion_overlay_line(line) {
      return line ~ /^[[:space:]]*# disabled by TEVS native installer:[[:space:]]*/ &&
        is_technexion_overlay_line(substr(line, index(line, "dtoverlay")))
    }

    function print_pending_blank_lines() {
      while (pending_blank_lines > 0) {
        print ""
        pending_blank_lines--
      }
    }

    $0 == begin {
      in_tevs_block = 1
      next
    }

    $0 == end {
      if (in_tevs_block) {
        in_tevs_block = 0
        next
      }
    }

    in_tevs_block {
      next
    }

    /^[[:space:]]*camera_auto_detect[[:space:]]*=/ {
      next
    }

    /^[[:space:]]*# TechNexion (TEVS )?camera[[:space:]]*$/ {
      next
    }

    is_disabled_technexion_overlay_line($0) {
      next
    }

    is_technexion_overlay_line($0) {
      next
    }

    /^[[:space:]]*$/ {
      pending_blank_lines++
      next
    }

    {
      print_pending_blank_lines()
      print
    }

    END {
      print_pending_blank_lines()
    }
  ' "$config_txt" >"$tmp_config"

  {
    printf '\n%s\n' "$TEVS_CONFIG_BEGIN"
    printf '[all]\n'
    printf '# Disable automatic camera detection for TechNexion camera\n'
    printf 'camera_auto_detect=0\n'
    printf '# TechNexion camera\n'
    for config_line in "${OVERLAY_CONFIG_LINES[@]}"; do
      printf '%s\n' "$config_line"
    done
    printf '%s\n' "$TEVS_CONFIG_END"
  } >"$block_file"

  cat "$block_file" >>"$tmp_config"
  run_sudo install -m 644 "$tmp_config" "$config_txt"
}

verify_config_txt() {
  local config_txt="$1"
  local config_line

  grep -Eq '^[[:space:]]*camera_auto_detect[[:space:]]*=[[:space:]]*0([[:space:]]*(#.*)?)?$' "$config_txt" ||
    fail "config.txt verification failed: camera_auto_detect=0 was not written"

  for config_line in "${OVERLAY_CONFIG_LINES[@]}"; do
    grep -Fxq "$config_line" "$config_txt" ||
      fail "config.txt verification failed: active overlay was not written: $config_line"
  done
}

remove_tevs_config_txt() {
  local config_txt="$1"
  local tmp_config="$TMP_DIR/config.txt.cleaned"

  run_sudo awk -v begin="$TEVS_CONFIG_BEGIN" -v end="$TEVS_CONFIG_END" '
    function is_technexion_overlay_line(line) {
      return line ~ /^[[:space:]]*dtoverlay[[:space:]]*=[[:space:]]*te(vs|vm)-rpi(15|22)([[:space:]]*(#.*)?|[[:space:]]*,.*)$/
    }

    function is_disabled_technexion_overlay_line(line) {
      return line ~ /^[[:space:]]*# disabled by TEVS native installer:[[:space:]]*/ &&
        is_technexion_overlay_line(substr(line, index(line, "dtoverlay")))
    }

    function print_pending_blank_lines() {
      while (pending_blank_lines > 0) {
        print ""
        pending_blank_lines--
      }
    }

    $0 == begin {
      in_tevs_block = 1
      next
    }

    $0 == end {
      if (in_tevs_block) {
        in_tevs_block = 0
        next
      }
    }

    in_tevs_block {
      next
    }

    /^[[:space:]]*# TechNexion (TEVS )?camera[[:space:]]*$/ {
      next
    }

    is_disabled_technexion_overlay_line($0) {
      next
    }

    is_technexion_overlay_line($0) {
      next
    }

    /^[[:space:]]*$/ {
      pending_blank_lines++
      next
    }

    {
      print_pending_blank_lines()
      print
    }

    END {
      print_pending_blank_lines()
    }
  ' "$config_txt" >"$tmp_config"

  run_sudo install -m 644 "$tmp_config" "$config_txt"
}

detect_boot_dir() {
  if [ -d /boot/firmware ]; then
    printf '%s\n' /boot/firmware
  else
    printf '%s\n' /boot
  fi
}

clean_previous_install() {
  local boot_dir="$1"
  local config_txt="$boot_dir/config.txt"

  log "Cleaning previous TechNexion camera installer artifacts"

  run_sudo rm -f \
    "/lib/modules/$KVER/extra/tevs.ko" \
    "/lib/modules/$KVER/extra/tevs.ko.xz" \
    "$MODULE_INSTALL_DIR/tevs.ko" \
    "$MODULE_INSTALL_PATH"
  run_sudo rm -f \
    /boot/firmware/overlays/tevs-rpi15.dtbo \
    /boot/firmware/overlays/tevs-rpi22.dtbo \
    /boot/firmware/overlays/tevm-rpi15.dtbo \
    /boot/firmware/overlays/tevm-rpi22.dtbo \
    /boot/overlays/tevs-rpi15.dtbo \
    /boot/overlays/tevs-rpi22.dtbo \
    /boot/overlays/tevm-rpi15.dtbo \
    /boot/overlays/tevm-rpi22.dtbo
  run_sudo rm -f /etc/modprobe.d/tevs.conf

  if [ -f "$config_txt" ]; then
    local backup="$config_txt.tevs-clean-backup.$(date +%Y%m%d-%H%M%S)"
    log "Backing up config.txt before cleanup to $backup"
    run_sudo cp "$config_txt" "$backup"

    log "Removing managed TechNexion config.txt entries"
    remove_tevs_config_txt "$config_txt"
  else
    log "Skipping config.txt cleanup because it was not found: $config_txt"
  fi

  run_sudo depmod -a "$KVER" || true
}

move_source_checkout_aside() {
  local reason="$1"
  local backup="$SOURCE_DIR.tevs-$reason.$(date +%Y%m%d-%H%M%S)"

  while [ -e "$backup" ]; do
    backup="$backup.$RANDOM"
  done

  log "Moving existing source checkout to $backup"
  mv "$SOURCE_DIR" "$backup"
}

clone_source_checkout() {
  local clone_dir="$SOURCE_DIR.clone.$$"

  rm -rf "$clone_dir"
  log "Cloning TechNexion driver source"
  git clone --depth=1 -b "$TN_BRANCH" "$SOURCE_REPO_URL" "$clone_dir"

  validate_source_checkout "$clone_dir" ||
    fail "Fresh source checkout failed validation: $clone_dir"
  mv "$clone_dir" "$SOURCE_DIR"
}

recover_source_checkout() {
  local reason="$1"
  local message="$2"

  log "$message"
  if [ "$DO_CLEAN" -ne 1 ]; then
    fail "$message: $SOURCE_DIR"
  fi

  move_source_checkout_aside "$reason"
  clone_source_checkout
}

validate_source_checkout() {
  local source_dir="$1"
  local tevs_dir="$source_dir/drivers/media/i2c/tevs"
  local overlay_dir="$source_dir/arch/arm64/boot/dts/overlays"

  if ! git -C "$source_dir" rev-parse --verify HEAD >/dev/null 2>&1; then
    log "Source checkout does not have a valid HEAD: $source_dir"
    return 1
  fi

  check_non_empty_file "$tevs_dir/Makefile" "TEVS module Makefile" &&
    check_non_empty_file "$tevs_dir/tevs_main.c" "TEVS module source" &&
    check_non_empty_file "$tevs_dir/tevs_tbls.h" \
      "TEVS module table header" &&
    check_non_empty_file "$overlay_dir/$CAMERA_MODULE-$ADAPTER-overlay.dts" \
      "$CAMERA_MODULE-$ADAPTER overlay source"
}

prepare_source_checkout() {
  if [ ! -d "$SOURCE_DIR/.git" ]; then
    if [ -e "$SOURCE_DIR" ]; then
      recover_source_checkout \
        "non-git-backup" \
        "Source directory exists but is not a git checkout"
      return
    fi

    clone_source_checkout
    return
  fi

  log "Using existing source checkout"

  if ! git -C "$SOURCE_DIR" rev-parse --verify HEAD >/dev/null 2>&1; then
    recover_source_checkout \
      "corrupt-backup" \
      "Existing source checkout is not readable"
    return
  fi

  log "Updating source checkout to $TN_BRANCH"
  if ! git -C "$SOURCE_DIR" fetch --depth=1 origin "$TN_BRANCH"; then
    recover_source_checkout \
      "fetch-failed-backup" \
      "Existing source checkout could not fetch $TN_BRANCH"
    return
  fi

  if [ "$DO_CLEAN" -eq 1 ]; then
    if ! git -C "$SOURCE_DIR" reset --hard FETCH_HEAD; then
      recover_source_checkout \
        "reset-failed-backup" \
        "Existing source checkout could not reset cleanly"
      return
    fi
  fi

  if ! git -C "$SOURCE_DIR" checkout --detach FETCH_HEAD; then
    recover_source_checkout \
      "checkout-failed-backup" \
      "Existing source checkout could not check out $TN_BRANCH"
    return
  fi

  if [ "$DO_CLEAN" -eq 1 ]; then
    if ! git -C "$SOURCE_DIR" clean -fdx; then
      recover_source_checkout \
        "clean-failed-backup" \
        "Existing source checkout could not remove generated files"
      return
    fi
  fi

  if ! validate_source_checkout "$SOURCE_DIR"; then
    recover_source_checkout \
      "invalid-backup" \
      "Existing source checkout failed validation"
    return
  fi
}

ADAPTER=""
CAMERA_MODULE="tevs"
PORT="auto"
SOURCE_DIR=""
DO_APT=1
DO_CONFIG=1
DO_CLEAN=0
DO_REBOOT=0

while [ "$#" -gt 0 ]; do
  case "$1" in
    --adapter)
      [ "$#" -ge 2 ] || fail "--adapter requires a value"
      ADAPTER="$2"
      shift 2
      ;;
    --camera-module|--sensor)
      [ "$#" -ge 2 ] || fail "$1 requires a value"
      CAMERA_MODULE="$2"
      shift 2
      ;;
    --port)
      [ "$#" -ge 2 ] || fail "--port requires a value"
      PORT="$2"
      shift 2
      ;;
    --source-dir)
      [ "$#" -ge 2 ] || fail "--source-dir requires a value"
      SOURCE_DIR="$2"
      shift 2
      ;;
    --no-apt)
      DO_APT=0
      shift
      ;;
    --no-config)
      DO_CONFIG=0
      shift
      ;;
    --clean)
      DO_CLEAN=1
      shift
      ;;
    --reboot)
      DO_REBOOT=1
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      fail "Unknown option: $1"
      ;;
  esac
done

[ -n "$ADAPTER" ] || {
  usage
  fail "--adapter is required"
}

case "$ADAPTER" in
  rpi15|rpi22) ;;
  *) fail "--adapter must be rpi15 or rpi22" ;;
esac

case "$CAMERA_MODULE" in
  tevs|tevm) ;;
  *) fail "--camera-module must be tevs or tevm" ;;
esac

case "$PORT" in
  auto|both|cam0|cam1) ;;
  *) fail "--port must be auto, both, cam0, or cam1" ;;
esac

if [ "$ADAPTER" = "rpi15" ] && [ "$PORT" != "auto" ] && [ "$PORT" != "cam1" ]; then
  fail "--port is only meaningful for rpi22"
fi

KVER="$(uname -r)"
KERNEL_FAMILY="$(printf '%s\n' "$KVER" | cut -d. -f1,2)"

case "$KERNEL_FAMILY" in
  6.1)
    TN_BRANCH="tn_rpi_kernel-6.1"
    ;;
  6.6)
    TN_BRANCH="tn_rpi_kernel-6.6"
    ;;
  6.12)
    TN_BRANCH="tn_rpi_kernel-6.12"
    ;;
  6.18)
    TN_BRANCH="tn_rpi_kernel-6.18"
    ;;
  *)
    fail "Unsupported kernel family: $KERNEL_FAMILY. Supported: 6.1, 6.6, 6.12, 6.18"
    ;;
esac

KBUILD="/lib/modules/$KVER/build"
BOOT_DIR="$(detect_boot_dir)"
CONFIG_TXT="$BOOT_DIR/config.txt"
OVERLAY_INSTALL_DIR="$BOOT_DIR/overlays"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
MODULE_INSTALL_DIR="/lib/modules/$KVER/kernel/drivers/media/i2c"
MODULE_INSTALL_PATH="$MODULE_INSTALL_DIR/tevs.ko.xz"

if [ -z "$SOURCE_DIR" ]; then
  SOURCE_DIR="$TMP_DIR/tn-rpi-camera-driver-$TN_BRANCH"
fi

log "Kernel release: $KVER"
log "Kernel family:  $KERNEL_FAMILY"
log "Driver branch:  $TN_BRANCH"
log "Script version: $SCRIPT_VERSION"
log "Adapter:        $ADAPTER"
log "Camera module:  $CAMERA_MODULE"
if [ "$ADAPTER" = "rpi22" ]; then
  if [ "$PORT" = "auto" ] || [ "$PORT" = "both" ]; then
    log "Camera port:    auto (CAM/DISP 0 and CAM/DISP 1)"
  else
    log "Camera port:    $PORT"
  fi
fi
log "Source dir:     $SOURCE_DIR"

if [ "$DO_CLEAN" -eq 1 ]; then
  clean_previous_install "$BOOT_DIR"
fi

if [ "$DO_APT" -eq 1 ]; then
  log "Updating apt package index"
  run_sudo apt update

  log "Installing build dependencies"
  run_sudo apt install -y git build-essential bc bison flex libssl-dev libelf-dev device-tree-compiler xz-utils

  if [ ! -e "$KBUILD" ]; then
    log "Kernel headers were not found at $KBUILD"
    log "Trying to install matching kernel headers"

    HEADER_CANDIDATES=("linux-headers-$KVER")
    case "$KVER" in
      *rpi-2712*) HEADER_CANDIDATES+=("linux-headers-rpi-2712") ;;
      *rpi-v8*) HEADER_CANDIDATES+=("linux-headers-rpi-v8") ;;
    esac
    HEADER_CANDIDATES+=("raspberrypi-kernel-headers")

    HEADER_INSTALLED=0
    for pkg in "${HEADER_CANDIDATES[@]}"; do
      log "Trying header package: $pkg"
      if run_sudo apt install -y "$pkg"; then
        if [ -e "$KBUILD" ]; then
          HEADER_INSTALLED=1
          break
        fi
      fi
    done

    [ "$HEADER_INSTALLED" -eq 1 ] || fail "Could not install matching headers for $KVER"
  fi
else
  log "Skipping apt steps because --no-apt was provided"
fi

[ -e "$KBUILD" ] || fail "Kernel build directory does not exist: $KBUILD"
command -v xz >/dev/null 2>&1 ||
  fail "xz is required to compress tevs.ko; install xz-utils and run again"

log "Verifying kernel build directory"
KBUILD_RELEASE="$(make -s -C "$KBUILD" kernelrelease)"
log "Kernel build release: $KBUILD_RELEASE"

if [ "$KBUILD_RELEASE" != "$KVER" ]; then
  log "Warning: kernelrelease differs from uname -r"
fi

prepare_source_checkout

MODULE_DIR="$SOURCE_DIR/drivers/media/i2c/tevs"
[ -d "$MODULE_DIR" ] || fail "Module directory not found: $MODULE_DIR"
require_non_empty_file "$MODULE_DIR/Makefile" "TEVS module Makefile"
require_non_empty_file "$MODULE_DIR/tevs_main.c" "TEVS module source"
require_non_empty_file "$MODULE_DIR/tevs_tbls.h" "TEVS module table header"

MODULE_BUILD_DIR="$TMP_DIR/tevs-module"
mkdir -p "$MODULE_BUILD_DIR"
cp -R "$MODULE_DIR"/. "$MODULE_BUILD_DIR"/
cat >"$MODULE_BUILD_DIR/Makefile" <<'EOF'
tevs-objs := tevs_main.o

obj-m += tevs.o
EOF

log "Building tevs.ko as an external kernel module"
make -C "$KBUILD" M="$MODULE_BUILD_DIR" clean
make -C "$KBUILD" M="$MODULE_BUILD_DIR" modules

TEVS_KO="$MODULE_BUILD_DIR/tevs.ko"
require_non_empty_file "$TEVS_KO" "Built TEVS kernel module"

log "Built module metadata"
modinfo -F vermagic "$TEVS_KO" || true

log "Compressing tevs.ko with xz"
TEVS_KO_XZ="$TEVS_KO.xz"
rm -f "$TEVS_KO_XZ"
xz --check=crc32 --lzma2=dict=512KiB "$TEVS_KO"
require_non_empty_file "$TEVS_KO_XZ" "Compressed TEVS kernel module"

OVERLAY_DIR="$SOURCE_DIR/arch/arm64/boot/dts/overlays"
[ -d "$OVERLAY_DIR" ] || fail "Overlay directory not found: $OVERLAY_DIR"

case "$ADAPTER" in
  rpi15)
    OVERLAY_SRC="$OVERLAY_DIR/$CAMERA_MODULE-rpi15-overlay.dts"
    OVERLAY_NAME="$CAMERA_MODULE-rpi15"
    OVERLAY_CONFIG_LINES=("dtoverlay=$CAMERA_MODULE-rpi15,media-controller=0")
    ;;
  rpi22)
    OVERLAY_SRC="$OVERLAY_DIR/$CAMERA_MODULE-rpi22-overlay.dts"
    OVERLAY_NAME="$CAMERA_MODULE-rpi22"
    case "$PORT" in
      cam0)
        OVERLAY_CONFIG_LINES=("dtoverlay=$CAMERA_MODULE-rpi22,cam0")
        ;;
      cam1)
        OVERLAY_CONFIG_LINES=("dtoverlay=$CAMERA_MODULE-rpi22")
        ;;
      auto|both)
        OVERLAY_CONFIG_LINES=(
          "dtoverlay=$CAMERA_MODULE-rpi22"
          "dtoverlay=$CAMERA_MODULE-rpi22,cam0"
        )
        ;;
    esac
    ;;
esac

require_non_empty_file "$OVERLAY_SRC" "Overlay source"

TMP_DTS="$TMP_DIR/$OVERLAY_NAME.dts"
TMP_DTBO="$TMP_DIR/$OVERLAY_NAME.dtbo"
TMP_INCLUDE_DIR="$TMP_DIR/include"
GPIO_BINDING="$KBUILD/include/dt-bindings/gpio/gpio.h"

CPP_INCLUDE_ARGS=(-I "$KBUILD/include")

if [ ! -f "$GPIO_BINDING" ]; then
  log "Kernel headers do not provide dt-bindings/gpio/gpio.h; using local fallback"
  mkdir -p "$TMP_INCLUDE_DIR/dt-bindings/gpio"
  cat >"$TMP_INCLUDE_DIR/dt-bindings/gpio/gpio.h" <<'EOF'
#ifndef _DT_BINDINGS_GPIO_GPIO_H
#define _DT_BINDINGS_GPIO_GPIO_H

#define GPIO_ACTIVE_HIGH 0
#define GPIO_ACTIVE_LOW 1
#define GPIO_OPEN_DRAIN 2
#define GPIO_OPEN_SOURCE 4

#endif
EOF
  CPP_INCLUDE_ARGS=(-I "$TMP_INCLUDE_DIR" -I "$KBUILD/include")
fi

log "Building device-tree overlay: $OVERLAY_NAME.dtbo"
cpp -nostdinc "${CPP_INCLUDE_ARGS[@]}" \
  -undef -x assembler-with-cpp \
  "$OVERLAY_SRC" "$TMP_DTS"

dtc -@ -H epapr -I dts -O dtb -o "$TMP_DTBO" "$TMP_DTS"
require_non_empty_file "$TMP_DTBO" "Built device-tree overlay"

log "Installing tevs.ko.xz"
run_sudo install -D -m 644 "$TEVS_KO_XZ" "$MODULE_INSTALL_PATH"
require_non_empty_file "$MODULE_INSTALL_PATH" \
  "Installed TEVS kernel module"
run_sudo depmod -a "$KVER"

log "Installing overlay to $OVERLAY_INSTALL_DIR"
run_sudo install -d "$OVERLAY_INSTALL_DIR"
run_sudo install -m 644 "$TMP_DTBO" "$OVERLAY_INSTALL_DIR/$OVERLAY_NAME.dtbo"
require_non_empty_file "$OVERLAY_INSTALL_DIR/$OVERLAY_NAME.dtbo" \
  "Installed device-tree overlay"

if [ "$DO_CONFIG" -eq 1 ]; then
  [ -f "$CONFIG_TXT" ] || fail "config.txt not found: $CONFIG_TXT"

  BACKUP="$CONFIG_TXT.tevs-backup.$(date +%Y%m%d-%H%M%S)"
  log "Backing up config.txt to $BACKUP"
  run_sudo cp "$CONFIG_TXT" "$BACKUP"

  log "Replacing TEVS config.txt block"
  update_config_txt "$CONFIG_TXT"
  log "Verifying TEVS config.txt block"
  verify_config_txt "$CONFIG_TXT"
else
  log "Skipping config.txt update because --no-config was provided"
fi

log "Installing module load ordering rule"
printf 'softdep tevs pre: gpio_pca953x\n' | run_sudo tee /etc/modprobe.d/tevs.conf >/dev/null

log "Installation complete"
log "Installed module: $MODULE_INSTALL_PATH"
log "Installed overlay: $OVERLAY_INSTALL_DIR/$OVERLAY_NAME.dtbo"
if [ "$DO_CONFIG" -eq 1 ]; then
  log "Configured overlay:"
  for config_line in "${OVERLAY_CONFIG_LINES[@]}"; do
    log "  $config_line"
  done
fi

if [ "$DO_REBOOT" -eq 1 ]; then
  log "Rebooting"
  run_sudo reboot
else
  log "Reboot is required to apply the overlay"
  log "Run: sudo reboot"
fi
