import { createClientChannel, } from "@seawork/relay/e2ee";
import { extractRelayMessageData, normalizeTransportPayload, } from "./daemon-client-transport-utils.js";
export function createRelayE2eeTransportFactory(args) {
    return ({ url, headers }) => {
        const base = args.baseFactory({ url, headers });
        return createEncryptedTransport(base, args.daemonPublicKeyB64, args.logger);
    };
}
export function createEncryptedTransport(base, daemonPublicKeyB64, logger) {
    let channel = null;
    let opened = false;
    let closed = false;
    const openHandlers = new Set();
    const closeHandlers = new Set();
    const errorHandlers = new Set();
    const messageHandlers = new Set();
    const emitOpen = () => {
        if (opened || closed) {
            return;
        }
        opened = true;
        emitHandlers(openHandlers);
    };
    const emitClose = (event) => {
        if (closed) {
            return;
        }
        closed = true;
        emitHandlers(closeHandlers, event);
    };
    const emitError = (event) => {
        if (closed) {
            return;
        }
        emitHandlers(errorHandlers, event);
    };
    const emitMessage = (data) => {
        if (closed) {
            return;
        }
        emitHandlers(messageHandlers, data);
    };
    const relayTransport = {
        send: (data) => {
            if (typeof data === "string") {
                base.send(data);
                return;
            }
            if (ArrayBuffer.isView(data)) {
                base.send(normalizeTransportPayload(data));
                return;
            }
            if (data instanceof ArrayBuffer) {
                base.send(data);
                return;
            }
            base.send(String(data));
        },
        close: (code, reason) => base.close(code, reason),
        onmessage: null,
        onclose: null,
        onerror: null,
    };
    const startHandshake = async () => {
        try {
            channel = await createClientChannel(relayTransport, daemonPublicKeyB64, {
                onopen: emitOpen,
                onmessage: (data) => emitMessage(data),
                onclose: (code, reason) => emitClose({ code, reason }),
                onerror: (error) => emitError(error),
            });
        }
        catch (error) {
            logger.warn({ err: normalizeTransportError(error) }, "relay_e2ee_handshake_failed");
            emitError(error);
            // Browser WebSocket.close only accepts 1000 or 3000-4999.
            // Use an app-defined code so this path works in browser and Node runtimes.
            base.close(4001, "E2EE handshake failed");
        }
    };
    base.onOpen(() => {
        void startHandshake();
    });
    base.onMessage((event) => {
        relayTransport.onmessage?.(extractRelayMessageData(event));
    });
    base.onClose((event) => {
        const record = event;
        relayTransport.onclose?.(record?.code ?? 0, record?.reason ?? "");
        emitClose(event);
    });
    base.onError((event) => {
        relayTransport.onerror?.(event instanceof Error ? event : new Error(String(event)));
        emitError(event);
    });
    return {
        send: (data) => {
            if (!channel) {
                throw new Error("Encrypted channel not ready");
            }
            void channel.send(normalizeTransportPayload(data)).catch((error) => {
                emitError(error);
            });
        },
        close: (code, reason) => {
            if (channel) {
                channel.close(code, reason);
            }
            else {
                base.close(code, reason);
            }
            emitClose({ code, reason });
        },
        onMessage: (handler) => {
            messageHandlers.add(handler);
            return () => messageHandlers.delete(handler);
        },
        onOpen: (handler) => {
            openHandlers.add(handler);
            if (opened) {
                invokeHandler(handler);
            }
            return () => openHandlers.delete(handler);
        },
        onClose: (handler) => {
            closeHandlers.add(handler);
            if (closed) {
                invokeHandler(handler);
            }
            return () => closeHandlers.delete(handler);
        },
        onError: (handler) => {
            errorHandlers.add(handler);
            return () => errorHandlers.delete(handler);
        },
    };
}
function emitHandlers(handlers, ...args) {
    for (const handler of handlers) {
        invokeHandler(handler, ...args);
    }
}
function invokeHandler(handler, ...args) {
    try {
        handler(...args);
    }
    catch {
        // no-op
    }
}
function normalizeTransportError(error) {
    if (error instanceof Error) {
        return {
            name: error.name,
            message: error.message,
            ...(typeof error.stack === "string" ? { stack: error.stack } : {}),
        };
    }
    return { message: String(error) };
}
//# sourceMappingURL=daemon-client-relay-e2ee-transport.js.map