/**
 * @module sync-websocket-peer-helpers
 *
 * Reusable, isomorphic pure validation helper functions and policies for the
 * WebSocket Sync Peer connect process.
 */
import { HashAlgorithm, extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbGibAddr, getIbAndGib } from '@ibgib/ts-gib/dist/helper.mjs';
import { validateIbGibAddr, validateIbGibIntrinsically } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
import { getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
import { GLOBAL_LOG_A_LOT } from '../../../../core-constants.mjs';
import { KeystoneReplenishStrategy, KeystoneChallengeType } from '../../../../keystone/keystone-types.mjs';
import { parseKeystoneIb } from '../../../../keystone/keystone-helpers.mjs';
import { KeystoneStrategyFactory } from '../../../../keystone/strategy/keystone-strategy-factory.mjs';
import { KeystoneService_V1 } from '../../../../keystone/keystone-service-v1.mjs';
import { POOL_ID_CONNECT, KEYSTONE_VERB_CONNECT } from '../../../../keystone/keystone-constants.mjs';
const logalot = GLOBAL_LOG_A_LOT;
/**
 * Standard configuration defaults for the Ephemeral Session Keystone.
 */
export const SESSION_KEYSTONE_POLICY = {
    COMMON: {
        ALGO: HashAlgorithm.sha_256,
        TYPE: KeystoneChallengeType.hash_reveal_v1,
        ROUNDS: 2,
        REPLENISH: KeystoneReplenishStrategy.topUp,
    },
    CONNECT_POOL: {
        ID: POOL_ID_CONNECT,
        VERB: KEYSTONE_VERB_CONNECT,
        SIZE: 10,
        SELECT_SEQUENTIALLY: 2,
        SELECT_RANDOMLY: 2,
        TARGET_BINDING_COUNT: 0,
        SERVER_DEMAND_COUNT: 3,
    }
};
/**
 * Resolves the deterministic dynamic upfront pre-filter challenge.
 */
export function getConnectChallenge(keystone) {
    const lc = `[${getConnectChallenge.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 3c791af2f978a00a087dfdde90884826)`);
        }
        const connectPool = (keystone.data?.challengePools ?? []).find((p) => p.id === SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID);
        if (!connectPool) {
            throw new Error(`Keystone missing "${SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID}" pool (E: 6382e29260e4ddbac42ed18f49733526)`);
        }
        const challengeIds = Object.keys(connectPool.challenges).sort();
        if (challengeIds.length === 0) {
            throw new Error(`Connect pool has no challenges (E: 1ef62fdbda182a790835c8c796475226)`);
        }
        const challengeId = challengeIds[0];
        const challenge = connectPool.challenges[challengeId];
        return { challengeId, challenge };
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Validates the upfront solution against the session keystone's first connect
 * challenge.
 */
export async function checkConnectSolution(keystone, solutionValue) {
    const lc = `[${checkConnectSolution.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 166aa101e109194556e6b99575945826)`);
        }
        const connectPool = (keystone.data?.challengePools ?? []).find((p) => p.id === SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID);
        if (!connectPool) {
            return false;
        }
        const { challengeId, challenge } = getConnectChallenge(keystone);
        const strategy = KeystoneStrategyFactory.create({ config: connectPool.config });
        const solution = {
            type: 'hash-reveal-v1',
            challengeId,
            poolId: SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID,
            value: solutionValue
        };
        return await strategy.validateSolution({ solution, challenge });
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Validates the upfront connect parameter solutions before socket connection upgrades.
 */
export async function validateUpfrontConnect({ sAddr, solution, space, metaspace, }) {
    const lc = `[${validateUpfrontConnect.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 7c21ecaf0b7f59347882bc18d8245826)`);
        }
        // 1. Initial address sanity check
        const addrErrors = validateIbGibAddr({ addr: sAddr }) ?? [];
        if (addrErrors.length > 0) {
            throw new Error(`Invalid sAddr: ${addrErrors.join(', ')} (E: 9a80cea03308ddab721eec08ffe0e826)`);
        }
        const { ib } = getIbAndGib({ ibGibAddr: sAddr });
        parseKeystoneIb({ ib });
        // 2. Fetch session keystone from storage
        const resGet = await metaspace.get({ addrs: [sAddr], space });
        if (!resGet.success || !resGet.ibGibs || resGet.ibGibs.length !== 1) {
            throw new Error(`Session keystone not found in space: ${sAddr} (E: d381a8269328dbe6b8a9e4f86c2a3626)`);
        }
        const authorizedS = resGet.ibGibs[0];
        // 3. Cryptographically check dynamic solution
        const isValid = await checkConnectSolution(authorizedS, solution);
        if (!isValid) {
            throw new Error(`Upfront connect solution verification failed (E: 67c5b17968b8829058925d8808d09826)`);
        }
        // 4. Resolve secure TJP
        const past = authorizedS.rel8ns?.past;
        const sAddr_tjp = (past && past.length > 0) ? past[0] : getIbGibAddr({ ibGib: authorizedS });
        return { sAddr_tjp, authorizedS };
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Verifies the multi-turn evolved Keystone challenge proof frame.
 */
export async function verifyConnectProof({ proofFrame, sKeystone_latest, challengeUuid, demandedIds, }) {
    const lc = `[${verifyConnectProof.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 4500528dabb80aeb98dabf886d6b2826)`);
        }
        // 1. Validate evolution structure intrinsically
        const keystoneService = new KeystoneService_V1();
        const validationErrors = await keystoneService.validate({
            prevIbGib: sKeystone_latest,
            currentIbGib: proofFrame,
        });
        if (validationErrors && validationErrors.length > 0) {
            throw new Error(`Proof validation failed: ${validationErrors.join(', ')} (E: e70278c90dc475a2d80d0ad8bbf1a826)`);
        }
        // 2. Validate connect target signature
        const proofs = proofFrame.data.proofs ?? [];
        const proof = proofs.find(p => p.claim?.verb === SESSION_KEYSTONE_POLICY.CONNECT_POOL.VERB);
        if (!proof || proof.claim?.target !== challengeUuid) {
            throw new Error(`Proof target mismatch or missing connect claim (E: e897bba1bb6161b418a630c1e30c5726)`);
        }
        // 3. Verify client successfully solved all demanded challenges
        const solvedIds = new Set(proof.solutions?.map(s => s.challengeId) ?? []);
        const missingIds = demandedIds.filter(id => !solvedIds.has(id));
        if (missingIds.length > 0) {
            throw new Error(`Proof is missing demanded solutions: ${missingIds.join(', ')} (E: 161329081aff00f1a8e4cf9330a62326)`);
        }
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Isomorphic pure function to intrinsically validate, persist, and register
 * posted keystones. Shared between REST API and sync websocket receiver peers.
 */
export async function validateAndRegisterPostKeystones({ ibGibs, metaspace, space }) {
    const lc = `[${validateAndRegisterPostKeystones.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: aae81e3412c8c87bc8f3e227d81be826)`);
        }
        // 1. Intrinsic Validation
        for (const ibGib of ibGibs) {
            const errors = await validateIbGibIntrinsically({ ibGib });
            if (errors && errors.length > 0) {
                throw new Error(`Intrinsic validation failed for ${getIbGibAddr({ ibGib })}: ${errors.join(', ')} (E: 4162884c53183b58b3caaa48c26be826)`);
            }
        }
        // 2. Keystone-specific checks & Registration
        for (const ibGib of ibGibs) {
            if (ibGib.ib?.startsWith('keystone')) {
                // Registration handles genesis vs evolution internally via metastones
                await metaspace.registerNewIbGib({
                    ibGib,
                    space
                });
            }
        }
        // 3. Persistence
        await metaspace.put({ ibGibs, space });
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Isomorphic pure function to validate a keystone evolution transition and related S session keystones,
 * and persist them to metaspace. Shared across REST API evolve handlers and future socket evolution channels.
 */
export async function validateAndRegisterEvolveKeystone({ domainAddr, keystoneIbGib, relatedIbGibs = [], metaspace, space }) {
    const lc = `[${validateAndRegisterEvolveKeystone.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 42b826a690881cd5a8fe520cbdfad826)`);
        }
        const addr = getIbGibAddr({ ibGib: keystoneIbGib });
        // 1. Intrinsic hash validation
        const intrinsicErrors = await validateIbGibIntrinsically({ ibGib: keystoneIbGib });
        if (intrinsicErrors && intrinsicErrors.length > 0) {
            throw new Error(`Intrinsic keystone validation failed: ${intrinsicErrors.join(', ')} (E: 38ea984585edc6ee88d2a698c7895826)`);
        }
        // 2. Verify URL :domainAddr matches the keystone's tjp
        const infoAddr = getGibInfo({ ibGibAddr: addr });
        const tjpGib = infoAddr.tjpGib ?? infoAddr.punctiliarHash;
        const infoDomain = getGibInfo({ ibGibAddr: domainAddr });
        const expectedTjpGib = infoDomain.tjpGib ?? infoDomain.punctiliarHash;
        if (tjpGib !== expectedTjpGib) {
            throw new Error(`Keystone tjpGib (${tjpGib}) does not match URL domainAddr tjpGib (${expectedTjpGib})`);
        }
        // 3. Load previous frame (the current tip in metaspace)
        const keystoneService = new KeystoneService_V1();
        let prevIbGib;
        try {
            prevIbGib = await keystoneService.getLatestKeystone({
                addr: domainAddr,
                metaspace,
                space
            });
        }
        catch (error) {
            throw new Error(`Could not retrieve previous keystone tip for ${domainAddr}: ${extractErrorMsg(error)} (E: b73ed8aeb0d837d248718b487345d326)`);
        }
        // 4. Validate Evolution Transition
        const validationErrors = await keystoneService.validate({
            currentIbGib: keystoneIbGib,
            prevIbGib
        });
        if (validationErrors && validationErrors.length > 0) {
            throw new Error(`Keystone evolution validation failed: ${validationErrors.join(', ')} (E: 5212f8492ac839953842631834bcd826)`);
        }
        // 5. Check Single Pool Restriction
        const proofs = keystoneIbGib.data?.proofs ?? [];
        if (proofs.length === 0) {
            throw new Error('No proofs found in evolved keystone data');
        }
        const poolIds = new Set();
        for (const proof of proofs) {
            for (const solution of proof.solutions || []) {
                if (solution.poolId) {
                    poolIds.add(solution.poolId);
                }
            }
        }
        if (poolIds.size > 1) {
            throw new Error(`Multiple pools evolved (${Array.from(poolIds).join(', ')}). Only one pool evolution allowed at a time. (E: 4a9b28ae33e8e3eb4ddbad981aa7b126)`);
        }
        // 6. Check that the verb is 'sync'
        if (proofs.length !== 1) {
            throw new Error(`evolving keystone with proofs.length !== 1? (E: 14e058a34964dcd7181395382cc60826)`);
        }
        const claim = proofs[0].claim;
        if (!claim) {
            throw new Error(`proof.claim is falsy (E: 54be88be2c975ef18e571a4286928826)`);
        }
        const { verb } = claim;
        if (!verb) {
            throw new Error(`claim.verb is falsy (E: 92cf38af58db2cd90e7132b8940fe326)`);
        }
        if (verb !== 'sync') {
            throw new Error(`non-sync keystone evolution not yet implemented (E: eca4681b05486d1f784c0cb815ef9926)`);
        }
        const sAddr = claim.target;
        if (!sAddr) {
            throw new Error('No target address found in sync claim');
        }
        // Validate S^Stjp (session keystone ibgib) from relatedIbGibs
        const sIbGib = relatedIbGibs.find(ibGib => getIbGibAddr({ ibGib }) === sAddr);
        if (!sIbGib) {
            throw new Error(`Session keystone ${sAddr} must be provided in relatedIbGibs (E: fe5a68438e288ab4e86c90d81d142826)`);
        }
        const sIntrinsicErrors = await validateIbGibIntrinsically({ ibGib: sIbGib });
        if (sIntrinsicErrors && sIntrinsicErrors.length > 0) {
            throw new Error(`Intrinsic validation failed for S^Stjp: ${sIntrinsicErrors.join(', ')} (E: 802d8350c3cccbd7184dd43866c05526)`);
        }
        const sGenesisErrors = await keystoneService.validateGenesisKeystone({ keystoneIbGib: sIbGib });
        if (sGenesisErrors && sGenesisErrors.length > 0) {
            throw new Error(`Genesis validation failed for S^Stjp: ${sGenesisErrors.join(', ')} (E: 8afdc1dbed48c000455b35585dc26826)`);
        }
        // Persist and Register both I and S
        await metaspace.put({ ibGibs: [keystoneIbGib, sIbGib], space });
        await metaspace.registerNewIbGib({ ibGib: keystoneIbGib, space });
        await metaspace.registerNewIbGib({ ibGib: sIbGib, space });
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
//# sourceMappingURL=sync-websocket-peer-helpers.mjs.map