// tools/safe-baseline-metering.mjs
/**
 * Safe-baseline metering — router-gate v4 spec §3.1.2 (Direction 1).
 * Pure: счётчики Read/Grep/Glob/LS/TodoWrite/AskUserQuestion per task.
 * Закрывает skill-substitution laundering (много Read/анализ вместо invoke skill).
 */
import crypto from 'node:crypto';

export const RESET_MARKERS = [
  'новая задача', 'сброс контекста', 'забудь предыдущее', 'забудь контекст',
  'начнём заново', 'с чистого листа',
];

/**
 * Tools whose usage is metered per-task.
 * NOTE: 'TodoWrite' maps to the counter key 'TodoWrite_writes' (via `counterKey`).
 * Consumers comparing against `state.counts` keys should use 'TodoWrite_writes', not 'TodoWrite'.
 */
export const METERED_TOOLS = ['Read', 'Grep', 'Glob', 'LS', 'TodoWrite', 'AskUserQuestion'];

// Fix 2: deep-freeze nested objects to match tools/cost-pricing.mjs pattern.
export const DEFAULT_THRESHOLDS = Object.freeze({
  Read: Object.freeze({ warn: 30, hard: 60 }),
  Grep: Object.freeze({ warn: 15, hard: 30 }),
  Glob: Object.freeze({ warn: 10, hard: 20 }),
  LS: Object.freeze({ warn: 10, hard: 20 }),
  TodoWrite_writes: Object.freeze({ warn: 5, hard: 15 }),
  AskUserQuestion: Object.freeze({ warn: 2, hard: 30 }),
});

const MUTATING = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'Bash', 'Skill', 'Task']);

export function isMutatingForBaseline(toolName) {
  return MUTATING.has(toolName);
}

export function isResetMarker(prompt) {
  const low = String(prompt || '').toLowerCase();
  return RESET_MARKERS.some((m) => low.includes(m));
}

export function deriveTaskId(firstPrompt) {
  return crypto.createHash('sha256').update(String(firstPrompt || '')).digest('hex').slice(0, 16);
}

// Fix 1: dedupe `a` into a Set so duplicate keywords don't inflate the count.
export function keywordOverlapCount(a, b) {
  const setB = new Set((b || []).map((k) => String(k).toLowerCase()));
  const setA = new Set((a || []).map((k) => String(k).toLowerCase()));
  let n = 0;
  for (const k of setA) if (setB.has(k)) n++;
  return n;
}

export function shouldInheritTaskId(prevKeywords, currentKeywords, prompt) {
  if (isResetMarker(prompt)) return false;
  return keywordOverlapCount(prevKeywords, currentKeywords) >= 2;
}

export function newCounterState({ taskId, startedAtIso, firstPromptExcerpt }) {
  return {
    schema_version: 1,
    task_id: taskId,
    task_started_at: startedAtIso,
    task_first_prompt_excerpt: String(firstPromptExcerpt || '').slice(0, 200),
    counts: { Read: 0, Grep: 0, Glob: 0, LS: 0, TodoWrite_writes: 0, AskUserQuestion: 0 },
    skill_match_within_task: false,
    warnings_issued: [],
    hard_blocks_issued: [],
  };
}

function counterKey(toolName) {
  return toolName === 'TodoWrite' ? 'TodoWrite_writes' : toolName;
}

export function incrementCounter(state, toolName) {
  const key = counterKey(toolName);
  if (!(key in state.counts)) return state; // not metered
  return {
    ...state,
    counts: { ...state.counts, [key]: state.counts[key] + 1 },
  };
}

// Fix 3: move mutating check to top (after skillMatched short-circuit) so
// `key`/`th` are only computed in the metered-tool branch where they're used.
export function evaluateThresholds(state, toolName, skillMatched, thresholds = DEFAULT_THRESHOLDS) {
  if (skillMatched) return { action: 'allow' };

  // mutating tool: block if ANY metered counter reached its hard threshold
  if (isMutatingForBaseline(toolName)) {
    for (const mk of Object.keys(state.counts)) {
      const t = thresholds[mk];
      if (t && state.counts[mk] >= t.hard) {
        return {
          action: 'hard_block',
          reason: `Превышен лимит safe-baseline tools (${mk}=${state.counts[mk]}) без Skill match. Паттерн skill-substitution. Вызови recommended skill ИЛИ перезапусти задачу с явным skill invocation.`,
        };
      }
    }
    return { action: 'allow' };
  }

  // metered safe-baseline tool
  const key = counterKey(toolName);
  const th = thresholds[key];
  if (th) {
    const count = state.counts[key];
    if (count >= th.hard) {
      // Read/Grep/etc остаются allowed (legit continuation)
      return { action: 'allow', tool: toolName };
    }
    if (count >= th.warn) {
      return {
        action: 'soft_flag',
        tool: toolName,
        reason: `Сделано ${count} ${toolName} в задаче без invoke skill. Invoke recommended skill ИЛИ продолжить direct с явным "direct ok".`,
      };
    }
  }
  return { action: 'allow' };
}
