#!/usr/bin/env node
/**
 * Active Memory Precompute Runner — PR-4 Async Precompute Shadow
 * 
 * Runs memory precomputation outside the main reply path.
 * Uses MemoryCore facade for search (read-only, FTS5).
 * Wraps results in a unified contract with timing and source metadata.
 * 
 * Safety:
 *   - READ-ONLY on memory data (never writes memory files)
 *   - Only writes to .active-memory-precompute-cache/ (temp cache dir)
 *   - Independent from active-memory hooks (no hook modification)
 *   - Never imported into production reply chain
 * 
 * Contract matches MemoryCore search output shape.
 * Input: { sessionId, recentMessages, queryHint, limit }
 * Output: { ok, sessionId, memories, source, generatedAt, ttlMs, latencyMs }
 * 
 * @module precompute-runner
 */

import { createMemoryCore } from "../memory-core/memory-core-facade.mjs";
import { createPrecomputeCache } from "./precompute-cache.mjs";

/**
 * @typedef {object} PrecomputeInput
 * @property {string} sessionId
 * @property {Array<{role:string, content:string}>} [recentMessages]
 * @property {string} [queryHint]    - Optional search query override
 * @property {number} [limit]        - Max results (default: 5)
 */

/**
 * @typedef {object} PrecomputeOutput
 * @property {boolean} ok
 * @property {string} sessionId
 * @property {string} source
 * @property {number} generatedAt
 * @property {number} ttlMs
 * @property {number} latencyMs
 * @property {Array<{id:string, path:string, snippet:string, score:number, source:string}>} [memories]
 * @property {{code:string, message:string}} [error]
 */

/**
 * Create a precompute runner instance.
 * 
 * @param {string} workspaceRoot - Absolute path to workspace
 * @param {object} [options]
 * @param {number} [options.defaultTtlMs] - Default cache TTL (default: 30 min)
 * @param {number} [options.defaultLimit] - Default result limit (default: 5)
 * @returns {object} { run, runWithCache, clearCache, cacheStats }
 */
export function createPrecomputeRunner(workspaceRoot, options = {}) {
  const root = workspaceRoot;
  const defaultTtlMs = options.defaultTtlMs ?? 30 * 60 * 1000;
  const defaultLimit = options.defaultLimit ?? 5;

  // Create internal dependencies
  const memoryCore = createMemoryCore(root);
  const cache = createPrecomputeCache(root);

  /**
   * Derive a search query from session context.
   * If queryHint provided, use it. Otherwise, extract from recent messages.
   * 
   * @param {PrecomputeInput} input
   * @returns {string}
   */
  function deriveQuery(input) {
    if (input.queryHint && input.queryHint.trim().length > 0) {
      return input.queryHint.trim();
    }

    const messages = input.recentMessages || [];
    if (messages.length === 0) {
      return "";
    }

    // Use the last user message as search query
    const lastUserMsg = [...messages].reverse().find(m => m.role === "user");
    if (lastUserMsg && lastUserMsg.content) {
      const content = lastUserMsg.content;
      // Truncate to first 200 chars for query derivation
      return content.substring(0, 200).trim();
    }

    return "";
  }

  /**
   * Run precomputation WITHOUT cache.
   * Always performs a fresh search.
   * 
   * @param {PrecomputeInput} input
   * @returns {Promise<PrecomputeOutput>}
   */
  async function run(input) {
    const startTime = Date.now();
    const { sessionId = "unknown", recentMessages = [], queryHint, limit = defaultLimit } = input;

    const query = deriveQuery({ sessionId, recentMessages, queryHint });

    if (!query || query.length === 0) {
      return {
        ok: false,
        sessionId,
        memories: [],
        source: "precompute-runner",
        generatedAt: Date.now(),
        ttlMs: defaultTtlMs,
        latencyMs: Date.now() - startTime,
        error: { code: "NO_QUERY", message: "No query could be derived from input" },
      };
    }

    try {
      const searchResult = await memoryCore.search(query, { maxResults: limit });

      return {
        ok: searchResult.ok,
        sessionId,
        memories: searchResult.results || [],
        source: searchResult.source || "precompute-runner",
        generatedAt: Date.now(),
        ttlMs: defaultTtlMs,
        latencyMs: Date.now() - startTime + (searchResult.latencyMs || 0),
      };
    } catch (e) {
      return {
        ok: false,
        sessionId,
        memories: [],
        source: "precompute-runner",
        generatedAt: Date.now(),
        ttlMs: defaultTtlMs,
        latencyMs: Date.now() - startTime,
        error: { code: "RUNNER_ERROR", message: e.message },
      };
    }
  }

  /**
   * Run precomputation WITH cache.
   * Cache key = sessionId + recentMessages hash.
   * Cache hit → returns cached result immediately.
   * Cache miss → runs precompute, caches result, returns.
   * 
   * @param {PrecomputeInput} input
   * @param {number} [ttlMs] - Override cache TTL for this call
   * @returns {Promise<PrecomputeOutput>}
   */
  async function runWithCache(input, ttlMs) {
    const startTime = Date.now();
    const effectiveTtl = ttlMs ?? defaultTtlMs;
    const { sessionId = "unknown", recentMessages = [] } = input;

    // Generate cache key
    const cacheKey = cache.makeKey(sessionId, recentMessages);

    // Try cache hit
    const cached = cache.get(cacheKey);
    if (cached) {
      return {
        ...cached,
        source: cached.source + " (cache-hit)",
        latencyMs: Date.now() - startTime,
      };
    }

    // Cache miss — run fresh precompute
    const result = await run(input);

    // Only cache successful results
    if (result.ok && result.memories && result.memories.length > 0) {
      cache.set(cacheKey, {
        ok: result.ok,
        sessionId: result.sessionId,
        memories: result.memories,
        source: result.source,
        generatedAt: result.generatedAt,
        ttlMs: effectiveTtl,
      }, effectiveTtl);
    }

    return {
      ...result,
      latencyMs: Date.now() - startTime,
    };
  }

  /**
   * Clear precompute cache (specific key or all).
   * @param {string} [key]
   */
  function clearCache(key) {
    cache.clear(key);
  }

  /**
   * Get cache statistics.
   */
  function cacheStats() {
    return cache.stats();
  }

  return {
    run,
    runWithCache,
    clearCache,
    cacheStats,
    // Exposed for testing
    _cache: cache,
    _memoryCore: memoryCore,
  };
}

export default createPrecomputeRunner;
