/**
 * Chain-scoped client for multi-chain SDK operations.
 *
 * Created via `sdk.forChain(config)`. Each ChainClient has its own server
 * connection, JWT token, and chain state, but shares identity keys
 * with the parent PrivacyBoost SDK.
 *
 * @example
 * ```typescript
 * const op = sdk.forChain({ serverUrl: 'https://op.example.com' });
 * await op.authenticate(walletBridge);
 * await op.vault.shield({ tokenAddress: '0x...', amount: 1000n });
 * ```
 */
import { VaultResource } from './resources/Vault';
import { TransactionsResource } from './resources/Transactions';
import { AuditResource } from './resources/Audit';
import { SDKStores } from './state/stores';
import { PrivacyBoostError } from './errors';
import { IndexerClient } from './api/indexer-client';
import { AuthClient } from './api/auth-client';
import { silentLogger } from './utils/logger';
/**
 * A chain-scoped client for performing operations on a specific blockchain.
 *
 * Shares the parent SDK's cryptographic identity but maintains independent
 * auth state, balances, and transactions per chain.
 */
export class ChainClient {
    appId;
    chainConfig;
    keyVault;
    indexer;
    authClient;
    onSwitchReceiptChain;
    onGetWalletAddress;
    _authenticated = false;
    _chainId = 0;
    _jwt = null;
    log;
    /** Per-chain state stores (balances, transactions, tokens) */
    stores;
    _vault = null;
    _transactions = null;
    _audit = null;
    /** @internal Use `sdk.forChain(config)` to create instances. */
    constructor(config, keyVault, appId, onSwitchReceiptChain, onGetWalletAddress, parentStores, proxyFetch, logger) {
        this.appId = appId;
        this.chainConfig = config;
        this.keyVault = keyVault;
        this.log = logger ?? silentLogger;
        // Create clients with a JWT getter that tracks this instance's JWT
        this.indexer = new IndexerClient(config.serverUrl, () => this._jwt);
        this.authClient = new AuthClient(config.serverUrl, () => this._jwt);
        if (proxyFetch) {
            this.indexer.setProxy(proxyFetch);
            this.authClient.setProxy(proxyFetch);
        }
        this.onSwitchReceiptChain = onSwitchReceiptChain;
        this.onGetWalletAddress = onGetWalletAddress;
        this._chainId = config.chainId ?? 0;
        // Each chain gets its own stores — balance and token data must be
        // isolated per chain to prevent cross-chain overwrites (setBalances
        // replaces the entire Map, so chain B's sync would wipe chain A's data).
        this.stores = new SDKStores();
    }
    /**
     * Whether auth keys are registered on this chain.
     */
    get isRegistered() {
        return this._authenticated && this.keyVault.isAuthenticated();
    }
    /**
     * Authenticate with this chain's server.
     *
     * Orchestrates the full auth flow using KeyVault for signing and
     * AuthClient for the chain's server endpoints.
     *
     * Must be called before any vault or transaction operations.
     */
    async authenticate(adapter) {
        try {
            const address = await adapter.getAddress();
            const chainId = await adapter.getChainId();
            // Phase A: Wallet login (EIP-191 personal_sign)
            const { nonce: loginNonce } = await this.authClient.getChallenge();
            const loginMessage = `Login to PrivacyBoost\nApp: ${this.appId}\nWallet: ${address}\nNonce: ${loginNonce}`;
            const signature = await adapter.signMessage(loginMessage);
            const loginResult = await this.authClient.walletLogin({
                address,
                signature,
                nonce: loginNonce,
                chainId,
                appId: this.appId,
            });
            this._jwt = loginResult.token;
            // Phase B: Upgrade with privacy keys (viewauth signature)
            const mpk = this.keyVault.getMpk();
            if (mpk) {
                try {
                    const { nonce: upgradeNonce } = await this.authClient.getChallenge();
                    const viewAuth = await this.keyVault.signViewAuth(mpk, upgradeNonce);
                    const publicKeys = await this.keyVault.getPublicKeys();
                    const regData = await this.keyVault.getRegistrationData();
                    const upgradeResult = await this.authClient.authUpgrade({
                        mpk,
                        viewauthSignature: viewAuth.signature,
                        nonce: upgradeNonce,
                        accountId: publicKeys.accountId,
                        viewingPublicKey: viewAuth.viewingPublicKey,
                        nullifyingKey: publicKeys.nullifyingPublicKey.x,
                        walletAddress: address,
                        authPkX: regData.authPkX,
                        authPkY: regData.authPkY,
                    });
                    this._jwt = upgradeResult.token;
                }
                catch (upgradeError) {
                    this.log.warn('ChainClient', 'JWT upgrade with privacy keys failed, using basic token', upgradeError);
                }
            }
            else {
                this.log.warn('ChainClient', 'Privacy keys not available (getMpk() returned null). Skipping auth upgrade — operations requiring an upgraded JWT will fail.');
            }
            this._authenticated = true;
        }
        catch (error) {
            throw PrivacyBoostError.from(error);
        }
        // Auto-discover chain ID if not provided
        if (!this._chainId) {
            try {
                const info = await this.indexer.getServerInfo();
                this._chainId = info.chainId;
            }
            catch {
                // Keep the configured chainId (0 if not set)
            }
        }
        // Point the shared wallet proxy's waitForTransactionReceipt at this
        // chain's RPC so shield/unshield receipts are queried correctly.
        this.onSwitchReceiptChain?.(this._chainId, this.chainConfig.rpcUrl);
        // Propagate wallet address to this chain's stores so that
        // VaultResource can query on-chain wallet balances and doesn't
        // short-circuit on "not authenticated".
        if (this.onGetWalletAddress) {
            const address = this.onGetWalletAddress();
            if (address) {
                this.stores.wallet.getState().setConnected(true, address);
                const mpk = this.keyVault.getMpk() ?? '';
                const privacyAddress = this.keyVault.getPrivacyAddress() ?? '';
                this.stores.wallet.getState().setAuthenticated(true, privacyAddress, mpk);
            }
        }
        // Initialize resources after auth
        this.ensureResources();
        // Sync balances after successful auth
        await this._vault.refreshBalances();
    }
    /** Ensure resources are created (lazy init after auth). */
    ensureResources() {
        if (!this._vault) {
            const resolvedConfig = {
                serverUrl: this.chainConfig.serverUrl,
                appId: '',
                chainId: this._chainId,
                shieldContractAddress: this.chainConfig.shieldContractAddress ?? '',
                wethContractAddress: this.chainConfig.wethContractAddress,
                rpcUrl: this.chainConfig.rpcUrl,
            };
            const getMpk = () => this.stores.wallet.getState().mpk || this.keyVault.getMpk();
            this._vault = new VaultResource(this.keyVault, this.indexer, resolvedConfig, null, this.stores);
            this._transactions = new TransactionsResource(this.indexer, this.keyVault, this.stores, getMpk);
            // ChainClient is the WASM-free wrapper; audit calls require the WASM SDK
            // and will throw via the resource if invoked here.
            this._audit = new AuditResource(() => null);
            // Auto-sync balance when a transaction resolves
            this._transactions.setOnTransactionResolved((tx) => {
                if (tx.tokenAddress) {
                    this._vault?.refreshBalance(tx.tokenAddress).catch((e) => {
                        this.log.warn('ChainClient', 'Failed to sync balance after transaction resolved', e);
                    });
                }
            });
        }
    }
    /**
     * Vault resource — shield, unshield, send, balances, fees.
     * @throws If not authenticated
     */
    get vault() {
        if (!this._vault) {
            throw new Error('Chain client not authenticated. Call authenticate() first.');
        }
        return this._vault;
    }
    /**
     * Transactions resource — history, status polling.
     * @throws If not authenticated
     */
    get transactions() {
        if (!this._transactions) {
            throw new Error('Chain client not authenticated. Call authenticate() first.');
        }
        return this._transactions;
    }
    /**
     * Audit resource — auditor-specific operations.
     * @throws If not authenticated
     */
    get audit() {
        if (!this._audit) {
            throw new Error('Chain client not authenticated. Call authenticate() first.');
        }
        return this._audit;
    }
    /** The chain ID (resolved after auto-discovery). */
    get chainId() {
        return this._chainId;
    }
    /** Whether this chain client has been authenticated. */
    get isAuthenticated() {
        return this._authenticated;
    }
    /**
     * Look up a user's identity by Ethereum address or MPK on this chain.
     */
    async resolveIdentity(identifier) {
        try {
            const result = await this.indexer.lookupIdentity(identifier);
            return {
                mpk: result.mpk,
                viewingPublicKeyX: result.viewingPublicKeyX,
                viewingPublicKeyY: result.viewingPublicKeyY,
                privacyAddress: result.privacyAddress,
            };
        }
        catch (error) {
            throw PrivacyBoostError.from(error);
        }
    }
    /** Clear this chain's session (JWT + pending transactions). */
    clearSession() {
        this._jwt = null;
        this._authenticated = false;
        this.stores.wallet.getState().setAuthenticated(false);
    }
    /** Clean up resources. */
    dispose() {
        this._vault = null;
        this._transactions = null;
        this._audit = null;
        this._jwt = null;
        this._authenticated = false;
        this.stores.resetAll();
    }
}
//# sourceMappingURL=ChainClient.js.map