import { execAsync } from '../../utils.js';
import { spawn } from 'child_process';
import { createModuleLogger } from '../../logger.js';
import { Platform } from '../../types.js';
import { PlatformHandler } from '../../platformHandler.js';
import { existsSync, mkdirSync, rmSync } from 'fs';
import path from 'path';
import { config } from '../../config.js';
const logger = createModuleLogger('XcodeBuild');
/**
 * Handles xcodebuild commands for Xcode projects
 */
export class XcodeBuild {
    /**
     * Validates that a scheme supports the requested platform
     * @throws Error if the platform is not supported
     */
    async validatePlatformSupport(projectPath, isWorkspace, scheme, platform) {
        const projectFlag = isWorkspace ? '-workspace' : '-project';
        let command = `xcodebuild -showBuildSettings ${projectFlag} "${projectPath}"`;
        if (scheme) {
            command += ` -scheme "${scheme}"`;
        }
        // Use a generic destination to check platform support
        const destination = PlatformHandler.getGenericDestination(platform);
        command += ` -destination '${destination}'`;
        try {
            logger.debug({ command }, 'Validating platform support');
            // Just check if the command succeeds - we don't need the output
            await execAsync(command, {
                maxBuffer: 1024 * 1024, // 1MB is enough for validation
                timeout: 10000 // 10 second timeout for validation
            });
            logger.debug({ platform, scheme }, 'Platform validation succeeded');
        }
        catch (error) {
            const stderr = error.stderr || '';
            const stdout = error.stdout || '';
            // Check if error indicates platform mismatch
            if (stderr.includes('Available destinations for') || stdout.includes('Available destinations for')) {
                // Extract available platforms from the error message
                const availablePlatforms = this.extractAvailablePlatforms(stderr + stdout);
                throw new Error(`Platform '${platform}' is not supported by scheme '${scheme || 'default'}'. ` +
                    `Available platforms: ${availablePlatforms.join(', ')}`);
            }
            // Some other error - let it pass through for now
            logger.warn({ error: error.message }, 'Platform validation check failed, continuing anyway');
        }
    }
    /**
     * Extracts available platforms from xcodebuild error output
     */
    extractAvailablePlatforms(output) {
        const platforms = new Set();
        const lines = output.split('\n');
        for (const line of lines) {
            // Look for lines like "{ platform:watchOS" or "{ platform:iOS Simulator"
            const match = line.match(/\{ platform:([^,}]+)/);
            if (match) {
                let platform = match[1].trim();
                // Normalize platform names
                if (platform.includes('Simulator')) {
                    platform = platform.replace(' Simulator', '');
                }
                platforms.add(platform);
            }
        }
        return Array.from(platforms);
    }
    /**
     * Build an Xcode project or workspace
     */
    async build(projectPath, isWorkspace, options = {}) {
        const { scheme, configuration = 'Debug', platform = Platform.iOS, deviceId, derivedDataPath = './DerivedData' } = options;
        // Validate platform support first
        await this.validatePlatformSupport(projectPath, isWorkspace, scheme, platform);
        const projectFlag = isWorkspace ? '-workspace' : '-project';
        let command = `xcodebuild ${projectFlag} "${projectPath}"`;
        if (scheme) {
            command += ` -scheme "${scheme}"`;
        }
        command += ` -configuration "${configuration}"`;
        // Determine destination
        let destination;
        if (deviceId) {
            destination = PlatformHandler.getDestination(platform, deviceId);
        }
        else {
            destination = PlatformHandler.getGenericDestination(platform);
        }
        command += ` -destination '${destination}'`;
        command += ` -derivedDataPath "${derivedDataPath}" build`;
        logger.debug({ command }, 'Build command');
        try {
            const { stdout, stderr } = await execAsync(command, {
                maxBuffer: 50 * 1024 * 1024
            });
            const output = stdout + (stderr ? `\n${stderr}` : '');
            // Try to find the built app using find command (more reliable than parsing output)
            let appPath;
            try {
                const { stdout: findOutput } = await execAsync(`find "${derivedDataPath}" -name "*.app" -type d | head -1`);
                appPath = findOutput.trim() || undefined;
                if (appPath) {
                    logger.info({ appPath }, 'Found app at path');
                    // Verify the app actually exists
                    if (!existsSync(appPath)) {
                        logger.error({ appPath }, 'App path does not exist!');
                        appPath = undefined;
                    }
                }
                else {
                    logger.warn({ derivedDataPath }, 'No app found in DerivedData');
                }
            }
            catch (error) {
                logger.error({ error: error.message, derivedDataPath }, 'Error finding app path');
            }
            logger.info({ projectPath, scheme, configuration, platform }, 'Build succeeded');
            return {
                success: true,
                output,
                appPath
            };
        }
        catch (error) {
            logger.error({ error: error.message, projectPath }, 'Build failed');
            // Return more detailed error information
            const stdout = error.stdout || '';
            const stderr = error.stderr || '';
            const errorMessage = error.message || 'Unknown build error';
            // Prefer stderr if it has the actual error details, otherwise use stdout
            let detailedError;
            if (stderr && stderr.includes('xcodebuild: error')) {
                detailedError = stderr;
            }
            else if (stdout && stdout.includes('xcodebuild')) {
                detailedError = stdout;
            }
            else if (stderr) {
                detailedError = stderr;
            }
            else {
                detailedError = errorMessage;
            }
            throw new Error(detailedError);
        }
    }
    /**
     * Run tests for an Xcode project
     */
    async test(projectPath, isWorkspace, options = {}) {
        const { scheme, configuration = 'Debug', platform = Platform.iOS, deviceId, testFilter, testTarget } = options;
        // Create a unique result bundle path in DerivedData
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const derivedDataPath = config.getDerivedDataPath(projectPath);
        const resultBundlePath = path.join(derivedDataPath, 'Logs', 'Test', `Test-${scheme || 'tests'}-${timestamp}.xcresult`);
        // Ensure result directory exists
        const resultDir = path.dirname(resultBundlePath);
        if (!existsSync(resultDir)) {
            mkdirSync(resultDir, { recursive: true });
        }
        const projectFlag = isWorkspace ? '-workspace' : '-project';
        let command = `xcodebuild ${projectFlag} "${projectPath}"`;
        if (scheme) {
            command += ` -scheme "${scheme}"`;
        }
        command += ` -configuration "${configuration}"`;
        // Determine destination
        let destination;
        if (deviceId) {
            destination = PlatformHandler.getDestination(platform, deviceId);
        }
        else {
            destination = PlatformHandler.getGenericDestination(platform);
        }
        command += ` -destination '${destination}'`;
        // Add test target/filter if provided
        if (testTarget) {
            command += ` -only-testing:${testTarget}`;
        }
        if (testFilter) {
            command += ` -only-testing:${testFilter}`;
        }
        // Disable parallel testing to avoid timeouts and multiple simulator instances
        command += ' -parallel-testing-enabled NO';
        // Add result bundle path
        command += ` -resultBundlePath "${resultBundlePath}"`;
        command += ' test';
        logger.debug({ command }, 'Test command');
        // Use spawn for streaming output to provide progress updates
        return new Promise((resolve, reject) => {
            const child = spawn('sh', ['-c', command]);
            let output = '';
            let lastProgressTime = Date.now();
            const progressInterval = 10000; // Log progress every 10 seconds
            // Stream stdout
            child.stdout.on('data', (data) => {
                const chunk = data.toString();
                output += chunk;
                // Log progress for long-running tests
                const now = Date.now();
                if (now - lastProgressTime > progressInterval) {
                    logger.info('Tests still running... (preventing timeout)');
                    lastProgressTime = now;
                }
                // Parse test progress
                if (chunk.includes('Testing started')) {
                    logger.info('🚀 Tests starting...');
                }
                if (chunk.includes('Test suite') && chunk.includes('started')) {
                    const match = chunk.match(/Test suite '([^']+)'/);
                    if (match) {
                        logger.info({ suite: match[1] }, '📦 Test suite started');
                    }
                }
                if (chunk.includes('Test case') && chunk.includes('passed')) {
                    const match = chunk.match(/Test case '([^']+)'/);
                    if (match) {
                        logger.debug({ test: match[1] }, '✅ Test passed');
                    }
                }
                if (chunk.includes('Test case') && chunk.includes('failed')) {
                    const match = chunk.match(/Test case '([^']+)'/);
                    if (match) {
                        logger.warn({ test: match[1] }, '❌ Test failed');
                    }
                }
            });
            // Stream stderr
            child.stderr.on('data', (data) => {
                const chunk = data.toString();
                output += '\n' + chunk;
                // xcodebuild outputs some normal progress to stderr, don't treat as error
                if (chunk.includes('xcodebuild: error')) {
                    logger.error({ error: chunk.trim() }, 'Build/test error');
                }
            });
            // Handle completion
            child.on('close', async (code) => {
                // Parse the xcresult bundle for accurate test results
                let testResult = { passed: 0, failed: 0, success: false, failingTests: undefined };
                try {
                    // Use xcresulttool to get structured test results
                    const { execSync } = require('child_process');
                    const testReportJson = execSync(`xcrun xcresulttool get test-report --format json --path "${resultBundlePath}"`, { encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 });
                    const testReport = JSON.parse(testReportJson);
                    // Parse the test report to count passed/failed tests
                    let totalPassed = 0;
                    let totalFailed = 0;
                    const failingTestNames = [];
                    // Navigate through the test report structure
                    if (testReport.tests) {
                        const countTests = (tests) => {
                            for (const test of tests) {
                                if (test.subtests) {
                                    // This is a test suite, recurse into it
                                    countTests(test.subtests);
                                }
                                else if (test.testStatus) {
                                    // This is an actual test
                                    if (test.testStatus === 'Success') {
                                        totalPassed++;
                                    }
                                    else if (test.testStatus === 'Failure' || test.testStatus === 'Expected Failure') {
                                        totalFailed++;
                                        // Extract test name
                                        if (test.identifier) {
                                            // Remove the target prefix if present (e.g., "TestTarget/TestClass/testMethod" -> "testMethod")
                                            const parts = test.identifier.split('/');
                                            failingTestNames.push(parts[parts.length - 1] || test.identifier);
                                        }
                                    }
                                }
                            }
                        };
                        countTests(testReport.tests);
                    }
                    testResult = {
                        passed: totalPassed,
                        failed: totalFailed,
                        success: totalFailed === 0 && code === 0,
                        failingTests: failingTestNames.length > 0 ? failingTestNames : undefined
                    };
                    logger.info({
                        projectPath,
                        ...testResult,
                        exitCode: code,
                        resultBundlePath
                    }, 'Tests completed (parsed from xcresult)');
                }
                catch (parseError) {
                    logger.error({
                        error: parseError.message,
                        resultBundlePath
                    }, 'Failed to parse xcresult bundle');
                    // If xcresulttool fails, we can't get accurate results
                    // Return basic failure info based on exit code
                    testResult = {
                        passed: 0,
                        failed: code === 0 ? 0 : 1,
                        success: code === 0,
                        failingTests: undefined
                    };
                }
                const result = {
                    ...testResult,
                    success: code === 0 && testResult.failed === 0,
                    output
                };
                // Clean up the result bundle if tests passed (keep failed results for debugging)
                if (result.success) {
                    try {
                        rmSync(resultBundlePath, { recursive: true, force: true });
                    }
                    catch {
                        // Ignore cleanup errors
                    }
                }
                resolve(result);
            });
            // Handle errors
            child.on('error', (error) => {
                logger.error({ error: error.message, projectPath }, 'Failed to spawn test process');
                reject(new Error(`Failed to run tests: ${error.message}`));
            });
        });
    }
    /**
     * Clean build artifacts
     */
    async clean(projectPath, isWorkspace, options = {}) {
        const { scheme, configuration = 'Debug' } = options;
        const projectFlag = isWorkspace ? '-workspace' : '-project';
        let command = `xcodebuild ${projectFlag} "${projectPath}"`;
        if (scheme) {
            command += ` -scheme "${scheme}"`;
        }
        command += ` -configuration "${configuration}" clean`;
        logger.debug({ command }, 'Clean command');
        try {
            await execAsync(command);
            logger.info({ projectPath }, 'Clean succeeded');
        }
        catch (error) {
            logger.error({ error: error.message, projectPath }, 'Clean failed');
            throw new Error(`Clean failed: ${error.message}`);
        }
    }
}
//# sourceMappingURL=XcodeBuild.old.js.map