/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { debugLogger } from '@google/gemini-cli-core';
import WebSocket from 'ws';
import { initActivityLogger, addNetworkTransport, ActivityLogger, } from './activityLogger.js';
const DEFAULT_DEVTOOLS_PORT = 25417;
const DEFAULT_DEVTOOLS_HOST = '127.0.0.1';
const MAX_PROMOTION_ATTEMPTS = 3;
let promotionAttempts = 0;
let serverStartPromise = null;
let connectedUrl = null;
/**
 * Probe whether a DevTools server is already listening on the given host:port.
 * Returns true if a WebSocket handshake succeeds within a short timeout.
 */
function probeDevTools(host, port) {
    return new Promise((resolve) => {
        const ws = new WebSocket(`ws://${host}:${port}/ws`);
        const timer = setTimeout(() => {
            ws.close();
            resolve(false);
        }, 500);
        ws.on('open', () => {
            clearTimeout(timer);
            ws.close();
            resolve(true);
        });
        ws.on('error', () => {
            clearTimeout(timer);
            ws.close();
            resolve(false);
        });
    });
}
/**
 * Start a DevTools server, then check if we won the default port.
 * If another instance grabbed it first (race), stop ours and connect as client.
 * Returns { host, port } of the DevTools to connect to.
 */
async function startOrJoinDevTools(defaultHost, defaultPort) {
    const mod = await import('@google/gemini-cli-devtools');
    const devtools = mod.DevTools.getInstance();
    const url = await devtools.start();
    const actualPort = devtools.getPort();
    if (actualPort === defaultPort) {
        // We won the port — we are the server
        debugLogger.log(`DevTools available at: ${url}`);
        return { host: defaultHost, port: actualPort };
    }
    // Lost the race — someone else has the default port.
    // Verify the winner is actually alive, then stop ours and connect to theirs.
    const winnerAlive = await probeDevTools(defaultHost, defaultPort);
    if (winnerAlive) {
        await devtools.stop();
        debugLogger.log(`DevTools (existing) at: http://${defaultHost}:${defaultPort}`);
        return { host: defaultHost, port: defaultPort };
    }
    // Winner isn't responding (maybe also racing and failed) — keep ours
    debugLogger.log(`DevTools available at: ${url}`);
    return { host: defaultHost, port: actualPort };
}
/**
 * Handle promotion: when reconnect fails, start or join a DevTools server
 * and add a new network transport for the logger.
 */
async function handlePromotion(config) {
    promotionAttempts++;
    if (promotionAttempts > MAX_PROMOTION_ATTEMPTS) {
        debugLogger.debug(`Giving up on DevTools promotion after ${MAX_PROMOTION_ATTEMPTS} attempts`);
        return;
    }
    try {
        const result = await startOrJoinDevTools(DEFAULT_DEVTOOLS_HOST, DEFAULT_DEVTOOLS_PORT);
        addNetworkTransport(config, result.host, result.port, () => handlePromotion(config));
    }
    catch (err) {
        debugLogger.debug('Failed to promote to DevTools server:', err);
    }
}
/**
 * Initializes the activity logger.
 * Interception starts immediately in buffering mode.
 * If an existing DevTools server is found, attaches transport eagerly.
 */
export async function setupInitialActivityLogger(config) {
    const target = process.env['GEMINI_CLI_ACTIVITY_LOG_TARGET'];
    if (target) {
        if (!config.storage)
            return;
        initActivityLogger(config, { mode: 'file', filePath: target });
    }
    else {
        // Start in buffering mode (no transport attached yet)
        initActivityLogger(config, { mode: 'buffer' });
        // Eagerly probe for an existing DevTools server
        try {
            const existing = await probeDevTools(DEFAULT_DEVTOOLS_HOST, DEFAULT_DEVTOOLS_PORT);
            if (existing) {
                const onReconnectFailed = () => handlePromotion(config);
                addNetworkTransport(config, DEFAULT_DEVTOOLS_HOST, DEFAULT_DEVTOOLS_PORT, onReconnectFailed);
                ActivityLogger.getInstance().enableNetworkLogging();
                connectedUrl = `http://localhost:${DEFAULT_DEVTOOLS_PORT}`;
                debugLogger.log(`DevTools (existing) at startup: ${connectedUrl}`);
            }
        }
        catch {
            // Probe failed silently — stay in buffer mode
        }
    }
}
/**
 * Starts the DevTools server and opens the UI in the browser.
 * Returns the URL to the DevTools UI.
 * Deduplicates concurrent calls — returns the same promise if already in flight.
 */
export function startDevToolsServer(config) {
    if (connectedUrl)
        return Promise.resolve(connectedUrl);
    if (serverStartPromise)
        return serverStartPromise;
    serverStartPromise = startDevToolsServerImpl(config).catch((err) => {
        serverStartPromise = null;
        throw err;
    });
    return serverStartPromise;
}
async function startDevToolsServerImpl(config) {
    const onReconnectFailed = () => handlePromotion(config);
    // Probe for an existing DevTools server
    const existing = await probeDevTools(DEFAULT_DEVTOOLS_HOST, DEFAULT_DEVTOOLS_PORT);
    let host = DEFAULT_DEVTOOLS_HOST;
    let port = DEFAULT_DEVTOOLS_PORT;
    if (existing) {
        debugLogger.log(`DevTools (existing) at: http://${DEFAULT_DEVTOOLS_HOST}:${DEFAULT_DEVTOOLS_PORT}`);
    }
    else {
        // No existing server — start (or join if we lose the race)
        try {
            const result = await startOrJoinDevTools(DEFAULT_DEVTOOLS_HOST, DEFAULT_DEVTOOLS_PORT);
            host = result.host;
            port = result.port;
        }
        catch (err) {
            debugLogger.debug('Failed to start DevTools:', err);
            throw err;
        }
    }
    // Promote the activity logger to use the network transport
    addNetworkTransport(config, host, port, onReconnectFailed);
    const capture = ActivityLogger.getInstance();
    capture.enableNetworkLogging();
    const url = `http://localhost:${port}`;
    connectedUrl = url;
    return url;
}
/**
 * Handles the F12 key toggle for the DevTools panel.
 * Starts the DevTools server, attempts to open the browser.
 * If the panel is already open, it closes it.
 * If the panel is closed:
 * - Attempts to open the browser.
 * - If browser opening is successful, the panel remains closed.
 * - If browser opening fails or is not possible, the panel is opened.
 */
export async function toggleDevToolsPanel(config, isOpen, toggle, setOpen) {
    if (isOpen) {
        toggle();
        return;
    }
    try {
        const { openBrowserSecurely, shouldLaunchBrowser } = await import('@google/gemini-cli-core');
        const url = await startDevToolsServer(config);
        if (shouldLaunchBrowser()) {
            try {
                await openBrowserSecurely(url);
                // Browser opened successfully, don't open drawer.
                return;
            }
            catch (e) {
                debugLogger.warn('Failed to open browser securely:', e);
            }
        }
        // If we can't launch browser or it failed, open drawer.
        setOpen();
    }
    catch (e) {
        setOpen();
        debugLogger.error('Failed to start DevTools server:', e);
    }
}
/** Reset module-level state — test only. */
export function resetForTesting() {
    promotionAttempts = 0;
    serverStartPromise = null;
    connectedUrl = null;
}
//# sourceMappingURL=devtoolsService.js.map