#!/bin/sh
# Bit Lantern installer — https://bit-lantern.dev
# Resolves the latest preview release from download.bit-lantern.dev, detects
# OS/architecture, downloads the matching artifact, verifies its SHA-256
# checksum (and the OpenPGP signature over SHA256SUMS when gpg is available),
# and installs the complete release behind stable user-level links.
# POSIX sh; Linux and macOS.
# Windows: download the zip by hand — https://bit-lantern.dev/docs#start
#
# Usage:
#   curl -fsSL https://bit-lantern.dev/install.sh -o install.sh && sh install.sh
#
# Overrides:
#   ATLAS_SCOUT_VERSION      exact release version, e.g. 1.0.0-preview.1
#                            (default: whatever preview/latest.json points at)
#   ATLAS_SCOUT_DATA_DIR     managed release root
#                            (default: ${XDG_DATA_HOME:-~/.local/share}/atlas-scout)
#   ATLAS_SCOUT_BIN_DIR      stable executable-link directory
#                            (default: ~/.local/bin)
#   ATLAS_SCOUT_INSTALL_DIR  deprecated alias for ATLAS_SCOUT_BIN_DIR
#   ATLAS_SCOUT_CLAUDE_PLUGIN
#                            ask, install, or skip persistent Claude Code setup
#                            (default: ask)
set -eu

DOWNLOAD_BASE="https://download.bit-lantern.dev"
CHANNEL="preview"
RELEASE_KEY_URL="${DOWNLOAD_BASE}/keys/atlas-scout-release-public-key.asc"
# Primary fingerprint of the Bit Lantern Release OpenPGP key.
RELEASE_KEY_FPR="CA35C410D8C7FE0485046A66704C70AA814F6D2E"

if [ -n "${ATLAS_SCOUT_BIN_DIR:-}" ] &&
   [ -n "${ATLAS_SCOUT_INSTALL_DIR:-}" ] &&
   [ "$ATLAS_SCOUT_BIN_DIR" != "$ATLAS_SCOUT_INSTALL_DIR" ]; then
  printf '%s\n' \
    "install.sh: ATLAS_SCOUT_BIN_DIR and deprecated ATLAS_SCOUT_INSTALL_DIR disagree" >&2
  exit 1
fi

DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
DATA_DIR="${ATLAS_SCOUT_DATA_DIR:-${DATA_HOME}/atlas-scout}"
BIN_DIR="${ATLAS_SCOUT_BIN_DIR:-${ATLAS_SCOUT_INSTALL_DIR:-$HOME/.local/bin}}"
CLAUDE_PLUGIN_MODE="${ATLAS_SCOUT_CLAUDE_PLUGIN:-ask}"
DATA_DIR="${DATA_DIR%/}"
BIN_DIR="${BIN_DIR%/}"

say()  { printf '%s\n' "$*"; }
fail() { printf 'install.sh: %s\n' "$*" >&2; exit 1; }

command -v curl >/dev/null 2>&1 || fail "curl is required"
command -v id >/dev/null 2>&1 || fail "id is required"
for required_command in awk cmp find sort stat sync tar wc
do
  command -v "$required_command" >/dev/null 2>&1 \
    || fail "${required_command} is required"
done

case "$DATA_DIR" in
  /*) : ;;
  *) fail "ATLAS_SCOUT_DATA_DIR/XDG_DATA_HOME must resolve to an absolute path" ;;
esac
case "$BIN_DIR" in
  /*) : ;;
  *) fail "ATLAS_SCOUT_BIN_DIR must be an absolute path" ;;
esac
[ "$DATA_DIR" != "/" ] && [ "$DATA_DIR" != "$HOME" ] \
  || fail "refusing unsafe managed data root: ${DATA_DIR}"
[ "$BIN_DIR" != "/" ] && [ "$BIN_DIR" != "$HOME" ] \
  || fail "refusing unsafe executable-link directory: ${BIN_DIR}"
case "$BIN_DIR" in
  *:*) fail "ATLAS_SCOUT_BIN_DIR cannot contain ':' because it must be a PATH entry" ;;
esac
case "$CLAUDE_PLUGIN_MODE" in
  ask|install|skip) : ;;
  *) fail "ATLAS_SCOUT_CLAUDE_PLUGIN must be ask, install, or skip" ;;
esac
case "$DATA_DIR:$BIN_DIR" in
  *"/./"*|*"/../"*|*"/."|*"/.."|*"//"*)
    fail "managed installation paths must not contain ., .., or empty components" ;;
esac

download_tmpdir=""
stage_dir=""
pending_link=""
pending_file=""
lock_dir=""
lock_held="false"

cleanup() {
  if [ -n "$pending_link" ] && { [ -e "$pending_link" ] || [ -L "$pending_link" ]; }; then
    rm -f "$pending_link"
  fi
  if [ -n "$pending_file" ] && [ -e "$pending_file" ]; then
    rm -f "$pending_file"
  fi
  if [ -n "$stage_dir" ] && [ -d "$stage_dir" ]; then
    rm -rf "$stage_dir"
  fi
  if [ "$lock_held" = "true" ] && [ -n "$lock_dir" ]; then
    rmdir "$lock_dir" 2>/dev/null || true
  fi
  if [ -n "$download_tmpdir" ] && [ -d "$download_tmpdir" ]; then
    rm -rf "$download_tmpdir"
  fi
}

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

# ---- detect platform --------------------------------------------------------
OS="$(uname -s)"
ARCH="$(uname -m)"

case "$OS" in
  Linux)
    case "$ARCH" in
      x86_64|amd64)  target="x86_64-unknown-linux-gnu" ;;
      aarch64|arm64) target="aarch64-unknown-linux-gnu" ;;
      *) fail "unsupported Linux architecture: $ARCH" ;;
    esac ;;
  Darwin)
    case "$ARCH" in
      arm64)
        command -v perl >/dev/null 2>&1 \
          || fail "perl is required for durable installation on macOS"
        target="aarch64-apple-darwin"
        ;;
      *) fail "the preview supports Apple Silicon only (uname -m: $ARCH). \
See https://bit-lantern.dev/docs#preview" ;;
    esac ;;
  MINGW*|MSYS*|CYGWIN*)
    fail "on Windows, download the zip manually and verify it: \
https://bit-lantern.dev/docs#start" ;;
  *) fail "unsupported operating system: $OS (see https://bit-lantern.dev/docs)" ;;
esac

# ---- resolve version --------------------------------------------------------
if [ -z "${ATLAS_SCOUT_VERSION:-}" ]; then
  pointer_url="${DOWNLOAD_BASE}/${CHANNEL}/latest.json"
  pointer="$(curl -fsSL "$pointer_url")" \
    || fail "cannot fetch ${pointer_url} — check your network and try again"
  ATLAS_SCOUT_VERSION="$(printf '%s\n' "$pointer" \
    | grep '"version"' | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' \
    | head -n 1)"
  [ -n "$ATLAS_SCOUT_VERSION" ] || fail "could not read the version from ${pointer_url}"
fi

case "$ATLAS_SCOUT_VERSION" in
  ""|.*|*[!A-Za-z0-9._-]*|*..*)
    fail "release version contains unsafe path characters: ${ATLAS_SCOUT_VERSION}" ;;
esac

release_base="${DOWNLOAD_BASE}/${CHANNEL}/${ATLAS_SCOUT_VERSION}"
artifact="atlas-scout-${ATLAS_SCOUT_VERSION}-${target}.tar.gz"

# ---- release preview information --------------------------------------------
say "Bit Lantern ${ATLAS_SCOUT_VERSION} (${target})"
say "Preview release: SHA-256 verification, plus OpenPGP when gpg is available."
say "Apple/Microsoft publisher signing is not yet available. Details:"
say "https://bit-lantern.dev/docs#preview"
say ""

# ---- download ---------------------------------------------------------------
download_tmpdir="$(mktemp -d)"
cd "$download_tmpdir"

say "Downloading ${release_base}/${artifact}"
curl -fsSL "${release_base}/${artifact}" -o "$artifact" \
  || fail "download failed. If you set ATLAS_SCOUT_VERSION, confirm it names a \
published release (see ${DOWNLOAD_BASE}/${CHANNEL}/latest.json)."
curl -fsSL "${release_base}/SHA256SUMS" -o SHA256SUMS \
  || fail "SHA256SUMS missing for ${ATLAS_SCOUT_VERSION}; refusing to install unverified binaries"

# ---- verify OpenPGP signature over SHA256SUMS (when gpg is available) -------
if command -v gpg >/dev/null 2>&1; then
  curl -fsSL "${release_base}/SHA256SUMS.asc" -o SHA256SUMS.asc \
    || fail "SHA256SUMS.asc missing for ${ATLAS_SCOUT_VERSION}"
  curl -fsSL "$RELEASE_KEY_URL" -o release-key.asc \
    || fail "cannot fetch the release public key"
  GNUPGHOME="${download_tmpdir}/gnupg"
  export GNUPGHOME
  mkdir -m 700 "$GNUPGHOME"
  gpg --batch --quiet --import release-key.asc 2>/dev/null \
    || fail "could not import the release public key"
  gpg --batch --with-colons --list-keys "$RELEASE_KEY_FPR" >/dev/null 2>&1 \
    || fail "downloaded key does not match the pinned fingerprint ${RELEASE_KEY_FPR}"
  gpg_status="$(gpg --batch --status-fd 1 --verify SHA256SUMS.asc SHA256SUMS 2>/dev/null || true)"
  case "$gpg_status" in
    *VALIDSIG*"$RELEASE_KEY_FPR"*) say "OpenPGP signature verified (${RELEASE_KEY_FPR})." ;;
    *) fail "OpenPGP signature verification FAILED for SHA256SUMS — aborting" ;;
  esac
else
  say "NOTE: gpg not found — skipping the OpenPGP signature check."
fi

# ---- verify checksum --------------------------------------------------------
grep "  ${artifact}\$" SHA256SUMS > artifact.sum \
  || fail "${artifact} is not listed in SHA256SUMS — aborting"
if command -v sha256sum >/dev/null 2>&1; then
  sha256sum -c artifact.sum >/dev/null || fail "checksum verification FAILED — aborting"
elif command -v shasum >/dev/null 2>&1; then
  shasum -a 256 -c artifact.sum >/dev/null || fail "checksum verification FAILED — aborting"
else
  fail "need sha256sum or shasum to verify the download"
fi
say "Checksum verified."

# ---- install ----------------------------------------------------------------
srcdir="atlas-scout-${ATLAS_SCOUT_VERSION}-${target}"

validate_command_destination() {
  destination="$1"
  product="$2"
  managed_target="$3"

  if [ -L "$destination" ]; then
    [ "$(readlink "$destination")" = "$managed_target" ] \
      || fail "refusing to replace unrelated command link: ${destination}"
  elif [ -e "$destination" ]; then
    [ -f "$destination" ] && [ -x "$destination" ] \
      || fail "refusing to replace unrelated command path: ${destination}"
    [ "$(path_uid "$destination" 2>/dev/null)" = "$(id -u)" ] \
      || fail "legacy command is not owned by the current user: ${destination}"
    existing_version="$("$destination" --version 2>/dev/null || true)"
    case "$existing_version" in
      "${product} "*) : ;;
      *) fail "refusing to replace unrelated command path: ${destination}" ;;
    esac
  fi
}

atomic_symlink() {
  target_path="$1"
  link_path="$2"
  pending_link="${link_path}.atlas-scout-new.$$"
  [ ! -e "$pending_link" ] && [ ! -L "$pending_link" ] \
    || return 1
  ln -s "$target_path" "$pending_link" \
    || return 1

  case "$OS" in
    Linux)
      mv -Tf "$pending_link" "$link_path" \
        || return 1 ;;
    Darwin)
      mv -fh "$pending_link" "$link_path" \
        || return 1 ;;
    *)
      return 1 ;;
  esac
  pending_link=""
}

validate_managed_dir() {
  managed_dir="$1"
  description="$2"

  [ -d "$managed_dir" ] && [ ! -L "$managed_dir" ] \
    || fail "${description} is not a safe directory: ${managed_dir}"

  case "$OS" in
    Linux)
      managed_uid="$(stat -c '%u' "$managed_dir" 2>/dev/null)" \
        || fail "cannot inspect owner of ${managed_dir}"
      managed_mode="$(stat -c '%a' "$managed_dir" 2>/dev/null)" \
        || fail "cannot inspect permissions of ${managed_dir}"
      ;;
    Darwin)
      managed_uid="$(stat -f '%u' "$managed_dir" 2>/dev/null)" \
        || fail "cannot inspect owner of ${managed_dir}"
      managed_mode="$(stat -f '%Lp' "$managed_dir" 2>/dev/null)" \
        || fail "cannot inspect permissions of ${managed_dir}"
      ;;
  esac

  [ "$managed_uid" = "$(id -u)" ] \
    || fail "${description} is not owned by the current user: ${managed_dir}"
  case "$managed_mode" in
    ""|*[!0-7]*) fail "cannot interpret permissions on ${managed_dir}" ;;
  esac
  [ $(( (managed_mode / 10) % 10 & 2 )) -eq 0 ] &&
    [ $(( managed_mode % 10 & 2 )) -eq 0 ] \
    || fail "${description} must not be group- or world-writable: ${managed_dir}"

  validate_directory_chain "$managed_dir" "$description"
}

path_uid() {
  inspected_path="$1"
  case "$OS" in
    Linux) stat -c '%u' "$inspected_path" ;;
    Darwin) stat -f '%u' "$inspected_path" ;;
  esac
}

path_mode() {
  inspected_path="$1"
  case "$OS" in
    Linux) stat -c '%a' "$inspected_path" ;;
    Darwin) stat -f '%Lp' "$inspected_path" ;;
  esac
}

validate_directory_chain() {
  chain_path="$1"
  chain_description="$2"
  chain_current="/"
  chain_remainder="${chain_path#/}"
  old_ifs="$IFS"
  IFS='/'
  set -f
  # All managed paths were already rejected if they contain empty components.
  # Splitting here is therefore unambiguous even when a component contains spaces.
  set -- $chain_remainder
  set +f
  IFS="$old_ifs"

  for chain_component in "$@"
  do
    if [ "$chain_current" = "/" ]; then
      chain_current="/${chain_component}"
    else
      chain_current="${chain_current}/${chain_component}"
    fi
    [ ! -L "$chain_current" ] && [ -d "$chain_current" ] \
      || fail "${chain_description} component is not a real directory: ${chain_current}"
    chain_uid="$(path_uid "$chain_current" 2>/dev/null)" \
      || fail "cannot inspect owner of ${chain_current}"
    chain_mode="$(path_mode "$chain_current" 2>/dev/null)" \
      || fail "cannot inspect permissions of ${chain_current}"
    case "$chain_mode" in
      ""|*[!0-7]*) fail "cannot interpret permissions on ${chain_current}" ;;
    esac
    chain_group_writable=$(( (chain_mode / 10) % 10 & 2 ))
    chain_world_writable=$(( chain_mode % 10 & 2 ))
    chain_sticky=$(( (chain_mode / 1000) % 10 & 1 ))
    if { [ "$chain_group_writable" -ne 0 ] || [ "$chain_world_writable" -ne 0 ]; } &&
       { [ "$chain_uid" -ne 0 ] || [ "$chain_sticky" -eq 0 ]; }; then
      fail "${chain_description} ancestor is writable by another user: ${chain_current}"
    fi
  done
}

prepare_managed_dir() {
  prepared_path="$1"
  prepared_description="$2"
  nearest_parent="$prepared_path"
  while [ ! -e "$nearest_parent" ] && [ ! -L "$nearest_parent" ]
  do
    parent_candidate="${nearest_parent%/*}"
    [ -n "$parent_candidate" ] || parent_candidate="/"
    [ "$parent_candidate" != "$nearest_parent" ] \
      || fail "cannot find an existing parent for ${prepared_description}: ${prepared_path}"
    nearest_parent="$parent_candidate"
  done
  [ ! -L "$nearest_parent" ] && [ -d "$nearest_parent" ] \
    || fail "${prepared_description} parent is not a real directory: ${nearest_parent}"
  validate_directory_chain "$nearest_parent" "$prepared_description"
  (
    umask 022
    mkdir -p "$prepared_path"
  ) || fail "cannot create ${prepared_description}: ${prepared_path}"
  validate_managed_dir "$prepared_path" "$prepared_description"
}

write_payload_files() {
  printf '%s\n' \
    atlas-scout \
    atlas-scoutd \
    docs/known-limitations.md \
    docs/operations.md \
    integrations/claude-code/README.md \
    integrations/claude-code/atlas-scout/.claude-plugin/plugin.json \
    integrations/claude-code/atlas-scout/.mcp.json \
    integrations/claude-code/atlas-scout/hooks/hooks.json \
    integrations/claude-code/atlas-scout/skills/code-navigation/SKILL.md \
    EULA.md \
    LICENSE \
    PRIVACY.md \
    README.md \
    REFUND_POLICY.md \
    THIRD_PARTY_NOTICES.md
}

write_payload_directories() {
  printf '%s\n' \
    docs \
    integrations \
    integrations/claude-code \
    integrations/claude-code/atlas-scout \
    integrations/claude-code/atlas-scout/.claude-plugin \
    integrations/claude-code/atlas-scout/hooks \
    integrations/claude-code/atlas-scout/skills \
    integrations/claude-code/atlas-scout/skills/code-navigation
}

validate_archive_inventory() {
  archive_path="$1"
  archive_root="$2"
  archive_expected="${download_tmpdir}/archive-expected"
  archive_actual="${download_tmpdir}/archive-actual"
  archive_verbose="${download_tmpdir}/archive-verbose"

  {
    printf '%s/\n' "$archive_root"
    write_payload_directories | while IFS= read -r payload_directory
    do
      printf '%s/%s/\n' "$archive_root" "$payload_directory"
    done
    write_payload_files | while IFS= read -r payload_file
    do
      printf '%s/%s\n' "$archive_root" "$payload_file"
    done
  } | LC_ALL=C sort >"$archive_expected"

  tar -tzf "$archive_path" >"$archive_actual" \
    || fail "cannot read the release archive inventory"
  LC_ALL=C sort "$archive_actual" -o "$archive_actual"
  cmp -s "$archive_expected" "$archive_actual" \
    || fail "release archive inventory does not exactly match the signed release contract"

  tar -tvzf "$archive_path" >"$archive_verbose" \
    || fail "cannot inspect release archive entry types"
  awk '
    {
      kind = substr($1, 1, 1)
      if (kind != "-" && kind != "d") {
        exit 1
      }
    }
  ' "$archive_verbose" \
    || fail "release archive contains a link or unsupported entry type"
}

validate_release_tree() {
  tree="$1"
  tree_files="${download_tmpdir}/tree-files"
  tree_directories="${download_tmpdir}/tree-directories"
  expected_files="${download_tmpdir}/payload-files"
  expected_directories="${download_tmpdir}/payload-directories"

  [ -d "$tree" ] && [ ! -L "$tree" ] \
    || fail "release tree is not a safe directory: ${tree}"
  [ "$(path_uid "$tree" 2>/dev/null)" = "$(id -u)" ] \
    || fail "release tree is not owned by the current user: ${tree}"
  [ "$(path_mode "$tree" 2>/dev/null)" = "755" ] \
    || fail "release tree has an unexpected mode: ${tree}"

  unexpected_type="$(find "$tree" ! -type f ! -type d -print -quit)"
  [ -z "$unexpected_type" ] \
    || fail "release tree contains a link or unsupported member: ${unexpected_type}"

  write_payload_files | LC_ALL=C sort >"$expected_files"
  write_payload_directories | LC_ALL=C sort >"$expected_directories"
  (
    cd "$tree"
    find . -mindepth 1 -type f -print | sed 's#^\./##' | LC_ALL=C sort
  ) >"$tree_files"
  (
    cd "$tree"
    find . -mindepth 1 -type d -print | sed 's#^\./##' | LC_ALL=C sort
  ) >"$tree_directories"
  cmp -s "$expected_files" "$tree_files" \
    || fail "release tree file inventory does not match the signed release contract"
  cmp -s "$expected_directories" "$tree_directories" \
    || fail "release tree directory inventory does not match the signed release contract"

  tree_total_bytes=0
  while IFS= read -r payload_directory
  do
    payload_path="${tree}/${payload_directory}"
    [ "$(path_uid "$payload_path" 2>/dev/null)" = "$(id -u)" ] \
      || fail "release directory is not owned by the current user: ${payload_path}"
    [ "$(path_mode "$payload_path" 2>/dev/null)" = "755" ] \
      || fail "release directory has an unexpected mode: ${payload_path}"
  done <"$expected_directories"

  while IFS= read -r payload_file
  do
    payload_path="${tree}/${payload_file}"
    case "$payload_file" in
      atlas-scout|atlas-scoutd) expected_mode="755" ;;
      *) expected_mode="644" ;;
    esac
    [ "$(path_uid "$payload_path" 2>/dev/null)" = "$(id -u)" ] \
      || fail "release file is not owned by the current user: ${payload_path}"
    [ "$(path_mode "$payload_path" 2>/dev/null)" = "$expected_mode" ] \
      || fail "release file has an unexpected mode: ${payload_path}"
    payload_bytes="$(wc -c <"$payload_path" | tr -d '[:space:]')"
    case "$payload_bytes" in
      ""|*[!0-9]*) fail "cannot determine release file size: ${payload_path}" ;;
    esac
    [ "$payload_bytes" -gt 0 ] && [ "$payload_bytes" -le 134217728 ] \
      || fail "release file size is outside the supported range: ${payload_path}"
    tree_total_bytes=$((tree_total_bytes + payload_bytes))
    [ "$tree_total_bytes" -le 536870912 ] \
      || fail "expanded release exceeds the 512 MiB safety limit"
  done <"$expected_files"
}

smoke_test_release_tree() {
  tree="$1"
  main_version="$("${tree}/atlas-scout" --version 2>/dev/null)" \
    || fail "staged atlas-scout did not pass its version smoke test"
  daemon_version="$("${tree}/atlas-scoutd" --version 2>/dev/null)" \
    || fail "staged atlas-scoutd did not pass its version smoke test"
  [ "$main_version" = "atlas-scout ${ATLAS_SCOUT_VERSION}" ] \
    || fail "staged atlas-scout reported an unexpected version: ${main_version}"
  [ "$daemon_version" = "atlas-scoutd ${ATLAS_SCOUT_VERSION}" ] \
    || fail "staged atlas-scoutd reported an unexpected version: ${daemon_version}"
}

compare_release_trees() {
  verified_tree="$1"
  existing_tree="$2"
  comparison_files="${download_tmpdir}/payload-files"
  write_payload_files | LC_ALL=C sort >"$comparison_files"
  while IFS= read -r payload_file
  do
    cmp -s "${verified_tree}/${payload_file}" "${existing_tree}/${payload_file}" \
      || fail "existing immutable release differs from the verified archive: ${payload_file}"
  done <"$comparison_files"
}

durable_sync_paths() {
  case "$OS" in
    Linux)
      sync "$@" >/dev/null 2>&1
      ;;
    Darwin)
      perl -MIO::Handle -e '
        for my $path (@ARGV) {
          open my $handle, "<", $path or die "$path: $!";
          $handle->sync or die "$path: $!";
        }
      ' "$@" >/dev/null 2>&1
      ;;
  esac
}

durable_sync_tree() {
  sync_tree="$1"
  sync_list="${download_tmpdir}/sync-tree-paths"
  find "$sync_tree" -type f -print >"$sync_list" \
    || return 1
  while IFS= read -r sync_path
  do
    durable_sync_paths "$sync_path" || return 1
  done <"$sync_list"
  find "$sync_tree" -type d -print >"$sync_list" \
    || return 1
  while IFS= read -r sync_path
  do
    durable_sync_paths "$sync_path" || return 1
  done <"$sync_list"
}

restore_regular_file() {
  backup_path="$1"
  destination="$2"
  pending_file="${destination}.atlas-scout-restore.$$"
  cp "$backup_path" "$pending_file" || return 1
  chmod 755 "$pending_file" || return 1
  case "$OS" in
    Linux) mv -Tf "$pending_file" "$destination" || return 1 ;;
    Darwin) mv -fh "$pending_file" "$destination" || return 1 ;;
    *) return 1 ;;
  esac
  pending_file=""
}

restore_command_path() {
  destination="$1"
  prior_state="$2"
  prior_value="$3"
  case "$prior_state" in
    absent)
      rm -f "$destination"
      ;;
    link)
      atomic_symlink "$prior_value" "$destination"
      ;;
    flat)
      restore_regular_file "$prior_value" "$destination"
      ;;
    *)
      return 1
      ;;
  esac
}

validate_archive_inventory "$artifact" "$srcdir"
tar -xzf "$artifact" \
  || fail "cannot extract the verified release archive"
[ -d "$srcdir" ] || fail "archive did not contain the expected release directory"
validate_release_tree "$srcdir"
smoke_test_release_tree "$srcdir"

if [ -L "$DATA_DIR" ]; then
  fail "managed data root must not be a symbolic link: ${DATA_DIR}"
fi
prepare_managed_dir "$DATA_DIR" "managed data root"

releases_dir="${DATA_DIR}/releases"
if [ -L "$releases_dir" ]; then
  fail "managed releases directory must not be a symbolic link: ${releases_dir}"
fi
prepare_managed_dir "$releases_dir" "managed releases directory"

lock_dir="${DATA_DIR}/.install.lock"
(
  umask 077
  mkdir "$lock_dir"
) 2>/dev/null \
  || fail "another Bit Lantern installation is active at ${DATA_DIR}"
chmod 700 "$lock_dir" \
  || fail "cannot secure the Bit Lantern installation lock"
validate_managed_dir "$lock_dir" "installation lock"
lock_held="true"

release_id="${ATLAS_SCOUT_VERSION}-${target}"
release_dir="${releases_dir}/${release_id}"
if [ -e "$release_dir" ] || [ -L "$release_dir" ]; then
  [ -d "$release_dir" ] && [ ! -L "$release_dir" ] \
    || fail "existing release path is not a safe directory: ${release_dir}"
  validate_release_tree "$release_dir"
  compare_release_trees "$srcdir" "$release_dir"
  smoke_test_release_tree "$release_dir"
  say "Verified byte-identical release directory already exists: ${release_dir}"
else
  stage_dir="$(mktemp -d "${releases_dir}/.${release_id}.XXXXXX")" \
    || fail "cannot create a same-filesystem release stage"
  chmod 755 "$stage_dir" \
    || fail "cannot set permissions on the release stage"
  cp -R "${srcdir}/." "$stage_dir/" \
    || fail "cannot stage the complete Bit Lantern release"
  validate_release_tree "$stage_dir"
  compare_release_trees "$srcdir" "$stage_dir"
  smoke_test_release_tree "$stage_dir"
  durable_sync_tree "$stage_dir" &&
    durable_sync_paths "$releases_dir" \
    || fail "cannot synchronize the staged release"
  mv "$stage_dir" "$release_dir" \
    || fail "cannot promote the staged release directory"
  stage_dir=""
  durable_sync_paths "$release_dir" "$releases_dir" \
    || fail "cannot synchronize the promoted release and releases directory"
fi

current_link="${DATA_DIR}/current"
had_current="false"
old_current_target=""
if [ -e "$current_link" ] || [ -L "$current_link" ]; then
  [ -L "$current_link" ] \
    || fail "refusing to replace non-link current path: ${current_link}"
  old_current_target="$(readlink "$current_link")" \
    || fail "cannot read the existing current link"
  case "$old_current_target" in
    releases/*)
      old_release_id="${old_current_target#releases/}"
      case "$old_release_id" in
        ""|*/*|.*|*[!A-Za-z0-9._-]*|*..*)
          fail "existing current link has an unsafe release identity" ;;
      esac
      ;;
    *) fail "existing current link is not managed by Bit Lantern" ;;
  esac
  [ -d "${DATA_DIR}/${old_current_target}" ] &&
    [ ! -L "${DATA_DIR}/${old_current_target}" ] \
    || fail "existing current link does not select a safe release directory"
  validate_managed_dir "${DATA_DIR}/${old_current_target}" "active release directory"
  had_current="true"
fi

[ ! -L "$BIN_DIR" ] \
  || fail "executable-link directory must not be a symbolic link: ${BIN_DIR}"
prepare_managed_dir "$BIN_DIR" "executable-link directory"
main_link="${BIN_DIR}/atlas-scout"
daemon_link="${BIN_DIR}/atlas-scoutd"
main_target="${DATA_DIR}/current/atlas-scout"
daemon_target="${DATA_DIR}/current/atlas-scoutd"
validate_command_destination "$main_link" "atlas-scout" "$main_target"
validate_command_destination "$daemon_link" "atlas-scoutd" "$daemon_target"

# Prepare every managed marketplace path before changing current. Claude Code's
# own copied cache remains a post-promotion host integration.
plugin_source_dir="${release_dir}/integrations/claude-code/atlas-scout"
plugin_dir="${DATA_DIR}/current/integrations/claude-code/atlas-scout"
plugin_manifest="${plugin_source_dir}/.claude-plugin/plugin.json"
plugin_version="$(grep '"version"' "$plugin_manifest" \
  | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' \
  | head -n 1)"
case "$plugin_version" in
  ""|.*|*[!A-Za-z0-9._-]*|*..*)
    fail "Claude Code plugin manifest has an unsafe or missing version" ;;
esac

marketplace_name="atlas-scout-local"
plugin_id="atlas-scout@${marketplace_name}"
marketplace_dir="${DATA_DIR}/claude-code-marketplace"
marketplace_meta_dir="${marketplace_dir}/.claude-plugin"
marketplace_plugins_dir="${marketplace_dir}/plugins"
if [ -L "$marketplace_dir" ]; then
  fail "Claude Code marketplace directory must not be a symbolic link: ${marketplace_dir}"
fi
prepare_managed_dir "$marketplace_dir" "Claude Code marketplace directory"
prepare_managed_dir "$marketplace_meta_dir" "Claude Code marketplace metadata directory"
prepare_managed_dir "$marketplace_plugins_dir" "Claude Code marketplace plugin directory"

marketplace_plugin_link="${marketplace_plugins_dir}/atlas-scout"
if [ -e "$marketplace_plugin_link" ] || [ -L "$marketplace_plugin_link" ]; then
  [ -L "$marketplace_plugin_link" ] \
    || fail "refusing to replace non-link Claude Code marketplace plugin path: ${marketplace_plugin_link}"
fi
atomic_symlink "$plugin_dir" "$marketplace_plugin_link" \
  || fail "cannot prepare the Claude Code marketplace plugin link"

marketplace_manifest="${marketplace_meta_dir}/marketplace.json"
pending_file="${marketplace_manifest}.atlas-scout-new.$$"
(
  umask 022
  cat >"$pending_file" <<EOF
{
  "name": "${marketplace_name}",
  "description": "Local, verified Bit Lantern integration for Claude Code",
  "owner": {
    "name": "ZaguanLabs"
  },
  "plugins": [
    {
      "name": "atlas-scout",
      "source": "./plugins/atlas-scout",
      "description": "Bit Lantern structural code navigation with Claude-aware tool routing",
      "version": "${plugin_version}"
    }
  ]
}
EOF
)
mv -f "$pending_file" "$marketplace_manifest" \
  || fail "cannot promote the Claude Code marketplace catalog"
pending_file=""
durable_sync_paths \
  "$marketplace_manifest" \
  "$marketplace_meta_dir" \
  "$marketplace_plugins_dir" \
  "$marketplace_dir" \
  "$DATA_DIR" \
  || fail "cannot synchronize the Claude Code marketplace"

# Preserve legacy flat executables so a failed selector promotion can restore
# their public paths. Managed links are recorded by target instead.
legacy_backup_dir="${DATA_DIR}/legacy-flat-backup"
main_prior_state="absent"
main_prior_value=""
daemon_prior_state="absent"
daemon_prior_value=""
if [ -L "$main_link" ]; then
  main_prior_state="link"
  main_prior_value="$(readlink "$main_link")"
elif [ -e "$main_link" ]; then
  prepare_managed_dir "$legacy_backup_dir" "legacy executable backup directory"
  cp "$main_link" "${legacy_backup_dir}/atlas-scout" \
    || fail "cannot retain the legacy atlas-scout executable"
  chmod 755 "${legacy_backup_dir}/atlas-scout"
  cmp -s "$main_link" "${legacy_backup_dir}/atlas-scout" \
    || fail "retained atlas-scout backup does not match the legacy executable"
  main_prior_state="flat"
  main_prior_value="${legacy_backup_dir}/atlas-scout"
fi
if [ -L "$daemon_link" ]; then
  daemon_prior_state="link"
  daemon_prior_value="$(readlink "$daemon_link")"
elif [ -e "$daemon_link" ]; then
  prepare_managed_dir "$legacy_backup_dir" "legacy executable backup directory"
  cp "$daemon_link" "${legacy_backup_dir}/atlas-scoutd" \
    || fail "cannot retain the legacy atlas-scoutd executable"
  chmod 755 "${legacy_backup_dir}/atlas-scoutd"
  cmp -s "$daemon_link" "${legacy_backup_dir}/atlas-scoutd" \
    || fail "retained atlas-scoutd backup does not match the legacy executable"
  daemon_prior_state="flat"
  daemon_prior_value="${legacy_backup_dir}/atlas-scoutd"
fi
if [ "$main_prior_state" = "flat" ] || [ "$daemon_prior_state" = "flat" ]; then
  if [ "$main_prior_state" = "flat" ]; then
    durable_sync_paths "${legacy_backup_dir}/atlas-scout" \
      || fail "cannot synchronize the retained atlas-scout executable"
  fi
  if [ "$daemon_prior_state" = "flat" ]; then
    durable_sync_paths "${legacy_backup_dir}/atlas-scoutd" \
      || fail "cannot synchronize the retained atlas-scoutd executable"
  fi
  durable_sync_paths "$legacy_backup_dir" "$DATA_DIR" \
    || fail "cannot synchronize retained legacy executables"
fi

# Stable command links are prepared before the only release-selection change.
if ! atomic_symlink "$main_target" "$main_link" ||
   ! atomic_symlink "$daemon_target" "$daemon_link" ||
   ! durable_sync_paths "$BIN_DIR"; then
  restore_command_path "$main_link" "$main_prior_state" "$main_prior_value" || true
  restore_command_path "$daemon_link" "$daemon_prior_state" "$daemon_prior_value" || true
  durable_sync_paths "$BIN_DIR" || true
  fail "cannot prepare stable Bit Lantern command links"
fi

# The complete release is selected by one pointer. If persistence fails after
# the rename, restore the prior selector and command paths.
if ! atomic_symlink "releases/${release_id}" "$current_link"; then
  restore_command_path "$main_link" "$main_prior_state" "$main_prior_value" || true
  restore_command_path "$daemon_link" "$daemon_prior_state" "$daemon_prior_value" || true
  durable_sync_paths "$BIN_DIR" || true
  fail "cannot atomically promote the Bit Lantern current release"
fi
if ! durable_sync_paths "$DATA_DIR"; then
  if [ "$had_current" = "true" ]; then
    atomic_symlink "$old_current_target" "$current_link" || true
  else
    rm -f "$current_link"
  fi
  restore_command_path "$main_link" "$main_prior_state" "$main_prior_value" || true
  restore_command_path "$daemon_link" "$daemon_prior_state" "$daemon_prior_value" || true
  durable_sync_paths "$DATA_DIR" "$BIN_DIR" || true
  fail "release promotion could not be synchronized; the prior selector was restored"
fi

installed_main_version="$("$main_link" --version 2>/dev/null)" \
  || fail "installed atlas-scout link failed its version smoke test"
installed_daemon_version="$("$daemon_link" --version 2>/dev/null)" \
  || fail "installed atlas-scoutd link failed its version smoke test"
[ "$installed_main_version" = "atlas-scout ${ATLAS_SCOUT_VERSION}" ] \
  || fail "installed atlas-scout link selected the wrong release"
[ "$installed_daemon_version" = "atlas-scoutd ${ATLAS_SCOUT_VERSION}" ] \
  || fail "installed atlas-scoutd link selected the wrong release"

say "Installed complete release: ${release_dir}"
say "Current release pointer: ${current_link}"
say "Bit Lantern command: ${main_link}"
say "Claude Code plugin source: ${plugin_dir}"
say "Claude Code marketplace: ${marketplace_dir}"
say "$installed_main_version"

# Processes started before this install keep executing the previous version
# until they are restarted. This installer never kills them.
if command -v pgrep >/dev/null 2>&1; then
  running="$( { pgrep -x atlas-scout; pgrep -x atlas-scoutd; } 2>/dev/null || true )"
  if [ -n "$running" ]; then
    say ""
    say "NOTE: running atlas-scout/atlas-scoutd processes still use the previous"
    say "version. Restart the atlas-scoutd daemon and start new MCP host sessions"
    say "to pick up ${ATLAS_SCOUT_VERSION}."
  fi
fi

if [ "$OS" = "Darwin" ]; then
  say ""
  say "macOS note: the binary is unsigned. If Gatekeeper ever blocks it, see"
  say "https://bit-lantern.dev/docs#preview — and never remove quarantine"
  say "attributes (xattr -d com.apple.quarantine) to work around a warning."
fi

path_ready="false"
case ":$PATH:" in
  *":${BIN_DIR}:"*) path_ready="true" ;;
  *)
    say ""
    say "NOTE: Bit Lantern is installed, but ${BIN_DIR} is not on PATH."
    say "For this shell:"
    say "  export PATH=\"${BIN_DIR}:\$PATH\""
    say "Do not edit a shell profile without the user's approval."
    ;;
esac

if [ "$path_ready" = "true" ]; then
  resolved_command="$(command -v atlas-scout 2>/dev/null || true)"
  if [ "$resolved_command" != "$main_link" ]; then
    path_ready="false"
    say ""
    say "NOTE: PATH resolves atlas-scout to ${resolved_command:-<nothing>}, not ${main_link}."
    say "An earlier PATH entry is shadowing the managed Bit Lantern command."
  fi
fi

claude_plugin_status="not configured"
claude_plugin_configured="false"
claude_plugin_enabled="false"
claude_mcp_conflict_scope=""
claude_mcp_remove_cmd=""

claude_plugin_field() {
  requested_field="$1"
  awk -v requested="$requested_field" '
    /"id"[[:space:]]*:[[:space:]]*"atlas-scout@atlas-scout-local"/ {
      found = 1
    }
    found && index($0, "\"" requested "\"") {
      value = $0
      prefix = "^.*\"" requested "\"[[:space:]]*:[[:space:]]*"
      sub(prefix, "", value)
      sub(/,[[:space:]]*$/, "", value)
      sub(/^"/, "", value)
      sub(/"$/, "", value)
      print value
      exit
    }
  '
}

configure_claude_plugin() {
  if ! claude plugin validate "$marketplace_dir" >/dev/null 2>&1; then
    say "WARNING: Claude Code rejected the Bit Lantern marketplace validation."
    return 1
  fi

  known_marketplaces="$(claude plugin marketplace list --json 2>/dev/null || true)"
  if printf '%s\n' "$known_marketplaces" \
    | grep -q '"name"[[:space:]]*:[[:space:]]*"atlas-scout-local"'; then
    known_path="$(printf '%s\n' "$known_marketplaces" | awk '
      /"name"[[:space:]]*:[[:space:]]*"atlas-scout-local"/ { found = 1 }
      found && /"path"[[:space:]]*:/ {
        value = $0
        sub(/^.*"path"[[:space:]]*:[[:space:]]*"/, "", value)
        sub(/".*$/, "", value)
        print value
        exit
      }
      found && /^[[:space:]]*}/ { exit }
    ')"
    if [ "$known_path" != "$marketplace_dir" ]; then
      say "WARNING: Claude Code already has ${marketplace_name} from another source."
      say "Expected ${marketplace_dir}; found ${known_path:-an unrecognized source}."
      return 1
    fi
    if ! claude plugin marketplace update "$marketplace_name" >/dev/null 2>&1; then
      say "WARNING: Claude Code could not refresh ${marketplace_name}."
      return 1
    fi
  else
    if ! claude plugin marketplace add "$marketplace_dir" --scope user >/dev/null 2>&1; then
      say "WARNING: Claude Code could not add the Bit Lantern marketplace."
      return 1
    fi
  fi

  installed_plugins="$(claude plugin list --json 2>/dev/null || true)"
  if printf '%s\n' "$installed_plugins" \
    | grep -q '"id"[[:space:]]*:[[:space:]]*"atlas-scout@atlas-scout-local"'; then
    if ! claude plugin update "$plugin_id" --scope user >/dev/null 2>&1; then
      say "WARNING: Claude Code could not update ${plugin_id}."
      return 1
    fi
    claude_plugin_action="updated"
  else
    if ! claude plugin install "$plugin_id" --scope user >/dev/null 2>&1; then
      say "WARNING: Claude Code could not install ${plugin_id}."
      return 1
    fi
    claude_plugin_action="installed"
  fi

  installed_plugins="$(claude plugin list --json 2>/dev/null || true)"
  if ! printf '%s\n' "$installed_plugins" \
    | grep -q '"id"[[:space:]]*:[[:space:]]*"atlas-scout@atlas-scout-local"'; then
    say "WARNING: Claude Code did not report ${plugin_id} after setup."
    return 1
  fi
  reported_plugin_version="$(printf '%s\n' "$installed_plugins" | claude_plugin_field version)"
  reported_plugin_scope="$(printf '%s\n' "$installed_plugins" | claude_plugin_field scope)"
  reported_plugin_enabled="$(printf '%s\n' "$installed_plugins" | claude_plugin_field enabled)"
  reported_plugin_path="$(printf '%s\n' "$installed_plugins" | claude_plugin_field installPath)"
  [ "$reported_plugin_version" = "$plugin_version" ] \
    || {
      say "WARNING: Claude Code cached plugin version ${reported_plugin_version:-<missing>}, expected ${plugin_version}."
      return 1
    }
  [ "$reported_plugin_scope" = "user" ] \
    || {
      say "WARNING: Claude Code reported unexpected plugin scope ${reported_plugin_scope:-<missing>}."
      return 1
    }
  [ -n "$reported_plugin_path" ] && [ -d "$reported_plugin_path" ] \
    || {
      say "WARNING: Claude Code reported an invalid plugin cache path."
      return 1
    }
  for plugin_relative in \
    .claude-plugin/plugin.json \
    .mcp.json \
    hooks/hooks.json \
    skills/code-navigation/SKILL.md
  do
    [ -f "${reported_plugin_path}/${plugin_relative}" ] &&
      [ ! -L "${reported_plugin_path}/${plugin_relative}" ] &&
      cmp -s \
        "${plugin_source_dir}/${plugin_relative}" \
        "${reported_plugin_path}/${plugin_relative}" \
      || {
        say "WARNING: Claude Code's cached plugin does not match the active verified release (${plugin_relative})."
        return 1
      }
  done
  case "$reported_plugin_enabled" in
    true)
      # A manual `claude mcp add atlas-scout` registration alongside the
      # enabled plugin registers every tool twice. The bare name never matches
      # the plugin's own server (plugin:atlas-scout:atlas-scout), so a zero
      # exit here always means a manual registration exists.
      if manual_mcp_details="$(claude mcp get atlas-scout 2>/dev/null)"; then
        claude_mcp_conflict_scope="$(printf '%s\n' "$manual_mcp_details" | awk '
          /^[[:space:]]*Scope[[:space:]]*:/ {
            sub(/^[[:space:]]*Scope[[:space:]]*:[[:space:]]*/, "")
            print
            exit
          }')"
        [ -n "$claude_mcp_conflict_scope" ] || claude_mcp_conflict_scope="unknown"
        conflict_scope_word="$(printf '%s\n' "$claude_mcp_conflict_scope" | awk '{ print tolower($1) }')"
        claude_mcp_remove_cmd="claude mcp remove atlas-scout"
        case "$conflict_scope_word" in
          local|project|user)
            claude_mcp_remove_cmd="claude mcp remove atlas-scout --scope ${conflict_scope_word}"
            ;;
        esac
        say "WARNING: Claude Code also has a manual atlas-scout MCP registration."
        say "Reported scope: ${claude_mcp_conflict_scope}"
        say "The enabled plugin ships its own MCP server, so the manual registration"
        say "duplicates every Bit Lantern tool. Remove it, then rerun this installer:"
        say "  ${claude_mcp_remove_cmd}"
        claude_plugin_status="${claude_plugin_action} at user scope (${plugin_version}); unhealthy until the duplicate manual MCP registration (${claude_mcp_conflict_scope}) is removed"
        claude_plugin_configured="true"
        return 1
      fi
      claude_plugin_status="${claude_plugin_action} at user scope (enabled, ${plugin_version})"
      claude_plugin_enabled="true"
      ;;
    false)
      claude_plugin_status="${claude_plugin_action} at user scope (disabled, ${plugin_version})"
      say "NOTE: Bit Lantern is installed in Claude Code but currently disabled."
      ;;
    *)
      say "WARNING: Claude Code reported an unknown enabled state for ${plugin_id}."
      return 1
      ;;
  esac
  claude_plugin_configured="true"
  return 0
}

if command -v claude >/dev/null 2>&1; then
  if [ "$CLAUDE_PLUGIN_MODE" = "ask" ]; then
    existing_claude_plugins="$(claude plugin list --json 2>/dev/null || true)"
    if printf '%s\n' "$existing_claude_plugins" \
      | grep -q '"id"[[:space:]]*:[[:space:]]*"atlas-scout@atlas-scout-local"'; then
      CLAUDE_PLUGIN_MODE="install"
      say ""
      say "Refreshing the existing Bit Lantern Claude Code plugin."
    fi
  fi

  if [ "$CLAUDE_PLUGIN_MODE" = "ask" ]; then
    if [ -t 0 ]; then
      say ""
      printf 'Install the Bit Lantern plugin persistently in Claude Code for this user? [Y/n] '
      read -r claude_answer
      case "$claude_answer" in
        ""|y|Y|yes|YES) CLAUDE_PLUGIN_MODE="install" ;;
        *) CLAUDE_PLUGIN_MODE="skip" ;;
      esac
    else
      say ""
      say "Claude Code detected. Plugin setup was not selected for this non-interactive run."
      say "For requested Claude setup, rerun with ATLAS_SCOUT_CLAUDE_PLUGIN=install."
    fi
  fi

  if [ "$CLAUDE_PLUGIN_MODE" = "install" ]; then
    say ""
    say "Installing ${plugin_id} persistently in Claude Code..."
    configure_claude_plugin || true
  elif [ "$CLAUDE_PLUGIN_MODE" = "skip" ]; then
    say ""
    say "Claude Code plugin setup skipped."
  fi
else
  say ""
  say "Claude Code is not installed or not on PATH; plugin setup was skipped."
fi

say "Claude Code plugin status: ${claude_plugin_status}"

say ""
say "Next steps:"
if [ "$path_ready" = "true" ]; then
  say "  atlas-scout index /absolute/path/to/project"
  say "  atlas-scout doctor --workspace /absolute/path/to/project"
  if [ "$claude_plugin_configured" = "true" ]; then
    if [ -n "$claude_mcp_remove_cmd" ]; then
      say "  ${claude_mcp_remove_cmd}"
      say "  Rerun this installer to confirm the Claude Code integration is healthy."
    elif [ "$claude_plugin_enabled" = "true" ]; then
      say "  Start a new Claude Code session (or run /reload-plugins --force in another session)."
    else
      say "  Claude Code has the plugin disabled; enable ${plugin_id} when desired."
    fi
  else
    say "  claude plugin marketplace add \"${marketplace_dir}\" --scope user"
    say "  claude plugin install \"${plugin_id}\" --scope user"
  fi
else
  say "  \"${main_link}\" index /absolute/path/to/project"
  say "  \"${main_link}\" doctor --workspace /absolute/path/to/project"
  say "  export PATH=\"${BIN_DIR}:\$PATH\""
  if [ "$claude_plugin_configured" = "true" ]; then
    if [ -n "$claude_mcp_remove_cmd" ]; then
      say "  ${claude_mcp_remove_cmd}"
      say "  Rerun this installer to confirm the Claude Code integration is healthy."
    elif [ "$claude_plugin_enabled" = "true" ]; then
      say "  Start a new Claude Code session (or run /reload-plugins --force in another session)."
    else
      say "  Claude Code has the plugin disabled; enable ${plugin_id} when desired."
    fi
  else
    say "  claude plugin marketplace add \"${marketplace_dir}\" --scope user"
    say "  claude plugin install \"${plugin_id}\" --scope user"
  fi
fi
say "Agent setup guide: https://bit-lantern.dev/bitlantern.md"
