"use strict";
/**
 * OAuth Port Diagnostics Module
 *
 * Pre-flight checks for OAuth callback ports to detect conflicts
 * before users attempt authentication.
 *
 * OAuth flows require specific localhost ports for callbacks:
 * - Gemini: 8085
 * - Codex: 1455
 * - Agy: 51121
 * - iFlow: 11451
 * - Kiro: Device Code Flow (no port needed)
 * - Claude: 54545
 * - Qwen: Device Code Flow (no port needed)
 * - GHCP: Device Code Flow (no port needed)
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.enhancedPreflightOAuthCheck = exports.preflightOAuthCheck = exports.formatOAuthPortDiagnostics = exports.getPortConflicts = exports.checkAuthCodePorts = exports.checkAllOAuthPorts = exports.checkOAuthPort = exports.OAUTH_FLOW_TYPES = exports.OAUTH_CALLBACK_PORTS = void 0;
const port_utils_1 = require("../utils/port-utils");
const profile_detector_1 = require("../auth/profile-detector");
const provider_capabilities_1 = require("../cliproxy/provider-capabilities");
/**
 * Build provider-indexed records from canonical provider capabilities.
 * Keeps diagnostics in sync with runtime OAuth flow metadata.
 */
function buildProviderMap(valueFor) {
    return provider_capabilities_1.CLIPROXY_PROVIDER_IDS.reduce((acc, provider) => {
        acc[provider] = valueFor(provider);
        return acc;
    }, {});
}
exports.OAUTH_CALLBACK_PORTS = buildProviderMap((provider) => (0, provider_capabilities_1.getOAuthCallbackPort)(provider));
/**
 * OAuth flow type per provider
 */
exports.OAUTH_FLOW_TYPES = buildProviderMap((provider) => (0, provider_capabilities_1.getOAuthFlowType)(provider));
/**
 * Check OAuth port availability for a single provider
 */
async function checkOAuthPort(provider) {
    const port = exports.OAUTH_CALLBACK_PORTS[provider];
    const flowType = exports.OAUTH_FLOW_TYPES[provider];
    // Device code flow doesn't need callback port
    if (port === null) {
        return {
            provider,
            flowType,
            port: null,
            status: 'not_applicable',
            process: null,
            message: 'Uses Device Code Flow (no callback port needed)',
            recommendation: null,
        };
    }
    // Check if port is in use
    const portProcess = await (0, port_utils_1.getPortProcess)(port);
    if (!portProcess) {
        return {
            provider,
            flowType,
            port,
            status: 'free',
            process: null,
            message: `Port ${port} is available`,
            recommendation: null,
        };
    }
    // Check if it's CLIProxy (expected if proxy is running)
    if ((0, port_utils_1.isCLIProxyProcess)(portProcess)) {
        return {
            provider,
            flowType,
            port,
            status: 'cliproxy',
            process: portProcess,
            message: `Port ${port} in use by CLIProxy (expected)`,
            recommendation: null,
        };
    }
    // Port is occupied by another process
    return {
        provider,
        flowType,
        port,
        status: 'occupied',
        process: portProcess,
        message: `Port ${port} occupied by ${portProcess.processName}`,
        recommendation: `Kill process: kill ${portProcess.pid} (or close ${portProcess.processName})`,
    };
}
exports.checkOAuthPort = checkOAuthPort;
/**
 * Check OAuth ports for all providers
 */
async function checkAllOAuthPorts() {
    const providers = [...profile_detector_1.CLIPROXY_PROFILES];
    const results = [];
    for (const provider of providers) {
        const diagnostic = await checkOAuthPort(provider);
        results.push(diagnostic);
    }
    return results;
}
exports.checkAllOAuthPorts = checkAllOAuthPorts;
/**
 * Check OAuth ports for providers that use Authorization Code flow only
 */
async function checkAuthCodePorts() {
    // Filter providers that use authorization_code flow (DRY: derive from OAUTH_FLOW_TYPES)
    const providers = profile_detector_1.CLIPROXY_PROFILES.filter((p) => exports.OAUTH_FLOW_TYPES[p] === 'authorization_code');
    const results = [];
    for (const provider of providers) {
        const diagnostic = await checkOAuthPort(provider);
        results.push(diagnostic);
    }
    return results;
}
exports.checkAuthCodePorts = checkAuthCodePorts;
/**
 * Get providers with port conflicts
 */
async function getPortConflicts() {
    const allPorts = await checkAllOAuthPorts();
    return allPorts.filter((d) => d.status === 'occupied');
}
exports.getPortConflicts = getPortConflicts;
/**
 * Format OAuth port diagnostics for display
 */
function formatOAuthPortDiagnostics(diagnostics) {
    const lines = [];
    for (const diag of diagnostics) {
        const providerName = diag.provider.charAt(0).toUpperCase() + diag.provider.slice(1);
        const portStr = diag.port !== null ? `(${diag.port})` : '';
        let statusIcon;
        switch (diag.status) {
            case 'free':
                statusIcon = '[OK]';
                break;
            case 'cliproxy':
                statusIcon = '[OK]';
                break;
            case 'occupied':
                statusIcon = '[!]';
                break;
            case 'not_applicable':
                statusIcon = '[i]';
                break;
            default:
                statusIcon = '[?]';
        }
        const label = `${providerName} ${portStr}`.padEnd(20);
        lines.push(`${statusIcon} ${label} ${diag.message}`);
        if (diag.recommendation) {
            lines.push(`                        → ${diag.recommendation}`);
        }
    }
    return lines;
}
exports.formatOAuthPortDiagnostics = formatOAuthPortDiagnostics;
/**
 * Pre-flight check before OAuth - returns issues or empty array if OK
 * @deprecated Use enhancedPreflightOAuthCheck for more detailed diagnostics
 */
async function preflightOAuthCheck(provider) {
    const result = await enhancedPreflightOAuthCheck(provider);
    return {
        ready: result.ready,
        issues: result.issues,
    };
}
exports.preflightOAuthCheck = preflightOAuthCheck;
/**
 * Enhanced pre-flight check with detailed step-by-step diagnostics
 * Returns structured results for real-time display
 */
async function enhancedPreflightOAuthCheck(provider) {
    const port = exports.OAUTH_CALLBACK_PORTS[provider];
    const flowType = exports.OAUTH_FLOW_TYPES[provider];
    const checks = [];
    const issues = [];
    // Device code flow doesn't need port checks
    if (flowType === 'device_code' || port === null) {
        checks.push({
            name: 'OAuth Flow',
            status: 'ok',
            message: 'Uses Device Code Flow (no callback port needed)',
        });
        return { ready: true, issues: [], checks };
    }
    // Check 1: Port availability via process detection
    const portProcess = await (0, port_utils_1.getPortProcess)(port);
    if (portProcess && !(0, port_utils_1.isCLIProxyProcess)(portProcess)) {
        checks.push({
            name: 'Port Availability',
            status: 'fail',
            message: `Port ${port} blocked by ${portProcess.processName} (PID ${portProcess.pid})`,
            fixCommand: process.platform === 'win32'
                ? `taskkill /F /PID ${portProcess.pid}`
                : `kill ${portProcess.pid}`,
        });
        issues.push(`Port ${port} is blocked by ${portProcess.processName} (PID ${portProcess.pid})`);
    }
    else if (portProcess && (0, port_utils_1.isCLIProxyProcess)(portProcess)) {
        checks.push({
            name: 'Port Availability',
            status: 'ok',
            message: `Port ${port} in use by CLIProxy (OK)`,
        });
    }
    else {
        checks.push({
            name: 'Port Availability',
            status: 'ok',
            message: `Port ${port} is available`,
        });
    }
    // Check 2: Localhost binding test (verifies we can actually listen)
    const bindingResult = await (0, port_utils_1.testLocalhostBinding)(port);
    if (!bindingResult.success && !portProcess) {
        // Only fail if no process found but binding failed (weird state)
        checks.push({
            name: 'Localhost Binding',
            status: 'warn',
            message: bindingResult.message,
        });
    }
    else if (bindingResult.success) {
        checks.push({
            name: 'Localhost Binding',
            status: 'ok',
            message: 'Can bind to localhost',
        });
    }
    // Check 3: Windows Firewall (only on Windows)
    let firewallWarning;
    let firewallFixCommand;
    if (process.platform === 'win32') {
        const firewallResult = await (0, port_utils_1.checkWindowsFirewall)(port);
        if (firewallResult.mayBlock) {
            checks.push({
                name: 'Windows Firewall',
                status: 'warn',
                message: firewallResult.message,
                fixCommand: firewallResult.fixCommand,
            });
            firewallWarning = firewallResult.message;
            firewallFixCommand = firewallResult.fixCommand;
            // Don't add to issues - just a warning, not a blocker
        }
        else if (firewallResult.checked) {
            checks.push({
                name: 'Windows Firewall',
                status: 'ok',
                message: 'Firewall rules allow port',
            });
        }
    }
    return {
        ready: issues.length === 0,
        issues,
        checks,
        firewallWarning,
        firewallFixCommand,
    };
}
exports.enhancedPreflightOAuthCheck = enhancedPreflightOAuthCheck;
exports.default = {
    OAUTH_CALLBACK_PORTS: exports.OAUTH_CALLBACK_PORTS,
    OAUTH_FLOW_TYPES: exports.OAUTH_FLOW_TYPES,
    checkOAuthPort,
    checkAllOAuthPorts,
    checkAuthCodePorts,
    getPortConflicts,
    formatOAuthPortDiagnostics,
    preflightOAuthCheck,
    enhancedPreflightOAuthCheck,
};
//# sourceMappingURL=oauth-port-diagnostics.js.map