import { extractErrorMsg, hash, HashAlgorithm, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
import { validateIbGibIntrinsically } from "@ibgib/ts-gib/dist/V1/validate-helper.mjs";
import { mut8 } from "@ibgib/ts-gib/dist/V1/transforms/mut8.mjs";
import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
import { getGib } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
import { KEYSTONE_ATOM, KEYSTONE_HASH_MAX_ROUNDS, KEYSTONE_POOL_ID_REGEXP, KEYSTONE_SALT_REGEXP, KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP } from "./keystone-constants.mjs";
import { KeystoneReplenishStrategy, KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES, KeystoneChallengeType, KEYSTONE_CHALLENGE_TYPE_VALID_VALUES, } from "./keystone-types.mjs";
import { KeystoneStrategyFactory } from "./strategy/keystone-strategy-factory.mjs";
import { getDependencyGraph } from "../common/other/graph-helper.mjs";
import { getIbGibsFromCache_fallbackToSpaces, getTimelinesGroupedByTjp, splitPerTjpAndOrDna } from "../common/other/ibgib-helper.mjs";
import { getLatestAddrs } from "../witness/space/space-helper.mjs";
const logalot = GLOBAL_LOG_A_LOT;
/**
 * space-delimited keystone ib containing select keystone metadata.
 *
 * NOTE: This must match {@link parseKeystoneIb}
 * @see {@link KeystoneIb_V1}
 */
export async function getKeystoneIb({ keystoneData, }) {
    const lc = `[${getKeystoneIb.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: c3022a146faac9730154f34d1439a225)`);
        }
        const atom = KEYSTONE_ATOM;
        const ib = [
            atom,
        ].join(' ');
        return ib;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * NOTE: This must match {@link getKeystoneIb}
 * @see {@link KeystoneIb_V1}
 */
export async function parseKeystoneIb({ ib, }) {
    const lc = `[${parseKeystoneIb.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 73cb6832984255ed48b2f44db6a21e25)`);
        }
        const [atom] = ib.split(' ');
        if (atom !== KEYSTONE_ATOM) {
            throw new Error(`invalid keystone ib. atom found in ib (${atom}) does not match keystone atom (${KEYSTONE_ATOM}) (E: 79b3d587824c4271b6e60acc76e0c825)`);
        }
        return {
            atom,
        };
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * The Policy Engine.
 * Calculates exactly which challenges MUST be consumed based on config and demands.
 * Enforces STRICT DISTINCTNESS (No double-dipping).
 */
export async function getDeterministicRequirements({ pool, requiredChallengeIds, targetAddr }) {
    const behavior = pool.config.behavior;
    const mandatory = new Set();
    // Start with all available IDs.
    // We assume Object.keys respects insertion order (ES2015+), crucial for FIFO.
    let available = Object.keys(pool.challenges);
    // ---------------------------------------------------------
    // 1. Alice's Demands (Explicit Requirements)
    // ---------------------------------------------------------
    if (requiredChallengeIds && requiredChallengeIds.length > 0) {
        for (const id of requiredChallengeIds) {
            if (!pool.challenges[id]) {
                throw new Error(`(UNEXPECTED) Required challenge ID not found in pool: ${id} (E: 10c5619d80d8cf6cc84ffbf8be0dbc25)`);
            }
            // Strict: Consume it.
            if (!available.includes(id)) {
                // Should be caught by check above, but handles duplicates in 'demands'
                continue;
            }
            mandatory.add(id);
        }
        // Remove from available pool
        available = available.filter(id => !mandatory.has(id));
    }
    // ---------------------------------------------------------
    // 2. Target Binding (Drawing Without Replacement)
    // ---------------------------------------------------------
    if (behavior.targetBindingCount > 0 && targetAddr) {
        // Sort remaining available IDs alphabetically to ensure stable ordering across platforms
        let sortedAvailable = [...available].sort();
        // Start hashing path from targetAddr
        let currentHash = await hash({ s: targetAddr });
        for (let i = 0; i < behavior.targetBindingCount; i++) {
            if (sortedAvailable.length === 0) {
                throw new Error(`Entropy Exhaustion. Cannot satisfy target binding. Sorted available pool is empty. (E: 341b95dc3a58be3e083d1d9c4a0c4925)`);
            }
            // Parse the entire hash via BigInt to eliminate modulo bias
            const hashBigInt = BigInt("0x" + currentHash);
            const index = Number(hashBigInt % BigInt(sortedAvailable.length));
            const selectedId = sortedAvailable[index];
            // Consume selected challenge ID
            mandatory.add(selectedId);
            sortedAvailable.splice(index, 1);
            // Filter out of insertion-ordered available list (for FIFO)
            available = available.filter(id => id !== selectedId);
            // Hash recursively for the next draw
            currentHash = await hash({ s: currentHash });
        }
    }
    // ---------------------------------------------------------
    // 3. FIFO (Sequential)
    // ---------------------------------------------------------
    if (behavior.selectSequentially > 0) {
        // Take the first N from the remaining available list
        if (available.length < behavior.selectSequentially) {
            console.error(`[getDeterministicRequirements] Entropy Exhaustion! AvailableCount: ${available.length}, Required Seq: ${behavior.selectSequentially}. Available IDs: ${available.join(',')}`);
            throw new Error(`Entropy Exhaustion. Insufficient challenges for FIFO requirement. (E: 9efa88003e1d5eccb837c85154ee5625)`);
        }
        const fifoIds = available.slice(0, behavior.selectSequentially);
        fifoIds.forEach(id => mandatory.add(id));
        // Remove from available
        available = available.slice(behavior.selectSequentially);
    }
    return { mandatoryIds: mandatory, availableIds: available };
}
/**
 * Selects the specific pool to use for an operation based on ID, filter criteria, or verb authorization.
 *
 * @returns The matching KeystoneChallengePool
 * @throws If no pool matches or if multiple pools match but one was expected.
 */
export function resolveTargetPool({ pools, poolId, poolFilter, verb, }) {
    const lc = `[resolveTargetPool]`;
    try {
        let pool;
        if (poolId) {
            // 1. Explicit ID Strategy
            pool = pools.find(p => p.id === poolId);
            if (!pool) {
                throw new Error(`Pool not found with ID: ${poolId} (E: 4a2b17428515c1e82813158581898125)`);
            }
        }
        else if (poolFilter) {
            // 2. Filter Strategy
            const matches = pools.filter(poolFilter);
            if (matches.length === 0) {
                throw new Error(`No pool matched the provided filter. (E: 5b3c27428515c1e82813158581898125)`);
            }
            // For now, we take the first match. In future, might want to be strict about uniqueness.
            pool = matches[0];
        }
        else {
            // 3. Auto-Resolution by Verb Strategy
            if (!verb) {
                throw new Error(`Cannot auto-resolve pool without a verb. (E: 6c4d37428515c1e82813158581898125)`);
            }
            // Priority A: Look for Specific Match (Pool explicitly lists this verb)
            pool = pools.find(p => p.config.allowedVerbs && p.config.allowedVerbs.includes(verb));
            // Priority B: Look for General/Default (No restrictions / Wildcard)
            if (!pool) {
                pool = pools.find(p => !p.config.allowedVerbs || p.config.allowedVerbs.length === 0);
            }
            if (!pool) {
                throw new Error(`No suitable pool found for verb: ${verb} (E: 7d5e47428515c1e82813158581898125)`);
            }
        }
        // 4. Authorization Check (Applies to all strategies if verb is present)
        if (verb && pool.config.allowedVerbs && pool.config.allowedVerbs.length > 0) {
            if (!pool.config.allowedVerbs.includes(verb)) {
                throw new Error(`Pool ${pool.id} is not authorized for verb: ${verb} (E: 8e6f57428515c1e82813158581898125)`);
            }
        }
        return pool;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
/**
 * Calculates the complete list of Challenge IDs to solve for a given operation.
 * Combines Deterministic requirements (Mandatory/Binding/FIFO) with Stochastic requirements.
 *
 * @returns Array of unique challenge IDs.
 */
export async function selectChallengeIds({ pool, targetAddr, requiredChallengeIds, }) {
    const lc = `[selectChallengeIds]`;
    try {
        // 1. Get Deterministic Requirements
        const { mandatoryIds, availableIds } = await getDeterministicRequirements({
            pool,
            requiredChallengeIds,
            targetAddr
        });
        // 2. Stochastic Selection
        const randomCount = pool.config.behavior.selectRandomly;
        const randomIds = [];
        if (randomCount > 0) {
            if (availableIds.length < randomCount) {
                throw new Error(`Insufficient challenges for random requirement. Need ${randomCount}, have ${availableIds.length} (E: 9f7a67428515c1e82813158581898125)`);
            }
            // Shuffle & Pick
            // Note: simple Math.random sort is sufficient for V1 stochastic selection
            // as we are just picking from valid available options.
            const shuffled = [...availableIds].sort(() => 0.5 - Math.random());
            randomIds.push(...shuffled.slice(0, randomCount));
        }
        // 3. Combine
        return [...mandatoryIds, ...randomIds];
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
/**
 * Generates an opaque, collision-resistant ID for a challenge.
 * atow (2025/12/22) - Hash(Salt + Timestamp + Index)
 */
export async function generateOpaqueChallengeId({ salt, timestamp, index }) {
    // Use first 16 chars of hex (64 bits) for brevity + safety.
    const raw = await hash({ s: `${salt}${timestamp}${index}` });
    return raw.substring(0, 16);
}
/**
 * Calculates the NEXT state of the Challenge Pools given a specific consumption event.
 * Handles TopUp, ReplaceAll, Consume, and ScorchedEarth strategies.
 *
 * @returns The new array of KeystoneChallengePools (including the modified one).
 */
export async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds, masterSecret, strategy, config, }) {
    const lc = `[applyReplenishmentStrategy]`;
    try {
        const newPools = JSON.parse(JSON.stringify(prevPools));
        const targetIdx = newPools.findIndex((p) => p.id === targetPoolId);
        if (targetIdx === -1) {
            throw new Error(`Target pool ${targetPoolId} not found in keystone data. (E: 75200388d22744838634524233772545)`);
        }
        const pool = newPools[targetIdx];
        const poolSecret = await strategy.derivePoolSecret({ masterSecret });
        const timestamp = Date.now().toString();
        const strategyType = config.behavior.replenish;
        if (strategyType === KeystoneReplenishStrategy.topUp) {
            // Remove consumed
            consumedIds.forEach(id => delete pool.challenges[id]);
            // Add New
            for (let i = 0; i < consumedIds.length; i++) {
                const newId = await generateOpaqueChallengeId({
                    salt: config.salt, timestamp, index: i
                });
                const solution = await strategy.generateSolution({
                    poolSecret, poolId: pool.id, challengeId: newId
                });
                pool.challenges[newId] = await strategy.generateChallenge({ solution });
            }
        }
        else if (strategyType === KeystoneReplenishStrategy.replaceAll) {
            pool.challenges = {};
            for (let i = 0; i < config.behavior.size; i++) {
                const newId = await generateOpaqueChallengeId({
                    salt: config.salt, timestamp, index: i
                });
                const solution = await strategy.generateSolution({
                    poolSecret, poolId: pool.id, challengeId: newId
                });
                pool.challenges[newId] = await strategy.generateChallenge({ solution });
            }
        }
        else if (strategyType === KeystoneReplenishStrategy.consume) {
            consumedIds.forEach(id => delete pool.challenges[id]);
        }
        else if (strategyType === KeystoneReplenishStrategy.deleteAll) {
            pool.challenges = {};
        }
        else {
            throw new Error(`Unknown replenish strategy: ${strategyType}. Valid list: ${pretty(KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES)} (E: 0acf56f1e1486240080e11e8046d0825)`);
        }
        return newPools;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
/**
 * High-level orchestration helper.
 * 1. Instantiates Strategy.
 * 2. Derives Secret.
 * 3. Solves Challenges (Generates Solutions).
 * 4. Replenishes Pool (Calculates the next state of ALL pools).
 * 5. Constructs Proof.
 *
 * @returns The resulting Proof and the full list of Next Pools.
 */
export async function solveAndReplenish({ targetPoolId, prevPools, masterSecret, challengeIds, claim, requiredChallengeIds, }) {
    const lc = `[solveAndReplenish]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... poolId: ${targetPoolId}, ids: ${challengeIds.length}`);
        }
        const pool = prevPools.find(p => p.id === targetPoolId);
        if (!pool) {
            throw new Error(`Target pool not found: ${targetPoolId} (E: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6)`);
        }
        const strategy = KeystoneStrategyFactory.create({ config: pool.config });
        const poolSecret = await strategy.derivePoolSecret({ masterSecret });
        const solutions = [];
        // 1. Solve
        for (const id of challengeIds) {
            const solution = await strategy.generateSolution({
                poolSecret, poolId: pool.id, challengeId: id,
            });
            solutions.push(solution);
        }
        // 2. Construct Proof
        const proof = {
            claim,
            solutions,
            requiredChallengeIds: (requiredChallengeIds && requiredChallengeIds.length > 0) ? requiredChallengeIds : undefined
        };
        // 3. Replenish
        const nextPools = await applyReplenishmentStrategy({
            prevPools,
            targetPoolId: pool.id,
            consumedIds: challengeIds,
            masterSecret,
            strategy,
            config: pool.config
        });
        return { proof, nextPools };
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
export async function validateChallengePool_typeHashRevealV1({ pool }) {
    const lc = `[${validateChallengePool_typeHashRevealV1.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: e2ffd568b698cc12d8c5e838d73ae726)`);
        }
        if (!pool.config) {
            throw new Error(`(UNEXPECTED) pool.configy falsy? (E: fcde88fe20e8004a58d7e445436bf526)`);
        }
        if (pool.config.type !== KeystoneChallengeType.hash_reveal_v1) {
            throw new Error(`(UNEXPECTED) pool.config.type !== KeystoneChallengeType.hash_reveal_v1? (E: e205f8872909c37168a624a9df778c26)`);
        }
        const errors = [];
        const { algo, rounds, salt, } = pool.config;
        const validAlgos = [HashAlgorithm.sha_256, HashAlgorithm.sha_512];
        if (algo) {
            if (!validAlgos.includes(algo)) {
                errors.push(`${lc} invalid hash algorithm (${algo}). Must be one of ${validAlgos}. (E: a22399ca3a68e63ffcc7507699be5826)`);
            }
        }
        else {
            errors.push(`${lc} algorithm required. (E: 8cd6bfb0590862b9bae24d98d2cf3826)`);
        }
        if (rounds && typeof rounds === 'number' && Number.isInteger(rounds)) {
            if (rounds > KEYSTONE_HASH_MAX_ROUNDS) {
                errors.push(`${lc} rounds too high. max rounds: ${KEYSTONE_HASH_MAX_ROUNDS} (E: 31ce082f7fd88d6b58ef08a816458826)`);
            }
        }
        else {
            errors.push(`${lc} invalid rounds (${rounds}). must be positive integer. (E: 73c643ccda78afaf28d52a6871a9e826)`);
        }
        if (salt && typeof salt === 'string') {
            // no limits on this? sanity check?
            if (!KEYSTONE_SALT_REGEXP.test(salt)) {
                errors.push(`${lc} invalid salt (${salt.substring(0, 99)}). Must match regexp ${KEYSTONE_SALT_REGEXP}`);
            }
        }
        else {
            errors.push(`${lc} invalid salt (${salt}). must be truthy string value. (E: 564a4844ec88fc1988197ce89c381426)`);
        }
        return errors;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function validateChallengePool({ pool }) {
    const lc = `[${validateChallengePool.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 1a0eafc158a83d1ba8fc019d5c979a26)`);
        }
        const errors = [];
        if (pool.config) {
            // first validate common to all pools, regardless of type...
            if (pool.config.id) {
                if (!KEYSTONE_POOL_ID_REGEXP.test(pool.config.id)) {
                    // not 100% that this is needed but hey
                    errors.push(`${lc} pool.config.id is not formatted correctly. must pass regexp: ${KEYSTONE_POOL_ID_REGEXP.source}`);
                }
            }
            else {
                errors.push(`${lc} pool.config.id falsy (E: 31d7943d95f877326d5f4ea14463d626)`);
            }
            if (pool.config.behavior) {
                const { size, selectSequentially, selectRandomly, targetBindingCount } = pool.config.behavior;
                if (!size || size === 0) {
                    errors.push(`${lc} invalid pool.config.behavior.size (${size}). Must be positive integer. (E: b221e36ec102bdc944552248ce8fe626)`);
                }
                const totalRequested = (selectSequentially || 0) + (selectRandomly || 0) + (targetBindingCount || 0);
                if (totalRequested >= size) {
                    errors.push(`${lc} Total requested challenges (${totalRequested}) cannot equal or exceed pool size (${size}) to prevent full pool exposure and selection exhaustion. (E: 81cb834f826315264bca81c15fca58f1)`);
                }
                // todo: more behavior checks
            }
            else {
                errors.push(`${lc} pool.config.behavior falsy (E: bede081c066c39732eefe2f92e296326)`);
            }
            // ...then do type-specific validation
            const poolType = pool.config.type;
            switch (poolType) {
                case KeystoneChallengeType.hash_reveal_v1:
                    const errorsHashRevealV1 = await validateChallengePool_typeHashRevealV1({ pool });
                    errorsHashRevealV1.forEach(x => errors.push(x));
                    break;
                default:
                    throw new Error(`(UNEXPECTED) unknown pool.config.type (${poolType})? We only implement the following at this time: ${KEYSTONE_CHALLENGE_TYPE_VALID_VALUES} (E: cce5285cf7580b8bc82172488eba2826)`);
            }
        }
        else {
            errors.push(`${lc} pool.config falsy. (E: 8d05c875a1d2c58018a5eaf803977d26)`);
        }
        return errors;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export function validateKeystoneMetadata({ data }) {
    const lc = `[validateKeystoneMetadata]`;
    const errors = [];
    const checkDetails = (details, source) => {
        if (!details)
            return;
        if (details.username !== undefined) {
            if (typeof details.username !== 'string' || !KEYSTONE_USERNAME_REGEXP.test(details.username)) {
                errors.push(`${lc} invalid username in ${source} (${details.username}). Must match ${KEYSTONE_USERNAME_REGEXP}`);
            }
        }
        if (details.description !== undefined) {
            if (typeof details.description !== 'string' || !KEYSTONE_DESCRIPTION_REGEXP.test(details.description)) {
                errors.push(`${lc} invalid description in ${source} (${details.description}). Must match ${KEYSTONE_DESCRIPTION_REGEXP}`);
            }
        }
    };
    checkDetails(data.frameDetails, 'frameDetails');
    checkDetails(data.checkpointDetails, 'checkpointDetails');
    return errors;
}
export async function validateGenesisKeystone({ keystoneIbGib }) {
    const lc = `[${validateGenesisKeystone.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: ec6ba82ec848b5ac3d8953e89d8d9826)`);
        }
        const errors = [];
        const { data, rel8ns } = keystoneIbGib;
        // Validate username/description metadata if present
        const metadataErrors = validateKeystoneMetadata({ data });
        metadataErrors.forEach(x => errors.push(x));
        // no proofs/solutions yet
        if (data.proofs && data.proofs.length > 0) {
            errors.push(`${lc} proofs already exist on genesis keystone. (E: 7a5e15f20918f1bbd8ffb62857dcd526)`);
        }
        for (const pool of data.challengePools) {
            // validate each pool intrinsically (config, sanity)
            const poolErrors = await validateChallengePool({ pool });
            poolErrors.forEach(x => errors.push(x));
            //  additionally ensure each pool has challenges. This is only on
            //  this genesis keystone, as it is valid to have pool without
            //  challenges after revocation
            if (!pool.challenges || Object.keys(pool.challenges).length === 0) {
                errors.push(`${lc} invalid pool (${pool.id}). pool.challenges is falsy/empty. (E: 8cd16a5416ae62a058342828be465b26)`);
            }
        }
        // not stated as revoked already
        if (!!data.revocationInfo) {
            errors.push(`${lc} genesis keystone already has revocationInfo. (E: e11408d0c558d0fa948efbce611ec826)`);
        }
        return errors;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Validates the transition from Prev -> Curr.
 * Enforces Cryptography AND Behavioral Policy.
 *
 * @returns Array of validation error strings. Empty array means Valid.
 */
export async function validateKeystoneTransition({ currentIbGib, prevIbGib, }) {
    const lc = `[${validateKeystoneTransition.name}]`;
    const errors = [];
    try {
        if (!currentIbGib) {
            throw new Error(`(UNEXPECTED) currentIbGib falsy? (E: 3c0f02655fa8279e386a079ebb604b25)`);
        }
        if (!prevIbGib) {
            throw new Error(`(UNEXPECTED) prevIbGib falsy? (E: 0d07c812634d839c784f31b8848ba825)`);
        }
        // intrinsic validation
        const validationErrors = await validateIbGibIntrinsically({ ibGib: currentIbGib });
        if (validationErrors && validationErrors.length > 0) {
            errors.push(...validationErrors);
        }
        const currData = currentIbGib.data;
        const prevData = prevIbGib.data;
        // Validate username/description metadata if present
        const metadataErrors = validateKeystoneMetadata({ data: currData });
        metadataErrors.forEach(x => errors.push(x));
        // must be sequential data.n values
        // note: "-2" because we don't want a "valid" sequential data.n if we
        // have an invalid prev and a valid currDataN of 0
        const currDataN = currData.n ?? -2;
        const prevDataN = prevData.n ?? -2;
        if (currDataN < 0) {
            errors.push('invalid keystone.currData.n is undefined or null.');
        }
        if (prevDataN < 0) {
            errors.push('invalid keystone.prevData.n is undefined or null.');
        }
        ;
        if (currDataN !== (prevDataN + 1)) {
            errors.push(`keystone data.n values are not sequential. prevData.n: ${prevDataN}, currData.n: ${currDataN}. `);
        }
        for (const proof of currData.proofs) {
            if (proof.solutions.length === 0) {
                errors.push(`Proof ${proof.id || 'unknown'} has no solutions.`);
                continue;
            }
            const poolId = proof.solutions[0].poolId;
            // Standard Verification (Internal Pools Only)
            // The pool MUST be present in the previous frame.
            const pool = prevData.challengePools.find(p => p.id === poolId);
            if (!pool) {
                errors.push(`Proof references unknown pool: ${poolId}`);
                continue;
            }
            await verifyProofAgainstPool({ proof, pool, errors });
        } // End proof loop
        // Revocation Logic checks
        if (currData.revocationInfo) {
            const target = currData.revocationInfo.proof.claim.target;
            const expectedTarget = getIbGibAddr({ ibGib: prevIbGib });
            if (target !== expectedTarget) {
                errors.push(`Revocation target mismatch. Expected ${expectedTarget}, got ${target}`);
            }
        }
        return errors;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error; // System errors still throw
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Helper to verify a single proof against a specific pool.
 */
export async function verifyProofAgainstPool({ proof, pool, errors, }) {
    const lc = `[${verifyProofAgainstPool.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: b8f9b6085888eea2258bf579ecd5e825)`);
        }
        // 0. VERB AUTH
        if (pool.config.allowedVerbs && pool.config.allowedVerbs.length > 0) {
            if (!proof.claim.verb || !pool.config.allowedVerbs.includes(proof.claim.verb)) {
                errors.push(`Policy Violation: Pool ${pool.id} used for unauthorized verb ${proof.claim.verb}`);
            }
        }
        // 1. Reconstruct Deterministic Requirements
        const { mandatoryIds, availableIds } = await getDeterministicRequirements({
            pool,
            requiredChallengeIds: proof.requiredChallengeIds,
            targetAddr: proof.claim.target // Not used extensively in V1 logic yet, mainly for logging/context
        });
        // 2. Check Mandatory
        const proofIds = new Set(proof.solutions.map(s => s.challengeId));
        for (const id of mandatoryIds) {
            if (!proofIds.has(id)) {
                errors.push(`Policy Violation: Missing mandatory challenge ${id}`);
            }
        }
        // 3. Stochastic
        const randomCandidates = [...proofIds].filter(id => !mandatoryIds.has(id));
        const requiredRandomCount = pool.config.behavior.selectRandomly;
        if (randomCandidates.length < requiredRandomCount) {
            errors.push(`Policy Violation: Insufficient random count. Need ${requiredRandomCount}, got ${randomCandidates.length}`);
        }
        // 4. Validity (Double Dip / Existence)
        for (const id of randomCandidates) {
            if (!availableIds.includes(id)) {
                errors.push(`Policy Violation: ID ${id} is invalid or double-dipped.`);
            }
        }
        // 5. Crypto
        const strategy = KeystoneStrategyFactory.create({ config: pool.config });
        for (const solution of proof.solutions) {
            const challenge = pool.challenges[solution.challengeId];
            if (!challenge) {
                errors.push(`Crypto Violation: Challenge ${solution.challengeId} not found in pool.`);
            }
            else {
                const isValid = await strategy.validateSolution({ solution, challenge });
                if (!isValid) {
                    errors.push(`Crypto Violation: Solution for ${solution.challengeId} is invalid.`);
                }
            }
        }
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function evolvePersistAndRegisterKeystone({ prevIbGib, newData, metaspace, space, }) {
    const lc = `[${evolvePersistAndRegisterKeystone.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 8b10e8920f08b7842803665834cf8925)`);
        }
        if (!prevIbGib.data) {
            throw new Error(`(UNEXPECTED) prevIbGib.data falsy? (E: 5e84875bf992c585b979e6c8ed5bf225)`);
        }
        if (prevIbGib.data.revocationInfo) {
            throw new Error(`Keystone has already been revoked (prevIbGib.data.revocationInfo truthy), so we cannot evolve the keystone. Keystone addr: ${getIbGibAddr({ ibGib: prevIbGib })} (E: 45d7f846556829de6b2a701838c3f825)`);
        }
        const prevData = prevIbGib.data;
        /**
         * we want to completely replace these keys, so we will remove them
         * from the data. This occurs first in the underlying mut8
         * transform.
         * @see {@link mut8}
         */
        let dataToRemove = {};
        if (prevData.proofs) {
            dataToRemove.proofs = [];
        }
        if (prevData.challengePools) {
            dataToRemove.challengePools = [];
        }
        if (prevData.frameDetails) {
            dataToRemove.frameDetails = {};
        }
        if (prevData.checkpointDetails) {
            dataToRemove.checkpointDetails = {};
        }
        if (Object.keys(dataToRemove).length === 0) {
            dataToRemove = undefined;
        }
        const resMut8 = await mut8({
            src: prevIbGib,
            dataToRemove,
            dataToAddOrPatch: newData,
            // dna: false, // explicitly set to false just to show
            nCounter: true,
        });
        if (!!resMut8.intermediateIbGibs) {
            throw new Error(`(UNEXPECTED) resMut8.intermediateIbGibs truthy? I'm not sure if we expect there to be intermediateIbGibs, but I feel like we shouldn't. Pretty sure we shouldn't, definitely don't *want* them. (E: ba40d55d7c2d36d438c413886f148625)`);
        }
        if (!!resMut8.dnas) {
            throw new Error(`(UNEXPECTED) resMut8.dnas truthy? We do not want dnas with keystones. (E: 49470513d018f97d28024f4e82da3b25)`);
        }
        const newKeystoneIbGib = resMut8.newIbGib;
        // run validation here
        const errors = await validateKeystoneTransition({
            currentIbGib: newKeystoneIbGib,
            prevIbGib,
        });
        if (errors.length > 0) {
            console.error(`${lc} Validation Failed:\n${errors.join('\n')}`);
            throw new Error(`(UNEXPECTED) invalid keystone after we just evolved it? Errors: ${errors.join('; ')} (E: ae2c58406c1db7687879dfb89fc1f825)`);
        }
        // save and register
        await metaspace.put({ ibGib: newKeystoneIbGib, space, });
        await metaspace.registerNewIbGib({ ibGib: newKeystoneIbGib, space, });
        return newKeystoneIbGib;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Creates a new keystone ibgib that has no dna and no past.
 */
export async function createKeystoneIbGibImpl({ data, metaspace, space, }) {
    const lc = `[${createKeystoneIbGibImpl.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 5e32389700e9899e788cbefacef7c825)`);
        }
        space ??= await metaspace.getLocalUserSpace({ lock: false });
        if (!space) {
            throw new Error(`(UNEXPECTED) space was falsy and we couldn't get default local user space from metaspace? (E: 9a6498cf16a8801f19ec376749742225)`);
        }
        // create the actual keystoneIbGib
        const resFirstGen = await Factory_V1.firstGen({
            parentIbGib: Factory_V1.primitive({ ib: KEYSTONE_ATOM }),
            ib: await getKeystoneIb({ keystoneData: data }),
            data,
            dna: false,
            nCounter: true,
            tjp: {
                timestamp: true,
                uuid: true,
            },
        });
        const keystoneIbGib = resFirstGen.newIbGib;
        if (!keystoneIbGib.data) {
            throw new Error(`(UNEXPECTED) keystoneIbGib.data falsy? We expect the data to be populated with real keystone data. (E: 38a358facdb89d16d81d48c8520d3d25)`);
        }
        if (!keystoneIbGib.rel8ns) {
            throw new Error(`(UNEXPECTED) keystoneIbGib.rel8ns falsy? we expect the rel8ns to have ancestor and past. (E: 20cb7723dc33ae1ef808fe76d1bf4b25)`);
        }
        if (!keystoneIbGib.rel8ns.past || keystoneIbGib.rel8ns.past.length === 0) {
            throw new Error(`(UNEXPECTED) keystoneIbGib.rel8ns.past falsy or empty? we expect the firstGen call to generate an interstitial ibgib that we will splice out. (E: 0fd8388d045ab9f37834c27d67e78825)`);
        }
        // reset n
        keystoneIbGib.data.n = 0;
        // reset tjp
        keystoneIbGib.data.isTjp = true;
        delete keystoneIbGib.rel8ns.tjp;
        // reset past
        delete keystoneIbGib.rel8ns.past;
        // recalculate gib
        keystoneIbGib.gib = await getGib({ ibGib: keystoneIbGib });
        // save and register
        await metaspace.put({ ibGib: keystoneIbGib, space, });
        await metaspace.registerNewIbGib({ ibGib: keystoneIbGib, space, });
        return keystoneIbGib;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function validateKeystoneGraph({ keystoneIbGib, dependencyGraph, getLatest, invalidIfMoreRecentKeystoneFoundInSpace, space, }) {
    const lc = `[${validateKeystoneGraph.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 22e1ca1d5a08a3f90b7fe9da95df7b26)`);
        }
        // maybe too defensive but...
        if (!keystoneIbGib) {
            throw new Error(`(UNEXPECTED) keystoneIbGib falsy? (E: 26482871d529fff6e8899c5d8a6c3826)`);
        }
        const errors = [];
        // first, get the latest if that is the case...
        if (getLatest) {
            const keystoneAddr = getIbGibAddr({ ibGib: keystoneIbGib });
            const resGetLatestAddrs = await getLatestAddrs({
                ibGibs: [keystoneIbGib],
                space,
            });
            if (!resGetLatestAddrs.data) {
                throw new Error(`(UNEXPECTED) resGetLatestAddrs.data falsy? (E: 3a23b3b420a8da1928219ca8d47b2126)`);
            }
            if (!resGetLatestAddrs.data.latestAddrsMap) {
                throw new Error(`(UNEXPECTED) resGetLatestAddrs.data.latestAddrsMap falsy? (E: 93f1180598fb65a4b8d36e08d6c50426)`);
            }
            const { latestAddrsMap } = resGetLatestAddrs.data;
            if (Object.keys(latestAddrsMap).length === 0) {
                throw new Error(`(UNEXPECTED) latestAddrsMap truthy but empty? (E: 7142f8f7625b9186281d08251e407826)`);
            }
            const latestAddr = latestAddrsMap[keystoneAddr];
            if (latestAddr) {
                if (latestAddr !== keystoneAddr) {
                    if (invalidIfMoreRecentKeystoneFoundInSpace) {
                        errors.push(`${lc} more recent keystone (${latestAddr}) found than the one passed in (${keystoneAddr}) in space (${space.ib}). (E: 64bf48de9448ee5c7528bf03bed2a826)`);
                    }
                    else {
                        // get the latest ibgib and point the incoming ref to that
                        const [latestKeystoneIbGib] = await getIbGibsFromCache_fallbackToSpaces({
                            addrs: [latestAddr],
                            space,
                        });
                        keystoneIbGib = latestKeystoneIbGib;
                    }
                }
                else {
                    // the incoming ibgib **is** the latest keystone, so nothing more to be done.
                    if (logalot) {
                        console.log(`${lc} incoming keystone (${keystoneAddr}) is the latest in the space (${space.ib}) (I: 8ffbd8d6cbc8a461e5e7f1084bd22a26)`);
                    }
                }
            }
            else {
                // keystone not found in space?
                console.warn(`${lc} keystoneIbGib (${keystoneAddr}) not found in space (${space.ib}). I don't know what the implications of this are. (W: 258c88742448db34a585669484771826)`);
            }
        }
        dependencyGraph ??= await getDependencyGraph({
            ibGib: keystoneIbGib,
            space,
        });
        const depIbGibs = Object.values(dependencyGraph);
        const { mapWithTjp_NoDna, mapWithTjp_YesDna, mapWithoutTjps, } = splitPerTjpAndOrDna({ ibGibs: depIbGibs });
        // there should be no DNA
        if (Object.keys(mapWithTjp_YesDna).length > 0) {
            errors.push(`${lc} DNA found. Keystones should NOT have DNA. (E: a3ec7827c4284cdea61c2372c0615826)`);
        }
        // is there a tjp? I think there is, so mapWithoutTjps should also be
        // empty
        if (Object.keys(mapWithoutTjps).length > 0) {
            errors.push(`${lc} Non-tjp ibgibs found? Keystones are expected to have tjps. (E: a3ec7827c4284cdea61c2372c0615826)`);
        }
        if (Object.keys(mapWithTjp_NoDna).length > 0) {
            // happy path here. These should be the keystone ibgibs. we will put
            // them in order, then do validation on the transitions. We will
            // also check other graph-scoped integrity of keystones (like do
            // certain properties match among ALL keystones, etc.)
            const keystoneIbGibs_unordered = Object.values(mapWithTjp_NoDna);
            const orderedKeystonesMap = getTimelinesGroupedByTjp({ ibGibs: keystoneIbGibs_unordered });
            const keys = Object.keys(orderedKeystonesMap);
            if (keys.length === 0) {
                throw new Error(`(UNEXPECTED) orderedKeystonesMap empty? (E: d437b80bef58e83a034b28af46278726)`);
            }
            else if (keys.length > 1) {
                // keys.length > 1
                throw new Error(`(UNEXPECTED) more than one timeline in keystone graph? ATOW (02/19/2026) we are expecting only a single timeline. (E: 66085b14e3887a59c872afd240511f26)`);
            }
            // happy path: exactly one timeline
            const keystoneTjpAddr = keys[0];
            const keystoneIbGibs_ordered = orderedKeystonesMap[keystoneTjpAddr];
            if (keystoneIbGibs_ordered.length === 0) {
                throw new Error(`(UNEXPECTED) empty keystoneIbGibs_ordered? (E: 4a6b085aa8a76828a8d65367e7e56526)`);
            }
            const genesisErrors = await validateGenesisKeystone({
                keystoneIbGib: keystoneIbGibs_ordered[0]
            });
            genesisErrors.forEach(x => errors.push(x));
            if (keystoneIbGibs_ordered.length === 1) {
                // only the genesis keystone, which has already been done.
            }
            else {
                // more than 1 keystone, so validate the transitions
                for (let i = 0; i < keystoneIbGibs_ordered.length - 1; i++) {
                    const prevIbGib = keystoneIbGibs_ordered[i];
                    const currentIbGib = keystoneIbGibs_ordered[i + 1];
                    const transitionErrors = await validateKeystoneTransition({
                        prevIbGib, currentIbGib
                    });
                    if (transitionErrors.length > 0) {
                        errors.push(`${lc} keystone has ${transitionErrors.length} transition errors. i: ${i}. prevIbGib addr: ${getIbGibAddr({ ibGib: prevIbGib })}. currentIbGib addr: ${getIbGibAddr({ ibGib: currentIbGib })}. (E: 3ac17be916141d0c286ca5a87bb1b426)`);
                        transitionErrors.forEach(x => errors.push(x));
                    }
                    break; // stop after the first found transition error
                }
            }
        }
        else {
            // one of the other errors regarding mapWithTjp_YesDna or
            // mapWithoutTjps will exist at this point, so we don't need to add
            // any more errors.
            if (errors.length === 0) {
                throw new Error(`(UNEXPECTED) mapWithTjp_NoDna empty (which is an error) but errors.length === 0? If we get here, we expect one of the other errors to have been populated. (E: 7bca2866b6f8fca318923de8c88e8826)`);
            }
        }
        return errors;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function getIdentity({ addr, metaspace, space, }) {
    const lc = `[${getIdentity.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 7f1dac53ff4691cb08cd267bf75f4326)`);
        }
        const resGet = await metaspace.get({ addr, space });
        const identityIbGib = resGet.ibGibs?.at(0);
        if (identityIbGib) {
            if (!identityIbGib.data) {
                throw new Error(`(UNEXPECTED) identityIbGib.data falsy? (E: d2f188232cd3bafb873b89e3a25a4826)`);
            }
            // should actually be a `isKeystone` guard, but hey...
            if (!identityIbGib.ib.startsWith(KEYSTONE_ATOM)) {
                throw new Error(`invalid. does not start with ${KEYSTONE_ATOM} (E: 4523f8647cc139f8c49597fb86329426)`);
            }
        }
        return identityIbGib;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function getIdentity_throwIfUndefined({ addr, metaspace, space, }) {
    const identityIbGib = await getIdentity({ metaspace, addr, space });
    if (!identityIbGib) {
        throw new Error(`addr (${addr}) not found in space: ${space.ib} (E: 7533682e805819cc78bdb0d8960be826)`);
    }
    return identityIbGib;
}
//# sourceMappingURL=keystone-helpers.mjs.map