"use strict";
/**
 * Browser MCP installer and ~/.claude.json provisioning.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensureBrowserMcpOrThrow = exports.uninstallBrowserMcp = exports.removeBrowserMcpConfig = exports.uninstallBrowserMcpServer = exports.syncBrowserMcpToConfigDir = exports.ensureBrowserMcp = exports.ensureBrowserMcpConfig = exports.installBrowserMcpServer = exports.getBrowserMcpServerPath = exports.getBrowserMcpServerName = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const lockfile = __importStar(require("proper-lockfile"));
const instance_manager_1 = require("../../management/instance-manager");
const claude_config_path_1 = require("../claude-config-path");
const config_manager_1 = require("../config-manager");
const BROWSER_MCP_SERVER = 'ccs-browser-server.cjs';
const BROWSER_MCP_SERVER_NAME = 'ccs-browser';
function resolvePackageRoot(fromPath) {
    let currentDir = path.dirname(fromPath);
    while (true) {
        if (fs.existsSync(path.join(currentDir, 'package.json'))) {
            return currentDir;
        }
        const parentDir = path.dirname(currentDir);
        if (parentDir === currentDir) {
            return null;
        }
        currentDir = parentDir;
    }
}
function getBrowserMcpServerEnv() {
    const sourcePath = resolveBundledServerSourcePath();
    if (!sourcePath) {
        return {};
    }
    const packageRoot = resolvePackageRoot(sourcePath);
    if (!packageRoot) {
        return {};
    }
    const nodeModulesPath = path.join(packageRoot, 'node_modules');
    return fs.existsSync(nodeModulesPath) ? { NODE_PATH: nodeModulesPath } : {};
}
function getCcsMcpDir() {
    return path.join((0, config_manager_1.getCcsDir)(), 'mcp');
}
function getBrowserMcpServerName() {
    return BROWSER_MCP_SERVER_NAME;
}
exports.getBrowserMcpServerName = getBrowserMcpServerName;
function getBrowserMcpServerPath() {
    return path.join(getCcsMcpDir(), BROWSER_MCP_SERVER);
}
exports.getBrowserMcpServerPath = getBrowserMcpServerPath;
function hasMatchingContents(sourcePath, destinationPath) {
    if (!fs.existsSync(destinationPath)) {
        return false;
    }
    const source = fs.readFileSync(sourcePath);
    try {
        const destination = fs.readFileSync(destinationPath);
        return source.equals(destination);
    }
    catch {
        return false;
    }
}
function getTempPath(targetPath) {
    const suffix = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
    return `${targetPath}.${suffix}.tmp`;
}
function resolveBundledServerSourcePath() {
    const possiblePaths = [
        path.join(__dirname, '..', '..', '..', 'lib', 'mcp', BROWSER_MCP_SERVER),
        path.join(__dirname, '..', '..', 'lib', 'mcp', BROWSER_MCP_SERVER),
        path.join(__dirname, '..', 'lib', 'mcp', BROWSER_MCP_SERVER),
    ];
    for (const candidate of possiblePaths) {
        if (fs.existsSync(candidate)) {
            return candidate;
        }
    }
    return null;
}
function readClaudeUserConfig(configPath) {
    if (!fs.existsSync(configPath)) {
        return {};
    }
    try {
        const raw = fs.readFileSync(configPath, 'utf8');
        const parsed = JSON.parse(raw);
        if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
            return null;
        }
        return parsed;
    }
    catch {
        return null;
    }
}
function writeClaudeUserConfig(configPath, config) {
    const tempPath = getTempPath(configPath);
    const fileMode = fs.existsSync(configPath) ? fs.statSync(configPath).mode & 0o777 : 0o600;
    try {
        fs.writeFileSync(tempPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
        fs.chmodSync(tempPath, fileMode);
        fs.renameSync(tempPath, configPath);
        return true;
    }
    finally {
        if (fs.existsSync(tempPath)) {
            fs.unlinkSync(tempPath);
        }
    }
}
function withClaudeUserConfigLock(configPath, callback) {
    const configDir = path.dirname(configPath);
    const lockTarget = path.join(configDir, `${path.basename(configPath)}.ccs-lock`);
    let release;
    if (!fs.existsSync(configDir)) {
        fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
    }
    if (!fs.existsSync(lockTarget)) {
        fs.writeFileSync(lockTarget, '', { encoding: 'utf8', mode: 0o600 });
    }
    try {
        release = lockfile.lockSync(lockTarget, { stale: 10000 });
        return callback();
    }
    finally {
        if (release) {
            try {
                release();
            }
            catch {
                // Best-effort release.
            }
        }
    }
}
function isLockUnavailableError(error) {
    const code = error?.code;
    return code === 'ELOCKED' || code === 'ENOTACQUIRED';
}
function removeManagedServerConfig(configPath) {
    if (!fs.existsSync(configPath)) {
        return false;
    }
    try {
        return withClaudeUserConfigLock(configPath, () => {
            const config = readClaudeUserConfig(configPath);
            if (config === null) {
                return false;
            }
            const existingServers = config.mcpServers &&
                typeof config.mcpServers === 'object' &&
                !Array.isArray(config.mcpServers)
                ? { ...config.mcpServers }
                : {};
            if (!(BROWSER_MCP_SERVER_NAME in existingServers)) {
                return false;
            }
            delete existingServers[BROWSER_MCP_SERVER_NAME];
            const nextConfig = { ...config };
            if (Object.keys(existingServers).length === 0) {
                delete nextConfig.mcpServers;
            }
            else {
                nextConfig.mcpServers = existingServers;
            }
            try {
                return writeClaudeUserConfig(configPath, nextConfig);
            }
            catch {
                return false;
            }
        });
    }
    catch (error) {
        if (isLockUnavailableError(error)) {
            return false;
        }
        throw error;
    }
}
function installBrowserMcpServer() {
    const sourcePath = resolveBundledServerSourcePath();
    if (!sourcePath) {
        return false;
    }
    const mcpDir = getCcsMcpDir();
    if (!fs.existsSync(mcpDir)) {
        fs.mkdirSync(mcpDir, { recursive: true, mode: 0o700 });
    }
    const serverPath = getBrowserMcpServerPath();
    const sourceMode = fs.statSync(sourcePath).mode & 0o777;
    if (hasMatchingContents(sourcePath, serverPath)) {
        if ((fs.statSync(serverPath).mode & 0o777) !== sourceMode) {
            fs.chmodSync(serverPath, sourceMode);
        }
        return true;
    }
    const tempPath = getTempPath(serverPath);
    try {
        fs.copyFileSync(sourcePath, tempPath);
        fs.chmodSync(tempPath, sourceMode);
        try {
            fs.renameSync(tempPath, serverPath);
        }
        catch (renameError) {
            const errorCode = renameError.code;
            if (errorCode !== 'EEXIST' && errorCode !== 'EPERM') {
                throw renameError;
            }
            if (!hasMatchingContents(sourcePath, serverPath)) {
                fs.copyFileSync(tempPath, serverPath);
                fs.chmodSync(serverPath, sourceMode);
            }
        }
        return true;
    }
    catch {
        return false;
    }
    finally {
        if (fs.existsSync(tempPath)) {
            fs.unlinkSync(tempPath);
        }
    }
}
exports.installBrowserMcpServer = installBrowserMcpServer;
function ensureBrowserMcpConfig() {
    const claudeUserConfigPath = (0, claude_config_path_1.getClaudeUserConfigPath)();
    const claudeUserConfigDir = path.dirname(claudeUserConfigPath);
    if (!fs.existsSync(claudeUserConfigDir)) {
        fs.mkdirSync(claudeUserConfigDir, { recursive: true, mode: 0o700 });
    }
    const desiredServerConfig = {
        type: 'stdio',
        command: 'node',
        args: [getBrowserMcpServerPath()],
        env: getBrowserMcpServerEnv(),
    };
    try {
        return withClaudeUserConfigLock(claudeUserConfigPath, () => {
            const config = readClaudeUserConfig(claudeUserConfigPath);
            if (config === null) {
                return false;
            }
            const existingServers = config.mcpServers &&
                typeof config.mcpServers === 'object' &&
                !Array.isArray(config.mcpServers)
                ? config.mcpServers
                : {};
            const currentConfig = existingServers[BROWSER_MCP_SERVER_NAME];
            if (typeof currentConfig === 'object' &&
                currentConfig !== null &&
                JSON.stringify(currentConfig) === JSON.stringify(desiredServerConfig)) {
                return true;
            }
            const nextConfig = {
                ...config,
                mcpServers: {
                    ...existingServers,
                    [BROWSER_MCP_SERVER_NAME]: desiredServerConfig,
                },
            };
            try {
                writeClaudeUserConfig(claudeUserConfigPath, nextConfig);
                return true;
            }
            catch {
                return false;
            }
        });
    }
    catch (error) {
        if (isLockUnavailableError(error)) {
            return false;
        }
        throw error;
    }
}
exports.ensureBrowserMcpConfig = ensureBrowserMcpConfig;
function ensureBrowserMcp() {
    const installed = installBrowserMcpServer();
    const configured = installed && ensureBrowserMcpConfig();
    return installed && configured;
}
exports.ensureBrowserMcp = ensureBrowserMcp;
function syncBrowserMcpToConfigDir(claudeConfigDir) {
    if (!claudeConfigDir) {
        return false;
    }
    return new instance_manager_1.InstanceManager().syncMcpServers(claudeConfigDir);
}
exports.syncBrowserMcpToConfigDir = syncBrowserMcpToConfigDir;
function uninstallBrowserMcpServer() {
    const serverPath = getBrowserMcpServerPath();
    if (!fs.existsSync(serverPath)) {
        return false;
    }
    try {
        fs.unlinkSync(serverPath);
        return true;
    }
    catch {
        return false;
    }
}
exports.uninstallBrowserMcpServer = uninstallBrowserMcpServer;
function removeBrowserMcpConfig() {
    let removed = removeManagedServerConfig((0, claude_config_path_1.getClaudeUserConfigPath)());
    const instanceManager = new instance_manager_1.InstanceManager();
    for (const instanceName of instanceManager.listInstances()) {
        const instancePath = instanceManager.getInstancePath(instanceName);
        const instanceClaudeConfigPath = path.join(instancePath, '.claude.json');
        removed = removeManagedServerConfig(instanceClaudeConfigPath) || removed;
    }
    return removed;
}
exports.removeBrowserMcpConfig = removeBrowserMcpConfig;
function uninstallBrowserMcp() {
    const removedConfig = removeBrowserMcpConfig();
    const removedServer = uninstallBrowserMcpServer();
    return removedConfig || removedServer;
}
exports.uninstallBrowserMcp = uninstallBrowserMcp;
function ensureBrowserMcpOrThrow() {
    const ready = ensureBrowserMcp();
    if (!ready) {
        throw new Error('Browser MCP is enabled, but CCS could not prepare the local browser tool.');
    }
    return ready;
}
exports.ensureBrowserMcpOrThrow = ensureBrowserMcpOrThrow;
//# sourceMappingURL=mcp-installer.js.map