#!/usr/bin/env bash
# observe.sh — Mulahazah PreToolUse/PostToolUse observation hook.
#
# Two-phase design:
#   1. PREFERRED: exec the Node observer (bin/observe.mjs) which captures the
#      rich event schema (tool_input.command for Bash, file_path for Edit/
#      Write/Read, tool_output) without depending on jq.
#   2. FALLBACK: if either node is missing OR the Node observer file is not
#      installed alongside this script, fall through to the in-bash thin-
#      schema path below. The fallback preserves prior behavior so an
#      operator who never re-runs `npx continuous-improvement install` is
#      no worse off than today.
#
# Always exits 0 — never blocks the Claude session.
# Usage: echo '<hook_json>' | observe.sh

trap 'exit 0' EXIT ERR INT TERM

# Phase 1 — try the Node observer. The shim copies bin/observe.mjs and
# lib/observe-event.mjs alongside this script under ${HOME}/.claude/instincts/
# preserving the bin/ + lib/ subdirectory layout so the relative import
# resolves: observe.mjs lives at .../instincts/bin/observe.mjs, the imported
# observe-event.mjs at .../instincts/lib/observe-event.mjs.
NODE_OBSERVER="$(dirname "${BASH_SOURCE[0]}")/bin/observe.mjs"
if [[ -f "$NODE_OBSERVER" ]] && command -v node &>/dev/null; then
  exec node "$NODE_OBSERVER"
fi

# Phase 2 — bash fallback (thin schema; preserved for back-compat).
INSTINCTS_DIR="${HOME}/.claude/instincts"

# ---------------------------------------------------------------------------
# Read stdin (hook payload) — single read for performance
# ---------------------------------------------------------------------------
INPUT="$(cat)"
[[ -z "$INPUT" ]] && exit 0

# ---------------------------------------------------------------------------
# Parse hook payload — use jq if available, otherwise basic extraction
#
# When jq is missing, observation rows lack tool_input/tool_output, which
# defeats the auto-instinct pipeline. Emit a one-shot stderr warning per
# project so the operator notices at the next session start instead of
# discovering the gap weeks later. The marker file makes it idempotent.
# ---------------------------------------------------------------------------
if command -v jq &>/dev/null; then
  read -r TOOL_NAME SESSION_ID HAS_OUTPUT INPUT_JSON OUTPUT_JSON <<< "$(
    printf '%s' "$INPUT" | jq -r '
      (.tool_name // ""),
      (.session_id // ""),
      (if has("tool_output") then "yes" else "no" end),
      ((.tool_input // {} | tostring) | .[0:500]),
      ((.tool_output // {} | tostring) | .[0:200])
    ' | paste - - - - -
  )"
else
  # One-shot stderr warning per HOME so the operator learns the constraint.
  # Marker lives next to instincts/ (not inside it) so it never collides with
  # tests or analysis loops that iterate INSTINCTS_DIR for project-hash dirs.
  JQ_WARN_MARKER="${HOME}/.claude/.continuous-improvement-jq-warned"
  if [[ ! -f "$JQ_WARN_MARKER" ]]; then
    mkdir -p "${HOME}/.claude" 2>/dev/null
    printf '[continuous-improvement] jq not found on PATH — observe.sh is using thin-schema fallback. Install jq to enable auto-instinct detection (winget install jqlang.jq | brew install jq | apt install jq). This warning is one-shot per host.\n' >&2
    : > "$JQ_WARN_MARKER"
  fi
  # Fallback: extract tool_name with basic pattern matching
  TOOL_NAME="$(printf '%s' "$INPUT" | sed -n 's/.*"tool_name" *: *"\([^"]*\)".*/\1/p' | head -1)"
  SESSION_ID="$(printf '%s' "$INPUT" | sed -n 's/.*"session_id" *: *"\([^"]*\)".*/\1/p' | head -1)"
  HAS_OUTPUT="no"
  printf '%s' "$INPUT" | grep -q '"tool_output"' && HAS_OUTPUT="yes"
  INPUT_JSON="$(printf '%s' "$INPUT" | head -c 500)"
  OUTPUT_JSON=""
fi

[[ -z "$TOOL_NAME" ]] && exit 0

# Determine event type
if [[ "$HAS_OUTPUT" == "yes" ]]; then
  EVENT="tool_complete"
else
  EVENT="tool_start"
fi

# ---------------------------------------------------------------------------
# Project detection
# ---------------------------------------------------------------------------
PROJECT_ROOT=""

if [[ -n "${CLAUDE_PROJECT_DIR:-}" && -d "${CLAUDE_PROJECT_DIR}" ]]; then
  PROJECT_ROOT="${CLAUDE_PROJECT_DIR}"
fi

if [[ -z "$PROJECT_ROOT" ]]; then
  PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
fi

if [[ -z "$PROJECT_ROOT" ]]; then
  PROJECT_ROOT="global"
fi

# ---------------------------------------------------------------------------
# Compute project hash and name
# ---------------------------------------------------------------------------
# Canonicalize MSYS-mounted Windows paths (/d/Path) to native form (D:/Path)
# so the bash fallback hashes to the same project bucket as the Node observer.
# Without this, Git Bash on Windows produces /d/Ai/repo from `git rev-parse`
# while Node's child_process produces D:/Ai/repo for the same physical path,
# splitting telemetry between two SHA-256 buckets per project.
# Pattern matches /<single-letter>/... only — Linux/macOS paths like /usr,
# /home, /Users are unaffected.
case "$PROJECT_ROOT" in
  /[a-zA-Z]/*)
    drive_letter="$(printf '%s' "$PROJECT_ROOT" | cut -c2 | tr '[:lower:]' '[:upper:]')"
    PROJECT_ROOT="${drive_letter}:$(printf '%s' "$PROJECT_ROOT" | cut -c3-)"
    ;;
esac

PROJECT_HASH="$(printf '%s' "$PROJECT_ROOT" | sha256sum | cut -c1-12)"
PROJECT_NAME="$(basename "${PROJECT_ROOT%.git}")"

# ---------------------------------------------------------------------------
# Directory setup
# ---------------------------------------------------------------------------
PROJECT_DIR="${INSTINCTS_DIR}/${PROJECT_HASH}"
OBS_FILE="${PROJECT_DIR}/observations.jsonl"

[[ -d "$PROJECT_DIR" ]] || mkdir -p "$PROJECT_DIR"

# ---------------------------------------------------------------------------
# Rotate observations.jsonl at 10,000 lines
# ---------------------------------------------------------------------------
if [[ -f "$OBS_FILE" ]]; then
  LINE_COUNT="$(wc -l < "$OBS_FILE")"
  if (( LINE_COUNT >= 10000 )); then
    mv "$OBS_FILE" "${PROJECT_DIR}/observations.$(date -u +"%Y-%m-%d-%H%M%S").jsonl"

    # Clean up old archives - keep only 10 most recent
    ls -t "${PROJECT_DIR}"/observations.*.jsonl 2>/dev/null | tail -n +11 | xargs -r rm -f
  fi
fi

# ---------------------------------------------------------------------------
# Append JSONL observation line
# ---------------------------------------------------------------------------
TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"

if command -v jq &>/dev/null; then
  printf '%s\n' "$(jq -cn \
    --arg ts "$TS" \
    --arg event "$EVENT" \
    --arg session "$SESSION_ID" \
    --arg tool "$TOOL_NAME" \
    --arg input_summary "$INPUT_JSON" \
    --arg output_summary "$OUTPUT_JSON" \
    --arg project_id "$PROJECT_HASH" \
    --arg project_name "$PROJECT_NAME" \
    '{ts:$ts,event:$event,session:$session,tool:$tool,input_summary:$input_summary,output_summary:$output_summary,project_id:$project_id,project_name:$project_name}')" \
    >> "$OBS_FILE"
else
  # Fallback: manual JSON construction
  printf '{"ts":"%s","event":"%s","session":"%s","tool":"%s","project_id":"%s","project_name":"%s"}\n' \
    "$TS" "$EVENT" "$SESSION_ID" "$TOOL_NAME" "$PROJECT_HASH" "$PROJECT_NAME" \
    >> "$OBS_FILE"
fi

# ---------------------------------------------------------------------------
# Write project.json if new project
# ---------------------------------------------------------------------------
PROJECT_JSON="${PROJECT_DIR}/project.json"
if [[ ! -f "$PROJECT_JSON" ]]; then
  if command -v jq &>/dev/null; then
    jq -n \
      --arg id "$PROJECT_HASH" \
      --arg name "$PROJECT_NAME" \
      --arg root "$PROJECT_ROOT" \
      --arg created_at "$TS" \
      '{id:$id,name:$name,root:$root,created_at:$created_at}' \
      > "$PROJECT_JSON"
  else
    printf '{"id":"%s","name":"%s","root":"%s","created_at":"%s"}\n' \
      "$PROJECT_HASH" "$PROJECT_NAME" "$PROJECT_ROOT" "$TS" \
      > "$PROJECT_JSON"
  fi
fi

exit 0
