"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmittedEnvelopeRegistry = void 0;
exports.generateDeduplicationKey = generateDeduplicationKey;
exports.createEmittedEnvelopeRegistry = createEmittedEnvelopeRegistry;
const crypto_1 = __importDefault(require("crypto"));
/**
 * Generate a deduplication key for a DataEnvelope.
 *
 * Key composition (in order of preference):
 * 1. skillId:stepId:contentHash (stable across different execution ids)
 * 2. normalized meta.source (strip volatile execution suffix)
 * 3. skillId:stepId:title (last resort)
 *
 * Content hash is computed from rows (including row count) to catch
 * identical data emitted in different stages/rounds.
 */
function generateDeduplicationKey(envelope) {
    const meta = envelope.meta || {};
    const display = envelope.display || {};
    const skillId = meta.skillId || 'unknown';
    const stepId = meta.stepId || 'unknown';
    // Priority 1: Stable content key (preferred over source because source usually carries
    // execution id suffixes like "#mandatory_frame_..." or "#t1").
    const contentHash = computeContentHash(envelope.data);
    if (contentHash) {
        return `${skillId}:${stepId}:${contentHash}`;
    }
    // Priority 2: normalized source (strip execution suffix after '#')
    if (meta.source && typeof meta.source === 'string' && meta.source.length > 0) {
        const normalized = normalizeSource(meta.source);
        if (normalized)
            return normalized;
    }
    // Priority 3: Fallback to skillId:stepId:title
    const title = display.title || '';
    return `${skillId}:${stepId}:${title}`;
}
function normalizeSource(source) {
    const normalized = source.split('#')[0]?.trim();
    return normalized || source.trim();
}
/**
 * Compute a short hash of the envelope's data content.
 * Returns null if data is empty or not suitable for hashing.
 */
function computeContentHash(data) {
    if (!data)
        return null;
    try {
        // For table data, hash the rows
        if (data.rows && Array.isArray(data.rows)) {
            if (data.rows.length === 0)
                return 'empty';
            // Hash row count + first/last rows to balance accuracy vs performance.
            // Including rowCount avoids collisions when samples look similar but sizes differ.
            const sample = [
                ...data.rows.slice(0, 3),
                ...(data.rows.length > 6 ? data.rows.slice(-3) : []),
            ];
            const content = JSON.stringify({
                rowCount: data.rows.length,
                columns: Array.isArray(data.columns) ? data.columns : undefined,
                sample,
            });
            return crypto_1.default.createHash('md5').update(content).digest('hex').slice(0, 8);
        }
        // For other data types, stringify and hash
        const content = JSON.stringify(data);
        if (content.length < 10)
            return null; // Too small to be meaningful
        return crypto_1.default.createHash('md5').update(content).digest('hex').slice(0, 8);
    }
    catch {
        return null;
    }
}
/**
 * Session-scoped registry for tracking emitted envelopes.
 */
class EmittedEnvelopeRegistry {
    constructor() {
        this.emittedKeys = new Set();
        this.emitLog = [];
    }
    /**
     * Check if an envelope has already been emitted.
     */
    hasBeenEmitted(envelope) {
        const key = generateDeduplicationKey(envelope);
        return this.emittedKeys.has(key);
    }
    /**
     * Mark an envelope as emitted.
     * Returns the deduplication key used.
     */
    markAsEmitted(envelope) {
        const key = generateDeduplicationKey(envelope);
        this.emittedKeys.add(key);
        this.emitLog.push({
            key,
            timestamp: Date.now(),
            skillId: envelope.meta?.skillId,
        });
        return key;
    }
    /**
     * Filter out envelopes that have already been emitted.
     * Returns only the new (non-duplicate) envelopes.
     */
    filterNewEnvelopes(envelopes) {
        const newEnvelopes = [];
        for (const envelope of envelopes) {
            if (!this.hasBeenEmitted(envelope)) {
                this.markAsEmitted(envelope);
                newEnvelopes.push(envelope);
            }
        }
        return newEnvelopes;
    }
    /**
     * Get count of unique envelopes emitted.
     */
    get emittedCount() {
        return this.emittedKeys.size;
    }
    /**
     * Get the emission log for debugging.
     */
    getEmitLog() {
        return [...this.emitLog];
    }
    /**
     * Clear the registry (for new analysis session).
     */
    clear() {
        this.emittedKeys.clear();
        this.emitLog = [];
    }
    /**
     * Get statistics for debugging.
     */
    getStats() {
        return {
            totalEmitted: this.emittedKeys.size,
            duplicatesBlocked: this.emitLog.length - this.emittedKeys.size,
        };
    }
}
exports.EmittedEnvelopeRegistry = EmittedEnvelopeRegistry;
/**
 * Factory function to create a registry.
 * Typically one per analysis session.
 */
function createEmittedEnvelopeRegistry() {
    return new EmittedEnvelopeRegistry();
}
//# sourceMappingURL=emittedEnvelopeRegistry.js.map