"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_test_1 = require("node:test");
const strict_1 = __importDefault(require("node:assert/strict"));
const fs = __importStar(require("node:fs"));
const os = __importStar(require("node:os"));
const path = __importStar(require("node:path"));
const logger_1 = require("../logger");
const sessions_1 = require("../sessions");
function makeStubRunner(opts = {}) {
    const calls = [];
    return {
        name: opts.name ?? 'stub',
        calls,
        isAvailable: () => opts.available !== false,
        run: async (prompt) => {
            calls.push(prompt);
            return {
                success: opts.success !== false,
                output: opts.output ?? '--stub summary--',
                error: '',
            };
        },
    };
}
function configWith(overrides) {
    return { autoCompress: true, compressThreshold: 12000, ...overrides };
}
function mkComment(id, text, author = 'alice') {
    return { id, text, author, authorId: author, date: 0 };
}
// ─── shouldCompress ───────────────────────────────────────────────────────────
(0, node_test_1.describe)('shouldCompress', () => {
    (0, node_test_1.it)('returns false when prompt length is just under the threshold', () => {
        strict_1.default.equal((0, sessions_1.shouldCompress)('a'.repeat(99), 100), false);
    });
    (0, node_test_1.it)('returns false when prompt length equals the threshold', () => {
        strict_1.default.equal((0, sessions_1.shouldCompress)('a'.repeat(100), 100), false);
    });
    (0, node_test_1.it)('returns true when prompt length is just over the threshold', () => {
        strict_1.default.equal((0, sessions_1.shouldCompress)('a'.repeat(101), 100), true);
    });
});
// ─── fingerprintComments ──────────────────────────────────────────────────────
(0, node_test_1.describe)('fingerprintComments', () => {
    (0, node_test_1.it)('produces the same hash for identical comment lists', () => {
        const a = [mkComment('1', 'hello'), mkComment('2', 'world')];
        const b = [mkComment('1', 'hello'), mkComment('2', 'world')];
        strict_1.default.equal((0, sessions_1.fingerprintComments)(a), (0, sessions_1.fingerprintComments)(b));
    });
    (0, node_test_1.it)('changes when a comment text is edited', () => {
        const a = [mkComment('1', 'hello'), mkComment('2', 'world')];
        const b = [mkComment('1', 'hello'), mkComment('2', 'WORLD')];
        strict_1.default.notEqual((0, sessions_1.fingerprintComments)(a), (0, sessions_1.fingerprintComments)(b));
    });
    (0, node_test_1.it)('changes when a comment id differs', () => {
        const a = [mkComment('1', 'hello'), mkComment('2', 'world')];
        const b = [mkComment('1', 'hello'), mkComment('3', 'world')];
        strict_1.default.notEqual((0, sessions_1.fingerprintComments)(a), (0, sessions_1.fingerprintComments)(b));
    });
    (0, node_test_1.it)('changes when comments are reordered', () => {
        const a = [mkComment('1', 'hello'), mkComment('2', 'world')];
        const b = [mkComment('2', 'world'), mkComment('1', 'hello')];
        strict_1.default.notEqual((0, sessions_1.fingerprintComments)(a), (0, sessions_1.fingerprintComments)(b));
    });
});
// ─── buildCompressedContext ──────────────────────────────────────────────────
(0, node_test_1.describe)('buildCompressedContext', () => {
    let tmpDir;
    let origCwd;
    (0, node_test_1.beforeEach)(() => {
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-sessions-'));
        origCwd = process.cwd();
        process.chdir(tmpDir);
        // silence warnings from deterministic-truncation fallback path
        node_test_1.mock.method(logger_1.logger, 'warn', () => { });
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(origCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
        node_test_1.mock.restoreAll();
    });
    function longComment(id, sizeChars, author = 'alice') {
        return mkComment(id, 'x'.repeat(sizeChars), author);
    }
    (0, node_test_1.it)('preserves the latest comment verbatim and includes the summary; writes a session file', async () => {
        const comments = [
            longComment('a', 5000, 'alice'),
            longComment('b', 5000, 'bob'),
            mkComment('c', 'LATEST-UNIQUE-TEXT', 'carol'),
        ];
        const runner = makeStubRunner({ output: 'SUMMARY-OF-EARLIER' });
        const result = await (0, sessions_1.buildCompressedContext)(comments, 'task-1', [runner], configWith({ compressThreshold: 100 }));
        strict_1.default.ok(result.includes('Summary of earlier conversation'));
        strict_1.default.ok(result.includes('SUMMARY-OF-EARLIER'));
        strict_1.default.ok(result.includes('Latest comment:'));
        strict_1.default.ok(result.includes('carol: LATEST-UNIQUE-TEXT'));
        // earlier comments should not appear verbatim in the compressed output
        strict_1.default.equal(result.includes('alice: '), false);
        strict_1.default.equal(result.includes('bob: '), false);
        strict_1.default.equal(runner.calls.length, 1);
        const sessionPath = (0, sessions_1.getSessionPath)('task-1');
        strict_1.default.ok(fs.existsSync(sessionPath), 'session file written');
        const session = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
        strict_1.default.equal(session.taskId, 'task-1');
        strict_1.default.equal(session.summary, 'SUMMARY-OF-EARLIER');
        strict_1.default.equal(session.lastCommentId, 'c');
        strict_1.default.equal(typeof session.fingerprint, 'string');
        strict_1.default.equal(session.fingerprint.length > 0, true);
    });
    (0, node_test_1.it)('returns raw context unchanged when autoCompress is false', async () => {
        const comments = [
            longComment('a', 5000, 'alice'),
            longComment('b', 5000, 'bob'),
            mkComment('c', 'latest', 'carol'),
        ];
        const runner = makeStubRunner({ output: 'should-not-appear' });
        const result = await (0, sessions_1.buildCompressedContext)(comments, 'task-noauto', [runner], configWith({ autoCompress: false, compressThreshold: 100 }));
        strict_1.default.ok(result.includes('Conversation context:'));
        strict_1.default.ok(result.includes('alice: '));
        strict_1.default.ok(result.includes('bob: '));
        strict_1.default.ok(result.includes('carol: latest'));
        strict_1.default.equal(result.includes('Summary of earlier conversation'), false);
        strict_1.default.equal(result.includes('should-not-appear'), false);
        strict_1.default.equal(runner.calls.length, 0);
        strict_1.default.equal(fs.existsSync((0, sessions_1.getSessionPath)('task-noauto')), false);
    });
    (0, node_test_1.it)('reuses the cached summary on a second call with the same fingerprint (no runner invocation)', async () => {
        const comments = [
            longComment('a', 5000, 'alice'),
            longComment('b', 5000, 'bob'),
            mkComment('c', 'latest', 'carol'),
        ];
        const cfg = configWith({ compressThreshold: 100 });
        const first = makeStubRunner({ output: 'FIRST-SUMMARY' });
        const r1 = await (0, sessions_1.buildCompressedContext)(comments, 'task-cached', [first], cfg);
        strict_1.default.ok(r1.includes('FIRST-SUMMARY'));
        strict_1.default.equal(first.calls.length, 1);
        // second call: same earlier comments → same fingerprint → cache hit
        const second = makeStubRunner({ output: 'WOULD-REPLACE' });
        const r2 = await (0, sessions_1.buildCompressedContext)(comments, 'task-cached', [second], cfg);
        strict_1.default.ok(r2.includes('FIRST-SUMMARY'));
        strict_1.default.equal(r2.includes('WOULD-REPLACE'), false);
        strict_1.default.equal(second.calls.length, 0);
    });
    (0, node_test_1.it)('re-summarizes when an earlier comment is edited (fingerprint miss)', async () => {
        const last = mkComment('c', 'latest', 'carol');
        const cfg = configWith({ compressThreshold: 100 });
        const r1 = makeStubRunner({ output: 'SUM-V1' });
        await (0, sessions_1.buildCompressedContext)([longComment('a', 5000, 'alice'), longComment('b', 5000, 'bob'), last], 'task-edit', [r1], cfg);
        strict_1.default.equal(r1.calls.length, 1);
        // edit text of an earlier comment → fingerprint changes → runner re-invoked
        const r2 = makeStubRunner({ output: 'SUM-V2' });
        const edited = await (0, sessions_1.buildCompressedContext)([longComment('a', 5000, 'alice'), mkComment('b', 'edited body ' + 'y'.repeat(5000), 'bob'), last], 'task-edit', [r2], cfg);
        strict_1.default.ok(edited.includes('SUM-V2'));
        strict_1.default.equal(r2.calls.length, 1);
    });
    (0, node_test_1.it)('re-summarizes when a new latest comment arrives (previous last moves into earlier)', async () => {
        const earlier = [longComment('a', 5000, 'alice'), longComment('b', 5000, 'bob')];
        const cfg = configWith({ compressThreshold: 100 });
        const r1 = makeStubRunner({ output: 'SUM-A' });
        await (0, sessions_1.buildCompressedContext)([...earlier, mkComment('c', 'first latest', 'carol')], 'task-newlast', [r1], cfg);
        strict_1.default.equal(r1.calls.length, 1);
        const r2 = makeStubRunner({ output: 'SUM-B' });
        const result = await (0, sessions_1.buildCompressedContext)([
            ...earlier,
            mkComment('c', 'first latest', 'carol'),
            mkComment('d', 'newer latest', 'dan'),
        ], 'task-newlast', [r2], cfg);
        strict_1.default.ok(result.includes('SUM-B'));
        strict_1.default.ok(result.includes('dan: newer latest'));
        strict_1.default.equal(r2.calls.length, 1);
    });
    (0, node_test_1.it)('falls back to deterministic truncation when all runners are unavailable and still writes a session', async () => {
        const comments = [
            longComment('a', 5000, 'alice'),
            longComment('b', 5000, 'bob'),
            mkComment('c', 'latest', 'carol'),
        ];
        const unavailable = makeStubRunner({ available: false, output: 'unused' });
        const result = await (0, sessions_1.buildCompressedContext)(comments, 'task-fallback', [unavailable], configWith({ compressThreshold: 100 }));
        strict_1.default.ok(result.includes('Summary of earlier conversation'));
        strict_1.default.ok(result.includes('carol: latest'));
        strict_1.default.equal(unavailable.calls.length, 0);
        const sessionPath = (0, sessions_1.getSessionPath)('task-fallback');
        strict_1.default.ok(fs.existsSync(sessionPath), 'session file must be written on fallback');
        const session = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
        strict_1.default.equal(session.taskId, 'task-fallback');
        strict_1.default.equal(session.lastCommentId, 'c');
        strict_1.default.equal(typeof session.summary, 'string');
        strict_1.default.equal(session.summary.length > 0, true);
        // deterministic truncation: summary stays within ~first 1500 + last 1500 chars (+ separator)
        strict_1.default.ok(session.summary.length < 3100);
    });
});
//# sourceMappingURL=sessions.test.js.map