#!/bin/bash
# omniroute-update: вернуть нативный better-sqlite3 в установку OmniRoute.
#
# Поставщик пакета кладёт в dist-копию better-sqlite3 prebuilds ТОЛЬКО под x64,
# поэтому на Apple Silicon шина не находит
#   <пакет>/dist/node_modules/better-sqlite3/build/Release/better_sqlite3.node
# и уходит на медленный JS-фолбэк. Скомпилировать на месте нельзя (в dist-копии
# нет binding.gyp/src/deps — npm rebuild и node-gyp бесполезны), зато рядом лежит
# полная копия пакета с prebuilds под все платформы: берём оттуда бинарь под
# текущую архитектуру и проверяем реальным запросом к sqlite.
#
# Шаг НЕ переживает обновление: любой npm install -g omniroute затирает
# build/Release. Запускать после каждого обновления (см. SKILL.md).
#
# Пароль sudo НЕ хранится: если каталог установки чужой, положить пароль в
# MINI_SUDO_PASS на время вызова (передаётся только по stdin). См. Decisions D1.
set -uo pipefail

MINI="cebers_mini@100.84.177.25"
SSH="ssh -i $HOME/.ssh/id_ed25519 -p 2222 -o ConnectTimeout=20 $MINI"
TARGETS=""

usage() {
  cat <<EOF
usage: fix_native.sh [mini|local|all]
  цель   mini | local (MacBook) | all (default: all)
Идемпотентно: на здоровой установке ничего не меняет и возвращает 0.
sudo (если каталог установки чужой): env MINI_SUDO_PASS, только по stdin.
EOF
  exit 0
}

while [ $# -gt 0 ]; do
  case "$1" in
    -h|--help) usage;;
    mini|local|all) TARGETS="$1"; shift;;
    *) echo "неизвестный аргумент: $1"; exit 1;;
  esac
done
TARGETS="${TARGETS:-all}"

# Тело выполняется как на mini (через ssh, там zsh), так и локально (bash),
# поэтому внутри — только переносимые конструкции и никаких одинарных кавычек.
BODY='
set -u
IFS= read -r SUDOPASS || SUDOPASS=""
export PATH=/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:$PATH
NODE=$(command -v node || true)
[ -n "$NODE" ] || { echo "node не найден в PATH"; exit 1; }

PLAT=$(uname -s | tr "[:upper:]" "[:lower:]")
case "$(uname -m)" in
  arm64|aarch64) ARCH=arm64;;
  x86_64|amd64)  ARCH=x64;;
  *) echo "неизвестная архитектура $(uname -m) — не знаю, какой prebuild брать"; exit 1;;
esac
KEY="$PLAT-$ARCH.node"

set --
for R in /opt/homebrew/lib/node_modules/omniroute "$HOME/.local/share/omniroute-app/node_modules/omniroute"; do
  if [ -d "$R/dist/node_modules/better-sqlite3" ]; then set -- "$@" "$R"; fi
done
if [ "$#" -eq 0 ]; then echo "установка omniroute не найдена — пропуск"; exit 2; fi

WORK=$(mktemp -d)
CHECK="$WORK/check.cjs"
cat > "$CHECK" <<"NODEEOF"
const dir = process.argv[2];
const Database = require(dir);
const db = new Database(":memory:");
const v = db.prepare("select sqlite_version() as v").get().v;
db.close();
console.log("SQLITE " + v);
NODEEOF

# $1 = каталог dist-копии better-sqlite3. Печатает вывод node (со stderr).
probe() { "$NODE" "$CHECK" "$1" 2>&1; }
sqlite_ver() { printf "%s\n" "$1" | sed -n "s/^SQLITE //p" | head -1; }
# из простыни node-стека оставить содержательные строки
why() {
  W=$(printf "%s\n" "$1" | grep -aE "Error|Cannot find|incompatible|mach-o|no such file" | head -2)
  [ -n "$W" ] || W=$(printf "%s\n" "$1" | head -2)
  printf "%s\n" "$W" | cut -c1-160 | sed "s/^/    /"
}

# $1 = донор, $2 = целевой файл. Сначала без sudo, потом с sudo из MINI_SUDO_PASS.
put() {
  DIR=$(dirname "$2")
  if mkdir -p "$DIR" 2>/dev/null && cp "$1" "$2.new.$$" 2>/dev/null &&
     chmod 755 "$2.new.$$" 2>/dev/null && mv "$2.new.$$" "$2" 2>/dev/null; then
    return 0
  fi
  rm -f "$2.new.$$" 2>/dev/null
  if [ -z "$SUDOPASS" ]; then
    echo "  нет прав на запись в $DIR."
    echo "  Нужен sudo: задайте MINI_SUDO_PASS в окружении вызова (в файлы пароль не писать)."
    return 1
  fi
  if ! printf "%s\n" "$SUDOPASS" | sudo -S -p "" -v 2>/dev/null; then
    echo "  sudo не принял пароль из MINI_SUDO_PASS"
    return 1
  fi
  sudo -n mkdir -p "$DIR" && sudo -n cp "$1" "$2" && sudo -n chmod 755 "$2" && return 0
  echo "  копирование под sudo не удалось"
  return 1
}

RC=0
for R in "$@"; do
  DIST="$R/dist/node_modules/better-sqlite3"
  TGT="$DIST/build/Release/better_sqlite3.node"
  echo "--- $R  (нужен prebuild $KEY)"
  OUT=$(probe "$DIST"); VER=$(sqlite_ver "$OUT")
  if [ -n "$VER" ]; then
    echo "  нативный модуль на месте и загружается, sqlite $VER — менять нечего"
    continue
  fi
  echo "  нативный модуль не загружается:"
  why "$OUT"

  DONOR=""
  for R2 in "$R" "$@"; do
    if [ -z "$DONOR" ]; then
      DONOR=$(find "$R2" -type f -path "*better-sqlite3/prebuilds/*" -name "$KEY" 2>/dev/null | head -1)
    fi
  done
  if [ -z "$DONOR" ]; then
    echo "  prebuild $KEY не найден ни в одной копии better-sqlite3."
    echo "  Собрать на месте нельзя: в dist-копии нет binding.gyp/src/deps, npm rebuild не поможет."
    echo "  Вариант: npm i better-sqlite3 в отдельном каталоге и взять prebuild оттуда."
    RC=1; continue
  fi
  echo "  донор: $DONOR"
  if ! put "$DONOR" "$TGT"; then RC=1; continue; fi

  OUT=$(probe "$DIST"); VER=$(sqlite_ver "$OUT")
  if [ -n "$VER" ]; then
    echo "  подложен и проверен запросом: sqlite $VER"
    echo "  работающий процесс подхватит модуль только после рестарта шины"
  else
    echo "  ПОДЛОЖЕН, НО НЕ ЗАГРУЖАЕТСЯ (не тот arch или битый бинарь):"
    why "$OUT"
    RC=1
  fi
done

rm -rf "$WORK"
exit $RC
'

run_target() { # $1 = mini | local
  local runner="bash -c"
  [ "$1" = "mini" ] && runner="$SSH"
  echo "=== fix_native [$1]"
  printf '%s\n' "${MINI_SUDO_PASS:-}" | $runner "$BODY"
  local rc=$?
  if [ "$rc" = 2 ]; then
    [ "$TARGETS" = "all" ] && { echo "    (цель $1 пропущена)"; return 0; }
    return 1
  fi
  return "$rc"
}

FAIL=0
[ "$TARGETS" != "local" ] && { run_target mini  || FAIL=1; }
[ "$TARGETS" != "mini" ]  && { run_target local || FAIL=1; }

if [ "$FAIL" = 0 ]; then echo "ИТОГ: нативный better-sqlite3 в порядке."; exit 0; fi
echo "ИТОГ: нативный модуль восстановить не удалось (см. выше)."; exit 1
