import { extractErrorMsg, } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
import { KeystoneReplenishStrategy, } from './keystone-types.mjs';
import { KeystoneStrategyFactory } from './strategy/keystone-strategy-factory.mjs';
import { POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE } from './keystone-constants.mjs';
import { addToBindingMap, createKeystoneIbGibImpl, evolvePersistAndRegisterKeystone, generateOpaqueChallengeId, resolveTargetPool, selectChallengeIds, solveAndReplenish, validateKeystoneTransition, } from './keystone-helpers.mjs';
const logalot = GLOBAL_LOG_A_LOT || true;
/**
 * Facade for managing Keystone Identities.
 *
 * Handles Genesis, Authorized Evolution (Signing), and Validation.
 */
export class KeystoneService_V1 {
    lc = `[${KeystoneService_V1.name}]`;
    /**
     * Creates a brand new Keystone Identity Timeline.
     */
    async genesis({ masterSecret, configs, metaspace, space, }) {
        const lc = `${this.lc}[${this.genesis.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: c98ae8adbc5888dbf84c5aced7610b25)`);
            }
            const challengePools = [];
            for (const config of configs) {
                const strategy = KeystoneStrategyFactory.create({ config });
                const poolSecret = await strategy.derivePoolSecret({ masterSecret });
                const challenges = {};
                const bindingMap = {};
                const targetSize = config.behavior.size;
                const timestamp = Date.now().toString();
                for (let i = 0; i < targetSize; i++) {
                    const challengeId = await generateOpaqueChallengeId({
                        salt: config.salt, timestamp, index: i
                    });
                    const solution = await strategy.generateSolution({
                        poolSecret, poolId: config.salt, challengeId,
                    });
                    const challenge = await strategy.generateChallenge({ solution });
                    challenges[challengeId] = challenge;
                    // Populate Binding Map
                    addToBindingMap(bindingMap, challengeId);
                }
                challengePools.push({
                    id: config.salt,
                    config,
                    challenges,
                    bindingMap
                });
            }
            if (challengePools.length === 0) {
                throw new Error(`No challenge pools created. (E: 38e538530996940e1f16a8b199995825)`);
            }
            const data = { challengePools, proofs: [] };
            const keystoneIbGib = await createKeystoneIbGibImpl({ data, metaspace, space });
            return keystoneIbGib;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * Signs a claim by solving challenges from a specific pool and evolving the Keystone timeline.
     *
     * Uses a hybrid selection strategy: Mandatory IDs (Alice) + Sequential (FIFO) + Random (Stochastic).
     *
     * Supports Delegation via `poolFilter` to find specific foreign pools.
     */
    async sign({ latestKeystone, masterSecret, claim, poolId, poolFilter, requiredChallengeIds = [], frameDetails, metaspace, space, }) {
        const lc = `${this.lc}[${this.sign.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            const prevData = latestKeystone.data;
            if (prevData.revocationInfo) {
                throw new Error(`Keystone has been revoked. Cannot sign. (E: 4f2198c39116d15c48ba191940316825)`);
            }
            // 1. Identify Authority (Resolve Pool)
            const pool = resolveTargetPool({
                pools: prevData.challengePools,
                poolId,
                poolFilter,
                verb: claim.verb
            });
            if (logalot) {
                console.log(`${lc} Selected pool: ${pool.id} (size: ${Object.keys(pool.challenges).length}) (I: genuuid)`);
            }
            // 2. Calculate Costs (Select IDs)
            const idsToSolve = selectChallengeIds({
                pool,
                targetAddr: claim.target,
                requiredChallengeIds
            });
            // 3. Pay the Cost (Solve & Replenish)
            // This helper handles the Strategy creation, Secret derivation, Solving, 
            // and the calculation of the Next state for ALL pools.
            const { proof, nextPools } = await solveAndReplenish({
                targetPoolId: pool.id,
                prevPools: prevData.challengePools,
                masterSecret,
                challengeIds: idsToSolve,
                claim,
                requiredChallengeIds
            });
            // 4. Construct New Data
            const newData = {
                challengePools: nextPools,
                proofs: [proof],
                frameDetails,
                // Revocation info is undefined for a standard sign operation
            };
            // 5. Commit (Evolve, Persist, Register)
            const resKeystone = await evolvePersistAndRegisterKeystone({
                prevIbGib: latestKeystone,
                newData,
                metaspace,
                space
            });
            return resKeystone;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * Validates a keystone.
     *
     * ## NOTES
     *
     * Atow (12/22/2025) this only validates the transition from Prev -> Curr.
     *
     * @returns Array of validation error strings. Empty array means Valid.
     *
     * @see {@link validateKeystoneTransition}
     */
    async validate({ currentIbGib, prevIbGib, }) {
        // todo: change this to validate the entire keystone graph. the next
        // step is to walk the history and validate each transition.
        const errors = await validateKeystoneTransition({ currentIbGib, prevIbGib });
        return errors;
    }
    /**
     * Permanently revokes the Identity.
     *
     * Logic:
     * 1. Locates the 'revoke' pool.
     * 2. Solves required challenges to prove ownership.
     * 3. Wipes the pool (via 'scorched-earth' strategy in solveAndReplenish).
     * 4. Sets the revocationInfo on the new frame.
     */
    async revoke({ latestKeystone, masterSecret, reason = "User initiated revocation", frameDetails, metaspace, space, }) {
        const lc = `${this.lc}[${this.revoke.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            const prevData = latestKeystone.data;
            // 1. Identify Authority (Resolve Revoke Pool)
            const pool = resolveTargetPool({
                pools: prevData.challengePools,
                poolId: POOL_ID_REVOKE // Explicitly require the special revoke pool
            });
            // 2. Construct Claim
            const claim = {
                verb: KEYSTONE_VERB_REVOKE,
                target: getIbGibAddr({ ibGib: latestKeystone })
            };
            // 3. Calculate Costs
            const idsToSolve = selectChallengeIds({
                pool,
                targetAddr: claim.target,
                requiredChallengeIds: []
            });
            if (idsToSolve.length === 0) {
                throw new Error(`Revocation policy selected 0 challenges? Check config for pool ${pool.id}. Revocation requires proof. (E: 97e5a8356d241ae7b882db791cb1f825)`);
            }
            // 4. Pay the Cost & Scorched Earth
            // The revoke pool config should have 'replenish: scorched-earth', 
            // causing solveAndReplenish to return an empty pool in nextPools.
            const { proof, nextPools } = await solveAndReplenish({
                targetPoolId: pool.id,
                prevPools: prevData.challengePools,
                masterSecret,
                challengeIds: idsToSolve,
                claim,
                requiredChallengeIds: []
            });
            // warn if nextPools contains pool.id that isn't empty (we were
            // supposed to do "scorched earth" which empties the pool)
            if (nextPools.find(p => p.id === pool.id && Object.keys(p.challenges).length > 0)) {
                console.warn(`${lc} revocation pool ${pool.id} is not empty after revocation. Is the revocation pool replenish strategy set to ${KeystoneReplenishStrategy.scorchedEarth}? (W: 300c28bc8b98fc3e3c0b0d988344f825)`);
            }
            // 5. Construct Revocation Info
            const revocationInfo = { reason, proof };
            // 6. Construct New Data
            const newData = {
                challengePools: nextPools,
                proofs: [proof],
                revocationInfo,
                frameDetails
            };
            // 7. Commit
            const newKeystone = await evolvePersistAndRegisterKeystone({
                prevIbGib: latestKeystone,
                newData,
                metaspace,
                space
            });
            return newKeystone;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * Structural evolution: Adds new challenge pools to the keystone.
     *
     * Use Case: Adding a delegate (Server) for SSO, adding a recovery key,
     * or rotating to a new set of pools.
     *
     * Requires the Master Secret to authorize the change via a pool containing
     * the 'manage' verb.
     */
    async addPools({ latestKeystone, masterSecret, newPools, metaspace, space, }) {
        const lc = `${this.lc}[${this.addPools.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            if (!latestKeystone.data) {
                throw new Error(`(UNEXPECTED) latestKeystone.data falsy? (E: 7334c8faed128166a999d428c7805b25)`);
            }
            const prevData = latestKeystone.data;
            if (prevData.revocationInfo) {
                throw new Error(`Keystone has been revoked. Cannot add pools. (E: 8599f8f51c78d722252ddb2894fdbe25)`);
            }
            if (newPools.length === 0) {
                throw new Error(`No new pools provided to add. (E: 6599f8f51c78d722252ddb2894fdbe25)`);
            }
            // 1. Identify Authority (Resolve Admin Pool)
            // We need a pool that allows the 'manage' verb.
            const adminPool = resolveTargetPool({
                pools: prevData.challengePools,
                verb: KEYSTONE_VERB_MANAGE,
            });
            if (logalot) {
                console.log(`${lc} Authorized via pool: ${adminPool.id}`);
            }
            // 2. Construct the Management Claim
            const target = getIbGibAddr({ ibGib: latestKeystone });
            const claim = {
                verb: KEYSTONE_VERB_MANAGE,
                target, // I am managing myself
                // Scope creates a cryptographic commitment to WHICH pools are being added
                scope: JSON.stringify({ add: newPools.map(p => p.id) })
            };
            // 3. Calculate Costs
            const idsToSolve = selectChallengeIds({
                pool: adminPool,
                targetAddr: target
            });
            // 4. Pay the Cost (Solve & Replenish)
            // This authorizes the change by evolving the admin pool.
            const { proof, nextPools: replenishedExistingPools } = await solveAndReplenish({
                targetPoolId: adminPool.id,
                prevPools: prevData.challengePools,
                masterSecret,
                challengeIds: idsToSolve,
                claim,
            });
            // 5. Mutate Structure (Add the new pools)
            // We verify ID uniqueness cheaply here to prevent blatant errors
            const existingIds = new Set(replenishedExistingPools.map(p => p.id));
            for (const newPool of newPools) {
                if (existingIds.has(newPool.id)) {
                    throw new Error(`Cannot add pool. ID collision: ${newPool.id} (E: 8a4c2b1d3e5f6a7b8c9d0e1f2a3b4c5d)`);
                }
            }
            const finalPools = [...replenishedExistingPools, ...newPools];
            // 6. Construct New Data
            const newData = {
                challengePools: finalPools,
                proofs: [proof], // The proof authorizes the structure change
            };
            // 7. Commit
            const newKeystone = await evolvePersistAndRegisterKeystone({
                prevIbGib: latestKeystone,
                newData,
                metaspace,
                space
            });
            return newKeystone;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
}
//# sourceMappingURL=keystone-service-v1.mjs.map