import { io } from 'socket.io-client';
import { connectorClientVersion } from './connector-client-version.js';
import { loadPairingIdentity } from './pairing-identity.js';
const CLIENT_EVENTS = [
    'command',
    'agents:history',
    'agents:subscribe',
    'agents:unsubscribe',
    'agents:focus',
    'agents:refresh',
];
export class RelayUpstream {
    config;
    onClientEvent;
    onConnect;
    socket = null;
    keepaliveTimer = null;
    constructor(config, onClientEvent, onConnect) {
        this.config = config;
        this.onClientEvent = onClientEvent;
        this.onConnect = onConnect;
    }
    registerPairing() {
        if (!this.socket?.connected)
            return;
        const roomId = this.config.relayRoomId;
        const tokens = [];
        if (this.config.pairingClientToken)
            tokens.push(this.config.pairingClientToken);
        if (tokens.length) {
            this.socket.emit('relay:register-clients', { roomId, clientTokens: tokens });
        }
        const identity = loadPairingIdentity();
        if (identity &&
            identity.roomId === roomId &&
            identity.pairingCode &&
            identity.pairingCodeExpiresAt > Date.now()) {
            this.socket.emit('relay:register-pairing-code', {
                roomId,
                code: identity.pairingCode,
                clientToken: identity.clientToken,
                expiresAt: identity.pairingCodeExpiresAt,
                machineLabel: identity.machineLabel,
            });
        }
    }
    connect() {
        if (!this.config.relayUrl)
            return;
        const url = this.config.relayUrl.replace(/\/$/, '');
        this.socket = io(url, {
            transports: ['websocket', 'polling'],
            reconnection: true,
            reconnectionAttempts: Infinity,
            auth: {
                role: 'connector',
                token: this.config.relayToken,
                roomId: this.config.relayRoomId,
                clientKind: 'connector',
                clientVersion: connectorClientVersion(),
                machineLabel: loadPairingIdentity()?.machineLabel ?? '',
            },
        });
        this.socket.on('connect', () => {
            console.log(`[relay-upstream] connected to ${url} room=${this.config.relayRoomId}`);
            this.registerPairing();
            this.onConnect?.();
        });
        this.socket.on('connect_error', (err) => {
            const v = connectorClientVersion();
            console.warn(`[relay-upstream] connect_error: ${err.message} (clientVersion=${v})`);
            if (String(err.message).includes('CLI_VERSION_OUTDATED') ||
                String(err.message).includes('CLI_VERSION_MISSING')) {
                console.warn('[relay-upstream] Обновите cursorconnect: npm install -g cursorconnect@latest && cursorconnect start');
            }
        });
        this.socket.on('disconnect', (reason) => {
            console.log('[relay-upstream] disconnected:', reason);
        });
        for (const event of CLIENT_EVENTS) {
            this.socket.on(event, (...args) => {
                this.onClientEvent(event, ...args);
            });
        }
        this.socket.on('relay:sync', () => {
            this.onClientEvent('relay:sync');
        });
        this.socket.on('relay:http', (req) => {
            void this.handleHttpProxy(req);
        });
        this.startKeepalive();
    }
    startKeepalive() {
        this.stopKeepalive();
        const ms = this.config.relayKeepaliveMs;
        if (!this.config.relayUrl || ms <= 0)
            return;
        this.keepaliveTimer = setInterval(() => {
            if (!this.socket?.connected)
                return;
            this.socket.emit('relay:connector-ping', {
                roomId: this.config.relayRoomId,
                ts: Date.now(),
            });
        }, ms);
    }
    stopKeepalive() {
        if (this.keepaliveTimer)
            clearInterval(this.keepaliveTimer);
        this.keepaliveTimer = null;
    }
    emit(event, ...args) {
        if (this.socket?.connected)
            this.socket.emit(event, ...args);
    }
    get connected() {
        return this.socket?.connected ?? false;
    }
    async handleHttpProxy(req) {
        if (!this.socket?.connected)
            return;
        const base = `http://127.0.0.1:${this.config.serverPort}`;
        const qs = new URLSearchParams(req.query).toString();
        const url = `${base}${req.path}${qs ? `?${qs}` : ''}`;
        const headers = new Headers(req.headers);
        let body;
        if (req.bodyBase64) {
            body = Buffer.from(req.bodyBase64, 'base64');
            if (!headers.has('content-type')) {
                headers.set('content-type', 'application/json');
            }
        }
        try {
            const res = await fetch(url, {
                method: req.method,
                headers,
                body: body,
            });
            const buf = Buffer.from(await res.arrayBuffer());
            const outHeaders = {};
            res.headers.forEach((v, k) => {
                outHeaders[k] = v;
            });
            const payload = {
                id: req.id,
                status: res.status,
                headers: outHeaders,
                bodyBase64: buf.toString('base64'),
            };
            this.socket.emit('relay:http:response', payload);
        }
        catch (err) {
            const payload = {
                id: req.id,
                status: 502,
                headers: { 'content-type': 'application/json' },
                bodyBase64: Buffer.from(JSON.stringify({ error: err.message })).toString('base64'),
            };
            this.socket.emit('relay:http:response', payload);
        }
    }
}
