#!/usr/bin/env node
/**
 * UserPromptSubmit hook вЂ” router prehook.
 * Stage 3 of router discipline overhaul.
 *
 * РџСЂРё РєР°Р¶РґРѕРј prompt'Рµ:
 *   1. Р§РёС‚Р°РµС‚ СЂРµРµСЃС‚СЂ.
 *   2. Р’С‹Р·С‹РІР°РµС‚ classifier.
 *   3. РџРёС€РµС‚ state РІ ~/.claude/runtime/router-state-<session>.json.
 *
 * РќРµ Р±Р»РѕРєРёСЂСѓРµС‚ prompt вЂ” С‚РѕР»СЊРєРѕ РіРѕС‚РѕРІРёС‚ state РґР»СЏ PreToolUse gate (router-tool-gate).
 *
 * РљРѕРЅС‚СЂР°РєС‚ UserPromptSubmit hook (Claude Code): С‡РёС‚Р°РµС‚ JSON РёР· stdin
 *   { session_id, transcript_path, hook_event_name, prompt }
 * РЅР° stdout вЂ” { } (РїСѓСЃС‚РѕР№ РѕР±СЉРµРєС‚ = РЅРёС‡РµРіРѕ РЅРµ РјРµРЅСЏРµРј РІ prompt'Рµ).
 * NB: Claude Code С€Р»С‘С‚ РїРѕР»Рµ `prompt` (РЅРµ `user_prompt`) вЂ” С‡РёС‚Р°РµРј РѕР±Р° РґР»СЏ СЃРѕРІРјРµСЃС‚РёРјРѕСЃС‚Рё.
 */

import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { homedir } from 'os';
import { fileURLToPath } from 'url';
import { randomUUID } from 'crypto';
import { readStdinAsUtf8 } from './router-stdin-helper.mjs';

// NB: ENFORCEMENT_TYPES + isEnforcementRequired removed in Phase 2 Task 14.
// router-tool-gate now decides exempt via NON_BLOCKING_TASK_TYPES on
// state.classification.task_type (spec В§4.4, D1 вЂ” continuation NOT exempt).

function hashPrompt(s) {
  let h = 0;
  for (let i = 0; i < s.length; i++) { h = ((h << 5) - h) + s.charCodeAt(i); h |= 0; }
  return String(h);
}

/**
 * Build the router state object written to ~/.claude/runtime/router-state-*.json.
 * Schema (Phase 2 Task 14, spec В§4.1 / В§4.2):
 *   - task_id: stable per turn (taskId option overrides randomUUID for tests).
 *   - classification: raw output from classify() (any of prefilter / llm / regex shapes).
 *   - skillInvokedThisTurn: gate watches this on PostToolUse Skill.
 *   - chainProgress: reserved for chain enforcement.
 *   - task_cost: classifier input/output token counts (caller fills it when LLM was called).
 *   - prompt_length: raw character length of the user prompt (added 2026-05-27,
 *     brain-retro #7 C2 fix). Lets router-tool-gate detect short ambiguous prompts
 *     where AskUserQuestion beats improvising.
 *   - inheritance: { inherited_from_task_id, inheritance_age_minutes } вЂ” present only
 *     on continuation; written by main() when classify() returns source: 'prefilter_inherited'.
 *   - timestamp: ISO вЂ” used by prefilter (next turn) to compute inheritance age.
 */
export function buildStateFromClassification(classification, options = {}) {
  const {
    sessionId,
    promptHash,
    promptLength = null,
    inheritedFrom = null,
    ageMin = null,
    cost = {},
    taskId,
  } = options;

  const state = {
    task_id: taskId ?? randomUUID(),
    sessionId,
    promptHash,
    prompt_length: promptLength,
    classification,
    skillInvokedThisTurn: false,
    chainProgress: [],
    task_cost: { ...cost },
    timestamp: new Date().toISOString(),
  };

  if (inheritedFrom) {
    state.inheritance = {
      inherited_from_task_id: inheritedFrom,
      inheritance_age_minutes: ageMin,
    };
  }

  return state;
}

/**
 * Convert Anthropic API usage shape into a classifier task_cost block. Pure.
 * Used by main() onUsage callback to persist classifier cost into router-state,
 * which observer-transcript-parser then merges into the episode's task_cost.
 *
 * Cost-tracking added 2026-05-26 (brain-retro #6 A1 follow-up). Previously
 * task_cost.classifier_* fields were hardcoded to 0 вЂ” no cost visibility.
 */
export function buildCostFromClassifierUsage(usage) {
  if (!usage || typeof usage !== 'object') return {};
  const out = {};
  if (typeof usage.input_tokens === 'number') out.classifier_input_tokens = usage.input_tokens;
  if (typeof usage.output_tokens === 'number') out.classifier_output_tokens = usage.output_tokens;
  if (typeof usage.cache_read_input_tokens === 'number') out.classifier_cache_read_input_tokens = usage.cache_read_input_tokens;
  if (typeof usage.cache_creation_input_tokens === 'number') out.classifier_cache_creation_input_tokens = usage.cache_creation_input_tokens;
  return out;
}

function stateFilePath(sessionId) {
  return join(homedir(), '.claude', 'runtime', `router-state-${sessionId}.json`);
}

async function main() {
  const input = await readStdinAsUtf8(process.stdin);
  const event = JSON.parse(input || '{}');
  const sessionId = event.session_id || 'unknown';
  const userPrompt = event.prompt || event.user_prompt || '';

  try {
    const { loadRegistry } = await import('./registry-load.mjs');
    const { classify } = await import('./router-classifier.mjs');
    const registry = loadRegistry({ useCache: false });

    const cachePath = join(homedir(), '.claude', 'runtime', 'router-classification-cache.json');
    const cache = new Map();
    if (existsSync(cachePath)) {
      try {
        const data = JSON.parse(readFileSync(cachePath, 'utf-8'));
        for (const [k, v] of Object.entries(data)) cache.set(k, v);
      } catch { /* ignore */ }
    }

    // Read previous turn's state BEFORE overwriting вЂ” feeds prefilter's
    // continuation/cancellation check (spec В§4.1 РїСЂРѕРІРµСЂРєРё 2 + 4).
    const statePath = stateFilePath(sessionId);
    let prevState = null;
    if (existsSync(statePath)) {
      try { prevState = JSON.parse(readFileSync(statePath, 'utf-8')); } catch { /* ignore */ }
    }

    // A1 (2026-05-26): capture classifier LLM usage to persist into state.task_cost
    // so brain-retro and STATUS.md can report real $ spend.
    let classifierCost = {};
    const onUsage = (usage) => { classifierCost = buildCostFromClassifierUsage(usage); };
    const classification = await classify(userPrompt, registry, { cache, prevState, onUsage, llmCall: async () => null });

    // If prefilter inherited from the previous turn, lift the inheritance
    // block into the new state вЂ” observer-stop-hook copies it to the episode (B5).
    const inh = (classification?.source === 'prefilter_inherited' && classification.inheritance)
      ? classification.inheritance
      : null;

    const state = buildStateFromClassification(classification, {
      sessionId,
      promptHash: hashPrompt(userPrompt),
      promptLength: userPrompt.length,
      inheritedFrom: inh?.inherited_from_task_id ?? null,
      ageMin: inh?.inheritance_age_minutes ?? null,
      cost: classifierCost,
    });

    mkdirSync(dirname(statePath), { recursive: true });
    writeFileSync(statePath, JSON.stringify(state, null, 2));

    // Persist cache
    const cacheObj = {};
    for (const [k, v] of cache) cacheObj[k] = v;
    writeFileSync(cachePath, JSON.stringify(cacheObj, null, 2));

    process.stdout.write(JSON.stringify({}));
    process.exit(0);
  } catch (err) {
    // Р›СЋР±Р°СЏ РѕС€РёР±РєР° РїСЂРµС…СѓРєР° РќР• РґРѕР»Р¶РЅР° СЃР»РѕРјР°С‚СЊ prompt РїРѕР»СЊР·РѕРІР°С‚РµР»СЏ вЂ” fallback: РїСѓСЃС‚РѕР№ state, РїСЂРѕС…РѕР¶Сѓ.
    process.stderr.write(`[router-prehook] ${err.message}\n`);
    process.stdout.write(JSON.stringify({}));
    process.exit(0);
  }
}

// CLI entry point guard вЂ” use fileURLToPath for correct non-ASCII path comparison on Windows
const __filename = fileURLToPath(import.meta.url);
if (process.argv[1] && process.argv[1] === __filename) {
  main();
}
