/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import fs from 'fs';
import path from 'path';
import os from 'os';
import dotenv from 'dotenv';
import { isSystemDirectory } from '@utils/fileUtils';
import { playwright } from '../../inprocess';
import { configFromIniFile } from './configIni';
async function fileExistsAsync(resolved) {
    try {
        return (await fs.promises.stat(resolved)).isFile();
    }
    catch {
        return false;
    }
}
const defaultConfig = {
    browser: {
        launchOptions: {},
        contextOptions: {},
    },
    timeouts: {
        action: 5000,
        navigation: 60000,
        expect: 5000,
    },
};
export async function resolveConfig(config) {
    const merged = mergeConfig(defaultConfig, config);
    const browser = await validateBrowserConfig(merged.browser);
    return { ...merged, browser };
}
export async function resolveCLIConfigForMCP(cliOptions, env) {
    const envOverrides = configFromEnv(env);
    const cliOverrides = configFromCLIOptions(cliOptions);
    const configFile = cliOverrides.configFile ?? envOverrides.configFile;
    const configInFile = await loadConfig(configFile);
    const configDir = configFile ? path.dirname(path.resolve(configFile)) : process.cwd();
    let result = defaultConfig;
    result = mergeConfig(result, resolveConfigPaths(configInFile, configDir));
    result = mergeConfig(result, resolveConfigPaths(envOverrides, process.cwd()));
    result = mergeConfig(result, resolveConfigPaths(cliOverrides, process.cwd()));
    const browser = await validateBrowserConfig(result.browser);
    if (browser.launchOptions.headless === undefined)
        browser.launchOptions.headless = os.platform() === 'linux' && !process.env.DISPLAY;
    validateOutputDir(result.outputDir);
    return { ...result, browser, configFile };
}
function validateOutputDir(outputDir) {
    if (!outputDir)
        return;
    if (isSystemDirectory(outputDir))
        throw new Error(`--output-dir cannot point to a system directory: ${path.resolve(outputDir)}.`);
}
export async function resolveCLIConfigForCLI(daemonProfilesDir, sessionName, options, env) {
    const config = options.config ? path.resolve(options.config) : undefined;
    try {
        const defaultConfigFile = path.resolve('.playwright', 'cli.config.json');
        if (!config && fs.existsSync(defaultConfigFile))
            options.config = defaultConfigFile;
    }
    catch {
    }
    const daemonOverrides = configFromCLIOptions({
        endpoint: options.endpoint,
        cdpEndpoint: options.cdp,
        config: options.config,
        browser: options.browser,
        headless: options.headed ? false : undefined,
        extension: options.extension,
        userDataDir: options.profile,
        snapshotMode: 'full',
    });
    const envOverrides = configFromEnv(env);
    const configFile = daemonOverrides.configFile ?? envOverrides.configFile;
    const configInFile = await loadConfig(configFile);
    const configDir = configFile ? path.dirname(path.resolve(configFile)) : process.cwd();
    const globalConfigPath = path.join((env ?? process.env)['PWTEST_CLI_GLOBAL_CONFIG'] ?? os.homedir(), '.playwright', 'cli.config.json');
    const globalConfigExists = fs.existsSync(globalConfigPath);
    const globalConfigInFile = await loadConfig(globalConfigExists ? globalConfigPath : undefined);
    const globalConfigDir = globalConfigExists ? path.dirname(globalConfigPath) : process.cwd();
    let result = defaultConfig;
    result = mergeConfig(result, resolveConfigPaths(globalConfigInFile, globalConfigDir));
    result = mergeConfig(result, resolveConfigPaths(configInFile, configDir));
    result = mergeConfig(result, resolveConfigPaths(envOverrides, process.cwd()));
    result = mergeConfig(result, resolveConfigPaths(daemonOverrides, process.cwd()));
    if (result.browser.isolated === undefined)
        result.browser.isolated = !options.profile && !options.persistent && !result.browser.userDataDir && !result.browser.remoteEndpoint && !result.browser.cdpEndpoint && !result.extension;
    if (result.browser.launchOptions.headless === undefined)
        result.browser.launchOptions.headless = true;
    const browser = await validateBrowserConfig(result.browser);
    validateOutputDir(result.outputDir);
    if (!result.extension && !browser.isolated && !browser.userDataDir && !browser.remoteEndpoint && !browser.cdpEndpoint) {
        // No custom value provided, use the daemon data dir.
        const browserToken = browser.launchOptions?.channel ?? browser?.browserName;
        const userDataDir = path.resolve(daemonProfilesDir, `ud-${sessionName}-${browserToken}`);
        browser.userDataDir = userDataDir;
    }
    return { ...result, browser, configFile, skillMode: true };
}
export function resolveExtensionOptions(cliOptions) {
    const browser = cliOptions.browser ?? envToString(process.env.PLAYWRIGHT_MCP_BROWSER);
    const { channel } = resolveBrowserParam(browser);
    const executablePath = cliOptions.executablePath ?? envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH);
    return { channel: channel ?? 'chrome', executablePath };
}
async function validateBrowserConfig(browser) {
    let browserName = browser.browserName;
    if (!browserName) {
        browserName = 'chromium';
        // Assign channel only if the browserName is not provided, otherwise assume full control to the user.
        if (browser.launchOptions.channel === undefined)
            browser.launchOptions.channel = 'chrome';
    }
    if (browser.browserName === 'chromium' && browser.launchOptions.chromiumSandbox === undefined) {
        if (process.platform === 'linux')
            browser.launchOptions.chromiumSandbox = browser.launchOptions.channel !== 'chromium' && browser.launchOptions.channel !== 'chrome-for-testing';
        else
            browser.launchOptions.chromiumSandbox = true;
    }
    if (browser.isolated && browser.userDataDir)
        throw new Error('Browser userDataDir is not supported in isolated mode.');
    if (browser.initScript) {
        for (const script of browser.initScript) {
            if (!await fileExistsAsync(script))
                throw new Error(`Init script file does not exist: ${script}`);
        }
    }
    if (browser.initPage) {
        for (const page of browser.initPage) {
            if (!await fileExistsAsync(page))
                throw new Error(`Init page file does not exist: ${page}`);
        }
    }
    if (browser.contextOptions.viewport === undefined) {
        if (browser.launchOptions.headless)
            browser.contextOptions.viewport = { width: 1280, height: 720 };
        else
            browser.contextOptions.viewport = null;
    }
    if (browserName === 'chromium') {
        browser.launchOptions.args = browser.launchOptions.args ?? [];
        if (!browser.launchOptions.args.some(a => a.includes('--disable-blink-features')))
            browser.launchOptions.args.push(`--disable-blink-features=AutomationControlled`);
    }
    return { ...browser, browserName };
}
function resolveBrowserParam(browserOption) {
    switch (browserOption) {
        case 'chrome':
        case 'chrome-beta':
        case 'chrome-canary':
        case 'chrome-dev':
        case 'msedge':
        case 'msedge-beta':
        case 'msedge-canary':
        case 'msedge-dev':
            return { browserName: 'chromium', channel: browserOption };
        case 'chromium':
            return { browserName: 'chromium', channel: 'chrome-for-testing' };
        case 'firefox':
            return { browserName: 'firefox' };
        case 'webkit':
            return { browserName: 'webkit' };
        default:
            return {};
    }
}
function configFromCLIOptions(cliOptions) {
    const { browserName, channel } = resolveBrowserParam(cliOptions.browser);
    // Launch options
    const launchOptions = {
        channel,
        executablePath: cliOptions.executablePath,
        headless: cliOptions.headless,
    };
    // --sandbox was passed, enable the sandbox
    // --no-sandbox was passed, disable the sandbox
    if (cliOptions.sandbox !== undefined)
        launchOptions.chromiumSandbox = cliOptions.sandbox;
    if (cliOptions.device && cliOptions.cdpEndpoint)
        throw new Error('Device emulation is not supported with cdpEndpoint.');
    // Context options
    const contextOptions = cliOptions.device ? playwright.devices[cliOptions.device] : {};
    if (cliOptions.proxyServer) {
        const proxy = { server: cliOptions.proxyServer };
        if (cliOptions.proxyBypass)
            proxy.bypass = cliOptions.proxyBypass;
        // Set on both to ensure CLI takes precedence over any proxy set in the config file
        // (launchOptions.proxy applies at browser launch, contextOptions.proxy at context creation).
        launchOptions.proxy = proxy;
        contextOptions.proxy = proxy;
    }
    if (cliOptions.storageState)
        contextOptions.storageState = cliOptions.storageState;
    if (cliOptions.userAgent)
        contextOptions.userAgent = cliOptions.userAgent;
    if (cliOptions.viewportSize)
        contextOptions.viewport = cliOptions.viewportSize;
    if (cliOptions.ignoreHttpsErrors)
        contextOptions.ignoreHTTPSErrors = true;
    if (cliOptions.blockServiceWorkers)
        contextOptions.serviceWorkers = 'block';
    if (cliOptions.grantPermissions)
        contextOptions.permissions = cliOptions.grantPermissions;
    const config = {
        browser: {
            browserName,
            isolated: cliOptions.isolated,
            userDataDir: cliOptions.userDataDir,
            launchOptions,
            contextOptions,
            cdpEndpoint: cliOptions.cdpEndpoint,
            cdpHeaders: cliOptions.cdpHeader,
            cdpTimeout: cliOptions.cdpTimeout,
            initPage: cliOptions.initPage,
            initScript: cliOptions.initScript,
            remoteEndpoint: cliOptions.endpoint,
        },
        extension: cliOptions.extension,
        server: {
            port: cliOptions.port,
            host: cliOptions.host,
            allowedHosts: cliOptions.allowedHosts,
        },
        capabilities: cliOptions.caps,
        console: {
            level: cliOptions.consoleLevel,
        },
        network: {
            allowedOrigins: cliOptions.allowedOrigins,
            blockedOrigins: cliOptions.blockedOrigins,
        },
        allowUnrestrictedFileAccess: cliOptions.allowUnrestrictedFileAccess,
        codegen: cliOptions.codegen,
        saveSession: cliOptions.saveSession,
        secrets: cliOptions.secrets,
        sharedBrowserContext: cliOptions.sharedBrowserContext,
        snapshot: cliOptions.snapshotMode ? { mode: cliOptions.snapshotMode } : undefined,
        outputDir: cliOptions.outputDir,
        imageResponses: cliOptions.imageResponses,
        testIdAttribute: cliOptions.testIdAttribute,
        timeouts: {
            action: cliOptions.timeoutAction,
            navigation: cliOptions.timeoutNavigation,
        },
    };
    return { ...config, configFile: cliOptions.config };
}
export function configFromEnv(env) {
    const e = env ?? process.env;
    const options = {};
    options.allowedHosts = commaSeparatedList(e.PLAYWRIGHT_MCP_ALLOWED_HOSTS);
    options.allowedOrigins = semicolonSeparatedList(e.PLAYWRIGHT_MCP_ALLOWED_ORIGINS);
    options.allowUnrestrictedFileAccess = envToBoolean(e.PLAYWRIGHT_MCP_ALLOW_UNRESTRICTED_FILE_ACCESS);
    options.blockedOrigins = semicolonSeparatedList(e.PLAYWRIGHT_MCP_BLOCKED_ORIGINS);
    options.blockServiceWorkers = envToBoolean(e.PLAYWRIGHT_MCP_BLOCK_SERVICE_WORKERS);
    options.browser = envToString(e.PLAYWRIGHT_MCP_BROWSER);
    options.caps = commaSeparatedList(e.PLAYWRIGHT_MCP_CAPS);
    options.cdpEndpoint = envToString(e.PLAYWRIGHT_MCP_CDP_ENDPOINT);
    options.cdpHeader = headerParser(envToString(e.PLAYWRIGHT_MCP_CDP_HEADERS));
    options.cdpTimeout = numberParser(e.PLAYWRIGHT_MCP_CDP_TIMEOUT);
    options.config = envToString(e.PLAYWRIGHT_MCP_CONFIG);
    if (e.PLAYWRIGHT_MCP_CONSOLE_LEVEL)
        options.consoleLevel = enumParser('--console-level', ['error', 'warning', 'info', 'debug'], e.PLAYWRIGHT_MCP_CONSOLE_LEVEL);
    options.device = envToString(e.PLAYWRIGHT_MCP_DEVICE);
    options.executablePath = envToString(e.PLAYWRIGHT_MCP_EXECUTABLE_PATH);
    options.extension = envToBoolean(e.PLAYWRIGHT_MCP_EXTENSION);
    options.grantPermissions = commaSeparatedList(e.PLAYWRIGHT_MCP_GRANT_PERMISSIONS);
    options.headless = envToBoolean(e.PLAYWRIGHT_MCP_HEADLESS);
    options.host = envToString(e.PLAYWRIGHT_MCP_HOST);
    options.ignoreHttpsErrors = envToBoolean(e.PLAYWRIGHT_MCP_IGNORE_HTTPS_ERRORS);
    const initPage = envToString(e.PLAYWRIGHT_MCP_INIT_PAGE);
    if (initPage)
        options.initPage = [initPage];
    const initScript = envToString(e.PLAYWRIGHT_MCP_INIT_SCRIPT);
    if (initScript)
        options.initScript = [initScript];
    options.isolated = envToBoolean(e.PLAYWRIGHT_MCP_ISOLATED);
    if (e.PLAYWRIGHT_MCP_IMAGE_RESPONSES)
        options.imageResponses = enumParser('--image-responses', ['allow', 'omit'], e.PLAYWRIGHT_MCP_IMAGE_RESPONSES);
    options.sandbox = envToBoolean(e.PLAYWRIGHT_MCP_SANDBOX);
    options.outputDir = envToString(e.PLAYWRIGHT_MCP_OUTPUT_DIR);
    options.port = numberParser(e.PLAYWRIGHT_MCP_PORT);
    options.proxyBypass = envToString(e.PLAYWRIGHT_MCP_PROXY_BYPASS);
    options.proxyServer = envToString(e.PLAYWRIGHT_MCP_PROXY_SERVER);
    options.secrets = dotenvFileLoader(e.PLAYWRIGHT_MCP_SECRETS_FILE);
    options.storageState = envToString(e.PLAYWRIGHT_MCP_STORAGE_STATE);
    options.testIdAttribute = envToString(e.PLAYWRIGHT_MCP_TEST_ID_ATTRIBUTE);
    options.timeoutAction = numberParser(e.PLAYWRIGHT_MCP_TIMEOUT_ACTION);
    options.timeoutNavigation = numberParser(e.PLAYWRIGHT_MCP_TIMEOUT_NAVIGATION);
    options.userAgent = envToString(e.PLAYWRIGHT_MCP_USER_AGENT);
    options.userDataDir = envToString(e.PLAYWRIGHT_MCP_USER_DATA_DIR);
    options.viewportSize = resolutionParser('--viewport-size', e.PLAYWRIGHT_MCP_VIEWPORT_SIZE);
    return configFromCLIOptions(options);
}
export async function loadConfig(configFile) {
    if (!configFile)
        return {};
    if (configFile.endsWith('.ini'))
        return configFromIniFile(configFile);
    try {
        const data = await fs.promises.readFile(configFile, 'utf8');
        return JSON.parse(data.charCodeAt(0) === 0xFEFF ? data.slice(1) : data);
    }
    catch {
        return configFromIniFile(configFile);
    }
}
// initPage/initScript paths are resolved against a per-source base dir
// (config-file dir for entries loaded from a --config file, cwd for entries
// supplied via CLI flags or PLAYWRIGHT_MCP_INIT_* env vars) so they keep
// working when the CLI is invoked from a different cwd.
function resolveConfigPaths(config, baseDir) {
    if (config.browser?.initPage)
        config.browser.initPage = config.browser.initPage.map(p => path.resolve(baseDir, p));
    if (config.browser?.initScript)
        config.browser.initScript = config.browser.initScript.map(p => path.resolve(baseDir, p));
    return config;
}
function pickDefined(obj) {
    return Object.fromEntries(Object.entries(obj ?? {}).filter(([_, v]) => v !== undefined));
}
function mergeConfig(base, overrides) {
    const browser = {
        ...pickDefined(base.browser),
        ...pickDefined(overrides.browser),
        browserName: overrides.browser?.browserName ?? base.browser?.browserName,
        isolated: overrides.browser?.isolated ?? base.browser?.isolated,
        launchOptions: {
            ...pickDefined(base.browser?.launchOptions),
            ...pickDefined(overrides.browser?.launchOptions),
        },
        contextOptions: {
            ...pickDefined(base.browser?.contextOptions),
            ...pickDefined(overrides.browser?.contextOptions),
        },
    };
    if (browser.browserName !== 'chromium' && browser.launchOptions)
        delete browser.launchOptions.channel;
    return {
        ...pickDefined(base),
        ...pickDefined(overrides),
        browser,
        console: {
            ...pickDefined(base.console),
            ...pickDefined(overrides.console),
        },
        network: {
            ...pickDefined(base.network),
            ...pickDefined(overrides.network),
        },
        server: {
            ...pickDefined(base.server),
            ...pickDefined(overrides.server),
        },
        snapshot: {
            ...pickDefined(base.snapshot),
            ...pickDefined(overrides.snapshot),
        },
        timeouts: {
            ...pickDefined(base.timeouts),
            ...pickDefined(overrides.timeouts),
        },
    };
}
export function semicolonSeparatedList(value) {
    if (!value)
        return undefined;
    return value.split(';').map(v => v.trim());
}
export function commaSeparatedList(value) {
    if (!value)
        return undefined;
    return value.split(',').map(v => v.trim());
}
export function dotenvFileLoader(value) {
    if (!value)
        return undefined;
    return dotenv.parse(fs.readFileSync(value, 'utf8'));
}
export function numberParser(value) {
    if (!value)
        return undefined;
    return +value;
}
export function resolutionParser(name, value) {
    if (!value)
        return undefined;
    if (value.includes('x')) {
        const [width, height] = value.split('x').map(v => +v);
        if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0)
            throw new Error(`Invalid resolution format: use ${name}="800x600"`);
        return { width, height };
    }
    // Legacy format
    if (value.includes(',')) {
        const [width, height] = value.split(',').map(v => +v);
        if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0)
            throw new Error(`Invalid resolution format: use ${name}="800x600"`);
        return { width, height };
    }
    throw new Error(`Invalid resolution format: use ${name}="800x600"`);
}
export function headerParser(arg, previous) {
    if (!arg)
        return previous;
    const result = { ...(previous ?? {}) };
    const colonIndex = arg.indexOf(':');
    const name = colonIndex === -1 ? arg.trim() : arg.substring(0, colonIndex).trim();
    const value = colonIndex === -1 ? '' : arg.substring(colonIndex + 1).trim();
    result[name] = value;
    return result;
}
export function enumParser(name, options, value) {
    if (!options.includes(value))
        throw new Error(`Invalid ${name}: ${value}. Valid values are: ${options.join(', ')}`);
    return value;
}
function envToBoolean(value) {
    if (value === 'true' || value === '1')
        return true;
    if (value === 'false' || value === '0')
        return false;
    return undefined;
}
function envToString(value) {
    return value ? value.trim() : undefined;
}
//# sourceMappingURL=config.js.map