import { jwkToKey } from '../lib/jwk_to_key.js';
import { maybeJWSAlgorithm } from '../lib/jws_algorithms.js';
import { JWKSInvalid, JOSENotSupported, JWKSNoMatchingKey, JWKSMultipleMatchingKeys, } from '../util/errors.js';
import { isObject } from '../lib/type_checks.js';
function signatureAlgorithm(alg) {
    const entry = typeof alg === 'string' ? maybeJWSAlgorithm(alg) : undefined;
    if (!entry || entry.symmetric) {
        throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
    }
    return entry;
}
function isJWKSLike(jwks) {
    if (!jwks || typeof jwks !== 'object') {
        return false;
    }
    const { keys } = jwks;
    return Array.isArray(keys) && keys.every(isJWKLike);
}
function isJWKLike(key) {
    return isObject(key);
}
class LocalJWKSetImpl {
    #jwks;
    #cached = new WeakMap();
    constructor(jwks) {
        if (!isJWKSLike(jwks)) {
            throw new JWKSInvalid('JSON Web Key Set malformed');
        }
        this.#jwks = structuredClone(jwks);
    }
    jwks() {
        return this.#jwks;
    }
    async getKey(protectedHeader, token) {
        const { alg, kid } = { ...protectedHeader, ...token?.header };
        const entry = signatureAlgorithm(alg);
        const candidates = this.#jwks.keys.filter((jwk) => {
            let candidate = entry.kty.includes(jwk.kty);
            if (candidate && typeof kid === 'string') {
                candidate = kid === jwk.kid;
            }
            if (candidate && (typeof jwk.alg === 'string' || jwk.kty === 'AKP')) {
                candidate = alg === jwk.alg;
            }
            if (candidate && typeof jwk.use === 'string') {
                candidate = jwk.use === 'sig';
            }
            if (candidate && Array.isArray(jwk.key_ops)) {
                candidate = jwk.key_ops.includes('verify');
            }
            if (candidate && entry.crv) {
                candidate = jwk.crv === entry.crv;
            }
            return candidate;
        });
        const { 0: jwk, length } = candidates;
        if (length === 0) {
            throw new JWKSNoMatchingKey();
        }
        if (length !== 1) {
            const error = new JWKSMultipleMatchingKeys();
            const _cached = this.#cached;
            error[Symbol.asyncIterator] = async function* () {
                for (const jwk of candidates) {
                    try {
                        yield await importWithAlgCache(_cached, jwk, entry);
                    }
                    catch { }
                }
            };
            throw error;
        }
        return importWithAlgCache(this.#cached, jwk, entry);
    }
}
async function importWithAlgCache(cache, jwk, entry) {
    const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk);
    if (cached[entry.alg] === undefined) {
        const key = await jwkToKey(entry, { ...jwk, alg: entry.alg, ext: true });
        if (key.type !== 'public') {
            throw new JWKSInvalid('JSON Web Key Set members must be public keys');
        }
        cached[entry.alg] = key;
    }
    return cached[entry.alg];
}
export function createLocalJWKSet(jwks) {
    const set = new LocalJWKSetImpl(jwks);
    const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
    Object.defineProperties(localJWKSet, {
        jwks: {
            value: () => structuredClone(set.jwks()),
            enumerable: false,
            configurable: false,
            writable: false,
        },
    });
    return localJWKSet;
}
