/**
 * Core worker class for DAP Proxy functionality
 * Encapsulates all business logic in a testable form
 */
import path from 'path';
import { ProxyState } from './dap-proxy-interfaces.js';
import { CallbackRequestTracker } from './dap-proxy-request-tracker.js';
import { GenericAdapterManager, DebugpyAdapterManager } from './dap-proxy-adapter-manager.js';
import { DapConnectionManager } from './dap-proxy-connection-manager.js';
import { validateProxyInitPayload, validateAdapterCommand, logAdapterCommandValidation } from '../utils/type-guards.js';
export class DapProxyWorker {
    dependencies;
    logger = null;
    dapClient = null;
    adapterProcess = null;
    currentSessionId = null;
    currentInitPayload = null;
    state = ProxyState.UNINITIALIZED;
    requestTracker;
    processManager = null;
    connectionManager = null;
    constructor(dependencies) {
        this.dependencies = dependencies;
        this.requestTracker = new CallbackRequestTracker((requestId, command) => this.handleRequestTimeout(requestId, command));
    }
    /**
     * Get current state for testing
     */
    getState() {
        return this.state;
    }
    /**
     * Main command handler
     */
    async handleCommand(command) {
        this.currentSessionId = command.sessionId;
        try {
            switch (command.cmd) {
                case 'init':
                    await this.handleInitCommand(command);
                    break;
                case 'dap':
                    await this.handleDapCommand(command);
                    break;
                case 'terminate':
                    await this.handleTerminate();
                    break;
            }
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            this.logger?.error(`[Worker] Error handling command ${command.cmd}:`, error);
            this.sendError(`Error handling ${command.cmd}: ${message}`);
        }
    }
    /**
     * Handle initialization command
     */
    async handleInitCommand(payload) {
        if (this.state !== ProxyState.UNINITIALIZED) {
            throw new Error(`Invalid state for init: ${this.state}`);
        }
        // Validate payload structure
        const validatedPayload = validateProxyInitPayload(payload);
        this.state = ProxyState.INITIALIZING;
        this.currentInitPayload = validatedPayload;
        try {
            // Create logger
            const logPath = path.join(payload.logDir, `proxy-${payload.sessionId}.log`);
            await this.dependencies.fileSystem.ensureDir(path.dirname(logPath));
            this.logger = await this.dependencies.loggerFactory(payload.sessionId, payload.logDir);
            this.logger.info(`[Worker] DAP Proxy worker initialized for session ${payload.sessionId}`);
            // Create managers with logger
            // Use generic adapter manager if adapter command is provided, otherwise fall back to Python
            if (payload.adapterCommand) {
                this.processManager = new GenericAdapterManager(this.dependencies.processSpawner, this.logger, this.dependencies.fileSystem);
            }
            else {
                // Backward compatibility - use Python adapter manager
                this.processManager = new DebugpyAdapterManager(this.dependencies.processSpawner, this.logger, this.dependencies.fileSystem);
            }
            this.connectionManager = new DapConnectionManager(this.dependencies.dapClientFactory, this.logger);
            // No path validation - let debugpy handle it
            this.logger.info(`[Worker] Script path to debug: ${payload.scriptPath}`);
            // Handle dry run
            if (payload.dryRunSpawn) {
                this.handleDryRun(payload);
                return;
            }
            // Start adapter and connect
            await this.startDebugpyAdapterAndConnect(payload);
        }
        catch (error) {
            this.state = ProxyState.UNINITIALIZED;
            const message = error instanceof Error ? error.message : String(error);
            this.logger?.error(`[Worker] Critical initialization error: ${message}`, error);
            // For any initialization error, ensure we shut down
            await this.shutdown();
            // Exit the process to trigger the 'exit' event in ProxyManager
            process.exit(1);
        }
    }
    /**
     * Handle dry run mode
     */
    handleDryRun(payload) {
        let command;
        let args;
        if (payload.adapterCommand) {
            // Use provided adapter command
            command = payload.adapterCommand.command;
            args = payload.adapterCommand.args;
        }
        else if (this.processManager instanceof DebugpyAdapterManager) {
            // Use Python-specific command building
            const spawnCommand = this.processManager.buildSpawnCommand(payload.executablePath, payload.adapterHost, payload.adapterPort, payload.logDir);
            command = spawnCommand.command;
            args = spawnCommand.args;
        }
        else {
            throw new Error('Cannot determine adapter command for dry run');
        }
        const fullCommand = `${command} ${args.join(' ')}`;
        this.logger.warn(`[Worker DRY_RUN] Would execute: ${fullCommand}`);
        this.logger.warn(`[Worker DRY_RUN] Script to debug: ${payload.scriptPath}`);
        this.sendStatus('dry_run_complete', { command: fullCommand, script: payload.scriptPath });
        // Indicate that the process should terminate
        this.state = ProxyState.TERMINATED;
        this.logger.info('[Worker DRY_RUN] Dry run complete. State set to TERMINATED.');
    }
    /**
     * Start debugpy adapter and establish connection
     */
    async startDebugpyAdapterAndConnect(payload) {
        // Spawn adapter process
        let spawnResult;
        if (payload.adapterCommand) {
            // Validate adapter command with detailed logging
            try {
                const validatedCommand = validateAdapterCommand(payload.adapterCommand, 'proxy-worker-init');
                logAdapterCommandValidation(validatedCommand, 'proxy-worker-init', true, {
                    executablePath: payload.executablePath,
                    scriptPath: payload.scriptPath
                });
                this.logger.info('[Worker] Adapter command validated successfully:', {
                    command: validatedCommand.command,
                    argsLength: validatedCommand.args.length,
                    hasEnv: !!validatedCommand.env
                });
            }
            catch (validationError) {
                logAdapterCommandValidation(payload.adapterCommand, 'proxy-worker-init', false, {
                    error: validationError instanceof Error ? validationError.message : String(validationError),
                    rawPayload: payload
                });
                throw validationError;
            }
            // Use validated adapter command
            const validatedCommand = validateAdapterCommand(payload.adapterCommand, 'proxy-worker-spawn');
            spawnResult = await this.processManager.spawn({
                command: validatedCommand.command,
                args: validatedCommand.args,
                host: payload.adapterHost,
                port: payload.adapterPort,
                logDir: payload.logDir,
                env: validatedCommand.env
            });
        }
        else if (this.processManager instanceof DebugpyAdapterManager) {
            // Use Python-specific spawning
            spawnResult = await this.processManager.spawnDebugpy({
                pythonPath: payload.executablePath,
                host: payload.adapterHost,
                port: payload.adapterPort,
                logDir: payload.logDir
            });
        }
        else {
            throw new Error('Cannot determine how to spawn adapter');
        }
        this.adapterProcess = spawnResult.process;
        this.logger.info(`[Worker] Adapter spawned with PID: ${spawnResult.pid}`);
        // Monitor adapter process
        this.adapterProcess.on('error', (err) => {
            this.logger.error('[Worker] Adapter process error:', err);
            this.sendError(`Adapter process error: ${err.message}`);
        });
        this.adapterProcess.on('exit', (code, signal) => {
            this.logger.info(`[Worker] Adapter process exited. Code: ${code}, Signal: ${signal}`);
            this.sendStatus('adapter_exited', { code, signal });
        });
        // Connect to adapter
        try {
            this.dapClient = await this.connectionManager.connectWithRetry(payload.adapterHost, payload.adapterPort);
            // Set up event handlers
            this.setupDapEventHandlers();
            // Initialize DAP session
            await this.connectionManager.initializeSession(this.dapClient, payload.sessionId);
            // Send launch request
            // DIAGNOSTIC: Log the scriptPath before sending to connection manager
            this.logger.info('[Worker] DIAGNOSTIC: About to send launch request with scriptPath:', payload.scriptPath);
            this.logger.info('[Worker] DIAGNOSTIC: scriptPath type:', typeof payload.scriptPath);
            this.logger.info('[Worker] DIAGNOSTIC: scriptPath length:', payload.scriptPath.length);
            this.logger.info('[Worker] DIAGNOSTIC: Full payload object:', JSON.stringify(payload, null, 2));
            await this.connectionManager.sendLaunchRequest(this.dapClient, payload.scriptPath, payload.scriptArgs, payload.stopOnEntry, payload.justMyCode);
            this.logger.info('[Worker] Waiting for "initialized" event from adapter.');
        }
        catch (error) {
            await this.shutdown();
            throw error;
        }
    }
    /**
     * Set up DAP event handlers
     */
    setupDapEventHandlers() {
        if (!this.dapClient || !this.connectionManager)
            return;
        this.connectionManager.setupEventHandlers(this.dapClient, {
            onInitialized: async () => {
                await this.handleInitializedEvent();
            },
            onOutput: (body) => {
                this.logger.debug('[Worker] DAP event: output', body);
                this.sendDapEvent('output', body);
            },
            onStopped: (body) => {
                this.logger.info('[Worker] DAP event: stopped', body);
                this.sendDapEvent('stopped', body);
            },
            onContinued: (body) => {
                this.logger.info('[Worker] DAP event: continued', body);
                this.sendDapEvent('continued', body);
            },
            onThread: (body) => {
                this.logger.debug('[Worker] DAP event: thread', body);
                this.sendDapEvent('thread', body);
            },
            onExited: (body) => {
                this.logger.info('[Worker] DAP event: exited (debuggee)', body);
                this.sendDapEvent('exited', body);
            },
            onTerminated: (body) => {
                this.logger.info('[Worker] DAP event: terminated (session)', body);
                this.sendDapEvent('terminated', body);
                this.shutdown();
            },
            onError: (err) => {
                this.logger.error('[Worker] DAP client error:', err);
                this.sendError(`DAP client error: ${err.message}`);
            },
            onClose: () => {
                this.logger.info('[Worker] DAP client connection closed.');
                this.sendStatus('dap_connection_closed');
                this.shutdown();
            }
        });
    }
    /**
     * Handle DAP initialized event
     */
    async handleInitializedEvent() {
        this.logger.info('[Worker] DAP "initialized" event received.');
        if (!this.currentInitPayload || !this.dapClient || !this.connectionManager) {
            throw new Error('Missing required state in initialized handler');
        }
        try {
            // Set initial breakpoints if provided
            if (this.currentInitPayload.initialBreakpoints?.length) {
                await this.connectionManager.setBreakpoints(this.dapClient, this.currentInitPayload.scriptPath, this.currentInitPayload.initialBreakpoints);
            }
            // Send configuration done
            await this.connectionManager.sendConfigurationDone(this.dapClient);
            // Update state and notify parent
            this.state = ProxyState.CONNECTED;
            this.sendStatus('adapter_configured_and_launched');
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            this.logger.error('[Worker] Error in initialized handler:', error);
            this.sendError(`Error in DAP sequence: ${message}`);
            await this.shutdown();
        }
    }
    /**
     * Handle DAP command
     */
    async handleDapCommand(payload) {
        if (this.state !== ProxyState.CONNECTED || !this.dapClient) {
            this.sendDapResponse(payload.requestId, false, undefined, 'DAP client not connected');
            return;
        }
        try {
            // Track request
            this.requestTracker.track(payload.requestId, payload.dapCommand);
            // Log setBreakpoints for debugging
            if (payload.dapCommand === 'setBreakpoints') {
                this.logger.info(`[Worker] Sending 'setBreakpoints' command. Args:`, payload.dapArgs);
            }
            // Send request
            const response = await this.dapClient.sendRequest(payload.dapCommand, payload.dapArgs);
            // Complete tracking
            this.requestTracker.complete(payload.requestId);
            // Log setBreakpoints response
            if (payload.dapCommand === 'setBreakpoints') {
                this.logger.info(`[Worker] Response from adapter for 'setBreakpoints':`, response);
            }
            // Send response
            this.sendDapResponse(payload.requestId, true, response);
        }
        catch (error) {
            this.requestTracker.complete(payload.requestId);
            const message = error instanceof Error ? error.message : String(error);
            this.logger.error(`[Worker] DAP command ${payload.dapCommand} failed:`, { error: message });
            this.sendDapResponse(payload.requestId, false, undefined, message);
        }
    }
    /**
     * Handle request timeout
     */
    handleRequestTimeout(requestId, command) {
        this.logger.error(`[Worker] DAP request '${command}' (id: ${requestId}) timed out`);
        this.sendDapResponse(requestId, false, undefined, `Request '${command}' timed out`);
    }
    /**
     * Handle terminate command
     */
    async handleTerminate() {
        this.logger.info('[Worker] Received terminate command.');
        await this.shutdown();
        this.sendStatus('terminated');
    }
    /**
     * Shutdown the worker
     */
    async shutdown() {
        if (this.state === ProxyState.SHUTTING_DOWN || this.state === ProxyState.TERMINATED) {
            this.logger?.info('[Worker] Shutdown already in progress.');
            return;
        }
        this.state = ProxyState.SHUTTING_DOWN;
        this.logger?.info('[Worker] Initiating shutdown sequence...');
        // Clear request tracking
        this.requestTracker.clear();
        // Reject any in-flight DAP requests and clear timers immediately
        if (this.dapClient) {
            this.dapClient.shutdown('worker shutdown');
        }
        // Disconnect DAP client
        if (this.connectionManager && this.dapClient) {
            await this.connectionManager.disconnect(this.dapClient);
        }
        this.dapClient = null;
        // Terminate adapter process
        if (this.processManager && this.adapterProcess) {
            await this.processManager.shutdown(this.adapterProcess);
        }
        this.adapterProcess = null;
        this.state = ProxyState.TERMINATED;
        this.logger?.info('[Worker] Shutdown sequence completed.');
    }
    // Message sending helpers
    sendStatus(status, extra = {}) {
        const message = {
            type: 'status',
            status,
            sessionId: this.currentSessionId || 'unknown',
            ...extra
        };
        this.dependencies.messageSender.send(message);
    }
    sendDapResponse(requestId, success, response, error) {
        const message = {
            type: 'dapResponse',
            requestId,
            success,
            sessionId: this.currentSessionId || 'unknown',
            ...(success && response ? {
                body: response.body,
                response: response
            } : { error })
        };
        this.dependencies.messageSender.send(message);
    }
    sendDapEvent(event, body) {
        const message = {
            type: 'dapEvent',
            event,
            body,
            sessionId: this.currentSessionId || 'unknown'
        };
        this.dependencies.messageSender.send(message);
    }
    sendError(message) {
        const errorMessage = {
            type: 'error',
            message,
            sessionId: this.currentSessionId || 'unknown'
        };
        this.dependencies.messageSender.send(errorMessage);
    }
}
//# sourceMappingURL=dap-proxy-worker.js.map