"use strict";
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.resetClaudeExtensionBinding = exports.applyClaudeExtensionBinding = exports.verifyClaudeExtensionBinding = exports.resolveClaudeExtensionIdeSettingsPath = exports.getDefaultClaudeExtensionIdeSettingsPath = void 0;
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const crypto_1 = require("crypto");
const claude_extension_setup_1 = require("../../shared/claude-extension-setup");
const claude_extension_hosts_1 = require("../../shared/claude-extension-hosts");
const claude_config_path_1 = require("../../utils/claude-config-path");
const helpers_1 = require("../../utils/helpers");
const claude_extension_binding_service_1 = require("./claude-extension-binding-service");
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function sortStringRecord(record) {
    return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
}
function toStringRecord(record) {
    const normalized = {};
    for (const [key, value] of Object.entries(record)) {
        if (typeof value === 'string') {
            normalized[key] = value;
        }
    }
    return normalized;
}
function uniqueFileNonce() {
    return `${process.pid}-${Date.now()}-${(0, crypto_1.randomUUID)()}`;
}
function stripJsonComments(input) {
    let output = '';
    let inString = false;
    let escaping = false;
    let inLineComment = false;
    let inBlockComment = false;
    for (let index = 0; index < input.length; index += 1) {
        const char = input[index];
        const nextChar = input[index + 1];
        if (inLineComment) {
            if (char === '\n') {
                inLineComment = false;
                output += char;
            }
            continue;
        }
        if (inBlockComment) {
            if (char === '*' && nextChar === '/') {
                inBlockComment = false;
                index += 1;
                continue;
            }
            if (char === '\n') {
                output += char;
            }
            continue;
        }
        if (inString) {
            output += char;
            if (escaping) {
                escaping = false;
            }
            else if (char === '\\') {
                escaping = true;
            }
            else if (char === '"') {
                inString = false;
            }
            continue;
        }
        if (char === '"') {
            inString = true;
            output += char;
            continue;
        }
        if (char === '/' && nextChar === '/') {
            inLineComment = true;
            index += 1;
            continue;
        }
        if (char === '/' && nextChar === '*') {
            inBlockComment = true;
            index += 1;
            continue;
        }
        output += char;
    }
    return output;
}
function stripTrailingCommas(input) {
    let output = '';
    let inString = false;
    let escaping = false;
    for (let index = 0; index < input.length; index += 1) {
        const char = input[index];
        if (inString) {
            output += char;
            if (escaping) {
                escaping = false;
            }
            else if (char === '\\') {
                escaping = true;
            }
            else if (char === '"') {
                inString = false;
            }
            continue;
        }
        if (char === '"') {
            inString = true;
            output += char;
            continue;
        }
        if (char === ',') {
            let lookahead = index + 1;
            while (lookahead < input.length && /\s/.test(input[lookahead])) {
                lookahead += 1;
            }
            if (input[lookahead] === '}' || input[lookahead] === ']') {
                continue;
            }
        }
        output += char;
    }
    return output;
}
function parseJsonDocumentObject(raw, filePath) {
    const normalized = stripTrailingCommas(stripJsonComments(raw));
    let parsed;
    try {
        parsed = JSON.parse(normalized);
    }
    catch (error) {
        throw new Error(`Failed to parse ${filePath}: ${error.message}`);
    }
    if (!isRecord(parsed)) {
        throw new Error(`Expected a JSON object in ${filePath}`);
    }
    return parsed;
}
function readJsonDocument(filePath) {
    if (!fs.existsSync(filePath)) {
        return { exists: false, data: {}, mtime: null };
    }
    if (fs.lstatSync(filePath).isSymbolicLink()) {
        throw new Error(`Refusing to manage symlinked file: ${filePath}`);
    }
    const raw = fs.readFileSync(filePath, 'utf8');
    return {
        exists: true,
        data: parseJsonDocumentObject(raw, filePath),
        mtime: fs.statSync(filePath).mtimeMs,
    };
}
function backupIfPresent(filePath, suffix) {
    if (!fs.existsSync(filePath))
        return;
    const backupPath = `${filePath}.${suffix}.${uniqueFileNonce()}`;
    fs.copyFileSync(filePath, backupPath, fs.constants.COPYFILE_EXCL);
}
function writeJsonDocument(filePath, data, backupSuffix) {
    if (fs.existsSync(filePath) && fs.lstatSync(filePath).isSymbolicLink()) {
        throw new Error(`Refusing to manage symlinked file: ${filePath}`);
    }
    fs.mkdirSync(path.dirname(filePath), { recursive: true });
    backupIfPresent(filePath, backupSuffix);
    const tempPath = `${filePath}.tmp.${uniqueFileNonce()}`;
    try {
        fs.writeFileSync(tempPath, JSON.stringify(data, null, 2) + '\n', 'utf8');
        fs.renameSync(tempPath, filePath);
    }
    catch (error) {
        if (fs.existsSync(tempPath)) {
            fs.rmSync(tempPath, { force: true });
        }
        throw error;
    }
}
function getManagedSharedEnv(data, managedKeys) {
    const rawEnv = isRecord(data.env) ? toStringRecord(data.env) : {};
    const managed = {};
    for (const key of managedKeys) {
        if (typeof rawEnv[key] === 'string') {
            managed[key] = rawEnv[key];
        }
    }
    return sortStringRecord(managed);
}
function getExtensionEnvEntries(value) {
    if (!Array.isArray(value))
        return [];
    return value
        .map((entry) => {
        if (!isRecord(entry) || typeof entry.name !== 'string' || typeof entry.value !== 'string') {
            return null;
        }
        return { name: entry.name, value: entry.value };
    })
        .filter((entry) => entry !== null);
}
function getExtensionEnvMap(value, managedKeys) {
    const entries = getExtensionEnvEntries(value)
        .filter((entry) => !managedKeys || managedKeys.has(entry.name))
        .map((entry) => [entry.name, entry.value]);
    return sortStringRecord(Object.fromEntries(entries));
}
function mergeManagedExtensionEnvEntries(value, managedKeys, nextManagedEnv) {
    const preservedEntries = getExtensionEnvEntries(value).filter((entry) => !managedKeys.has(entry.name));
    const managedEntries = Object.entries(sortStringRecord(nextManagedEnv)).map(([name, entryValue]) => ({
        name,
        value: entryValue,
    }));
    return [...preservedEntries, ...managedEntries];
}
function recordsMatch(left, right) {
    return JSON.stringify(sortStringRecord(left)) === JSON.stringify(sortStringRecord(right));
}
function booleansMatch(left, right) {
    return left === right;
}
function targetEnabled(target, candidate) {
    return target === 'all' || target === candidate;
}
function getManagedKeysForTarget(binding, target, currentResolvedKeys) {
    const manifest = (0, claude_extension_binding_service_1.getClaudeExtensionManagedEnvManifest)(binding.id);
    const manifestKeys = target === 'shared' ? manifest.shared : manifest.ide;
    return new Set([...currentResolvedKeys, ...manifestKeys]);
}
function getCurrentResolvedManagedKeys(setup) {
    return [...new Set([...setup.removeEnvKeys, ...Object.keys(setup.extensionEnv)])].sort((left, right) => left.localeCompare(right));
}
function getDefaultClaudeExtensionIdeSettingsPath(host) {
    if (process.platform === 'darwin') {
        if (host === 'vscode') {
            return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'settings.json');
        }
        if (host === 'cursor') {
            return path.join(os.homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'settings.json');
        }
        return path.join(os.homedir(), 'Library', 'Application Support', 'Windsurf', 'User', 'settings.json');
    }
    if (process.platform === 'win32') {
        const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
        if (host === 'vscode')
            return path.join(appData, 'Code', 'User', 'settings.json');
        if (host === 'cursor')
            return path.join(appData, 'Cursor', 'User', 'settings.json');
        return path.join(appData, 'Windsurf', 'User', 'settings.json');
    }
    if (host === 'vscode')
        return path.join(os.homedir(), '.config', 'Code', 'User', 'settings.json');
    if (host === 'cursor')
        return path.join(os.homedir(), '.config', 'Cursor', 'User', 'settings.json');
    return path.join(os.homedir(), '.config', 'Windsurf', 'User', 'settings.json');
}
exports.getDefaultClaudeExtensionIdeSettingsPath = getDefaultClaudeExtensionIdeSettingsPath;
function resolveClaudeExtensionIdeSettingsPath(binding) {
    return binding.ideSettingsPath
        ? (0, helpers_1.expandPath)(binding.ideSettingsPath)
        : getDefaultClaudeExtensionIdeSettingsPath(binding.host);
}
exports.resolveClaudeExtensionIdeSettingsPath = resolveClaudeExtensionIdeSettingsPath;
async function verifyClaudeExtensionBinding(binding) {
    const setup = await (0, claude_extension_setup_1.resolveClaudeExtensionSetup)(binding.profile);
    const currentResolvedKeys = getCurrentResolvedManagedKeys(setup);
    const sharedManagedKeys = getManagedKeysForTarget(binding, 'shared', currentResolvedKeys);
    const ideManagedKeys = getManagedKeysForTarget(binding, 'ide', currentResolvedKeys);
    const sharedPath = (0, claude_config_path_1.getClaudeSettingsPath)();
    const idePath = resolveClaudeExtensionIdeSettingsPath(binding);
    const sharedDoc = readJsonDocument(sharedPath);
    const ideDoc = readJsonDocument(idePath);
    const hostDefinition = (0, claude_extension_hosts_1.getClaudeExtensionHostDefinition)(binding.host);
    const expectedIde = (0, claude_extension_setup_1.buildClaudeExtensionSettingsObject)(setup, binding.host);
    const expectedIdeEnv = getExtensionEnvMap(expectedIde[hostDefinition.settingsKey], ideManagedKeys);
    const expectedDisablePrompt = hostDefinition.disableLoginPromptKey
        ? expectedIde[hostDefinition.disableLoginPromptKey]
        : undefined;
    const actualIdeEnv = getExtensionEnvMap(ideDoc.data[hostDefinition.settingsKey], ideManagedKeys);
    const actualDisablePrompt = hostDefinition.disableLoginPromptKey
        ? ideDoc.data[hostDefinition.disableLoginPromptKey]
        : undefined;
    const expectedShared = sortStringRecord(setup.extensionEnv);
    const actualShared = getManagedSharedEnv(sharedDoc.data, sharedManagedKeys);
    const missingSharedState = Object.keys(expectedShared).length > 0 ? 'missing' : 'applied';
    const missingIdeState = Object.keys(expectedIdeEnv).length > 0 || expectedDisablePrompt === true
        ? 'missing'
        : 'applied';
    const sharedSettings = !sharedDoc.exists
        ? {
            target: 'shared',
            path: sharedPath,
            exists: false,
            mtime: null,
            state: missingSharedState,
            message: Object.keys(expectedShared).length > 0
                ? 'Shared Claude settings file does not exist yet.'
                : 'No shared CCS-managed values are required.',
        }
        : recordsMatch(actualShared, expectedShared)
            ? {
                target: 'shared',
                path: sharedPath,
                exists: true,
                mtime: sharedDoc.mtime,
                state: 'applied',
                message: 'Shared Claude settings match this binding.',
            }
            : Object.keys(actualShared).length === 0
                ? {
                    target: 'shared',
                    path: sharedPath,
                    exists: true,
                    mtime: sharedDoc.mtime,
                    state: 'unconfigured',
                    message: 'Shared Claude settings are not configured for this binding.',
                }
                : {
                    target: 'shared',
                    path: sharedPath,
                    exists: true,
                    mtime: sharedDoc.mtime,
                    state: 'drifted',
                    message: 'Shared Claude settings differ from the expected managed values.',
                };
    const ideSettings = !ideDoc.exists
        ? {
            target: 'ide',
            path: idePath,
            exists: false,
            mtime: null,
            state: missingIdeState,
            message: Object.keys(expectedIdeEnv).length > 0 || expectedDisablePrompt === true
                ? `${hostDefinition.label} settings file does not exist yet.`
                : 'No IDE-local CCS-managed values are required.',
        }
        : recordsMatch(actualIdeEnv, expectedIdeEnv) &&
            booleansMatch(actualDisablePrompt, expectedDisablePrompt)
            ? {
                target: 'ide',
                path: idePath,
                exists: true,
                mtime: ideDoc.mtime,
                state: 'applied',
                message: `${hostDefinition.label} settings match this binding.`,
            }
            : Object.keys(actualIdeEnv).length === 0 && actualDisablePrompt === undefined
                ? {
                    target: 'ide',
                    path: idePath,
                    exists: true,
                    mtime: ideDoc.mtime,
                    state: 'unconfigured',
                    message: `${hostDefinition.label} settings are not configured for this binding.`,
                }
                : {
                    target: 'ide',
                    path: idePath,
                    exists: true,
                    mtime: ideDoc.mtime,
                    state: 'drifted',
                    message: `${hostDefinition.label} settings differ from the expected managed values.`,
                };
    return { bindingId: binding.id, sharedSettings, ideSettings };
}
exports.verifyClaudeExtensionBinding = verifyClaudeExtensionBinding;
async function applyClaudeExtensionBinding(binding, target = 'all') {
    const setup = await (0, claude_extension_setup_1.resolveClaudeExtensionSetup)(binding.profile);
    const currentResolvedKeys = getCurrentResolvedManagedKeys(setup);
    if (targetEnabled(target, 'shared')) {
        const managedKeys = getManagedKeysForTarget(binding, 'shared', currentResolvedKeys);
        const filePath = (0, claude_config_path_1.getClaudeSettingsPath)();
        const document = readJsonDocument(filePath);
        const env = isRecord(document.data.env) ? { ...document.data.env } : {};
        for (const key of managedKeys)
            delete env[key];
        for (const [key, value] of Object.entries(setup.extensionEnv))
            env[key] = value;
        const nextData = { ...document.data };
        const nextEnv = toStringRecord(env);
        if (Object.keys(nextEnv).length > 0)
            nextData.env = sortStringRecord(nextEnv);
        else
            delete nextData.env;
        if (document.exists || Object.keys(setup.extensionEnv).length > 0) {
            writeJsonDocument(filePath, nextData, 'backup');
        }
        (0, claude_extension_binding_service_1.updateClaudeExtensionManagedEnvManifest)(binding.id, {
            shared: Object.keys(setup.extensionEnv),
        });
    }
    if (targetEnabled(target, 'ide')) {
        const managedKeys = getManagedKeysForTarget(binding, 'ide', currentResolvedKeys);
        const hostDefinition = (0, claude_extension_hosts_1.getClaudeExtensionHostDefinition)(binding.host);
        const filePath = resolveClaudeExtensionIdeSettingsPath(binding);
        const document = readJsonDocument(filePath);
        const nextData = { ...document.data };
        const payload = (0, claude_extension_setup_1.buildClaudeExtensionSettingsObject)(setup, binding.host);
        const mergedEnvEntries = mergeManagedExtensionEnvEntries(document.data[hostDefinition.settingsKey], managedKeys, setup.extensionEnv);
        if (mergedEnvEntries.length > 0) {
            nextData[hostDefinition.settingsKey] = mergedEnvEntries;
        }
        else {
            delete nextData[hostDefinition.settingsKey];
        }
        if (hostDefinition.disableLoginPromptKey) {
            if (payload[hostDefinition.disableLoginPromptKey] === true) {
                nextData[hostDefinition.disableLoginPromptKey] = true;
            }
            else {
                delete nextData[hostDefinition.disableLoginPromptKey];
            }
        }
        if (document.exists ||
            Object.keys(setup.extensionEnv).length > 0 ||
            payload[hostDefinition.disableLoginPromptKey ?? ''] === true) {
            writeJsonDocument(filePath, nextData, 'ccs-backup');
        }
        (0, claude_extension_binding_service_1.updateClaudeExtensionManagedEnvManifest)(binding.id, {
            ide: Object.keys(setup.extensionEnv),
        });
    }
    return verifyClaudeExtensionBinding(binding);
}
exports.applyClaudeExtensionBinding = applyClaudeExtensionBinding;
async function resetClaudeExtensionBinding(binding, target = 'all') {
    const setup = await (0, claude_extension_setup_1.resolveClaudeExtensionSetup)(binding.profile);
    const currentResolvedKeys = getCurrentResolvedManagedKeys(setup);
    if (targetEnabled(target, 'shared')) {
        const managedKeys = getManagedKeysForTarget(binding, 'shared', currentResolvedKeys);
        const filePath = (0, claude_config_path_1.getClaudeSettingsPath)();
        const document = readJsonDocument(filePath);
        if (document.exists) {
            const env = isRecord(document.data.env) ? { ...document.data.env } : {};
            for (const key of managedKeys)
                delete env[key];
            const nextData = { ...document.data };
            const nextEnv = toStringRecord(env);
            if (Object.keys(nextEnv).length > 0)
                nextData.env = sortStringRecord(nextEnv);
            else
                delete nextData.env;
            writeJsonDocument(filePath, nextData, 'backup');
        }
        (0, claude_extension_binding_service_1.updateClaudeExtensionManagedEnvManifest)(binding.id, { shared: [] });
    }
    if (targetEnabled(target, 'ide')) {
        const managedKeys = getManagedKeysForTarget(binding, 'ide', currentResolvedKeys);
        const hostDefinition = (0, claude_extension_hosts_1.getClaudeExtensionHostDefinition)(binding.host);
        const filePath = resolveClaudeExtensionIdeSettingsPath(binding);
        const document = readJsonDocument(filePath);
        if (document.exists) {
            const nextData = { ...document.data };
            const preservedEntries = getExtensionEnvEntries(document.data[hostDefinition.settingsKey]).filter((entry) => !managedKeys.has(entry.name));
            if (preservedEntries.length > 0) {
                nextData[hostDefinition.settingsKey] = preservedEntries;
            }
            else {
                delete nextData[hostDefinition.settingsKey];
            }
            if (hostDefinition.disableLoginPromptKey) {
                delete nextData[hostDefinition.disableLoginPromptKey];
            }
            writeJsonDocument(filePath, nextData, 'ccs-backup');
        }
        (0, claude_extension_binding_service_1.updateClaudeExtensionManagedEnvManifest)(binding.id, { ide: [] });
    }
    return verifyClaudeExtensionBinding(binding);
}
exports.resetClaudeExtensionBinding = resetClaudeExtensionBinding;
//# sourceMappingURL=claude-extension-settings-service.js.map