/**
 * @module sync-conflict-basic-multitimelines.withid.respec
 *
 * Verifies Divergence Conflict Resolution (optimistic merge / grafting) with Identity
 * when multiple related timelines are involved.
 */
import { respecfully, iReckon, ifWe } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
const maam = `[${import.meta.url}]`, sir = maam;
import { clone, delay } 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 { 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 { createTimelineRootTestHelper } from '../test-helpers.mjs';
import { appendToTimeline, 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 { getIbGibsFromCache_fallbackToSpaces, getTjpAddr } from '../common/other/ibgib-helper.mjs';
import { getDependencyGraph, graphsAreEquivalent } from '../common/other/graph-helper.mjs';
import { SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1 } from './sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs';
import { SyncConflictStrategy } from './sync-constants.mjs';
import { GRAFT_BASE_REL8N_NAME, GRAFT_ORPHAN_REL8N_NAME, GRAFT_INFO_REL8N_NAME } from './graft-info/graft-info-constants.mjs';
import { KeystoneService_V1 } from '../keystone/keystone-service-v1.mjs';
import { KEYSTONE_VERB_SYNC, POOL_ID_SYNC, } from '../keystone/keystone-constants.mjs';
import { createStandardPoolConfig } from '../keystone/keystone-config-builder.mjs';
import { KeystoneReplenishStrategy } from '../keystone/keystone-types.mjs';
const logalot = GLOBAL_LOG_A_LOT || false;
const lc = `[sync-conflict-basic-multitimelines.withid.respec]`;
const SENDER_SECRET = 'test-sender-secret-conflict-basic-multitimelines-withid';
const SENDER_IDENTITY_SYNC_POOL_CONFIG = createStandardPoolConfig({
    id: POOL_ID_SYNC,
    salt: 'senderidentitysyncsaltconflictbasicmultitimelineswithid',
    verbs: [KEYSTONE_VERB_SYNC],
    size: 200,
    sequential: 1,
    random: 1,
    targetBinding: 2,
    replenishStrategy: KeystoneReplenishStrategy.topUp,
});
const TEST_REL8N_NAME = 'testrel8n';
await respecfully(sir, `Sync Conflict Basic Multitimelines with Identity`, async () => {
    for (let i = 0; i <= 1; i++) {
        const srcMakesFirstDivergentEdit = i === 0;
        await respecfully(sir, `srcMakesFirstDivergentEdit: ${srcMakesFirstDivergentEdit}`, async () => {
            // 1. Setup Spaces
            const 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;
            const sourceSpace = new InnerSpace_V1({
                ...DEFAULT_INNER_SPACE_DATA_V1,
                name: 'source',
                uuid: 'source_uuid',
                description: 'source test space',
            });
            await sourceSpace.initialized;
            const destSpace = new InnerSpace_V1({
                ...DEFAULT_INNER_SPACE_DATA_V1,
                name: 'dest',
                uuid: 'dest_uuid',
                description: 'dest test space',
            });
            await destSpace.initialized;
            // 2. Setup Identities
            const keystoneSvc = new KeystoneService_V1();
            const r1_senderIdentity_v0_source = await keystoneSvc.genesis({
                masterSecret: SENDER_SECRET,
                configs: [SENDER_IDENTITY_SYNC_POOL_CONFIG],
                metaspace,
                space: sourceSpace,
            });
            // Register identity in both spaces
            await metaspace.put({ ibGib: r1_senderIdentity_v0_source, space: destSpace });
            await metaspace.registerNewIbGib({ ibGib: r1_senderIdentity_v0_source, space: destSpace });
            // 3. Seed Common History (V0 -> V1)
            const alpha_v0 = await createTimelineRootTestHelper({
                ib: 'alpha_multitimelines_withid',
                data: { type: 'alpha', },
                space: sourceSpace,
            });
            const tjpAddr = getTjpAddr({ ibGib: alpha_v0, defaultIfNone: 'incomingAddr' }) ?? getIbGibAddr({ ibGib: alpha_v0 });
            // Create v1 (common)
            const alpha_v1_common_onSource = await mut8Timeline({
                timeline: alpha_v0,
                mut8Opts: {
                    mut8Ib: alpha_v0.ib + '_common',
                    dataToAddOrPatch: { commonField: 'common yo' }
                },
                metaspace,
                space: sourceSpace,
            });
            // Sync alpha to dest immediately
            const depGraph_alpha_v1Andcommon = await getDependencyGraph({
                ibGibAddrs: [tjpAddr],
                space: sourceSpace,
                live: true,
            }) ?? {};
            await metaspace.put({ ibGibs: Object.values(depGraph_alpha_v1Andcommon), space: destSpace });
            await metaspace.registerNewIbGib({ ibGib: alpha_v0, space: destSpace });
            const resGetDest = await getFromSpace({ space: destSpace, addr: getIbGibAddr({ ibGib: alpha_v1_common_onSource }) });
            if (!resGetDest.success || !resGetDest.ibGibs || resGetDest.ibGibs.length === 0) {
                throw new Error("Failed to copy V1 to Dest");
            }
            const alpha_v1_common_onDest = resGetDest.ibGibs[0];
            await metaspace.registerNewIbGib({ ibGib: alpha_v1_common_onDest, space: destSpace });
            // 4. Create Divergences (V2a vs V2b)
            let alpha_v3_source_rel8beta;
            let alpha_v2_dest;
            let baseAddr;
            let orphanAddr;
            let beta_v0_source;
            let betaAddr_v0;
            // Beta is created on source
            beta_v0_source = await createTimelineRootTestHelper({
                ib: 'beta_multitimelines_withid',
                data: {
                    fieldA: 'beta field A created on source',
                },
                space: sourceSpace,
            });
            betaAddr_v0 = getIbGibAddr({ ibGib: beta_v0_source });
            if (srcMakesFirstDivergentEdit) {
                // Source: V1 -> V2a (Edit Field A + Rel8 Beta)
                const alpha_v2_source_sansBeta = await mut8Timeline({
                    timeline: alpha_v1_common_onSource,
                    mut8Opts: {
                        mut8Ib: alpha_v0.ib + '_v2source',
                        dataToAddOrPatch: { fieldA: 'source_edit' }
                    },
                    metaspace,
                    space: sourceSpace,
                });
                alpha_v3_source_rel8beta = await appendToTimeline({
                    timeline: alpha_v2_source_sansBeta,
                    metaspace,
                    space: sourceSpace,
                    rel8nInfos: [
                        { rel8nName: TEST_REL8N_NAME, ibGibs: [beta_v0_source] }
                    ],
                });
                baseAddr = getIbGibAddr({ ibGib: alpha_v3_source_rel8beta });
                await delay(16);
                // Dest: V1 -> V2b (Edit Field B)
                alpha_v2_dest = await mut8Timeline({
                    timeline: alpha_v1_common_onDest,
                    mut8Opts: {
                        mut8Ib: alpha_v0.ib + '_v2dest',
                        dataToAddOrPatch: { fieldB: 'dest_edit' }
                    },
                    metaspace,
                    space: destSpace,
                });
                orphanAddr = getIbGibAddr({ ibGib: alpha_v2_dest });
            }
            else {
                // Dest: V1 -> V2b (Edit Field B)
                alpha_v2_dest = await mut8Timeline({
                    timeline: alpha_v1_common_onDest,
                    mut8Opts: {
                        mut8Ib: alpha_v0.ib + '_v2dest',
                        dataToAddOrPatch: { fieldB: 'dest_edit' }
                    },
                    metaspace,
                    space: destSpace,
                });
                baseAddr = getIbGibAddr({ ibGib: alpha_v2_dest });
                await delay(16);
                // Source: V1 -> V2a (Edit Field A + Rel8 Beta)
                const alpha_v2_source_sansBeta = await mut8Timeline({
                    timeline: alpha_v1_common_onSource,
                    mut8Opts: {
                        mut8Ib: alpha_v0.ib + '_v2source',
                        dataToAddOrPatch: { fieldA: 'source_edit' }
                    },
                    metaspace,
                    space: sourceSpace,
                });
                alpha_v3_source_rel8beta = await appendToTimeline({
                    timeline: alpha_v2_source_sansBeta,
                    metaspace,
                    space: sourceSpace,
                    rel8nInfos: [
                        { rel8nName: TEST_REL8N_NAME, ibGibs: [beta_v0_source] }
                    ],
                });
                orphanAddr = getIbGibAddr({ ibGib: alpha_v3_source_rel8beta });
            }
            // 5. Setup Coordinators
            const senderCoordinator = new SyncSagaCoordinator();
            const receiverCoordinator = new SyncSagaCoordinator();
            const peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
            await peer.initialized;
            await peer.initializeOpts({
                localMetaspace: metaspace,
                localSpace: sourceSpace,
                receiverSpace: destSpace,
                receiverCoordinator,
                receiverMetaspace: metaspace,
            });
            // 6. Run Sync with Identity
            console.log(`${lc} Running Sync...`);
            const resSync = await senderCoordinator.sync({
                peer,
                localSpace: sourceSpace,
                metaspace,
                domainIbGibs: [alpha_v3_source_rel8beta],
                conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
                senderIdentity: r1_senderIdentity_v0_source,
                fnSenderSecret: async () => SENDER_SECRET,
            });
            await resSync.done;
            console.log(`${lc} Sync finished.`);
            // 7. Verify merge & related timelines
            await respecfully(sir, `verify merge and related timelines`, async () => {
                await ifWe(sir, `verify graft/merge tip contains both edits and graft relations`, async () => {
                    const sourceKV = await senderCoordinator.getKnowledgeMap({
                        space: sourceSpace,
                        metaspace,
                        domainIbGibs: [alpha_v0]
                    });
                    const sourceTipAddr = sourceKV[tjpAddr];
                    iReckon(sir, sourceTipAddr).asTo('Source tip exists').isGonnaBeTruthy();
                    if (sourceTipAddr) {
                        iReckon(sir, sourceTipAddr).asTo('Tip should not be baseAddr').not.isGonnaBe(baseAddr);
                        iReckon(sir, sourceTipAddr).asTo('Tip should not be orphanAddr').not.isGonnaBe(orphanAddr);
                        const resTip = await getFromSpace({ space: sourceSpace, addr: sourceTipAddr });
                        const newTip = resTip.ibGibs[0];
                        iReckon(sir, newTip.data.fieldA).asTo('Merged fieldA').isGonnaBe('source_edit');
                        iReckon(sir, newTip.data.fieldB).asTo('Merged fieldB').isGonnaBe('dest_edit');
                        const graftInfoRel = newTip.rel8ns?.[GRAFT_INFO_REL8N_NAME];
                        iReckon(sir, graftInfoRel).asTo('New Tip has graftinfo rel8n').isGonnaBeTruthy();
                        if (graftInfoRel?.[0]) {
                            const resGraft = await getFromSpace({ space: sourceSpace, addr: graftInfoRel[0] });
                            const graftInfo = resGraft.ibGibs[0];
                            const baseRel = graftInfo.rel8ns?.[GRAFT_BASE_REL8N_NAME];
                            const orphanRel = graftInfo.rel8ns?.[GRAFT_ORPHAN_REL8N_NAME];
                            iReckon(sir, baseRel).asTo('Graft Base exists').isGonnaBeTruthy();
                            iReckon(sir, orphanRel).asTo('Graft Orphan exists').isGonnaBeTruthy();
                            iReckon(sir, baseRel.at(-1)).asTo('base rel8ns final addr will be baseAddr').isGonnaBe(baseAddr);
                            iReckon(sir, orphanRel.at(-1)).asTo('orphan rel8ns final addr will be orphanAddr').isGonnaBe(orphanAddr);
                        }
                    }
                });
                await ifWe(sir, 'related timeline beta is synced to destSpace and graphs match', async () => {
                    const [beta_dest] = await getIbGibsFromCache_fallbackToSpaces({
                        addrs: [betaAddr_v0],
                        space: destSpace,
                    });
                    iReckon(sir, beta_dest).asTo('beta exists in dest').isGonnaBeTruthy();
                    if (beta_dest) {
                        const depGraph_beta_source = await getDependencyGraph({ ibGib: beta_v0_source, space: sourceSpace });
                        const depGraph_beta_dest = await getDependencyGraph({ ibGib: beta_dest, space: destSpace });
                        const betaDepsAreEqual = graphsAreEquivalent({
                            graphA: depGraph_beta_source,
                            graphB: depGraph_beta_dest,
                            slowButThorough: true,
                        });
                        iReckon(sir, betaDepsAreEqual).asTo('beta dependencies are fully synced').isGonnaBeTrue();
                    }
                });
                await ifWe(sir, 'sessionIdentity evolved the expected number of times', async () => {
                    const latestS = peer.currentSessionIdentity;
                    iReckon(sir, latestS).asTo('latest session identity exists').isGonnaBeTruthy();
                    if (latestS) {
                        console.log("ACTUAL SESSION IDENTITY N:", latestS.data?.n);
                        iReckon(sir, latestS.data?.n).asTo('session identity data.n').isGonnaBe(3);
                    }
                });
                await ifWe(sir, 'sender durable space has I (evolved frame)', async () => {
                    const latestSenderIdentityAddr = await metaspace.getLatestAddr({
                        addr: getIbGibAddr({ ibGib: r1_senderIdentity_v0_source }),
                        space: sourceSpace,
                    });
                    iReckon(sir, latestSenderIdentityAddr).asTo('latestSenderIdentityAddr exists').isGonnaBeTruthy();
                    if (latestSenderIdentityAddr) {
                        const resGetSourceI = await getFromSpace({ space: sourceSpace, addr: latestSenderIdentityAddr });
                        iReckon(sir, resGetSourceI.success && resGetSourceI.ibGibs?.length === 1).asTo('evolved I exists in sourceSpace').isGonnaBeTrue();
                        if (resGetSourceI.success && resGetSourceI.ibGibs?.[0]) {
                            const evolvedI = resGetSourceI.ibGibs[0];
                            iReckon(sir, evolvedI.data?.n).asTo('evolved I data.n').isGonnaBe(1);
                        }
                    }
                });
                await ifWe(sir, 'receiver durable space has I (evolved frame)', async () => {
                    const latestSenderIdentityAddr = await metaspace.getLatestAddr({
                        addr: getIbGibAddr({ ibGib: r1_senderIdentity_v0_source }),
                        space: sourceSpace,
                    });
                    iReckon(sir, latestSenderIdentityAddr).asTo('latestSenderIdentityAddr exists').isGonnaBeTruthy();
                    if (latestSenderIdentityAddr) {
                        const resGetDestI = await getFromSpace({ space: destSpace, addr: latestSenderIdentityAddr });
                        iReckon(sir, resGetDestI.success && resGetDestI.ibGibs?.length === 1).asTo('evolved I exists in destSpace').isGonnaBeTrue();
                        if (resGetDestI.success && resGetDestI.ibGibs?.[0]) {
                            const evolvedI = resGetDestI.ibGibs[0];
                            iReckon(sir, evolvedI.data?.n).asTo('evolved I data.n').isGonnaBe(1);
                        }
                    }
                });
                await ifWe(sir, 'session keystone dependency graphs are equivalent on source and dest', async () => {
                    const latestS = peer.currentSessionIdentity;
                    iReckon(sir, latestS).asTo('latestS exists').isGonnaBeTruthy();
                    if (latestS) {
                        const depGraphSource = await getDependencyGraph({ ibGib: latestS, space: sourceSpace });
                        const depGraphDest = await getDependencyGraph({ ibGib: latestS, space: destSpace });
                        const equal = graphsAreEquivalent({ graphA: depGraphSource, graphB: depGraphDest });
                        iReckon(sir, equal).asTo('session graphs are equivalent').isGonnaBeTrue();
                    }
                });
            });
        });
    }
});
//# sourceMappingURL=sync-conflict-basic-multitimelines.withid.respec.mjs.map