import { extractErrorMsg, mergeMapsOrArrays_Naive } 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, KEYSTONE_CHECKPOINT_FREQUENCY } from './keystone-constants.mjs';
import { createKeystoneIbGibImpl, evolvePersistAndRegisterKeystone, generateOpaqueChallengeId, resolveTargetPool, selectChallengeIds, solveAndReplenish, validateKeystoneTransition, validateGenesisKeystone, validateKeystoneGraph } from './keystone-helpers.mjs';
const logalot = GLOBAL_LOG_A_LOT;
/**
 * 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, frameDetails, 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 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.id, challengeId,
                    });
                    const challenge = await strategy.generateChallenge({ solution });
                    challenges[challengeId] = challenge;
                }
                challengePools.push({
                    id: config.id,
                    config,
                    challenges,
                });
            }
            if (challengePools.length === 0) {
                throw new Error(`No challenge pools created. (E: 38e538530996940e1f16a8b199995825)`);
            }
            const data = { challengePools, proofs: [] };
            if (frameDetails) {
                data.frameDetails = frameDetails;
            }
            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.
     *
     * todo: wrap this and other entire keystone sign/method implementations in locks on the keystone's tjpGib.
     */
    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: 6b26d6f4aad18380f2b3a0989b592826)`);
            }
            // 2. Calculate Costs (Select IDs)
            const idsToSolve = await 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 n = (prevData.n ?? 0) + 1;
            let checkpointDetails;
            if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
                // Generate a temporary view of the keystone as it would be in this frame
                // to calculate the aggregated state.
                const currentFrameView = {
                    ...latestKeystone,
                    data: { ...prevData, frameDetails }
                };
                checkpointDetails = await this.getAggregateDetails({
                    latestKeystone: currentFrameView,
                    metaspace,
                    space
                });
            }
            const newData = {
                challengePools: nextPools,
                proofs: [proof],
                frameDetails,
                checkpointDetails,
            };
            // 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;
    }
    /**
     * Validates a genesis keystone.
     *
     * @returns Array of validation error strings. Empty array means Valid.
     */
    async validateGenesisKeystone({ keystoneIbGib, }) {
        return await validateGenesisKeystone({ keystoneIbGib });
    }
    /**
     * Retrieves the latest keystone frame for the given address.
     *
     * Uses `metaspace.getLatestAddr` to find the actual tip instead of trusting
     * an incoming payload's `past` rel8n.
     *
     * @see `ibgib-get-pattern` SKILL.md
     */
    async getLatestKeystone({ addr, metaspace, space, }) {
        const lc = `${this.lc}[${this.getLatestKeystone.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            const latestAddr = await metaspace.getLatestAddr({ tjpAddr: addr, space });
            if (!latestAddr) {
                throw new Error(`could not find latest addr for ${addr}`);
            }
            const resGet = await metaspace.get({ addr: latestAddr, space });
            if (resGet.success && resGet.ibGibs && resGet.ibGibs.length > 0) {
                return resGet.ibGibs[0];
            }
            else {
                const resIbGib = resGet.rawResultIbGib;
                const addrsNotFound = resIbGib?.data?.addrsNotFound ?? 'unknown';
                throw new Error(`couldn't find latest keystone at ${latestAddr}. addrsNotFound: ${addrsNotFound}? resGet.errorMsg: ${resGet.errorMsg}`);
            }
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * Validates an entire keystone graph from tip to genesis.
     *
     * @returns Array of validation error strings. Empty array means Valid.
     */
    async validateKeystoneGraph({ keystoneIbGib, dependencyGraph, getLatest, invalidIfMoreRecentKeystoneFoundInSpace, space, }) {
        const lc = `${this.lc}[${this.validateKeystoneGraph.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 01c5cd455038c270cbf1effc1704af26)`);
            }
            return await validateKeystoneGraph({
                keystoneIbGib,
                dependencyGraph,
                getLatest,
                invalidIfMoreRecentKeystoneFoundInSpace,
                space
            });
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * Permanently revokes the Identity.
     *
     * Logic:
     * 1. Locates the 'revoke' pool.
     * 2. Solves required challenges to prove ownership.
     * 3. Wipes the pool (via the delete all 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 = await 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 'replenishStrategy:
            // KeystoneReplenishStrategy.deleteAll', 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.deleteAll}? (W: 300c28bc8b98fc3e3c0b0d988344f825)`);
            }
            // 5. Construct Revocation Info
            const revocationInfo = { reason, proof };
            // 6. Construct New Data
            const n = (prevData.n ?? 0) + 1;
            let checkpointDetails;
            if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
                const currentFrameView = {
                    ...latestKeystone,
                    data: { ...prevData, frameDetails, revocationInfo }
                };
                checkpointDetails = await this.getAggregateDetails({
                    latestKeystone: currentFrameView,
                    metaspace,
                    space
                });
            }
            const newData = {
                challengePools: nextPools,
                proofs: [proof],
                revocationInfo,
                frameDetails,
                checkpointDetails,
            };
            // 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 = await 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 n = (prevData.n ?? 0) + 1;
            let checkpointDetails;
            if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
                const currentFrameView = {
                    ...latestKeystone,
                    data: { ...prevData, challengePools: finalPools }
                };
                checkpointDetails = await this.getAggregateDetails({
                    latestKeystone: currentFrameView,
                    metaspace,
                    space
                });
            }
            const newData = {
                challengePools: finalPools,
                proofs: [proof], // The proof authorizes the structure change
                checkpointDetails,
            };
            // 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.`);
            }
        }
    }
    /**
     * Consolidates the state of a Keystone identity across its history.
     * Walks back until it hits a checkpoint or genesis.
     */
    async getAggregateDetails({ latestKeystone, metaspace, space, }) {
        const lc = `${this.lc}[${this.getAggregateDetails.name}]`;
        try {
            let aggregated = {};
            let current = latestKeystone;
            while (current) {
                const data = current.data;
                // 1. If we hit a checkpoint, merge it and STOP.
                if (data.checkpointDetails) {
                    aggregated = mergeMapsOrArrays_Naive({
                        dominant: aggregated,
                        recessive: data.checkpointDetails
                    });
                    break;
                }
                // 2. Merge this frame's details
                if (data.frameDetails) {
                    aggregated = mergeMapsOrArrays_Naive({
                        dominant: aggregated,
                        recessive: data.frameDetails
                    });
                }
                // 3. Genesis check (n=0)
                if (data.n === 0) {
                    break;
                }
                // 4. Walk back
                const past = current.rel8ns?.past;
                const prevAddr = (past && past.length > 0) ? past[past.length - 1] : undefined;
                if (!prevAddr) {
                    break;
                }
                const res = await metaspace.get({ addr: prevAddr, space });
                if (!res.success || !res.ibGibs || res.ibGibs.length === 0) {
                    throw new Error(`Could not find previous keystone frame at ${prevAddr} (E: e3d069b22e11e550e58844889ddb5325)`);
                }
                current = res.ibGibs[0];
            }
            return aggregated;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
    }
}
//# sourceMappingURL=keystone-service-v1.mjs.map