import * as plugins from '../../plugins.js';
/**
 * Safely cleanup a socket by removing all listeners and destroying it
 * @param socket The socket to cleanup
 * @param socketName Optional name for logging
 * @param options Cleanup options
 */
export function cleanupSocket(socket, socketName, options = {}) {
    if (!socket || socket.destroyed)
        return Promise.resolve();
    return new Promise((resolve) => {
        const cleanup = () => {
            try {
                // Remove all event listeners
                socket.removeAllListeners();
                // Destroy if not already destroyed
                if (!socket.destroyed) {
                    socket.destroy();
                }
            }
            catch (err) {
                console.error(`Error cleaning up socket${socketName ? ` (${socketName})` : ''}: ${err}`);
            }
            resolve();
        };
        if (options.immediate) {
            // Immediate cleanup (old behavior)
            socket.unpipe();
            cleanup();
        }
        else if (options.allowDrain && socket.writable) {
            // Allow pending writes to complete
            socket.end(() => cleanup());
            // Force cleanup after grace period
            if (options.gracePeriod) {
                setTimeout(() => {
                    if (!socket.destroyed) {
                        cleanup();
                    }
                }, options.gracePeriod);
            }
        }
        else {
            // Default: immediate cleanup
            socket.unpipe();
            cleanup();
        }
    });
}
/**
 * Create independent cleanup handlers for paired sockets that support half-open connections
 * @param clientSocket The client socket
 * @param serverSocket The server socket
 * @param onBothClosed Callback when both sockets are closed
 * @returns Independent cleanup functions for each socket
 */
export function createIndependentSocketHandlers(clientSocket, serverSocket, onBothClosed, options = {}) {
    let clientClosed = false;
    let serverClosed = false;
    let clientReason = '';
    let serverReason = '';
    const checkBothClosed = () => {
        if (clientClosed && serverClosed) {
            onBothClosed(`client: ${clientReason}, server: ${serverReason}`);
        }
    };
    const cleanupClient = async (reason) => {
        if (clientClosed)
            return;
        clientClosed = true;
        clientReason = reason;
        // Default behavior: close both sockets when one closes (required for proxy chains)
        if (!serverClosed && !options.enableHalfOpen) {
            serverSocket.destroy();
        }
        // Half-open support (opt-in only)
        if (!serverClosed && serverSocket.writable && options.enableHalfOpen) {
            // Half-close: stop reading from client, let server finish
            clientSocket.pause();
            clientSocket.unpipe(serverSocket);
            await cleanupSocket(clientSocket, 'client', { allowDrain: true, gracePeriod: 5000 });
        }
        else {
            await cleanupSocket(clientSocket, 'client', { immediate: true });
        }
        checkBothClosed();
    };
    const cleanupServer = async (reason) => {
        if (serverClosed)
            return;
        serverClosed = true;
        serverReason = reason;
        // Default behavior: close both sockets when one closes (required for proxy chains)
        if (!clientClosed && !options.enableHalfOpen) {
            clientSocket.destroy();
        }
        // Half-open support (opt-in only)
        if (!clientClosed && clientSocket.writable && options.enableHalfOpen) {
            // Half-close: stop reading from server, let client finish
            serverSocket.pause();
            serverSocket.unpipe(clientSocket);
            await cleanupSocket(serverSocket, 'server', { allowDrain: true, gracePeriod: 5000 });
        }
        else {
            await cleanupSocket(serverSocket, 'server', { immediate: true });
        }
        checkBothClosed();
    };
    return { cleanupClient, cleanupServer };
}
/**
 * Setup socket error and close handlers with proper cleanup
 * @param socket The socket to setup handlers for
 * @param handleClose The cleanup function to call
 * @param handleTimeout Optional custom timeout handler
 * @param errorPrefix Optional prefix for error messages
 */
export function setupSocketHandlers(socket, handleClose, handleTimeout, errorPrefix) {
    socket.on('error', (error) => {
        const prefix = errorPrefix || 'Socket';
        handleClose(`${prefix}_error: ${error.message}`);
    });
    socket.on('close', () => {
        const prefix = errorPrefix || 'socket';
        handleClose(`${prefix}_closed`);
    });
    socket.on('timeout', () => {
        if (handleTimeout) {
            handleTimeout(socket); // Custom timeout handling
        }
        else {
            // Default: just log, don't close
            console.warn(`Socket timeout: ${errorPrefix || 'socket'}`);
        }
    });
}
/**
 * Setup bidirectional data forwarding between two sockets with proper cleanup
 * @param clientSocket The client/incoming socket
 * @param serverSocket The server/outgoing socket
 * @param handlers Object containing optional handlers for data and cleanup
 * @returns Cleanup functions for both sockets
 */
export function setupBidirectionalForwarding(clientSocket, serverSocket, handlers) {
    // Set up cleanup handlers
    const { cleanupClient, cleanupServer } = createIndependentSocketHandlers(clientSocket, serverSocket, handlers.onCleanup, { enableHalfOpen: handlers.enableHalfOpen });
    // Set up error and close handlers
    setupSocketHandlers(clientSocket, cleanupClient, undefined, 'client');
    setupSocketHandlers(serverSocket, cleanupServer, undefined, 'server');
    // Set up data forwarding with backpressure handling
    clientSocket.on('data', (chunk) => {
        if (handlers.onClientData) {
            handlers.onClientData(chunk);
        }
        if (serverSocket.writable) {
            const flushed = serverSocket.write(chunk);
            // Handle backpressure
            if (!flushed) {
                clientSocket.pause();
                serverSocket.once('drain', () => {
                    if (!clientSocket.destroyed) {
                        clientSocket.resume();
                    }
                });
            }
        }
    });
    serverSocket.on('data', (chunk) => {
        if (handlers.onServerData) {
            handlers.onServerData(chunk);
        }
        if (clientSocket.writable) {
            const flushed = clientSocket.write(chunk);
            // Handle backpressure
            if (!flushed) {
                serverSocket.pause();
                clientSocket.once('drain', () => {
                    if (!serverSocket.destroyed) {
                        serverSocket.resume();
                    }
                });
            }
        }
    });
    return { cleanupClient, cleanupServer };
}
/**
 * Create a socket with immediate error handling to prevent crashes
 * @param options Socket creation options
 * @returns The created socket
 */
export function createSocketWithErrorHandler(options) {
    const { port, host, onError, onConnect, timeout } = options;
    // Create socket with immediate error handler attachment
    const socket = new plugins.net.Socket();
    // Track if connected
    let connected = false;
    let connectionTimeout = null;
    // Attach error handler BEFORE connecting to catch immediate errors
    socket.on('error', (error) => {
        console.error(`Socket connection error to ${host}:${port}: ${error.message}`);
        // Clear the connection timeout if it exists
        if (connectionTimeout) {
            clearTimeout(connectionTimeout);
            connectionTimeout = null;
        }
        if (onError) {
            onError(error);
        }
    });
    // Attach connect handler
    const handleConnect = () => {
        connected = true;
        // Clear the connection timeout
        if (connectionTimeout) {
            clearTimeout(connectionTimeout);
            connectionTimeout = null;
        }
        // Set inactivity timeout if provided (after connection is established)
        if (timeout) {
            socket.setTimeout(timeout);
        }
        if (onConnect) {
            onConnect();
        }
    };
    socket.on('connect', handleConnect);
    // Implement connection establishment timeout
    if (timeout) {
        connectionTimeout = setTimeout(() => {
            if (!connected && !socket.destroyed) {
                // Connection timed out - destroy the socket
                const error = new Error(`Connection timeout after ${timeout}ms to ${host}:${port}`);
                error.code = 'ETIMEDOUT';
                console.error(`Socket connection timeout to ${host}:${port} after ${timeout}ms`);
                // Destroy the socket
                socket.destroy();
                // Call error handler
                if (onError) {
                    onError(error);
                }
            }
        }, timeout);
    }
    // Now attempt to connect - any immediate errors will be caught
    socket.connect(port, host);
    return socket;
}
//# sourceMappingURL=data:application/json;base64, 