"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Historian = void 0;
/**
 * Historian builds a chronological history (oldest → newest) of typed values by traversing
 * a transaction's input ancestry and interpreting each output with a provided interpreter.
 *
 * Core ideas:
 * - You provide an interpreter `(tx, outputIndex) => T | undefined` that decodes one output
 *   into your domain value (e.g., kvstore entries). If it returns `undefined`, that output
 *   contributes nothing to history.
 * - Traversal follows `Transaction.inputs[].sourceTransaction` recursively, so callers must
 *   supply transactions whose inputs have `sourceTransaction` populated (e.g., via overlay
 *   history reconstruction).
 * - The traversal visits each transaction once (cycle-safe) and collects interpreted values
 *   in reverse-chronological order, then returns them as chronological (oldest-first).
 * - Optional caching support: provide a `historyCache` Map to cache complete history results
 *   and avoid re-traversing identical transaction chains with the same context.
 *
 * Usage:
 * - Construct with an interpreter (and optional cache)
 * - Call historian.buildHistory(tx, context) to get an array of values representing the history of a token over time.
 *
 * Example:
 *   const cache = new Map() // Optional: for caching repeated queries
 *   const historian = new Historian(interpreter, { historyCache: cache })
 *   const history = await historian.buildHistory(tipTransaction, context)
 *   // history: T[] (e.g., prior values for a protected kvstore key)
 *
 * Caching:
 * - Cache keys are generated from `interpreterVersion|txid|contextKey`
 * - Cached results are immutable snapshots to prevent external mutation
 * - Bump `interpreterVersion` when interpreter semantics change to invalidate old cache entries
 */
class Historian {
    /**
     * Create a new Historian instance
     *
     * @param interpreter - Function to interpret transaction outputs into typed values
     * @param options - Configuration options
     * @param options.debug - Enable debug logging (default: false)
     * @param options.historyCache - Optional external cache for complete history results
     * @param options.interpreterVersion - Version identifier for cache invalidation (default: 'v1')
     * @param options.ctxKeyFn - Custom function to serialize context for cache keys (default: JSON.stringify)
     */
    constructor(interpreter, options) {
        this.interpreter = interpreter;
        this.debug = options?.debug ?? false;
        // Configure caching (all optional)
        this.historyCache = options?.historyCache;
        this.interpreterVersion = options?.interpreterVersion ?? 'v1';
        this.ctxKeyFn = options?.ctxKeyFn ?? ((ctx) => {
            try {
                return JSON.stringify(ctx ?? null);
            }
            catch {
                return '';
            }
        });
    }
    historyKey(startTransaction, context) {
        const txid = startTransaction.id('hex');
        const ctxKey = this.ctxKeyFn(context);
        return `${this.interpreterVersion}|${txid}|${ctxKey}`;
    }
    /**
     * Build history by traversing input chain from a starting transaction
     * Returns values in chronological order (oldest first)
     *
     * If caching is enabled, will first check for cached results matching the
     * startTransaction and context. On cache miss, performs full traversal and
     * stores the result for future queries.
     *
     * @param startTransaction - The transaction to start traversal from
     * @param context - The context to pass to the interpreter
     * @returns Array of interpreted values in chronological order
     */
    async buildHistory(startTransaction, context) {
        // --- minimal cache fast path ---
        if (this.historyCache != null) {
            const cacheKey = this.historyKey(startTransaction, context);
            if (this.historyCache.has(cacheKey)) {
                const cached = this.historyCache.get(cacheKey);
                if (cached != null) {
                    if (this.debug)
                        console.log('[Historian] History cache hit:', cacheKey);
                    // Return a shallow copy to avoid external mutation of the cached array
                    return cached.slice();
                }
            }
        }
        const history = [];
        const visited = new Set();
        // Recursively traverse input transactions to build history
        const traverseHistory = async (transaction) => {
            const txid = transaction.id('hex');
            // Prevent infinite loops
            if (visited.has(txid)) {
                if (this.debug) {
                    console.log(`[Historian] Skipping already visited transaction: ${txid}`);
                }
                return;
            }
            visited.add(txid);
            if (this.debug) {
                console.log(`[Historian] Processing transaction: ${txid}`);
            }
            // Check all outputs in this transaction for interpretable values
            for (let outputIndex = 0; outputIndex < transaction.outputs.length; outputIndex++) {
                try {
                    // Try to interpret this output
                    const interpretedValue = await Promise.resolve(this.interpreter(transaction, outputIndex, context));
                    if (interpretedValue !== undefined) {
                        history.push(interpretedValue);
                        if (this.debug) {
                            console.log('[Historian] Added value to history:', interpretedValue);
                        }
                    }
                }
                catch (error) {
                    if (this.debug) {
                        console.log(`[Historian] Failed to interpret output ${outputIndex}:`, error);
                    }
                    // Skip outputs that can't be interpreted
                }
            }
            // Recursively traverse input transactions
            for (const input of transaction.inputs) {
                if (input.sourceTransaction != null) {
                    await traverseHistory(input.sourceTransaction);
                }
                else if (this.debug) {
                    console.log('[Historian] Input missing sourceTransaction, skipping');
                }
            }
        };
        // Start traversal from the provided transaction
        await traverseHistory(startTransaction);
        // History is built in reverse chronological order during traversal,
        // so we reverse it to return oldest-first
        const chronological = history.toReversed();
        if (this.historyCache != null) {
            const cacheKey = this.historyKey(startTransaction, context);
            // Store an immutable snapshot to avoid accidental external mutation
            this.historyCache.set(cacheKey, Object.freeze(chronological.slice()));
            if (this.debug)
                console.log('[Historian] History cached:', cacheKey);
        }
        return chronological;
    }
}
exports.Historian = Historian;
//# sourceMappingURL=Historian.js.map