import { describe, expect, it } from 'vitest';

import {
    ChangeJournal,
    createReadModelOutboxPayload,
    normalizeReadModelRevisionKeys,
    READ_MODEL_DOMAINS,
    type ReadModelDomain,
} from '../src/realtime/changeJournal.js';

describe('ChangeJournal', () => {
    it('keeps viewer-filtering semantics as distinct durable domains', () => {
        expect(READ_MODEL_DOMAINS).toEqual(
            expect.arrayContaining([
                'map.general',
                'lobby.general',
                'contacts.world',
                'reserved.general',
                'messages.mailbox',
                'dashboard.global',
            ])
        );
    });

    it('dedupes keys and returns them in a stable lock order', () => {
        const journal = new ChangeJournal()
            .mark('nation.content', 4)
            .mark('general.content', 9)
            .mark('nation.content', 4)
            .mark('general.content', 2)
            .mark('map.world');

        expect(journal.size).toBe(4);
        expect(journal.snapshot()).toEqual([
            { domain: 'general.content', entityId: 2 },
            { domain: 'general.content', entityId: 9 },
            { domain: 'map.world', entityId: 0 },
            { domain: 'nation.content', entityId: 4 },
        ]);
    });

    it('merges another journal without sharing mutable state', () => {
        const source = new ChangeJournal().mark('records.general', 7);
        const destination = new ChangeJournal().mark('records.global').merge(source);

        source.clear();

        expect(source.isEmpty).toBe(true);
        expect(destination.snapshot()).toEqual([
            { domain: 'records.general', entityId: 7 },
            { domain: 'records.global', entityId: 0 },
        ]);
    });

    it('rejects unknown domains and unstable entity IDs at runtime', () => {
        expect(() => new ChangeJournal().mark('unknown' as ReadModelDomain, 1)).toThrow(TypeError);
        expect(() => new ChangeJournal().mark('general.content', -1)).toThrow(RangeError);
        expect(() => new ChangeJournal().mark('general.content', Number.MAX_SAFE_INTEGER + 1)).toThrow(RangeError);
    });
});

describe('durable read-model invalidation contract', () => {
    it('normalizes an arbitrary iterable independently of the collector', () => {
        expect(
            normalizeReadModelRevisionKeys([
                { domain: 'front.nation', entityId: 3 },
                { domain: 'front.global', entityId: 0 },
                { domain: 'front.nation', entityId: 3 },
            ])
        ).toEqual([
            { domain: 'front.global', entityId: 0 },
            { domain: 'front.nation', entityId: 3 },
        ]);
    });

    it('serializes bigint revisions to a compact JSON-safe payload', () => {
        const payload = createReadModelOutboxPayload({
            revisions: [
                { domain: 'world.content', entityId: 0, revision: 12n },
                { domain: 'general.content', entityId: 7, revision: 4n },
            ],
        });

        expect(payload).toEqual({
            version: 1,
            changes: [
                ['general.content', 7, '4'],
                ['world.content', 0, '12'],
            ],
        });
        expect(() => JSON.stringify(payload)).not.toThrow();
    });
});
