#!/usr/bin/env node
/**
 * UserPromptSubmit hook — ruflo memory advisory recall.
 *
 * On each prompt: runs `ruflo memory search` and injects the top matches as
 * additionalContext so Claude sees relevant recalled memory.
 *
 * FAIL-OPEN: any error / timeout / no results -> empty injection, exit 0.
 * The hook NEVER blocks a prompt.
 *
 * Requires the H7 fix (tools/ruflo-h7-patch.mjs) — otherwise ruflo memory
 * does not persist and search always returns nothing.
 *
 * See docs/superpowers/specs/2026-05-15-ruflo-memory-h7-fix-and-advisory-hook-design.md §5
 */
import { execFile } from 'node:child_process';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';

const SEARCH_TIMEOUT_MS = 3500;
const MAX_RESULTS = 3;
const MAX_QUERY_CHARS = 400;

/** Extract the `prompt` string from the UserPromptSubmit hook stdin JSON. */
export function parsePrompt(stdinRaw) {
  try {
    const obj = JSON.parse(stdinRaw);
    return typeof obj.prompt === 'string' ? obj.prompt : '';
  } catch {
    return '';
  }
}

/**
 * Extract the JSON object from `ruflo memory search --format json` stdout.
 * stdout is noisy ([INFO]..., "Using sql.js"...) followed by a pretty-printed
 * JSON object — take from the first '{' to the last '}'.
 */
export function extractJson(stdout) {
  const start = stdout.indexOf('{');
  const end = stdout.lastIndexOf('}');
  if (start === -1 || end === -1 || end < start) return null;
  try {
    return JSON.parse(stdout.slice(start, end + 1));
  } catch {
    return null;
  }
}

/**
 * Format the search result object into an additionalContext block.
 * Returns '' when there are no usable results.
 */
export function formatRecall(searchObj) {
  if (!searchObj || !Array.isArray(searchObj.results) || searchObj.results.length === 0) {
    return '';
  }
  const lines = searchObj.results.slice(0, MAX_RESULTS).map((r) => {
    const key = typeof r.key === 'string' ? r.key : '(no-key)';
    const preview = typeof r.preview === 'string' ? r.preview : '';
    return `- [${key}] ${preview}`;
  });
  return ['[ruflo memory recall — релевантное из прошлых сессий]', ...lines].join('\n');
}

/** Build the UserPromptSubmit hook stdout payload. */
export function buildHookOutput(additionalContext) {
  return {
    hookSpecificOutput: {
      hookEventName: 'UserPromptSubmit',
      additionalContext,
    },
  };
}

/** Candidate paths for the ruflo bin JS. RUFLO_RECALL_BIN overrides all. */
export function rufloBinCandidates(env) {
  if (env.RUFLO_RECALL_BIN) return [env.RUFLO_RECALL_BIN];
  const out = [];
  if (env.APPDATA) {
    out.push(join(env.APPDATA, 'npm', 'node_modules', 'ruflo', 'bin', 'ruflo.js'));
  }
  return out;
}

function resolveRufloBin(env) {
  return rufloBinCandidates(env).find((p) => existsSync(p)) ?? null;
}

function readStdin() {
  return new Promise((resolve) => {
    let data = '';
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', (c) => { data += c; });
    process.stdin.on('end', () => resolve(data));
    process.stdin.on('error', () => resolve(data));
  });
}

function runSearch(rufloBin, query) {
  return new Promise((resolve) => {
    execFile(
      process.execPath,
      [rufloBin, 'memory', 'search', '-q', query, '--format', 'json', '-l', String(MAX_RESULTS)],
      { timeout: SEARCH_TIMEOUT_MS, encoding: 'utf8' },
      (_err, stdout) => resolve(typeof stdout === 'string' ? stdout : ''),
    );
  });
}

async function main() {
  let additionalContext = '';
  try {
    const stdinRaw = await readStdin();
    const prompt = parsePrompt(stdinRaw);
    const rufloBin = resolveRufloBin(process.env);
    if (prompt.trim().length > 0 && rufloBin) {
      const stdout = await runSearch(rufloBin, prompt.slice(0, MAX_QUERY_CHARS));
      additionalContext = formatRecall(extractJson(stdout));
    }
  } catch {
    additionalContext = '';
  }
  if (additionalContext) {
    process.stdout.write(JSON.stringify(buildHookOutput(additionalContext)));
  }
  process.exit(0);
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  main();
}
