import { connect } from 'net';
import { EventEmitter } from 'events';
import { createLogger } from './logger.js';
import { NetworkError, ClientError, ServerError, AuthError } from './errors.js';
import { generateRequestId } from './utils.js';
const logger = createLogger('bridge-client');
const REQUEST_TIMEOUT = 3 * 60 * 1000;
const CONNECT_TIMEOUT = 5 * 1000;
export class BridgeClient extends EventEmitter {
    socket = null;
    socketPath;
    buffer = '';
    pendingRequests = new Map();
    constructor(socketPath) {
        super();
        this.socketPath = socketPath;
    }
    async connect() {
        if (this.socket) {
            return;
        }
        return new Promise((resolve, reject) => {
            logger.debug(`Connecting to bridge socket: ${this.socketPath}`);
            let settled = false;
            const settle = (fn) => {
                if (!settled) {
                    settled = true;
                    clearTimeout(timeoutId);
                    fn();
                }
            };
            const timeoutId = setTimeout(() => {
                settle(() => {
                    logger.debug(`Socket connection timeout after ${CONNECT_TIMEOUT}ms`);
                    if (this.socket) {
                        this.socket.destroy();
                        this.socket = null;
                    }
                    reject(new NetworkError(`Connection to bridge timed out`));
                });
            }, CONNECT_TIMEOUT);
            this.socket = connect(this.socketPath);
            this.socket.on('connect', () => {
                settle(() => {
                    logger.debug('Connected to bridge socket');
                    this.setupSocket();
                    resolve();
                });
            });
            this.socket.on('error', (error) => {
                settle(() => {
                    logger.debug('Socket connection error:', error.message);
                    this.socket = null;
                    reject(new NetworkError(`Failed to connect to bridge: ${error.message}`));
                });
            });
        });
    }
    setupSocket() {
        if (!this.socket)
            return;
        this.socket.on('data', (data) => {
            this.buffer += data.toString();
            let newlineIndex;
            while ((newlineIndex = this.buffer.indexOf('\n')) !== -1) {
                const line = this.buffer.slice(0, newlineIndex);
                this.buffer = this.buffer.slice(newlineIndex + 1);
                if (line.trim()) {
                    try {
                        const message = JSON.parse(line);
                        this.handleMessage(message);
                    }
                    catch (error) {
                        logger.error('Failed to parse message:', error);
                    }
                }
            }
        });
        this.socket.on('end', () => {
            logger.debug('Socket disconnected');
            this.cleanup();
        });
        this.socket.on('error', (error) => {
            logger.error('Socket error:', error);
            this.cleanup();
        });
    }
    handleMessage(message) {
        logger.debug('Received message:', { type: message.type, id: message.id });
        if (message.type === 'response' && message.id) {
            const pending = this.pendingRequests.get(message.id);
            if (pending) {
                clearTimeout(pending.timeoutId);
                this.pendingRequests.delete(message.id);
                if (message.error) {
                    let error;
                    switch (message.error.code) {
                        case 1:
                            error = new ClientError(message.error.message);
                            break;
                        case 2:
                            error = new ServerError(message.error.message);
                            break;
                        case 3:
                            error = new NetworkError(message.error.message);
                            break;
                        case 4:
                            error = new AuthError(message.error.message);
                            break;
                        default:
                            error = new Error(message.error.message);
                    }
                    pending.reject(error);
                }
                else {
                    pending.resolve(message.result);
                }
            }
        }
        else if (message.type === 'notification' && message.notification) {
            const notification = message.notification;
            logger.debug('Received notification:', notification.method);
            this.emit('notification', notification);
        }
    }
    async request(method, params) {
        if (!this.socket) {
            throw new NetworkError('Not connected to bridge');
        }
        const id = generateRequestId();
        const message = {
            type: 'request',
            id,
            method,
            params,
        };
        logger.debug('Sending request:', { id, method });
        const promise = new Promise((resolve, reject) => {
            const timeoutId = setTimeout(() => {
                this.pendingRequests.delete(id);
                reject(new NetworkError(`Request timeout: ${method}`));
            }, REQUEST_TIMEOUT);
            this.pendingRequests.set(id, { resolve, reject, timeoutId });
        });
        const data = JSON.stringify(message) + '\n';
        this.socket.write(data);
        return promise;
    }
    send(message) {
        if (!this.socket) {
            throw new NetworkError('Not connected to bridge');
        }
        logger.debug('Sending message:', { type: message.type });
        const data = JSON.stringify(message) + '\n';
        this.socket.write(data);
    }
    sendAuthCredentials(credentials) {
        this.send({
            type: 'set-auth-credentials',
            authCredentials: credentials,
        });
    }
    close() {
        logger.debug('Closing bridge client');
        this.cleanup();
        return Promise.resolve();
    }
    cleanup() {
        for (const [id, pending] of this.pendingRequests.entries()) {
            clearTimeout(pending.timeoutId);
            pending.reject(new NetworkError('Connection closed'));
            this.pendingRequests.delete(id);
        }
        if (this.socket) {
            this.socket.end();
            this.socket = null;
        }
        this.buffer = '';
    }
}
//# sourceMappingURL=bridge-client.js.map