import { Platform } from 'react-native';
import { Buffer } from 'buffer';
// Lazy-loaded crypto module - initialized on first use to avoid native module timing issues
let _crypto = null;
let _cryptoInitialized = false;
/**
 * Lazily load the crypto module.
 *
 * Supports customer pre-initialization via global.__QuickCrypto for environments
 * where native module timing is unpredictable (e.g., certain React Native setups).
 *
 * @returns The crypto module or null if unavailable
 */
function getCrypto() {
    if (_cryptoInitialized) {
        return _crypto;
    }
    _cryptoInitialized = true;
    if (Platform.OS === 'web') {
        // On Web, we shouldn't be using this provider (Core SDK has WebDPoPProvider)
        _crypto = {
            generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); },
            randomUUID: () => globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`
        };
        return _crypto;
    }
    // Check for customer pre-initialized global (for native module timing issues)
    if (typeof global !== 'undefined' && global.__QuickCrypto) {
        _crypto = global.__QuickCrypto;
        return _crypto;
    }
    // Standard lazy load
    try {
        _crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
    }
    catch (e) {
        console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto.', e);
        console.warn('Tip: If you encounter timing issues, pre-initialize in index.js: global.__QuickCrypto = require("react-native-quick-crypto")');
        _crypto = null;
    }
    return _crypto;
}
export class ReactNativeDPoPProvider {
    /**
     * Generates a new key pair (ES256 recommended)
     *
     * Uses WebCrypto API (crypto.subtle) for cross-platform compatibility.
     * Falls back to Node.js crypto API on iOS if needed.
     */
    async generateKeyPair(options) {
        const crypto = getCrypto();
        if (!crypto) {
            throw new Error('Crypto module not available. Install react-native-quick-crypto or pre-initialize global.__QuickCrypto');
        }
        // Try WebCrypto API first (works on both iOS and Android)
        if (crypto.subtle) {
            try {
                const keyPair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, // extractable - required for JWK export
                ['sign', 'verify']);
                const publicKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
                const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
                return {
                    publicKey: publicKeyJwk,
                    privateKey: privateKeyJwk
                };
            }
            catch (err) {
                console.warn('[DPoP] WebCrypto API failed, trying Node.js crypto API', err);
            }
        }
        // Fallback: Node.js crypto API (works on iOS)
        return new Promise((resolve, reject) => {
            crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err, publicKey, privateKey) => {
                if (err)
                    return reject(err);
                try {
                    const pJwk = publicKey.export({ format: 'jwk' });
                    const prJwk = privateKey.export({ format: 'jwk' });
                    resolve({ publicKey: pJwk, privateKey: prJwk });
                }
                catch (exportErr) {
                    reject(exportErr);
                }
            });
        });
    }
    async signProof(payload, keyPair) {
        const crypto = getCrypto();
        if (!crypto) {
            throw new Error('Crypto module not available for signing');
        }
        // 1. Construct Header
        const header = {
            typ: 'dpop+jwt',
            alg: 'ES256',
            jwk: keyPair.publicKey
        };
        // 2. Add Claims (iat/jti)
        const finalPayload = {
            ...payload,
            iat: payload.iat || Math.floor(Date.now() / 1000),
            jti: payload.jti || (crypto.randomUUID?.() || `${Date.now()}-${Math.random()}`)
        };
        // 3. Encode
        const b64Header = this.base64Url(JSON.stringify(header));
        const b64Payload = this.base64Url(JSON.stringify(finalPayload));
        const signingInput = `${b64Header}.${b64Payload}`;
        // 4. Sign - try WebCrypto first, fallback to Node.js crypto
        if (crypto.subtle) {
            try {
                const privateKey = await crypto.subtle.importKey('jwk', keyPair.privateKey, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
                const signatureBuffer = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, privateKey, Buffer.from(signingInput));
                // WebCrypto returns signature in IEEE P1363 format (r || s), which is what we need for JWT
                return `${signingInput}.${this.base64UrlBuffer(Buffer.from(signatureBuffer))}`;
            }
            catch (err) {
                console.warn('[DPoP] WebCrypto sign failed, trying Node.js crypto', err);
            }
        }
        // Fallback: Node.js crypto API
        const sign = crypto.createSign('SHA256');
        sign.update(signingInput);
        const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey, format: 'jwk' });
        const signature = sign.sign(privateKeyObj);
        return `${signingInput}.${this.base64UrlBuffer(signature)}`;
    }
    async hashAccessToken(accessToken) {
        const crypto = getCrypto();
        if (!crypto) {
            throw new Error('Crypto module not available for hashing');
        }
        // Try WebCrypto first
        if (crypto.subtle) {
            try {
                const hashBuffer = await crypto.subtle.digest('SHA-256', Buffer.from(accessToken));
                return this.base64UrlBuffer(Buffer.from(hashBuffer));
            }
            catch (err) {
                // Fallback to Node.js crypto
            }
        }
        const hash = crypto.createHash('sha256').update(accessToken).digest();
        return this.base64UrlBuffer(hash);
    }
    // --- Helpers ---
    base64Url(str) {
        return this.base64UrlBuffer(Buffer.from(str));
    }
    base64UrlBuffer(buf) {
        const buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
        return buffer.toString('base64')
            .replace(/=/g, '')
            .replace(/\+/g, '-')
            .replace(/\//g, '_');
    }
}
