import { clone, extractErrorMsg, hash, pretty } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbAndGib, getIbGibAddr, } from '@ibgib/ts-gib/dist/helper.mjs';
import { GIB, ROOT, isPrimitive, getGib, getGibInfo, GIB_DELIMITER, } from '@ibgib/ts-gib/dist/V1/index.mjs';
import { validateIbGibIntrinsically } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
import { GLOBAL_LOG_A_LOT } from '../../../core-constants.mjs';
import { SpaceBase_V1 } from '../space-base-v1.mjs';
import { argy_ } from '../../witness-helper.mjs';
import { isBinary, getBinHashAndExt, } from '../../../common/other/ibgib-helper.mjs';
import { parseSpaceIb, getSpaceIb, } from '../space-helper.mjs';
import { DEFAULT_LOCAL_SPACE_POLLING_INTERVAL_MS, IBGIB_SPACE_NAME_DEFAULT, ZERO_SPACE_ID } from '../space-constants.mjs';
import { IBGIB_BASE_SUBPATH, IBGIB_SPACE_SUBPATH_DEFAULT, IBGIB_BASE_DIR, IBGIB_ENCODING, IBGIB_IBGIBS_SUBPATH, IBGIB_META_SUBPATH, DEFAULT_FILESYSTEM_SPACE_DESCRIPTION, IBGIB_BIN_SUBPATH, IBGIB_DNA_SUBPATH, DEFAULT_LONG_PATH_LENGTH, IBGIB_LONG_SUBPATH, DEFAULT_PATH_SEPARATOR, ARBITRARY_IB_SUBSTRING_LENGTH_FOR_MITIGATE_LONG_PATH, } from './filesystem-constants.mjs';
import { isMetaStone, parseMetaStoneIb, validateCommonMetaStoneIbGib } from '../../../common/meta-stone/meta-stone-helper.mjs';
import { META_STONE_TARGET_REL8N_NAME } from '../../../common/meta-stone/meta-stone-constants.mjs';
const logalot = GLOBAL_LOG_A_LOT;
/**
 * Used in bootstrapping.
 *
 * If you change this, please bump the version
 *
 * (but of course won't be the end of the world when this doesn't happen).
 */
const DEFAULT_FILESYSTEM_SPACE_DATA_V1 = {
    version: '3',
    uuid: ZERO_SPACE_ID,
    name: IBGIB_SPACE_NAME_DEFAULT,
    classname: 'FilesystemSpace_V1',
    baseDir: IBGIB_BASE_DIR,
    encoding: IBGIB_ENCODING,
    baseSubPath: IBGIB_BASE_SUBPATH,
    spaceSubPath: IBGIB_SPACE_SUBPATH_DEFAULT,
    ibgibsSubPath: IBGIB_IBGIBS_SUBPATH,
    metaSubPath: IBGIB_META_SUBPATH,
    binSubPath: IBGIB_BIN_SUBPATH,
    dnaSubPath: IBGIB_DNA_SUBPATH,
    longSubPath: IBGIB_LONG_SUBPATH,
    longPathLength: DEFAULT_LONG_PATH_LENGTH,
    mitigateLongPaths: true,
    persistOptsAndResultIbGibs: false,
    validateIbGibAddrsMatchIbGibs: false,
    longPollingIntervalMs: DEFAULT_LOCAL_SPACE_POLLING_INTERVAL_MS,
    allowPrimitiveArgs: false,
    catchAllErrors: true,
    description: DEFAULT_FILESYSTEM_SPACE_DESCRIPTION,
    trace: false,
};
// #endregion
/**
 * basic validation for filesystem space
 */
export async function validateFilesystemSpace_V1Intrinsically({ space }) {
    const lc = `[${validateFilesystemSpace_V1Intrinsically.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: fcfe57145426ab4a5a0e961e87001922)`);
        }
        const errors = (await validateIbGibIntrinsically({ ibGib: space })) ?? [];
        const { ib, gib, data, rel8ns } = space;
        if (!ib) {
            errors.push('ib required. (E: a67fbb88841048f0a5bbf5ea266d7789)');
        }
        if (!gib) {
            errors.push('gib required. (E: c811ce87f1e042769d0a43ff56091e22)');
        }
        if (gib === GIB) {
            errors.push('gib cannot be primitive. (E: 5933bb715efc4e7cb74383d69d55ca64)');
        }
        if (!data) {
            errors.push('data required. (E: 0b358c37f244422a96c7edb638b5ec00)');
            return errors;
        }
        if (!data.name) {
            errors.push('space name required. (E: f66412cd4d4e43ebb3cef55f91914f7c)');
        }
        if (!data.baseDir) {
            errors.push(`data.baseDir required. (E: 9df11d82a4bc4da38765c8e21ed9bace)`);
        }
        if (!data.baseSubPath) {
            errors.push(`data.baseSubPath required. (E: a44d3afec8b345d49480a640b33edfcd)`);
        }
        if (!data.binSubPath) {
            errors.push(`data.binSubPath required. (E: 52896e2405e842ebb7b5b11419556873)`);
        }
        if (!data.dnaSubPath) {
            errors.push(`data.dnaSubPath required. (E: 698a7853722b4e99927570231c350434)`);
        }
        if (!data.ibgibsSubPath) {
            errors.push(`data.ibgibsSubPath required. (E: f254076e67324969b5b2361d1fc514fb)`);
        }
        if (!data.metaSubPath) {
            errors.push(`data.metaSubPath required. (E: b675033dd2584fafbfe7a96d0c33b444)`);
        }
        if (data.n && typeof data.n !== 'number') {
            errors.push(`data.n must be a number. (E: a86fc07807594c90a4e67f9a7605b1b1)`);
        }
        if (data.n === undefined) {
            // the very first space record (tjp) has an undefined n and no rel8ns
            if (rel8ns) {
                errors.push(`rel8ns not expected when data.n is falsy (custom temporal junction point indicator I suppose...) (E: ef4317e9db9147abba66d75ffe63740b)`);
            }
        }
        if (!data.spaceSubPath) {
            errors.push(`data.spaceSubPath required. (E: 477d02f719bc45f08d133681c6e3f492)`);
        }
        if (!data.uuid) {
            errors.push(`data.uuid required. (E: 7ac3e7ae16ef47b78414b07b18d1a740)`);
        }
        if (!data.encoding) {
            errors.push(`data.encoding required. (E: 4f4baf9c9adf4a11a3357b7cfe450b92)`);
        }
        /** should probably get this from Capacitor... */
        const validEncodings = ["utf8", "ascii", "utf16"];
        if (!validEncodings.includes(data.encoding)) {
            errors.push(`invalid encoding: ${data.encoding}. validEncodings: ${validEncodings.join(', ')} (E: 0c910168b12f4f22935c3d23b23bbfd8)`);
        }
        // ensure ib matches up with internal data
        const { spaceClassname, spaceId, spaceName } = parseSpaceIb({ spaceIb: ib });
        if (data.classname && (spaceClassname !== data.classname)) {
            errors.push(`ib's spaceClassname (${spaceClassname}) must match data.classname (${data.classname}) (E: 57977f50d9bb4c64a0d06544023742a1)`);
        }
        if (spaceId !== data.uuid) {
            errors.push(`ib's spaceId (${spaceId}) must match data.uuid (${data.uuid}) (E: 03dd4d0d119b48a8a2d97c1444e2239e)`);
        }
        if (spaceName !== data.name) {
            errors.push(`ib's spaceName (${spaceName}) must match data.name (${data.name}) (E: 2cfa219c948f4fc8869d47b7217aed6f)`);
        }
        // ensure rel8ns make sense
        if (data.n === undefined && (rel8ns?.past ?? []).length > 0) {
            errors.push(`"past" rel8n not expected when data.n is falsy (E: 189bdfe41a7a46db947590d50c11ef88)`);
        }
        if (data.n && (rel8ns?.past ?? []).length === 0) {
            errors.push(`"past" rel8n required when data.n is truthy (E: abab9f88c898406497181c872b619276)`);
        }
        if (data.n === 0 && (rel8ns?.past ?? []).length !== 1) {
            errors.push(`"past" rel8n expected to have a single record when data.n === 0 (E: d5c9721b475d45ce9b94ba989bbc231b)`);
        }
        if (rel8ns && (rel8ns.past?.length ?? -1) > 0) {
            const pastAddrs = rel8ns.past;
            pastAddrs.forEach(x => {
                const { ib: pastIb } = getIbAndGib({ ibGibAddr: x });
                const pastIbInfo = parseSpaceIb({ spaceIb: pastIb });
                if (pastIbInfo.spaceClassname && (pastIbInfo.spaceClassname !== spaceClassname)) {
                    errors.push(`rel8ns.past address classname (${pastIbInfo.spaceClassname}) must match current spaceClassname (${spaceClassname}) (E: 51d859ab2e9f46279b9492c1a3ad1037)`);
                }
                if (pastIbInfo.spaceId !== spaceId) {
                    errors.push(`rel8ns.past address spaceId (${pastIbInfo.spaceId}) must match current spaceId (${spaceId}) (E: 738a23cf54c947528e085cba24ab547a)`);
                }
                // i want to allow this, but for now we're going to require not changing the name...
                if (pastIbInfo.spaceName !== spaceName) {
                    errors.push(`rel8ns.past address spaceName (${pastIbInfo.spaceName}) must match current spaceName (${spaceName}) (E: c0512b208885489eaf0d073613205fa7)`);
                }
            });
        }
        return errors;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Base class convenience for a local space with V1 ibgibs.
 *
 * This naively caches ibGibs in memory. When not found there,
 * will looks in files using Ionic `FileSystem`.
 */
export class FilesystemSpace_V1 extends SpaceBase_V1 {
    /**
     * Log context for convenience with logging.
     */
    lc = `[${FilesystemSpace_V1.name}]`;
    /**
     * Naive caching in-memory. Memory leak as it stands right now!
     */
    ibGibs = {};
    /**
     * Check every time app starts if paths exist.
     * But don't check every time do anything whatsoever.
     *
     * @see {@link ensureAllDirsExist}
     * @see {@link ensureDirsImpl}
     */
    pathExistsMap = {};
    constructor(
    // /**
    //  * Default predicate value when putting an unknown ibGib.
    //  *
    //  * ## notes
    //  *
    //  * So when a repo witnesses another ibGib, it either defaults to
    //  * storing that ibGib or not storing that ibGib. This is what that
    //  * is referring to. If it's optimistic, then it stores any ibGib by
    //  * default and it passes its put predicate.
    //  */
    // public optimisticPut: boolean = true,
    initialData, initialRel8ns) {
        super(initialData ?? clone(DEFAULT_FILESYSTEM_SPACE_DATA_V1), initialRel8ns);
    }
    async validateWitnessArg(arg) {
        const lc = `${this.lc}[${this.validateWitnessArg.name}]`;
        let errors = [];
        try {
            errors = (await super.validateWitnessArg(arg)) || [];
            if (arg.data?.cmd === 'put' && (arg.ibGibs || []).length === 0) {
                errors.push(`when "put" cmd is called, ibGibs required.`);
            }
            if (arg.data?.cmd === 'get' && (arg.data?.ibGibAddrs || []).length === 0) {
                errors.push(`when "get" cmd is called, ibGibAddrs required.`);
            }
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (errors?.length > 0) {
                console.error(`${lc} errors: ${errors}`);
            }
        }
        return errors;
    }
    /**
     * Initializes to default space values.
     */
    async initialize() {
        const lc = `${this.lc}[${this.initialize.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            if (!this.data) {
                this.data = clone(DEFAULT_FILESYSTEM_SPACE_DATA_V1);
                this.data = this.data; // why does ts compiler need this?
            }
            if (!this.data?.classname) {
                this.data.classname = FilesystemSpace_V1.name;
            }
            if (!this.data.baseDir) {
                this.data.baseDir = IBGIB_BASE_DIR;
            }
            if (!this.data.encoding) {
                this.data.encoding = IBGIB_ENCODING;
            }
            if (!this.data.baseSubPath) {
                this.data.baseSubPath = IBGIB_BASE_SUBPATH;
            }
            if (!this.data.spaceSubPath) {
                this.data.spaceSubPath = IBGIB_SPACE_SUBPATH_DEFAULT;
            }
            if (!this.data.ibgibsSubPath) {
                this.data.ibgibsSubPath = IBGIB_IBGIBS_SUBPATH;
            }
            if (!this.data.metaSubPath) {
                this.data.metaSubPath = IBGIB_META_SUBPATH;
            }
            if (!this.data.binSubPath) {
                this.data.binSubPath = IBGIB_BIN_SUBPATH;
            }
            if (!this.data.dnaSubPath) {
                this.data.dnaSubPath = IBGIB_DNA_SUBPATH;
            }
            // do nothing, allow falsy
            // if (!this.data.longSubPath) { this.data.longSubPath = IBGIB_LONG_SUBPATH; }
            this.ib = getSpaceIb({ space: this, classname: FilesystemSpace_V1.name });
            this.gib = await getGib({ ibGib: this });
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    hasInCache({ addr }) {
        if (isPrimitive({ gib: getIbAndGib({ ibGibAddr: addr }).gib })) {
            // primitives never cached
            return Promise.resolve(false);
        }
        else if (Object.keys(this.ibGibs).includes(addr)) {
            // has in local instance cache
            return Promise.resolve(true);
        }
        else if (this.cacheSvc) {
            // not local so delegate to cache svc
            return this.cacheSvc.has({ addr });
        }
        else {
            // no external cache svc and not in local instance cache
            return Promise.resolve(false);
        }
    }
    async putInCache({ addr, ibGib }) {
        const lc = `${this.lc}[${this.putInCache.name}][${addr}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            if (!ibGib) {
                throw new Error(`ibGib required (E: 993e26fe40894bab9feccac3938f37df)`);
            }
            if (isPrimitive({ ibGib })) {
                if (logalot) {
                    console.log(`${lc} skipping caching primitive (I: a04dfb691582a4db8a0bfecfefe5e622)`);
                }
                return;
            }
            else {
                if (logalot) {
                    console.log(`${lc} caching addr (I: b996a306f256ee5f39db1e2f5d515c22)`);
                }
            }
            // instance cache
            this.ibGibs[addr] = ibGib;
            // cache svc
            if (this.cacheSvc) {
                await this.cacheSvc.put({ addr, ibGib });
            }
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async getFromCache({ addr }) {
        const lc = `${this.lc}[${this.getFromCache.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            let cached = this.ibGibs[addr];
            if (!cached && this.cacheSvc) {
                const info = await this.cacheSvc.get({ addr });
                cached = info?.ibGib;
            }
            return cached;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    deleteFromCache({ addr }) {
        const lc = `${this.lc}[${this.getFromCache.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            delete this.ibGibs[addr];
            return Promise.resolve();
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async getImpl(arg) {
        const lc = `${this.lc}[${this.getImpl.name}]`;
        const resultIbGibs = [];
        const resultData = { optsAddr: getIbGibAddr({ ibGib: arg }), };
        let notFoundIbGibAddrs = undefined;
        try {
            if (!arg.data) {
                throw new Error('arg.data is falsy');
            }
            let { ibGibAddrs, isDna, } = arg.data;
            ibGibAddrs ||= [];
            const binAddrs = ibGibAddrs.filter(addr => isBinary({ addr }));
            let ibGibAddrsNonBin = ibGibAddrs.filter(addr => !binAddrs.includes(addr));
            if (logalot) {
                console.log(`${lc} getting non-bin ibgibs. ibGibAddrsNonBin: ${ibGibAddrsNonBin}`);
            }
            for (let i = 0; i < ibGibAddrsNonBin.length; i++) {
                const addr = ibGibAddrsNonBin[i];
                if (!arg.data.force && await this.hasInCache({ addr })) {
                    if (logalot) {
                        console.log(`${lc} YES found in naive cache. (I: 0b23f394fd944c2a96df3543dd0a59c5)`);
                    }
                    const cached = await this.getFromCache({ addr });
                    if (!cached) {
                        throw new Error(`(UNEXPECTED) we had in cache but failed to retrieve? (E: 4af334f7a90cb22ffa3b549d0db19a22)`);
                    }
                    resultIbGibs.push(cached);
                }
                else {
                    // not found in memory, so look in files
                    // if (!isPrimitive({ gib: getIbAndGib({ ibGibAddr: addr }).gib }) && this.cacheSvc) {
                    // }
                    const getResult = await this.getFile({ addr, isDna, });
                    if (getResult?.success && getResult.ibGib) {
                        await this.putInCache({ addr, ibGib: getResult.ibGib });
                        resultIbGibs.push(getResult.ibGib);
                    }
                    else {
                        // not found in memory or in files
                        if (!notFoundIbGibAddrs) {
                            notFoundIbGibAddrs = [];
                        }
                        notFoundIbGibAddrs.push(addr);
                    }
                }
            }
            for (let i = 0; i < binAddrs.length; i++) {
                // debugger;
                const addr = binAddrs[i];
                const { binHash, binExt } = getBinHashAndExt({ addr });
                // getting binary, not a regular ibGib via addr
                if (logalot) {
                    console.log(`${lc} getting binHash.binExt: ${binHash}.${binExt}`);
                }
                const getResult = await this.getFile({ addr });
                if (getResult?.success && getResult.ibGib) {
                    if (logalot) {
                        console.log(`${lc} getResult.success. ibGib.data?.length: ${getResult.ibGib.data?.length}`);
                    }
                    resultIbGibs.push(getResult.ibGib);
                }
                else {
                    if (logalot) {
                        console.log(`${lc} not found in files. (binData is not cached atm)`);
                    }
                    // not found in files. (binData is not cached atm)
                    if (!notFoundIbGibAddrs) {
                        notFoundIbGibAddrs = [];
                    }
                    notFoundIbGibAddrs.push(addr);
                }
            }
            if (notFoundIbGibAddrs && notFoundIbGibAddrs.length > 0) {
                if (logalot) {
                    console.log(`${lc}[${this.data.uuid.substring(0, 8)}] notFoundIbGibAddrs: ${notFoundIbGibAddrs} (I: 10f7e35e75445a1d64b353851d00b923)`);
                }
                resultData.addrsNotFound = notFoundIbGibAddrs;
                resultData.errors ??= [];
                resultData.errors.push(`notFoundIbGibAddrs.length > 0 (E: e7eadcfa2f4643238590469bc917a35c)`);
                resultData.success = false;
            }
            else {
                resultData.success = true;
            }
        }
        catch (error) {
            const emsg = extractErrorMsg(error);
            console.error(`${lc} error: ${emsg}`);
            resultData.errors = [emsg];
        }
        try {
            const result = await this.resulty({ resultData });
            if (resultIbGibs.length > 0) {
                result.ibGibs = resultIbGibs;
            }
            return result;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
    }
    async putImpl(arg) {
        const lc = `${this.lc}[${this.putImpl.name}]`;
        const resultData = { optsAddr: getIbGibAddr({ ibGib: arg }), };
        const errors = [];
        try {
            if (logalot) {
                console.log(`validating arg and internal state...`);
            }
            if (!arg.data) {
                throw new Error('arg.data required. (E: d64a46e5efab4b09b57850ecf0854386)');
            }
            if (arg.ibGibs?.length === 0) {
                throw new Error(`arg.ibGibs required. (E: b4930d564b284fb9b26b542f14143a28)`);
            }
            if (logalot) {
                console.log(`arg and internal state validated...calling impl func`);
            }
            return await this.putIbGibsImpl(arg); // <<<< returns early
        }
        catch (error) {
            const emsg = extractErrorMsg(error);
            console.error(`${lc} error: ${emsg}`);
            resultData.errors = errors.concat([emsg]);
            resultData.success = false;
        }
        // only executes if there is an error.
        const result = await this.resulty({ resultData });
        return result;
    }
    async putIbGibsImpl(arg) {
        const lc = `${this.lc}[${this.putIbGibsImpl.name}]`;
        const resultData = { optsAddr: getIbGibAddr({ ibGib: arg }), };
        const errors = [];
        const warnings = [];
        const addrsErrored = [];
        try {
            if (!arg.data) {
                throw new Error('arg.data is falsy (E: aae5757c158d4a799deb7fca9d6245f0)');
            }
            const { isDna, force } = arg.data;
            const ibGibs = arg.ibGibs || [];
            const addrsAlreadyHave = [];
            for (let i = 0; i < ibGibs.length; i++) {
                const ibGib = ibGibs[i];
                const addr = getIbGibAddr({ ibGib });
                if (logalot) {
                    console.log(`${lc} checking to see if already exists...`);
                }
                const getResult = await this.getFile({ addr, isDna });
                // const getResult: any = null; // testing performance
                if (getResult?.success && getResult.ibGib) {
                    // already exists...
                    if (logalot) {
                        console.log(`${lc} already exists...`);
                    }
                    if (force) {
                        // ...but save anyway.
                        if (logalot) {
                            console.log(`${lc} Forcing save of already put addr: ${addr} (I: 325f363e5d438b43c24b810c150e4e22)`);
                        }
                        const putResult = await this.putFile({ ibGib, isDna });
                        if (putResult.success) {
                            if (!isDna) {
                                // naive cache will cause "memory leak" eventually
                                await this.putInCache({ addr, ibGib });
                            }
                        }
                        else {
                            errors.push(putResult.errorMsg || `${lc} error putting ${addr}`);
                            addrsErrored.push(addr);
                        }
                    }
                    else {
                        // ...so annotate
                        // deb ugger;
                        if (logalot) {
                            warnings.push(`${lc} skipping (non-force) of already exists addr: ${addr} (W: b7fbe22473014dd090db88aee631fecb)`);
                        }
                        addrsAlreadyHave.push(addr);
                    }
                }
                else {
                    // does not already exist.
                    if (logalot) {
                        console.log(`${lc} does NOT already exist...`);
                    }
                    const putResult = await this.putFile({ ibGib, isDna, });
                    if (putResult.success) {
                        if (!isDna) {
                            await this.putInCache({ addr, ibGib });
                        }
                    }
                    else {
                        errors.push(putResult.errorMsg || `${lc} error putting ${addr}`);
                        addrsErrored.push(addr);
                    }
                }
            }
            if (addrsAlreadyHave.length > 0) {
                resultData.addrsAlreadyHave = addrsAlreadyHave;
            }
            if (warnings.length > 0) {
                resultData.warnings = warnings;
            }
            if (errors.length === 0) {
                resultData.success = true;
            }
            else {
                resultData.errors = errors;
                resultData.addrsErrored = addrsErrored;
            }
        }
        catch (error) {
            const emsg = extractErrorMsg(error);
            console.error(`${lc} error: ${emsg}`);
            resultData.errors = errors.concat([emsg]);
            resultData.addrsErrored = addrsErrored;
            resultData.success = false;
        }
        const result = await this.resulty({ resultData });
        return result;
    }
    async deleteImpl(arg) {
        const lc = `${this.lc}[${this.deleteImpl.name}]`;
        const resultData = { optsAddr: getIbGibAddr({ ibGib: arg }), };
        const errors = [];
        const warnings = [];
        const addrsDeleted = [];
        const addrsErrored = [];
        try {
            if (!arg.data) {
                throw new Error('arg.data is falsy');
            }
            const { isDna, } = arg.data;
            const ibGibAddrs = arg.data.ibGibAddrs || [];
            // delete cached entries first
            // const cachedAddrs = Object.entries(this.ibGibs)
            //     .filter(([x,y]) => ibGibAddrs.includes(x))
            //     .map(([x,y]) => x);
            // cachedAddrs.forEach(addr => { delete this.ibGibs[addr]; });
            // iterate through ibGibs, but this may be an empty array if we're doing binData.
            for (let i = 0; i < ibGibAddrs.length; i++) {
                const addr = ibGibAddrs[i];
                await this.deleteFromCache({ addr });
                const deleteResult = await this.deleteFile({ addr, isDna, });
                if (deleteResult?.success) {
                    addrsDeleted.push(addr);
                }
                else {
                    errors.push(deleteResult.errorMsg || `delete failed: addr (${addr})`);
                    addrsErrored.push(addr);
                }
            }
            if (warnings.length > 0) {
                resultData.warnings = warnings;
            }
            if (errors.length === 0) {
                resultData.success = true;
                resultData.addrs = addrsDeleted;
            }
            else {
                resultData.errors = errors;
                resultData.addrsErrored = addrsErrored;
                if (addrsDeleted.length > 0) {
                    const warningMsg = `some addrs (${addrsDeleted.length}) were indeed deleted, but not all. See result addrs and addrsErrored.`;
                    resultData.warnings = (resultData.warnings || []).concat([warningMsg]);
                }
            }
        }
        catch (error) {
            const emsg = extractErrorMsg(error);
            console.error(`${lc} error: ${emsg}`);
            resultData.errors = errors.concat([emsg]);
            resultData.addrsErrored = addrsErrored;
            resultData.success = false;
        }
        const result = await this.resulty({ resultData });
        return result;
    }
    async getAddrsImpl(arg) {
        const lc = `${this.lc}[${this.getAddrsImpl.name}]`;
        throw new Error(`${lc} not implemented. (E: 74dd50ce4b564559bc44d6c08d446cb3)`);
    }
    /**
     * Performs a naive `exists: boolean` or `includes: boolean` analog.
     *
     * If all of the addresses are found, will result in `success` and `can` being `true`.
     *
     * Else, `can` will be falsy, and `addrsNotFound` will be populated with all/some of
     * the queried addresses.
     *
     * ## notes
     *
     * This does not take authorization into account in any way. it's a simple, naive in-memory
     * storage ibGib witness.
     *
     * @returns result ibGib whose primary value is `can`
     */
    async canGetImpl(arg) {
        const lc = `${this.lc}[${this.canGetImpl.name}]`;
        const resultData = { optsAddr: getIbGibAddr({ ibGib: arg }), };
        try {
            throw new Error(`not implemented (E: 2a45977c584c6bbbc4f164c16106bd22)`);
        }
        catch (error) {
            const emsg = extractErrorMsg(error);
            console.error(`${lc} error: ${emsg}`);
            resultData.errors = [emsg];
        }
        const result = await this.resulty({ resultData });
        return result;
    }
    async canPutImpl(arg) {
        const lc = `${this.lc}[${this.canPutImpl.name}]`;
        const resultData = { optsAddr: getIbGibAddr({ ibGib: arg }), };
        try {
            throw new Error(`not implemented (E: 25a005d496efc69faefdec155ffb4a22)`);
        }
        catch (error) {
            const emsg = extractErrorMsg(error);
            console.error(`${lc} error: ${emsg}`);
            resultData.errors = [emsg];
        }
        const result = await this.resulty({ resultData });
        return result;
    }
    async canDeleteImpl(arg) {
        const lc = `${this.lc}[${this.canDeleteImpl.name}]`;
        throw new Error(`${lc} not implemented (E: a3306138b4ac429ba5f8f293ad7dd07b)`);
    }
    async getLatestAddrsImpl(arg) {
        const lc = `${this.lc}[${this.getLatestAddrsImpl.name}]`;
        const resultData = { optsAddr: getIbGibAddr({ ibGib: arg }), };
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            if (!arg.data) {
                throw new Error(`arg.data required (E: cd92cb965d711c8014455c69e898ac23)`);
            }
            if (!arg.data.ibGibAddrs) {
                throw new Error(`arg.data.ibGibAddrs required (E: 2d0824f9b8aec916a751ef17e2d4a823)`);
            }
            if (arg.data.ibGibAddrs.length === 0) {
                throw new Error(`arg.data.ibGibAddrs required to be non-zero (E: 2d0824f9b8aec916a751ef17e2d4a823)`);
            }
            const latestAddrs = new Set();
            const addrsNotFound = new Set();
            const addrsErrored = new Set();
            resultData.latestAddrsMap = {};
            // iterate through incoming ibGibAddrs, get their corresponding
            // ibgib from file storage, and use our existing
            // `getLatestAddrImpl` function
            for (let i = 0; i < arg.data.ibGibAddrs.length; i++) {
                const addr = arg.data.ibGibAddrs[i];
                const getResult = await this.getFile({ addr });
                if (getResult?.success && getResult.ibGib) {
                    const ibGib = getResult.ibGib;
                    const latestAddr = await this.getLatestAddrImpl({ ibGib });
                    if (latestAddr) {
                        latestAddrs.add(latestAddr);
                        resultData.latestAddrsMap[addr] = latestAddr;
                    }
                    else {
                        console.warn(`expecting latestAddr to either be assigned or throw. Adding addr (${addr}) to addrsErrored. (W: 35c3712b1a1e579ccf28c389eb2ecc22)`);
                        addrsErrored.add(addr);
                    }
                }
                else if (getResult?.success) {
                    addrsNotFound.add(addr);
                    resultData.latestAddrsMap[addr] = null;
                }
                else if (getResult.errorMsg) {
                    addrsErrored.add(addr);
                }
                else {
                    throw new Error(`unknown (invalid) getResult: ${pretty(getResult)} (E: 2674ad5d30294be29a3d3fdcf54ef6d9)`);
                }
            }
            resultData.addrs = latestAddrs.size > 0 ? [...latestAddrs] : undefined;
            resultData.addrsErrored = addrsErrored.size > 0 ? [...addrsErrored] : undefined;
            resultData.addrsNotFound = addrsNotFound.size > 0 ? [...addrsNotFound] : undefined;
            // we didn't throw, so that's a success
            resultData.success = true;
        }
        catch (error) {
            const emsg = extractErrorMsg(error);
            console.error(`${lc} error: ${emsg}`);
            resultData.errors = [emsg];
        }
        try {
            let result = await this.resulty({ resultData });
            return result;
        }
        catch (error) {
            console.error(`${lc}[resulty] ${extractErrorMsg(error)}`);
            throw error;
        }
    }
    /**
     * @internal
     *
     * This is the actual implementation function to get a single latest address
     * for a single ibgib.
     *
     * @returns latest addr in this space according to the "latest" special ibgib index
     *
     * ## implementation notes
     *
     * ### pre-11/2023 Single SpecialIbGibType.latest "special ibgib" indexing
     *
     * an early implementation, this used a single meta "special ibgib" () that
     * is a central point in the space that maps from tjpAddr => latestAddr.
     * this makes it a bottleneck (not scalable) and a single point-of-failure
     * (not robust).
     *
     * ### 11/2023 (current atow 11/2023)
     *
     * default latest address mapping from tjpAddr => latestAddr implementation
     * candidates are considered here.
     *
     * #### individual constant-named files
     *
     * individual files inside /basePath/spaceSubpath/metaSubpath/latest. These
     * will have tjpAddr filename (no extension?) but the contents should simply
     * be the latestAddr.
     *
     * ##### advantages
     *
     * * this would require additional/specialized file getting/putting
     *   implementations.
     * * this might be a proximally faster implementation as no additional
     *   hashing would be required.
     *
     * ##### disadvantages
     *
     * mainly it is about possible security concerns on this implementation and
     * less ibgib dogfooding.
     *
     * I am unsure if this would be any "less safe" than the previous
     * implementation that relied on the special ibgib indexing, as even though
     * the latest special ibgib index was itself hashed, it was still ephemeral
     * and the space did not track changes to it. Either way, it is always a
     * security consideration to change the tjpAddr => latestAddr mapping as you
     * can hijack timelines. But this is mitigated as long as you have a
     * monotonically increasing space, you can correct any malfeasance and
     * "stitch" together the "correct" space state.
     *
     * Still, it would be good to have some timeline whose job is to snapshot
     * these periodically. Perhaps that is simply a functionality that would be
     * good to have regardless of "security" concerns. Reified checkpointing.
     *
     * #### meta latest ibgib stones (constants/no tjp themselves)
     *
     * instead of bare files, we reuse ibgib plumbing via ibgib stones with
     * special ib schemas that allow us to mimic a timeline with the following
     * information:
     *
     * * meta_latest [tjpGib] [n] [index timestamp ticks]
     *
     * _I mention this as mimicking a timeline because if we were to have them as
     * colocated ibgibs in space (not separated specially to ignore when sending
     * graphs), we would then have a recursive problem of keeping track of the
     * latest addrs of the latest addr tracking ibgibs (turtles all the way
     * down)._
     *
     * this would be put in a separate subpath underneath the meta subpath, or
     * possibly as a sibling beside it. definitely need a separate path though
     * for the same reason we have a separate path for dna. there will be many,
     * **many** of these constants. so much so that we may want to prune them on
     * some basis. Possibly will do sibling beside it to reduce overall path
     * length.
     *
     * the index timestamp ticks is the timestamp when the indexing occurs in
     * the local space (not the ibgib's internal timestamp, if it exists). I
     * wanted to add this also, but could be yagni and there is a cost with path
     * length implementations.
     *
     * ##### advantages
     *
     * * reuses existing get/put implementation
     * * allows consistent coding of helper functions/types/etc.
     * * allows for (future implementation) of on-chain schema information
     * * more robust against soft hacks
     *   * soft = non-total access hacks
     * * allows for future consensus/witness backups/gossip
     *
     * overall would be less distal overhead (more simplification) and dogfoods
     * the ibgib protocol to a greater degree.
     *
     * ##### disadvantages
     *
     * * more proximal overhead
     *   * already "practical" performance issues in prototype, though afaict
     *     this is largely because i'm working solo on the darn project and i suck
     *     at ux (i.e. angular)
     *     * not just me, angular/react/others dispatching are necessarily overhead as
     *       they duplicate some of the awesomeness that comes for free when starting with
     *       ibgib timelines from the beginning.
     */
    async getLatestAddrImpl({ ibGib, ibGibAddr, }) {
        let lc = `${this.lc}[${this.getLatestAddrImpl.name}]`;
        if (logalot) {
            console.log(`${lc} starting...`);
        }
        try {
            if (!ibGib && !ibGibAddr) {
                throw new Error(`either ibGib or ibGibAddr required (E: 1f8f0cb61d1b4c708b06762048735c22)`);
            }
            if (ibGib && ibGibAddr && (getIbGibAddr({ ibGib }) !== ibGibAddr)) {
                throw new Error(`(UNEXPECTED) both ibGib and ibGibAddr provided but they don't match? (E: 01d1938252ef49b53d41d58abfaac523)`);
            }
            ibGibAddr ||= getIbGibAddr({ ibGib });
            const { ib, gib } = getIbAndGib({ ibGibAddr });
            if (!gib) {
                throw new Error(`(UNEXPECTED) gib falsy? (E: d2506b99162e27348310f8e7e68e4523)`);
            }
            // first do some initial checking to rule out quick special cases...
            // if it's primitive, warn and return incoming addr
            if (isPrimitive({ gib })) {
                console.warn(`${lc} incoming ibGibAddr is primitive. Why are we calling getLatest on a primitive? returning incoming addr. (W: 57fcf479d5cc48d393841ec3d53587fa)`);
                return ibGibAddr; /* <<<< returns early */
            }
            const probablyIsDna = ['fork', 'mut8', 'rel8'].includes(ib);
            if (probablyIsDna) {
                if (logalot) {
                    console.log(`${lc} probably is dna (ib === fork/mut8/rel8). returning incoming ibGibAddr early. (I: 2081628936de6a36bbb51224f0223523)`);
                }
                return ibGibAddr; /* <<<< returns early */
            }
            // at this point it might be...
            // 1) a stone (constant/no timeline)
            // 2) a tjp (data.isTjp is true)
            // 3) a timeline member (tjpGib in gib)
            // in any case, we can pull the tjpGib out of the addr. if it's falsy, we use the gib as
            // if it were the tjpGib.
            //
            const gibInfo = getGibInfo({ ibGibAddr });
            let { tjpGib } = gibInfo;
            tjpGib ||= gib;
            // if (!tjpGib) {
            //     // may still itself be the tjp
            //     if (!!ibGib.data?.isTjp) {
            //         // the ibgib is itself the tjp, so it's gib is the tjpGib
            //         tjpGib = gib;
            //     } else {
            //         // no tjp, just return the incoming ibGib
            //         if (logalot) { console.log(`${lc} incoming ibGib has no tjp and is not itself the tjp. so it's a stone, not a timeline. returning incoming ibGibAddr. (I: 30bace867ea274cdc3263ab901c9dd23)`); }
            //         return ibGibAddr; /* <<<< returns early */
            //     }
            // }
            // guaranteed to have a tjp at this point & tjpGib is truthy
            // get the tjp for the rel8nName mapping, and also for
            // some checking logic
            // let tjp = await getTjpIbGib({ ibGib, space: this });
            // if (!tjp) {
            //     throw new Error(`(UNEXPECTED) tjp not found for ${ibGibAddr}? Should at least just be the ibGib's address itself. (E: 860bdcaaf80548feb6b61b4cde21a722)`);
            //     // console.warn();
            //     // tjp = ibGib;
            // }
            // const tjpAddr = getIbGibAddr({ ibGib: tjp });
            // if (logalot) { console.log(`${lc} tjp (${tjpAddr}) (I: 5245a2ec85a943f98479e93a32d67f22)`); }
            // here is where the implementation change starts. instead of using
            // the special ibgib, we will look for the subfolder that corresponds to the tjpGib.
            // if we get a list of the metastone addrs in the folder, we can
            // filter for the highest n and latest timestampInTicks.
            // * do we need to load any actual files to do more searching?
            // * ensure that tjpGib matches in ib?
            // * is this an implementation detail that should only be in descendant class
            // * pass in lambda to do filtering in the function?
            const metaStoneAddrs = await this.getMetaStoneAddrs({ tjpGib, ibGibAddr });
            if (metaStoneAddrs.length === 0) {
                if (logalot) {
                    console.warn(`${lc} no metastones found for given ibGibAddr (${ibGibAddr}). returning incoming ibGibAddr (W: a6a35cbbf40745e8a3e202226bb457ff)`);
                }
                return ibGibAddr; /* <<<< returns early */
            }
            // security-wise, a stronger implementation would verify each meta
            // stone intrinsically at the very least, as well as additional
            // checking once authorization/authentication/keystones are in use.
            // for now, we'll just take it for granted, as this provides the
            // same (lack of) security guarantees as previous implementation that
            // used the SpecialIbGibType.latest.
            const metaStoneAddrsAndIbInfos = metaStoneAddrs
                .map(xAddr => [xAddr, getIbAndGib({ ibGibAddr: xAddr }).ib])
                .map(([xAddr, metaStoneIb]) => [xAddr, parseMetaStoneIb({ ib: metaStoneIb })]);
            /**
             * sort mutates the actual array (shuffle sort I think), but
             * we're storing it in a different variable name to be more
             * explicit.
             */
            const metaStoneAddrsAndIbInfos_descending = metaStoneAddrsAndIbInfos.sort((a, b) => {
                const [_aAddr, aInfo] = a;
                const [_bAddr, bInfo] = b;
                // we want the highest n, with tiebreaker going to
                // timestampInTicks. if that's the same also, then what are we
                // quibbling about?
                if (aInfo.n > bInfo.n) {
                    return -1;
                }
                else if (aInfo.n < bInfo.n) {
                    return 1;
                }
                else if (aInfo.timestampInTicks > bInfo.timestampInTicks) {
                    return -1;
                }
                else if (aInfo.timestampInTicks < bInfo.timestampInTicks) {
                    return 1;
                }
                else {
                    // neither discriminator is different.
                    return 0;
                }
            });
            if (logalot) {
                console.log(`${lc} console.dir(metaStoneAddrsAndIbInfos_descending)... (I: 1bb5e7d994248e8b6559ca2863c72123)`);
                console.dir(metaStoneAddrsAndIbInfos_descending);
            }
            // since we sorted into a descending order, the latest is the very
            // first item.  and we have already guaranteed that there is at
            // least 1 in the array.
            if (metaStoneAddrsAndIbInfos_descending.length === 0) {
                throw new Error(`(UNEXPECTED) metaStoneAddrsAndIbInfos_descending is empty? earlier in the function there should have been a check. did getMetaStoneAddrs return a populated list but no stones were found? (E: 63c035ebed77741f2330c7a625823f23)`);
            }
            const [latestMetaStoneAddr, latestMetaStoneInfo] = metaStoneAddrsAndIbInfos_descending[0];
            if (logalot) {
                console.log(`${lc} [latestMetaStoneAddr, latestMetaStoneInfo]: ${[latestMetaStoneAddr, latestMetaStoneInfo]}  (I: 1307ca46b4abd62c7a30fe87c08fa923)`);
            }
            let latestMetaStoneIbGib;
            const getResult = await this.getFile({ addr: latestMetaStoneAddr, isDna: false, });
            if (getResult?.success && getResult.ibGib) {
                latestMetaStoneIbGib = getResult.ibGib;
                const errors = await validateCommonMetaStoneIbGib({ ibGib: latestMetaStoneIbGib }) ?? [];
                if (errors.length > 0) {
                    throw new Error(`invalid metastone (${latestMetaStoneAddr}) for tjpGib (${tjpGib}). errors: ${errors.join('|')} (E: 7fa7c6fa696d860cc7a9148861f96123)`);
                }
            }
            else {
                throw new Error(`(UNEXPECTED) we just found a metastone addr (${latestMetaStoneAddr}) that was read from the directory's contents, but we can't get the file? (E: 3b934b4adcd9d0efee7820f8d4012e23)`);
            }
            // latestMetaStoneIbGib.rel8ns ensured via validation at this point
            const latestMetaStoneRel8ns = latestMetaStoneIbGib.rel8ns;
            /**
             * this is accurate as the punctiliar address that corresponds to
             * the metastone.
             *
             * atow (11/2023) interface of rel8ns
             * [META_STONE_TARGET_REL8N_NAME]: IbGibAddr[];
             * [META_STONE_TARGET_TJP_REL8N_NAME]?: IbGibAddr[];
             */
            const latestAddr = latestMetaStoneRel8ns[META_STONE_TARGET_REL8N_NAME].at(0);
            if (!latestAddr) {
                throw new Error(`metastone passed validation but latestMetaStoneRel8ns[META_STONE_TARGET_REL8N_NAME].at(0) is empty? (E: c188ec0089ad86efe4d925c118599d23)`);
            }
            return latestAddr;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * Extremely crude implementation that just
     * saves the ibgibs alongside existing data.
     *
     * ## future
     *
     * * At the very least, this could be changed similar to dna to have
     *   its own folder.
     */
    async persistOptsAndResultIbGibs({ arg, result, }) {
        const lc = `${this.lc}[${this.persistOptsAndResultIbGibs.name}]`;
        if (logalot || this.data?.trace) {
            console.log(`${lc} doing arg?.data?.cmd: ${arg?.data?.cmd}, result?.data?.success: ${result?.data?.success}`);
        }
        const ibGibs = [arg, result ?? ROOT];
        let argPersist = await argy_({
            argData: {
                cmd: 'put',
                catchAllErrors: true,
                ibGibAddrs: ibGibs.map(x => getIbGibAddr({ ibGib: x })),
            },
        });
        argPersist.ibGibs = ibGibs;
        // this is a best effort storage, so we aren't using the result
        // in the future, we should incorporate what to do if this persistence
        // fails into the larger success requirements of spaces.
        const resPut = await this.putIbGibsImpl(argPersist);
        if (!resPut?.data) {
            throw new Error(`resPut.data falsy (E: f3115254240b8eff6cd82878a4a35a23)`);
        }
        if (!resPut.data.success || resPut.data.errors) {
            console.error(`${lc} Errors persisting arg & result: ${(resPut.data?.errors ?? ['unknown errs']).join('\n')}. (E: 65ef314a4f8e445d851dab5b290e9a03)`);
        }
    }
    // #endregion files related
    // #region ensure related
    async ensurePermissions() {
        const lc = `${this.lc}[${this.ensurePermissions.name}]`;
        const keyPermissionRequested = 'ibgib_permissionRequested_ensurePermissions';
        try {
            return true;
        }
        catch (error) {
            console.error(`${lc} Dont have permissions... error: ${extractErrorMsg(error)}`);
            // localStorage.removeItem(keyPermissionRequested);
            return false;
        }
    }
    /**
     * Ensure directories are created on filesystem before trying to read/write
     * paths.
     */
    async ensureAllDirsExist() {
        // does nothing in base impl
        // throw? i don't remember why i didn't have this throw...
        const lc = `${this.lc}[${this.ensureAllDirsExist.name}]`;
        console.warn(`${lc} not implemented in base class (W: c1b63ec036134db5960e0d3ea169d15b)`);
    }
    /**
     * we are storing tjp-related things under the tjp subpath. so it will be
     * atow (11/2023) something like .ibgib/ibgib/ibgibs/[tjpGib]/[ibGibAddr].json.
     * i'm thinking that i should put the metastones inside a sub folder, so,
     * .ibgib/ibgib/ibgibs/[tjpGib]/meta/[metaStoneAddr].json
     *
     * NOTE: does NOT ensure that the subpath exists.
     *
     * ## driving use case
     *
     * in here is where i will be putting the metastones. but I'm thinking I
     * should store all ibgibs in this way, if they have a tjpGib. in fact, i
     * may want to store the metastones underneath this tjpGib subpath.
     *
     * @returns full path of the (possibly non-existent) directory.
     */
    getTjpSubpath({ viaTargetAddr, viaTargetTjpGib, viaMetaStoneAddr, pathSeparator = DEFAULT_PATH_SEPARATOR, }) {
        const lc = `${this.lc}[${this.getTjpSubpath.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: c65dabcf47acf6d6881087b404861e23)`);
            }
            // validate
            if (!viaTargetAddr && !viaTargetTjpGib && !viaMetaStoneAddr) {
                throw new Error(`either viaTargetAddr/TargetTjpGib/metaStoneAddr required (E: a784c4f0369e6e38eafcdf3289c38523)`);
            }
            if (!this.data) {
                throw new Error(`(UNEXPECTED) this.data falsy? (E: 441ff1b038366a1c07a94cf128454923)`);
            }
            if (!pathSeparator) {
                console.warn(`${lc} pathSeparator falsy? using default: "${DEFAULT_PATH_SEPARATOR}" (W: 295d21ab226743dd83a721f78870ab6e)`);
                pathSeparator = DEFAULT_PATH_SEPARATOR;
            }
            // get the tjpGib depending on what is passed in
            if (!viaTargetTjpGib) {
                if (viaMetaStoneAddr) {
                    // get the tjpGib via the metastone
                    viaTargetTjpGib = parseMetaStoneIb({ ib: getIbAndGib({ ibGibAddr: viaMetaStoneAddr }).ib }).tjpGib;
                }
                else {
                    // get the tjpGib via the targetIbGibAddr
                    viaTargetTjpGib = getGibInfo({ ibGibAddr: viaTargetAddr }).tjpGib;
                }
            }
            if (!viaTargetTjpGib) {
                throw new Error(`targetTjpGib could not be gotten. are you sure this has a tjp? or is the ibGib a stone? (E: e507f1996aaa68a229a3158b3dbc5723)`);
            }
            // build the path
            const { baseSubPath, spaceSubPath, ibgibsSubPath } = this.data;
            const tjpFullSubpath = `${baseSubPath}${pathSeparator}${spaceSubPath}${pathSeparator}${ibgibsSubPath}${pathSeparator}${viaTargetTjpGib}`;
            // return it
            return tjpFullSubpath;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    // #endregion metastone related
    // #region other helpers
    /**
     * builds the path of a given file, based on params.
     *
     * ## implementation change (11/2023)
     *
     * i'm changing the implementation from a naive `subpath/ib^gib.json` approach
     * to a slightly more sophisticated one. we will change the path depending on
     * the address:
     *
     * 1. if the addr has no tjp delimiter, path will be:
     *    * `(bin|dna)/[gib]/ib.json` for "binaries" and dna
     *    * `[gib]/ib.json` for all others
     * 2. if the addr has a tjp delimiter, path will be:
     *    * `[tjpGib]/[punctiliarHash]/ib.json` for all others
     *    * it would be nicer to have a "timelines" folder but
     *
     * _NOTE: all dna and bins are stones (and have no tjpGib)._
     * _NOTE: bins may have a different extension than "json"._
     * _REFRESHER: punctiliarHash is the hash of a specific ibGib in time (punctiliar === point in time)._
     *
     * some benefits:
     *
     * 1. With this new structure, we can now conceivably create metastones
     *    targeted at multiple levels:
     *    * metastones that apply to entire timeline (tjpGib)
     *    * metastones that apply to punctiliar ibGibs (punctiliarHash)
     *    * metastones that apply to non-timeline stones (gib)
     *    * NOTE: we atow (11/2023) are only creating metastones for
     *      tracking latest punctiliar ibGibs in timelines.
     * 2. separation between meta and regular ibgibs was slightly annoying &
     *    useless, as the distinction is not as clear cut as the metastone one,
     *    so condensing.
     *
     * @returns the path of a given file, based on params
     */
    async buildPath({ addr, isDna, isBin, ensureMetaStonePaths, pathSeparator = '/', pretendItsALongPath = false, addrIsForAMetaStone, }) {
        const lc = `${this.lc}[${this.buildPath.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: d630823bbe0e1a1aa3592103768e8423)`);
            }
            const data = this.data;
            if (!data) {
                throw new Error(`this.data required (E: d2553d11b637577aa7b074387b613f23)`);
            }
            if (!addr) {
                throw new Error(`addr required (E: 68149eccdbe2e6ef95eb50d588d68423)`);
            }
            let resPath;
            pathSeparator ||= '/';
            let { ib, gib } = getIbAndGib({ ibGibAddr: addr });
            if (!gib) {
                throw new Error(`gib required in building paths. if it's a primitive ibgib, then it shouldn't be put into/gotten from a space (E: 8a34bc925496c35d1892638623ec0a23)`);
            }
            // we always start from the root dir of the space subpath
            resPath = `${data.baseSubPath}${pathSeparator}${data.spaceSubPath}${pathSeparator}`;
            if (logalot) {
                console.log(`${lc} resPath as of space subpath: ${resPath} (I: 5feccfadec1d8ac856243e78377c1e23)`);
            }
            /**
             * store the result of isMetaStone just to keep from having to
             * call it more than once.
             */
            const addrIsAMetaStone = isMetaStone({ addr });
            // if the caller did not specify that the addr is for a metastone,
            // we still set this variable if the addr itself is a metastone.
            if (addrIsAMetaStone) {
                addrIsForAMetaStone = true;
            }
            // by looking at gib, determine if...
            // 1. stone (no tjp)
            // 2. tjp (gib is tjpGib b/c start of timeline)
            // 3. ibgib belongs to timeline (has tjpGib and punctiliarHash)
            const gibPieces = gib.split(GIB_DELIMITER);
            if (gibPieces.length === 1) {
                // either tjp, metastone, or other stone (e.g. dna).
                // first prefix subpath is point if dna and bin files
                if (isDna) {
                    resPath += `${data.dnaSubPath}${pathSeparator}`;
                    if (logalot) {
                        console.log(`${lc} resPath dna appended subpath: ${resPath} (I: eecd26dfc1699110ae5b91dd68438f23)`);
                    }
                }
                else if (isBin) {
                    resPath += `${data.binSubPath}${pathSeparator}`;
                    if (logalot) {
                        console.log(`${lc} resPath bin appended subpath: ${resPath} (I: b53248d5b8d142108cf5a363f148980b)`);
                    }
                }
                if (addrIsForAMetaStone || addrIsAMetaStone) {
                    // we want to store the metastone in the same dir as the
                    // timeline, not in the metastone's own gib dir
                    let targetTjpGib;
                    if (addrIsAMetaStone) {
                        targetTjpGib = parseMetaStoneIb({ ib }).tjpGib;
                    }
                    else {
                        const gibInfo = getGibInfo({ ibGibAddr: addr });
                        targetTjpGib = gibInfo.tjpGib ?? getIbAndGib({ ibGibAddr: addr }).gib;
                    }
                    resPath += `${targetTjpGib}${pathSeparator}`;
                }
                else {
                    // at this point, stones and tjps are treated the same. for
                    // tjp's, their gib _is_ the tjpGib
                    resPath += `${gib}${pathSeparator}`;
                }
            }
            else if (gibPieces.length === 2) {
                // belongs to a timeline
                let { tjpGib, punctiliarHash } = getGibInfo({ gib, gibDelimiter: GIB_DELIMITER });
                resPath += `${tjpGib}${pathSeparator}${punctiliarHash}${pathSeparator}`;
            }
            else {
                throw new Error(`invalid gib atow (11/2023). expected only one gib delimiter (${GIB_DELIMITER}) that splits two pieces of the gib, but we have ${gibPieces.length} many pieces. gib: ${gib} (E: 7d5f8608d79854e6d1d2878975258423)`);
            }
            // at this point, we have a full path to a directory. if this space
            // is configured to mitigate long paths and if the path is already
            // too long, then i'm not sure what to do. if it's not too long, we
            // need to check to see if it will be when we add the filename.
            const mitigateLongPaths = !!data.mitigateLongPaths;
            const longPathLength = data.longPathLength;
            /**
             * we tweak the extension for binaries (i.e. pictures like .jpg, .png,
             * etc.) in order to maintain the extension in the os's filesystem. (in
             * a future full-ibgib-os, this will be a non-issue).
             */
            let ext;
            if (isBinary({ addr })) {
                const { binExt } = getBinHashAndExt({ addr });
                ext = binExt.concat();
            }
            else {
                ext = 'json';
            }
            /**
             * filename+ext using the `ib`.
             * NOTE: if we're mitigating long paths, then we may not use this.
             */
            let filenameAsIbPlusExt = `${ib}.${ext}`;
            /**
             * filename+ext using the entire `addr`.
             *
             * this is used when doing meta stones (and I may change dna to use
             * this also, but if so then i need to change the path in previous
             * code).
             *
             * NOTE: if we're mitigating long paths, then we may not use this.
             */
            let filenameAsAddrPlusExt = `${addr}.${ext}`;
            if (mitigateLongPaths && longPathLength) {
                // have to take long path lengths into account
                const ameliorateLongPathMsg = ` you have to place the root folder higher up in the OS's filesystem. (and you should put in a change request to the OS to fix their weak sauce/move to/fund a better OS.) Will try what we have and hope+pray.`;
                if (resPath.length >= longPathLength) {
                    throw new Error(`path is already too long without even having a filename. ${ameliorateLongPathMsg} (E: c126522a5cda4c2acb6de3a92fc2c423)`);
                }
                else if ((resPath.length + ib.length) >= longPathLength || pretendItsALongPath) {
                    if (logalot) {
                        console.warn(`${lc} the path is too long when adding the full ib as the filename (even without the ext). (W: 3011a1e58641c53274d4776f58bb5323)`);
                    }
                    if (addrIsForAMetaStone) {
                        if (addrIsAMetaStone) {
                            console.error(`can't even get a metastone ib in the path. ${ameliorateLongPathMsg} (E: 962c52deb7a5b57842c97676a1192623)`);
                        }
                        resPath += filenameAsAddrPlusExt;
                    }
                    else {
                        if (logalot) {
                            console.log(`${lc} non metastone ib found (not logging ib here for privacy). will try to shorten... (I: 73dcb3f019c9b48b92fe67a9c4f2f123)`);
                        }
                        const shorterFilenamePlusExt = await this.buildPath_getShorterFilenameExtSubpath_ibTooLong_nonMetaStone({
                            ib, ext, pathSeparator,
                        });
                        resPath += shorterFilenamePlusExt;
                        if (resPath.length >= longPathLength) {
                            console.error(`${lc} shortened filenamePlusExt and the path is still too long. i give up. ${ameliorateLongPathMsg} (E: df9e52098b94a99212c15c167e4d9f23)`);
                        }
                    }
                }
                else {
                    // the path + full ib/addr+ext is ok
                    if (addrIsForAMetaStone) {
                        resPath += filenameAsAddrPlusExt;
                    }
                    else {
                        resPath += filenameAsIbPlusExt;
                    }
                }
            }
            else {
                // don't need to worry about mitigating path lengths
                // the path + full ib/addr+ext is ok
                if (addrIsForAMetaStone) {
                    resPath += filenameAsAddrPlusExt;
                }
                else {
                    resPath += filenameAsIbPlusExt;
                }
            }
            if (logalot) {
                console.log(`${lc} returning resPath: ${resPath} (I: 62a76a630335a872a4fc5f25dec4b623)`);
            }
            return resPath;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * if we're here, then the path is too long with the ib.
     * we need to do a couple things:
     *
     * 1. make this deterministic
     * 2. indicate to others that may be traversing raw file system ibgib files
     *    (like this one) that it has been adjusted to a long path.
     * 3. not make it too convoluted so that those same traversers can do so in
     *    a reasonably straight-forward manner.
     *
     * With this given, my first implementation (atow 11/2023) will...
     *
     * 1. subpath to a "long" subpath (which should be a single character plus a
     *    path delimiter e.g. slash).
     *   * adds 1 characters + 1 slash = 2
     * 2. hash the ib and shard subpath to the first 4 chars.
     *   * adds 4 characters + 1 slash = 5
     * 3. substring the ib to the first N characters
     *   * atow is N = 32
     *   * adds N characters + 1 dot + ext.length ~=~ N+5-ish chars. (37ish)
     *
     * This will add a total of 44-ish chars, which is less than what a
     * metastone path would be. I say this, because atow (11/2023) the code
     * execution has already checked that a metastone would be valid. So we're
     * just trying to exclude very long ib's that have e.g. 100+ characters.
     */
    async buildPath_getShorterFilenameExtSubpath_ibTooLong_nonMetaStone({ ib, ext, pathSeparator, }) {
        const lc = `${this.lc}[${this.buildPath_getShorterFilenameExtSubpath_ibTooLong_nonMetaStone.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 1f14c5de4174d726182c5dc97bd62a23)`);
            }
            let resAdjustedFilenameExtSubpath = '';
            //  * 1. subpath to a "long" subpath (which should be a single character plus a
            //  *    path delimiter e.g. slash).
            //  *   * adds 1 characters + 1 slash = 2
            let longSubPath = this.data.longSubPath || IBGIB_LONG_SUBPATH;
            if (longSubPath.length !== 1) {
                console.warn(`${lc} longSubPath (this.data.longSubPath || IBGIB_LONG_SUBPATH) longer than one char. should be 1 char. substringing to the first char. (W: 8c19a983730f4b62ac4208f7d21c90fc)`);
                longSubPath = longSubPath.substring(0, 1) || 'l'; // l for long, get it
            }
            resAdjustedFilenameExtSubpath += `${longSubPath}${pathSeparator}`;
            //  * 2. hash the ib and shard subpath to the first 4 chars.
            //  *   * adds 4 characters + 1 slash = 5
            const ibHash = await hash({ s: ib });
            const firstFourOfIbHash = ibHash.substring(0, 4);
            resAdjustedFilenameExtSubpath += `${firstFourOfIbHash}${pathSeparator}`;
            //  * 3. substring the ib to the first N characters and add ext
            //  *   * atow is N = 32
            //  *   * adds N characters + 1 dot + ext.length ~=~ N+5-ish chars. (37ish)
            const ibSubstring = ib.substring(0, ARBITRARY_IB_SUBSTRING_LENGTH_FOR_MITIGATE_LONG_PATH);
            resAdjustedFilenameExtSubpath += `${ibSubstring}.${ext}`;
            return resAdjustedFilenameExtSubpath;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
}
//# sourceMappingURL=filesystem-space-v1.mjs.map