import { delay, extractErrorMsg, pretty, unique } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbAndGib, getIbGibAddr, } from '@ibgib/ts-gib/dist/helper.mjs';
import { validateIbGibAddr } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
import { GIB, isPrimitive, IBGIB_DELIMITER, } from '@ibgib/ts-gib/dist/V1/index.mjs';
import { GLOBAL_LOG_A_LOT } from '../../core-constants.mjs';
import { getFromSpace, getLatestAddrs } from '../../witness/space/space-helper.mjs';
import { getTimelinesGroupedByTjp, toDto } from './ibgib-helper.mjs';
const logalot = GLOBAL_LOG_A_LOT;
/**
 *
 * @returns dependency graph, live or non-live depending on {@link GetDependencyGraphOptions}
 *
 * Getting a `live` dependency graph means that we will be looking in the given
 * @see {@link GetDependencyGraphOptions.space} for updates to ibGibs' timelines
 * (those ibGibs that have timelines/tjps). This is more costly computationally
 * in the short-term, but often cheaper in the long-term.
 */
export async function getDependencyGraph({ ibGib, ibGibs, ibGibAddr, ibGibAddrs, live, gotten, tjpAddrsAlreadyAnalyzed, skipAddrs, skipRel8nNames, onlyRel8nNames, maxRetries, msBetweenRetries, space, timeLogName, mapTjpAddrToLatestAddrsInSpace, }) {
    const lc = `[${getDependencyGraph.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: c2a4426c22e849611ca0cedabe683a22)`);
        }
        const graph = await getGraphProjection({
            ibGib, ibGibs, ibGibAddr, ibGibAddrs,
            live,
            gotten, tjpAddrsAlreadyAnalyzed,
            skipAddrs, skipRel8nNames, onlyRel8nNames,
            maxRetries, msBetweenRetries,
            space,
            timeLogName,
            mapTjpAddrToLatestAddrsInSpace,
        });
        Object.values(graph).filter(ibGib => ibGib.ib.startsWith('comment ')).forEach(ibGib => { console.table(ibGib); });
        if (logalot) {
            console.log(`${lc} graph size: ${Object.keys(graph).length} (I: c6cb5e7e1e2611d35b9006fac6503d22)`);
            const ibs = Object.values(graph).filter(ibGib => ibGib.ib.startsWith('comment ') || ibGib.ib.startsWith('pic '))
                .map(ibGib => ibGib.ib);
            unique(ibs).forEach(ib => console.log(ib));
        }
        return graph;
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
async function getGraphProjection_initializeOpts({ ibGib, ibGibs, ibGibAddr, ibGibAddrs, live, gotten, tjpAddrsAlreadyAnalyzed, skipAddrs, skipRel8nNames, onlyRel8nNames, maxRetries, msBetweenRetries, space, timeLogName, mapTjpAddrToLatestAddrsInSpace, }) {
    const lc = `[${getGraphProjection_initializeOpts.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: bd6807477f679345df9dddefe0b4e922)`);
        }
        if (!space) {
            throw new Error(`space required. (E: 9f38166ab70340cb919174f8d26af909)`);
        }
        if (!ibGib && !ibGibAddr && (ibGibs ?? []).length === 0 && (ibGibAddrs ?? []).length === 0) {
            throw new Error(`either ibGib/s or ibGibAddr/s required. (E: b6d08699651f455697f0d05a41edb039)`);
        }
        skipRel8nNames = skipRel8nNames || [];
        // do NOT initialize onlyRel8nNames because we do logic based on falsy value
        skipAddrs = skipAddrs || [];
        gotten = gotten || {};
        tjpAddrsAlreadyAnalyzed = tjpAddrsAlreadyAnalyzed || [];
        // convert single args (ibGib, ibGibAddr) into the array args, filtering
        // out primitives that we don't want. The `filter` function creates the
        // copy here, so we won't mutate the incoming arrays. (The ibgibs and
        // addrs themselves are immutable).
        ibGibAddrs = (ibGibAddrs ?? [])
            .filter(x => !isPrimitive({ gib: getIbAndGib({ ibGibAddr: x }).gib })) // no primitives
            .filter(x => !skipAddrs.includes(x));
        ibGibs =
            (ibGibs ?? [])
                .filter(x => !isPrimitive({ ibGib: x })) // no primitives
                .filter(x => !skipAddrs.includes(getIbGibAddr({ ibGib: x })));
        // if we're passed in a single ibGib, add it to the ibGibs array because
        // we're going to work off of that.
        if (ibGib &&
            !isPrimitive({ ibGib }) &&
            !ibGibs.some(x => x.gib === ibGib.gib) &&
            !skipAddrs.includes(getIbGibAddr({ ibGib }))) {
            ibGibs.push(ibGib);
        }
        // if we're passed in a single ibGibAddr, add it to the ibGibAddrs array because
        // we're going to work off of that.
        if (ibGibAddr &&
            !isPrimitive({ gib: getIbAndGib({ ibGibAddr }).gib }) &&
            !ibGibAddrs.includes(ibGibAddr) &&
            !skipAddrs.includes(ibGibAddr)) {
            ibGibAddrs.push(ibGibAddr);
        }
        return {
            ibGib, ibGibs, ibGibAddr, ibGibAddrs,
            live,
            gotten, tjpAddrsAlreadyAnalyzed,
            skipAddrs, skipRel8nNames, onlyRel8nNames,
            maxRetries, msBetweenRetries,
            space,
            timeLogName,
            mapTjpAddrToLatestAddrsInSpace,
        };
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
async function getGraphProjection_getIbGibsInIbGibAddrs({ ibGibs, ibGibAddrs, gotten, skipAddrs, maxRetries, msBetweenRetries, timeLogName, space, }) {
    const lc = `[${getGraphProjection_getIbGibsInIbGibAddrs.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 67b2be37ff24393fff56e229304da122)`);
        }
        /**
         * ibgibs that we'll return
         */
        const resIbGibs = [];
        ibGibs ??= [];
        ibGibAddrs ??= [];
        skipAddrs ??= [];
        gotten ??= {};
        const addrsToGetFromSpace = [];
        const gottenAddrs = Object.keys(gotten ?? {}); // compute once in this closure
        const incomingIbGibAddrs = ibGibs.map(x => getIbGibAddr({ ibGib: x }));
        // const noNeedAddrs = [...gottenAddrs, ...incomingIbGibAddrs, ...skipAddrs,];
        for (let i = 0; i < ibGibAddrs.length; i++) {
            const addr = ibGibAddrs[i];
            if (skipAddrs.includes(addr) || incomingIbGibAddrs.includes(addr)) {
                continue;
            }
            else if (gottenAddrs.includes(addr)) {
                // we've already gotten this addr previously and put it into the
                // gotten object, so we don't need to retrieve it from space
                resIbGibs.push(gotten[addr]);
            }
            else {
                // if (!noNeedAddrs.includes(ibGibAddrs[i])) {
                addrsToGetFromSpace.push(ibGibAddrs[i]);
            }
        }
        if (logalot) {
            console.log(`${lc}[analyze debugging] addrsToGetFromSpace (${addrsToGetFromSpace.length}):\n${addrsToGetFromSpace.join('\n')} (I: b45a2614184b48b694becfd377c789f5)`);
        }
        if (addrsToGetFromSpace.length > 0) {
            // get from space, with retries if applicable
            let addrsToGet = addrsToGetFromSpace.concat();
            let retryCount = 0;
            maxRetries = maxRetries ?? 0;
            while (retryCount <= maxRetries && addrsToGet.length > 0) {
                if (timeLogName && retryCount === 0) {
                    console.timeLog(timeLogName, `${lc} FIRST try starting...`);
                }
                if (timeLogName && retryCount > 0) {
                    console.timeLog(timeLogName, `${lc} RETRY starting...`);
                }
                // delay if applicable
                if (retryCount > 0 && msBetweenRetries) {
                    if (timeLogName) {
                        console.timeLog(timeLogName, `${lc} delaying ${msBetweenRetries}ms for retry`);
                    }
                    if (logalot) {
                        console.log(`${lc} retrying. addrsToGet (${addrsToGet.length}): ${addrsToGet} (I: 8460694cdd5518472680784c3b96a822)`);
                    }
                    await delay(msBetweenRetries);
                }
                // do the get
                if (timeLogName) {
                    console.timeLog(timeLogName, `${lc} getFromSpace (${addrsToGet?.length}) starting...`);
                }
                if (!space) {
                    throw new Error(`(UNEXPECTED) space falsy? (E: aa9f657695717034b3581066932d7d23)`);
                }
                let resGetThese = await getFromSpace({ addrs: addrsToGet, space });
                if (timeLogName) {
                    console.timeLog(timeLogName, `${lc} getFromSpace complete.`);
                }
                if (resGetThese.success && (resGetThese.ibGibs?.length ?? 0) > 0) {
                    resGetThese.ibGibs.forEach(x => resIbGibs.push(x));
                    // resGetThese.ibGibs.forEach(x => ibGibs.push(x));
                    const gottenAddrs = resGetThese.ibGibs.map(x => getIbGibAddr({ ibGib: x }));
                    if (gottenAddrs.length === addrsToGet.length) {
                        if (timeLogName) {
                            console.timeLog(timeLogName, `${lc} got all.`);
                        }
                        // got them all, so we're done
                        addrsToGet = [];
                        break;
                    }
                    else {
                        if (timeLogName) {
                            console.timeLog(timeLogName, `${lc} got some.`);
                        }
                        // got only some, prune addrsToGet for next retry (if any)
                        addrsToGet = addrsToGet.filter(x => !gottenAddrs.includes(x));
                    }
                }
                else {
                    // failed, maybe partial maybe completely
                    const gottenIbGibs = resGetThese.rawResultIbGib.ibGibs ?? [];
                    if (gottenIbGibs.length > 0) {
                        // failed partial
                        gottenIbGibs.forEach(x => resIbGibs.push(x));
                        // resGetThese.ibGibs.forEach(x => ibGibs.push(x));
                        const gottenAddrs = gottenIbGibs.map(x => getIbGibAddr({ ibGib: x }));
                        if (timeLogName) {
                            console.timeLog(timeLogName, `${lc} got some.`);
                        }
                        // got only some, prune addrsToGet for next retry (if any)
                        addrsToGet = addrsToGet.filter(x => !gottenAddrs.includes(x));
                    }
                    else {
                        // failed completely, addrsToGet stays the same
                        if (timeLogName) {
                            console.timeLog(timeLogName, `${lc} failed. addrs: ${addrsToGet?.join(',')}`);
                        }
                    }
                }
                retryCount++;
            }
            if (addrsToGet?.length > 0) {
                // console.dir(primaryKeysDebug);
                throw new Error(`unable to retrieve dependency ibgibs from space (ib: ${space?.ib}). addrsToGet: ${addrsToGet.join('\n')} (E: 8413594b6c1b447988781cf3f3e1729d)`);
                // throw new Error(`unable to retrieve dependency ibgibs from space (ib: ${space?.ib}).\n\nThis is often because downloading failed due to the sync space's server getting temporarily overloaded, OR...it sometimes happens when an ibgib doesn't get fully published to the sync space in the first place.\n\nYou could retry immediately or later, but if the problem persists, then retry from the publishers end (have the publisher sync again). (E: 8413594b6c1b447988781cf3f3e1729d)`);
            }
        }
        return resIbGibs;
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function getGraphProjection(opts) {
    const lc = `[${getGraphProjection.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 70508d7a5c63eae1f22ae851b32b3d22)`);
        }
        if (!opts.ibGib && !opts.ibGibAddr && (opts.ibGibs ?? []).length === 0 && (opts.ibGibAddrs ?? []).length === 0) {
            // no ibgibs/addrs, return empty - don't throw
            return {}; /* <<<< returns early */
        }
        // ibGib and ibGibAddr get condensed into ibGibs and ibGibAddrs
        let { 
        /*ibGib,*/ ibGibs, /*ibGibAddr,*/ ibGibAddrs, live, gotten, tjpAddrsAlreadyAnalyzed, skipAddrs, skipRel8nNames, onlyRel8nNames, maxRetries, msBetweenRetries, space, timeLogName, mapTjpAddrToLatestAddrsInSpace, } = await getGraphProjection_initializeOpts(opts);
        if (timeLogName) {
            console.timeLog(timeLogName, `${lc} starting...`);
        }
        // retrieve ibgibs listed in incoming ibGibAddrs (if any) and put them
        // in our incoming ibGibs array
        if (ibGibAddrs?.length ?? 0 > 0) {
            const supplementalIbGibs = await getGraphProjection_getIbGibsInIbGibAddrs({
                ibGibs, ibGibAddrs,
                gotten,
                skipAddrs,
                maxRetries, msBetweenRetries,
                timeLogName,
                space,
            });
            ibGibs = ibGibs ?
                [...ibGibs, ...supplementalIbGibs] :
                [...supplementalIbGibs];
        }
        ibGibAddrs = ibGibs.map(x => getIbGibAddr({ ibGib: x }));
        // at this point, there are two different strategies for diving deeper,
        // depending on if we are building a `live` graph or not.
        let commentIbs = unique(ibGibs.map(x => x.ib).filter(x => x.startsWith('comment ')));
        if (logalot) {
            console.log(`${lc}[analyze debugging] ibGibs commentIbs: ${commentIbs.join('\n')} (I: c89dc951315f746954f9bddbe8941122)`);
        }
        if (live) {
            return getGraphProjection_Live({
                ibGibs, ibGibAddrs,
                gotten, tjpAddrsAlreadyAnalyzed,
                skipAddrs, skipRel8nNames, onlyRel8nNames,
                maxRetries, msBetweenRetries,
                space, timeLogName,
                mapTjpAddrToLatestAddrsInSpace,
            });
        }
        else {
            return getGraphProjection_NonLive({
                ibGibs, ibGibAddrs,
                gotten, /* tjpAddrsAlreadyAnalyzed not used */
                skipAddrs, skipRel8nNames, onlyRel8nNames,
                maxRetries, msBetweenRetries,
                space, timeLogName,
            });
        }
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Get the dependency graph for {@link ibGibAddr} since {@link latestCommonFrameAddr}.
 *
 * if ibGibAddr is A^5 and latestCommonFrameAddr is A^3, then this should
 * get all dependencies in A^5 dependency graph NOT ALREADY IN the A^3
 * graph.
 *
 * NOTE: The {@link live} param is for the delta graph itself. The graph
 * corresponding to {@link latestCommonFrameAddr} is always calculated live ===
 * false.
 */
export async function getDeltaDependencyGraph({ ibGibAddr, latestCommonFrameAddr, live, space, }) {
    const lc = `[${getDeltaDependencyGraph.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 5d225e56dc11e62cd83c5366ea288826)`);
        }
        if (ibGibAddr === latestCommonFrameAddr) {
            console.warn(`${lc} ibGibAddr === latestCommonFrameAddr. So the delta graph is empty. what are we doing here? (W: e0c8287ae6de8152bf36b6c73ca59a26)`);
            return {};
        }
        const dependencyGraph_lcf = await getDependencyGraph({
            ibGibAddr: latestCommonFrameAddr,
            live: false, // always get the smaller graph live === false!
            space,
        });
        const dependencyGraph_delta = await getDependencyGraph({
            ibGibAddr,
            live,
            skipAddrs: Object.keys(dependencyGraph_lcf),
            space,
        });
        return dependencyGraph_delta;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 *
 * live dependency graph searching requires a timeline-centric approach.
 * for each incoming ibGib, we must first determine if it has a
 * timeline.  if it does, then we get the latest in that timeline and
 * get all ibgibs in that lineage only. we do NOT YET go through any
 * rel8d ibgibs that also have timelines
 *
 * what we can do is compile our list of ibGib addrs according to each
 * timeline and then call the nonlive version of the dependency graph.
 * but we must get the latest in the timeline, then get all associated
 * timelines and get the latest of those. Then we can call get
 * dependency graph on the non-live version, passing in all of those
 * latest ibgibs in the timelines (and everything we've gotten in the
 * interim so we don't waste time getting more).
 *
 * @see {@link getGraphProjection}
 * @see {@link getDependencyGraph}
 */
async function getGraphProjection_Live({ ibGibs, ibGibAddrs, gotten, tjpAddrsAlreadyAnalyzed, mapTjpAddrToLatestAddrsInSpace, skipAddrs, skipRel8nNames, onlyRel8nNames, maxRetries, msBetweenRetries, space, timeLogName, }) {
    const lc = `[${getGraphProjection_Live.name}]`;
    try {
        mapTjpAddrToLatestAddrsInSpace = mapTjpAddrToLatestAddrsInSpace ?? {};
        tjpAddrsAlreadyAnalyzed = tjpAddrsAlreadyAnalyzed ?? [];
        // `ibGibs` contains the ones we explicitly are still working on getting
        // projections within, `gotten` are the ones we have completed. so we
        // want to get all of them together so we have a complete (up to this
        // point) picture of timelines, because we're looking for timelines that
        // we haven't yet analyzed
        let allIbGibsSoFar = { ...gotten };
        (ibGibs ?? []).forEach(x => allIbGibsSoFar[getIbGibAddr({ ibGib: x })] = x);
        const allKnownTimelinesAtThisPoint = getTimelinesGroupedByTjp({
            ibGibs: Object.values(allIbGibsSoFar),
        });
        if (logalot) {
            console.log(`${lc}[analyze debugging] timeline tjps(${Object.keys(allKnownTimelinesAtThisPoint).length}): ${Object.keys(allKnownTimelinesAtThisPoint)} (I: 509f04ac15ca08c5a3f0777b48934622)`);
        }
        const timelinesNotAnalyzed = {};
        Object.keys(allKnownTimelinesAtThisPoint).forEach(tjpAddr => {
            if (!tjpAddrsAlreadyAnalyzed.includes(tjpAddr)) {
                timelinesNotAnalyzed[tjpAddr] = allKnownTimelinesAtThisPoint[tjpAddr];
            }
        });
        if (logalot) {
            console.log(`${lc}[analyze debugging] timelinesNotAnalyzed: ${pretty(timelinesNotAnalyzed)} (I: a4b67d95fdd6b98d3241c0a7d0a93c22)`);
        }
        const mapTjpAddrToLatestIbGibInTimelineThatWeHaventAlreadyAnalyzed = {};
        /**
         * Convience mapping back from latest addr already gotten back to the tjp addr.
         * Convenience = not strictly necessary, but makes it easier later.
         */
        const mapLatestAddrAlreadyGottenToTjpAddr = {};
        Object.keys(allKnownTimelinesAtThisPoint)
            .filter(tjpAddr => !tjpAddrsAlreadyAnalyzed.includes(tjpAddr))
            .forEach(tjpAddr => {
            const timeline = allKnownTimelinesAtThisPoint[tjpAddr];
            // add to the array we'll send below
            const latestIbGibAlreadyGotten = timeline[timeline.length - 1];
            mapTjpAddrToLatestIbGibInTimelineThatWeHaventAlreadyAnalyzed[tjpAddr] = latestIbGibAlreadyGotten;
            // add to mapping back from addr to tjpaddr for convenience below
            const latestAddrAlreadyGotten = getIbGibAddr({ ibGib: latestIbGibAlreadyGotten });
            mapLatestAddrAlreadyGottenToTjpAddr[latestAddrAlreadyGotten] = tjpAddr;
        });
        let countOfTimelinesNotYetGotten = Object.keys(mapTjpAddrToLatestIbGibInTimelineThatWeHaventAlreadyAnalyzed).length;
        if (countOfTimelinesNotYetGotten > 0) {
            // we have more timelines still to do. get the latest ibGib in each timeline,
            // add it, check for more timelines. But we may have already gotten a latestAddrsMap
            // in a previous call, so account for this.
            // query only latest ibgibs that we haven't already gotten AND whose
            // timelines we've not already analyzed, per
            // mapTjpAddrToLatestAddrsInSpace.
            /**
             * build this map of latest addr given -> latest addr in space (per timeline/tjp).
             */
            let latestAddrsMap = {};
            Object.keys(mapTjpAddrToLatestAddrsInSpace)
                .filter(tjpAddr => !tjpAddrsAlreadyAnalyzed.includes(tjpAddr))
                .forEach(tjpAddr => {
                const latestIbGibCorrespondingToTjpAddr = mapTjpAddrToLatestIbGibInTimelineThatWeHaventAlreadyAnalyzed[tjpAddr];
                if (latestIbGibCorrespondingToTjpAddr) {
                    const latestAddrCorrespondingToTjpAddr = getIbGibAddr({ ibGib: latestIbGibCorrespondingToTjpAddr });
                    latestAddrsMap[latestAddrCorrespondingToTjpAddr] = mapTjpAddrToLatestAddrsInSpace[tjpAddr];
                }
                else {
                    console.error(`${lc} latestIbGibCorrespondingToTjpAddr is falsy`);
                }
            });
            if (logalot) {
                console.log(`${lc} latestAddrsMap *before* getLatestAddrs: ${pretty(latestAddrsMap)} (I: 427b4bb78595e9e521ecf2c5e5c80722)`);
            }
            /**
             * these are the ibgibs whose timelines we are aware of but haven't
             * explicitly queried for the latest in the space.
             */
            const ibGibsToQueryLatestAddrs = Object.values(mapTjpAddrToLatestIbGibInTimelineThatWeHaventAlreadyAnalyzed);
            /**
             * this is the result map of ibgibs whose timelines we are aware of
             * but hadn't explicitly queried for.
             */
            let queriedLatestAddrsMap = {};
            if (ibGibsToQueryLatestAddrs.length > 0) {
                if (!space) {
                    throw new Error(`(UNEXPECTED) space falsy? (E: 8e878975e3561df9a8dc4c628b0abc23)`);
                }
                /** This is result with the map of the latest addrs in the space */
                const resLatestAddrsMapInEntireSpace = await getLatestAddrs({
                    ibGibs: ibGibsToQueryLatestAddrs,
                    space,
                });
                if (!resLatestAddrsMapInEntireSpace?.data?.latestAddrsMap) {
                    throw new Error(`(UNEXPECTED) getLatestAddrs result latestAddrsMap falsy (E: 088caa1fc95fd3b079108ab63ef33422)`);
                }
                queriedLatestAddrsMap = resLatestAddrsMapInEntireSpace?.data?.latestAddrsMap;
                if (Object.keys(queriedLatestAddrsMap).length !== countOfTimelinesNotYetGotten) {
                    // this happens when the space does not have the address, sometimes because of
                    // not pushing the most recent changes to the sync space...hmmm
                    throw new Error(`(UNEXPECTED) latestAddrsMap is not the same size as the incoming map (E: 666af512bbd44534983bb28ee8d43fed)`);
                }
                if (logalot) {
                    console.log(`${lc} queriedLatestAddrsMap: ${pretty(queriedLatestAddrsMap)} (I: 7b39a5f7ce9e9d9fabae4be98ed44522)`);
                }
                latestAddrsMap = {
                    ...queriedLatestAddrsMap,
                    ...latestAddrsMap
                };
            }
            if (logalot) {
                console.log(`${lc} combined latestAddrsMap: ${pretty(latestAddrsMap)} (I: e3aedea63f29c5b06a79632f691aa522)`);
            }
            /**
             * these addrs are those whose timelines we did not yet have the latest.
             * this means that we will have to work off of these ibgibs.
             */
            const newerAddrsFound = [];
            const newerAddrFoundToTjpAddrMap = {};
            Object.values(mapTjpAddrToLatestIbGibInTimelineThatWeHaventAlreadyAnalyzed)
                .forEach(latestIbGibAlreadyGotten => {
                const latestAddrAlreadyGotten = getIbGibAddr({ ibGib: latestIbGibAlreadyGotten });
                const tjpAddr = mapLatestAddrAlreadyGottenToTjpAddr[latestAddrAlreadyGotten];
                const latestAddrInSpace = latestAddrsMap[latestAddrAlreadyGotten];
                if (!latestAddrInSpace) {
                    throw new Error(`(UNEXPECTED) latestAddrInSpace not found in latestAddrsMap (E: 095d2b3f88e3e8a2c3e7d3de4c6d5622)`);
                }
                if (latestAddrInSpace === latestAddrAlreadyGotten) {
                    // we've already got the latest for this timeline. This
                    // means that we must have already at least queued all
                    // timelines possible, so nothing else to do.
                    if (logalot) {
                        console.log(`${lc} analyzed ${tjpAddr}(I: afb7960900f04b8a87538f4c7bd903b7)`);
                    }
                    tjpAddrsAlreadyAnalyzed.push(tjpAddr);
                }
                else {
                    // there is a newer "latest" in this timeline that we
                    // haven't gotten yet, so add that addr to the
                    newerAddrsFound.push(latestAddrInSpace);
                    newerAddrFoundToTjpAddrMap[latestAddrInSpace] = tjpAddr;
                }
            });
            let rel8dAddrsNotYetGotten = [];
            for (let i = 0; i < (ibGibs ?? []).length; i++) {
                const ibGib = ibGibs[i];
                const rel8ns = ibGib.rel8ns ?? {};
                const rel8nNames = Object.keys(rel8ns)
                    .filter(x => !(skipRel8nNames ?? []).includes(x))
                    .filter(x => onlyRel8nNames ? onlyRel8nNames.includes(x) : true);
                rel8nNames.forEach(rel8nName => {
                    const rel8dAddrs = rel8ns[rel8nName] ?? [];
                    rel8dAddrs.forEach(rel8dAddr => {
                        // only add todo if we don't already have the ibgib
                        if (!rel8dAddrsNotYetGotten.includes(rel8dAddr) &&
                            !ibGibs.some(x => getIbGibAddr({ ibGib: x }) === rel8dAddr) &&
                            !isPrimitive({ gib: getIbAndGib({ ibGibAddr: rel8dAddr }).gib })) {
                            rel8dAddrsNotYetGotten.push(rel8dAddr);
                        }
                    });
                });
            }
            ibGibAddrs = unique([...(ibGibAddrs ?? []), ...rel8dAddrsNotYetGotten, ...newerAddrsFound]);
            // if (newerAddrsFound.length === 0) {
            // there were no newer addrs found, necessitating that we have
            // no additional ibgibs that we don't know about.
            return await getGraphProjection({
                ibGibs, ibGibAddrs,
                gotten, tjpAddrsAlreadyAnalyzed,
                skipAddrs, skipRel8nNames, onlyRel8nNames,
                maxRetries, msBetweenRetries,
                space,
                timeLogName,
                live: true,
                mapTjpAddrToLatestAddrsInSpace,
            });
            // } else {
            //     // #region old notes
            //     // i don't know if i'll need these or if this algorithm is
            //     // working correctly.  atow (02/2024) it looks like it is
            //     // working from initial testing in b2tfs ibgib testing.
            //     // there are still newer ibgibs that must be included in the analysis., without yet
            //     // going into the direct rel8ns of
            //     // need to call recursively this _Live function, passing in
            //     // the already computated latest map in mapTjpAddrToLatestAddrsInSpace
            //     // I need to also adjust preceding code to use this map.
            //     // Object.keys(queriedLatestAddrsMap).forEach(latestAddrNotGotten => {
            //     // Object.keys(queriedLatestAddrsMap).forEach(tjpAddr => {
            //     //     const latestAddrNotGotten = queriedLatestAddrsMap[tjpAddr];
            //     //     // const tjpAddr = mapLatestAddrAlreadyGottenToTjpAddr[latestAddrNotGotten];
            //     //     mapTjpAddrToLatestAddrsInSpace![tjpAddr] = latestAddrNotGotten;
            //     //     if (queriedLatestAddrsMap[latestAddrNotGotten]) { ibGibAddrs!.push(); }
            //     // });
            //     // ibGibAddrs = unique(ibGibAddrs);
            //     // // ibGibAddrs = unique([...ibGibAddrs, ...rel8dAddrsNotYetGotten]);
            //     // #endregion old notes
            //     return await getGraphProjection({
            //         ibGibs, ibGibAddrs,
            //         gotten, tjpAddrsAlreadyAnalyzed,
            //         skipAddrs, skipRel8nNames, onlyRel8nNames,
            //         maxRetries, msBetweenRetries,
            //         space,
            //         timeLogName,
            //         live: true,
            //         mapTjpAddrToLatestAddrsInSpace,
            //     });
            // }
        }
        else {
            // we have no more timelines that we haven't already gotten, so we
            // can pass off to the non-live version
            if (!ibGibs) {
                ibGibs = [];
            }
            Object.values(gotten ?? {}).forEach(x => {
                const addr = getIbGibAddr({ ibGib: x });
                if (!ibGibs.some(y => getIbGibAddr({ ibGib: x }) === addr)) {
                    ibGibs.push(x);
                }
            });
            return await getGraphProjection({
                ibGibs, ibGibAddrs,
                gotten, /* tjpAddrsAlreadyAnalyzed not used */
                skipAddrs, skipRel8nNames, onlyRel8nNames,
                maxRetries, msBetweenRetries,
                space,
                timeLogName,
                live: false,
            });
        }
    }
    catch (error) {
        const emsg = `${lc} ${error.message}`;
        console.error(emsg);
        if (timeLogName) {
            console.timeLog(timeLogName, `${lc} error: ${emsg}`);
        }
        throw error;
    }
    finally {
        if (timeLogName) {
            console.timeLog(timeLogName, `${lc} complete.`);
        }
    }
}
/**
 * NOT EXPORTED
 *
 * @see {@link getGraphProjection}
 * @see {@link getDependencyGraph}
 */
async function getGraphProjection_NonLive({ ibGibs, ibGibAddrs, gotten, /* tjpAddrsAlreadyAnalyzed not used */ skipAddrs, skipRel8nNames, onlyRel8nNames, maxRetries, msBetweenRetries, space, timeLogName, }) {
    const lc = `[${getGraphProjection_NonLive.name}]`;
    try {
        // next, compile what could be a rather large list of rel8d ibgibAddrs
        // which must necessarily be in the past of the futuremost incoming
        // ibGib/ibGibAddr/s as rel8ns only work backwards (whereas tjp's can
        // refer to future timelines, the DAG substrate only looks backwards)
        const addrsWeDontHaveAlready_Rel8dAddrs = [];
        ibGibs ??= [];
        gotten ??= {};
        skipAddrs ??= [];
        skipRel8nNames ??= [];
        // so, we will iterate through all of our given and loaded ibGibs (not
        // the ones in gotten map though), look through all of their rel8ns, and
        // add any that haven't already been gotten
        if (timeLogName) {
            console.timeLog(timeLogName, `${lc} analyzing next step starting...`);
        }
        for (let i = 0; i < ibGibs.length; i++) {
            const ibGib = ibGibs[i];
            const ibGibAddr = getIbGibAddr({ ibGib });
            // do i need this?
            const { gib } = getIbAndGib({ ibGib });
            if (gib === GIB) {
                throw new Error(`cannot get dependency graph of primitive.`);
            }
            // ?
            // I believe I have this so we don't try to do this ibgib again on recursive call.
            // but should I be adding it at this point? hmm...
            if (!Object.keys(gotten).includes(ibGibAddr)) {
                gotten[ibGibAddr] = ibGib;
            }
            // iterate through rel8ns and compile list of ibgib addrs not yet gotten
            /** map of addr to validation errors array */
            const invalidAddrs = {};
            const rel8ns = ibGib.rel8ns || {};
            let rel8nNames = (Object.keys(rel8ns) || []).filter(x => !skipRel8nNames.includes(x));
            if (onlyRel8nNames) {
                rel8nNames = rel8nNames.filter(x => onlyRel8nNames.includes(x));
            }
            const gottenKeys = Object.keys(gotten);
            for (let i = 0; i < rel8nNames.length; i++) {
                const rel8nName = rel8nNames[i];
                const rel8dAddrs = rel8ns[rel8nName] ?? [];
                const falsyAddrs = rel8dAddrs.filter(addr => addr === '' ||
                    addr === undefined ||
                    addr === null ||
                    !addr.includes(IBGIB_DELIMITER));
                if (falsyAddrs.length > 0) {
                    console.warn(`${lc} (UNEXPECTED) has falsyAddrs: ${falsyAddrs} (W: da9505cb0a4db68a4aff7f279ad2d322)`);
                }
                const rel8dAddrsNotGottenYetThisRel8n = rel8dAddrs
                    .filter(addr => !!addr)
                    .filter(addr => !gottenKeys.includes(addr))
                    .filter(addr => !skipAddrs.includes(addr))
                    .filter(addr => getIbAndGib({ ibGibAddr: addr }).gib !== GIB)
                    .filter(addr => !addrsWeDontHaveAlready_Rel8dAddrs.includes(addr));
                rel8dAddrsNotGottenYetThisRel8n.forEach(rel8dAddr => {
                    const validationErrors = validateIbGibAddr({ addr: rel8dAddr });
                    if ((validationErrors || []).length === 0) {
                        // valid addr. add it if we haven't gotten/queued it yet
                        addrsWeDontHaveAlready_Rel8dAddrs.push(rel8dAddr);
                    }
                    else {
                        // invalid address
                        invalidAddrs[rel8dAddr] = validationErrors;
                    }
                });
            }
            if (Object.keys(invalidAddrs).length > 0) {
                throw new Error(`invalid addresses found in dependency graph. Errors (clipped to 1kB): ${JSON.stringify(invalidAddrs).substring(0, 1024)}`);
            }
        }
        if (timeLogName) {
            console.timeLog(timeLogName, `${lc} analyzing next step complete.`);
        }
        if (addrsWeDontHaveAlready_Rel8dAddrs.length > 0) {
            if (timeLogName) {
                console.timeLog(timeLogName, `${lc} get addrsWeDontHaveAlready_Rel8dAddrs starting...`);
            }
            // execute the get on those addrs
            if (!space) {
                throw new Error(`(UNEXPECTED) space falsy? (E: 0ce327cf5d521bd798340333460f3423)`);
            }
            const resGet = await getFromSpace({ addrs: addrsWeDontHaveAlready_Rel8dAddrs, space });
            if (timeLogName) {
                console.timeLog(timeLogName, `${lc} get addrsWeDontHaveAlready_Rel8dAddrs complete.`);
            }
            if (resGet.success) {
                if (resGet.ibGibs?.length === addrsWeDontHaveAlready_Rel8dAddrs.length) {
                    if (logalot) {
                        console.log(`${lc} got ALL of them (happy path)`);
                    }
                    resGet.ibGibs.forEach(x => gotten[getIbGibAddr({ ibGib: x })] = x);
                    // return a recursive call for the newly-gotten ibgibs'
                    // dependencies, passing in the now-larger accumulating
                    // `gotten` map of ibgibs already processed.
                    if (timeLogName) {
                        console.timeLog(timeLogName, `${lc} call getGraphProjection recursively starting...`);
                    }
                    const result = await getGraphProjection({
                        ibGibs: resGet.ibGibs,
                        live: false,
                        gotten, /* tjpAddrsAlreadyAnalyzed not used */
                        skipAddrs, skipRel8nNames, onlyRel8nNames,
                        maxRetries, msBetweenRetries,
                        space,
                    });
                    if (timeLogName) {
                        console.timeLog(timeLogName, `${lc} call getGraphProjection recursively complete.`);
                    }
                    return result; // <<<< returns early
                }
                else if ((resGet.ibGibs?.length ?? 0) > 0 && resGet.ibGibs.length < addrsWeDontHaveAlready_Rel8dAddrs.length) {
                    if (logalot) {
                        console.warn(`${lc} got SOME of them (happy-ish path?). not sure what to do here... (W: e3458f61a1ae4979af9e6b18ac935c14)`);
                    }
                    throw new Error(`trouble getting dependency ibgibs (E: 8156bf65fd084ae4a4e8a0669db28b07)`);
                }
                else if ((resGet.ibGibs?.length ?? 0 > 0) && resGet.ibGibs.length > addrsWeDontHaveAlready_Rel8dAddrs.length) {
                    // got more than our original list? not a good space behavior...
                    throw new Error(`(UNEXPECTED) got more ibGibs than addrs that we asked for. space not working properly. (E: 352219b3d18543bcbda957f2d60b78f3)`);
                }
                else {
                    // didn't get any...hmm...
                    throw new Error(`couldn't get dependency ibgibs from space. (E: 225f26b7d7f84911bb033753a062209b)`);
                }
            }
            else {
                // resGet.success falsy indicates an error in the space. If it wasn't found
                // then resGet.success would (should) still be truthy.
                throw new Error(`failure getting addrs in space ${space?.data?.name || '[no name?]'} (id: ${space?.data?.uuid || '[no uuid?]'}). (E: 60404e6e389249d9bbecf0039cd51878) addrs:\n${addrsWeDontHaveAlready_Rel8dAddrs.join('\n')} `);
            }
        }
        else {
            // no other rel8d addrs to get, so our job is done and the `gotten`
            // map of dependency ibgibs is complete (no need for another
            // recursive call).
            return gotten;
        }
    }
    catch (error) {
        const emsg = `${lc} ${error.message}`;
        console.error(emsg);
        if (timeLogName) {
            console.timeLog(timeLogName, `${lc} error: ${emsg}`);
        }
        throw error;
    }
    finally {
        if (timeLogName) {
            console.timeLog(timeLogName, `${lc} complete.`);
        }
    }
}
/**
 * Helper function to convert an array of {@link ibGibs} (or undefined) to a
 * {@link FlatIbGibGraph} ([addr] -> ibGib map).
 * @returns if ibGibs undefined returns undefined, else flat graph map
 */
export function toFlatGraph({ ibGibs }) {
    const lc = `[${toFlatGraph.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 6019a812cff8597f79c12088e45cc226)`);
        }
        if (ibGibs) {
            const resGraph = {};
            ibGibs.forEach(x => { resGraph[getIbGibAddr({ ibGib: x })] = x; });
            return resGraph;
        }
        else {
            return undefined;
        }
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * compare two graphs for "equivalency".
 *
 * The default just checks that sizes and addrs are the same.
 *
 * The more thorough check does the json strings of the values (as dtos).
 */
export function graphsAreEquivalent({ graphA, graphB, slowButThorough, }) {
    const lc = `[${graphsAreEquivalent.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: e50a98b659189dece8b4ef581e4ad826)`);
        }
        const sameSize = Object.keys(graphA).length === Object.keys(graphB).length;
        if (!sameSize) {
            if (logalot) {
                console.log(`${lc} not the same size. false. (I: e1044e52421dbf7e067e0fe850ac6826)`);
            }
            return false; /* <<<< returns early */
        }
        for (const addr of Object.keys(graphA)) {
            if (!graphB[addr]) {
                if (logalot) {
                    console.log(`${lc} addr (${addr}) in graphA not found in graphB (I: 264c68408378ce30b8f6a978f8b86926)`);
                }
                return false; /* <<<< returns early */
            }
        }
        if (slowButThorough) {
            // go back through and check for dto string equality (deep equality)
            for (const addr of Object.keys(graphA)) {
                const ibGibA = toDto({ ibGib: graphA[addr] });
                const jsonA = JSON.stringify(ibGibA);
                const ibGibB = toDto({ ibGib: graphB[addr] });
                const jsonB = JSON.stringify(ibGibB);
                if (jsonA !== jsonB) {
                    if (logalot) {
                        console.log(`${lc} jsonA !== jsonB for addr (${addr}) (I: 98ed9f58ac88dabba83f96582f725926)`);
                    }
                    return false; /* <<<< returns early */
                }
            }
        }
        return true;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
//# sourceMappingURL=graph-helper.mjs.map