"use strict";
/** ******************************************************************************
 * Copyright (C) 2018 Red Hat, Inc. and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License v. 2.0 are satisfied: GNU General Public License, version 2
 * with the GNU Classpath Exception which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 ********************************************************************************/
// Some code copied and modified from https://github.com/eclipse-theia/theia/tree/v1.14.0/packages/plugin-ext/src/plugin/preference-registry.ts
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExtHostPreference = void 0;
const tslib_1 = require("tslib");
const cloneDeep_1 = tslib_1.__importDefault(require("lodash/cloneDeep"));
const ide_core_common_1 = require("@opensumi/ide-core-common");
const vscode_1 = require("../../../common/vscode");
const preferences_1 = require("./preferences");
/**
 * 查找属性对应值
 * @param {*} tree
 * @param {string} key
 * @returns {*}
 */
function lookUp(tree, key) {
    if (!key) {
        return;
    }
    const parts = key.split('.');
    let node = tree;
    for (let i = 0; node && i < parts.length; i++) {
        node = node[parts[i]];
    }
    return node;
}
class ExtHostPreference {
    constructor(rpcProtocol, workspace) {
        this.workspace = workspace;
        this._onDidChangeConfiguration = new ide_core_common_1.Emitter();
        this.onDidChangeConfiguration = this._onDidChangeConfiguration.event;
        this.OVERRIDE_PROPERTY = '\\[(.*)\\]$';
        this.OVERRIDE_PROPERTY_PATTERN = new RegExp(this.OVERRIDE_PROPERTY);
        this.rpcProtocol = rpcProtocol;
        this.proxy = this.rpcProtocol.getProxy(vscode_1.MainThreadAPIIdentifier.MainThreadPreference);
    }
    init(data) {
        this._preferences = this.parse(data);
    }
    $initializeConfiguration(data) {
        this.init(data);
    }
    $acceptConfigurationChanged(data, eventData) {
        this.init(data);
        this._onDidChangeConfiguration.fire(this.toConfigurationChangeEvent(eventData));
    }
    parse(data) {
        const defaultConfiguration = this.getConfigurationModel(data[ide_core_common_1.PreferenceScope.Default]);
        const userConfiguration = this.getConfigurationModel(data[ide_core_common_1.PreferenceScope.User]);
        const workspaceConfiguration = this.getConfigurationModel(data[ide_core_common_1.PreferenceScope.Workspace]);
        const folderConfigurations = {};
        Object.keys(data[ide_core_common_1.PreferenceScope.Folder]).forEach((resource) => {
            folderConfigurations[resource] = this.getConfigurationModel(data[ide_core_common_1.PreferenceScope.Folder][resource]);
        });
        return new preferences_1.Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folderConfigurations);
    }
    getConfigurationModel(data) {
        if (!data) {
            return new preferences_1.ConfigurationModel();
        }
        return new preferences_1.ConfigurationModel(this.parseConfigurationData(data), Object.keys(data));
    }
    parseConfigurationData(data) {
        return (Object.keys(data)
            // 配置项按字段长度从大到小排序，方便后续自然过滤有包含关系的配置项
            // 如当前如果存在 a.b 声明为 string，而又有 a.b.c 的声明
            // 则这里默认应该忽略掉 a.b 的声明
            .sort((keyA, keyB) => keyB.length - keyA.length)
            .reduce((result, key) => {
            const parts = key.split('.');
            let branch = result;
            for (let i = 0; i < parts.length; i++) {
                if (i === parts.length - 1) {
                    if ((0, ide_core_common_1.isUndefined)(branch[parts[i]])) {
                        // 仅当值为 undefined 时使用配置项的默认值
                        branch[parts[i]] = data[key];
                    }
                    continue;
                }
                if (!branch[parts[i]]) {
                    branch[parts[i]] = {};
                }
                branch = branch[parts[i]];
                // overridden 的属性，如languages的 [typescript].editor.tabsize 转换为
                // "[typescript]" : {
                //    "editor.tabsize" : "2"
                //  }
                if (i === 0 && this.OVERRIDE_PROPERTY_PATTERN.test(parts[i])) {
                    branch[key.substring(parts[0].length + 1)] = data[key];
                    break;
                }
            }
            return result;
        }, {}));
    }
    toConfigurationChangeEvent(eventData) {
        return Object.freeze({
            affectsConfiguration: (section, uri) => {
                for (const change of eventData) {
                    const tree = change.preferenceName
                        .split('.')
                        .reverse()
                        .reduce((prevValue, curValue) => ({ [curValue]: prevValue }), change.newValue);
                    return typeof lookUp(tree, section) !== 'undefined';
                }
                return false;
            },
        });
    }
    getConfiguration(section, resource, extensionId) {
        resource = resource === null ? undefined : resource;
        const preferences = this.toReadonlyValue(section
            ? lookUp(this._preferences.getValue(undefined, this.workspace, resource), section)
            : this._preferences.getValue(undefined, this.workspace, resource));
        const configuration = {
            has(key) {
                return typeof lookUp(preferences, key) !== 'undefined';
            },
            get: (key, defaultValue) => {
                const result = lookUp(preferences, key);
                if (typeof result === 'undefined') {
                    return defaultValue;
                }
                else {
                    let clonedConfig;
                    const cloneOnWriteProxy = (target, accessor) => {
                        let clonedTarget;
                        const cloneTarget = () => {
                            clonedConfig = clonedConfig ? clonedConfig : (0, cloneDeep_1.default)(preferences);
                            clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor);
                        };
                        if (!(0, ide_core_common_1.isObject)(target)) {
                            return target;
                        }
                        return new Proxy(target, {
                            get: (targ, prop) => {
                                if (typeof prop === 'string' && prop.toLowerCase() === 'tojson') {
                                    cloneTarget();
                                    // 当调用过配置对象的toJSON方法后，会固化某一份配置的配置值
                                    // 从而保障在某些情况下插件的配置值不被外部改变
                                    return () => clonedTarget;
                                }
                                if (clonedConfig) {
                                    // 当存在克隆过的配置对象时，直接从缓存对象中获取属性值
                                    clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor);
                                    return clonedTarget[prop];
                                }
                                const res = targ[prop];
                                if (typeof prop === 'string') {
                                    return cloneOnWriteProxy(res, `${accessor}.${prop}`);
                                }
                                return res;
                            },
                            set: (targ, prop, val) => {
                                cloneTarget();
                                clonedTarget[prop] = val;
                                return true;
                            },
                            deleteProperty: (targ, prop) => {
                                cloneTarget();
                                delete clonedTarget[prop];
                                return true;
                            },
                            defineProperty: (targ, prop, descr) => {
                                cloneTarget();
                                Object.defineProperty(clonedTarget, prop, descr);
                                return true;
                            },
                        });
                    };
                    return cloneOnWriteProxy(result, key);
                }
            },
            update: (key, value, arg) => {
                key = section ? `${section}.${key}` : key;
                const resourceStr = resource ? resource.toString() : undefined;
                if (typeof value !== 'undefined') {
                    return this.proxy.$updateConfigurationOption(arg, key, value, resourceStr);
                }
                else {
                    return this.proxy.$removeConfigurationOption(arg, key, resourceStr);
                }
            },
            inspect: (key) => {
                key = section ? `${section}.${key}` : key;
                resource = resource === null ? undefined : resource;
                const result = (0, cloneDeep_1.default)(this._preferences.inspect(key, this.workspace, resource));
                if (!result) {
                    return undefined;
                }
                const configInspect = { key };
                if (typeof result.default !== 'undefined') {
                    configInspect.defaultValue = result.default;
                }
                if (typeof result.user !== 'undefined') {
                    configInspect.globalValue = result.user;
                }
                if (typeof result.workspace !== 'undefined') {
                    configInspect.workspaceValue = result.workspace;
                }
                if (typeof result.workspaceFolder !== 'undefined') {
                    configInspect.workspaceFolderValue = result.workspaceFolder;
                }
                return configInspect;
            },
        };
        if (typeof preferences === 'object') {
            (0, ide_core_common_1.mixin)(configuration, preferences, false);
        }
        return Object.freeze(configuration);
    }
    toReadonlyValue(data) {
        const readonlyProxy = (target) => (0, ide_core_common_1.isObject)(target)
            ? new Proxy(target, {
                get: (targ, prop) => readonlyProxy(targ[prop]),
                set: (targ, prop, val) => {
                    throw new Error(`TypeError: Cannot assign to read only property '${prop}' of object`);
                },
                deleteProperty: (targ, prop) => {
                    throw new Error(`TypeError: Cannot delete read only property '${prop}' of object`);
                },
                defineProperty: (targ, prop) => {
                    throw new Error(`TypeError: Cannot define property '${prop}' of a readonly object`);
                },
                setPrototypeOf: (targ) => {
                    throw new Error('TypeError: Cannot set prototype for a readonly object');
                },
                isExtensible: () => false,
                preventExtensions: () => true,
            })
            : target;
        return readonlyProxy(data);
    }
}
exports.ExtHostPreference = ExtHostPreference;
//# sourceMappingURL=ext.host.preference.js.map