/**
 * @module timeline-api.respec
 *
 * specs for the high-level timeline api.
 */
import { firstOfEach, ifWe, iReckon, respecfully } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
const maam = `[${import.meta.url}]`, sir = maam;
import { getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
import { getLatestAddrs, getFromSpace } from '../witness/space/space-helper.mjs';
import { createTimeline, mut8Timeline, getHistory, appendToTimeline } from './timeline-api.mjs';
import { Metaspace_Innerspace } from '../witness/space/metaspace/metaspace-innerspace/metaspace-innerspace.mjs';
import { resetInnerSpaces } from '../witness/space/metaspace/metaspace-innerspace/metaspace-innerspace-helper.mjs';
const logalot = GLOBAL_LOG_A_LOT;
respecfully(sir, `[timeline-api]`, async () => {
    let space;
    let metaspace;
    firstOfEach(sir, async () => {
        resetInnerSpaces();
        metaspace = new Metaspace_Innerspace(undefined);
        await metaspace.initialize({
            getFnAlert: () => async ({ title, msg }) => { console.log(`[Alert] ${title}: ${msg}`); },
            getFnPrompt: () => async ({ title, msg }) => { console.log(`[Prompt] ${title}: ${msg}`); return ''; },
            getFnPromptPassword: () => async (title, msg) => { console.log(`[PromptPwd] ${title}: ${msg}`); return 'password'; },
        });
        space = metaspace.zeroSpace;
    });
    await ifWe(sir, `createTimeline`, async () => {
        const res = await createTimeline({
            space,
            metaspace,
            data: { note: 'first timeline' },
            ib: 'timeline',
        });
        const newTimeline = res.newIbGib;
        iReckon(sir, newTimeline).asTo('newTimeline').isGonnaBeTruthy();
        const addr = getIbGibAddr({ ibGib: newTimeline });
        // verify persistence
        const resGet = await getFromSpace({ addr, space });
        iReckon(sir, resGet.success).asTo('persisted success').isGonnaBeTrue();
        iReckon(sir, resGet.ibGibs[0]).asTo('persisted ibGib').isGonnaBe(newTimeline);
    });
    await ifWe(sir, `mut8Timeline`, async () => {
        // Arrange
        const resCreate = await createTimeline({
            space,
            metaspace,
            data: { note: 'orig' },
            ib: 'timeline',
        });
        const timeline = resCreate.newIbGib;
        console.log(`[mut8Timeline] created timeline n=${timeline.data.n}`);
        const initialPastLength = timeline.rel8ns?.past?.length || 0;
        console.log(`[mut8Timeline] created timeline past length=${initialPastLength}`);
        // Act
        const mutatedTimeline = await mut8Timeline({
            timeline,
            metaspace,
            space,
            mut8Opts: {
                dataToAddOrPatch: { note: 'mutated' },
            }
        });
        console.log(`[mut8Timeline] mutated timeline n=${mutatedTimeline.data.n}`);
        // Assert
        iReckon(sir, mutatedTimeline).asTo('mutatedTimeline').isGonnaBeTruthy();
        iReckon(sir, mutatedTimeline.data.note).asTo('data.note').isGonnaBe('mutated');
        // n should increment by 1
        const expectedN = (timeline.data.n || 0) + 1;
        iReckon(sir, mutatedTimeline.data.n).asTo('data.n').isGonnaBe(expectedN);
        iReckon(sir, mutatedTimeline.rel8ns.past).asTo('past').isGonnaBeTruthy();
        // past length should include the previous frame + whatever it had
        iReckon(sir, mutatedTimeline.rel8ns.past.length).asTo('past length').isGonnaBe(initialPastLength + 1);
        iReckon(sir, mutatedTimeline.rel8ns.past.at(-1)).asTo('past addr').isGonnaBe(getIbGibAddr({ ibGib: timeline }));
    });
    await ifWe(sir, `appendToTimeline`, async () => {
        // Arrange
        const resCreate = await createTimeline({
            space,
            metaspace,
            data: { n: 0 },
            ib: 'timeline',
        });
        let timeline = resCreate.newIbGib;
        const rel8nInfos = [{
                rel8nName: 'attachment',
                ibGibs: [timeline] // just attaching itself as a test, weird but valid structure
            }];
        const timelinePastLength = timeline.rel8ns?.past?.length || 0;
        // Act
        const appendedTimeline = await appendToTimeline({
            timeline,
            rel8nInfos,
            metaspace,
            space,
        });
        // Assert
        iReckon(sir, appendedTimeline).asTo('appendedTimeline').isGonnaBeTruthy();
        iReckon(sir, appendedTimeline.rel8ns.attachment).asTo('attachment').isGonnaBeTruthy();
        iReckon(sir, appendedTimeline.rel8ns.past?.length).asTo('past').isGonnaBe(timelinePastLength + 1);
    });
    await ifWe(sir, `getHistory`, async () => {
        // Arrange
        let t0 = (await createTimeline({ space, metaspace, data: { n: 0 }, ib: 'timeline' })).newIbGib;
        let t1 = await mut8Timeline({ timeline: t0, metaspace, space, mut8Opts: { dataToAddOrPatch: { n: 1 } } });
        let t2 = await mut8Timeline({ timeline: t1, metaspace, space, mut8Opts: { dataToAddOrPatch: { n: 2 } } });
        // Act
        const history = await getHistory({
            timeline: t2,
            metaspace,
            space,
        });
        // Assert
        // t0 is actually 2nd frame (fork -> mut8).
        // so history is [genesis, t0, t1]. length 3.
        iReckon(sir, history.orderedPastIbGibs.length).asTo('history length').isGonnaBe(3);
        // index 0 is genesis (the fork)
        // index 1 is t0
        // index 2 is t1
        iReckon(sir, getIbGibAddr({ ibGib: history.orderedPastIbGibs[1] })).asTo('t0').isGonnaBe(getIbGibAddr({ ibGib: t0 }));
        iReckon(sir, getIbGibAddr({ ibGib: history.orderedPastIbGibs[2] })).asTo('t1').isGonnaBe(getIbGibAddr({ ibGib: t1 }));
        iReckon(sir, getIbGibAddr({ ibGib: history.headIbGib })).asTo('head').isGonnaBe(getIbGibAddr({ ibGib: t2 }));
    });
    await ifWe(sir, `Concurrency & Locking (Stress Test)`, async () => {
        // const concurrencyLevels = [1, 2, 5, 50, 1000]; // too slow, because the locking mechanism doesn't scale well.
        const concurrencyLevels = [1, 2, 5, 50];
        for (const N of concurrencyLevels) {
            if (logalot) {
                console.log(`[Stress Test] Testing concurrency level: ${N}`);
            }
            // Arrange: Baseline
            let tInitial = (await createTimeline({
                space,
                metaspace,
                data: { n: 0, testId: `concurrent-${N}` },
                ib: 'timeline',
            })).newIbGib;
            // Initial state check
            const initialN = (tInitial.data.n || 0); // should be 1
            const historyInitial = await getHistory({ timeline: tInitial, metaspace, space });
            const initialHistoryLength = historyInitial.orderedPastIbGibs.length; // should be 1 (genesis)
            console.log(`already had addr for fork expected (I: 585a392f1c5aa0eedc1aedd8bbc31325)`);
            // Act: Launch N mutations simultaneously
            /**
             * I'm getting output (warnings?) that the transform ibgib addr
             * already exists. This stems from the fact that dna/transform
             * ibgibs do not have timestamps or anything, they simply are
             * memoized args to the transform functions. So we need something
             * unique _in the arg itself_, which in this case means in the
             * `dataToAddOrPatch` field.
             *
             * Note, however, that it is expected for the original `fork`
             * transform that created tInitial to be common and for it to
             * already have this fork dna ibgib. But this shouldn't happen for
             * each mut8 transform.
             */
            const uniqueIdsForUniqueTransforms = [];
            for (let i = 0; i < N; i++) {
                uniqueIdsForUniqueTransforms.push(await getUUID());
            }
            const promises = [];
            for (let i = 0; i < N; i++) {
                promises.push(mut8Timeline({
                    timeline: tInitial, // All start referencing the same initial frame (or logically the timeline itself)
                    // Note: In reality, if we pass the *same* object tInitial, mut8Timeline should
                    // resolve the *latest* inside the function.
                    metaspace,
                    space,
                    mut8Opts: {
                        dataToAddOrPatch: {
                            uniqueId: uniqueIdsForUniqueTransforms.pop(),
                        }
                    }
                    // No skipLock! We want them to contend.
                }));
            }
            try {
                // Wait for all to complete
                await Promise.all(promises);
                // Verification
                // 1. Get Latest Address
                const resLatest = await getLatestAddrs({ ibGibs: [tInitial], space });
                const latestAddr = resLatest.data.latestAddrsMap[getIbGibAddr({ ibGib: tInitial })];
                iReckon(sir, latestAddr).asTo(`[N=${N}] latestAddr exists`).isGonnaBeTruthy();
                // 2. Load the latest frame
                const resGet = await getFromSpace({ addr: latestAddr, space });
                const latestIbGib = resGet.ibGibs[0];
                // 3. Verify data.n
                // Initial n=1. We did N mutations.
                // Each mutation increments nCounter.
                const expectedN = initialN + N;
                iReckon(sir, latestIbGib.data.n).asTo(`[N=${N}] data.n should be ${expectedN}`).isGonnaBe(expectedN);
                // 4. Verify Lineage (no divergence)
                // History length should be initialHistoryLength + N
                const history = await getHistory({ timeline: latestIbGib, metaspace, space });
                const expectedHistoryLength = initialHistoryLength + N;
                iReckon(sir, history.orderedPastIbGibs.length).asTo(`[N=${N}] history length`).isGonnaBe(expectedHistoryLength);
                if (logalot) {
                    console.log(`[Stress Test] [N=${N}] SUCCESS. Final n=${latestIbGib.data.n}`);
                }
            }
            catch (err) {
                console.error(`[Stress Test] [N=${N}] FAILED:`, err);
                throw err;
            }
        }
    });
});
//# sourceMappingURL=timeline-api.respec.mjs.map