import { clone, extractErrorMsg, groupBy, hash, pretty } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { UUID_REGEXP } from '@ibgib/helper-gib/dist/constants.mjs';
import { getIbAndGib, getIbGibAddr, } from '@ibgib/ts-gib/dist/helper.mjs';
import { Factory_V1 } from '@ibgib/ts-gib/dist/V1/factory.mjs';
import { GIB, FORBIDDEN_ADD_RENAME_REMOVE_REL8N_NAMES, GIB_DELIMITER } from '@ibgib/ts-gib/dist/V1/constants.mjs';
import { validateIb } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
import { getGib, getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
import { GLOBAL_LOG_A_LOT, IB_REGEXP_DEFAULT } from '../../core-constants.mjs';
import { SpecialIbGibType } from './other-types.mjs';
import { INVALID_DATE_STRING, SPECIAL_IBGIB_TYPE_REGEXP } from './other-constants.mjs';
import { CONFIG_KEY_ATOM } from './ibgib-constants.mjs';
import { getFromSpace } from '../../witness/space/space-helper.mjs';
const logalot = GLOBAL_LOG_A_LOT || false;
/**
 * Gets ibgibs that are directly related to the given `ibGib` via `rel8nNames`.
 *
 * @returns map of rel8nName -> ibgibs that are related.
 */
export async function getRel8dIbGibs({ ibGib, rel8nNames, space, }) {
    const lc = `[${getRel8dIbGibs.name}]`;
    try {
        if (!ibGib) {
            throw new Error(`ibGib required (E: a3c1b2d4e5f6a7b8c9d0e1f2a3b4c5d6)`);
        }
        if (!space) {
            throw new Error(`space required (E: b4d2c3e4f5a6b7c8d9e0f1a2b3c4d5e6)`);
        }
        // Default to all rel8n names if none provided
        if (!rel8nNames) {
            rel8nNames = Object.keys(ibGib.rel8ns || {});
        }
        const rel8dIbGibs = {};
        for (const rel8nName of rel8nNames) {
            const rel8dAddrs = ibGib.rel8ns?.[rel8nName] || [];
            if (rel8dAddrs.length > 0) {
                const resGet = await getFromSpace({ addrs: rel8dAddrs, space });
                if (resGet.success && resGet.ibGibs?.length === rel8dAddrs.length) {
                    rel8dIbGibs[rel8nName] = resGet.ibGibs.concat();
                }
                else {
                    // Start logging warnings instead of throwing immediately?
                    // For now, consistent with user request to adapt legacy code
                    throw new Error(`Problem getting rel8d ibgibs for rel8nName: ${rel8nName}. ${resGet.errorMsg || 'Unknown error'} (E: c5e3d4f5a6b7c8d9e0f1a2b3c4d5e6)`);
                }
            }
            else {
                rel8dIbGibs[rel8nName] = [];
            }
        }
        return rel8dIbGibs;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
/**
 * Utility function to generate hard-coded ibgibs to use at runtime "on-chain" but
 * written at compile-time in (for now) "off-chain" source code.
 *
 * Because this is supposed to create and re-create deterministically the equivalent
 * of a non-primitive ibgib "constant", this function creates a single ibgib with...
 * * one ancestor
 * * no past, dna, or tjp rel8ns
 * * no tjp timestamp or uuid
 * * no nCounter
 *
 * ## validation
 *
 * * validates the given `ib` against `ibRegExpPattern` or default regexp.
 * * validates that rel8ns doesn't include default forbidden rel8n names or
 *   atow `'tjp'`.
 *
 * ## intent
 *
 * I want to be able to create deterministic ibGibs that I can reference at
 * runtime, similar to an ibgib primitive (e.g. "root^gib"), but with the
 * integrity of the `gib` hash. This way, I can reference a deterministic ibgib
 * from code at compile time, and at runtime this will have a corresponding
 * ibgib datum with gib-hashed integrity.
 *
 * ## example
 *
 * I want to create a "hard-coded" schema ibgib that I rel8 to some protocol
 * ibgib. So I'll create the data here, which lives in source control in a text file,
 * and then I'll render that as an ibgib that verifies integrity. If I as a coder change
 * it at all, then the `gib` of course will be different.
 */
export async function constantIbGib({ parentPrimitiveIb, ib, ibRegExpPattern, data, rel8ns, }) {
    const lc = `[${constantIbGib.name}]`;
    try {
        // validation
        // parentPrimitiveIb
        if (!parentPrimitiveIb) {
            throw new Error(`parentPrimitiveIb required. (E: 88ddf188cc5a4340b597abefba1481e2)`);
        }
        if (validateIb({ ib: parentPrimitiveIb }) !== null) {
            throw new Error(`Invalid parentPrimitiveIb: ${parentPrimitiveIb}. (E:5aec0320956d492ebeeaca41eb1fe1c6)`);
        }
        // ib
        if (!ib) {
            throw new Error(`ib required. (E: 7bbc88f4f2e842d6b00126e55b1783e4)`);
        }
        const regExp = ibRegExpPattern ? new RegExp(ibRegExpPattern) : IB_REGEXP_DEFAULT;
        if (!ib.match(regExp)) {
            throw new Error(`invalid ib. does not match regexp (${regExp})`);
        }
        // rel8ns
        const incomingRel8nNames = Object.keys(rel8ns ?? {});
        const forbiddenRel8nNames = [...FORBIDDEN_ADD_RENAME_REMOVE_REL8N_NAMES, 'tjp'];
        const rel8nsIsInvalid = incomingRel8nNames.some(x => {
            // we don't want constants trying to look like they have/are descendants/tjps/etc.
            return forbiddenRel8nNames.includes(x);
        });
        if (rel8nsIsInvalid) {
            throw new Error(`Invalid rel8ns. forbiddenRel8nNames: ${forbiddenRel8nNames}. rel8ns keys: ${Object.keys(rel8ns ?? {})}. (E: 837a993c265c4362b6aa0b1a234ea5f8)`);
        }
        // create the constant
        const resFirstGen = await Factory_V1.firstGen({
            ib,
            parentIbGib: Factory_V1.primitive({ ib: parentPrimitiveIb }),
            data,
            rel8ns,
            dna: false,
            noTimestamp: true,
            nCounter: false,
        });
        const constantIbGib = resFirstGen.newIbGib;
        // remove any extraneous stuff
        if (constantIbGib?.rel8ns?.past) {
            delete constantIbGib.rel8ns.past;
        }
        if (constantIbGib?.rel8ns?.tjp) {
            delete constantIbGib.rel8ns.tjp;
        }
        if (constantIbGib?.rel8ns?.identity) {
            delete constantIbGib.rel8ns.identity;
        }
        // recalculate the gib hash
        // constantIbGib.gib = await sha256v1({
        constantIbGib.gib = await getGib({
            ibGib: {
                ib: constantIbGib.ib,
                data: constantIbGib.data,
                rel8ns: constantIbGib.rel8ns,
            },
            hasTjp: false,
        });
        return constantIbGib;
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
}
/**
 * Binaries require special handling, since they do not conform to the
 * "normal" IbGib_V1 data structure per se. This stems from wanting to
 * be able to have binaries (jpgs, gifs, etc. especially) able to
 * sit on a server and be served as regular files.
 *
 * @returns string in expected template for binaries in this app.
 */
export function getBinIb({ binHash, binExt, binEncoding }) {
    if (binEncoding) {
        // adding this part after the fact here. not sure how this is going to
        // go down. just noting because of the strange shape of the logic here.
        return binExt ? `bin ${binHash} ${binExt} enc=${binEncoding}` : `bin ${binHash} enc=${binEncoding}`;
    }
    else {
        return binExt ? `bin ${binHash} ${binExt}` : `bin ${binHash}`;
    }
}
/**
 * @deprecated
 *
 * use {@link parseBinIb}
 */
export function getBinHashAndExt({ addr }) {
    return parseBinIb({ addr });
}
/**
 * parses a given addr that is expected to be for a {@link BinIbGib_V1}.
 *
 * @throws an error if addr is not for a {@link BinIbGib_V1} (via {@link isBinary})
 *
 * @returns parsed bin ib information
 */
export function parseBinIb({ addr }) {
    const lc = `[${parseBinIb.name}]`;
    try {
        if (!isBinary({ addr })) {
            throw new Error(`not a bin address (E: df0804d129bc4888bd6939cb76c5e0f6)`);
        }
        const { ib } = getIbAndGib({ ibGibAddr: addr });
        const ibPieces = ib.split(' ');
        if (ibPieces.length === 2) {
            // bin ${binHash}
            return {
                binHash: ibPieces[1],
                binExt: '',
                // binEncoding: undefined,
            };
        }
        else if (ibPieces.length === 3) {
            // could be
            // bin ${binHash} enc=${binEncoding}
            // or...
            // bin ${binHash} ${binExt}
            return ibPieces[2].startsWith("enc=") ?
                {
                    binHash: ibPieces[1],
                    binExt: '',
                    binEncoding: ibPieces[2].substring("enc=".length),
                } :
                {
                    binHash: ibPieces[1],
                    binExt: ibPieces[2],
                    // binEncoding: undefined,
                };
        }
        else if (ibPieces.length === 4) {
            if (!ibPieces[3].startsWith("enc=")) {
                throw new Error(`bin ib (${ib}) has 4 space-delimited pieces. we expect therefore the form to be bin \${binHash} \${binExt} enc=\${binEncoding}. but ibPieces[3] doesn't start with "enc=". (E: 87196d7cdd7c12d982cc8204a1163524)`);
            }
            return {
                binHash: ibPieces[1],
                binExt: ibPieces[2],
                binEncoding: ibPieces[3].substring("enc=".length),
            };
        }
        else {
            throw new Error(`(UNEXPECTED) ibPieces.length not 2, 3, or 4? atow (01/2024) the only components of a bin ib are: atom, binHash, binExt, binEncoding. binExt and binEncoding are optional, with binEncoding being prefixed with "enc=". (E: b05df3c1b3949f76464a83c9fd1c4f24)`);
        }
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
}
/**
 * atow (01/2024) this looks to see...
 *
 * * that data is truthy (if ibGib is truthy)
 * * gib is a UUID
 *
 * ## dev note
 *
 * this is tightly coupled with {@link getBinIb} and {@link parseBinIb}
 *
 * @returns true if analysis of ibgib indicates it is a {@link BinIbGib_V1}, else false
 */
export function isBinary({ ibGib, addr, }) {
    const lc = `[${isBinary.name}]`;
    try {
        // probably overkill here, but...
        if (!ibGib && !addr) {
            throw new Error(`either ibGib or addr required. (E: c935b51e773f41a2a547c556e9dc16c6)`);
        }
        if (ibGib && !ibGib.data) {
            return false;
        }
        addr = addr || getIbGibAddr({ ibGib });
        const { ib, gib } = getIbAndGib({ ibGibAddr: addr });
        if (!ib) {
            return false;
        }
        if (!gib) {
            return false;
        }
        if (!ib.startsWith('bin ')) {
            return false;
        }
        // at this point any weirdness throws an error, because the ib plainly
        // indicates this is a bin ibgib.
        if (gib.length !== 64) {
            throw new Error(`ib starts with "bin " but gib length is not 64, so return false. Two things: 1) bin ibgibs are expected to be stones, i.e., have no temporal junction point and only a single punctiliar hash for the gib. the default hash used atow (01/2024) is sha-256, so the length should be 64. But this may not be true if using another hash algorithm. need to change this function when this becomes the case. (W: 60725d65d13c4b4fbb7b96d7feb19d67)`);
        }
        // tightly coupled here with parsing algorithm
        const ibPieces = ib.split(' ');
        if (ibPieces.length === 2) {
            // bin ${binHash}
            if (!ibPieces[1].match(UUID_REGEXP)) {
                throw new Error(`(UNEXPECTED) ib starts with "bin " but space-delimited !ibPieces[1].match(UUID_REGEXP)? (E: f6baca2ff155f1017b278a5d7597a224)`);
            }
        }
        else if (ibPieces.length === 3) {
            // could be
            // bin ${binHash} enc=${binEncoding}
            // or...
            // bin ${binHash} ${binExt}
            if (!ibPieces[1].match(UUID_REGEXP)) {
                throw new Error(`(UNEXPECTED) ib starts with "bin " but space-delimited !ibPieces[1].match(UUID_REGEXP)? (E: 8d0443d6cada4b50be79a8390a7a426c)`);
            }
        }
        else if (ibPieces.length === 4) {
            if (!ibPieces[1].match(UUID_REGEXP)) {
                throw new Error(`(UNEXPECTED) ib starts with "bin " but space-delimited !ibPieces[1].match(UUID_REGEXP)? (E: 3209eacc0a3848a59593d4c3ee956191)`);
            }
            // bin ${binHash} ${binExt} enc=${binEncoding}
            if (!ibPieces[3].startsWith("enc=")) {
                throw new Error(`(UNEXPECTED) ib starts with "bin " but space-delimited !ibPieces[3].startsWith("enc=")? (E: 4c8a0c3b07a90d30e3d1a4373d57d824)`);
            }
        }
        else {
            throw new Error(`(UNEXPECTED) ib starts with "bin " but space-delimited ibPieces length is not 2, 3 or 4? (E: c7eccf88518304997c7e42b32e08d224)`);
        }
        // passed gauntlet
        return true;
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
}
/**
 * Recursively hashes some string {@link recursionCount} number of times.
 */
export async function hash16816({ s, algorithm, recursionCount, saltPrependedPerHash, }) {
    const lc = `[${hash16816.name}]`;
    try {
        saltPrependedPerHash ??= '';
        recursionCount ??= 16816;
        algorithm ??= 'SHA-256';
        let hashed = '';
        if (saltPrependedPerHash) {
            for (let i = 0; i < recursionCount; i++) {
                hashed = await hash({ s: saltPrependedPerHash + s, algorithm });
            }
        }
        else {
            for (let i = 0; i < recursionCount; i++) {
                hashed = await hash({ s, algorithm });
            }
        }
        return hashed;
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
}
/**
 * creates atow (10/2023) space-delimited ib in the form of `meta special ${type}`.
 *
 * @returns ib for given `type`
 * @throws error if `type` doesn't match {@link SPECIAL_IBGIB_TYPE_REGEXP}
 *
 * @see {@link getSpecialTypeFromIb}
 * @see {@link SPECIAL_IBGIB_TYPE_REGEXP}
 */
export function getSpecialIbGibIb({ type }) {
    if (!type.match(SPECIAL_IBGIB_TYPE_REGEXP)) {
        throw new Error(`special ibgib type must not have spaces and just be alphanumerics. regexp: ${SPECIAL_IBGIB_TYPE_REGEXP.source} (E: 6c1c92521b7f076cc4666e4915593723)`);
    }
    return `meta special ${type}`;
}
/**
 * extracts the {@link SpecialIbGibType} from the given `ib`.
 *
 * @returns type {@link SpecialIbGibType}
 *
 * @see {@link getSpecialIbGibIb}
 * @see {@link SPECIAL_IBGIB_TYPE_REGEXP}
 */
export function getSpecialTypeFromIb({ ib }) {
    const lc = `[${getSpecialTypeFromIb.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: c82ba222bd345ee6b695df4d63a23322)`);
        }
        if (!ib) {
            throw new Error(`ib required (E: 08897145f7138e644fe01c4a59353322)`);
        }
        if (!isSpecial({ ib })) {
            throw new Error(`ib is not special (E: 174aff63b992adff3ac2394643735922)`);
        }
        const pieces = ib.split(' ');
        if (pieces.length < 3) {
            throw new Error(`invalid ib. should be space-delimited in form of "meta special [type]" (E: ffd89e2cbe63427f98634ab897aab222)`);
        }
        const specialType = pieces[2];
        if (!Object.values(SpecialIbGibType).some(x => x === specialType)) {
            console.warn(`unknown special type (${specialType}). This may be expected, but atow I am adding special types to the SpecialIbGibType enum-like. (W: f4e26c3ebb57fe49d69014a4ba32a922)`);
        }
        return specialType;
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * gets a special ib and tacks on ^gib for a constant address.
 *
 * used in primitives (ancestors for special ibgibs), as well as as of atow
 * (11/2023) the "latest" special ibgib.
 *
 * @returns special IbGibAddr
 */
export function getSpecialIbGibAddr({ type }) {
    const ib = getSpecialIbGibIb({ type });
    return `${ib}^${GIB}`;
}
export function getSpecialConfigKey({ type }) {
    return `${CONFIG_KEY_ATOM} ${getSpecialIbGibAddr({ type })}`;
}
export function isSpecial({ ib, ibGib }) {
    if (!ib && !ibGib?.ib) {
        throw new Error(`either ib or ibGib.ib required (E: b4cf539638d7966c2e351987f55e1a23)`);
    }
    return (ib ?? ibGib?.ib)?.startsWith('meta special');
}
/**
 * returns ib for a given root. ATOW this is simply "root {text}"
 *
 * @returns ib for the given rootText
 */
export function getRootIb(rootText) {
    const lc = `[${getRootIb.name}]`;
    if (!rootText) {
        throw new Error(`${lc} text required.`);
    }
    return `root ${rootText}`;
}
/**
 * Tags for this app have the form: tag [tagText]
 *
 * @param tagText e.g. "Favorites"
 *
 * @example
 * For the Favorites tag, the ib would be "tag Favorites"
 */
export function tagTextToIb(tagText) {
    const lc = `[${tagTextToIb.name}]`;
    if (!tagText) {
        throw new Error(`${lc} tag required.`);
    }
    return `tag ${tagText}`;
}
/**
 * Living: has tjp and dna.
 * Stones: does not have Dna, maybe has tjp.
 *
 * Splits the given `ibGibs` into two maps, one that includes the ibgibs that
 * have a tjp (temporal junction point) AND dna ("living") and those that do not
 * have both tjp AND dna ("stones").
 *
 * ## notes
 *
 * Having dna implies having a tjp, but the reverse is not necessarily true.
 * Sometimes you want an ibgib that has a tjp so you can, e.g., reference the
 * entire timeline easily.  But at the same time you don't want to keep track of
 * the transforms, perhaps this is because you don't want to be able to merge
 * timelines.
 */
export function splitPerTjpAndOrDna({ ibGibs, filterPrimitives, }) {
    const lc = `[${splitPerTjpAndOrDna.name}]`;
    try {
        const mapWithTjp_YesDna = {};
        const mapWithTjp_NoDna = {};
        const mapWithoutTjps = {};
        // const mapLivingIbGibs: { [gib: string]: IbGib_V1 } = {};
        // const mapStoneIbGibs: { [gib: string]: IbGib_V1 } = {};
        const ibGibsTodo = filterPrimitives ?
            ibGibs.filter(ibGib => ibGib.gib ?? ibGib.gib !== GIB) :
            ibGibs;
        ibGibsTodo.forEach(ibGib => {
            if (hasTjp({ ibGib })) {
                if ((ibGib.rel8ns?.dna ?? []).length > 0) {
                    mapWithTjp_YesDna[ibGib.gib] = ibGib;
                }
                else {
                    mapWithTjp_NoDna[ibGib.gib] = ibGib;
                }
            }
            else {
                mapWithoutTjps[ibGib.gib] = ibGib;
            }
        });
        return { mapWithTjp_YesDna, mapWithTjp_NoDna, mapWithoutTjps };
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
}
/**
 * Takes incoming `ibGibs`, filters out those that do
 * not have tjps( i.e. non-timelines), and groups
 * the timeline ibgibs by tjp in ascending order.
 *
 * This means that each map entry will be in the form:
 * `[tjpAddr] => [ibgib0 (tjp), ibgib1, ibgib2, ..., ibgibN (latest)]`
 *
 * ## notes
 *
 * * sorts by `ibgib.data.n`. If this is undefined, will not sort in ascending
 *   order properly.
 *
 * @returns filtered, sorted map of incoming `ibGibs` [tjpAddr] => timeline [ibgib0 (tjp), ibgib1, ibgib2, ..., ibgibN (latest)]
 */
export function getTimelinesGroupedByTjp({ ibGibs, }) {
    const lc = `[${getTimelinesGroupedByTjp.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting...`);
        }
        // pull out only the ibgibs in timelines (either is tjp or has tjp)
        let { mapWithTjp_YesDna, mapWithTjp_NoDna } = splitPerTjpAndOrDna({ ibGibs, filterPrimitives: true });
        const mapIbGibsWithTjp = { ...mapWithTjp_YesDna, ...mapWithTjp_NoDna };
        const ibGibsWithTjp = Object.values(mapIbGibsWithTjp);
        const mapTjpTimelines_Ascending = groupBy({
            items: ibGibsWithTjp,
            keyFn: x => {
                // x.data?.isTjp ? getIbGibAddr({ ibGib: x }) : (x.rel8ns?.tjp[0] ?? '') // converting this untested...hmm
                if (x.data?.isTjp) {
                    return getIbGibAddr({ ibGib: x });
                }
                else if (x.rel8ns?.tjp) {
                    return x.rel8ns?.tjp[0] ?? '';
                }
                else {
                    if (logalot) {
                        console.log(`${lc} neither isTjp nor x.rel8ns.tjp truthy (I: ec9a5597bf53dec1bd3d83350abbf823)`);
                    }
                    return '';
                }
            }
        });
        if (logalot) {
            console.log(`${lc} sorting (ascending) ibGibsWithTjpGroupedByTjpAddr: ${pretty(mapTjpTimelines_Ascending)} (I: 9b9fff5ce61444a6cb06d62db9a99422)`);
        }
        Object.entries(mapTjpTimelines_Ascending).forEach(([_tjpAddr, timeline]) => {
            if (timeline.some(ibGib => ibGib.data?.n === undefined)) {
                console.warn(`${lc} timeline includes ibgibs with ibGib.data?.n === undefined (W: cab9a6b64a38c4279fe82c3569bbab22)`);
            }
            // sort mutates array in place
            timeline.sort((a, b) => (a.data?.n ?? -1) > (b.data?.n ?? -1) ? 1 : -1); // sorts ascending, e.g., 0,1,2...[Highest]
        });
        if (logalot) {
            console.log(`${lc} after sort ibGibsWithTjpGroupedByTjpAddr: ${pretty(mapTjpTimelines_Ascending)} (I: 9b9fff5ce61444a6cb06d62db9a99422)`);
        }
        return mapTjpTimelines_Ascending; // ascending
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Helper function that checks the given `ibGib` to see if it
 * either has a tjp or is a tjp itself.
 *
 * ## notes
 *
 * Only unique ibGibs are meant to have tjps, or rather, if an
 * ibGib timeline is expected to be unique over "time", then the
 * tjp is an extremely convenient mechanism that provides a
 * "name" for that timeline.
 *
 * Otherwise, if they are not unique, then successive "different"
 * timelines cannot be easily referenced by their first unique
 * frame in time, making it much harder to pub/sub updates among
 * other things. (If there are no unique frames, then they are
 * the same ibGib.)
 *
 * ## tjp = temporal junction point
 *
 * I've written elsewhere on this as well. Refer to B2tF2.
 *
 * @returns true if the ibGib has/is a tjp, else false
 */
export function hasTjp({ ibGib }) {
    const lc = `[${hasTjp.name}]`;
    if (!ibGib) {
        throw new Error(`(UNEXPECTED) ibGib falsy? (E: ce3a59f9db14e6158bb2c438ca1a3823)`);
    }
    // most likely is that rel8ns.tjp has an entry or the internal data.isTjp
    if ((ibGib.rel8ns?.tjp?.length ?? 0) > 0 || ibGib.data?.isTjp) {
        return true; /* <<<< returns early */
    }
    // dna transforms do not have tjp
    const dnaPrimitives = ['fork^gib', 'mut8^gib', 'rel8^gib'];
    if ((ibGib.rel8ns?.ancestor ?? []).some(x => dnaPrimitives.includes(x))) {
        return false; /* <<<< returns early */
    }
    if (!ibGib.gib) {
        console.warn(`${lc} ibGib.gib falsy. (W: 6400d780822b44d992846f1196509be3)`);
        return false; /* <<<< returns early */
    }
    if (ibGib.gib.includes(GIB_DELIMITER)) {
        return true; /* <<<< returns early */
    }
    if (ibGib.gib === GIB) {
        // primitive
        return false; /* <<<< returns early */
    }
    // at this point, we've already determined most likely what the result is.
    // definitively, we'll pass the buck to getGibInfo in case implementation
    // details change in the future.  it is a more expensive call.  could
    // possibly just return false at this point, but since gib info would change
    // if we change our standards for gib, this is nicer.
    const gibInfo = getGibInfo({ ibGibAddr: getIbGibAddr({ ibGib }) });
    return !!gibInfo.tjpGib;
}
export function hasDna({ ibGib }) {
    const lc = `[${hasDna.name}]`;
    if (!ibGib) {
        console.warn(`${lc} ibGib falsy. (W: 5fd19751f5c84da59d83dd33487ed859)`);
        return false;
    }
    return (ibGib.rel8ns?.dna ?? []).length > 0;
}
/**
 * Extracts the tjp addr from the ibgib record. If there is no tjp addr found,
 * then refers to `defaultIfNone` arg to determine if it returns undefined or
 * the incoming ibgib's addr.
 * @returns extracted tjp addr or undefined, depending on if found and defaultIfNone value
 */
export function getTjpAddr({ ibGib, defaultIfNone = 'undefined', }) {
    const lc = `[${getTjpAddr.name}]`;
    try {
        const tjpMap = getTjpAddrs({ ibGibs: [ibGib], defaultIfNone });
        return tjpMap && Object.keys(tjpMap).length === 1 ? Object.values(tjpMap)[0] : undefined;
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
}
/**
 * builds a map of the given ibgib's addrs to each's corresponding tjp addr.
 *
 * @returns a map of ibgib addr -> tjp addr
 */
export function getTjpAddrs({ ibGibs, defaultIfNone = 'undefined', }) {
    const lc = `[${getTjpAddrs.name}]`;
    try {
        const resultMap = {};
        ibGibs.forEach(ibGib => {
            let ibGibAddr = getIbGibAddr({ ibGib });
            let tjpAddr;
            if (ibGib.rel8ns?.tjp?.length ?? 0 > 0) {
                // get the last tjp addr atow
                tjpAddr = ibGib.rel8ns.tjp[ibGib.rel8ns.tjp.length - 1];
            }
            else if (ibGib.data?.isTjp || defaultIfNone === 'incomingAddr') {
                // either the incoming addr is the tjp or we're defaulting to it per defaultIfNone arg
                tjpAddr = ibGibAddr;
            }
            else {
                // explicitly set to undefined per defaultIfNone arg
                tjpAddr = undefined;
            }
            resultMap[ibGibAddr] = tjpAddr;
        });
        return resultMap;
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
}
/**
 * Returns true if the given {@param ibGib} is the temporal junction
 * point for a given ibGib timeline.
 */
export async function isTjp_Naive({ ibGib, naive = true, }) {
    const lc = `[${isTjp_Naive.name}]`;
    try {
        if (!ibGib) {
            throw new Error('ibGib required.');
        }
        if (naive) {
            if (ibGib.data) {
                if (ibGib.data.isTjp) {
                    return true;
                }
                if (!ibGib.rel8ns) {
                    if (logalot) {
                        console.log(`${lc} ibGib.rel8ns falsy (I: c69c9e78b34845311ce7c674d7195622)`);
                    }
                    return false;
                }
                if (ibGib.rel8ns.past && ibGib.rel8ns.past.length > 0) {
                    return false;
                }
                if (ibGib.rel8ns.past && ibGib.rel8ns.past.length === 0) {
                    return true;
                }
                return false;
            }
            else {
                throw new Error('loaded ibGib required (data).');
            }
        }
        else {
            throw new Error('only naive implemented right now.');
        }
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
}
export function toDto({ ibGib, }) {
    const lc = `[${toDto.name}]`;
    if (!ibGib.ib) {
        console.warn(`${lc} ibGib.ib is falsy. (W: e60e41c2a1fc48268379d88ce13cb77b)`);
    }
    if (!ibGib.gib) {
        console.warn(`${lc} ibGib.gib is falsy. (W: fb3889cbf0684ae4ac51e48f28570377)`);
    }
    let dtoIbGib = { ib: (ibGib.ib || '').slice() };
    if (ibGib.gib) {
        dtoIbGib.gib = ibGib.gib.slice();
    }
    ;
    if (ibGib.data) {
        if (isBinary({ ibGib })) {
            // binary has data of Uint8Array atow (02/2024)
            dtoIbGib.data = ibGib.data.slice();
        }
        else {
            dtoIbGib.data = clone(ibGib.data);
        }
    }
    if (ibGib.rel8ns) {
        dtoIbGib.rel8ns = clone(ibGib.rel8ns);
    }
    return dtoIbGib;
}
/**
 * helpful for differentiating discriminated unions.
 *
 * technically, atow, anything with an ib meets the minimum requirements for an
 * ibgib. this is because you can think of any value as being a metadata
 * "header" with other derivative & metadatas as being absent.
 *
 * For example, a string value "my string yo" is all you have to go on if you
 * don't have a hash which would be metadata about that value. its address would
 * be "my string yo^gib" as it's a primitive, and its intrinisic data would just
 * be a duplicate of the ib, so it's left undefined, and there are no extrinsic
 * rel8ns so rel8ns would be undefined.
 *
 * ## driving use case
 *
 * In doing rxjs replacement, I'm needing to tell if an error coming down the
 * pipe is ErrorIbGib_V1 | Error | string.
 *
 * @param obj any object. note: if falsy, returns false
 * @returns true if the obj is an ibgib, else false
 */
export function isIbGib(obj) {
    const lc = `[${isIbGib.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 57aef93fc6cc2309523b5c72a6b11823)`);
        }
        // technically
        return !!obj && typeof obj.ib === 'string';
    }
    catch (error) {
        console.error(`${lc} ${error.message}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * extracts timestamp info from the given `ibGib`.
 *
 * ## about
 *
 * not all ibGibs have timestamps (data.timestamp) and they may be in the form
 * of a timestamp string. this may actually be a timestamp string for a Date
 * object or it could be a string of ticks from date.getTime(). or maybe
 * downstream someone just does a number instead of a string.
 *
 * so this function is supposed to be good at figuring this out given an
 * `ibGib`.
 *
 * ## todo
 *
 * 1. we could add an optional path parameter that allows us to specify the
 * path(s) of things that should be timestamps.
 *
 * 2. we could have a simple extractTimestamp(value: any) that does the same
 * thing and then call that from this function.
 *
 * @returns info object that has the timestamp in various formats (if valid) or an error msg (if invalid)
 */
export function getTimestampInfo({ ibGib, data, timestamp, }) {
    const lc = `[${getTimestampInfo.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 35cb68744bcf6139d78e37de644fad23)`);
        }
        timestamp = timestamp || data?.timestamp || ibGib?.data?.timestamp;
        const ms = ibGib?.data?.timestampMs || data?.timestampMs;
        // ensure timestamp truthy
        if (!timestamp) {
            const emsg = `${lc} timestamp is falsy. (W: 5f1f1182c8804807b886a3d922ac3dcf)`;
            console.warn(emsg);
            return { valid: false, emsg }; /* <<<< returns early */
        }
        // we have a timestamp, but no idea what it actually is. supposed to be
        // a string with either a utc timestamp or a number, but check for other
        // cases as well
        // try it as a timestamp string
        if (typeof timestamp === 'string') {
            if (Number.isInteger(Number.parseInt(timestamp))) {
                // #region maybe ticks string
                const parsedTimestamp = Number.parseInt(timestamp);
                let date = new Date();
                date.setTime(parsedTimestamp);
                if (date.toString() !== INVALID_DATE_STRING) {
                    // valid timestampInTicks string
                    if (logalot) {
                        console.log(`${lc} valid timestampInTicks string (I: 7069a1c3047f3bbc92b78674b20f5b23)`);
                    }
                    return {
                        valid: true,
                        date,
                        utc: date.toUTCString(),
                        ticks: date.getTime().toString(),
                        ms,
                    }; /* <<<< returns early */
                }
                else {
                    const emsg = `${lc} data.timestamp (${timestamp}) is an integer string but not a valid timestampInTicks string. (produces invalid date) (E: f49fbb600fe1426c8805dfc8afbe5cf3)`;
                    console.error(emsg);
                    return { valid: false, emsg }; /* <<<< returns early */
                }
                // #endregion maybe ticks string
            }
            else {
                // #region maybe timestamp string
                let date = new Date(timestamp);
                if (date.toString() !== INVALID_DATE_STRING) {
                    // valid timestamp string (may or may not be UTC)
                    return {
                        valid: true,
                        date,
                        utc: date.toUTCString(),
                        ticks: date.getTime().toString(),
                        ms,
                    }; /* <<<< returns early */
                }
                else {
                    const emsg = `${lc} data.timestamp is a non-integer string but not a valid date timestamp string (new date instantiated produces invalid date). (W: c6c90f7f3bfb49838d2081ba84fdb5fb)`;
                    console.warn(emsg);
                    return { valid: false, emsg }; /* <<<< returns early */
                }
                // #endregion maybe timestamp string
            }
        }
        else if (typeof timestamp === 'number') {
            if (Number.isInteger(timestamp)) {
                console.warn(`${lc} expected data.timestamp to be a string but is a number (W: 9d786f4d4e8d4279b5af7bee19bf721b)`);
                let date = new Date();
                date.setTime(timestamp);
                if (date.toString() !== INVALID_DATE_STRING) {
                    // valid timestamp string
                    return {
                        valid: true,
                        date,
                        utc: date.toUTCString(),
                        ticks: date.getTime().toString(),
                        ms,
                    }; /* <<<< returns early */
                }
                else {
                    const emsg = `${lc} timestamp (${timestamp}) is an integer number but not a valid timestampInTicks (new date setTime produces invalid date). (W: c6c90f7f3bfb49838d2081ba84fdb5fb)`;
                    console.warn(emsg);
                    return { valid: false, emsg }; /* <<<< returns early */
                }
            }
            else {
                // number but not integer? hmm
                const emsg = `${lc} timestamp (${timestamp}) is a non-integer number (supposed to be a string either timestamp string or ticks). (E: 2963d95f8d3b464c992b0bff948b4479)`;
                console.error(emsg);
                return { valid: false, emsg }; /* <<<< returns early */
            }
        }
        else {
            const emsg = `${lc} unknown typeof timestamp (${typeof timestamp}). (E: 12458a9c16c84e4f859b5771a30dd2ef)`;
            console.error(emsg);
            return { valid: false, emsg }; /* <<<< returns early */
        }
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * First we combine {@link readCache_graph} and {@link readCache_array} into
 * one unified cache. This can even be empty (if both empty/falsy).
 *
 * Then looks in {@link space} for all other addrs (throws if *any* not found).
 *
 * Then creates the result array, which will be in the same order as incoming
 * {@link addrs} (so this acts like a fancy .map() call).
 *
 * @throws if any addr is not found
 *
 * ## notes
 *
 * Since you can pass in empty cache(s), you can actually use this as a generic
 * getIbGibs function that throws if you don't want to use `getFromSpace` and
 * parse the results. (which is what I find I want more and more the lazier I
 * get).
 */
export async function getIbGibsFromCache_fallbackToSpaces({ addrs, readCache_graph, readCache_array, space, }) {
    const lc = `[${getIbGibsFromCache_fallbackToSpaces.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 5db66871239dfa05182277f886a20926)`);
        }
        readCache_graph ??= {};
        readCache_array ??= [];
        /**
         * we're going to put all ibgibs into this graph. IOW, we'll start with
         * incoming caches, then get all other addrs not found here from the
         * space in a single call. At that point, we'll be able to create the
         * result array.
         */
        const allIbGibs_graph = { ...readCache_graph };
        readCache_array.forEach(x => allIbGibs_graph[getIbGibAddr({ ibGib: x })] = x);
        const allCachedAddrs = Object.keys(readCache_graph);
        const addrsNotFoundInCache = addrs.filter(x => !allCachedAddrs.includes(x));
        if (addrsNotFoundInCache.length > 0) {
            if (logalot) {
                console.log(`${lc} ${addrsNotFoundInCache.length} addrsNotFoundInCache: ${addrsNotFoundInCache} (I: fccbd18116ffb849f813aaebfc6c0826)`);
            }
            /**
             * I changed this to spaces for a workaround but I am putting back
             * the signature to the single space. But I kind of like the idea of
             * this being able to pull from multiple spaces, so I'm leaving the
             * logic here for future.
             */
            const spaces = [space];
            let addrsStillNotFound = addrsNotFoundInCache.concat();
            for (const space of spaces) {
                const resGet = await getFromSpace({ addrs: addrsStillNotFound, space });
                if (resGet.success && resGet.ibGibs && resGet.ibGibs.length === addrsStillNotFound.length) {
                    resGet.ibGibs.forEach(x => allIbGibs_graph[getIbGibAddr({ ibGib: x })] = x);
                    addrsStillNotFound = [];
                }
                else {
                    // might have gotten some ibgibs
                    if (resGet.ibGibs && resGet.ibGibs.length > 0) {
                        addrsStillNotFound =
                            addrsStillNotFound.filter(addr => !resGet.ibGibs.some(gottenIbGib => getIbGibAddr({ ibGib: gottenIbGib }) === addr));
                        if (addrsStillNotFound.length === 0) {
                            break;
                        }
                    }
                    else {
                        if (logalot) {
                            console.log(`${lc} no addrs found in space (${space.ib}). trying next space. (I: 4f99d8bacbb10ab3f8737a752564b326)`);
                        }
                    }
                }
            }
            if (addrsStillNotFound.length > 0) {
                throw new Error(`could not get all addrs from cache or spaces. addrsStillNotFound: ${addrsStillNotFound}. spaces: ${spaces.map(x => x.ib).join('|')} (E: 0c6418f83dd85bda886e56f5b98f3126)`);
            }
        }
        // at this point, we're guaranteed to have all addrs in allIbGibs_graph,
        // but we will still check
        const resIbGibs = [];
        for (const addr of addrs) {
            // refactor this later to be less defensive if this stands the test
            // of time. (ATOW 01/28/2026)
            const ibGib = allIbGibs_graph[addr];
            if (!ibGib) {
                throw new Error(`(UNEXPECTED) allIbGibs_graph doesn't have addr? We're expecting logic to preclude this at this point. addr: ${addr} (E: f68e1d236ae5e3eb08da9933c8230626)`);
            }
            resIbGibs.push(ibGib);
        }
        return resIbGibs;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
//# sourceMappingURL=ibgib-helper.mjs.map