#!/usr/bin/env node
/**
 * Active Memory Precompute Cache
 * PR-4: Async Precompute Shadow
 * 
 * File-based cache for precompute results.
 * Cache key = sessionId + hash of recentMessages.
 * Default TTL: 30 minutes.
 * 
 * Safety: only writes to .active-memory-precompute-cache/ directory.
 * Never touches real memory files.
 */

import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, readdirSync, statSync } from "node:fs";
import { join, resolve } from "node:path";
import { createHash } from "node:crypto";

/**
 * @param {string} workspaceRoot
 */
export function createPrecomputeCache(workspaceRoot) {
  const root = resolve(workspaceRoot);
  const cacheDir = join(root, ".active-memory-precompute-cache");

  let hits = 0;
  let misses = 0;

  function ensureDir() {
    if (!existsSync(cacheDir)) {
      mkdirSync(cacheDir, { recursive: true });
    }
  }

  /**
   * Generate a cache key from sessionId + recentMessages.
   */
  function makeKey(sessionId, recentMessages) {
    const messagesStr = (recentMessages || [])
      .map(m => `${m.role || ""}:${(m.content || "").substring(0, 200)}`)
      .join("|");
    
    const hash = createHash("sha256")
      .update(`${sessionId}:${messagesStr}`)
      .digest("hex")
      .substring(0, 16);

    return `${sessionId || "default"}__${hash}`;
  }

  /**
   * Try to read a cached entry. Returns null on miss or expiry.
   */
  function get(key) {
    ensureDir();
    const filePath = join(cacheDir, `${key}.json`);

    if (!existsSync(filePath)) {
      misses++;
      return null;
    }

    try {
      const raw = readFileSync(filePath, "utf8");
      const entry = JSON.parse(raw);

      // Check TTL
      if (entry.expiresAt && Date.now() > entry.expiresAt) {
        // Expired — remove
        try { unlinkSync(filePath); } catch {}
        misses++;
        return null;
      }

      hits++;
      return entry.data;
    } catch {
      misses++;
      return null;
    }
  }

  /**
   * Write a cache entry with TTL.
   * @param {string} key
   * @param {object} data
   * @param {number} ttlMs
   */
  function set(key, data, ttlMs = 30 * 60 * 1000) {
    ensureDir();

    const entry = {
      key,
      data,
      createdAt: Date.now(),
      expiresAt: Date.now() + ttlMs,
      ttlMs,
    };

    const filePath = join(cacheDir, `${key}.json`);
    writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf8");
  }

  /**
   * Clear a specific key or all keys.
   * @param {string} [key] - If omitted, clears ALL test cache.
   */
  function clear(key) {
    if (key) {
      const filePath = join(cacheDir, `${key}.json`);
      if (existsSync(filePath)) {
        unlinkSync(filePath);
      }
    } else {
      // Clear all
      if (existsSync(cacheDir)) {
        const files = readdirSync(cacheDir).filter(f => f.endsWith(".json"));
        for (const f of files) {
          try { unlinkSync(join(cacheDir, f)); } catch {}
        }
      }
    }
  }

  /**
   * Get cache statistics.
   */
  function stats() {
    ensureDir();
    let fileCount = 0;
    let totalSize = 0;

    try {
      const files = readdirSync(cacheDir).filter(f => f.endsWith(".json"));
      for (const f of files) {
        try {
          const s = statSync(join(cacheDir, f));
          totalSize += s.size;
          fileCount++;
        } catch {}
      }
    } catch {}

    return {
      hits,
      misses,
      hitRate: hits + misses > 0 ? Math.round((hits / (hits + misses)) * 100) : 0,
      cachedEntries: fileCount,
      totalSizeBytes: totalSize,
      cacheDir,
    };
  }

  /**
   * Reset hit/miss counters (not files).
   */
  function resetCounters() {
    hits = 0;
    misses = 0;
  }

  return { get, set, clear, stats, makeKey, resetCounters, cacheDir };
}
