import { InMemorySessionRepository } from '@/packages/infrastructure/repositories/InMemorySessionRepository'; import { MockBrowserAutomationAdapter } from '@/packages/infrastructure/adapters/automation/MockBrowserAutomationAdapter'; import { NutJsAutomationAdapter } from '@/packages/infrastructure/adapters/automation/NutJsAutomationAdapter'; import { MockAutomationEngineAdapter } from '@/packages/infrastructure/adapters/automation/MockAutomationEngineAdapter'; import { PermissionService } from '@/packages/infrastructure/adapters/automation/PermissionService'; import { StartAutomationSessionUseCase } from '@/packages/application/use-cases/StartAutomationSessionUseCase'; import { loadAutomationConfig, getAutomationMode, AutomationMode } from '@/packages/infrastructure/config'; import { PinoLogAdapter } from '@/packages/infrastructure/adapters/logging/PinoLogAdapter'; import { NoOpLogAdapter } from '@/packages/infrastructure/adapters/logging/NoOpLogAdapter'; import { loadLoggingConfig } from '@/packages/infrastructure/config/LoggingConfig'; import type { ISessionRepository } from '@/packages/application/ports/ISessionRepository'; import type { IScreenAutomation } from '@/packages/application/ports/IScreenAutomation'; import type { IAutomationEngine } from '@/packages/application/ports/IAutomationEngine'; import type { ILogger } from '@/packages/application/ports/ILogger'; export interface BrowserConnectionResult { success: boolean; error?: string; } /** * Create logger based on environment configuration. * In test environment, returns NoOpLogAdapter for silent logging. */ function createLogger(): ILogger { const config = loadLoggingConfig(); if (process.env.NODE_ENV === 'test') { return new NoOpLogAdapter(); } return new PinoLogAdapter(config); } /** * Create screen automation adapter based on configuration mode. * * Mode mapping: * - 'production' → NutJsAutomationAdapter with iRacing window * - 'test'/'development' → MockBrowserAutomationAdapter * * @param mode - The automation mode from configuration * @param logger - Logger instance for the adapter * @returns IScreenAutomation adapter instance */ function createBrowserAutomationAdapter(mode: AutomationMode, logger: ILogger): IScreenAutomation { const config = loadAutomationConfig(); switch (mode) { case 'production': return new NutJsAutomationAdapter(config.nutJs, logger.child({ adapter: 'NutJs' })); case 'test': default: return new MockBrowserAutomationAdapter(); } } export class DIContainer { private static instance: DIContainer; private logger: ILogger; private sessionRepository: ISessionRepository; private browserAutomation: IScreenAutomation; private automationEngine: IAutomationEngine; private startAutomationUseCase: StartAutomationSessionUseCase; private automationMode: AutomationMode; private permissionService: PermissionService; private constructor() { // Initialize logger first - it's needed by other components this.logger = createLogger(); this.automationMode = getAutomationMode(); this.logger.info('DIContainer initializing', { automationMode: this.automationMode, nodeEnv: process.env.NODE_ENV }); const config = loadAutomationConfig(); this.sessionRepository = new InMemorySessionRepository(); this.browserAutomation = createBrowserAutomationAdapter(config.mode, this.logger); this.automationEngine = new MockAutomationEngineAdapter( this.browserAutomation, this.sessionRepository ); this.startAutomationUseCase = new StartAutomationSessionUseCase( this.automationEngine, this.browserAutomation, this.sessionRepository ); this.permissionService = new PermissionService( this.logger.child({ service: 'PermissionService' }) ); this.logger.info('DIContainer initialized', { automationMode: config.mode, sessionRepositoryType: 'InMemorySessionRepository', browserAutomationType: this.getBrowserAutomationType(config.mode) }); } private getBrowserAutomationType(mode: AutomationMode): string { switch (mode) { case 'production': return 'NutJsAutomationAdapter'; case 'test': default: return 'MockBrowserAutomationAdapter'; } } public static getInstance(): DIContainer { if (!DIContainer.instance) { DIContainer.instance = new DIContainer(); } return DIContainer.instance; } public getStartAutomationUseCase(): StartAutomationSessionUseCase { return this.startAutomationUseCase; } public getSessionRepository(): ISessionRepository { return this.sessionRepository; } public getAutomationEngine(): IAutomationEngine { return this.automationEngine; } public getAutomationMode(): AutomationMode { return this.automationMode; } public getBrowserAutomation(): IScreenAutomation { return this.browserAutomation; } public getLogger(): ILogger { return this.logger; } public getPermissionService(): PermissionService { return this.permissionService; } /** * Initialize automation connection based on mode. * In production mode, connects to iRacing window via nut.js. * In test/development mode, returns success immediately (no connection needed). */ public async initializeBrowserConnection(): Promise { this.logger.info('Initializing automation connection', { mode: this.automationMode }); if (this.automationMode === 'production') { try { const nutJsAdapter = this.browserAutomation as NutJsAutomationAdapter; const result = await nutJsAdapter.connect(); if (!result.success) { this.logger.error('Automation connection failed', new Error(result.error || 'Unknown error'), { mode: 'production' }); return { success: false, error: result.error }; } this.logger.info('Automation connection established', { mode: 'production', adapter: 'NutJs' }); return { success: true }; } catch (error) { const errorMsg = error instanceof Error ? error.message : 'Failed to initialize nut.js'; this.logger.error('Automation connection failed', error instanceof Error ? error : new Error(errorMsg), { mode: 'production' }); return { success: false, error: errorMsg }; } } this.logger.debug('Test/development mode - no automation connection needed'); return { success: true }; } /** * Shutdown the container and cleanup resources. * Should be called when the application is closing. */ public async shutdown(): Promise { this.logger.info('DIContainer shutting down'); if (this.browserAutomation && 'disconnect' in this.browserAutomation) { try { await (this.browserAutomation as NutJsAutomationAdapter).disconnect(); this.logger.info('Automation adapter disconnected'); } catch (error) { this.logger.error('Error disconnecting automation adapter', error instanceof Error ? error : new Error('Unknown error')); } } this.logger.info('DIContainer shutdown complete'); } /** * Reset the singleton instance (useful for testing with different configurations). */ public static resetInstance(): void { DIContainer.instance = undefined as unknown as DIContainer; } }