/**
 * First-message WebSocket authentication helper.
 *
 * Opens a WebSocket, sends `{type:'auth', token}` as the first message,
 * then waits for `{type:'auth_ok'}` before resolving.  Rejects on
 * `{type:'auth_error'}`, connection error, or timeout.
 *
 * The returned WebSocket is ready for normal use — all auth-related
 * event handlers are removed before resolution.
 */

import { apiKey } from './state.ts';

export class WsAuthError extends Error {
    constructor(public readonly reason: string) {
        super(`WebSocket auth failed: ${reason}`);
        this.name = 'WsAuthError';
    }
}

/**
 * Open a WebSocket with first-message authentication.
 *
 * @param url    WebSocket URL (without ?token= query string).
 * @param token  API key to authenticate with (defaults to the global apiKey).
 *               Pass `null` explicitly when running on loopback with no keys.
 * @param timeoutMs  Time in ms to wait for auth_ok (default 5000).
 * @returns  A connected, authenticated WebSocket.
 */
export function openAuthedWs(
    url: string,
    token: string | null = apiKey ?? null,
    timeoutMs = 5000,
): Promise<WebSocket> {
    return new Promise<WebSocket>((resolve, reject) => {
        let ws: WebSocket;
        try {
            ws = new WebSocket(url);
        } catch (e: any) {
            reject(new WsAuthError(e.message ?? 'WebSocket constructor failed'));
            return;
        }

        let settled = false;
        let timer: ReturnType<typeof setTimeout> | null = null;

        const cleanup = () => {
            if (timer) { clearTimeout(timer); timer = null; }
            ws.onmessage = null;
            ws.onerror = null;
            // Keep onclose for the caller
        };

        const fail = (reason: string) => {
            if (settled) return;
            settled = true;
            cleanup();
            try { if (ws.readyState <= WebSocket.OPEN) ws.close(); } catch { /* ignore */ }
            reject(new WsAuthError(reason));
        };

        const succeed = () => {
            if (settled) return;
            settled = true;
            cleanup();
            resolve(ws);
        };

        ws.onopen = () => {
            ws.send(JSON.stringify({ type: 'auth', token }));
        };

        ws.onmessage = (event) => {
            try {
                const msg = JSON.parse(event.data);
                if (msg.type === 'auth_ok') {
                    succeed();
                } else if (msg.type === 'auth_error') {
                    fail(msg.reason ?? 'auth rejected');
                } else {
                    fail(`unexpected first message type: ${msg.type}`);
                }
            } catch {
                fail('failed to parse auth response');
            }
        };

        ws.onerror = () => {
            fail('connection error');
        };

        ws.onclose = () => {
            fail('connection closed before auth completed');
        };

        timer = setTimeout(() => {
            fail('auth timeout');
        }, timeoutMs);
    });
}
