/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ContextManager } from './contextManager.js';
import { createMockEnvironment, createDummyNode, } from './testing/contextTestUtils.js';
import { NodeType } from './graph/types.js';
describe('ContextManager - Multi-stage and Incremental GC', () => {
    let mockEnv;
    let mockOrchestrator;
    let mockChatHistory;
    let mockAdvancedTokenCalculator;
    beforeEach(() => {
        mockEnv = createMockEnvironment();
        mockOrchestrator = {
            setNodeProvider: vi.fn(),
            waitForPipelines: vi.fn().mockResolvedValue(undefined),
            executeTriggerSync: vi
                .fn()
                .mockImplementation(async (trigger, buffer) => buffer),
            executeIngestionPipeline: vi
                .fn()
                .mockImplementation(async (nodes) => nodes),
            shutdown: vi.fn(),
        };
        mockChatHistory = {
            all: vi.fn().mockReturnValue([]),
            getHistory: vi.fn().mockReturnValue([]),
            get: vi.fn().mockReturnValue([]),
            subscribe: vi.fn(),
        };
        mockAdvancedTokenCalculator = {
            getRawBaseUnits: vi.fn().mockReturnValue(0),
            getRawBaseUnitsForContent: vi.fn().mockReturnValue(0),
            calculateTokensAndBaseUnits: vi.fn(),
        };
    });
    const setupManager = (config) => {
        const sidecar = {
            name: 'test',
            config,
            buildPipelines: () => [],
            buildAsyncPipelines: () => [],
        };
        return new ContextManager(sidecar, mockEnv, mockEnv.tracer, mockOrchestrator, mockChatHistory, mockAdvancedTokenCalculator);
    };
    it('should emit NormalizeNeeded when normalizedTokens budget is exceeded', async () => {
        const manager = setupManager({
            budget: {
                retainedTokens: 100,
                normalizedTokens: 150,
                maxTokens: 300,
            },
        });
        const normalizeSpy = vi.fn();
        mockEnv.eventBus.onNormalizeNeeded(normalizeSpy);
        const consolidationSpy = vi.fn();
        mockEnv.eventBus.onConsolidationNeeded(consolidationSpy);
        // Mock token calculator for evaluateTriggers
        mockEnv.tokenCalculator.calculateConcreteListTokens = vi
            .fn()
            .mockImplementation((nodes) => nodes.reduce((sum, n) => 
        // Look for the mock tokens we attached to the dummy node
        sum + (n._mockTokens || 0), 0));
        const createNodeWithTokens = (id, type, tokens) => {
            const node = createDummyNode(id, type);
            // @ts-expect-error - attaching mock tokens for test
            node._mockTokens = tokens;
            return node;
        };
        // Create 4 nodes, each 80 tokens. Total = 320 tokens.
        // Node 1 (oldest): prior=240. 240 > 150 -> Normalization (Archiving trigger)
        // Node 2: prior=160. 160 > 150 -> Normalization
        // Node 3: prior=80. 80 <= 100 -> Retained
        // Node 4 (newest): prior=0. 0 <= 100 -> Retained
        const nodes = [
            createNodeWithTokens('ep1', NodeType.USER_PROMPT, 80),
            createNodeWithTokens('ep2', NodeType.AGENT_THOUGHT, 80),
            createNodeWithTokens('ep3', NodeType.TOOL_EXECUTION, 80),
            createNodeWithTokens('ep4', NodeType.TOOL_EXECUTION, 80),
        ];
        // @ts-expect-error - access private method for testing
        manager.buffer = { nodes };
        // Trigger evaluation manually with a dummy "new node" to bypass the empty check
        // @ts-expect-error - access private method for testing
        await manager.evaluateTriggers(nodes, new Set([nodes[3].id]), new Set());
        // Nodes 3 and 4 are retained.
        // Node 2 and Node 1 both fall out of normalizedTokens (160 > 150, 240 > 150).
        // Therefore they should trigger NormalizeNeeded. They should NOT trigger ConsolidationNeeded
        // because they exceeded normalized budget, so they skip the retained fallback.
        expect(consolidationSpy).not.toHaveBeenCalled();
        expect(normalizeSpy).toHaveBeenCalledOnce();
        const normalizeEvent = normalizeSpy.mock.calls[0][0];
        expect(normalizeEvent.targetNodeIds.has(nodes[0].id)).toBe(true);
        expect(normalizeEvent.targetNodeIds.has(nodes[1].id)).toBe(true);
        expect(normalizeEvent.targetNodeIds.has(nodes[2].id)).toBe(false);
    });
});
//# sourceMappingURL=contextManager.incremental.test.js.map