"use strict";
/**
 * WebDriverAgent (WDA) Preparation Script
 *
 * This script builds, signs, and packages WebDriverAgent for iOS devices.
 * It can be run as a CLI command: `hub prepare-wda`
 *
 * Options:
 *   --mobile-provisioning-file, -m  Path to the mobile provisioning file
 *   --wda-project-path, -p          Path to WebDriverAgent xcode project
 *   --platform                      Platform type: ios, tvos, or both (default: ios)
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.prepareWda = prepareWda;
const child_process_1 = require("child_process");
const fs_1 = __importDefault(require("fs"));
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const util_1 = __importDefault(require("util"));
const hub_config_1 = require("../config/hub.config");
const execAsync = util_1.default.promisify(child_process_1.exec);
const WDA_BUILD_PATH = '/appium_wda_ios/Build/Products/Debug-iphoneos';
let bundleIdName = null;
let freeBundleID = null;
let isFreeAccount = false;
let customOutputFileName = null;
let generatedIpaPath = null;
/**
 * Get Xcode major version
 */
async function getXcodeMajorVersion() {
    const { stdout } = await execAsync('xcodebuild -version');
    const match = stdout.match(/Xcode (\d+)\./);
    if (!match) {
        throw new Error('Unable to determine Xcode version');
    }
    return parseInt(match[1], 10);
}
/**
 * Get provisioning profile path based on Xcode version
 */
async function getProvisioningProfilePath() {
    const xcodeVersion = await getXcodeMajorVersion();
    if (xcodeVersion <= 15) {
        return path_1.default.join(os_1.default.homedir(), 'Library/MobileDevice/Provisioning Profiles');
    }
    else {
        return path_1.default.join(os_1.default.homedir(), 'Library/Developer/Xcode/UserData/Provisioning Profiles');
    }
}
/**
 * Load required modules dynamically
 */
async function loadDependencies() {
    try {
        // Dynamic imports to handle optional dependencies
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const Applesign = require('applesign');
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const archiver = require('archiver');
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const { provision } = require('ios-mobileprovision-finder');
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const Listr = require('listr');
        const { Observable } = await Promise.resolve().then(() => __importStar(require('rxjs')));
        const { select, input } = await Promise.resolve().then(() => __importStar(require('@inquirer/prompts')));
        return { Applesign, archiver, provision, Listr, Observable, select, input };
    }
    catch (error) {
        console.error('❌ Missing required dependencies for prepare-wda command.');
        console.error('Please install them:');
        console.error('  npm install applesign archiver ios-mobileprovision-finder listr rxjs @inquirer/prompts');
        process.exit(1);
    }
}
/**
 * Get mobile provisioning file (interactive selection if not provided)
 */
async function getMobileProvisioningFile(provision, select, mobileProvisioningFile) {
    if (mobileProvisioningFile) {
        if (!fs_1.default.existsSync(mobileProvisioningFile) || !fs_1.default.statSync(mobileProvisioningFile).isFile()) {
            throw new Error(`Mobile provisioning file ${mobileProvisioningFile} does not exist`);
        }
        return mobileProvisioningFile;
    }
    const provisionFileDir = await getProvisioningProfilePath();
    if (!fs_1.default.existsSync(provisionFileDir)) {
        throw new Error(`Provisioning directory does not exist: ${provisionFileDir}`);
    }
    const files = fs_1.default
        .readdirSync(provisionFileDir, { encoding: 'utf8' })
        .filter(file => file.endsWith('.mobileprovision'));
    const provisioningFiles = files.map(file => {
        const fullPath = path_1.default.join(provisionFileDir, file);
        const mp = provision.readFromFile(fullPath);
        return { ...mp, _filePath: fullPath };
    });
    if (!provisioningFiles || !provisioningFiles.length) {
        throw new Error('No mobileprovision file found on the machine');
    }
    const prompt = await select({
        message: 'Select the mobileprovision to use for signing',
        choices: provisioningFiles.map(file => ({
            value: file.UUID,
            name: `${file.Name.split(':')[1] || file.Name} (Team: ${file.TeamName}) (${file.UUID})`,
        })),
    });
    isFreeAccount = await select({
        message: 'Is this a free or enterprise account provisioning profile?',
        choices: [
            { value: true, name: 'Free Account' },
            { value: false, name: 'Enterprise Account' },
        ],
    });
    if (isFreeAccount) {
        bundleIdName = provisioningFiles.map(file => ({
            uuid: file.UUID,
            name: `${file.Name.split(':')[1] || file.Name}`,
        }));
    }
    freeBundleID = bundleIdName?.find(d => d.uuid === prompt) || null;
    return path_1.default.join(await getProvisioningProfilePath(), `${prompt}.mobileprovision`);
}
/**
 * Get WebDriverAgent project path
 */
async function getWdaProject(wdaProjectPath) {
    if (wdaProjectPath) {
        if (!fs_1.default.existsSync(wdaProjectPath) || !fs_1.default.statSync(wdaProjectPath).isDirectory()) {
            throw new Error(`Unable to find webdriver agent project in path ${wdaProjectPath}`);
        }
        return wdaProjectPath;
    }
    try {
        const { stdout } = await execAsync('find $HOME/.appium -name WebDriverAgent.xcodeproj');
        const projectPath = stdout.trim().split('\n')[0];
        if (!projectPath) {
            throw new Error('WebDriverAgent.xcodeproj not found');
        }
        return path_1.default.dirname(projectPath);
    }
    catch (err) {
        throw new Error('Unable to find WebDriverAgent project');
    }
}
/**
 * Build WebDriverAgent
 */
async function buildWebDriverAgent(projectDir, logger, _bundleId) {
    try {
        let buildCommand = 'xcodebuild clean build-for-testing -project WebDriverAgent.xcodeproj -derivedDataPath appium_wda_ios -scheme WebDriverAgentRunner -destination generic/platform=iOS CODE_SIGNING_ALLOWED=NO';
        if (isFreeAccount && freeBundleID) {
            buildCommand += ` PRODUCT_BUNDLE_IDENTIFIER=${freeBundleID.name.replace(/^\s+|\s+$/g, '')}`;
        }
        logger(buildCommand);
        await execAsync(buildCommand, { cwd: projectDir, maxBuffer: 1024 * 1024 * 100 });
        return `${projectDir}/${WDA_BUILD_PATH}/WebDriverAgentRunner-Runner.app`;
    }
    catch (error) {
        console.error(error);
        throw new Error(`❌ Error building WebDriverAgent: ${error?.message}`);
    }
}
/**
 * Zip payload directory
 */
async function zipPayloadDirectory(archiver, outputZipPath, folderPath, observer) {
    return new Promise((resolve, reject) => {
        const output = fs_1.default.createWriteStream(outputZipPath);
        const archive = archiver('zip', { zlib: { level: 9 } });
        output.on('close', () => {
            observer.next(`Zipped ${archive.pointer()} total bytes`);
            observer.next(`Archive has been written to ${outputZipPath}`);
            resolve();
        });
        archive.on('error', (err) => {
            reject(err);
        });
        archive.pipe(output);
        archive.directory(folderPath, 'Payload');
        archive.finalize();
    });
}
/**
 * Store WDA IPA in hub storage
 */
function storeWdaInHub(ipaPath, filename) {
    const config = (0, hub_config_1.getMergedConfig)();
    const wdaStoragePath = config.wdaStoragePath || path_1.default.join(process.cwd(), '.cache', 'wda');
    // Ensure storage directory exists
    if (!fs_1.default.existsSync(wdaStoragePath)) {
        fs_1.default.mkdirSync(wdaStoragePath, { recursive: true });
    }
    const targetFilename = filename.endsWith('.ipa') ? filename : `${filename}.ipa`;
    const targetPath = path_1.default.join(wdaStoragePath, targetFilename);
    // Copy the file to hub storage
    fs_1.default.copyFileSync(ipaPath, targetPath);
    return targetPath;
}
/**
 * Check if hub server is running
 */
async function isHubRunning(hubUrl) {
    return new Promise(resolve => {
        const url = new URL('/health', hubUrl);
        const client = url.protocol === 'https:' ? https_1.default : http_1.default;
        const req = client.get(url.href, { timeout: 3000 }, res => {
            resolve(res.statusCode === 200);
        });
        req.on('error', () => resolve(false));
        req.on('timeout', () => {
            req.destroy();
            resolve(false);
        });
    });
}
/**
 * Get online nodes from hub
 */
async function getOnlineNodes(hubUrl, authHeader) {
    return new Promise((resolve, reject) => {
        const url = new URL('/api/nodes', hubUrl);
        const client = url.protocol === 'https:' ? https_1.default : http_1.default;
        const req = client.get(url.href, {
            headers: {
                Authorization: authHeader,
                'Content-Type': 'application/json',
            },
            timeout: 10000,
        }, res => {
            let data = '';
            res.on('data', chunk => (data += chunk));
            res.on('end', () => {
                try {
                    const response = JSON.parse(data);
                    const nodes = (response.data || response || []).filter((n) => n.isOnline === true);
                    resolve(nodes.map((n) => ({
                        id: n.id,
                        name: n.name,
                    })));
                }
                catch {
                    resolve([]);
                }
            });
        });
        req.on('error', err => reject(err));
        req.on('timeout', () => {
            req.destroy();
            reject(new Error('Request timeout'));
        });
    });
}
/**
 * Deploy WDA to a node via hub API
 */
async function deployWdaToNode(hubUrl, authHeader, nodeId, filename) {
    return new Promise(resolve => {
        const url = new URL('/api/wda/deploy', hubUrl);
        const client = url.protocol === 'https:' ? https_1.default : http_1.default;
        const postData = JSON.stringify({ nodeId, filename });
        const req = client.request(url.href, {
            method: 'POST',
            headers: {
                Authorization: authHeader,
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(postData),
            },
            timeout: 300000, // 5 minutes for large file transfer
        }, res => {
            let data = '';
            res.on('data', chunk => (data += chunk));
            res.on('end', () => {
                if (res.statusCode !== 200) {
                    // Log error responses for debugging
                    console.log(`\n   Debug: HTTP ${res.statusCode} - ${data.substring(0, 200)}`);
                }
                try {
                    const response = JSON.parse(data);
                    resolve({
                        success: res.statusCode === 200,
                        message: response.message || response.error || `HTTP ${res.statusCode}`,
                    });
                }
                catch {
                    resolve({
                        success: false,
                        message: `Failed to parse response (HTTP ${res.statusCode})`,
                    });
                }
            });
        });
        req.on('error', err => {
            console.log(`\n   Debug: Request error - ${err.message}`);
            resolve({ success: false, message: err.message });
        });
        req.on('timeout', () => {
            console.log('\n   Debug: Request timeout');
            req.destroy();
            resolve({ success: false, message: 'Request timeout' });
        });
        req.write(postData);
        req.end();
    });
}
/**
 * Main function to prepare WDA
 */
async function prepareWda(options) {
    console.log('🍎 Starting WebDriverAgent preparation...\n');
    const { Applesign, archiver, provision, Listr, Observable, select, input } = await loadDependencies();
    const mobileProvisioningFile = await getMobileProvisioningFile(provision, select, options.mobileProvisioningFile);
    const platform = options.platform || 'ios';
    // Ask user for custom output filename
    const defaultFileName = platform === 'tvos' ? 'wda-resign_tvos' : 'wda-resign';
    customOutputFileName = await input({
        message: 'Enter output filename (without .ipa extension):',
        default: defaultFileName,
    });
    const tasks = new Listr([
        {
            title: '🔍 Searching for WebDriverAgent.xcodeproj',
            task: async (context, task) => {
                context.wdaProjectPath = await getWdaProject(options.wdaProjectPath);
                task.title = `Found WebDriverAgent.xcodeproj at: ${context.wdaProjectPath}`;
            },
        },
        {
            title: '🏗️ Building WebDriverAgent',
            task: (context, task) => {
                return new Observable((observer) => {
                    buildWebDriverAgent(context.wdaProjectPath, observer.next.bind(observer))
                        .then(wdaAppPath => {
                        context.wdaAppPath = wdaAppPath;
                        task.title = 'Successfully built WebDriverAgent';
                        observer.complete();
                    })
                        .catch((err) => {
                        observer.error(err);
                    });
                });
            },
        },
        {
            title: '📦 Preparing WebDriverAgent IPA',
            task: (context) => {
                return new Observable((observer) => {
                    const wdaBuildPath = path_1.default.join(context.wdaProjectPath, WDA_BUILD_PATH);
                    const payloadDirectory = path_1.default.join(wdaBuildPath, 'Payload');
                    observer.next('Removing framework directory');
                    const frameworksPath = `${context.wdaAppPath}/Frameworks`;
                    if (fs_1.default.existsSync(frameworksPath)) {
                        fs_1.default.readdirSync(frameworksPath).forEach(f => fs_1.default.rmSync(`${frameworksPath}/${f}`, { recursive: true }));
                    }
                    const infoPlistPath = path_1.default.join(context.wdaAppPath, 'Info.plist');
                    const bundleId = freeBundleID?.name.replace(/^\s+|\s+$/g, '') || '';
                    // Read and update Info.plist
                    if (fs_1.default.existsSync(infoPlistPath) && bundleId) {
                        let infoPlistContent = fs_1.default.readFileSync(infoPlistPath, 'utf8');
                        infoPlistContent = infoPlistContent.replace(/<key>CFBundleIdentifier<\/key>\n\s*<string>(.*?)<\/string>/, `<key>CFBundleIdentifier</key>\n<string>${bundleId}</string>`);
                        fs_1.default.writeFileSync(infoPlistPath, infoPlistContent, 'utf8');
                    }
                    observer.next('Creating Payload directory');
                    execAsync(`mkdir -p ${payloadDirectory}`)
                        .then(() => {
                        observer.next('Payload directory created successfully');
                    })
                        .then(() => {
                        observer.next('🚚 Moving .app file to Payload directory...');
                        return execAsync(`mv ${context.wdaAppPath} ${payloadDirectory}`);
                    })
                        .then(() => {
                        observer.next('Packing Payload directory...');
                        return zipPayloadDirectory(archiver, `${wdaBuildPath}/wda-resign.zip`, payloadDirectory, observer);
                    })
                        .then(() => observer.complete())
                        .catch((err) => {
                        observer.error(err);
                    });
                });
            },
        },
        {
            title: '✍️ Signing WebDriverAgent IPA',
            task: async (context, task) => {
                const wdaBuildPath = path_1.default.join(context.wdaProjectPath, WDA_BUILD_PATH);
                if (platform === 'both') {
                    const platforms = ['ios', 'tvos'];
                    const results = [];
                    for (const p of platforms) {
                        const wdaFileName = p === 'tvos' ? `${customOutputFileName}_tvos.ipa` : `${customOutputFileName}.ipa`;
                        const ipaPath = `${wdaBuildPath}/${wdaFileName}`;
                        const appleOptions = {
                            mobileprovision: mobileProvisioningFile,
                            outfile: ipaPath,
                        };
                        if (freeBundleID) {
                            appleOptions.bundleId = freeBundleID.name.replace(/^\s+|\s+$/g, '');
                        }
                        const as = new Applesign(appleOptions);
                        await as.signIPA(path_1.default.join(wdaBuildPath, 'wda-resign.zip'));
                        results.push(`${p}: ${ipaPath}`);
                        // Store the iOS IPA path for deployment
                        if (p === 'ios') {
                            generatedIpaPath = ipaPath;
                        }
                    }
                    task.title = `Successfully signed WebDriverAgent files for both platforms: ${results.join(', ')}`;
                }
                else {
                    const wdaFileName = `${customOutputFileName}.ipa`;
                    const ipaPath = `${wdaBuildPath}/${wdaFileName}`;
                    const appleOptions = {
                        mobileprovision: mobileProvisioningFile,
                        outfile: ipaPath,
                    };
                    if (freeBundleID) {
                        appleOptions.bundleId = freeBundleID.name.replace(/^\s+|\s+$/g, '');
                    }
                    const as = new Applesign(appleOptions);
                    await as.signIPA(path_1.default.join(wdaBuildPath, 'wda-resign.zip'));
                    generatedIpaPath = ipaPath;
                    task.title = `Successfully signed WebDriverAgent file for ${platform}: ${ipaPath}`;
                }
            },
        },
        {
            title: '💾 Storing WDA in hub',
            task: (_context, task) => {
                if (!generatedIpaPath) {
                    throw new Error('No IPA file generated');
                }
                if (!fs_1.default.existsSync(generatedIpaPath)) {
                    throw new Error(`Generated IPA file not found: ${generatedIpaPath}`);
                }
                const filename = `${customOutputFileName}.ipa`;
                const storedPath = storeWdaInHub(generatedIpaPath, filename);
                if (!fs_1.default.existsSync(storedPath)) {
                    throw new Error(`Failed to store WDA file: ${storedPath}`);
                }
                task.title = `Stored WDA in hub: ${storedPath}`;
            },
        },
    ], { exitOnError: true });
    await tasks.run();
    console.log('\n✅ WebDriverAgent preparation complete!\n');
    // Track if deployment was completed successfully
    let deploymentCompleted = false;
    // Offer to deploy to nodes if hub is running
    const hubUrl = process.env.HUB_URL || 'http://localhost:3000';
    const hubRunning = await isHubRunning(hubUrl);
    if (hubRunning) {
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
        console.log('🌐 Hub server detected! Deploy WDA to remote nodes?');
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
        const shouldDeploy = await select({
            message: 'Would you like to deploy WDA to remote nodes?',
            choices: [
                { value: true, name: 'Yes, deploy to nodes' },
                { value: false, name: 'No, skip deployment' },
            ],
        });
        if (shouldDeploy) {
            // Get credentials from user (Basic Auth with access key and token)
            console.log('📝 Enter your hub credentials (same as used in node.config.json)\n');
            const accessKey = await input({
                message: 'Enter your Access Key:',
            });
            const secretToken = await input({
                message: 'Enter your Token:',
            });
            if (accessKey && secretToken) {
                // Create Basic Auth header
                const basicAuth = Buffer.from(`${accessKey}:${secretToken}`).toString('base64');
                const authHeader = `Basic ${basicAuth}`;
                try {
                    const nodes = await getOnlineNodes(hubUrl, authHeader);
                    if (nodes.length === 0) {
                        console.log('\n⚠️  No online nodes found. Skipping deployment.\n');
                    }
                    else {
                        // Let user select nodes
                        const { checkbox } = await Promise.resolve().then(() => __importStar(require('@inquirer/prompts')));
                        const selectedNodeIds = await checkbox({
                            message: 'Select nodes to deploy WDA to:',
                            choices: nodes.map(n => ({
                                value: n.id,
                                name: `${n.name} (${n.id.substring(0, 8)}...)`,
                            })),
                        });
                        console.log(`\nℹ️  Selected ${selectedNodeIds.length} node(s)`);
                        if (selectedNodeIds.length > 0) {
                            console.log('\n📤 Deploying WDA to selected nodes...');
                            const filename = `${customOutputFileName}.ipa`;
                            console.log(`   WDA file: ${filename}`);
                            console.log(`   Hub URL: ${hubUrl}\n`);
                            let successCount = 0;
                            for (const nodeId of selectedNodeIds) {
                                const node = nodes.find(n => n.id === nodeId);
                                process.stdout.write(`   Deploying to ${node?.name || nodeId}... `);
                                try {
                                    const result = await deployWdaToNode(hubUrl, authHeader, nodeId, filename);
                                    if (result.success) {
                                        console.log('✅');
                                        successCount++;
                                    }
                                    else {
                                        console.log(`❌ ${result.message}`);
                                    }
                                }
                                catch (deployError) {
                                    console.log(`❌ ${deployError.message}`);
                                }
                            }
                            if (successCount > 0) {
                                deploymentCompleted = true;
                            }
                            console.log('');
                        }
                        else {
                            console.log('ℹ️  No nodes selected, skipping deployment.\n');
                        }
                    }
                }
                catch (err) {
                    console.log(`\n⚠️  Failed to fetch nodes: ${err.message}\n`);
                }
            }
        }
    }
    // Print post-installation instructions
    console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    if (deploymentCompleted) {
        // If deployment was successful, only show trust instructions
        console.log('📱 Next Step: Trust the WDA Certificate on your Device');
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
        console.log('✅ WDA has been automatically installed on your devices!\n');
        console.log('⚠️  IMPORTANT: Trust the developer certificate on your iOS device(s):');
        console.log('    1. On your iOS device, go to: Settings → General → VPN & Device Management');
        console.log('    2. Under "Developer App", tap on your developer/team name');
        console.log('    3. Tap "Trust <Developer Name>" and confirm\n');
        console.log('📱 Verify WDA is installed:');
        console.log('    • Look for "WebDriverAgentRunner" app on your device');
        console.log('    • The app icon may be blank/white - this is normal\n');
    }
    else {
        // If deployment was skipped/failed, show full manual installation instructions
        console.log('📱 Next Steps: Install and Trust WebDriverAgent on your device');
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
        console.log('1️⃣  Install the signed WDA IPA on your device:');
        console.log('    Using Xcode:');
        console.log('    • Open Xcode → Window → Devices and Simulators');
        console.log('    • Select your device → Click "+" under "Installed Apps"');
        console.log('    • Select the generated .ipa file\n');
        console.log('    Using xcrun devicectl (recommended):');
        console.log('    $ xcrun devicectl device install app --device <udid> <path-to-wda.ipa>\n');
        console.log('    Using command line (legacy tools):');
        console.log('    $ ideviceinstaller -i <path-to-wda.ipa>');
        console.log('    $ ios-deploy --bundle <path-to-wda.ipa>\n');
        console.log('2️⃣  Trust the developer certificate on your iOS device:');
        console.log('    • Go to Settings → General → VPN & Device Management');
        console.log('    • Under "Developer App", tap on your developer/team name');
        console.log('    • Tap "Trust <Developer Name>" and confirm\n');
        console.log('3️⃣  Verify WDA is installed:');
        console.log('    • Look for "WebDriverAgentRunner" app on your device');
        console.log('    • The app icon may be blank/white - this is normal\n');
    }
    console.log('💡 Tip: For free Apple Developer accounts, the certificate expires');
    console.log('   after 7 days. You will need to re-sign and reinstall WDA.\n');
    console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
}
