import { extractErrorMsg, groupBy } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { Factory_V1 as factory, } from '@ibgib/ts-gib/dist/V1/factory.mjs';
import { getIbAndGib, getIbGibAddr, } from '@ibgib/ts-gib/dist/helper.mjs';
import { mut8 } from '@ibgib/ts-gib/dist/V1/transforms/mut8.mjs';
import { GLOBAL_LOG_A_LOT } from '../../../core-constants.mjs';
import { SpaceBase_V1 } from '../space-base-v1.mjs';
import { IbGibSpaceOptionsCmd, } from '../space-types.mjs';
import { execInSpaceWithLocking, throwIfDuplicates, } from '../space-helper.mjs';
import { getTjpAddr, splitPerTjpAndOrDna, } from '../../../common/other/ibgib-helper.mjs';
import { getStatusIb } from '../outer-space/outer-space-helper.mjs';
import { StatusCode } from '../outer-space/outer-space-types.mjs';
import { STATUS_UNDEFINED_TX_ID } from '../space-constants.mjs';
import { applyTransforms } from './reconciliation-space-helper.mjs';
const logalot = GLOBAL_LOG_A_LOT;
/**
 * Base class for spaces that perform intelligent merging of ibgib timelines,
 * whether it's a bi-directional sync, a one-way merge, or another custom
 * reconciliation strategy.
 *
 * This class contains the core, backend-agnostic logic for timeline
 * reconciliation. Concrete implementations must provide the primitive
 * I/O functions for their specific backing store.
 */
export class ReconciliationSpaceBase extends SpaceBase_V1 {
    lc = `[ReconciliationSpaceBase]`;
    // protected abstract spinOffToCompleteSync(arg: any): Promise<void>;
    async spinOffToCompleteSync({ srcSpaceId, sagaId, ibGibs, statusStartIbGibs, syncStatusIbGib_Start, syncStatus$, errors, warnings, }) {
        const lc = `${this.lc}[${this.spinOffToCompleteSync.name}]`;
        errors = errors ?? [];
        warnings = warnings ?? [];
        const verbose = logalot;
        let timeLogName = `[sync_log][${sagaId}][${ibGibs.length}]`;
        if (verbose) {
            console.time(timeLogName);
        }
        try {
            if (verbose) {
                console.timeLog(timeLogName, `${lc} starting...`);
            }
            let statusIbGib = syncStatusIbGib_Start;
            statusIbGib.statusIbGibGraph = statusStartIbGibs;
            await this.saveInStoreIfConfigAndPublishStatus({ statusIbGib, syncStatus$ });
            if (verbose) {
                console.timeLog(timeLogName, 'saveInStoreAndPublishStatus complete');
            }
            const ibGibsToStoreNotAlreadyStored = [];
            const updates = {};
            if (verbose) {
                console.timeLog(timeLogName, '_getLatestAddrsInStore starting...');
            }
            const resLatestAddrsMap = await this._getLatestAddrsInStore(ibGibs);
            if (verbose) {
                console.timeLog(timeLogName, '_getLatestAddrsInStore complete');
            }
            let { mapWithTjp_YesDna, mapWithTjp_NoDna, mapWithoutTjps } = splitPerTjpAndOrDna({ ibGibs });
            const mapIbGibsWithTjp = { ...mapWithTjp_YesDna, ...mapWithTjp_NoDna };
            const stoneIbGibs = Object.values(mapWithoutTjps);
            const stoneIbGibsAddrToIbGibMap = {};
            stoneIbGibs.forEach(x => { stoneIbGibsAddrToIbGibMap[getIbGibAddr({ ibGib: x })] = x; });
            const stoneAddrs = Object.keys(stoneIbGibsAddrToIbGibMap);
            const stoneIbGibsNotInStore = stoneIbGibs.filter(x => !resLatestAddrsMap[getIbGibAddr({ ibGib: x })]);
            const stoneAddrsNotInStore = stoneIbGibsNotInStore.map(x => getIbGibAddr({ ibGib: x }));
            const ibGibsWithTjp = Object.values(mapIbGibsWithTjp);
            const mapIbGibsWithTjpGroupedByTjpAddr = groupBy({
                items: ibGibsWithTjp,
                keyFn: x => getTjpAddr({ ibGib: x })
            });
            const tjpAddrs = Object.keys(mapIbGibsWithTjpGroupedByTjpAddr);
            if (logalot) {
                console.timeLog(timeLogName, `tjpAddrs.length: ${tjpAddrs.length}`);
            }
            const tjpOrStoneAddrs = [...tjpAddrs,];
            if (stoneAddrsNotInStore.length > 0) {
                tjpOrStoneAddrs.push(stoneAddrsNotInStore.at(0));
            }
            else if (stoneAddrs.length > 0) {
                tjpOrStoneAddrs.push(stoneAddrs.at(0));
            }
            if (logalot) {
                console.timeLog(timeLogName, `tjpOrStoneAddrs.length: ${tjpOrStoneAddrs.length}`);
            }
            for (let i = 0; i < tjpOrStoneAddrs.length; i++) {
                let statusCode;
                const tjpOrStoneAddr = tjpOrStoneAddrs[i];
                const tjpGib = getIbAndGib({ ibGibAddr: tjpOrStoneAddr }).gib;
                if (verbose) {
                    console.timeLog(timeLogName, `execInSpaceWithLocking tjpGib: ${tjpGib.slice(0, 16)} starting...`);
                }
                await execInSpaceWithLocking({
                    space: this,
                    scope: tjpGib,
                    secondsValid: 60 * 5,
                    maxDelayMs: 1000 * 60,
                    callerInstanceId: sagaId,
                    maxLockAttempts: 5,
                    fn: async () => {
                        const lcFn = `${lc}[fn][${tjpGib.slice(0, 5)}]`;
                        const ibGibsToStore_thisIteration = [];
                        const ibGibsCreated_thisTjp = [];
                        const ibGibsOnlyInStore_thisTjp = [];
                        const ibGibsMergeMap_thisTjp = {};
                        const latestAddr_Store = resLatestAddrsMap[tjpOrStoneAddr];
                        const tjpGroupIbGibs_Local_Ascending = tjpAddrs.includes(tjpOrStoneAddr) ?
                            mapIbGibsWithTjpGroupedByTjpAddr[tjpOrStoneAddr]
                                .filter(x => (x.data?.n ?? -1) >= 0)
                                .sort((a, b) => (a.data?.n ?? -1) > (b.data?.n ?? -1) ? 1 : -1) :
                            [];
                        if (latestAddr_Store) {
                            if (tjpAddrs.includes(tjpOrStoneAddr)) {
                                const tjpGroupAddrs_Local_Ascending = tjpGroupIbGibs_Local_Ascending.map(x => getIbGibAddr({ ibGib: x }));
                                const latestAddr_Local = tjpGroupAddrs_Local_Ascending[tjpGroupAddrs_Local_Ascending.length - 1];
                                if (latestAddr_Store === latestAddr_Local) {
                                    statusCode = StatusCode.already_synced;
                                }
                                else if (tjpGroupAddrs_Local_Ascending.includes(latestAddr_Store)) {
                                    this.reconcile_UpdateStoreWithMoreRecentLocal({
                                        tjpAddr: tjpOrStoneAddr,
                                        latestAddr_Store,
                                        tjpGroupAddrs_Local_Ascending,
                                        tjpGroupIbGibs_Local_Ascending,
                                        ibGibsToStore: ibGibsToStore_thisIteration,
                                        updates,
                                    });
                                    statusCode = StatusCode.updated;
                                }
                                else {
                                    const latestIbGib_Local = tjpGroupIbGibs_Local_Ascending.find(x => getIbGibAddr({ ibGib: x }) === latestAddr_Local);
                                    const latestIbGib_Local_HasDna = (latestIbGib_Local.rel8ns?.dna ?? []).length > 0;
                                    const resGetStoreIbGib = await this._getIbGibsFromStore([latestAddr_Store]);
                                    const latestIbGib_Store = resGetStoreIbGib[latestAddr_Store];
                                    if (!latestIbGib_Store) {
                                        throw new Error(`(UNEXPECTED) latestAddr_Store not found in store: ${latestAddr_Store}`);
                                    }
                                    const latestIbGib_Store_HasDna = (latestIbGib_Store.rel8ns?.dna ?? []).length > 0;
                                    if (latestIbGib_Local_HasDna && latestIbGib_Store_HasDna) {
                                        await this.reconcile_MergeLocalWithStore_ViaDna({
                                            tjpAddr: tjpOrStoneAddr,
                                            latestAddr_Local, latestIbGib_Local,
                                            latestAddr_Store, latestIbGib_Store,
                                            ibGibsCreated: ibGibsCreated_thisTjp,
                                            ibGibMergeMap: ibGibsMergeMap_thisTjp,
                                            ibGibsOnlyInStore: ibGibsOnlyInStore_thisTjp,
                                            allLocalIbGibs: ibGibs,
                                            updates,
                                            timeLogName,
                                        });
                                        ibGibsCreated_thisTjp.forEach(x => ibGibsToStore_thisIteration.push(x));
                                        statusCode = StatusCode.merged_dna;
                                    }
                                    else {
                                        await this.reconcile_MergeLocalWithStore_ViaState({
                                            tjpAddr: tjpOrStoneAddr,
                                            latestIbGib_Local, latestAddr_Local, latestAddr_Store,
                                            ibGibsCreated: ibGibsCreated_thisTjp,
                                            ibGibMergeMap: ibGibsMergeMap_thisTjp,
                                            updates,
                                        });
                                        ibGibsCreated_thisTjp.forEach(x => ibGibsToStore_thisIteration.push(x));
                                        statusCode = StatusCode.merged_state;
                                    }
                                }
                            }
                            else {
                                statusCode = StatusCode.already_synced;
                            }
                        }
                        else {
                            if (tjpAddrs.includes(tjpOrStoneAddr)) {
                                this.reconcile_InsertFirstTimeIntoStore({
                                    tjpGroupIbGibs_Local_Ascending,
                                    ibGibsToStore: ibGibsToStore_thisIteration,
                                });
                            }
                            else {
                                stoneAddrsNotInStore.forEach(stoneAddrNotInStore => {
                                    ibGibsToStore_thisIteration.push(stoneIbGibsAddrToIbGibMap[stoneAddrNotInStore]);
                                });
                            }
                            statusCode = StatusCode.inserted;
                        }
                        const ibGibAddrsForSureAlreadyInStore = Object.values(resLatestAddrsMap);
                        for (let i = 0; i < ibGibsToStore_thisIteration.length; i++) {
                            const maybeIbGib = ibGibsToStore_thisIteration[i];
                            const maybeAddr = getIbGibAddr({ ibGib: maybeIbGib });
                            if (!ibGibAddrsForSureAlreadyInStore.includes(maybeAddr) &&
                                !ibGibsToStoreNotAlreadyStored.some(x => getIbGibAddr({ ibGib: x }) === maybeAddr)) {
                                ibGibsToStoreNotAlreadyStored.push(maybeIbGib);
                            }
                        }
                        if (ibGibsToStoreNotAlreadyStored.length > 0) {
                            await this._putIbGibsToStore(ibGibsToStoreNotAlreadyStored);
                        }
                        const ibGibAddrs_DidRxFromLocal = ibGibsToStoreNotAlreadyStored.length > 0 ?
                            ibGibsToStoreNotAlreadyStored.map(x => getIbGibAddr({ ibGib: x })) :
                            undefined;
                        const ibGibAddrsCreated = ibGibsCreated_thisTjp.length > 0 ?
                            ibGibsCreated_thisTjp.map(x => getIbGibAddr({ ibGib: x })) :
                            undefined;
                        const ibGibAddrs_DidTxToLocal = ibGibsOnlyInStore_thisTjp.length > 0 ?
                            ibGibsOnlyInStore_thisTjp.map(x => getIbGibAddr({ ibGib: x })) :
                            undefined;
                        const ibGibAddrsMergeMap = {};
                        for (const [oldAddr, newIbGib] of Object.entries(ibGibsMergeMap_thisTjp)) {
                            ibGibAddrsMergeMap[oldAddr] = getIbGibAddr({ ibGib: newIbGib });
                        }
                        const resSyncStatusIbGib = await mut8({
                            src: statusIbGib,
                            mut8Ib: getStatusIb({
                                spaceType: 'sync', spaceSubtype: 'aws-dynamodb', statusCode, sagaId,
                            }),
                            dataToAddOrPatch: {
                                statusCode: statusCode,
                                success: true,
                                errors: (errors ?? []).length > 0 ? errors.concat() : undefined,
                                warnings: (warnings ?? []).length > 0 ? warnings.concat() : undefined,
                                didRx: ibGibAddrs_DidRxFromLocal?.concat(),
                                didTx: ibGibAddrs_DidTxToLocal?.concat(),
                                didCreate: ibGibAddrsCreated?.concat(),
                                didMergeMap: ibGibAddrsMergeMap,
                            },
                            dna: false,
                        });
                        statusIbGib = resSyncStatusIbGib.newIbGib;
                        statusIbGib.statusIbGibGraph = resSyncStatusIbGib.intermediateIbGibs ?
                            [statusIbGib, ...resSyncStatusIbGib.intermediateIbGibs] :
                            [statusIbGib];
                        // if (ibGibsCreated_thisTjp.length >)
                        // stopped here...need to provide the rest of this code
                        //                 }
                        //             });
                        //         }
                        //     } catch (error) {
                        //         console.error(`${lc} ${extractErrorMsg(error)}`);
                        //         throw error;
                        //     }
                        // }
                        if (ibGibsCreated_thisTjp.length > 0) {
                            statusIbGib.createdIbGibs = ibGibsCreated_thisTjp.concat();
                        }
                        if (ibGibsOnlyInStore_thisTjp.length > 0) {
                            statusIbGib.storeOnlyIbGibs = ibGibsOnlyInStore_thisTjp.concat();
                        }
                        if (Object.keys(ibGibsMergeMap_thisTjp).length > 0) {
                            statusIbGib.ibGibsMergeMap = ibGibsMergeMap_thisTjp;
                        }
                        await this.saveInStoreIfConfigAndPublishStatus({ statusIbGib, syncStatus$ });
                    }, // end of fn for execInSpaceWithLocking
                }); // end of execInSpaceWithLocking
                if (verbose) {
                    console.timeLog(timeLogName, `execInSpaceWithLocking tjpGib: ${tjpGib.slice(0, 16)} complete.`);
                }
            } // end of for loop
            if (verbose) {
                console.timeLog(timeLogName, 'complete saga cleanup starting...');
            }
            const statusCode = StatusCode.completed;
            const resSyncStatusIbGib_Complete = await mut8({
                src: statusIbGib,
                mut8Ib: getStatusIb({
                    spaceType: 'sync', spaceSubtype: this.data.subtype, statusCode, sagaId,
                }),
                dataToAddOrPatch: {
                    statusCode,
                    success: true,
                    errors: (errors ?? []).length > 0 ? errors.concat() : undefined,
                    warnings: (warnings ?? []).length > 0 ? warnings.concat() : undefined,
                },
                dna: false,
            });
            statusIbGib = resSyncStatusIbGib_Complete.newIbGib;
            statusIbGib.statusIbGibGraph = resSyncStatusIbGib_Complete.intermediateIbGibs ?
                [statusIbGib, ...resSyncStatusIbGib_Complete.intermediateIbGibs] :
                [statusIbGib];
            await this.saveInStoreIfConfigAndPublishStatus({ statusIbGib, syncStatus$ });
            if (verbose) {
                console.timeLog(timeLogName, 'complete saga cleanup complete.');
            }
            if (syncStatus$.complete && !syncStatus$.isCompleteOrErrored) {
                if (timeLogName) {
                    if (verbose) {
                        console.timeEnd(timeLogName);
                    }
                    timeLogName = '';
                }
                await syncStatus$.complete();
            }
            else {
                console.warn(`(UNEXPECTED) !syncStatus$.complete? this complete handler is expected to be truthy (W: e706de7fa3289194e59bee9ecf96c224)`);
            }
        }
        catch (error) {
            const emsg = `${lc} ${extractErrorMsg(error)}`;
            if (verbose) {
                console.timeLog(timeLogName, `errored. emsg: ${emsg}`);
            }
            console.error(emsg);
            if (!syncStatus$.isCompleteOrErrored) {
                if (!syncStatus$.error) {
                    throw new Error(`(UNEXPECTED) !syncStatus$.error? (E: a26bfba7663301986c633c3c2c764824)`);
                }
                if (timeLogName) {
                    if (verbose) {
                        console.timeEnd(timeLogName);
                    }
                    timeLogName = '';
                }
                await syncStatus$.error(emsg);
            }
        }
    }
    async saveInStoreIfConfigAndPublishStatus({ statusIbGib, syncStatus$ }) {
        const lc = `${this.lc}[${this.saveInStoreIfConfigAndPublishStatus.name}]`;
        try {
            if (this.data?.dontStoreStatusUpdatesInSyncSpace) {
                if (logalot) {
                    console.log(`${lc} configured to NOT store status updates in this space. (I: 0166928761a34375b367b61f9d45398d)`);
                }
            }
            else {
                if ((statusIbGib.statusIbGibGraph ?? []).length === 0) {
                    throw new Error(`statusIbGib.statusIbGibGraph required. (E: 2a11a84f501243799b66a5b28b76b88b)`);
                }
                await this._putIbGibsToStore(statusIbGib.statusIbGibGraph);
            }
            if (syncStatus$.next && !syncStatus$.isCompleteOrErrored) {
                await syncStatus$.next(statusIbGib);
            }
            else {
                // just warn for now
                console.warn(`${lc} (UNEXPECTED) syncStatus$ subject either does not have a next handler or is already completed/errored. (W: 489a580a563b4f6990264177d6118d53)`);
            }
        }
        catch (error) {
            const emsg = `${lc} ${extractErrorMsg(error)}`;
            console.error(emsg);
            throw error;
        }
    }
    // protected abstract reconcile_UpdateStoreWithMoreRecentLocal(arg: {
    //     tjpAddr: IbGibAddr,
    //     latestAddr_Store: IbGibAddr,
    //     tjpGroupAddrs_Local_Ascending: IbGibAddr[],
    //     tjpGroupIbGibs_Local_Ascending: TIbGib[],
    //     ibGibsToStore: TIbGib[],
    //     updates: { [tjpAddr: string]: IbGibAddr },
    // }): void;
    reconcile_UpdateStoreWithMoreRecentLocal({ tjpAddr, latestAddr_Store, tjpGroupAddrs_Local_Ascending, tjpGroupIbGibs_Local_Ascending, ibGibsToStore, updates, }) {
        const lc = `${this.lc}[${this.reconcile_UpdateStoreWithMoreRecentLocal.name}]`;
        try {
            const lastSyncPoint_Local_Idx = tjpGroupAddrs_Local_Ascending.indexOf(latestAddr_Store);
            if (lastSyncPoint_Local_Idx === -1) {
                throw new Error(`(UNEXPECTED) latestAddr_Store not found in local ibGibs. latestAddr_Store: ${latestAddr_Store} (E: 8820c754d5d94ab4971d87af48a4369e)`);
            }
            if (lastSyncPoint_Local_Idx === tjpGroupAddrs_Local_Ascending.length - 1) {
                // we thought we were out of sync, but we are not.
                // this can happen in race conditions.
                console.warn(`${lc} (UNEXPECTED) lastSyncPoint_Local_Idx is latest local ibgib index. This means we are already synced, but somehow thought we weren't. (W: 4927cf51c76a4e2e8509c25f46a9a0f7)`);
                return;
            }
            const ibGibsToUpdate = tjpGroupIbGibs_Local_Ascending.slice(lastSyncPoint_Local_Idx + 1);
            ibGibsToUpdate.forEach(x => ibGibsToStore.push(x));
            updates[tjpAddr] = getIbGibAddr({ ibGib: ibGibsToUpdate[ibGibsToUpdate.length - 1] });
        }
        catch (error) {
            const emsg = `${lc} ${extractErrorMsg(error)}`;
            console.error(emsg);
            throw error;
        }
    }
    async getDna(arg) {
        const lc = `${this.lc}[${this.getDna.name}]`;
        const { ibGib, throwOnDnaFail = true } = arg;
        try {
            const dnaAddrs = ibGib.rel8ns?.dna ?? [];
            if (dnaAddrs.length === 0) {
                // This would be unexpected for a dna'd ibgib, but we handle it.
                return [];
            }
            // BATCH fetch all dna ibgibs from the store in a single call.
            // This will return a map of { addr: ibGib }.
            const dnaIbGibsMap = await this._getIbGibsFromStore(dnaAddrs);
            // The order of the dnaAddrs array *is* the canonical order.
            // We must reconstruct the array in that order from the map.
            const orderedDnaIbGibs = dnaAddrs.map(addr => dnaIbGibsMap[addr]).filter(ibGib => !!ibGib);
            if (orderedDnaIbGibs.length !== dnaAddrs.length) {
                const emsg = `Mismatch in DNA. Requested ${dnaAddrs.length} addrs but only found ${orderedDnaIbGibs.length}. This may indicate a partial save or data loss. (E: 1ce346ba0fc88b71c6a399282d481825)`;
                if (throwOnDnaFail) {
                    throw new Error(emsg);
                }
                else {
                    console.warn(`${lc} ${emsg}`);
                }
            }
            return orderedDnaIbGibs;
        }
        catch (error) {
            const emsg = `${lc} ${extractErrorMsg(error)}`;
            console.error(emsg);
            throw error;
        }
    }
    async reconcile_MergeLocalWithStore_ViaDna({ tjpAddr, latestAddr_Local, latestIbGib_Local, latestAddr_Store, latestIbGib_Store, ibGibsCreated, ibGibMergeMap, ibGibsOnlyInStore, allLocalIbGibs, updates, timeLogName, }) {
        const lc = `${this.lc}[${this.reconcile_MergeLocalWithStore_ViaDna.name}]`;
        const verbose = logalot;
        try {
            if (verbose) {
                console.timeLog(timeLogName, 'getting full dna timeline for local and store...');
            }
            // get full dna timelines for both local and store versions
            // TO THIS:
            const dnaGraph_Local = await this.getDna({ ibGib: latestIbGib_Local });
            const dnaAddrs_Local = dnaGraph_Local.map(x => getIbGibAddr({ ibGib: x }));
            const dnaGraph_Store = await this.getDna({ ibGib: latestIbGib_Store });
            const dnaAddrs_Store = dnaGraph_Store.map(x => getIbGibAddr({ ibGib: x }));
            // const resGetDna_Local = await this.getDna({ ibGib: latestIbGib_Local, mode: 'all' });
            // const dnaGraph_Local = resGetDna_Local.ibGibs;
            // const dnaAddrs_Local = dnaGraph_Local.map(x => getIbGibAddr({ ibGib: x }));
            // const resGetDna_Store = await this.getDna({ ibGib: latestIbGib_Store, mode: 'all' });
            // const dnaGraph_Store = resGetDna_Store.ibGibs;
            // const dnaAddrs_Store = dnaGraph_Store.map(x => getIbGibAddr({ ibGib: x }));
            if (verbose) {
                console.timeLog(timeLogName, 'getting full dna timeline for local and store...complete');
            }
            // find the common ancestor addr, if any.
            // we start from the end of the shorter list and work our way backwards.
            let commonAncestorAddr;
            if (dnaAddrs_Local.length < dnaAddrs_Store.length) {
                for (let i = dnaAddrs_Local.length - 1; i >= 0; i--) {
                    const addr = dnaAddrs_Local[i];
                    if (dnaAddrs_Store.includes(addr)) {
                        commonAncestorAddr = addr;
                        break;
                    }
                }
            }
            else {
                for (let i = dnaAddrs_Store.length - 1; i >= 0; i--) {
                    const addr = dnaAddrs_Store[i];
                    if (dnaAddrs_Local.includes(addr)) {
                        commonAncestorAddr = addr;
                        break;
                    }
                }
            }
            if (!commonAncestorAddr) {
                throw new Error(`Could not find common ancestor between local and store timelines. Local: ${latestAddr_Local}, Store: ${latestAddr_Store}. This timeline requires manual reconciliation. (E: 49219b101a9f4568bd740d046e30ea2d)`);
            }
            const commonAncestorIdx_Local = dnaAddrs_Local.indexOf(commonAncestorAddr);
            const commonAncestorIdx_Store = dnaAddrs_Store.indexOf(commonAncestorAddr);
            const dnaAddrsToApplyToStoreVersion = dnaAddrs_Local.slice(commonAncestorIdx_Local + 1);
            const dnaAddrsToApplyToLocalVersion = dnaAddrs_Store.slice(commonAncestorIdx_Store + 1);
            // get the ibGibs that are only in the store and send them to the local client
            if (dnaAddrsToApplyToLocalVersion.length > 0) {
                if (verbose) {
                    console.timeLog(timeLogName, `getting ${dnaAddrsToApplyToLocalVersion.length} ibgibs from store that do not exist locally...`);
                }
                const resGetIbGibs = await this._getIbGibsFromStore(dnaAddrsToApplyToLocalVersion);
                Object.values(resGetIbGibs).forEach(x => ibGibsOnlyInStore.push(x));
                if (verbose) {
                    console.timeLog(timeLogName, `getting ${dnaAddrsToApplyToLocalVersion.length} ibgibs from store that do not exist locally...complete`);
                }
            }
            if (dnaAddrsToApplyToStoreVersion.length > 0) {
                // this is the merge part, where we create new ibgibs that represent the merge
                if (verbose) {
                    console.timeLog(timeLogName, `applying ${dnaAddrsToApplyToStoreVersion.length} transforms from local that do not exist in store...`);
                }
                const createdIbGibs_Running = [];
                const resApply = await applyTransforms({
                    src: latestIbGib_Store,
                    createdIbGibs_Running,
                    dnaAddrsToApplyToStoreVersion_MUTATES_IN_PLACE: dnaAddrsToApplyToStoreVersion,
                    allLocalIbGibs,
                });
                const mergedIbGib = resApply;
                if (verbose) {
                    console.timeLog(timeLogName, `applying ${dnaAddrsToApplyToStoreVersion.length} transforms from local that do not exist in store...complete`);
                }
                if (verbose) {
                    console.timeLog(timeLogName, `applying ${dnaAddrsToApplyToLocalVersion.length} transforms from store that do not exist locally...`);
                }
                const resApply2 = await applyTransforms({
                    src: mergedIbGib,
                    createdIbGibs_Running,
                    dnaAddrsToApplyToStoreVersion_MUTATES_IN_PLACE: dnaAddrsToApplyToLocalVersion,
                    allLocalIbGibs: ibGibsOnlyInStore,
                });
                const finalMergedIbGib = resApply2;
                if (verbose) {
                    console.timeLog(timeLogName, `applying ${dnaAddrsToApplyToLocalVersion.length} transforms from store that do not exist locally...complete`);
                }
                // gather all the created ibgibs (including intermediates)
                createdIbGibs_Running.forEach(x => {
                    const addr = getIbGibAddr({ ibGib: x });
                    if (!ibGibsCreated.some(y => getIbGibAddr({ ibGib: y }) === addr)) {
                        ibGibsCreated.push(x);
                    }
                });
                const finalMergedAddr = getIbGibAddr({ ibGib: finalMergedIbGib });
                if (!ibGibsCreated.some(y => getIbGibAddr({ ibGib: y }) === finalMergedAddr)) {
                    ibGibsCreated.push(finalMergedIbGib);
                }
                ibGibMergeMap[latestAddr_Local] = finalMergedIbGib;
                ibGibMergeMap[latestAddr_Store] = finalMergedIbGib;
                updates[tjpAddr] = finalMergedAddr;
            }
        }
        catch (error) {
            const emsg = `${lc} ${extractErrorMsg(error)}`;
            console.error(emsg);
            throw error;
        }
    }
    // protected abstract reconcile_MergeLocalWithStore_ViaState(arg: {
    //     tjpAddr: IbGibAddr,
    //     latestIbGib_Local: TIbGib,
    //     latestAddr_Local: IbGibAddr,
    //     latestAddr_Store: IbGibAddr,
    //     ibGibsCreated: TIbGib[],
    //     ibGibMergeMap: { [oldLatestAddr: string]: TIbGib },
    //     updates: { [tjpAddr: string]: IbGibAddr },
    // }): Promise<void>;
    async reconcile_MergeLocalWithStore_ViaState({ tjpAddr, latestIbGib_Local, latestAddr_Local, latestAddr_Store, ibGibsCreated, ibGibMergeMap, updates, }) {
        const lc = `${this.lc}[${this.reconcile_MergeLocalWithStore_ViaState.name}]`;
        try {
            // we're essentially just creating a new ibgib with the merged data.
            const resMut8 = await mut8({
                src: latestIbGib_Local,
                // we're just going to merge the data properties.
                // this is a naive "last-write-wins" approach for non-dna timelines
                dataToAddOrPatch: { ...latestIbGib_Local.data },
                noTimestamp: true, // we want to control the ib and gib
            });
            const mergedIbGib = resMut8.newIbGib;
            const mergedAddr = getIbGibAddr({ ibGib: mergedIbGib });
            if (resMut8.intermediateIbGibs) {
                resMut8.intermediateIbGibs.forEach(x => ibGibsCreated.push(x));
            }
            ibGibsCreated.push(mergedIbGib);
            ibGibMergeMap[latestAddr_Local] = mergedIbGib;
            ibGibMergeMap[latestAddr_Store] = mergedIbGib;
            updates[tjpAddr] = mergedAddr;
        }
        catch (error) {
            const emsg = `${lc} ${extractErrorMsg(error)}`;
            console.error(emsg);
            throw error;
        }
    }
    async reconcile_InsertFirstTimeIntoStore({ tjpGroupIbGibs_Local_Ascending, ibGibsToStore, }) {
        const lc = `${this.lc}[${this.reconcile_InsertFirstTimeIntoStore.name}]`;
        try {
            // This is the first time we are seeing this tjp in the store.
            // So we can just add the entire local timeline to the list of
            // ibgibs to be persisted.
            tjpGroupIbGibs_Local_Ascending.forEach(x => ibGibsToStore.push(x));
        }
        catch (error) {
            const emsg = `${lc} ${extractErrorMsg(error)}`;
            console.error(emsg);
            throw error;
        }
    }
    // protected async applyTransforms({
    //     src,
    //     createdIbGibs_Running,
    //     dnaAddrsToApplyToStoreVersion,
    //     allLocalIbGibs,
    // }: {
    //     src: TIbGib,
    //     createdIbGibs_Running: TIbGib[],
    //     dnaAddrsToApplyToStoreVersion: IbGibAddr[],
    //     allLocalIbGibs: TIbGib[],
    // }): Promise<TIbGib> {
    //     const lc = `${this.lc}[${this.applyTransforms.name}]`;
    //     try {
    //         if (logalot) { console.log(`${lc} starting...`); }
    //         let currentSrc = src;
    //         // find the dna ibgibs from the local ibgibs that were passed in.
    //         const allLocalIbGibsMap = new Map<IbGibAddr, TIbGib>();
    //         allLocalIbGibs.forEach(x => allLocalIbGibsMap.set(getIbGibAddr({ ibGib: x }), x));
    //         for (const dnaAddr of dnaAddrsToApplyToStoreVersion) {
    //             const dnaIbGib = allLocalIbGibsMap.get(dnaAddr);
    //             if (!dnaIbGib) { throw new Error(`(UNEXPECTED) dnaIbGib not found in allLocalIbGibs. addr: ${dnaAddr} (E: 106402377a644e5989710f606e9323ff)`); }
    //             const resMut8 = await mut8({
    //                 src: currentSrc,
    //                 mut8Ib: dnaIbGib.ib,
    //                 dataToAddOrPatch: { ...dnaIbGib.data }, // this is a transform, so we just care about data and ib
    //                 noTimestamp: true,
    //             });
    //             currentSrc = resMut8.newIbGib as TIbGib;
    //             if (resMut8.intermediateIbGibs) {
    //                 resMut8.intermediateIbGibs.forEach(x => createdIbGibs_Running.push(x as TIbGib));
    //             }
    //             createdIbGibs_Running.push(currentSrc);
    //         }
    //         return currentSrc;
    //     } catch (error) {
    //         const emsg = `${lc} ${extractErrorMsg(error)}`;
    //         console.error(emsg);
    //         throw error;
    //     }
    // }
    async routeAndDoCommand({ cmd, cmdModifiers, arg, }) {
        const lc = `${this.lc}[${this.routeAndDoCommand.name}]`;
        if ((cmdModifiers ?? []).length === 0 || !cmdModifiers.includes('sync')) {
            return super.routeAndDoCommand({ cmd, cmdModifiers, arg });
        }
        switch (cmd) {
            case IbGibSpaceOptionsCmd.put:
                if (cmdModifiers.includes('sync')) {
                    if (logalot) {
                        console.log(`${lc} cmd is put and modifier includes sync. Routing to putSync function. (I: 2dc358c88a1746329b379d4e8ba7e09e)`);
                    }
                    let resPutSync = await this.putSync(arg);
                    return resPutSync;
                }
                else {
                    return super.routeAndDoCommand({ cmd, cmdModifiers, arg });
                }
            default:
                return super.routeAndDoCommand({ cmd, cmdModifiers, arg });
        }
    }
    async putSync(arg) {
        let lc = `${this.lc}[${this.putSync.name}]`;
        const resultData = { optsAddr: getIbGibAddr({ ibGib: arg }), };
        let errors = [];
        let warnings = [];
        let syncSagaInfo = arg?.syncSagaInfo;
        const verbose = logalot;
        try {
            // #region validation, initialize some variables
            if (!arg.data) {
                throw new Error(`arg.data required. (E: 847a1506e7054d53b7bf5ff87a4b32da)`);
            }
            if ((arg.data.ibGibAddrs ?? []).length === 0) {
                throw new Error(`arg.data.ibGibAddrs required. (E: 6f2062572cc247f6a12b34759418c66b)`);
            }
            throwIfDuplicates({ ibGibAddrs: arg.data.ibGibAddrs });
            if (!arg.data.sagaId) {
                throw new Error(`sagaId required. (E: af30b1b3cf3a4676a89399514743da79)`);
            }
            const timeLogName = `[sync_log][${arg.data.sagaId}]`;
            if (verbose) {
                console.timeLog(timeLogName, `${lc} starting...`);
            }
            if ((arg.ibGibs ?? []).length === 0) {
                throw new Error(`no ibgibs given. (E: 62ae74eab0434b90b866caa285403143)`);
            }
            throwIfDuplicates({ ibGibs: arg.ibGibs });
            if (!arg.syncSagaInfo) {
                throw new Error(`arg.syncSagaInfo required. (E: 33efb28789ff40b9b340eedcba0017f7)`);
            }
            if (!arg.syncSagaInfo.spaceId) {
                throw new Error(`arg.syncSagaInfo.spaceId required. (E: b20f6dbf3c4a466c9ff5aa7488e903ee)`);
            }
            if ((arg.data.participants ?? []).length === 0) {
                throw new Error(`arg.data.participants required. (E: 3e8121dc078342d18810a3e2c9671f98)`);
            }
            if (arg.data.participants.filter(x => x.s_d === "src").length === 0) {
                throw new Error(`invalid participants. src required. (E: fbfcab1b8cf34d4a8d51a48a0a43e6af)`);
            }
            if (arg.data.participants.filter(x => x.s_d === "src").length > 1) {
                throw new Error(`invalid participants. only 1 src allowed. (E: 9c2d76ab4dc141ce878de2649a68d4b6)`);
            }
            if (arg.data.participants.filter(x => x.s_d === "dest").length === 0) {
                throw new Error(`invalid participants. at least 1 dest required. (E: 7d037be500844191b25655521eb4cde7)`);
            }
            const srcSpaceId = arg.data.participants.filter(p => p.s_d === "src")[0].id;
            if (!srcSpaceId) {
                throw new Error(`invalid participants. srcSpaceId required. (E: c55c96ebb1c9429fabb0e90adf5ce157)`);
            }
            if (!this.data) {
                throw new Error(`(UNEXPECTED) !this.data? (E: 77163b8ffc7f439a8ba8b410db0071e7)`);
            }
            // #endregion validation, initialize some variables
            const { statusIbGib: syncStatusIbGib_Start, statusIbGibsGraph: statusStartIbGibs } = await this.getStatusIbGibs_Start({
                sagaId: arg.data.sagaId,
                participants: arg.data.participants,
                ibGibAddrs: arg.data.ibGibAddrs,
            });
            resultData.sagaId = arg.data.sagaId;
            if (!syncStatusIbGib_Start.rel8ns?.tjp?.at(0)) {
                throw new Error(`(UNEXPECTED) !syncStatusIbGib_Start.rel8ns?.tjp?.at(0)? (E: 7ab2b8efb18a446d8695a01cada13c24)`);
            }
            resultData.statusTjpAddr = syncStatusIbGib_Start.rel8ns.tjp[0];
            this.spinOffToCompleteSync({
                srcSpaceId,
                sagaId: arg.data.sagaId,
                ibGibs: arg.ibGibs.concat(),
                statusStartIbGibs: statusStartIbGibs,
                syncStatusIbGib_Start,
                syncStatus$: syncSagaInfo.syncStatus$,
                errors,
                warnings,
            });
        }
        catch (error) {
            console.error(`${lc} error: ${extractErrorMsg(error)}`);
            resultData.errors = [extractErrorMsg(error)];
        }
        try {
            const result = await this.resulty({ resultData });
            result.syncSagaInfo = syncSagaInfo;
            return result;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
    }
    async getStatusIbGibs_Start({ sagaId, participants, ibGibAddrs, }) {
        const lc = `${this.lc}[${this.getStatusIbGibs_Start.name}]`;
        try {
            if ((participants ?? []).length === 0) {
                throw new Error(`participants required. (E: a707efcdd7594a4aa70599e84ffa43c4)`);
            }
            const parentIb = getStatusIb({
                spaceType: 'sync', spaceSubtype: 'aws-dynamodb',
                statusCode: StatusCode.undefined,
                sagaId: STATUS_UNDEFINED_TX_ID, // "undefined" means '0' atow!!
            });
            const parentIbGib = factory.primitive({ ib: parentIb });
            const statusCode = StatusCode.started;
            const tjpIb = getStatusIb({
                statusCode,
                spaceType: 'sync', spaceSubtype: 'aws-dynamodb',
                sagaId,
            });
            const data = {
                statusCode: statusCode,
                participants,
                toTx: ibGibAddrs,
            };
            const resTjp = await factory.firstGen({
                parentIbGib,
                ib: tjpIb,
                data,
                dna: false,
                nCounter: true,
                tjp: { uuid: true, timestamp: true },
            });
            const startIbGib = resTjp.newIbGib;
            let statusIbGibsGraph = [
                startIbGib,
                ...(resTjp.intermediateIbGibs ?? []),
            ];
            return { statusIbGib: startIbGib, statusIbGibsGraph };
        }
        catch (error) {
            const emsg = `${lc} ${extractErrorMsg(error)}`;
            console.error(emsg);
            throw error;
        }
    }
}
//# sourceMappingURL=reconciliation-space-base.mjs.map