/**
 * Structured audit log for permission decisions.
 *
 * DecisionLog provides an in-memory circular buffer of PermissionDecision
 * records with structured output suitable for debugging and compliance auditing.
 *
 * Decisions are appended on every evaluation. The buffer is bounded to prevent
 * unbounded memory growth during long sessions.
 */
// ── Configuration ─────────────────────────────────────────────────────────────────
/** Maximum number of decisions held in the circular buffer. */
const DEFAULT_MAX_ENTRIES = 1000;
// ── DecisionLog ───────────────────────────────────────────────────────────────────
/**
 * DecisionLog — Bounded circular buffer for permission decision audit records.
 *
 * Thread-safe for single-threaded Bun/Node runtimes (no async gaps in write path).
 * Oldest entries are evicted when the buffer is full; `totalRecorded` and
 * `summary().evicted` expose rollover.
 *
 * Implements a true O(1) ring buffer with head/tail indexing to avoid O(n)
 * Array.shift() evictions.
 */
export class DecisionLog {
    /** Ring buffer storage (fixed-size array). */
    buffer;
    /** Write head: index where next entry will be written. */
    head = 0;
    /** Total entries currently stored (≤ maxEntries). */
    count = 0;
    seq = 0;
    maxEntries;
    constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
        this.maxEntries = maxEntries;
        this.buffer = new Array(maxEntries);
    }
    /**
     * append — Records a permission decision in the audit log.
     *
     * If the buffer has reached `maxEntries`, the oldest entry is evicted in O(1)
     * time via ring buffer head advancement. Rollover is observable via
     * `totalRecorded` and `summary().evicted`.
     *
     * @param decision — The completed PermissionDecision to record.
     */
    append(decision) {
        this.seq += 1;
        const entry = { seq: this.seq, decision };
        this.buffer[this.head] = entry;
        this.head = (this.head + 1) % this.maxEntries;
        if (this.count < this.maxEntries)
            this.count += 1;
    }
    /**
     * toArray — Returns all current entries in chronological order (oldest first).
     */
    toArray() {
        if (this.count === 0)
            return [];
        const result = new Array(this.count);
        // tail = oldest entry index in ring buffer
        const tail = this.count < this.maxEntries ? 0 : this.head;
        for (let i = 0; i < this.count; i++) {
            result[i] = this.buffer[(tail + i) % this.maxEntries];
        }
        return result;
    }
    /**
     * query — Returns filtered log entries in chronological order.
     *
     * @param filters — Optional filter criteria.
     */
    query(filters = {}) {
        let results = this.toArray();
        if (filters.toolName !== undefined) {
            results = results.filter((e) => e.decision.toolName === filters.toolName);
        }
        if (filters.allowed !== undefined) {
            results = results.filter((e) => e.decision.allowed === filters.allowed);
        }
        if (filters.since !== undefined) {
            results = results.filter((e) => e.decision.timestamp >= (filters.since ?? 0));
        }
        if (filters.limit !== undefined && filters.limit > 0) {
            results = results.slice(-filters.limit);
        }
        return results;
    }
    /**
     * latest — Returns the most recent decision, or undefined if the log is empty.
     */
    latest() {
        if (this.count === 0)
            return undefined;
        // Most recent entry is at (head - 1 + maxEntries) % maxEntries
        const latestIdx = (this.head - 1 + this.maxEntries) % this.maxEntries;
        return this.buffer[latestIdx];
    }
    /**
     * size — Returns the current number of entries in the buffer.
     */
    get size() {
        return this.count;
    }
    /**
     * totalRecorded — Returns the total number of decisions ever recorded
     * (including evicted entries). Monotonically increasing.
     */
    get totalRecorded() {
        return this.seq;
    }
    /**
     * clear — Empties the log and resets the sequence counter.
     * Primarily useful in tests.
     */
    clear() {
        this.buffer.fill(undefined);
        this.head = 0;
        this.count = 0;
        this.seq = 0;
    }
    /**
     * summary — Returns aggregate counts of allow/deny decisions.
     */
    summary() {
        const entries = this.toArray();
        const allowed = entries.filter((e) => e.decision.allowed).length;
        return {
            total: this.count,
            allowed,
            denied: this.count - allowed,
            evicted: Math.max(0, this.seq - this.count),
        };
    }
}
