// NERF Memory Bridge — Connects legacy CLI memory pipeline to nerf-engine extraction
//
// Enriches extracted memories with security entity metadata (SemanticCompressor)
// and deduplicates against existing entries (SemanticSynthesizer) before storage.
// All bridge operations are non-fatal: failures log and fall through, never blocking auto-mode.
import { fileURLToPath } from 'node:url';
import { join, dirname } from 'node:path';
import { pathToFileURL } from 'node:url';
import { existsSync } from 'node:fs';
// ─── Debug Logger ───────────────────────────────────────────────────────────
const debug = process.env.NERF_DEBUG === '1'
    ? (msg) => process.stderr.write(`[nerf] memory bridge: ${msg}\n`)
    : () => { };
// ─── Resolve CLI Memory Module Paths ────────────────────────────────────────
// The cli/lib/memory/ modules are plain .js files outside the src/ tree.
// We use absolute file:// URLs to avoid the test hook's .js→.ts rewriting.
const _bridgeDir = dirname(fileURLToPath(import.meta.url));
/**
 * Resolve the project root by walking up from the bridge file until we find
 * a directory that contains cli/lib/memory/compressor.js. This makes the
 * path resolution work both from the source tree (src/resources/extensions/…)
 * and from test compilation output (dist-test/src/resources/extensions/…).
 *
 * Called lazily (not at module load time) so that _resetBridgeState() in tests
 * can clear cached module references and force a fresh resolution on next call.
 */
function _resolveMemoryPath(filename) {
    let dir = _bridgeDir;
    for (let i = 0; i < 8; i++) {
        if (existsSync(join(dir, 'cli', 'lib', 'memory', 'compressor.js'))) {
            return pathToFileURL(join(dir, 'cli', 'lib', 'memory', filename)).href;
        }
        const parent = dirname(dir);
        if (parent === dir)
            break;
        dir = parent;
    }
    // Fallback: 4 levels up (original source-tree behaviour)
    dir = _bridgeDir;
    for (let i = 0; i < 4; i++)
        dir = dirname(dir);
    return pathToFileURL(join(dir, 'cli', 'lib', 'memory', filename)).href;
}
// Paths are resolved lazily on first use (see getCompressorModule etc.)
// so that _resetBridgeState() in tests clears the module cache cleanly.
const _compressorPath = _resolveMemoryPath('compressor.js');
const _synthesizerPath = _resolveMemoryPath('synthesizer.js');
const _enginePath = _resolveMemoryPath('engine.js');
// ─── Lazy Module Cache ──────────────────────────────────────────────────────
let _compressorModule = null;
let _synthesizerModule = null;
let _engineModule = null;
async function getCompressorModule() {
    if (_compressorModule)
        return _compressorModule;
    try {
        _compressorModule = await import(_compressorPath);
        return _compressorModule;
    }
    catch (err) {
        debug(`compressor import failed: ${err?.message ?? err}`);
        return null;
    }
}
async function getSynthesizerModule() {
    if (_synthesizerModule)
        return _synthesizerModule;
    try {
        _synthesizerModule = await import(_synthesizerPath);
        return _synthesizerModule;
    }
    catch (err) {
        debug(`synthesizer import failed: ${err?.message ?? err}`);
        return null;
    }
}
async function getEngineModule() {
    if (_engineModule)
        return _engineModule;
    try {
        _engineModule = await import(_enginePath);
        return _engineModule;
    }
    catch (err) {
        debug(`engine import failed: ${err?.message ?? err}`);
        return null;
    }
}
// ─── NerfMemory Singleton ───────────────────────────────────────────────────
let _nerfMemoryInstance = null;
let _exitHandlerRegistered = false;
/**
 * Lazy singleton for the legacy NerfMemory engine.
 * Opens on first call, closes via process.on('exit').
 * Returns null if ~/.nerf/memory/default is not writable.
 */
export function getNerfMemoryInstance() {
    if (_nerfMemoryInstance)
        return _nerfMemoryInstance;
    try {
        // Synchronous check: can we resolve the engine module?
        // The module is cached after first async load, so this works
        // only after getEngineModule() has been called at least once.
        if (!_engineModule)
            return null;
        const { NerfMemory } = _engineModule;
        const homeDir = process.env.HOME || process.env.USERPROFILE || '';
        if (!homeDir) {
            debug('no HOME dir, skipping legacy memory');
            return null;
        }
        const memoryDir = `${homeDir}/.nerf/memory/default`;
        _nerfMemoryInstance = new NerfMemory(memoryDir);
        if (!_exitHandlerRegistered) {
            _exitHandlerRegistered = true;
            process.on('exit', () => {
                try {
                    _nerfMemoryInstance?.close();
                }
                catch {
                    // swallow — process is exiting
                }
            });
        }
        debug('legacy NerfMemory opened');
        return _nerfMemoryInstance;
    }
    catch (err) {
        debug(`NerfMemory init failed: ${err?.message ?? err}`);
        return null;
    }
}
// ─── Compressor Bridge ──────────────────────────────────────────────────────
/**
 * Enrich a transcript with security entity extraction via SemanticCompressor.
 * Uses heuristic mode only (no llmClient) — no extra LLM calls.
 *
 * Converts the transcript string into DialogueTurn objects, runs compress(),
 * and returns CompressedEntry-like objects with extracted security entities.
 */
export async function enrichWithCompressor(transcript, unitType, unitId) {
    try {
        const mod = await getCompressorModule();
        if (!mod) {
            debug('compressor unavailable, skipping enrichment');
            return [];
        }
        const { SemanticCompressor, DialogueTurn } = mod;
        // Heuristic only — no llmClient
        const compressor = new SemanticCompressor({ llmClient: null });
        // Convert transcript into DialogueTurn objects
        // Split by double-newline paragraphs and assign alternating roles
        const paragraphs = transcript.split(/\n{2,}/).filter((p) => p.trim());
        const turns = paragraphs.map((text, i) => new DialogueTurn({
            turnId: i,
            role: i % 2 === 0 ? 'user' : 'assistant',
            content: text.trim(),
            timestamp: new Date().toISOString(),
        }));
        if (turns.length === 0) {
            debug('no turns produced from transcript');
            return [];
        }
        const entries = await compressor.compress(turns, unitId, unitType);
        debug(`compressor produced ${entries.length} entries from ${turns.length} turns`);
        return entries;
    }
    catch (err) {
        debug(`enrichWithCompressor error: ${err?.message ?? err}`);
        return [];
    }
}
// ─── Synthesizer Bridge ─────────────────────────────────────────────────────
/**
 * Deduplicate new memory actions against existing memories using SemanticSynthesizer.
 *
 * Converts Memory[] (nerf-engine format) to CompressedEntry-like objects for comparison,
 * converts MemoryAction[] (CREATE actions) to CompressedEntry-like for synthesis,
 * runs synthesize(), and returns only the truly new/merged actions.
 */
export async function deduplicateWithSynthesizer(newActions, existingMemories) {
    try {
        // If there are no CREATE actions, nothing to deduplicate
        const createActions = newActions.filter(a => a.action === 'CREATE');
        const nonCreateActions = newActions.filter(a => a.action !== 'CREATE');
        if (createActions.length === 0) {
            debug('no CREATE actions to deduplicate');
            return newActions;
        }
        const mod = await getSynthesizerModule();
        if (!mod) {
            debug('synthesizer unavailable, skipping dedup');
            return newActions;
        }
        const compMod = await getCompressorModule();
        if (!compMod) {
            debug('compressor module needed for entry conversion, skipping dedup');
            return newActions;
        }
        const { SemanticSynthesizer } = mod;
        const { CompressedEntry } = compMod;
        const synthesizer = new SemanticSynthesizer();
        // Convert CREATE actions to CompressedEntry-like objects
        const newEntries = createActions.map((a) => new CompressedEntry({
            losslessRestatement: a.content,
            memoryType: 'note',
            confidence: 'confirmed',
            keywords: [],
            targets: [],
            cveIds: [],
            mitreAttack: [],
            hashes: [],
            toolsUsed: [],
            persons: [],
            entities: [],
            sourceTurns: [],
        }));
        // Convert existing Memory objects to CompressedEntry-like for comparison
        const existingEntries = existingMemories.map(m => new CompressedEntry({
            entryId: m.id,
            losslessRestatement: m.content,
            memoryType: 'note',
            confidence: 'confirmed',
            keywords: [],
            targets: [],
            cveIds: [],
            mitreAttack: [],
            hashes: [],
            toolsUsed: [],
            persons: [],
            entities: [],
            sourceTurns: [],
        }));
        // Run synthesis
        const result = synthesizer.synthesize(newEntries, existingEntries);
        const stats = result.stats || {};
        debug(`synthesizer: ${stats.deduplicated ?? 0} deduped, ${stats.merged ?? 0} merged, ${stats.new ?? 0} new`);
        // Rebuild CREATE actions from the truly-new entries only
        const survivingContents = new Set(result.newEntries.map((e) => e.losslessRestatement));
        const filteredCreates = createActions.filter((a) => survivingContents.has(a.content));
        return [...nonCreateActions, ...filteredCreates];
    }
    catch (err) {
        debug(`deduplicateWithSynthesizer error: ${err?.message ?? err}`);
        return newActions;
    }
}
// ─── Legacy Store Bridge ────────────────────────────────────────────────────
/**
 * Store compressed entries in the legacy NerfMemory engine for cross-session retrieval.
 */
export async function storeInLegacyMemory(entries) {
    try {
        if (entries.length === 0)
            return;
        // Ensure engine module is loaded
        await getEngineModule();
        const memory = getNerfMemoryInstance();
        if (!memory) {
            debug('legacy memory unavailable, skipping store');
            return;
        }
        const { MemoryEntry } = _engineModule;
        let stored = 0;
        for (const entry of entries) {
            try {
                const legacyEntry = new MemoryEntry({
                    content: entry.losslessRestatement,
                    memoryType: entry.memoryType || 'note',
                    confidence: entry.confidence || 'confirmed',
                    targets: entry.targets || [],
                    cveIds: entry.cveIds || [],
                    mitreAttack: entry.mitreAttack || [],
                    severity: entry.severity || '',
                    keywords: entry.keywords || [],
                    timestamp: entry.timestamp || new Date().toISOString(),
                    engagementId: entry.engagementId || '',
                    sourceSkill: entry.sourceSkill || '',
                });
                memory.store(legacyEntry);
                stored++;
            }
            catch (err) {
                debug(`failed to store entry: ${err?.message ?? err}`);
            }
        }
        debug(`stored ${stored}/${entries.length} entries in legacy memory`);
    }
    catch (err) {
        debug(`storeInLegacyMemory error: ${err?.message ?? err}`);
    }
}
// ─── Testing Helpers ────────────────────────────────────────────────────────
/** Reset module cache and singleton (testing only). */
export function _resetBridgeState() {
    try {
        _nerfMemoryInstance?.close();
    }
    catch {
        // swallow
    }
    _nerfMemoryInstance = null;
    _compressorModule = null;
    _synthesizerModule = null;
    _engineModule = null;
}
