import { extractErrorMsg, getUUID, groupBy } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
import { IbGibAddr } from '@ibgib/ts-gib/dist/types.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 {
    IbGibSpaceData,
    IbGibSpaceOptionsData,
    IbGibSpaceOptionsRel8ns,
    IbGibSpaceOptionsIbGib,
    IbGibSpaceResultData,
    IbGibSpaceResultRel8ns,
    IbGibSpaceResultIbGib,
    IbGibSpaceRel8ns,
    IbGibSpaceOptionsCmd,
} from '../space-types.mjs';
import { execInSpaceWithLocking, getSpaceIb, throwIfDuplicates, } from '../space-helper.mjs';
import { getTjpAddr, splitPerTjpAndOrDna, } from '../../../common/other/ibgib-helper.mjs';
import { getStatusIb } from '../outer-space/outer-space-helper.mjs';
import { ParticipantInfo, StatusCode, SyncSagaInfo, SyncSpaceOptionsCmdModifier, SyncSpaceOptionsIbGib, SyncStatusData, SyncStatusIbGib } from '../outer-space/outer-space-types.mjs';
import { STATUS_UNDEFINED_TX_ID } from '../space-constants.mjs';
import { SubjectWitness } from '../../../common/pubsub/subject/subject-types.mjs';
import { applyTransforms } from './reconciliation-space-helper.mjs';


const logalot = GLOBAL_LOG_A_LOT;

export interface ReconciliationSpaceData extends IbGibSpaceData {
    // currently no special properties, but creating for extensibility
}
export interface ReconciliationSpaceRel8ns extends IbGibSpaceRel8ns {
    // currently no special properties, but creating for extensibility
}


/**
 * 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 abstract class ReconciliationSpaceBase<
    TIbGib extends IbGib_V1 = IbGib_V1,
    TData extends ReconciliationSpaceData = ReconciliationSpaceData,
    TRel8ns extends ReconciliationSpaceRel8ns = ReconciliationSpaceRel8ns,
    TSpaceOptionsData extends IbGibSpaceOptionsData = IbGibSpaceOptionsData,
    TSpaceOptionsRel8ns extends IbGibSpaceOptionsRel8ns = IbGibSpaceOptionsRel8ns,
    TSpaceOptionsIbGib extends IbGibSpaceOptionsIbGib<TIbGib, TSpaceOptionsData, TSpaceOptionsRel8ns> = IbGibSpaceOptionsIbGib<TIbGib, TSpaceOptionsData, TSpaceOptionsRel8ns>,
    TSpaceResultData extends IbGibSpaceResultData = IbGibSpaceResultData,
    TSpaceResultRel8ns extends IbGibSpaceResultRel8ns = IbGibSpaceResultRel8ns,
    TSpaceResultIbGib extends IbGibSpaceResultIbGib<TIbGib, TSpaceResultData, TSpaceResultRel8ns> = IbGibSpaceResultIbGib<TIbGib, TSpaceResultData, TSpaceResultRel8ns>
> extends SpaceBase_V1<
    TIbGib,
    TSpaceOptionsData,
    TSpaceOptionsRel8ns,
    TSpaceOptionsIbGib,
    TSpaceResultData,
    TSpaceResultRel8ns,
    TSpaceResultIbGib,
    TData,
    TRel8ns
> {

    protected lc: string = `[ReconciliationSpaceBase]`;

    protected abstract _getLatestAddrsInStore(ibGibs: TIbGib[]): Promise<{ [addr: string]: IbGibAddr | null }>;

    protected abstract _getIbGibsFromStore(addrs: IbGibAddr[]): Promise<{ [addr: string]: TIbGib }>;

    protected abstract _putIbGibsToStore(ibGibs: TIbGib[]): Promise<void>;

    protected abstract _deleteIbGibsFromStore(addrs: IbGibAddr[]): Promise<void>;

    // protected abstract spinOffToCompleteSync(arg: any): Promise<void>;
    protected async spinOffToCompleteSync({
        srcSpaceId,
        sagaId,
        ibGibs,
        statusStartIbGibs,
        syncStatusIbGib_Start,
        syncStatus$,
        errors,
        warnings,
    }: {
        srcSpaceId: string,
        sagaId: string,
        ibGibs: TIbGib[],
        statusStartIbGibs: TIbGib[],
        syncStatusIbGib_Start: SyncStatusIbGib,
        syncStatus$: SubjectWitness<SyncStatusIbGib>;
        errors: string[],
        warnings: string[],
    }): Promise<void> {
        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: TIbGib[] = [];
            const updates: { [tjpAddr: string]: IbGibAddr } = {};

            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: TIbGib[] = Object.values(mapWithoutTjps) as TIbGib[];
            const stoneIbGibsAddrToIbGibMap: { [addr: string]: TIbGib } = {};
            stoneIbGibs.forEach(x => { stoneIbGibsAddrToIbGibMap[getIbGibAddr({ ibGib: x })] = x as TIbGib; });
            const stoneAddrs = Object.keys(stoneIbGibsAddrToIbGibMap);

            const stoneIbGibsNotInStore: TIbGib[] = 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: 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: TIbGib[] = [];
                        const ibGibsCreated_thisTjp: TIbGib[] = [];
                        const ibGibsOnlyInStore_thisTjp: TIbGib[] = [];
                        const ibGibsMergeMap_thisTjp: { [oldAddr: string]: TIbGib } = {};
                        const latestAddr_Store = resLatestAddrsMap[tjpOrStoneAddr];
                        const tjpGroupIbGibs_Local_Ascending: TIbGib[] = 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) as TIbGib[] :
                            [];

                        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: { [oldAddr: string]: IbGibAddr } = {};
                        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,
                            } satisfies Partial<SyncStatusData>,
                            dna: false,
                        });

                        statusIbGib = resSyncStatusIbGib.newIbGib as SyncStatusIbGib;
                        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,
                } satisfies Partial<SyncStatusData>,
                dna: false,
            });
            statusIbGib = resSyncStatusIbGib_Complete.newIbGib as SyncStatusIbGib;
            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);
            }
        }
    }

    protected async saveInStoreIfConfigAndPublishStatus({
        statusIbGib,
        syncStatus$
    }: {
        statusIbGib: SyncStatusIbGib,
        syncStatus$: SubjectWitness<SyncStatusIbGib>
    }): Promise<void> {
        const lc = `${this.lc}[${this.saveInStoreIfConfigAndPublishStatus.name}]`;
        try {
            if ((this.data as any)?.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! as TIbGib[]);
            }

            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;
    protected reconcile_UpdateStoreWithMoreRecentLocal({
        tjpAddr,
        latestAddr_Store,
        tjpGroupAddrs_Local_Ascending,
        tjpGroupIbGibs_Local_Ascending,
        ibGibsToStore,
        updates,
    }: {
        tjpAddr: IbGibAddr,
        latestAddr_Store: IbGibAddr,
        tjpGroupAddrs_Local_Ascending: IbGibAddr[],
        tjpGroupIbGibs_Local_Ascending: TIbGib[],
        ibGibsToStore: TIbGib[],
        updates: { [tjpAddr: string]: IbGibAddr },
    }): void {
        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;
        }
    }

    protected async getDna(arg: {
        ibGib: TIbGib,
        throwOnDnaFail?: boolean,
    }): Promise<TIbGib[]> {
        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 as TIbGib[];
        } catch (error) {
            const emsg = `${lc} ${extractErrorMsg(error)}`;
            console.error(emsg);
            throw error;
        }
    }


    protected async reconcile_MergeLocalWithStore_ViaDna({
        tjpAddr,
        latestAddr_Local,
        latestIbGib_Local,
        latestAddr_Store,
        latestIbGib_Store,
        ibGibsCreated,
        ibGibMergeMap,
        ibGibsOnlyInStore,
        allLocalIbGibs,
        updates,
        timeLogName,
    }: {
        tjpAddr: IbGibAddr,
        latestAddr_Local: IbGibAddr,
        latestIbGib_Local: TIbGib,
        latestAddr_Store: IbGibAddr,
        latestIbGib_Store: TIbGib,
        ibGibsCreated: TIbGib[],
        ibGibsOnlyInStore: TIbGib[],
        ibGibMergeMap: { [oldLatestAddr: string]: TIbGib },
        allLocalIbGibs: TIbGib[],
        updates: { [tjpAddr: string]: IbGibAddr },
        timeLogName: string,
    }): Promise<void> {
        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: IbGibAddr | undefined;
            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: TIbGib[] = [];
                const resApply = await applyTransforms({
                    src: latestIbGib_Store,
                    createdIbGibs_Running,
                    dnaAddrsToApplyToStoreVersion,
                    allLocalIbGibs,
                }) as TIbGib;
                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: dnaAddrsToApplyToLocalVersion,
                    allLocalIbGibs: ibGibsOnlyInStore,
                }) as TIbGib;
                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>;
    protected async reconcile_MergeLocalWithStore_ViaState({
        tjpAddr,
        latestIbGib_Local,
        latestAddr_Local,
        latestAddr_Store,
        ibGibsCreated,
        ibGibMergeMap,
        updates,
    }: {
        tjpAddr: IbGibAddr,
        latestIbGib_Local: TIbGib,
        latestAddr_Local: IbGibAddr,
        latestAddr_Store: IbGibAddr,
        ibGibsCreated: TIbGib[],
        ibGibMergeMap: { [oldLatestAddr: string]: TIbGib },
        updates: { [tjpAddr: string]: IbGibAddr },
    }): Promise<void> {
        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 as TIbGib;
            const mergedAddr = getIbGibAddr({ ibGib: mergedIbGib });
            if (resMut8.intermediateIbGibs) {
                resMut8.intermediateIbGibs.forEach(x => ibGibsCreated.push(x as TIbGib));
            }
            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;
        }
    }

    protected async reconcile_InsertFirstTimeIntoStore({
        tjpGroupIbGibs_Local_Ascending,
        ibGibsToStore,
    }: {
        tjpGroupIbGibs_Local_Ascending: TIbGib[],
        ibGibsToStore: TIbGib[],
    }): Promise<void> {
        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;
    //     }
    // }


    protected async routeAndDoCommand<TCmdModifier extends SyncSpaceOptionsCmdModifier = SyncSpaceOptionsCmdModifier>({
        cmd,
        cmdModifiers,
        arg,
    }: {
        cmd: IbGibSpaceOptionsCmd | string,
        cmdModifiers: (TCmdModifier | string)[],
        arg: TSpaceOptionsIbGib,
    }): Promise<TSpaceResultIbGib | undefined> {
        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 as unknown as SyncSpaceOptionsIbGib);
                    return resPutSync as unknown as TSpaceResultIbGib;
                } else {
                    return super.routeAndDoCommand({ cmd, cmdModifiers, arg });
                }

            default:
                return super.routeAndDoCommand({ cmd, cmdModifiers, arg });
        }
    }

    protected async putSync(arg: SyncSpaceOptionsIbGib):
        Promise<IbGibSpaceResultIbGib<TIbGib, IbGibSpaceResultData, IbGibSpaceResultRel8ns>> {
        let lc = `${this.lc}[${this.putSync.name}]`;
        const resultData: TSpaceResultData = { optsAddr: getIbGibAddr({ ibGib: arg }), } as TSpaceResultData;
        let errors: string[] = [];
        let warnings: string[] = [];
        let syncSagaInfo: 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 as any).sagaId = arg.data.sagaId;
            if (!syncStatusIbGib_Start.rel8ns?.tjp?.at(0)) { throw new Error(`(UNEXPECTED) !syncStatusIbGib_Start.rel8ns?.tjp?.at(0)? (E: 7ab2b8efb18a446d8695a01cada13c24)`); }
            (resultData as any).statusTjpAddr = syncStatusIbGib_Start.rel8ns.tjp[0];

            this.spinOffToCompleteSync({
                srcSpaceId,
                sagaId: arg.data.sagaId,
                ibGibs: arg.ibGibs!.concat() as TIbGib[],
                statusStartIbGibs: statusStartIbGibs as TIbGib[],
                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 as any).syncSagaInfo = syncSagaInfo;
            return result;
        } catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
    }

    private async getStatusIbGibs_Start({
        sagaId,
        participants,
        ibGibAddrs,
    }: {
        sagaId: string,
        participants: ParticipantInfo[],
        ibGibAddrs: IbGibAddr[],
    }): Promise<{ statusIbGib: SyncStatusIbGib, statusIbGibsGraph: IbGib_V1[] }> {
        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,
            } satisfies SyncStatusData;
            const resTjp = await factory.firstGen({
                parentIbGib,
                ib: tjpIb,
                data,
                dna: false,
                nCounter: true,
                tjp: { uuid: true, timestamp: true },
            });
            const startIbGib = resTjp.newIbGib as SyncStatusIbGib;
            let statusIbGibsGraph: IbGib_V1[] = [
                startIbGib,
                ...(resTjp.intermediateIbGibs ?? []),
            ];

            return { statusIbGib: startIbGib, statusIbGibsGraph };
        } catch (error) {
            const emsg = `${lc} ${extractErrorMsg(error)}`;
            console.error(emsg);
            throw error;
        }
    }

}
