/**
 * @module sync-conflict.respec
 *
 * Verifies Conflict Resolution strategies in SyncSagaCoordinator.
 * Reproduces divergence scenarios and asserts resolution behavior.
 */
import { respecfully, iReckon, ifWeMight } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
const maam = `[${import.meta.url}]`, sir = maam;
import { clone, delay, extractErrorMsg, pretty } 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 { TestTransformer, getTestKeystoneServiceHelper } from '../test-helpers.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 { 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';
const logalot = GLOBAL_LOG_A_LOT;
const lc = sir;
const TEST_REL8N_NAME = 'testrel8n';
await respecfully(sir, `Two different fields and rel8d`, async () => {
    // #region Init/Setup
    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;
    const testTransformer = new TestTransformer(metaspace, sourceSpace, destSpace);
    // #endregion Init/Setup
    // #region Round 1: Seed common history
    const r1 = testTransformer.newRound({
        name: 'r1_seedCommonHistory',
        description: 'Create alpha v0 and v1 on source, sync to dest so both v0 and v1 are established as synced.',
    });
    console.log(`${lc} Creating common History...`);
    const r1_alpha_v0_source = await testTransformer.create({
        ib: 'alpha',
        in: 'source',
        name: 'r1_alpha_v0_source', // should be auto-calculated?
    });
    // Create v1 (common) - will be synced to dest
    const r1_alpha_v1_source_common = await testTransformer.mut8({
        stepInfo: r1_alpha_v0_source,
        strField: 'commonField',
        name: 'r1_alpha_v1_source_common'
    });
    const alpha_tjpAddr = getTjpAddr({ ibGib: r1_alpha_v0_source.ibGib, defaultIfNone: 'incomingAddr' }) ??
        getIbGibAddr({ ibGib: r1_alpha_v0_source.ibGib });
    // Verify destSpace does NOT have alpha timeline before sync
    await ifWeMight(sir, 'verify dest pre-sync (should NOT have alpha)', async () => {
        const resGet = await getFromSpace({
            space: destSpace,
            addr: alpha_tjpAddr,
        });
        // When timeline doesn't exist, getFromSpace returns success:false
        iReckon(sir, resGet.success).asTo('getFromSpace should return false for missing timeline').isGonnaBeFalse();
        iReckon(sir, resGet.ibGibs?.length ?? 0).asTo('Dest should NOT have alpha timeline yet').isGonnaBe(0);
    });
    // Sync alpha timeline to dest
    console.log(`${lc} Syncing alpha to dest...`);
    const mockKeystone_r1 = await getTestKeystoneServiceHelper();
    const senderCoordinator_r1 = new SyncSagaCoordinator(mockKeystone_r1);
    const receiverCoordinator_r1 = new SyncSagaCoordinator(mockKeystone_r1);
    const r1_peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
    await r1_peer.initialized;
    await r1_peer.initializeSender({
        senderSpace: sourceSpace,
        receiverSpace: destSpace,
        receiverCoordinator: receiverCoordinator_r1,
        receiverMetaspace: metaspace,
    });
    const resSync_r1 = await senderCoordinator_r1.sync({
        peer: r1_peer,
        localSpace: sourceSpace,
        metaspace,
        domainIbGibs: [r1_alpha_v1_source_common.ibGib],
        conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
        useSessionIdentity: false,
    });
    await resSync_r1.done;
    console.log(`${lc} Round 1 Sync Complete.`);
    // Verify destSpace now HAS alpha timeline after sync
    await ifWeMight(sir, 'verify dest post-sync (should HAVE alpha)', async () => {
        const destKV = await receiverCoordinator_r1.getKnowledgeMap({
            space: destSpace,
            metaspace,
            domainIbGibs: [r1_alpha_v1_source_common.ibGib]
        });
        const alpha_destTipAddr = destKV[alpha_tjpAddr];
        iReckon(sir, alpha_destTipAddr).asTo(`Dest KV should have timeline tip for ${alpha_tjpAddr}`).isGonnaBeTruthy();
        if (alpha_destTipAddr) {
            const resTip = await getFromSpace({ space: destSpace, addr: alpha_destTipAddr });
            iReckon(sir, resTip.success).asTo('Should successfully get synced timeline').isGonnaBeTruthy();
            iReckon(sir, resTip.ibGibs?.length).asTo('Should have exactly 1 ibGib').isGonnaBe(1);
            const alpha_v1_common_onDest = resTip.ibGibs[0];
            iReckon(sir, alpha_v1_common_onDest.ib).asTo('Dest alpha ib should match source').isGonnaBe(r1_alpha_v1_source_common.ibGib.ib);
        }
    });
    // Get alpha_v1_common from destSpace for Round 2
    const resGetDest = await getFromSpace({ space: destSpace, addr: alpha_tjpAddr });
    if (!resGetDest.success || !resGetDest.ibGibs || resGetDest.ibGibs.length === 0) {
        throw new Error(`Failed to retrieve alpha from destSpace after sync. (E: 40c67811eba14f729880a46dcbb65126)`);
    }
    const alpha_v1_common_onDest = resGetDest.ibGibs[0];
    // #endregion Round 1: Seed common history
    // #region Round 2: Create Divergence (v2source vs v2dest) - "simultaneous" parallel edits
    const r2 = testTransformer.newRound({
        name: 'r2_divergent_edits',
        description: 'Create conflicts on alpha, add beta relation on source, separate edit on dest'
    });
    console.log(`${lc} Creating Divergence...`);
    // #region r2 source edits
    // Source: Create beta, mutate alpha fieldA, add beta relation
    const r2_beta_v0_source = await testTransformer.create({
        ib: 'beta',
        data: { fieldA: 'beta field A created on source' },
        in: 'source',
        name: 'r2_beta_v0_source'
    });
    const r2_alpha_v2_source_arbitraryMut8 = await testTransformer.mut8({
        ibGib: r1_alpha_v1_source_common.ibGib,
        in: 'source',
        strField: 'fieldA',
        name: 'r2_alpha_v2_source_arbitraryMut8'
    });
    const r2_alpha_v3_source_rel8dBeta = await testTransformer.rel8({
        stepInfo: r2_alpha_v2_source_arbitraryMut8,
        in: 'source',
        targetIbGibs: [r2_beta_v0_source.ibGib],
        rel8nName: TEST_REL8N_NAME,
        name: 'r2_alpha_v3_source_rel8dBeta'
    });
    // #endregion r2 source edits
    /**
     * timestamp/timestampMs drives orphan/base timeline selection in
     * graftTimelines
     */
    await delay(16);
    // #region r2 dest edits
    // Dest: Mutate alpha fieldB (different field than source)
    const r2_alpha_v2_dest_mut8FieldB = await testTransformer.mut8({
        ibGib: alpha_v1_common_onDest,
        in: 'dest',
        strField: 'fieldB',
        name: 'r2_alpha_v2_dest_mut8FieldB'
    });
    // #endregion r2 dest edits
    // 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 r2_peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
    await r2_peer.initialized;
    await r2_peer.initializeSender({
        senderSpace: sourceSpace, // "Client"
        receiverSpace: destSpace, // "Server"
        receiverCoordinator,
        receiverMetaspace: metaspace,
    });
    // Verify Receiver has correct KV (Pre-Sync Check)
    // This ensures the conflict precondition exists.
    await ifWeMight(sir, 'r2 verify receiver KV pre-sync', async () => {
        try {
            const destKV = await receiverCoordinator.getKnowledgeMap({
                space: destSpace,
                metaspace,
                domainIbGibs: [alpha_v1_common_onDest]
            });
            const v1TjpAddr = getTjpAddr({ ibGib: alpha_v1_common_onDest, defaultIfNone: 'incomingAddr' }) ?? getIbGibAddr({ ibGib: alpha_v1_common_onDest });
            // 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('getKnowledgeMap errored out').isGonnaBeFalse();
        }
    });
    console.log(`${lc} Running Sync (ConflictStrategy: optimistic)...`);
    let resSync;
    try {
        console.dir(sourceSpace);
        console.dir(destSpace);
        resSync = await senderCoordinator.sync({
            peer: r2_peer,
            localSpace: sourceSpace,
            metaspace,
            domainIbGibs: [r2_alpha_v3_source_rel8dBeta.ibGib],
            conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
            useSessionIdentity: false,
        });
        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 v2source and v2dest.
    await respecfully(sir, `r2 post verify`, async () => {
        // Retrieve updated KV from Source (or check what happened to v2source)
        // Ideally, we just check the source space for the timeline tip
        // The timeline tip for alpha/v0 should now be NEW.
        try {
            // Get the KV for the Source Space
            const sourceKV_afterSync = await senderCoordinator.getKnowledgeMap({
                space: sourceSpace,
                metaspace,
                domainIbGibs: [r1_alpha_v0_source.ibGib] // We want to know the tip of this timeline
            });
            const tjpAddr = getTjpAddr({ ibGib: r1_alpha_v0_source.ibGib, defaultIfNone: 'incomingAddr' }) ??
                getIbGibAddr({ ibGib: r1_alpha_v0_source.ibGib });
            if (logalot) {
                console.log(`${lc} getKnowledgeMap returned. sourceKV_afterSync: ${pretty(sourceKV_afterSync)} (I: e8780cda37c8b2a46eeb85786874e926)`);
            }
            const sourceTipAddr = sourceKV_afterSync[tjpAddr];
            if (!sourceTipAddr) {
                throw new Error(`Source Space missing timeline tip for ${tjpAddr} (E: ec95980b9c980c5c5870812e15e43826)`);
            }
            // Fetch the new tip
            const resTip = await getFromSpace({ space: sourceSpace, addr: sourceTipAddr });
            const newTip = resTip.ibGibs[0];
            await ifWeMight(sir, 'r2 basics of alpha merge', async () => {
                iReckon(sir, sourceTipAddr)
                    .asTo(`Source Tip (${sourceTipAddr}) should NOT be r2_alpha_v3_source_rel8dBeta`)
                    .not.isGonnaBe(r2_alpha_v3_source_rel8dBeta.addr);
                iReckon(sir, sourceTipAddr)
                    .asTo(`Source Tip (${sourceTipAddr}) should NOT be r2_alpha_v2_dest_mut8FieldB`)
                    .not.isGonnaBe(r2_alpha_v2_dest_mut8FieldB.addr);
                // Check Data: Should have BOTH edits (using testTransformer-generated values)
                const r2_source_mut8Info = r2_alpha_v2_source_arbitraryMut8.infos[0];
                const r2_dest_mut8Info = r2_alpha_v2_dest_mut8FieldB.infos[0];
                iReckon(sir, newTip.data[r2_source_mut8Info.key]).asTo(`New Tip has source field ${r2_source_mut8Info.key}`).isGonnaBe(r2_source_mut8Info.value);
                iReckon(sir, newTip.data[r2_dest_mut8Info.key]).asTo(`New Tip has dest field ${r2_dest_mut8Info.key}`).isGonnaBe(r2_dest_mut8Info.value);
            });
            await ifWeMight(sir, 'r2 alpha and deps synced', async () => {
                // alpha's full dep graph should exist on dest
                const [beta_dest] = await getIbGibsFromCache_fallbackToSpaces({
                    addrs: [r2_beta_v0_source.addr],
                    space: destSpace,
                });
                iReckon(sir, beta_dest).asTo('beta exists in dest').isGonnaBeTruthy();
                if (beta_dest) {
                    const depGraph_beta_source = await getDependencyGraph({ ibGib: r2_beta_v0_source.ibGib, space: sourceSpace });
                    const depGraph_beta_dest = await getDependencyGraph({ ibGib: beta_dest, space: destSpace });
                    const betaDepsAreEqual = graphsAreEquivalent({
                        graphA: depGraph_beta_source, graphB: depGraph_beta_dest
                    });
                    iReckon(sir, betaDepsAreEqual).asTo('beta got synced and graphs are equal').isGonnaBeTrue();
                }
            });
            await ifWeMight(sir, 'r2 beta and deps synced', async () => {
                // beta's full dep graph should exist on dest
                const [beta_dest] = await getIbGibsFromCache_fallbackToSpaces({
                    addrs: [r2_beta_v0_source.addr],
                    space: destSpace,
                });
                iReckon(sir, beta_dest).asTo('beta exists in dest').isGonnaBeTruthy();
                if (beta_dest) {
                    const depGraph_beta_source = await getDependencyGraph({ ibGib: r2_beta_v0_source.ibGib, 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 got synced and graphs are equal').isGonnaBeTrue();
                }
            });
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            iReckon(sir, true).asTo('getKnowledgeMap errored out').isGonnaBeFalse();
        }
    });
    // #endregion Round 2: Create Divergence (v2source vs v2dest) - "simultaneous" parallel edits
});
//# sourceMappingURL=sync-conflict-adv-multitimelines.respec.mjs.map