/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Workspace
 */
import * as fs from "fs-extra";
import { parse } from "json5";
import { extname, join } from "path";
import { BeEvent } from "@itwin/core-bentley";
import { IModelJsFs } from "../../IModelJsFs";
import { Setting } from "../../workspace/Settings";
import { IModelHost } from "../../IModelHost";
import { _implementationProhibited } from "../Symbols";
const dictionaryMatches = (d1, d2) => {
    return (d1.workspaceDb === d2.workspaceDb) && (d1.name === d2.name);
};
class SettingsDictionaryImpl {
    [_implementationProhibited] = undefined;
    props;
    settings;
    constructor(props, settings) {
        this.props = { ...props }; // make a copy so it can't be changed by caller
        this.settings = settings;
    }
    getSetting(settingName) {
        const value = this.settings[settingName];
        return undefined !== value ? Setting.clone(value) : undefined;
    }
}
/**
 * Internal implementation of Settings interface.
 * @internal
 */
export class SettingsImpl {
    [_implementationProhibited] = undefined;
    dictionaries = [];
    verifyPriority(_priority) { }
    close() { }
    onSettingsChanged = new BeEvent();
    addFile(fileName, priority) {
        this.addJson({ name: fileName, priority }, fs.readFileSync(fileName, "utf-8"));
    }
    addDirectory(dirName, priority) {
        for (const fileName of IModelJsFs.readdirSync(dirName)) {
            const ext = extname(fileName);
            if (ext === ".json5" || ext === ".json")
                this.addFile(join(dirName, fileName), priority);
        }
    }
    addJson(props, settingsJson) {
        this.addDictionary(props, parse(settingsJson));
    }
    addDictionary(props, settings) {
        this.verifyPriority(props.priority);
        this.dropDictionary(props, false); // make sure we don't have the same dictionary twice
        const dict = new SettingsDictionaryImpl(props, settings);
        const doAdd = () => {
            for (let i = 0; i < this.dictionaries.length; ++i) {
                if (this.dictionaries[i].props.priority <= dict.props.priority) {
                    this.dictionaries.splice(i, 0, dict);
                    return;
                }
            }
            this.dictionaries.push(dict);
        };
        doAdd();
        this.onSettingsChanged.raiseEvent();
    }
    getDictionary(source) {
        for (const dictionary of this.dictionaries) {
            if (dictionaryMatches(dictionary.props, source))
                return dictionary;
        }
        return undefined;
    }
    dropDictionary(source, raiseEvent = true) {
        for (let i = 0; i < this.dictionaries.length; ++i) {
            if (dictionaryMatches(this.dictionaries[i].props, source)) {
                this.dictionaries.splice(i, 1);
                if (raiseEvent)
                    this.onSettingsChanged.raiseEvent();
                return true;
            }
        }
        return false;
    }
    *getSettingEntries(settingName) {
        for (const dictionary of this.dictionaries) {
            const value = dictionary.getSetting(settingName);
            if (undefined !== value) {
                yield { value, dictionary };
            }
        }
    }
    *getSettingValues(settingName) {
        for (const entry of this.getSettingEntries(settingName)) {
            yield entry.value;
        }
    }
    getSetting(settingName, defaultValue) {
        for (const value of this.getSettingValues(settingName)) {
            return value;
        }
        return defaultValue;
    }
    // get the setting and verify the result is either undefined or the correct type. If so, return it. Otherwise throw an exception.
    getResult(name, expectedType) {
        const out = this.getSetting(name);
        if (out === undefined || typeof out === expectedType)
            return out;
        throw new Error(`setting "${name}" is not a ${expectedType}: ${typeof out}`);
    }
    getString(name, defaultValue) {
        return this.getResult(name, "string") ?? defaultValue;
    }
    getBoolean(name, defaultValue) {
        return this.getResult(name, "boolean") ?? defaultValue;
    }
    getNumber(name, defaultValue) {
        return this.getResult(name, "number") ?? defaultValue;
    }
    getObject(name, defaultValue) {
        const out = this.getResult(name, "object");
        return out ? IModelHost.settingsSchemas.validateSetting(out, name) : defaultValue;
    }
    getArray(name, defaultValue) {
        if (IModelHost.settingsSchemas.settingDefs.get(name)?.combineArray) {
            return this.getCombinedArray(name, defaultValue);
        }
        const out = this.getSetting(name);
        if (out === undefined)
            return defaultValue;
        if (!Array.isArray(out))
            throw new Error(`setting ${name} is not an array: ${String(out)}`);
        return IModelHost.settingsSchemas.validateSetting(out, name);
    }
    getCombinedArray(name, defaultValue) {
        let foundSetting = false;
        const out = [];
        for (const array of this.getSettingValues(name)) {
            foundSetting = true;
            IModelHost.settingsSchemas.validateSetting(array, name);
            for (const value of array) {
                if (undefined === out.find((x) => Setting.areEqual(x, value))) {
                    out.push(value);
                }
            }
        }
        return foundSetting ? out : defaultValue;
    }
}
//# sourceMappingURL=SettingsImpl.js.map