#!/usr/bin/env bash
# SessionStart hook for ha-nova plugin
# Injects the compact HA NOVA index and session bootstrap into every Claude
# Code session; the full context skill loads on demand.

set -euo pipefail
HOME="${HOME:-}"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"

# Inject only the compact session index plus the first-use bootstrap; the full
# context skill (skills/ha-nova/SKILL.md) stays the single source of truth and
# loads on demand as `ha-nova:ha-nova`. Claude receives this text from
# arbitrary project directories, so inject the shared files themselves.
for required in skills/ha-nova/SKILL.md skills/ha-nova/index.md skills/ha-nova/session-bootstrap.md; do
  [[ -f "${PLUGIN_ROOT}/${required}" ]] || {
    echo "Missing HA NOVA file: ${required}" >&2
    exit 1
  }
done
context_skill_content="$(cat "${PLUGIN_ROOT}/skills/ha-nova/index.md")"$'\n\n'"$(cat "${PLUGIN_ROOT}/skills/ha-nova/session-bootstrap.md")"

# Escape string for JSON embedding
escape_for_json() {
    local s="$1"
    s="${s//\\/\\\\}"
    s="${s//\"/\\\"}"
    s="${s//$'\n'/\\n}"
    s="${s//$'\r'/\\r}"
    s="${s//$'\t'/\\t}"
    s="${s//$'\b'/\\b}"
    s="${s//$'\f'/\\f}"
    # Every remaining C0 control character (JSON forbids them unescaped).
    local code hex char rep
    for code in 1 2 3 4 5 6 7 11 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31; do
      printf -v hex '%02x' "$code"
      printf -v char "\\x${hex}"
      rep="\\u00${hex}"
      s="${s//$char/$rep}"
    done
    printf '%s' "$s"
}

session_context=$(escape_for_json "$context_skill_content")

# ---------------------------------------------------------------------------
# Relay version check
# ---------------------------------------------------------------------------

# Compares X.Y.Z only: a "-rcN" or "+build" suffix is cut first (under set -u,
# "0-rc4" in arithmetic names an unset variable and aborts the hook).
semver_lt() {
  # shellcheck disable=SC2206
  local IFS='.'; local -a a=(${1%%[-+]*}) b=(${2%%[-+]*})
  for i in 0 1 2; do
    local ai="${a[$i]:-0}" bi="${b[$i]:-0}"
    (( ai < bi )) && return 0; (( ai > bi )) && return 1
  done
  return 1
}

extract_json_string() {
  local key="$1" json="$2"
  local match
  match=$(echo "$json" | grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" 2>/dev/null | head -1) || true
  [[ -z "$match" ]] && return 0
  echo "$match" | sed 's/.*"\([^"]*\)"$/\1/'
}

# The installed CLI. GUI-launched clients may start without ~/.local/bin on
# PATH, so fall back to the installer's fixed locations (Unix symlink, Windows
# install root under Git Bash).
ha_nova_bin="$(command -v ha-nova 2>/dev/null || true)"
path_notice=""
if [[ -z "$ha_nova_bin" ]]; then
  for candidate in "${HOME}/.local/bin/ha-nova" ${LOCALAPPDATA:+"${LOCALAPPDATA}/Programs/ha-nova/ha-nova.exe"}; do
    if [[ -f "$candidate" && -x "$candidate" ]]; then
      ha_nova_bin="$candidate"
      path_notice="[ha-nova] (Home Assistant tasks only) ha-nova is not on PATH here. Run it by its full path, in quotes: \\\"$(escape_for_json "$candidate")\\\"\\n"
      break
    fi
  done
fi

version_warning=""
version_file="${PLUGIN_ROOT}/version.json"
if [[ -f "$version_file" ]]; then
  version_json=$(cat "$version_file")
  SKILL_VERSION=$(extract_json_string "skill_version" "$version_json")
  MIN_RELAY_VERSION=$(extract_json_string "min_relay_version" "$version_json")

  # --- Relay version check ---
  if [[ -n "$ha_nova_bin" ]]; then
    # `relay health` accepts curl-compatible --connect-timeout/--max-time
    # (seconds); keep this probe tight so a dead relay never blocks session
    # start: a refused port fails at once, a silent host after 0.5 s.
    health_json="$("$ha_nova_bin" relay health --connect-timeout 0.5 --max-time 2 2>/dev/null || true)"
    if [[ -z "$health_json" ]]; then
      version_warning="[ha-nova] WARNING (Home Assistant tasks only): Relay unreachable. Check relay status or run: ha-nova doctor\\n"
    elif [[ -n "$MIN_RELAY_VERSION" ]]; then
      relay_version=$(extract_json_string "version" "$health_json")
      if [[ -n "$relay_version" && "$relay_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && semver_lt "$relay_version" "$MIN_RELAY_VERSION"; then
        version_warning="[ha-nova] WARNING (Home Assistant tasks only): Relay version ${relay_version} is below minimum ${MIN_RELAY_VERSION}. Update: HA Settings > Apps > NOVA Relay > Update\\n"
      fi
    fi
  fi
  # No relay CLI = not onboarded yet, skip silently

  # --- Release cache refresh ---
  # No update notice here: the first Home Assistant task's
  # `ha-nova check-update --quiet` (session-bootstrap.md) and every typed
  # verb's notices[] say it once, in the CLI's plain words, together with a
  # NOVA App update — and never in sessions with no Home Assistant task.
  # This only keeps the CLI's release cache warm, so that check stays fast.
  # The CLI's cache dir (cli/paths.go): %LOCALAPPDATA%\ha-nova\cache on Windows.
  update_cache_dir="${HOME}/.cache/ha-nova"
  [[ -n "${LOCALAPPDATA:-}" ]] && update_cache_dir="${LOCALAPPDATA}/ha-nova/cache"
  update_cache_file="${update_cache_dir}/latest-release.json"
  # <= the CLI's updateCacheTTLSeconds (cli/paths.go).
  update_ttl=3600

  needs_refresh="1"
  if [[ -f "$update_cache_file" ]]; then
    if [[ "$(uname)" == "Darwin" ]]; then
      cache_mtime=$(stat -f %m "$update_cache_file" 2>/dev/null || echo 0)
    else
      cache_mtime=$(stat -c %Y "$update_cache_file" 2>/dev/null || echo 0)
    fi
    now=$(date +%s)
    if (( now - cache_mtime < update_ttl )); then
      needs_refresh="0"
    fi
  fi

  # Background refresh: fire-and-forget through the shared CLI contract.
  if [[ "$needs_refresh" == "1" && -n "$ha_nova_bin" ]]; then
    mkdir -p "$update_cache_dir" 2>/dev/null || true
    # Redirect the whole subshell: an inherited stdout keeps Claude Code's
    # pipe open, so the session would wait for the background job.
    ("$ha_nova_bin" check-update --quiet --json || true) </dev/null >/dev/null 2>&1 &
    disown 2>/dev/null || true
  fi
fi

# `ha-nova try`: the shared demo house (demo.json beside config.json). With
# only the demo connected, the unselected Relay probe above failed on purpose.
demo_notice=""
demo_dir="${HA_NOVA_CONFIG_DIR:-${HOME}/.config/ha-nova}"
if [[ -z "${HA_NOVA_CONFIG_DIR:-}" && -n "${APPDATA:-}" ]]; then
  demo_dir="${APPDATA}/ha-nova"
fi
if [[ -f "${demo_dir}/demo.json" ]]; then
  demo_notice="[ha-nova] The demo house is connected (ha-nova try): a shared demo home, never the user's. Use --server demo only for requests about the demo house.\\n"
  [[ -f "${demo_dir}/config.json" ]] || version_warning=""
fi

# Background auto-repair: silently reattach drifted clients (e.g., a Claude
# Code update wiped the marketplace registration). Fire-and-forget so this
# session starts immediately; the repair lands in time for the next session.
# Only on a fresh start: stdin carries the hook input JSON, and only
# "source": "startup" runs it (never resume, clear, compact, or no input).
hook_input=""
[[ -t 0 ]] || IFS= read -r -t 2 -d '' hook_input || true
if [[ -n "$ha_nova_bin" ]] && grep -Eq '"source"[[:space:]]*:[[:space:]]*"startup"' <<<"$hook_input"; then
  ("$ha_nova_bin" doctor --auto-repair --quiet || true) </dev/null >/dev/null 2>&1 &
  disown 2>/dev/null || true
fi

if [[ -n "$demo_notice" ]]; then
  session_context="${demo_notice}\\n${session_context}"
fi

if [[ -n "$version_warning" ]]; then
  session_context="${version_warning}\\n${session_context}"
fi

if [[ -n "$path_notice" ]]; then
  session_context="${path_notice}\\n${session_context}"
fi

if [[ -n "${SKILL_VERSION:-}" ]]; then
  session_context="HA NOVA Skills v${SKILL_VERSION}\\n${session_context}"
fi

cat <<EOF
{
  "additional_context": "${session_context}",
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "${session_context}"
  }
}
EOF

exit 0
