#!/bin/bash
#
# Nexus Memory - Enhanced Store Memory Hook (GraphRAG v2)
# Stores memories to Nexus GraphRAG with entity extraction, knowledge graph,
# and episodic memory support for rich cross-session context.
#
# Usage:
#   echo '{"content": "...", "event_type": "fix"}' | store-memory.sh
#
# Environment Variables:
#   NEXUS_API_KEY        - API key for authentication (REQUIRED)
#   NEXUS_API_URL        - API endpoint (default: https://api.adverant.ai)
#   NEXUS_COMPANY_ID     - Company identifier (default: adverant)
#   NEXUS_APP_ID         - Application identifier (default: claude-code)
#   NEXUS_VERBOSE        - Set to 1 for debug output
#
# GraphRAG Enhancement Options:
#   NEXUS_EXTRACT_ENTITIES  - Enable entity extraction (default: true)
#   NEXUS_CREATE_RELATIONS  - Create knowledge graph relationships (default: true)
#   NEXUS_ENTITY_TYPES      - Comma-separated entity types to extract
#   NEXUS_DOMAIN            - Content domain: code, conversation, decision (default: auto)
#
# Event Types:
#   fix        - Bug fixes and solutions
#   decision   - Architecture/design choices
#   learning   - Discoveries while coding
#   pattern    - Reusable code patterns
#   preference - User/project preferences
#   context    - Project-specific context
#   bead       - Git-backed issue/task tracking (synced via bead-sync.sh)
#

set -o pipefail

# Source the API key helper for interactive prompting
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/api-key-helper.sh" 2>/dev/null || true

# Configuration with environment variable overrides
NEXUS_API_KEY="${NEXUS_API_KEY:-}"
NEXUS_API_URL="${NEXUS_API_URL:-https://api.adverant.ai}"
COMPANY_ID="${NEXUS_COMPANY_ID:-adverant}"
APP_ID="${NEXUS_APP_ID:-claude-code}"
VERBOSE="${NEXUS_VERBOSE:-0}"

# GraphRAG Enhancement Configuration
EXTRACT_ENTITIES="${NEXUS_EXTRACT_ENTITIES:-true}"
CREATE_RELATIONS="${NEXUS_CREATE_RELATIONS:-true}"
ENTITY_TYPES="${NEXUS_ENTITY_TYPES:-code_pattern,error,decision,learning,file,function,class,variable}"
DOMAIN="${NEXUS_DOMAIN:-auto}"

# State tracking for episodic context
STATE_DIR="${HOME}/.claude/session-env"
LAST_MEMORY_FILE="${STATE_DIR}/last_memory_id"

# Logging function
log() {
  if [[ "$VERBOSE" == "1" ]]; then
    echo "[store-memory] $1" >&2
  fi
}

log_error() {
  echo "[store-memory] ERROR: $1" >&2
}

# Detect content domain for entity extraction
detect_domain() {
  local content="$1"
  local event_type="$2"

  # If domain is explicitly set, use it
  if [[ "$DOMAIN" != "auto" ]]; then
    echo "$DOMAIN"
    return
  fi

  # Auto-detect based on content patterns
  if echo "$content" | grep -qE '(function|class|const|let|var|import|export|def |async |await )'; then
    echo "code"
  elif echo "$content" | grep -qE '(Error:|Exception|failed|error:|warning:|TypeError|SyntaxError)'; then
    echo "code"
  elif [[ "$event_type" == "decision" ]] || [[ "$event_type" == "preference" ]]; then
    echo "decision"
  elif [[ "$event_type" == "fix" ]] || [[ "$event_type" == "pattern" ]]; then
    echo "code"
  else
    echo "conversation"
  fi
}

# Classify content to determine if it should be stored
# Returns: bead | decision | learning | error_fix | code_change | routine | noise
classify_content() {
  local content="$1"
  local event_type="$2"
  local content_lower=$(echo "$content" | tr '[:upper:]' '[:lower:]')

  # Bead operations (HIGH priority - always store + trigger sync)
  # Matches: bd create, bd close, bd work, bd dep, bd list, bd ready, bd show, bd init
  if echo "$content" | grep -qE '\bbd (create|close|work|dep|list|ready|show|init|update|add|remove|link|unlink|graph|molecule)\b'; then
    echo "bead"
    return 0
  fi

  # Also detect bead-related event type
  if [[ "$event_type" == "bead" ]]; then
    echo "bead"
    return 0
  fi

  # High-value patterns - ALWAYS store
  # Decisions: chose X over Y, decided to, instead of, went with
  if echo "$content_lower" | grep -qE '(decided to|chose|instead of|went with|opted for|prefer|selected|picked)'; then
    echo "decision"
    return 0
  fi

  # Learnings: learned, realized, discovered, found out, turns out
  if echo "$content_lower" | grep -qE '(learned|realized|discovered|found out|turns out|figured out|now i know|aha|insight)'; then
    echo "learning"
    return 0
  fi

  # Error fixes: fixed, resolved, solved, bug, error, issue
  if echo "$content_lower" | grep -qE '(fixed|resolved|solved|the (bug|error|issue)|fix for|solution)'; then
    echo "error_fix"
    return 0
  fi

  # Event type overrides for explicit categorization
  if [[ "$event_type" == "decision" ]] || [[ "$event_type" == "preference" ]]; then
    echo "decision"
    return 0
  fi

  if [[ "$event_type" == "learning" ]]; then
    echo "learning"
    return 0
  fi

  if [[ "$event_type" == "fix" ]] || [[ "$event_type" == "pattern" ]]; then
    echo "error_fix"
    return 0
  fi

  # Medium-value: code changes with explanation
  if echo "$content_lower" | grep -qE '(modified|updated|changed|added|implemented|created|refactored)'; then
    echo "code_change"
    return 0
  fi

  # Low-value: routine commands - SKIP storage
  # Common shell commands, git status checks, npm installs
  if echo "$content" | grep -qE '^(ls |cd |pwd|cat |head |tail |grep |find |git status|git diff|git log|npm install|npm run|yarn |pnpm )'; then
    echo "routine"
    return 0
  fi

  # Noise: very short content, single words, numbers only
  if [[ ${#content} -lt 30 ]]; then
    echo "noise"
    return 0
  fi

  # Default: store as context
  echo "context"
  return 0
}

# Get previous memory ID for causal chaining
get_previous_memory_id() {
  if [[ -f "$LAST_MEMORY_FILE" ]]; then
    cat "$LAST_MEMORY_FILE" 2>/dev/null
  else
    echo ""
  fi
}

# Save current memory ID for future causal chaining
save_memory_id() {
  local memory_id="$1"
  mkdir -p "$STATE_DIR"
  echo "$memory_id" > "$LAST_MEMORY_FILE"
}

# Convert comma-separated entity types to JSON array
entity_types_to_json() {
  echo "$ENTITY_TYPES" | tr ',' '\n' | jq -R . | jq -s .
}

# Check for API key (REQUIRED) - Interactive prompt if running in terminal
# store-memory.sh can be user-invoked or called by hooks
if [[ -z "$NEXUS_API_KEY" ]]; then
  # Try loading from saved file first
  if type load_api_key &>/dev/null; then
    load_api_key
  fi

  # If still no key, check if we should prompt
  if [[ -z "$NEXUS_API_KEY" ]]; then
    if [[ -t 0 ]] && type require_api_key &>/dev/null; then
      # Interactive terminal - prompt for key
      require_api_key --interactive || exit 1
    else
      # Non-interactive - show error and exit
      log_error "NEXUS_API_KEY environment variable is required but not set."
      log_error "Get your API key from: https://dashboard.adverant.ai/dashboard/api-keys"
      log_error ""
      log_error "Set it in your shell profile or Claude Code settings:"
      log_error "  export NEXUS_API_KEY='your-api-key-here'"
      exit 1
    fi
  fi
fi

# Check dependencies
if ! command -v jq &> /dev/null; then
  log_error "jq is required but not installed. Install with: brew install jq"
  exit 1
fi

if ! command -v curl &> /dev/null; then
  log_error "curl is required but not installed."
  exit 1
fi

# Read input from stdin (JSON with conversation context)
INPUT=$(cat)

if [[ -z "$INPUT" ]]; then
  log "No input provided, skipping"
  exit 0
fi

log "Received input: ${INPUT:0:100}..."

# Extract relevant fields
CONTENT=$(echo "$INPUT" | jq -r '.content // .tool_input.command // .prompt // empty' 2>/dev/null)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null)
EVENT_TYPE=$(echo "$INPUT" | jq -r '.event_type // "conversation"' 2>/dev/null)

# Validate event type
VALID_TYPES=("fix" "decision" "learning" "pattern" "preference" "context" "conversation" "bead")
TYPE_VALID=0
for type in "${VALID_TYPES[@]}"; do
  if [[ "$EVENT_TYPE" == "$type" ]]; then
    TYPE_VALID=1
    break
  fi
done

if [[ "$TYPE_VALID" == "0" ]]; then
  log "Warning: Unknown event type '$EVENT_TYPE', defaulting to 'context'"
  EVENT_TYPE="context"
fi

# Get current working directory as project context
PROJECT_DIR=$(pwd)
PROJECT_NAME=$(basename "$PROJECT_DIR")

log "Project: $PROJECT_NAME"
log "Event type: $EVENT_TYPE"

# Skip if no content
if [[ -z "$CONTENT" ]] || [[ "$CONTENT" == "null" ]]; then
  log "No content to store, skipping"
  exit 0
fi

# Content quality check moved to API level
# Hook no longer filters content locally - API will return HTTP 400 if rejected
# This ensures users see the helpful error message from the API
# Still classify for bead detection and metadata tagging
CONTENT_CLASS=$(classify_content "$CONTENT" "$EVENT_TYPE")
log "Content classification: $CONTENT_CLASS (advisory only, not filtering)"

# Truncate very long content (max 10000 chars)
if [[ ${#CONTENT} -gt 10000 ]]; then
  CONTENT="${CONTENT:0:10000}... [truncated]"
  log "Content truncated to 10000 characters"
fi

# Detect domain for entity extraction
DETECTED_DOMAIN=$(detect_domain "$CONTENT" "$EVENT_TYPE")
log "Detected domain: $DETECTED_DOMAIN"

# Get previous memory ID for causal chaining
PREV_MEMORY_ID=$(get_previous_memory_id)
if [[ -n "$PREV_MEMORY_ID" ]]; then
  log "Previous memory ID: $PREV_MEMORY_ID"
fi

# Get entity types as JSON array
ENTITY_TYPES_JSON=$(entity_types_to_json)

# Build the enhanced payload with GraphRAG features
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
INTERACTION_ID="${SESSION_ID}-$(date +%s)"

PAYLOAD=$(jq -n \
  --arg content "$CONTENT" \
  --arg session "$SESSION_ID" \
  --arg event "$EVENT_TYPE" \
  --arg contentClass "$CONTENT_CLASS" \
  --arg project "$PROJECT_NAME" \
  --arg projectDir "$PROJECT_DIR" \
  --arg timestamp "$TIMESTAMP" \
  --arg domain "$DETECTED_DOMAIN" \
  --arg prevMemory "$PREV_MEMORY_ID" \
  --arg interactionId "$INTERACTION_ID" \
  --argjson extractEntities "$EXTRACT_ENTITIES" \
  --argjson createRelations "$CREATE_RELATIONS" \
  --argjson entityTypes "$ENTITY_TYPES_JSON" \
  '{
    content: $content,
    tags: ["claude-code", "session:\($session)", "type:\($event)", "class:\($contentClass)", "project:\($project)", "domain:\($domain)"],
    metadata: {
      sessionId: $session,
      eventType: $event,
      contentClass: $contentClass,
      projectDir: $projectDir,
      projectName: $project,
      timestamp: $timestamp,
      domain: $domain,
      interactionId: $interactionId
    },
    extract_entities: $extractEntities,
    entity_types: $entityTypes,
    domain: $domain,
    create_relationships: $createRelations,
    episodic: {
      type: "memory",
      interaction_id: $interactionId,
      causal_context: (if $prevMemory != "" then $prevMemory else null end)
    }
  }')

# Use the UNIFIED /api/memory endpoint for all memory operations
# This single endpoint handles both store (with content) and recall (with query)
log "Storing memory to unified endpoint: $NEXUS_API_URL/api/memory"

# Store to GraphRAG via UNIFIED memory endpoint
# Authorization: Bearer token for API authentication
# Headers: X-Company-ID, X-App-ID, X-User-ID (required for tenant context)
# Run async (background) with timeout
if [[ "$VERBOSE" == "1" ]]; then
  # VERBOSE MODE: Synchronous with detailed output
  log "Storing memory (sync mode)..."

  RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$NEXUS_API_URL/api/memory" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $NEXUS_API_KEY" \
    -H "X-Company-ID: $COMPANY_ID" \
    -H "X-App-ID: $APP_ID" \
    -H "X-User-ID: ${USER:-unknown}" \
    -d "$PAYLOAD" \
    --max-time 10 2>&1)

  HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
  BODY=$(echo "$RESPONSE" | sed '$d')

  if [[ "$HTTP_CODE" == "200" ]] || [[ "$HTTP_CODE" == "201" ]]; then
    MEMORY_ID=$(echo "$BODY" | jq -r '.memoryId // .data.memoryId // empty' 2>/dev/null)

    if [[ -n "$MEMORY_ID" ]]; then
      echo "✅ Memory stored: $MEMORY_ID" >&2
      save_memory_id "$MEMORY_ID"
    else
      echo "⚠️  Memory stored but no ID returned" >&2
    fi

    # Show triage decision if available
    TRIAGE=$(echo "$BODY" | jq -r '.triageDecision.contentType // ""' 2>/dev/null)
    if [[ -n "$TRIAGE" ]] && [[ "$TRIAGE" != "null" ]]; then
      echo "   Content type: $TRIAGE" >&2
    fi

  elif [[ "$HTTP_CODE" == "400" ]]; then
    # QUALITY FILTER REJECTION - show API's error message
    echo "❌ Storage rejected (HTTP 400)" >&2
    ERROR_MSG=$(echo "$BODY" | jq -r '.error.message // .message // ""' 2>/dev/null)
    REASON=$(echo "$BODY" | jq -r '.error.reason // ""' 2>/dev/null)

    if [[ -n "$ERROR_MSG" ]]; then
      echo "   Error: $ERROR_MSG" >&2
    fi
    if [[ -n "$REASON" ]] && [[ "$REASON" != "null" ]]; then
      echo "   Reason: $REASON" >&2
    fi
    exit 1  # Non-blocking error

  elif [[ "$HTTP_CODE" == "401" ]]; then
    # AUTHENTICATION FAILURE - clear actionable message
    echo "❌ Authentication failed (HTTP 401)" >&2
    echo "   Check NEXUS_API_KEY environment variable" >&2
    echo "   Current key prefix: ${NEXUS_API_KEY:0:10}..." >&2
    exit 1  # Non-blocking error

  else
    # OTHER ERROR - show details
    echo "❌ Storage failed (HTTP $HTTP_CODE)" >&2
    ERROR_MSG=$(echo "$BODY" | jq -r '.error.message // .message // ""' 2>/dev/null)
    if [[ -n "$ERROR_MSG" ]]; then
      echo "   Error: $ERROR_MSG" >&2
    fi
    exit 1  # Non-blocking error
  fi
else
  # Async mode with memory ID tracking for normal operation
  (
    RESPONSE=$(curl -s -X POST "$NEXUS_API_URL/api/memory" \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer $NEXUS_API_KEY" \
      -H "X-Company-ID: $COMPANY_ID" \
      -H "X-App-ID: $APP_ID" \
      -H "X-User-ID: ${USER:-unknown}" \
      -d "$PAYLOAD" \
      --max-time 5 2>/dev/null)

    # Extract and save memory ID for causal chaining
    MEMORY_ID=$(echo "$RESPONSE" | jq -r '.memoryId // .data.memoryId // empty' 2>/dev/null)
    if [[ -n "$MEMORY_ID" ]]; then
      mkdir -p "$STATE_DIR"
      echo "$MEMORY_ID" > "$LAST_MEMORY_FILE"
    fi
  ) &
fi

# Trigger bead-sync if this was a bead operation
# This syncs the local beads to GraphRAG in the background
if [[ "$CONTENT_CLASS" == "bead" ]]; then
  BEAD_SYNC_HOOK="${HOME}/.claude/hooks/bead-sync.sh"
  if [[ -x "$BEAD_SYNC_HOOK" ]]; then
    log "Triggering bead-sync for bead operation"
    # Run sync-latest in background to not block the hook
    ("$BEAD_SYNC_HOOK" sync-latest 2>/dev/null) &
  fi
fi

exit 0
