var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { ParaCore, Environment, decryptPrivateKeyAndDecryptShare, encryptPrivateKey, getAsymmetricKeyPair, getDerivedPrivateKeyAndDecrypt, getPublicKeyHex, getSHA256HashHex, parseCredentialCreationRes, } from '@getpara/web-sdk';
import { ReactNativeUtils } from './ReactNativeUtils.js';
import { Passkey, } from 'react-native-passkey';
import { AuthMethodStatus } from '@getpara/user-management-client';
import { mapPasskeyError, logPasskeyDiagnostic } from './errors.js';
import { setEnv } from '../config.js';
import base64url from 'base64url';
const ES256_ALGORITHM = -7;
const RS256_ALGORITHM = -257;
/**
 * Represents a mobile implementation of the Para SDK.
 * @extends ParaCore
 *
 * @example
 * const para = new ParaMobile(Environment.BETA, "api_key");
 */
export class ParaMobile extends ParaCore {
    constructor(envOrApiKey, apiKeyOrRelyingPartyId, relyingPartyIdOrOpts, optsArg) {
        let env, apiKey, relyingPartyId, opts;
        // Helper function to check if value is a valid Environment
        const isEnvironment = (value) => {
            return Object.values(Environment).includes(value);
        };
        if (arguments.length === 1) {
            // 1-parameter case: (apiKey)
            env = undefined;
            apiKey = envOrApiKey;
            relyingPartyId = undefined;
            opts = undefined;
        }
        else if (arguments.length === 2) {
            if (isEnvironment(envOrApiKey)) {
                // 2-parameter case: (env, apiKey)
                env = envOrApiKey;
                apiKey = apiKeyOrRelyingPartyId;
                relyingPartyId = undefined;
                opts = undefined;
            }
            else {
                // 2-parameter case: (apiKey, relyingPartyId)
                env = undefined;
                apiKey = envOrApiKey;
                relyingPartyId = apiKeyOrRelyingPartyId;
                opts = undefined;
            }
        }
        else if (arguments.length === 3) {
            if (isEnvironment(envOrApiKey)) {
                // 3-parameter case: (env, apiKey, relyingPartyId)
                env = envOrApiKey;
                apiKey = apiKeyOrRelyingPartyId;
                relyingPartyId = relyingPartyIdOrOpts;
                opts = undefined;
            }
            else {
                // 3-parameter case: (apiKey, relyingPartyId, opts)
                env = undefined;
                apiKey = envOrApiKey;
                relyingPartyId = apiKeyOrRelyingPartyId;
                opts = relyingPartyIdOrOpts;
            }
        }
        else {
            // 4-parameter case: (env, apiKey, relyingPartyId, opts)
            env = envOrApiKey;
            apiKey = apiKeyOrRelyingPartyId;
            relyingPartyId = relyingPartyIdOrOpts;
            opts = optsArg;
        }
        env = ParaCore.resolveEnvironment(env, apiKey); // Ensure the environment is resolved before calling super
        super(env, apiKey, opts);
        this.isNativePasskey = true;
        setEnv(env);
        if (relyingPartyId) {
            this.relyingPartyId = relyingPartyId;
        }
        else {
            switch (env) {
                case Environment.DEV:
                    throw new Error('relyingPartyId is required');
                case Environment.SANDBOX:
                    this.relyingPartyId = 'app.sandbox.usecapsule.com';
                    break;
                case Environment.BETA:
                    this.relyingPartyId = 'app.beta.usecapsule.com';
                    break;
                case Environment.PROD:
                    this.relyingPartyId = 'app.usecapsule.com';
                    break;
            }
        }
    }
    setup() {
        return __awaiter(this, void 0, void 0, function* () {
            this.isSetup = true;
        });
    }
    getPlatformUtils() {
        return new ReactNativeUtils();
    }
    isPasskeySupported() {
        return __awaiter(this, void 0, void 0, function* () {
            return Passkey.isSupported();
        });
    }
    registerPasskey(authStateOrPasskeyId) {
        return __awaiter(this, void 0, void 0, function* () {
            const passkeyId = typeof authStateOrPasskeyId === 'string' ? authStateOrPasskeyId : authStateOrPasskeyId.passkeyId;
            if (!passkeyId) {
                throw new Error('Passkey ID not found. Make sure you have enabled passkey logins in the Para Developer Portal.');
            }
            const userId = this.assertUserId();
            const authInfo = this.assertIsAuthSet();
            if (!globalThis.crypto || !globalThis.crypto.getRandomValues) {
                throw new Error('Web crypto is not available. Ensure you have imported the shim from @getpara/react-native-wallet.');
            }
            const userHandle = new Uint8Array(32);
            globalThis.crypto.getRandomValues(userHandle);
            const userHandleEncoded = base64url.encode(userHandle);
            const requestJson = {
                authenticatorSelection: {
                    authenticatorAttachment: 'platform',
                    requireResidentKey: true,
                    residentKey: 'required',
                    userVerification: 'required',
                },
                rp: {
                    id: this.relyingPartyId,
                    name: 'Para',
                },
                user: {
                    id: userHandleEncoded,
                    name: authInfo.identifier,
                    displayName: authInfo.identifier,
                },
                pubKeyCredParams: [
                    {
                        type: 'public-key',
                        alg: ES256_ALGORITHM,
                    },
                    {
                        type: 'public-key',
                        alg: RS256_ALGORITHM,
                    },
                ],
                attestation: 'direct',
                timeout: 60000,
                challenge: base64url.encode('somechallenge'),
            };
            let result;
            try {
                result = yield Passkey.create(requestJson);
            }
            catch (error) {
                const enriched = mapPasskeyError(error);
                logPasskeyDiagnostic(enriched, 'registration', this.relyingPartyId);
                throw enriched;
            }
            let resultJson;
            if (typeof result === 'string') {
                resultJson = JSON.parse(result);
            }
            else {
                resultJson = result;
            }
            const { cosePublicKey, clientDataJSON } = parseCredentialCreationRes(resultJson, ES256_ALGORITHM);
            const keyPair = yield getAsymmetricKeyPair(this.ctx);
            const publicKeyHex = getPublicKeyHex(keyPair);
            const encryptionKeyHash = getSHA256HashHex(userHandleEncoded);
            const encryptedPrivateKeyHex = yield encryptPrivateKey(keyPair, userHandleEncoded);
            const { partnerId } = yield this.ctx.client.touchSession();
            yield this.ctx.client.patchSessionPublicKey(partnerId, userId, passkeyId, {
                publicKey: resultJson.id,
                sigDerivedPublicKey: publicKeyHex,
                cosePublicKey,
                clientDataJSON,
                status: AuthMethodStatus.COMPLETE,
            });
            yield this.ctx.client.uploadEncryptedWalletPrivateKey(userId, encryptedPrivateKeyHex, encryptionKeyHash, resultJson.id);
        });
    }
    /**
     * Logs in the user using their authentication credentials.
     * @param {AuthParams} params - The authentication parameters.
     * @returns {Promise<void>}
     */
    loginWithPasskey() {
        return __awaiter(this, void 0, void 0, function* () {
            this.assertIsAuthSet();
            const userId = this.assertUserId();
            const { challenge, allowedPublicKeys } = yield this.ctx.client.getWebChallenge({ userId });
            const requestJson = {
                challenge,
                timeout: 60000,
                rpId: this.relyingPartyId,
                allowCredentials: (allowedPublicKeys === null || allowedPublicKeys === void 0 ? void 0 : allowedPublicKeys[0]) ? [{ type: 'public-key', id: allowedPublicKeys[0] }] : [],
            };
            let result;
            try {
                result = yield Passkey.get(requestJson);
            }
            catch (error) {
                const enriched = mapPasskeyError(error);
                logPasskeyDiagnostic(enriched, 'login', this.relyingPartyId);
                throw enriched;
            }
            let resultJson;
            if (typeof result === 'string') {
                resultJson = JSON.parse(result);
            }
            else {
                resultJson = result;
            }
            const { partnerId, sessionLookupId } = yield this.ctx.client.touchSession();
            const publicKey = resultJson.id;
            const verifyWebChallengeResult = yield this.ctx.client.verifyWebChallenge(partnerId, {
                publicKey,
                signature: {
                    clientDataJSON: resultJson.response.clientDataJSON,
                    authenticatorData: resultJson.response.authenticatorData,
                    signature: resultJson.response.signature,
                },
            });
            if (userId !== verifyWebChallengeResult.data.userId) {
                throw new Error('User ID mismatch');
            }
            const encryptedSharesResult = yield this.ctx.client.getBiometricKeyshares(userId, resultJson.id);
            const encryptionKeyHash = getSHA256HashHex(resultJson.response.userHandle);
            const { encryptedPrivateKeys } = yield this.ctx.client.getEncryptedWalletPrivateKeys(userId, encryptionKeyHash);
            let decryptedShares;
            if (encryptedPrivateKeys.length === 0) {
                decryptedShares = yield getDerivedPrivateKeyAndDecrypt(this.ctx, resultJson.response.userHandle, encryptedSharesResult.data.keyShares);
                const keyPair = yield getAsymmetricKeyPair(this.ctx, resultJson.response.userHandle);
                const encryptedPrivateKeyHex = yield encryptPrivateKey(keyPair, resultJson.response.userHandle);
                yield this.ctx.client.uploadEncryptedWalletPrivateKey(userId, encryptedPrivateKeyHex, encryptionKeyHash, resultJson.id);
            }
            else {
                decryptedShares = yield decryptPrivateKeyAndDecryptShare(resultJson.response.userHandle, encryptedSharesResult.data.keyShares, encryptedPrivateKeys[0].encryptedPrivateKey);
            }
            const walletsRes = yield this.ctx.client.getWallets(userId);
            const desiredWallets = walletsRes.data.wallets;
            const walletsToInsert = {};
            for (let desiredWallet of desiredWallets) {
                const decryptedShare = decryptedShares.find(share => share.walletId === desiredWallet.id);
                walletsToInsert[decryptedShare.walletId] = {
                    id: decryptedShare.walletId,
                    signer: decryptedShare.signer,
                    address: desiredWallet.address || undefined,
                    publicKey: desiredWallet.publicKey || undefined,
                    scheme: desiredWallet.scheme,
                    type: desiredWallet.type || undefined,
                };
            }
            const currentWalletIds = {};
            for (const wallet of Object.values(walletsToInsert)) {
                const { id, type } = wallet;
                const currentIdsForType = currentWalletIds[type || 'EVM'] || [];
                currentWalletIds[type || 'EVM'] = [...currentIdsForType, id];
            }
            yield this.setWallets(walletsToInsert);
            yield this.setCurrentWalletIds(currentWalletIds, {
                sessionLookupId,
            });
        });
    }
    /**
     * Sets a global handler for transaction review URLs. When a signing operation requires
     * user approval (e.g., due to permissions policies), the SDK needs to open a review URL.
     * On React Native, provide a handler that opens the URL in an in-app browser.
     *
     * @example
     * ```ts
     * import { openBrowserAsync } from 'expo-web-browser';
     * para.setTransactionReviewHandler((url) => openBrowserAsync(url));
     * ```
     */
    setTransactionReviewHandler(handler) {
        this.platformUtils.transactionReviewHandler = handler;
    }
    /**
     * Handles a message from an in-app browser (WebView).
     * Call this from your WebView's `onMessage` callback to bridge portal events
     * (e.g. TELEGRAM_SUCCESS, FARCASTER_SUCCESS) into the SDK's event system.
     *
     * @example
     * ```tsx
     * <WebView
     *   source={{ uri: portalUrl }}
     *   onMessage={(event) => {
     *     const data = JSON.parse(event.nativeEvent.data);
     *     para.handleWebViewMessage(data);
     *   }}
     * />
     * ```
     */
    handleWebViewMessage(data) {
        var _a, _b;
        (_b = (_a = this.platformUtils).postMessage) === null || _b === void 0 ? void 0 : _b.call(_a, { data });
    }
}
