/**
 * MessageBus
 *
 * Handles bidirectional postMessage communication between the parent page
 * (IframeKeyVault) and the cross-origin iframe (KeyVaultHost).
 * Provides request/response correlation, timeouts, origin validation,
 * and wallet bridge routing.
 */
import { PROTOCOL_ID, PROTOCOL_VERSION, isProtocolMessage, createMessageId, getTimeout, isIframeBridgeOp, } from './protocol';
export class VaultRequestError extends Error {
    code;
    requestType;
    constructor(message, code, requestType) {
        super(message);
        this.code = code;
        this.requestType = requestType;
        this.name = 'VaultRequestError';
    }
}
export class VaultTimeoutError extends Error {
    constructor(message) {
        super(message);
        this.name = 'VaultTimeoutError';
    }
}
export class MessageBus {
    targetWindow;
    targetOrigin;
    pending = new Map();
    walletHandler = null;
    boundHandler;
    destroyed = false;
    constructor(targetWindow, targetOrigin) {
        this.targetWindow = targetWindow;
        this.targetOrigin = targetOrigin;
        this.boundHandler = this.handleMessage.bind(this);
        window.addEventListener('message', this.boundHandler);
    }
    async request(type, payload, options) {
        if (this.destroyed) {
            throw new VaultRequestError('MessageBus has been destroyed', 'DESTROYED', type);
        }
        const id = createMessageId();
        const timeout = options?.timeout ?? getTimeout(type);
        return new Promise((resolve, reject) => {
            const timer = setTimeout(() => {
                this.pending.delete(id);
                reject(new VaultTimeoutError(`Request "${type}" timed out after ${timeout}ms`));
            }, timeout);
            this.pending.set(id, { resolve, reject, timer, expectedType: type });
            const message = {
                protocol: PROTOCOL_ID,
                version: PROTOCOL_VERSION,
                id,
                type,
                direction: 'request',
                payload,
            };
            this.targetWindow.postMessage(message, this.targetOrigin);
        });
    }
    onWalletRequest(handler) {
        this.walletHandler = handler;
    }
    destroy() {
        this.destroyed = true;
        window.removeEventListener('message', this.boundHandler);
        for (const [, req] of this.pending) {
            clearTimeout(req.timer);
            req.reject(new VaultRequestError('MessageBus destroyed', 'DESTROYED', req.expectedType));
        }
        this.pending.clear();
    }
    handleMessage(event) {
        if (event.origin !== this.targetOrigin)
            return;
        if (event.source !== this.targetWindow)
            return;
        const data = event.data;
        if (!isProtocolMessage(data))
            return;
        // Wallet bridge requests from iframe → parent
        if (data.direction === 'request' && isIframeBridgeOp(data.type)) {
            this.handleWalletBridgeRequest(data.id, data.type, data.payload);
            return;
        }
        if (data.direction !== 'response')
            return;
        // Response to a pending request
        const req = this.pending.get(data.id);
        if (!req)
            return;
        if (data.type !== req.expectedType)
            return;
        clearTimeout(req.timer);
        this.pending.delete(data.id);
        const response = data;
        if (response.error) {
            req.reject(new VaultRequestError(response.error.message, response.error.code, response.type));
        }
        else {
            req.resolve(response);
        }
    }
    async handleWalletBridgeRequest(id, type, payload) {
        if (!this.walletHandler) {
            this.sendWalletReply(id, type, undefined, {
                message: 'No wallet connected',
            });
            return;
        }
        try {
            const result = await this.walletHandler(type, payload);
            this.sendWalletReply(id, type, result);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            this.sendWalletReply(id, type, undefined, { message });
        }
    }
    sendWalletReply(id, type, payload, error) {
        if (this.destroyed)
            return;
        this.targetWindow.postMessage({
            protocol: PROTOCOL_ID,
            version: PROTOCOL_VERSION,
            id,
            type,
            direction: 'response',
            payload,
            error,
        }, this.targetOrigin);
    }
}
//# sourceMappingURL=message-bus.js.map