/**
 * @module sync-conflict.respec
 *
 * Verifies Conflict Resolution strategies in SyncSagaCoordinator.
 * Reproduces divergence scenarios and asserts resolution behavior.
 */
import { respecfully, ifWe, iReckon } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
const maam = `[${import.meta.url}]`, sir = maam;
import { delay, extractErrorMsg, pretty } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
import { SyncSagaCoordinator } from './sync-saga-coordinator.mjs';
import { getFromSpace } from '../witness/space/space-helper.mjs';
import { Metaspace_Innerspace } from '../witness/space/metaspace/metaspace-innerspace/metaspace-innerspace.mjs';
import { InnerSpace_V1 } from '../witness/space/inner-space/inner-space-v1.mjs';
import { createTimelineRootHelper, getTestKeystoneServiceHelper } from '../agent-helpers.mjs';
import { mut8Timeline } from '../timeline/timeline-api.mjs';
import { SyncPeerInnerspace_V1 } from './sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs';
import { DEFAULT_INNER_SPACE_DATA_V1 } from '../witness/space/inner-space/inner-space-types.mjs';
import { getTjpAddr } from '../common/other/ibgib-helper.mjs';
import { SyncConflictStrategy } from './sync-types.mjs';
import { fnObs } from '../common/pubsub/observer/observer-helper.mjs';
import { getDependencyGraph } from '../common/other/graph-helper.mjs';
const logalot = true;
const lc = `[sync-conflict.respec]`;
await respecfully(sir, `Sync Conflict Resolution`, async () => {
    let metaspace;
    let sourceSpace;
    let destSpace;
    await respecfully(sir, `Scenario: Simple Divergence (Field A vs Field B)`, async () => {
        // 1. Setup Spaces
        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 null; },
        });
        while (!metaspace.initialized) {
            await delay(10);
        }
        const defaultLocalUserSpace = await metaspace.getLocalUserSpace({ lock: false });
        await defaultLocalUserSpace.initialized;
        sourceSpace = new InnerSpace_V1({
            ...DEFAULT_INNER_SPACE_DATA_V1,
            name: 'source',
            uuid: 'source_uuid',
            description: 'source test space',
        });
        await sourceSpace.initialized;
        destSpace = new InnerSpace_V1({
            ...DEFAULT_INNER_SPACE_DATA_V1,
            name: 'dest',
            uuid: 'dest_uuid',
            description: 'dest test space',
        });
        await destSpace.initialized;
        // 2. Seed Common History (V0 -> V1)
        console.log(`${lc} Creating Common History...`);
        const testRoot = await createTimelineRootHelper({
            ib: 'timeline_root',
            data: { type: 'testRoot', text: 'v0' },
            space: sourceSpace,
        });
        // Create V1 (Common)
        const v1_Common = await mut8Timeline({
            timeline: testRoot,
            mut8Opts: {
                mut8Ib: testRoot.ib + '_common',
                dataToAddOrPatch: { text: 'v1_common' }
            },
            metaspace,
            space: sourceSpace,
        });
        const tjpAddr = getTjpAddr({ ibGib: testRoot, defaultIfNone: 'incomingAddr' }) ??
            getIbGibAddr({ ibGib: testRoot });
        console.log(`${lc} [TEST DEBUG] tjpAddr: ${tjpAddr}`);
        // Sync testRoot to dest immediately so they start synced
        // must get the entire dependency graph for testRoot
        const depGraph_testRootAndV1Common = await getDependencyGraph({
            ibGibAddrs: [tjpAddr],
            space: sourceSpace,
            live: true,
        }) ?? {};
        console.log(`${lc} depGraph_testRootAndV1Common: ${pretty(depGraph_testRootAndV1Common)}`);
        if (Object.keys(depGraph_testRootAndV1Common).length === 0) {
            throw new Error(`(UNEXPECTED) depGraph_testRootAndV1Common empty? (E: 39b4d855ffa65476084b4123786da826)`);
        }
        // put the entire graph into the destspace, but...
        await metaspace.put({ ibGibs: Object.values(depGraph_testRootAndV1Common), space: destSpace });
        // ...only register the main ibgibs
        await metaspace.registerNewIbGib({ ibGib: testRoot, space: destSpace });
        await metaspace.registerNewIbGib({ ibGib: v1_Common, space: destSpace });
        const resGetDest = await getFromSpace({ space: destSpace, addr: getIbGibAddr({ ibGib: v1_Common }) });
        if (!resGetDest.success || !resGetDest.ibGibs || resGetDest.ibGibs.length === 0) {
            throw new Error(`Failed to retrieve v1_Dest from destSpace. (E: a1b2c3d4e5f6g7h8i9j0)`);
        }
        const v1_Dest = resGetDest.ibGibs[0];
        // 3. Create Divergence (V2a vs V2b)
        console.log(`${lc} Creating Divergence...`);
        // Source: V1 -> V2a (Edit Field A)
        const v2a = await mut8Timeline({
            timeline: v1_Common,
            mut8Opts: {
                mut8Ib: testRoot.ib + '_v2a',
                dataToAddOrPatch: { fieldA: 'source_edit' }
            },
            metaspace,
            space: sourceSpace,
        });
        // Dest: V1 -> V2b (Edit Field B)
        const v2b = await mut8Timeline({
            timeline: v1_Dest,
            mut8Opts: {
                mut8Ib: testRoot.ib + '_v2b',
                dataToAddOrPatch: { fieldB: 'dest_edit' }
            },
            metaspace,
            space: destSpace,
        });
        // Verify Divergence
        console.log(`${lc} Divergence created.`);
        // 4. Run Sync (Optimistic)
        console.log(`${lc} Setting up Coordinators...`);
        const mockKeystone = await getTestKeystoneServiceHelper();
        const senderCoordinator = new SyncSagaCoordinator(mockKeystone);
        const receiverCoordinator = new SyncSagaCoordinator(mockKeystone);
        const peer = new SyncPeerInnerspace_V1({
            senderSpace: sourceSpace,
            // No senderTempSpace - sync() creates its own internally
            receiverSpace: destSpace,
            receiverCoordinator,
            receiverMetaspace: metaspace,
            receiverTempSpace: 0, // placeholder this is wrong
            senderTempSpace: 0, // placeholder this is wrong
        }, {
            classname: SyncPeerInnerspace_V1.name,
        });
        await peer.initialized;
        await peer.initialize();
        // Verify Receiver has correct KV (Pre-Sync Check)
        // This ensures the conflict precondition exists.
        await ifWe(sir, 'verify receiver KV pre-sync', async () => {
            try {
                const destKV = await receiverCoordinator.getKnowledgeVector({
                    space: destSpace,
                    metaspace,
                    domainIbGibs: [v1_Dest]
                });
                const v1TjpAddr = getTjpAddr({ ibGib: v1_Dest, defaultIfNone: 'incomingAddr' }) ?? getIbGibAddr({ ibGib: v1_Dest });
                console.log(`[TEST DEBUG] v1_Dest: ${JSON.stringify(v1_Dest)}`);
                console.log(`[TEST DEBUG] v1TjpAddr: ${v1TjpAddr}`);
                console.log(`[TEST DEBUG] destKV: ${JSON.stringify(destKV)}`);
                console.log(`[TEST DEBUG] v1_Dest.rel8ns.tjp: ${v1_Dest.rel8ns?.tjp?.join(', ')}`);
                // Actually v1 has tjp rel8n.
                const destTip = destKV[v1TjpAddr];
                iReckon(sir, !!destTip).asTo(`Dest KV has timeline tip for ${v1TjpAddr}`).isGonnaBeTruthy();
                if (!destTip) {
                    throw new Error(`Test Setup Fail: Dest Space does not have index for timeline ${v1TjpAddr}. Seeding failed. (E: c194a80b4e4877b77826c37a1753b826)`);
                }
            }
            catch (error) {
                console.error(`${lc} ${extractErrorMsg(error)}`);
                iReckon(sir, true).asTo('getKnowledgeVector errored out').isGonnaBeFalse();
            }
        });
        console.log(`${lc} Running Sync (ConflictStrategy: optimistic)...`);
        let resSync;
        try {
            resSync = await senderCoordinator.sync({
                peer,
                source: sourceSpace,
                dest: destSpace,
                metaspace,
                domainIbGibs: [v2a],
                conflictStrategy: SyncConflictStrategy.optimistic,
                // identity: undefined as any, // Not needed? If removed, it uses session identity.
                // identitySecret: undefined as any,
                // explicit useSessionIdentity: true is default
            });
            const sublc = `${lc}[updates$]`;
            /**
             * I have added this so you can see how to subscribe to an ibgib
             * observable using {@link fnObs}.
             */
            const subscription = await resSync.updates$.subscribe(fnObs({
                next: async (ctxIbGib) => {
                    // console.log(`${sublc} next fired. ${JSON.stringify(ctxIbGib)}`);
                    console.log(`${sublc} next fired. (I: 72db8820f058312f5cb2a92b8f9bde26)`);
                },
                error: async (e) => {
                    if (e.data) {
                        console.error(`${sublc} error fired. error: ${JSON.stringify(e.data)} (E: 01cc08ba05ad99682831174fd7c31a26)`);
                    }
                    else {
                        console.dir(e);
                        console.error(`${sublc} error fired. error: ${extractErrorMsg(e)} (E: 73d3d61464e8e4ce4cd6efd8b9675826)`);
                    }
                },
                complete: async () => {
                    console.log(`${sublc} complete fired`);
                },
            }));
            console.log(`${lc} awaiting resSync.done`);
            await resSync.done;
            console.log(`${lc} Sync Complete.`);
        }
        catch (e) {
            console.error(`${lc} Sync Failed with Error:`, e);
            iReckon(sir, false).asTo(`Sync failed with error: ${e}`).isGonnaBeTruthy();
            return; // Exit test early
        }
        // 5. Verification
        // Expectation: Both Spaces should now have a NEW tip (V3) that merges V2a and V2b.
        const resSourceTip = await getFromSpace({ space: sourceSpace, addr: getIbGibAddr({ ibGib: v2a }) });
        await ifWe(sir, `verify merge happened`, async () => {
            // Retrieve updated KV from Source (or check what happened to v2a)
            // Ideally, we just check the source space for the timeline tip
            // The timeline tip for testRoot/v0 should now be NEW.
            try {
                // Get the KV for the Source Space
                const sourceKV = await senderCoordinator.getKnowledgeVector({
                    space: sourceSpace,
                    metaspace,
                    domainIbGibs: [testRoot] // We want to know the tip of this timeline
                });
                const tjpAddr = getTjpAddr({ ibGib: testRoot, defaultIfNone: 'incomingAddr' }) ??
                    getIbGibAddr({ ibGib: testRoot });
                if (logalot) {
                    console.log(`${lc} getKnowledgeVector returned. sourceKV: ${pretty(sourceKV)} (I: e8780cda37c8b2a46eeb85786874e926)`);
                }
                const sourceTipAddr = sourceKV[tjpAddr];
                if (!sourceTipAddr) {
                    throw new Error(`Source Space missing timeline tip for ${tjpAddr} (E: ec95980b9c980c5c5870812e15e43826)`);
                }
                console.log(`${lc} [TEST DEBUG] sourceTipAddr: ${sourceTipAddr}`);
                console.log(`${lc} [TEST DEBUG] v2a: ${getIbGibAddr({ ibGib: v2a })}`);
                console.log(`${lc} [TEST DEBUG] v2b: ${getIbGibAddr({ ibGib: v2b })}`);
                iReckon(sir, sourceTipAddr).asTo(`Source Tip (${sourceTipAddr}) should NOT be v2a`).not.isGonnaBe(getIbGibAddr({ ibGib: v2a }));
                iReckon(sir, sourceTipAddr).asTo(`Source Tip (${sourceTipAddr}) should NOT be v2b`).not.isGonnaBe(getIbGibAddr({ ibGib: v2b }));
                // Fetch the new tip
                const resTip = await getFromSpace({ space: sourceSpace, addr: sourceTipAddr });
                const newTip = resTip.ibGibs[0];
                console.log(`${lc} [TEST DEBUG] newTip: ${JSON.stringify(newTip)}`);
                // Check Data: Should have BOTH edits
                iReckon(sir, newTip.data.fieldA).asTo('New Tip Data Field A').isGonnaBe('source_edit');
                iReckon(sir, newTip.data.fieldB).asTo('New Tip Data Field B').isGonnaBe('dest_edit');
                // Check Graft Structure
                // Look for 'graftinfo' rel8n
                const graftInfoRel = newTip.rel8ns?.graftinfo;
                iReckon(sir, graftInfoRel).asTo('New Tip should have graftinfo rel8n').isGonnaBeTruthy();
                if (graftInfoRel) {
                    iReckon(sir, graftInfoRel.length).asTo('Only 1 graftinfo').isGonnaBe(1);
                    // Fetch Graft Info Stone
                    const resGraft = await getFromSpace({ space: sourceSpace, addr: graftInfoRel[0] });
                    const graftInfo = resGraft.ibGibs[0];
                    console.log(`${lc} [TEST DEBUG] graftInfo: ${JSON.stringify(graftInfo)}`);
                    // Check Graft Relations (graftbase, graftorphan)
                    const baseRel = graftInfo.rel8ns?.graftbase;
                    const orphanRel = graftInfo.rel8ns?.graftorphan;
                    iReckon(sir, baseRel).asTo('Graft Base exists').isGonnaBeTruthy();
                    iReckon(sir, orphanRel).asTo('Graft Orphan exists').isGonnaBeTruthy();
                    // Verify Base/Orphan identity (one should be v2a, one v2b)
                    const addrV2a = getIbGibAddr({ ibGib: v2a });
                    const addrV2b = getIbGibAddr({ ibGib: v2b });
                    const bases = baseRel || [];
                    const orphans = orphanRel || [];
                    const isV2aInvolved = bases.includes(addrV2a) || orphans.includes(addrV2a);
                    const isV2bInvolved = bases.includes(addrV2b) || orphans.includes(addrV2b);
                    iReckon(sir, isV2aInvolved).asTo('V2a is involved in graft').isGonnaBeTrue();
                    iReckon(sir, isV2bInvolved).asTo('V2b is involved in graft').isGonnaBeTrue();
                }
            }
            catch (error) {
                console.error(`${lc} ${extractErrorMsg(error)}`);
                iReckon(sir, true).asTo('getKnowledgeVector errored out').isGonnaBeFalse();
            }
        });
    });
});
//# sourceMappingURL=sync-conflict.respec.mjs.map