/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import { EnvHttpProxyAgent, fetch as undiciFetch } from 'undici';
import { debugLogger } from '../utils/debugLogger.js';
import { isSubpath, resolveToRealPath } from '../utils/paths.js';
import { isNodeError } from '../utils/errors.js';
import {} from './detect-ide.js';
const logger = {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    debug: (...args) => debugLogger.debug('[DEBUG] [IDEConnectionUtils]', ...args),
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    error: (...args) => debugLogger.error('[ERROR] [IDEConnectionUtils]', ...args),
};
export function validateWorkspacePath(ideWorkspacePath, cwd) {
    if (ideWorkspacePath === undefined) {
        return {
            isValid: false,
            error: `Failed to connect to IDE companion extension. Please ensure the extension is running. To install the extension, run /ide install.`,
        };
    }
    if (ideWorkspacePath === '') {
        return {
            isValid: false,
            error: `To use this feature, please open a workspace folder in your IDE and try again.`,
        };
    }
    const ideWorkspacePaths = ideWorkspacePath
        .split(path.delimiter)
        .map((p) => resolveToRealPath(p))
        .filter((e) => !!e);
    const realCwd = resolveToRealPath(cwd);
    const isWithinWorkspace = ideWorkspacePaths.some((workspacePath) => isSubpath(workspacePath, realCwd));
    if (!isWithinWorkspace) {
        return {
            isValid: false,
            error: `Directory mismatch. Gemini CLI is running in a different location than the open workspace in the IDE. Please run the CLI from one of the following directories: ${ideWorkspacePaths.join(', ')}`,
        };
    }
    return { isValid: true };
}
export function getPortFromEnv() {
    const port = process.env['GEMINI_CLI_IDE_SERVER_PORT'];
    if (!port) {
        return undefined;
    }
    return port;
}
export function getStdioConfigFromEnv() {
    const command = process.env['GEMINI_CLI_IDE_SERVER_STDIO_COMMAND'];
    if (!command) {
        return undefined;
    }
    const argsStr = process.env['GEMINI_CLI_IDE_SERVER_STDIO_ARGS'];
    let args = [];
    if (argsStr) {
        try {
            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
            const parsedArgs = JSON.parse(argsStr);
            if (Array.isArray(parsedArgs)) {
                // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                args = parsedArgs;
            }
            else {
                logger.error('GEMINI_CLI_IDE_SERVER_STDIO_ARGS must be a JSON array string.');
            }
        }
        catch (e) {
            logger.error('Failed to parse GEMINI_CLI_IDE_SERVER_STDIO_ARGS:', e);
        }
    }
    return { command, args };
}
const IDE_SERVER_FILE_REGEX = /^gemini-ide-server-(\d+)-\d+\.json$/;
export async function getConnectionConfigFromFile(pid) {
    // For backwards compatibility
    try {
        const portFile = path.join(os.tmpdir(), 'gemini', 'ide', `gemini-ide-server-${pid}.json`);
        const portFileContents = await fs.promises.readFile(portFile, 'utf8');
        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
        return JSON.parse(portFileContents);
    }
    catch (_) {
        // For newer extension versions, the file name matches the pattern
        // /^gemini-ide-server-${pid}-\d+\.json$/. If multiple IDE
        // windows are open, multiple files matching the pattern are expected to
        // exist.
    }
    const portFileDir = path.join(os.tmpdir(), 'gemini', 'ide');
    let portFiles;
    try {
        portFiles = await fs.promises.readdir(portFileDir);
    }
    catch (e) {
        logger.debug('Failed to read IDE connection directory:', e);
        return undefined;
    }
    if (!portFiles) {
        return undefined;
    }
    const matchingFiles = portFiles.filter((file) => IDE_SERVER_FILE_REGEX.test(file));
    if (matchingFiles.length === 0) {
        return undefined;
    }
    sortConnectionFiles(matchingFiles, pid);
    let fileContents;
    try {
        fileContents = await Promise.all(matchingFiles.map((file) => fs.promises.readFile(path.join(portFileDir, file), 'utf8')));
    }
    catch (e) {
        logger.debug('Failed to read IDE connection config file(s):', e);
        return undefined;
    }
    const parsedContents = fileContents.map((content) => {
        try {
            // eslint-disable-next-line @typescript-eslint/no-unsafe-return
            return JSON.parse(content);
        }
        catch (e) {
            logger.debug('Failed to parse JSON from config file: ', e);
            return undefined;
        }
    });
    const validWorkspaces = parsedContents.filter((content) => {
        if (!content) {
            return false;
        }
        const { isValid } = validateWorkspacePath(content.workspacePath, process.cwd());
        return isValid;
    });
    if (validWorkspaces.length === 0) {
        return undefined;
    }
    if (validWorkspaces.length === 1) {
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
        const selected = validWorkspaces[0];
        const fileIndex = parsedContents.indexOf(selected);
        if (fileIndex !== -1) {
            logger.debug(`Selected IDE connection file: ${matchingFiles[fileIndex]}`);
        }
        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
        return selected;
    }
    const portFromEnv = getPortFromEnv();
    if (portFromEnv) {
        const matchingPortIndex = validWorkspaces.findIndex((content) => String(content.port) === portFromEnv);
        if (matchingPortIndex !== -1) {
            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
            const selected = validWorkspaces[matchingPortIndex];
            const fileIndex = parsedContents.indexOf(selected);
            if (fileIndex !== -1) {
                logger.debug(`Selected IDE connection file (matched port from env): ${matchingFiles[fileIndex]}`);
            }
            // eslint-disable-next-line @typescript-eslint/no-unsafe-return
            return selected;
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const selected = validWorkspaces[0];
    const fileIndex = parsedContents.indexOf(selected);
    if (fileIndex !== -1) {
        logger.debug(`Selected first valid IDE connection file: ${matchingFiles[fileIndex]}`);
    }
    // eslint-disable-next-line @typescript-eslint/no-unsafe-return
    return selected;
}
// Sort files to prioritize the one matching the target pid,
// then by whether the process is still alive, then by newest (largest PID).
function sortConnectionFiles(files, targetPid) {
    files.sort((a, b) => {
        const aMatch = a.match(IDE_SERVER_FILE_REGEX);
        const bMatch = b.match(IDE_SERVER_FILE_REGEX);
        const aPid = aMatch ? parseInt(aMatch[1], 10) : 0;
        const bPid = bMatch ? parseInt(bMatch[1], 10) : 0;
        if (aPid === targetPid && bPid !== targetPid) {
            return -1;
        }
        if (bPid === targetPid && aPid !== targetPid) {
            return 1;
        }
        const aIsAlive = isPidAlive(aPid);
        const bIsAlive = isPidAlive(bPid);
        if (aIsAlive && !bIsAlive) {
            return -1;
        }
        if (bIsAlive && !aIsAlive) {
            return 1;
        }
        // Newest PIDs first as a heuristic
        return bPid - aPid;
    });
}
function isPidAlive(pid) {
    if (pid <= 0) {
        return false;
    }
    // Assume the process is alive since checking would introduce significant overhead.
    if (os.platform() === 'win32') {
        return true;
    }
    try {
        process.kill(pid, 0);
        return true;
    }
    catch (e) {
        return isNodeError(e) && e.code === 'EPERM';
    }
}
export async function createProxyAwareFetch(ideServerHost) {
    // ignore proxy for the IDE server host to allow connecting to the ide mcp server
    const existingNoProxy = process.env['NO_PROXY'] || '';
    const agent = new EnvHttpProxyAgent({
        noProxy: [existingNoProxy, ideServerHost].filter(Boolean).join(','),
    });
    return async (url, init) => {
        const fetchOptions = {
            ...init,
            dispatcher: agent,
        };
        // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
        const options = fetchOptions;
        try {
            const response = await undiciFetch(url, options);
            // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
            return new Response(response.body, {
                status: response.status,
                statusText: response.statusText,
                headers: [...response.headers.entries()],
            });
        }
        catch (error) {
            const urlString = typeof url === 'string' ? url : url.href;
            logger.error(`IDE fetch failed for ${urlString}`, error);
            throw error;
        }
    };
}
export function getIdeServerHost() {
    let host;
    host = '127.0.0.1';
    if (isInContainer()) {
        // when ssh-connection (e.g. remote-ssh) or devcontainer setup:
        // --> host must be '127.0.0.1' to have cli companion working
        if (!isSshConnected() && !isDevContainer()) {
            host = 'host.docker.internal';
        }
    }
    logger.debug(`[getIdeServerHost] Mapping IdeServerHost to '${host}'`);
    return host;
}
function isInContainer() {
    return fs.existsSync('/.dockerenv') || fs.existsSync('/run/.containerenv');
}
function isSshConnected() {
    return !!process.env['SSH_CONNECTION'];
}
function isDevContainer() {
    return !!(process.env['VSCODE_REMOTE_CONTAINERS_SESSION'] ||
        process.env['REMOTE_CONTAINERS']);
}
//# sourceMappingURL=ide-connection-utils.js.map