/**
 * @module timeline-api.mts This facade provides a simplified, timeline-oriented
 * API for interacting with ibGibs, abstracting away low-level transform details
 * (like nCounter, dna, dna, tjp rel8ns) and handling concurrency control (via
 * locking) for maintaining timeline integrity.
 */
import { extractErrorMsg, getTimestampInTicks } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
import { Rel8n } from "@ibgib/ts-gib/dist/V1/types.mjs";
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
import { mut8 } from "@ibgib/ts-gib/dist/V1/transforms/mut8.mjs";
import { rel8 } from "@ibgib/ts-gib/dist/V1/transforms/rel8.mjs";
import { getGibInfo, isPrimitive } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
import { ROOT } from '@ibgib/ts-gib/dist/V1/constants.mjs';
import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
import { getSpecialConfigKey, toDto } from "../common/other/ibgib-helper.mjs";
import { execInSpaceWithLocking, persistTransformResult } from "../witness/space/space-helper.mjs";
import { toFlatGraph } from "../common/other/graph-helper.mjs";
const logalot = GLOBAL_LOG_A_LOT;
/**
 * Deterministically generates a lock scope for a given timeline.
 *
 * ## internal details, may change
 *
 * ATOW (05/2025), this will return the tjpGib of the timeline.  So if the addr
 * is `comment Foo^ABC123.DEF456`, where ABC123 and DEF456 are hashes, then this
 * will return `DEF456`. For an addr that is just `comment Bar^GHI789`, then
 * this *is* the TJP and thus its gib is `GHI789` and that is what is returned.
 */
export async function getLockScope({ timeline }) {
    const lc = `[${getLockScope.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting...`);
        }
        if (isPrimitive({ ibGib: timeline })) {
            // this is an abnormal/unusual path
            console.warn(`${lc} getting lock scope on a primitive ibgib can grossly impact performance. (W: 55203e415ae2ac7ddb19c21772b28925)`);
            return getIbGibAddr({ ibGib: timeline });
        }
        else {
            // happy path
            const gibInfo = getGibInfo({ gib: timeline.gib });
            const tjpGib = gibInfo.tjpGib ?? gibInfo.punctiliarHash;
            if (!tjpGib) {
                // This should not happen for a valid ibGib, but as a safeguard
                throw new Error(`${lc} (UNEXPECTED) Could not determine tjpGib or punctiliarHash from ibGib. (E: eb3b6472e9b335991a1b0869f772f7d3)`);
            }
            return tjpGib;
        }
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function isSameTimeline({ a, b, }) {
    const lc = `[${isSameTimeline.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 0f8b68106f48a5c5c8d67f58b4b4d825)`);
        }
        // DRY kluge b/c I'm soooooooo tired ...need to gtfo here
        const aLockScope = await getLockScope({ timeline: a });
        const bLockScope = await getLockScope({ timeline: b });
        return aLockScope === bLockScope;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Appends a list of ibGibs to a timeline in the order given using the
 * associated rel8nName (named edge).
 *
 * ## regarding persistence and registerNewIbGib pubsub
 *
 * The caller is responsible for persisting any ibGibs that are contained in
 * {@link rel8nInfos}. However, all newly created ibgibs resulting from extending
 * {@link timeline} *WILL* be saved in the {@link space}.
 *
 * This also will call `metaspace.registerNewIbGib` for the new timeline ibgib
 * created ONLY, and will only do this AFTER both...
 * * the timeline's lock is released AND AFTER
 * * any indexes are updated if given via {@link timelineIndexInfo}.
 * * atow (05/2025) this does not await the registerNewIbGib call, but this may
 *   change at any time.
 */
export async function appendToTimeline({ timeline, rel8nInfos, rel8nRemovalInfos, timelineIndexInfo, metaspace, space, skipLock, noDna, }) {
    const lc = `[${appendToTimeline.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: fcdff10832215068019355265f4cbc25)`);
        }
        space ??= await metaspace.getLocalUserSpace({ lock: false });
        if (!space) {
            throw new Error(`(UNEXPECTED) space falsy? couldn't even get default local user space? (E: 2051b44a052d6a2984b9a22f680e4625)`);
        }
        const lockScope = await getLockScope({ timeline });
        /**
         * This fn actually does the append and will be executed in the locked
         * setting if skipLock is falsy.
         */
        const fn = async () => {
            if (!space) {
                throw new Error(`(UNEXPECTED) space falsy? couldn't even get default local user space? (E: 845c4181c8c15d7219166ceb3c4dca25)`);
            }
            // Get the latest version of the provided timeline ibgib.
            const timelineAddr = getIbGibAddr({ ibGib: timeline });
            const latestTimelineIbGibAddr = await metaspace.getLatestAddr({
                addr: timelineAddr,
                space,
            });
            if (!latestTimelineIbGibAddr) {
                throw new Error(`${lc} (UNEXPECTED) Could not get latest address for timeline ${getIbGibAddr({ ibGib: timeline })}. (E: a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9)`);
            }
            let latestTimelineIbGibDto = toDto({ ibGib: timeline });
            if (timelineAddr !== latestTimelineIbGibAddr) {
                const resGetLatest = await metaspace.get({
                    addrs: [latestTimelineIbGibAddr],
                    space,
                });
                if (resGetLatest.errorMsg || (resGetLatest.ibGibs ?? []).length !== 1) {
                    throw new Error(`${lc} (UNEXPECTED) couldn't get latest timeline ibgib (${latestTimelineIbGibAddr}) from space (${space.ib}). errorMsg: ${resGetLatest.errorMsg ?? '[unknown error]'} (E: b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0)`);
                }
                latestTimelineIbGibDto = resGetLatest.ibGibs.at(0);
            }
            /**
             * these rel8ns will be passed into rel8 transform for removal
             */
            let rel8nsToRemoveByAddr = undefined;
            if (rel8nRemovalInfos && rel8nRemovalInfos.length > 0) {
                rel8nsToRemoveByAddr = {};
                for (const rel8nRemovalInfo of rel8nRemovalInfos) {
                    const { rel8nName, ibGibAddrs } = rel8nRemovalInfo;
                    if (ibGibAddrs.length === 0) {
                        console.warn(`${lc} tried to remove 0 ibGibAddrs in rel8nName ${rel8nName}? skipping... (W: 93aeb601be66d680989e72ec70e4c826)`);
                        continue;
                    }
                    if (!rel8nsToRemoveByAddr[rel8nName]) {
                        rel8nsToRemoveByAddr[rel8nName] = [];
                    }
                    rel8nsToRemoveByAddr[rel8nName] = rel8nsToRemoveByAddr[rel8nName].concat(ibGibAddrs);
                }
            }
            // Add rel8ns from the provided rel8nInfos.
            /**
             * these rel8ns will be passed into rel8 transform to be added
             */
            const rel8nsToAddByAddr = {};
            for (const rel8nInfo of rel8nInfos) {
                const { rel8nName, ibGibs } = rel8nInfo;
                if (ibGibs.length === 0) {
                    console.warn(`${lc} tried to add 0 ibGibs to rel8nName ${rel8nName}? skipping... (W: 708252dfdeb60757fee4554e769aaa25)`);
                    continue;
                }
                if (!rel8nsToAddByAddr[rel8nName]) {
                    rel8nsToAddByAddr[rel8nName] = [];
                }
                rel8nsToAddByAddr[rel8nName].push(...ibGibs.map(ibGib => getIbGibAddr({ ibGib })));
            }
            // Perform a rel8 transform on the latest timeline ibgib.
            const resRel8 = await rel8({
                src: latestTimelineIbGibDto,
                rel8nsToRemoveByAddr,
                rel8nsToAddByAddr,
                dna: noDna ? false : true,
                nCounter: true,
            });
            const newTimelineIbGib = resRel8.newIbGib;
            // Persist the transform result.
            await metaspace.persistTransformResult({ resTransform: resRel8, space });
            // If timelineIndexInfo is provided, update the special index.
            if (timelineIndexInfo) {
                await updateSpecialIndex({
                    type: timelineIndexInfo.type,
                    rel8nInfos: [{ rel8nName: timelineIndexInfo.rel8nName, ibGibs: [newTimelineIbGib] }],
                    metaspace,
                    space,
                    skipLock,
                });
            }
            // should we publish within the lock or not within the lock?
            // should we await the publish or spin it off
            // right now I have this spin off, inside the lock
            // await metaspace.registerNewIbGib({ ibGib: newTimelineIbGib, space });
            metaspace.registerNewIbGib({ ibGib: newTimelineIbGib, space }); // spin off
            return newTimelineIbGib;
        };
        const newTimelineIbGib = skipLock ?
            await fn() :
            await execInSpaceWithLocking({
                scope: lockScope,
                secondsValid: 180, // Example timeout
                maxDelayMs: 100, // Example delay
                maxLockAttempts: 1800, // Example attempts
                space,
                callerInstanceId: getTimestampInTicks(),
                fn,
            });
        // should we publish within the lock or not within the lock?
        // should we await the publish or spin it off
        // right now I have this spin off, inside the lock
        // await metaspace.registerNewIbGib({ ibGib: newTimelineIbGib, space });
        // metaspace.registerNewIbGib({ ibGib: newTimelineIbGib, space }); // spin off
        return newTimelineIbGib;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Updates a special ibgib in the given {@link space} with new and/or updated
 * {@link rel8nInfos}.
 *
 * ## on "special" ibgibs
 *
 * "Special" ibgibs are mostly used as indexes, but not necessarily. These are
 * slightly different than most normal ibgibs in that they are not in some way
 * or another attached to a root in the space, rather, they are rel8d directly
 * to the local user space itself. So when a special ibgib is changed, i.e. its
 * own timeline is extended, its new address is always proactively updated on
 * the space directly.  so `space.rel8ns[key]` (where key is derived from the
 * given {@link type}) is updated with newSpecialIbGib's addr.
 *
 * @see {@link setConfigAddr}
 * @see {@link rel8ToSpecialIbGib}
 *
 * ## upsert
 *
 * This is analogous to an upsert operation *into* the special index. So we are
 * updating the index's rel8ns either way whether or not that rel8n already
 * exists.
 */
export async function updateSpecialIndex({ type, rel8nInfos, dataToAddOrPatch, metaspace, space, skipLock, }) {
    const lc = `[${updateSpecialIndex.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 6e7d05fe916d813b71766e2eaa747c25)`);
        }
        if (type === "latest") {
            throw new Error(`cannot update the "latest" special index by this function. The registerNewIbGib will do this automatically. (E: 231eb78a221ccdbcf56cd1899260a425)`);
        }
        space ??= await metaspace.getLocalUserSpace({ lock: false });
        if (!space) {
            throw new Error(`(UNEXPECTED) space falsy? couldn't even get default local user space? (E: 43067821a4749a7648fe2612d25d7f25)`);
        }
        /**
         * This fn actually does the update and will be executed in the locked
         * setting.
         */
        const fn = async () => {
            const fnGetSpecialIndex = async () => {
                // always get the latest version of the special index ibgib
                // fresh. The initialize: true option will create it if it
                // doesn't exist.
                const specialIndex = await metaspace.getSpecialIbGib({
                    type,
                    space,
                    initialize: true,
                });
                if (!specialIndex) {
                    throw new Error(`(UNEXPECTED) couldn't initialize/get special ibgib index of type ${type}. (E: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6)`);
                }
                if (!specialIndex.rel8ns) {
                    console.warn(`${lc} not sure if this ever hits, but is there ever a scenario where specialIndex.rel8ns is falsy? (W: 06161c366bc7ec858b9b5aa7ee9b6d25)`);
                }
                return specialIndex;
            };
            for (const rel8nInfo of (rel8nInfos ?? [])) {
                // parse the rel8nInfo for this iteration
                const { rel8nName, ibGibs } = rel8nInfo;
                if (ibGibs.length === 0) {
                    console.error(`${lc} ibGibs empty. skipping rel8nName (${rel8nName}). (E: 150d74f73f97aea1b97d173d92c77125)`);
                }
                const specialIndex = await fnGetSpecialIndex();
                // // always get the latest version of the special index ibgib
                // // fresh. The initialize: true option will create it if it
                // // doesn't exist.
                // const specialIndex = await metaspace.getSpecialIbGib({
                //     type,
                //     space,
                //     initialize: true,
                // });
                // if (!specialIndex) {
                //     throw new Error(`(UNEXPECTED) couldn't initialize/get special ibgib index of type ${type}. (E: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6)`);
                // }
                // if (!specialIndex.rel8ns) {
                //     console.warn(`${lc} not sure if this ever hits, but is there ever a scenario where specialIndex.rel8ns is falsy? (W: 06161c366bc7ec858b9b5aa7ee9b6d25)`)
                // }
                const existingSpecialIndexRel8ns = specialIndex.rel8ns ?? {};
                // before we just add all the ibgibs, we need to gather info on
                // what previous ibgibs we need to unrel8
                let addrsToUnRel8 = undefined;
                // check if we even need to even compare existing rel8ns
                const existingAddrsThisRel8nName = existingSpecialIndexRel8ns[rel8nName] ?? [];
                if (existingAddrsThisRel8nName.length > 0) {
                    // we have existing rel8ns for this rel8nName, so remove any
                    // that share the tjpGib of addrs we're adding or use the
                    // past/current full addrs. check actual code here for impl
                    const toUnrel8ThisRel8nName_Set = new Set();
                    ibGibs.forEach(ibGib => {
                        const addr = getIbGibAddr({ ibGib });
                        // by tjpGib impl
                        // const gibInfo = getGibInfo({ gib: ibGib.gib });
                        // const tjpGib = gibInfo.tjpGib ?? gibInfo.punctiliarHash ?? ibGib.gib ?? GIB;
                        // existingAddrsThisRel8nName
                        //     .filter(x => x.includes(tjpGib))
                        //     .forEach(x => toUnrel8ThisRel8nName_Set.add(x));
                        // by full addrs impl
                        const pastAddrs = ibGib.rel8ns?.past ?? [];
                        existingAddrsThisRel8nName
                            .filter(x => [...pastAddrs, addr].includes(x))
                            .forEach(x => toUnrel8ThisRel8nName_Set.add(x));
                    });
                    if (toUnrel8ThisRel8nName_Set.size > 0) {
                        addrsToUnRel8 = Array.from(toUnrel8ThisRel8nName_Set);
                    }
                }
                // we now have enough to execute the rel8
                await metaspace.rel8ToSpecialIbGib({
                    type,
                    rel8nName,
                    ibGibsToRel8: ibGibs,
                    addrsToUnRel8,
                    space,
                    linked: false,
                    severPast: false,
                    deletePreviousSpecialIbGib: false, // only used in latest special ibgib
                });
            }
            if (Object.keys(dataToAddOrPatch ?? {}).length > 0) {
                const specialIndex = await fnGetSpecialIndex();
                const resNewSpecial = await mut8({
                    src: specialIndex,
                    dataToAddOrPatch,
                    dna: false,
                    linkedRel8ns: [Rel8n.past],
                    nCounter: true,
                });
                const newSpecialIbGib = resNewSpecial.newIbGib;
                // persist
                await persistTransformResult({ resTransform: resNewSpecial, space: space });
                // update the space ibgib which contains the special/config information
                const configKey = getSpecialConfigKey({ type });
                await metaspace.setConfigAddr({
                    key: configKey,
                    addr: getIbGibAddr({ ibGib: newSpecialIbGib }),
                    space,
                });
                await metaspace.registerNewIbGib({ ibGib: newSpecialIbGib, space, });
            }
        };
        if (skipLock) {
            await fn();
        }
        else {
            // Use execInSpaceWithLocking with the special index type as the scope.
            // We are currently choosing to always lock for special indexes for
            // safety.  The skipLock parameter from CommonTimelineOpts is included
            // in the signature for consistency but not used in the locking logic
            // here based on our decision to always lock special indexes.
            await execInSpaceWithLocking({
                scope: type, // Use the special index type as the lock scope.
                secondsValid: 60, // Example timeout
                maxDelayMs: 100, // Example delay
                maxLockAttempts: 600, // Example attempts
                space,
                // callerInstanceId: // You might need a way to generate a unique ID for the caller
                fn,
            });
        }
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function getLatestTimelineIbGibDto_nonLocking({ timeline, timelineAddr, metaspace, space, }) {
    const lc = `[${getLatestTimelineIbGibDto_nonLocking.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: f2b605361368e1956f6db878dbfeb825)`);
        }
        if (!timeline && !timelineAddr) {
            throw new Error(`either timeline or timelineAddr required. (E: 97551b2b18ecd22a18185a783f9a0825)`);
        }
        timelineAddr ??= getIbGibAddr({ ibGib: timeline });
        // const timelineAddr = getIbGibAddr({ ibGib: timeline });
        const latestTimelineIbGibAddr = await metaspace.getLatestAddr({
            addr: timelineAddr,
            space,
        }) ?? timelineAddr;
        if (!latestTimelineIbGibAddr) {
            throw new Error(`${lc} (UNEXPECTED) Could not get latest address for timeline ${getIbGibAddr({ ibGib: timeline })}. (E: a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9)`);
        }
        let latestTimelineIbGibDto; // = toDto({ ibGib: timeline }) as TIbGib;
        if (timelineAddr === latestTimelineIbGibAddr) {
            // latest addr is the same
            if (!timeline) {
                const resGetLatest = await metaspace.get({
                    addrs: [latestTimelineIbGibAddr],
                    space,
                });
                if (resGetLatest.errorMsg || (resGetLatest.ibGibs ?? []).length !== 1) {
                    throw new Error(`${lc} (UNEXPECTED) couldn't get latest timeline ibgib (${latestTimelineIbGibAddr}) from space (${space.ib}). errorMsg: ${resGetLatest.errorMsg ?? '[unknown error]'} (E: 3fc767a594788a43a4ba18c87491a825)`);
                }
                timeline = resGetLatest.ibGibs.at(0);
            }
            latestTimelineIbGibDto = toDto({ ibGib: timeline });
        }
        else {
            // latest addr is different
            const resGetLatest = await metaspace.get({
                addrs: [latestTimelineIbGibAddr],
                space,
            });
            if (resGetLatest.errorMsg || (resGetLatest.ibGibs ?? []).length !== 1) {
                throw new Error(`${lc} (UNEXPECTED) couldn't get latest timeline ibgib (${latestTimelineIbGibAddr}) from space (${space.ib}). errorMsg: ${resGetLatest.errorMsg ?? '[unknown error]'} (E: b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0)`);
            }
            latestTimelineIbGibDto = resGetLatest.ibGibs.at(0);
        }
        return latestTimelineIbGibDto;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function mut8Timeline({ timeline, timelineAddr, mut8Opts, metaspace, space, skipLock, timelineIndexInfo, noDna, }) {
    const lc = `[${mut8Timeline.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: genuuid)`);
        }
        if (!timeline && !timelineAddr) {
            throw new Error(`(UNEXPECTED) both timeline and timelineAddr falsy? either one is required. (E: a19d98c6acb87af3b876c1484f6ac825)`);
        }
        space ??= await metaspace.getLocalUserSpace({ lock: false });
        if (!space) {
            throw new Error(`(UNEXPECTED) space falsy? couldn't even get default local user space? (E: genuuid)`);
        }
        if (!timeline) {
            const resGet = await metaspace.get({
                addrs: [timelineAddr],
                space,
            });
            if (!resGet.success || resGet.ibGibs?.length !== 1) {
                throw new Error(`couldn't get timeline ibGib from timelineAddr (${timelineAddr}) in space (${space?.ib ?? 'undefined'}) (E: genuuid)`);
            }
            timeline = resGet.ibGibs.at(0);
        }
        const lockScope = await getLockScope({ timeline });
        /**
         * This fn actually does the update and will be executed in the locked
         * setting.
         */
        const fn = async () => {
            if (!space) {
                throw new Error(`(UNEXPECTED) space falsy? couldn't even get default local user space? (E: 845c4181c8c15d7219166ceb3c4dca25)`);
            }
            // Get the latest version of the provided timeline ibgib.
            if (!timeline) {
                throw new Error(`(UNEXPECTED) timeline falsy? we should be ensured that it's truthy at this point. (E: 2e1758b2cc3820531f7ac7f5f6b3a825)`);
            }
            const latestTimelineIbGibDto = await getLatestTimelineIbGibDto_nonLocking({
                timeline, metaspace, space,
            });
            // adjust mut8Opts depending
            const resMut8 = await mut8({
                ...mut8Opts,
                src: latestTimelineIbGibDto,
                dna: noDna ? false : true,
                nCounter: true,
            });
            const newTimelineIbGib = resMut8.newIbGib;
            // Persist the transform result.
            await metaspace.persistTransformResult({ resTransform: resMut8, space });
            // If timelineIndexInfo is provided, update the special index.
            if (timelineIndexInfo) {
                await updateSpecialIndex({
                    type: timelineIndexInfo.type,
                    rel8nInfos: [{ rel8nName: timelineIndexInfo.rel8nName, ibGibs: [newTimelineIbGib] }],
                    metaspace,
                    space,
                    skipLock,
                });
            }
            // should we publish within the lock or not within the lock?
            // should we await the publish or spin it off
            // right now I have this spin off, inside the lock
            // await metaspace.registerNewIbGib({ ibGib: newTimelineIbGib, space });
            // we await the register call because this spins off the publish events (we are assuming implementation details here)
            await metaspace.registerNewIbGib({ ibGib: newTimelineIbGib, space }); // already spins off
            return newTimelineIbGib;
        };
        const newTimelineIbGib = skipLock ?
            await fn() :
            await execInSpaceWithLocking({
                scope: lockScope,
                secondsValid: 180, // Example timeout
                maxDelayMs: 100, // Example delay
                maxLockAttempts: 1800, // Example attempts
                space,
                callerInstanceId: getTimestampInTicks(),
                fn,
            });
        return newTimelineIbGib;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
// todo: implement simplified locking wrappers to raw `rel8`, and `fork`
// transforms functions. These should be `rel8Timeline`, and `forkTimeline`. But
// these should not have skipLock available, since at that point you're really
// basically doing the raw transforms and can use those.
/**
 * Creates a new ibGib timeline and persists it to the given space.
 *
 * This is the primary entry point for creating any new, trackable piece of data
 * in the ibGib system.
 *
 * ## On usage of this factory function
 *
 * This is often used for one-offs or testing, but usually you will want to
 * create your own factory function and use this internally.
 *
 * ## On "New" vs. "Fork"
 *
 * Every ibGib is a transformation (a "fork") of an existing ibgib, with the
 * most primitive ibGib being the {@link ROOT} primitive. This is like the ibgib
 * analog to a zero/infinity value (the same asymptote from two sides), or in CS
 * OOP, this is like the root/base of the class hierarchy. But it's important to
 * keep in mind that time is a first-class citizen and that we are always
 * working with that instead of some detached "new" siloed thing.
 *
 * ## On Parenting
 *
 * The identity of an ibGib is tied to its ancestry via the `ancestor` rel8n
 * (`someIbGib.rel8ns.ancestor`). This function gives you two ways to specify
 * the parent to fork:
 *
 * 1. {@link parentIb}: If you provide a simple string like `'comment'` or `'picture'`,
 *    this function will handle creating the "primitive" parent ibGib
 *    (`comment^gib` or `picture^gib`) for you. This is the easiest way to
 *    create "typed" data, so that if you examine the `ancestor` rel8n, and see
 *    this, you know would know that this ibgib is a "comment" or "picture". Use
 *    this if you are not forking a particular instance of another ibGib.
 *
 * 2. {@link parentIbGib}: Provide a full, existing `IbGib` reference to be the
 *    parent. This can be used when using a previous ibGib as a template for
 *    just one use case. Note that this parent ibgib (and all of its
 *    dependency graph) must exist in the same given {@link space} (if
 *    provided), or in the default local user space via the {@link metaspace}.
 *    Almost always (always?), an existing parent ibgib will itself ultimately
 *    descend from a primitive ibgib.
 *
 * If neither `parentIb` nor `parentIbGib` is provided, it defaults to forking
 * the absolute {@link ROOT} ibGib, resulting in a generic, untyped timeline (or
 * relegates the typing to some other mechanism, like the `ibGib.ib` or other).
 *
 * @returns The entire transform result object, which includes `newIbGib` (the
 * latest/head of the newly created timeline) and `intermediateIbGibs` (the
 * primitive ibGibs that form its DNA and any interstitial ibGibs that were
 * created).
 */
export async function createTimeline({ ib, data, rel8ns, noDna, parentIb, parentIbGib, metaspace, space, }) {
    const lc = `[${createTimeline.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting...`);
        }
        space ??= await metaspace.getLocalUserSpace({ lock: false });
        if (!space) {
            throw new Error(`(UNEXPECTED) space is required, but is falsy. (E: 252994a554224b17aa9816c7a765c925)`);
        }
        // Determine the parent to fork based on the provided options.
        parentIbGib ??= parentIb ?
            // Easiest consumer-friendly path: create a primitive parent from a string.
            parentIbGib = Factory_V1.primitive({ ib: parentIb }) :
            // Default to the absolute ROOT if no parent info is given.
            parentIbGib = ROOT;
        const resNew = await Factory_V1.firstGen({
            parentIbGib,
            ib,
            data,
            rel8ns,
            dna: noDna ? false : true,
            nCounter: true,
            squash: noDna ? true : false,
            tjp: {
                /**
                 * always use the timestamp to generate a unique timeline
                 */
                timestamp: true,
                /**
                 * only generate new uuid if it's not in the incoming data
                 */
                uuid: !data?.uuid,
            },
        });
        // Persist the new timeline and its DNA to the designated space.
        await metaspace.persistTransformResult({ resTransform: resNew, space });
        await metaspace.registerNewIbGib({ ibGib: resNew.newIbGib, space });
        // We don't need to lock here because it's a new, independent timeline,
        // so there are no concurrency concerns yet.
        return resNew;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
/**
 * Retrieves the DNA ibGibs for a given ibGib.
 *
 * In V1, DNA is considered atomic and is not nested. This function performs a
 * direct lookup of the ibGibs pointed to by the `dna` rel8n.
 *
 * @returns An array of the primitive DNA ibGibs.
 */
export async function getDna({ ibGib, metaspace, space, }) {
    const lc = `[${getDna.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting...`);
        }
        space ??= await metaspace.getLocalUserSpace({ lock: false });
        if (!space) {
            throw new Error(`(UNEXPECTED) space is required. (E: c1b9a9b8c7d6e5f4a3b2a1a0b9c8d7e6)`);
        }
        const dnaAddrs = ibGib.rel8ns?.dna ?? [];
        if (dnaAddrs.length === 0) {
            return [];
        }
        const resGet = await metaspace.get({ addrs: dnaAddrs, space });
        if (!resGet.success || (resGet.ibGibs ?? []).length !== dnaAddrs.length) {
            const notFound = dnaAddrs.filter(addr => !resGet.ibGibs?.some(x => getIbGibAddr({ ibGib: x }) === addr));
            throw new Error(`Could not get all dna ibGibs for ${getIbGibAddr({ ibGib })}. Addrs not found: ${notFound.join(', ')}. Errors: ${resGet.errorMsg ?? '[unknown error (E: 92cf95774828d8a978331da859098625)]'} (E: d2c0b1a9a8b7c6d5e4f3a2b1a0b9c8d7)`);
        }
        return resGet.ibGibs;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
/**
 * Constructs a {@link TimelineHistoryInfo} record about the given timeline
 * (using the {@link timeline} `ibGib.rel8ns.past`), with loaded past ibgibs,
 * and if {@link includeDna}, a reference to their dna ibgibs as well.
 *
 * @returns A complete, ordered array of ibGibs constituting the timeline,
 * from the earliest ancestor to the provided `timeline` ibGib.
 */
export async function getHistory({ timeline, getLatest, includeDna, metaspace, space, }) {
    const lc = `[${getHistory.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 7fbc442a27e8f9fda8ac4228ded84825)`);
        }
        space ??= await metaspace.getLocalUserSpace({ lock: false });
        if (!space) {
            throw new Error(`(UNEXPECTED) space is required. (E: e3d1c0b1a9a8b7c6d5e4f3a2b1a0b9c8)`);
        }
        const headIbGib = getLatest ?
            await getLatestTimelineIbGibDto_nonLocking({ timeline, metaspace, space }) :
            toDto({ ibGib: timeline });
        const headAddr = getIbGibAddr({ ibGib: headIbGib });
        let pastIbGibMap = {};
        const dnaMap = includeDna ?
            toFlatGraph({
                ibGibs: await getDna({ ibGib: headIbGib, metaspace, space })
            }) :
            undefined;
        const pastAddrs = timeline.rel8ns?.past ?? [];
        if (pastAddrs.length === 0) {
            return {
                headIbGib,
                orderedPastIbGibs: [],
                pastIbGibMap,
                dnaMap,
            }; /* <<<< returns early */
        }
        const resGet = await metaspace.get({ addrs: pastAddrs, space });
        if (!resGet.success || (resGet.ibGibs ?? []).length !== pastAddrs.length) {
            const notFound = pastAddrs.filter(addr => !resGet.ibGibs?.some(x => getIbGibAddr({ ibGib: x }) === addr));
            throw new Error(`Could not get all dna ibGibs for ${headAddr}. Addrs not found: ${notFound.join(', ')}. Errors: ${resGet.errorMsg ?? '[unknown error (E: 887339d8b5580ed1ac9892a554255525)]'} (E: 4ab7a8a58adff4e238171f7c110c6825)`);
        }
        pastIbGibMap = toFlatGraph({ ibGibs: resGet.ibGibs }) ?? {};
        const orderedPastIbGibs = [];
        for (const pastAddr of pastAddrs) {
            const pastIbGib = pastIbGibMap[pastAddr];
            if (!pastIbGib) {
                throw new Error(`(UNEXPECTED) pastIbGib falsy? due to our logic, the pastIbGibMap should be complete. (E: 6010d8c14184124008631928e1265525)`);
            }
            orderedPastIbGibs.push(pastIbGib);
        }
        return {
            headIbGib,
            orderedPastIbGibs,
            pastIbGibMap,
            dnaMap,
        };
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function getHistoryAddrs({ timeline, getLatest, metaspace, space, }) {
    const lc = `[${getHistoryAddrs.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: c7a328b14018642138ebb47870c15626)`);
        }
        const historyInfo = await getHistory({
            timeline,
            getLatest,
            includeDna: false,
            metaspace,
            space,
        });
        const addrs = historyInfo.orderedPastIbGibs.map(x => getIbGibAddr({ ibGib: x }));
        return addrs;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
//# sourceMappingURL=timeline-api.mjs.map