/**
 * PrivacyBoost SDK (WASM-powered)
 *
 * Main SDK class that provides a high-level interface for privacy operations.
 * Delegates all cryptographic operations to the Rust WASM module.
 *
 * @example
 * ```typescript
 * const sdk = await PrivacyBoost.create({
 *   serverUrl: 'https://indexer.example.com',
 *   appId: 'my-app',
 * });
 * ```
 */
import { loadWasm, releaseWasm, isClient, isWasmSupported, WasmLoadError, } from './core';
import { VaultResource } from './resources/Vault';
import { AuthResource } from './resources/Auth';
import { TransactionsResource } from './resources/Transactions';
import { AuditResource } from './resources/Audit';
import { AppsResource } from './resources/Apps';
import { SDKStores } from './state/stores';
import { PrivacyBoostError, ErrorCodes } from './errors';
import { ChainClient } from './ChainClient';
import { LocalKeyVault } from './keyvault/LocalKeyVault';
import { IframeKeyVault } from './keyvault/IframeKeyVault';
import { IndexerClient } from './api/indexer-client';
import { AuthClient } from './api/auth-client';
import { createLogger, LogLevel } from './utils/logger';
import { convertMapsToObjects } from './keyvault/schemas';
const LOG_LEVEL_MAP = {
    silent: LogLevel.silent,
    error: LogLevel.error,
    warn: LogLevel.warn,
    info: LogLevel.info,
    debug: LogLevel.debug,
};
/**
 * PrivacyBoost SDK
 *
 * The main entry point for using the PrivacyBoost SDK.
 * Provides access to privacy operations including shields, unshields,
 * private transfers.
 */
export class PrivacyBoost {
    config;
    wasmSDK = null;
    _keyVault = null;
    _indexerClient = null;
    _authClient = null;
    _jwt = null;
    initialized = false;
    /** Per-instance logger. Level is controlled by `config.logLevel`. */
    logger;
    /**
     * Per-instance stores. Each SDK instance gets independent state.
     * @internal
     */
    stores;
    // Resource instances (lazy-initialized)
    _vault = null;
    _auth = null;
    _transactions = null;
    _audit = null;
    _apps = null;
    // Multi-chain: cached chain clients keyed by serverUrl
    chainClients = new Map();
    // Session event listeners
    _eventListeners = {};
    /** @internal Use `PrivacyBoost.create()` instead. */
    constructor(config) {
        this.config = { ...config };
        this.logger = createLogger(LOG_LEVEL_MAP[config.logLevel ?? 'silent'] ?? LogLevel.silent);
        this.stores = new SDKStores();
    }
    /**
     * Create and initialize a PrivacyBoost SDK instance.
     *
     * Only `serverUrl` and `appId` are required. `chainId`, `shieldContractAddress`,
     * `tokenRegistryAddress`, and `teePublicKey` are auto-discovered from the
     * indexer's `/api/v1/info` endpoint. Explicit values take precedence.
     *
     * @param config - SDK configuration
     * @returns Initialized SDK instance
     * @throws {PrivacyBoostError} `SDK_INITIALIZATION_FAILED` if initialization fails
     *
     * @example
     * ```typescript
     * const sdk = await PrivacyBoost.create({
     *   serverUrl: 'https://indexer.example.com',
     *   appId: 'my-app',
     * });
     * ```
     */
    static async create(config) {
        const sdk = new PrivacyBoost(config);
        await sdk.initialize();
        return sdk;
    }
    /**
     * Check if the SDK can be initialized in the current environment.
     *
     * @returns true if WASM is supported and we're in a browser
     */
    static isSupported() {
        return isClient() && isWasmSupported();
    }
    /**
     * Initialize the SDK by loading the WASM module and setting up the KeyVault.
     *
     * Must be called before any other operations.
     * This is idempotent - calling it multiple times is safe.
     *
     * @throws {PrivacyBoostError} `SDK_INITIALIZATION_FAILED` if initialization fails
     */
    async initialize() {
        if (this.initialized) {
            return;
        }
        try {
            const wasm = await loadWasm();
            const keyVaultMode = this.config.keyVault?.mode ?? 'local';
            // Create KeyVault based on mode
            if (keyVaultMode === 'iframe') {
                const iframeUrl = this.config.keyVault?.iframeUrl;
                this._keyVault = new IframeKeyVault(iframeUrl);
            }
            else {
                this._keyVault = new LocalKeyVault(wasm);
            }
            // Initialize KeyVault (passes WASM constructor config through)
            await this._keyVault.initialize({
                serverUrl: this.config.serverUrl,
                chainId: this.config.chainId ?? 0,
                shieldContractAddress: this.config.shieldContractAddress ?? '',
                wethContractAddress: this.config.wethContractAddress,
                appId: this.config.appId,
                rpcUrl: this.config.rpcUrl,
                tokenRegistryAddress: this.config.tokenRegistryAddress,
                persistence: this.config.persistence,
                autoConsolidate: this.config.features?.autoConsolidate,
                autoConsolidateThreshold: this.config.features?.autoConsolidateThreshold,
                autoConsolidateMaxRounds: this.config.features?.autoConsolidateMaxRounds,
            });
            // In local mode, hold a WASM reference for the public `wasm` getter
            // (advanced usage). Resources use IndexerClient/KeyVault, not this.
            if (keyVaultMode === 'local') {
                this.wasmSDK = this._keyVault.getWasmInstance();
            }
            // Create HTTP clients
            this._indexerClient = new IndexerClient(this.config.serverUrl, () => this._jwt);
            this._authClient = new AuthClient(this.config.serverUrl, () => this._jwt);
            // Route HTTP requests through the WASM encryption layer so that
            // non-plaintext endpoints are sent as encrypted POST envelopes.
            // Without this, direct fetch() calls to the server are rejected
            // with 405 because the server requires encrypted transport.
            if (keyVaultMode === 'iframe') {
                const iframeVault = this._keyVault;
                const proxyFn = (path, method, body, query) => iframeVault.proxyFetch(path, method, body, query);
                this._indexerClient.setProxy(proxyFn);
                this._authClient.setProxy(proxyFn);
            }
            else if (this.wasmSDK) {
                const wasm = this.wasmSDK;
                // serde_wasm_bindgen serializes Rust maps as JS Map objects;
                // convertMapsToObjects normalizes them to plain objects so
                // downstream code (Zod schemas, .map() calls) works correctly.
                const proxyFn = async (path, method, body, query) => convertMapsToObjects(await wasm.proxyFetch(path, method, body ?? null, query ?? null));
                this._indexerClient.setProxy(proxyFn);
                this._authClient.setProxy(proxyFn);
            }
            this.config = Object.freeze({ ...this.config });
            const resolvedConfig = this.config;
            const keyVault = this._keyVault;
            const indexer = this._indexerClient;
            const getMpk = () => this.stores.wallet.getState().mpk ?? keyVault.getMpk();
            this._auth = new AuthResource(resolvedConfig, this.stores, keyVault, this._authClient, this.logger);
            // In both iframe and local mode, the WASM encryption layer manages
            // its own JWT. The TypeScript-side acquireJwt() would fail for
            // encrypted endpoints (auth/upgrade) in local mode, and is
            // unnecessary since all HTTP goes through the WASM proxy.
            if (keyVaultMode === 'iframe' || this.wasmSDK) {
                this._auth.setSkipAcquireJwt(true);
            }
            this._vault = new VaultResource(keyVault, indexer, resolvedConfig, this._auth, this.stores, this.logger);
            this._transactions = new TransactionsResource(indexer, keyVault, this.stores, getMpk, this.logger);
            this._audit = new AuditResource(() => this.wasmSDK, this.logger);
            this._apps = new AppsResource(indexer);
            // Auto-sync balance when a transaction resolves (e.g. unshield confirms on-chain)
            this._transactions.setOnTransactionResolved((tx) => {
                if (tx.tokenAddress) {
                    this._vault?.refreshBalance(tx.tokenAddress).catch((e) => {
                        this.logger.warn('PrivacyBoost', 'Failed to sync balance after transaction resolved', e);
                    });
                }
            });
            // Wire up JWT propagation so IndexerClient/AuthClient can authenticate
            this._auth.setOnJwtUpdate((token) => this.setJwt(token));
            // Set up post-login callback to sync balances
            this._auth.setOnLoginComplete(async () => {
                await this._vault.refreshBalances();
            });
            // Handle wallet account changes: protocol keys are wallet-specific,
            // so a different account invalidates the current session.
            this._auth.setOnAccountChange((accounts) => {
                this.logger.warn('PrivacyBoost', `Wallet account changed (${accounts.length} account(s)), session invalidated`);
            });
            // Handle wallet chain changes
            this._auth.setOnChainChange((chainId) => {
                this.logger.warn('PrivacyBoost', `Wallet chain changed to ${chainId}`);
                this.stores.sdk.getState().setChainId(chainId);
            });
            // Wire session events
            this._auth.setOnSessionRestored((event) => this.emit('sessionRestored', event));
            this._auth.setOnSessionExpired((event) => this.emit('sessionExpired', event));
            // Update per-instance state
            this.stores.sdk.getState().setInitialized(true);
            this.initialized = true;
        }
        catch (error) {
            if (error instanceof WasmLoadError) {
                throw new PrivacyBoostError(ErrorCodes.SDK_INITIALIZATION_FAILED, 'Failed to load WASM module. Ensure you are in a browser environment.', { cause: error });
            }
            throw new PrivacyBoostError(ErrorCodes.SDK_INITIALIZATION_FAILED, 'Failed to initialize SDK', { cause: error });
        }
    }
    /**
     * Ensure SDK is initialized before accessing resources.
     */
    ensureInitialized() {
        if (!this.initialized) {
            throw new PrivacyBoostError(ErrorCodes.SDK_NOT_INITIALIZED, 'SDK not initialized. Use PrivacyBoost.create() or call initialize() first.');
        }
    }
    /**
     * Update the stored JWT token (called by AuthResource after login).
     * @internal
     */
    setJwt(token) {
        this._jwt = token;
    }
    /**
     * Get the KeyVault instance.
     * @internal
     */
    get keyVault() {
        this.ensureInitialized();
        return this._keyVault;
    }
    // ============================================================
    // Resources
    // ============================================================
    /**
     * Vault resource for shield, unshield, and send operations.
     */
    get vault() {
        this.ensureInitialized();
        return this._vault;
    }
    /**
     * Auth resource for wallet connection and authentication.
     */
    get auth() {
        this.ensureInitialized();
        return this._auth;
    }
    /**
     * Transactions resource for transaction history.
     */
    get transactions() {
        this.ensureInitialized();
        return this._transactions;
    }
    /**
     * Audit resource for auditor-specific operations.
     */
    get audit() {
        this.ensureInitialized();
        return this._audit;
    }
    /**
     * Apps resource for looking up app metadata.
     */
    get apps() {
        this.ensureInitialized();
        return this._apps;
    }
    /**
     * Access the underlying WASM SDK instance.
     *
     * For advanced usage only. Only available in local mode.
     * @throws {PrivacyBoostError} `SDK_NOT_INITIALIZED` if not initialized or in iframe mode
     */
    get wasm() {
        this.ensureInitialized();
        if (!this.wasmSDK) {
            throw new Error('Direct WASM access is not available in iframe mode. Use the SDK resource APIs instead.');
        }
        return this.wasmSDK;
    }
    /**
     * Set an external key manager for use by authenticate().
     *
     * When set, authenticate() will use this key manager's keys
     * instead of its internal one.
     *
     * Only available in local mode.
     */
    setKeyManager(keyManager) {
        this.ensureInitialized();
        if (!this.wasmSDK) {
            throw new Error('setKeyManager() is not supported in iframe mode');
        }
        this.wasmSDK.setKeyManager(keyManager);
    }
    // ============================================================
    // Events
    // ============================================================
    on(event, handler) {
        if (!this._eventListeners[event]) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            this._eventListeners[event] = new Set();
        }
        this._eventListeners[event].add(handler);
    }
    off(event, handler) {
        this._eventListeners[event]?.delete(handler);
    }
    emit(event, data) {
        const handlers = this._eventListeners[event];
        if (handlers) {
            for (const handler of handlers) {
                try {
                    handler(data);
                }
                catch (error) {
                    this.logger.error('PrivacyBoost', `Error in ${event} event handler`, error);
                }
            }
        }
    }
    // ============================================================
    // Utility Methods
    // ============================================================
    /**
     * Add tokens to track (triggers balance sync for these tokens).
     *
     * @param tokenAddresses - Array of token addresses to track
     */
    addTrackedTokens(tokenAddresses) {
        this.ensureInitialized();
        for (const address of tokenAddresses) {
            this._vault.refreshBalance(address).catch((err) => {
                this.logger.error('PrivacyBoost', 'Failed to sync balance for tracked token', err);
            });
        }
    }
    /**
     * Get the current chain ID.
     * Returns the resolved chain ID after initialization (auto-discovered or explicit).
     */
    get chainId() {
        if (!this.config.chainId) {
            throw new PrivacyBoostError(ErrorCodes.SDK_NOT_INITIALIZED, 'SDK not initialized. Use PrivacyBoost.create() or call initialize() first.');
        }
        return this.config.chainId;
    }
    /**
     * Check if the SDK is initialized.
     */
    get isInitialized() {
        return this.initialized;
    }
    /**
     * Get the current configuration (immutable).
     */
    getConfig() {
        return this.config;
    }
    /**
     * Whether auth keys are registered on-chain.
     * Checks the store state set during authentication.
     */
    get isRegistered() {
        this.ensureInitialized();
        // In local mode, delegate to WASM if available
        if (this.wasmSDK) {
            return this.wasmSDK.isRegistered;
        }
        // In iframe mode, use authenticated state as proxy for registration
        // (the auth flow registers keys if they're not already registered)
        return this._keyVault?.isAuthenticated() ?? false;
    }
    // ============================================================
    // Multi-Chain
    // ============================================================
    /**
     * Create a chain-scoped client for operations on a specific blockchain.
     *
     * The chain client shares this SDK's identity keys but has its own
     * server connection, JWT, and chain state. Call `.authenticate()` on
     * the returned client before performing operations.
     *
     * Returns a cached client if the same `serverUrl` was used before.
     *
     * @param config - Chain configuration (only `serverUrl` required)
     * @returns A chain-scoped client
     *
     * @example
     * ```typescript
     * const op = sdk.forChain({ serverUrl: 'https://op.example.com' });
     * await op.authenticate(walletBridge);
     * await op.vault.shield({ tokenAddress: '0x...', amount: 1000n });
     * ```
     */
    forChain(config) {
        this.ensureInitialized();
        const key = config.serverUrl;
        const cached = this.chainClients.get(key);
        if (cached) {
            return cached;
        }
        // In iframe mode, pass the proxy function to ChainClient so its HTTP
        // clients route through the iframe's WASM encryption layer. The proxy
        // forwards baseUrl + authToken per request, so the WASM creates a
        // per-chain EncryptedHttpClient and sets the JWT before each call.
        const keyVaultMode = this.config.keyVault?.mode ?? 'local';
        let proxyFn;
        if (keyVaultMode === 'iframe') {
            const iframeVault = this._keyVault;
            proxyFn = (path, method, body, query, baseUrl, authToken) => iframeVault.proxyFetch(path, method, body, query, baseUrl, authToken);
        }
        else if (this.wasmSDK) {
            const wasm = this.wasmSDK;
            proxyFn = async (path, method, body, query) => convertMapsToObjects(await wasm.proxyFetch(path, method, body ?? null, query ?? null));
        }
        const client = new ChainClient(config, this._keyVault, this.config.appId, (chainId, rpcUrl) => { this._auth?.switchReceiptChain(chainId, rpcUrl); }, () => this.stores.wallet.getState().address, this.stores, proxyFn, this.logger);
        this.chainClients.set(key, client);
        return client;
    }
    /**
     * Dispose the SDK and clean up all resources.
     *
     * After calling this, the SDK instance cannot be used again.
     */
    dispose() {
        // Clean up wallet event subscriptions before nulling auth
        this._auth?.dispose();
        // Dispose all chain clients
        for (const client of this.chainClients.values()) {
            client.dispose();
        }
        this.chainClients.clear();
        // Dispose KeyVault
        this._keyVault?.dispose();
        this._keyVault = null;
        this._vault = null;
        this._auth = null;
        this._transactions = null;
        this._audit = null;
        this._apps = null;
        this._indexerClient = null;
        this._authClient = null;
        this._jwt = null;
        this.wasmSDK = null;
        this.initialized = false;
        // Clear event listeners
        this._eventListeners = {};
        // Clear per-instance stores
        this.stores.resetAll();
        // Release this instance's WASM reference (only resets when no instances remain)
        releaseWasm();
    }
    // ============================================================
    // Identity Lookup
    // ============================================================
    /**
     * Look up a user's identity by Ethereum address or MPK.
     *
     * Queries the indexer's /identity/{identifier} API to retrieve the user's
     * public keys and derived privacy address. Useful for resolving an
     * Ethereum address to a privacy address before sending a transfer.
     *
     * @param identifier - Ethereum address (0x... 42 chars) or MPK (66 char hex)
     * @returns Identity result with MPK, viewing keys, and privacy address
     * @throws If not authenticated or identifier not found
     *
     * @example
     * ```typescript
     * // Look up by Ethereum address
     * const identity = await sdk.resolveIdentity('0x1234...abcd');
     * console.log('Privacy address:', identity.privacyAddress);
     *
     * // Use for transfer
     * await sdk.vault.send({
     *   to: identity.privacyAddress,
     *   amount: 1000000n,
     *   tokenAddress: '0x...',
     * });
     * ```
     */
    async resolveIdentity(identifier) {
        this.ensureInitialized();
        try {
            // In local mode, use the WASM resolveIdentity which computes the
            // privacy address client-side from the viewing keys. The raw
            // IndexerClient.lookupIdentity only returns mpk + viewing keys
            // — the server does not return privacyAddress.
            if (this.wasmSDK) {
                const wasm = this.wasmSDK;
                const raw = convertMapsToObjects(await wasm.resolveIdentity(identifier));
                return {
                    mpk: raw.mpk,
                    viewingPublicKeyX: raw.viewingPublicKeyX,
                    viewingPublicKeyY: raw.viewingPublicKeyY,
                    privacyAddress: raw.privacyAddress,
                };
            }
            const result = await this._indexerClient.lookupIdentity(identifier);
            return {
                mpk: result.mpk,
                viewingPublicKeyX: result.viewingPublicKeyX,
                viewingPublicKeyY: result.viewingPublicKeyY,
                privacyAddress: result.privacyAddress,
            };
        }
        catch (error) {
            throw PrivacyBoostError.from(error);
        }
    }
}
//# sourceMappingURL=PrivacyBoost.js.map