#!/usr/bin/env bash
# UserPromptSubmit hook — injects unread coord messages and (in dev mode) active locks.
# Strip .local suffix if present (macOS convention)
HOSTNAME_RAW="$(hostname)"
SESSION="${HOSTNAME_RAW%.local}/claude-main"
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
MODE_FILE="${SCRIPT_DIR}/current-mode"

# Coord API endpoint — per-machine override in identity.json (same lookup as sync.sh).
# Falls back to the on-LAN default so existing machines keep working unchanged.
# This script is invoked by both Claude Code (via .claude/settings.json UserPromptSubmit)
# and Grok (via .grok/hooks/ UserPromptSubmit) for cross-driver coexistence.
IDENTITY_PATH=""
for candidate in "$HOME/.claude/identity.json" "${SCRIPT_DIR}/identity.json"; do
    if [ -f "$candidate" ]; then
        IDENTITY_PATH="$candidate"
        break
    fi
done
API=""
if [ -n "$IDENTITY_PATH" ] && command -v jq >/dev/null 2>&1; then
    API=$(jq -r '.coord_api // empty' "$IDENTITY_PATH" 2>/dev/null)
fi
[ -z "$API" ] && API="http://172.16.3.30:8001"  # default when identity.json doesn't define coord_api

# --- Initialize mode file if missing -----------------------------------------
# The mode file is machine-local (gitignored) and required by this hook.
# If missing, create it with "general" as the default mode.
if [ ! -f "$MODE_FILE" ]; then
  echo "general" > "$MODE_FILE"
  echo "[INFO] Created .claude/current-mode with default mode: general" >&2
fi

# Read short username alias from identity.json (if present)
IDENTITY_FILE="${SCRIPT_DIR}/identity.json"
USER_ALIAS=""
if [ -f "$IDENTITY_FILE" ]; then
  USER_ALIAS=$(jq -r '.user // empty' "$IDENTITY_FILE" 2>/dev/null)
fi

# Resolve a Python interpreter for the JSON sanitizer. Prefer the value in
# identity.json (.python.command — set during machine onboarding); fall back
# to a PATH lookup. On ACG Windows boxes the Microsoft Store `python3` alias
# is disabled and `py` is the canonical launcher, so an unconditional
# `python3` call would silently fail and cause every coord message to be
# dropped. If no Python is available at all, the sanitizer falls back to
# `tr -d` (lossy on real \n/\t in string fields but keeps messages visible).
PY=""
if [ -f "$IDENTITY_FILE" ]; then
  PY=$(jq -r '.python.command // empty' "$IDENTITY_FILE" 2>/dev/null)
fi
if [ -z "$PY" ]; then
  PY=$(command -v python3 || command -v py || command -v python)
fi

# Sanitize JSON: the coord API sometimes returns message bodies with unescaped
# control chars (U+0000-U+001F) — invalid JSON per RFC 8259, which makes jq
# abort with "Invalid string: control characters ... must be escaped". Round-
# trip through python's json.loads(strict=False) (which accepts them) and
# re-emit properly escaped JSON so jq downstream works on every payload.
sanitize_json() {
  if [ -n "$PY" ]; then
    "$PY" -c '
import json, sys
try:
    print(json.dumps(json.loads(sys.stdin.read(), strict=False)))
except Exception:
    pass
' 2>/dev/null
  else
    tr -d '\000-\037'
  fi
}

# --- Unread messages ---------------------------------------------------------

# Query for messages addressed to full session ID
result=$(curl -s --connect-timeout 3 "${API}/api/coord/messages?to_session=${SESSION}&unread_only=true" 2>/dev/null | sanitize_json)

# Also query for messages addressed to the short username alias (e.g. "howard")
result_alias=""
if [ -n "$USER_ALIAS" ] && [ "$USER_ALIAS" != "$SESSION" ]; then
  result_alias=$(curl -s --connect-timeout 3 "${API}/api/coord/messages?to_session=${USER_ALIAS}&unread_only=true" 2>/dev/null | sanitize_json)
fi

# Merge personal + alias result sets (combine .messages arrays, recompute total).
# IMPORTANT: use `printf '%s'`, NOT `echo`, to pipe JSON to jq — bash `echo`
# interprets backslash escapes (e.g. \n inside string bodies) and corrupts JSON.
if [ -n "$result_alias" ]; then
  alias_msgs=$(printf '%s' "$result_alias" | jq '.messages // []' 2>/dev/null)
  if [ -n "$alias_msgs" ] && [ "$alias_msgs" != "[]" ] && [ "$alias_msgs" != "null" ]; then
    if [ -n "$result" ]; then
      result=$(printf '%s\n%s\n' "$result" "$result_alias" | jq -s '{total: (.[0].total + .[1].total), messages: (.[0].messages + .[1].messages)}' 2>/dev/null)
    else
      result="$result_alias"
    fi
  fi
fi

# --- Broadcasts (to_session=ALL_SESSIONS) -----------------------------------
# Broadcasts are marked as read on the server just like personal messages.
# Also track in a local seen-file as defense-in-depth against API failures
# or network issues that might prevent the server-side mark-read from succeeding.
SEEN_FILE="${SCRIPT_DIR}/coord-broadcasts-seen"
[ -f "$SEEN_FILE" ] || : > "$SEEN_FILE"

bcast_raw=$(curl -s --connect-timeout 3 "${API}/api/coord/messages?to_session=ALL_SESSIONS&unread_only=true&limit=100" 2>/dev/null | sanitize_json)
bcast_msgs="[]"
if [ -n "$bcast_raw" ]; then
  # JSON array of already-seen broadcast IDs (lines from seen-file; empty -> [])
  seen_json=$(jq -R . "$SEEN_FILE" | jq -s 'map(select(length>0))' 2>/dev/null)
  [ -z "$seen_json" ] && seen_json='[]'
  # Normalize this session for self-broadcast filter (drop .local for compare)
  session_norm=$(printf '%s' "$SESSION" | tr 'A-Z' 'a-z')
  # Bind each message to $m so .id / .from_session inside index() and sub()
  # resolve against the message, not against $seen (jq scoping gotcha).
  bcast_msgs=$(printf '%s' "$bcast_raw" | jq --argjson seen "$seen_json" --arg self "$session_norm" '
    (.messages // [])
    | map(. as $m | select(
        (($seen | index($m.id)) == null)
        and (($m.from_session | ascii_downcase | sub("\\.local/"; "/")) != $self)
      ))
  ' 2>/dev/null)
  [ -z "$bcast_msgs" ] && bcast_msgs="[]"
fi

# --- Combined display (personal + alias + filtered broadcasts) --------------
# NB: do NOT use ${result:-{}} as a default — bash parameter expansion treats
# the first '}' as the close, producing '{}}' and breaking jq downstream.
result_safe="$result"
[ -z "$result_safe" ] && result_safe='{"messages":[]}'
personal_msgs=$(printf '%s' "$result_safe" | jq '.messages // []' 2>/dev/null)
[ -z "$personal_msgs" ] && personal_msgs="[]"
total_count=$(jq -n --argjson p "$personal_msgs" --argjson b "$bcast_msgs" '($p|length) + ($b|length)' 2>/dev/null)
[ -z "$total_count" ] && total_count=0

if [ "$total_count" -gt 0 ] 2>/dev/null; then
  combined=$(jq -n --argjson p "$personal_msgs" --argjson b "$bcast_msgs" '$p + $b')
  bcast_count=$(printf '%s' "$bcast_msgs" | jq 'length' 2>/dev/null)
  [ -z "$bcast_count" ] && bcast_count=0
  echo ""
  echo "============================================================"
  if [ "$bcast_count" -gt 0 ] 2>/dev/null; then
    echo "UNREAD COORD MESSAGES (${total_count}, including ${bcast_count} broadcast)"
  else
    echo "UNREAD COORD MESSAGES (${total_count})"
  fi
  echo "============================================================"
  printf '%s' "$combined" | jq -r '.[] | "FROM: \(.from_session)\nDATE: \(.created_at)\nSUBJECT: \(.subject)\n\nMESSAGE:\n\(.body)\n---"'
  echo "============================================================"
  echo ""

  # Fire a Windows toast so the user sees it even if not watching the terminal
  toast_body=$(printf '%s' "$combined" | jq -r '[.[] | .from_session + ": " + .subject] | join(", ")' | tr -d '\r')
  notify_ps1=$(cygpath -w "${SCRIPT_DIR}/scripts/notify.ps1" 2>/dev/null || echo "${SCRIPT_DIR}/scripts/notify.ps1" | sed 's|^/\([a-zA-Z]\)/|\1:/|')
  powershell.exe -NonInteractive -NoProfile -Command \
    "& '$notify_ps1' -Title 'ClaudeTools: ${total_count} new message(s)' -Message '$toast_body'" \
    >/dev/null 2>&1 &

  # Mark personal + alias messages as read on the server.
  printf '%s' "$personal_msgs" | jq -r '.[].id' 2>/dev/null | tr -d '\r' | while read -r id; do
    [ -n "$id" ] && curl -s -X PUT "${API}/api/coord/messages/${id}/read" >/dev/null 2>&1
  done

  # Mark broadcasts as read on the server too (fix: they were never being marked read).
  # Also record in local seen-file as defense-in-depth against API issues.
  printf '%s' "$bcast_msgs" | jq -r '.[].id' 2>/dev/null | tr -d '\r' | while read -r id; do
    if [ -n "$id" ]; then
      curl -s -X PUT "${API}/api/coord/messages/${id}/read" >/dev/null 2>&1
      printf '%s\n' "$id" >> "$SEEN_FILE"
    fi
  done
fi

# --- Active locks (dev mode only) -------------------------------------------

current_mode=""
[ -f "$MODE_FILE" ] && current_mode=$(cat "$MODE_FILE" | tr -d '[:space:]')

if [ "$current_mode" = "dev" ]; then
  locks=$(curl -s --connect-timeout 3 "${API}/api/coord/locks" 2>/dev/null)
  if [ -n "$locks" ]; then
    lock_count=$(printf '%s' "$locks" | jq '.total' 2>/dev/null)
    if [ -n "$lock_count" ] && [ "$lock_count" -gt 0 ]; then
      echo ""
      echo "============================================================"
      echo "[WARNING] ACTIVE LOCKS ($lock_count) — check before editing"
      echo "============================================================"
      printf '%s' "$locks" | jq -r '.locks[] | "[DEV MODE] LOCK: \(.project_key) / \(.resource)\n  Held by: \(.session_id)\n  Reason:  \(.description // "none")\n  Expires: \(.expires_at // "unknown")\n---"'
      echo "============================================================"
      echo ""
    fi
  fi
fi

exit 0
