// engine/token-optimizer/deduplicator.ts — Content deduplication (SPEC-082 AC-04)
import { createHash } from 'node:crypto';
import { countTokens } from './counter.js';
/**
 * Session-scoped deduplication engine.
 * Tracks fingerprints of content already seen in a session.
 */
export class Deduplicator {
    // sessionId -> Set of content hashes
    seen = new Map();
    /**
     * Deduplicate content against previously seen content in this session.
     * Returns only lines not previously sent.
     */
    deduplicate(content, sessionId) {
        const originalTokens = countTokens(content).tokens;
        if (!this.seen.has(sessionId)) {
            this.seen.set(sessionId, new Set());
        }
        // Safe: we just set it above if it was missing
        const sessionSeen = this.seen.get(sessionId) ?? new Set();
        const lines = content.split('\n');
        const newLines = [];
        let duplicatesRemoved = 0;
        // Process in blocks for semantic deduplication
        const blocks = this.splitIntoBlocks(lines);
        for (const block of blocks) {
            const hash = this.hashBlock(block);
            if (sessionSeen.has(hash)) {
                duplicatesRemoved++;
            }
            else {
                sessionSeen.add(hash);
                newLines.push(...block);
            }
        }
        const deduplicated = newLines.join('\n');
        const deduplicatedTokens = countTokens(deduplicated).tokens;
        return {
            deduplicated,
            originalTokens,
            deduplicatedTokens,
            duplicatesRemoved,
            ratio: originalTokens > 0 ? 1 - deduplicatedTokens / originalTokens : 0,
        };
    }
    /**
     * Check if specific content was already seen in a session.
     */
    hasSeen(content, sessionId) {
        const sessionSeen = this.seen.get(sessionId);
        if (!sessionSeen) {
            return false;
        }
        return sessionSeen.has(this.hashBlock(content.split('\n')));
    }
    /**
     * Clear all fingerprints for a session.
     */
    clearSession(sessionId) {
        this.seen.delete(sessionId);
    }
    /**
     * Clear all sessions.
     */
    clear() {
        this.seen.clear();
    }
    /**
     * Get the number of unique fingerprints for a session.
     */
    getSessionSize(sessionId) {
        return this.seen.get(sessionId)?.size ?? 0;
    }
    // --- Internal helpers ---
    splitIntoBlocks(lines) {
        const blocks = [];
        let current = [];
        for (const line of lines) {
            if (line.trim() === '' && current.length > 0) {
                blocks.push(current);
                current = [];
            }
            else {
                current.push(line);
            }
        }
        if (current.length > 0) {
            blocks.push(current);
        }
        // If no blocks found (no empty lines), treat each line as a block
        if (blocks.length === 0 && lines.length > 0) {
            blocks.push(lines);
        }
        return blocks;
    }
    hashBlock(lines) {
        const normalized = lines.map((l) => l.trim()).join('\n');
        return createHash('sha256').update(normalized).digest('hex').slice(0, 16);
    }
}
//# sourceMappingURL=deduplicator.js.map