#!/usr/bin/env bash
# Web / Status tip apply helpers (phases). Mirrors update.sh USB Enter prompts
# via operator confirm APIs — never SKIP_DRIVER silently when USB is required.
#
#   PHASE=plan|inject|usb_unplug|usb_replug|finish bash web_tip_apply.sh
#
# Exit codes:
#   0  — phase ok
#   10 — plan: driver USB cycle required (inject may still be needed)
#   11 — plan: inject rebuild needed, no USB
#   12 — plan: nothing to rebuild
#   non-zero — failure

if grep -q $'\r' "${BASH_SOURCE[0]}" 2>/dev/null; then
  sed -i 's/\r$//' "${BASH_SOURCE[0]}"
  exec bash "${BASH_SOURCE[0]}" "$@"
fi

set -euo pipefail

if [[ "$(id -un)" == root ]]; then
  echo "FATAL: do not run as root/sudo." >&2
  exit 1
fi

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=ethergold_paths.sh
source "${ROOT}/ethergold_paths.sh"

PHASE="${PHASE:-plan}"
DEV="${ETHERGOLD_DEV}"
INJECT="${DEV}/inject-shim"
MOD_NAME="xr_usb_serial_common"
WANT_DRIVER="${WANT_DRIVER:-}"

_log() { echo "web_tip_apply[${PHASE}]: $*"; }

_tty_ok() {
  ls /dev/ttyXRUSB* >/dev/null 2>&1
}

_driver_loaded_ver() {
  if [[ -r "/sys/module/${MOD_NAME}/version" ]]; then
    tr -d '\n' < "/sys/module/${MOD_NAME}/version"
    return 0
  fi
  echo "none"
}

_stop_uart_users() {
  pkill -f pog422_uartd 2>/dev/null || true
  pkill -f pog422_fixture_bridge 2>/dev/null || true
  pkill -f pog422_fixture_listen 2>/dev/null || true
  pkill -f pog422_bridge.py 2>/dev/null || true
  sleep 0.2
}

_abi_from_core() {
  python3 -c "
from pathlib import Path
import re
p = Path(r'''${DEV}/pog422_core.py''')
t = p.read_text(encoding='utf-8', errors='replace') if p.is_file() else ''
m = re.search(r'^UARTD_ABI_VERSION\s*=\s*\"([^\"]+)\"', t, re.M)
print(m.group(1) if m else '')
"
}

_binary_ver() {
  local bin="$1"
  if [[ ! -x "${bin}" ]]; then
    echo ""
    return 0
  fi
  strings "${bin}" 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1 || true
}

_inject_needs() {
  local abi uartd so
  abi="$(_abi_from_core)"
  uartd="${INJECT}/pog422_uartd"
  so="${INJECT}/libpog422_inject.so"
  if [[ -z "${abi}" ]]; then
    echo "missing UARTD_ABI in tip core"
    return 0
  fi
  if [[ ! -x "${uartd}" || ! -f "${so}" ]]; then
    echo "missing uartd/so"
    return 0
  fi
  local bv sv
  bv="$(_binary_ver "${uartd}")"
  sv="$(_binary_ver "${so}")"
  if [[ "${bv}" != "${abi}" ]]; then
    echo "uartd ${bv:-?} != ABI ${abi}"
    return 0
  fi
  if [[ "${sv}" != "${abi}" ]]; then
    echo "so ${sv:-?} != ABI ${abi}"
    return 0
  fi
  # source newer than binary
  local src
  for src in "${INJECT}/pog422_uartd.c" "${INJECT}/pog422_inject.c" "${INJECT}/pog422_ipc.h"; do
    if [[ -f "${src}" && "${src}" -nt "${uartd}" ]]; then
      echo "source newer: $(basename "${src}")"
      return 0
    fi
  done
  return 1
}

_driver_needs_usb() {
  # Reuse update.sh dry-run plan when available.
  if [[ -x "${ROOT}/update.sh" || -f "${ROOT}/update.sh" ]]; then
    local out
    out="$(cd "${ROOT}" && DRY_RUN=1 SKIP_PULL=1 ETHERGOLD_DEV="${DEV}" bash update.sh 2>&1 || true)"
    if echo "${out}" | grep -q 'plan: driver_install=1'; then
      return 0
    fi
  fi
  return 1
}

_write_apply_log() {
  mkdir -p "${ETHERGOLD_ROOT}/run"
  {
    echo "phase=${PHASE}"
    echo "at=$(date -Is)"
    echo "tip=$(_abi_from_core)"
    echo "uartd=$(_binary_ver "${INJECT}/pog422_uartd")"
    echo "tty=$(_tty_ok && echo yes || echo no)"
    echo "driver_loaded=$(_driver_loaded_ver)"
  } >> "${ETHERGOLD_ROOT}/run/last-apply.txt"
}

case "${PHASE}" in
  plan)
    NEED_INJECT=0
    NEED_USB=0
    INJECT_REASON=""
    if INJECT_REASON="$(_inject_needs)"; then
      NEED_INJECT=1
    fi
    if _driver_needs_usb; then
      NEED_USB=1
    fi
    # Prefer source VERSION under tip xr-usb tree when present.
    WANT_DRV=""
    if [[ -f "${DEV}/xr-usb-serial-pog422/VERSION" ]]; then
      WANT_DRV="$(tr -d '\r\n' < "${DEV}/xr-usb-serial-pog422/VERSION" || true)"
    fi
    echo "need_inject=${NEED_INJECT}"
    echo "need_usb=${NEED_USB}"
    echo "inject_reason=${INJECT_REASON}"
    echo "abi=$(_abi_from_core)"
    echo "uartd=$(_binary_ver "${INJECT}/pog422_uartd")"
    echo "tty=$(_tty_ok && echo 1 || echo 0)"
    echo "driver_loaded=$(_driver_loaded_ver)"
    echo "want_driver=${WANT_DRV}"
    _write_apply_log
    if [[ "${NEED_USB}" == "1" ]]; then
      exit 10
    fi
    if [[ "${NEED_INJECT}" == "1" ]]; then
      exit 11
    fi
    exit 12
    ;;
  inject)
    _log "rebuild inject/uartd under ${INJECT}"
    if [[ ! -d "${INJECT}" ]]; then
      echo "FATAL: missing ${INJECT}" >&2
      exit 1
    fi
    (
      cd "${INJECT}"
      make clean
      make -j"$(nproc)"
    )
    abi="$(_abi_from_core)"
    bv="$(_binary_ver "${INJECT}/pog422_uartd")"
    sv="$(_binary_ver "${INJECT}/libpog422_inject.so")"
    if [[ -n "${abi}" && "${bv}" != "${abi}" ]]; then
      echo "FATAL: uartd ${bv} != ABI ${abi}" >&2
      exit 1
    fi
    if [[ -n "${abi}" && "${sv}" != "${abi}" ]]; then
      echo "FATAL: so ${sv} != ABI ${abi}" >&2
      exit 1
    fi
    _log "ok inject ${bv}"
    _write_apply_log
    ;;
  usb_unplug)
    _log "operator confirmed unplug — unload ${MOD_NAME}"
    _stop_uart_users
    sudo modprobe -r "${MOD_NAME}" 2>/dev/null || true
    sleep 0.3
    if lsmod | grep -q "^${MOD_NAME}"; then
      echo "FATAL: ${MOD_NAME} still loaded after unplug confirm" >&2
      exit 1
    fi
    _log "ok unloaded — keep cable disconnected until replug confirm"
    _write_apply_log
    ;;
  usb_replug)
    _log "operator confirmed replug — load + wait tty"
    if ! lsmod | grep -q "^${MOD_NAME}"; then
      sudo modprobe "${MOD_NAME}"
    fi
    if [[ -x "${DEV}/xr-usb-serial-pog422/pog422_rebind_xr21.sh" ]]; then
      sudo bash "${DEV}/xr-usb-serial-pog422/pog422_rebind_xr21.sh" 2>/dev/null || true
    elif [[ -x /usr/local/sbin/pog422_rebind_xr21.sh ]]; then
      sudo /usr/local/sbin/pog422_rebind_xr21.sh 2>/dev/null || true
    fi
    for _i in $(seq 1 40); do
      _tty_ok && break
      sleep 0.25
    done
    if ! _tty_ok; then
      echo "FATAL: /dev/ttyXRUSB* still missing after replug confirm" >&2
      exit 1
    fi
    loaded="$(_driver_loaded_ver)"
    if [[ -n "${WANT_DRIVER}" && "${loaded}" != "${WANT_DRIVER}" ]]; then
      echo "WARN: loaded=${loaded} want=${WANT_DRIVER} (continuing; install_pi may still be needed)"
    fi
    _log "ok tty present loaded=${loaded}"
    _write_apply_log
    ;;
  finish)
    _log "finish tip apply — ensure HTTP tip WD"
    if [[ -f "${DEV}/install_ethergold_http.sh" ]]; then
      bash "${DEV}/install_ethergold_http.sh" 2>/dev/null || true
    fi
    # Never restart HTTP mid web Status request — that kills the handler before
    # apply-job.json can leave await_replug (modal loops forever).
    if [[ "${WEB_APPLY:-0}" == "1" || "${WEB_TIP_APPLY_NO_HTTP_RESTART:-0}" == "1" ]]; then
      _log "skip HTTP restart (WEB_APPLY) — operator can refresh Status"
    elif [[ -f "${ROOT}/ota_lib.sh" ]]; then
      # shellcheck source=ota_lib.sh
      source "${ROOT}/ota_lib.sh"
      ota_restart_ethergold_http 2>/dev/null || true
    fi
    {
      echo "finished_at=$(date -Is)"
      echo "abi=$(_abi_from_core)"
      echo "uartd=$(_binary_ver "${INJECT}/pog422_uartd")"
    } >> "${ETHERGOLD_ROOT}/run/last-apply.txt"
    _log "ok finish"
    ;;
  *)
    echo "FATAL: unknown PHASE=${PHASE}" >&2
    exit 2
    ;;
esac
