"use strict";
/**
 * Unified Proxy Detector
 *
 * Single source of truth for CLIProxy detection.
 * Uses multiple detection methods with fallbacks:
 * 1. HTTP health check (most reliable, ~1s timeout)
 * 2. Session lock file (sessions.json)
 * 3. Port process detection (fallback for orphaned proxies)
 *
 * Detection Order Rationale:
 * - HTTP first: Fastest verification that proxy is responsive
 * - Session lock second: Catches proxies that are starting up
 * - Port process third: Handles orphaned proxies and Windows PID-XXXXX case
 *
 * Solves race conditions between cliproxy-executor.ts and service-manager.ts
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.reclaimOrphanedProxy = exports.waitForProxyHealthy = exports.detectRunningProxy = void 0;
const session_tracker_1 = require("../session-tracker");
const stats_fetcher_1 = require("../services/stats-fetcher");
const port_utils_1 = require("../../utils/port-utils");
const port_manager_1 = require("../config/port-manager");
const logging_1 = require("../../services/logging");
/** No-op logger for when verbose is disabled */
const noopLog = () => { };
const logger = (0, logging_1.createLogger)('cliproxy:proxy-detector');
/**
 * Detect running CLIProxy using multiple methods with fallbacks.
 *
 * Detection order (most reliable first):
 * 1. HTTP health check - fastest, verifies proxy is responsive
 * 2. Session lock - checks sessions.json for tracked proxy
 * 3. Port process - checks OS-level port ownership
 *
 * @param port Port to check (default: 8317)
 * @param verbose Enable verbose logging (default: false)
 * @returns ProxyStatus with detection details
 */
async function detectRunningProxy(port = port_manager_1.CLIPROXY_DEFAULT_PORT, verbose = false) {
    const log = verbose ? (msg) => logger.debug('detect.verbose', msg, { port }) : noopLog;
    // Validate port - fallback to default if invalid
    const validPort = port && Number.isFinite(port) && port >= 1 && port <= 65535 ? port : port_manager_1.CLIPROXY_DEFAULT_PORT;
    log(`Detecting proxy on port ${validPort}...`);
    // 1. First: HTTP health check (fastest, most reliable)
    log('Trying HTTP health check...');
    const healthy = await (0, stats_fetcher_1.isCliproxyRunning)(validPort);
    if (healthy) {
        // Proxy is running and responsive
        // Try to get PID from session lock if available
        const lock = (0, session_tracker_1.getExistingProxy)(validPort);
        let pid = lock?.pid;
        // If no PID from session lock, try port-process detection
        // This handles orphaned proxies (running but no sessions.json)
        if (!pid) {
            log('No PID from session lock, checking port process...');
            const portProcess = await (0, port_utils_1.getPortProcess)(validPort);
            if (portProcess) {
                pid = portProcess.pid;
                log(`Got PID from port process: ${pid}`);
            }
        }
        // Get version from session lock
        const runningVersion = (0, session_tracker_1.getRunningProxyVersion)(validPort);
        log(`HTTP check passed, proxy healthy (PID: ${pid ?? 'unknown'}, version: ${runningVersion ?? 'unknown'})`);
        return {
            running: true,
            verified: true,
            method: 'http',
            pid,
            sessionCount: lock?.sessions?.length,
            version: runningVersion ?? undefined,
        };
    }
    log('HTTP check failed, proxy not responding');
    // 2. Second: Check session lock file
    log('Checking session lock file...');
    const lock = (0, session_tracker_1.getExistingProxy)(validPort);
    if (lock) {
        // Session lock exists - proxy might be starting up
        // The lock validates PID is running, so proxy exists but not ready
        const lockVersion = (0, session_tracker_1.getRunningProxyVersion)(validPort);
        log(`Session lock found: PID ${lock.pid}, ${lock.sessions.length} sessions, version: ${lockVersion ?? 'unknown'}`);
        return {
            running: true,
            verified: false,
            method: 'session-lock',
            pid: lock.pid,
            sessionCount: lock.sessions.length,
            version: lockVersion ?? undefined,
        };
    }
    log('No session lock found');
    // 3. Third: Check port process (fallback for orphaned/untracked proxy)
    log('Checking port process...');
    const portProcess = await (0, port_utils_1.getPortProcess)(validPort);
    if (portProcess) {
        log(`Port occupied by: ${portProcess.processName} (PID ${portProcess.pid})`);
        // Check by process name first (works on Linux/macOS, may fail on Windows)
        if ((0, port_utils_1.isCLIProxyProcess)(portProcess)) {
            log('Process name matches CLIProxy, retrying HTTP...');
            // Looks like CLIProxy by name - verify with HTTP
            const retryHealth = await (0, stats_fetcher_1.isCliproxyRunning)(validPort);
            if (retryHealth) {
                log('HTTP retry passed, proxy is healthy');
                return {
                    running: true,
                    verified: true,
                    method: 'http-retry',
                    pid: portProcess.pid,
                };
            }
            // CLIProxy by name but not responding - might be starting up
            log('HTTP retry failed, proxy starting up or unresponsive');
            return {
                running: true,
                verified: false,
                method: 'port-process',
                pid: portProcess.pid,
            };
        }
        // Process name doesn't match (or Windows returned PID-XXXXX)
        // Do one more HTTP check to be sure it's not ours
        log('Process name unrecognized, final HTTP check (Windows PID-XXXXX case)...');
        const finalHealthCheck = await (0, stats_fetcher_1.isCliproxyRunning)(validPort);
        if (finalHealthCheck) {
            // It IS our proxy, just with unrecognized name (Windows case)
            log('Final HTTP check passed, reclaiming proxy with unrecognized name');
            return {
                running: true,
                verified: true,
                method: 'http-retry',
                pid: portProcess.pid,
            };
        }
        // Definitely not our proxy - port is blocked
        log(`Port blocked by non-CLIProxy process: ${portProcess.processName}`);
        return {
            running: false,
            verified: false,
            blocked: true,
            blocker: portProcess,
        };
    }
    // Port is free
    log('Port is free, no proxy detected');
    return {
        running: false,
        verified: false,
    };
}
exports.detectRunningProxy = detectRunningProxy;
/**
 * Wait for proxy to become ready (healthy via HTTP).
 * Useful after detecting proxy via session-lock or port-process.
 *
 * @param port Port to check
 * @param timeout Max wait time in ms (default: 5000)
 * @param pollInterval Time between checks in ms (default: 100)
 * @returns true if proxy became ready, false if timeout
 */
async function waitForProxyHealthy(port, timeout = 5000, pollInterval = 100) {
    const start = Date.now();
    while (Date.now() - start < timeout) {
        const healthy = await (0, stats_fetcher_1.isCliproxyRunning)(port);
        if (healthy) {
            return true;
        }
        await new Promise((r) => setTimeout(r, pollInterval));
    }
    return false;
}
exports.waitForProxyHealthy = waitForProxyHealthy;
/**
 * Attempt to reclaim an orphaned CLIProxy (running but not in session tracker).
 * Registers a new session with the detected proxy.
 *
 * @param port Port where proxy is running
 * @param pid PID of the proxy process
 * @param verbose Enable verbose logging (default: false)
 * @returns Session ID if reclaimed, null if failed
 */
function reclaimOrphanedProxy(port, pid, verbose = false) {
    const log = verbose
        ? (msg) => logger.debug('reclaim.verbose', msg, { port, pid })
        : noopLog;
    try {
        log(`Reclaiming orphaned proxy: port=${port}, pid=${pid}`);
        const sessionId = (0, session_tracker_1.registerSession)(port, pid);
        log(`Successfully reclaimed proxy, session ID: ${sessionId}`);
        return sessionId;
    }
    catch (err) {
        const error = err;
        log(`Failed to reclaim proxy: ${error.message}`);
        return null;
    }
}
exports.reclaimOrphanedProxy = reclaimOrphanedProxy;
//# sourceMappingURL=proxy-detector.js.map