"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LibraryConfig = void 0;
const fs_1 = __importDefault(require("fs"));
const object_hash_1 = __importDefault(require("object-hash"));
const os_1 = __importDefault(require("os"));
const util_1 = __importDefault(require("util"));
const detect_environment_1 = require("../util/detect-environment");
const base_config_1 = require("./base-config");
const config_util_1 = require("./config-util");
const contracts_1 = require("./contracts");
/**
 * This class provides all the functionality to work with configuration files
 * within the project (TS library)
 *
 * Configuration levels:
 * 1. Default - hardcoded values in the config-schema.ts file.
 * (to make it possible come back to default configuration after config files changing and re-read them).
 * Can be overridden by the higher levels (2, 3 ...)
 * 2. User's Home directory level. For Unix - $HOME, Windows - %HOMEPATH%. Can be overridden by the higher levels (3, ...)
 * 3. Project Working Directory level - process.cwd(). Can be overridden by higher levels (env, args)
 * 4. Environment variables can't be overridden by config files, but can with the process arguments (node index.js --loglevel value)
 * 5. Process arguments can't be overridden. But you still can manipulate them programmatically in your code.
 * How to set up env variables and process arguments see 'convict npm => readme.md'
 * Default config is saved to config-schema.ts
 */
class LibraryConfig extends base_config_1.BaseConfig {
    constructor(schema) {
        super(schema);
        this.configFileChangedHandler = this.configFileChangedHandler.bind(this);
        this.watchedFiles = [];
        this.init();
    }
    get isBrowser() {
        return (0, detect_environment_1.detectEnvironment)() === "WEB" /* WEB */;
    }
    /**
     * Returns default event name. Is used as event name to notify that config
     * has been changed
     */
    get CONFIG_FILE_UPDATED() {
        return contracts_1.CONFIG_FILE_UPDATED;
    }
    /**
     * Event name for event when method set() is used to change a config
     */
    get CONFIG_SET() {
        return contracts_1.NEW_CONFIG_VALUE_SET;
    }
    set(name, value) {
        const setResult = super.set(name, value);
        this.emit(this.CONFIG_SET, this.get(), name, value);
        return setResult;
    }
    /**
     * Returns path of configuration files to be monitored for changes
     * If the file does not exist - the path to the file will still be present in this list
     * The file with the higher index in the array has higher priority
     */
    getConfigFilesPath() {
        const env = this.get('platform');
        const configDir = this.get('dir');
        const userConfigFilePath = (0, config_util_1.getConfigFilePath)(env, os_1.default.homedir());
        const projectConfigFilePath = (0, config_util_1.getConfigFilePath)(env, configDir);
        return Array.from(new Set([userConfigFilePath, projectConfigFilePath]));
    }
    /**
     * EventEmitter event to notify observes that any config file has been changed
     * @param {string} event Name of event to be emitted once any config file has been changed. Default - 'update'.
     * @param {ConfigSchema} data Configuration object. Default - full configuration object after merging all the config files.
     * @param {string} source Source of the configuration which has been changed (file path of 'default-config')
     */
    notifyConfigUpdated(event = contracts_1.CONFIG_FILE_UPDATED, data = this.get(), source) {
        return this.emit(event, data, source);
    }
    /**
     * Watch for changes in the files and re-read config files on any changes (delete/add/change a file)
     */
    watch() {
        if (this.isBrowser) {
            return;
        }
        this.getConfigFilesPath().forEach(file => {
            if (this.watchedFiles.includes(file)) {
                return;
            }
            fs_1.default.watchFile(file, (curr, prev) => this.configFileChangedHandler(curr, prev, file));
            this.watchedFiles.push(file);
        });
    }
    /**
     * Remove configuration file watchers
     */
    unwatch() {
        if (this.isBrowser) {
            return;
        }
        this.watchedFiles.forEach(file => fs_1.default.unwatchFile(file));
        this.watchedFiles = [];
    }
    /**
     * Method to be used by developers to extend/override default library config to new defaults
     * @param configExtend Object to extend default configuration
     */
    extendDefault(configExtend) {
        this.registerExtendedDefault(configExtend);
        this.resetConfig();
        this.init();
    }
    /**
     * Read and merge all the configuration files with appropriate priorities
     */
    init() {
        if (this.isBrowser) {
            return;
        }
        const configFileList = this.getConfigFilesPath();
        for (const configFile of configFileList) {
            try {
                const fileContent = fs_1.default.readFileSync(configFile, { encoding: 'utf-8' });
                const withReplacedTemplates = (0, config_util_1.replaceConfigTemplates)(fileContent);
                this.load(withReplacedTemplates);
                // tslint:disable-next-line: no-empty
            }
            catch (error) { }
        }
    }
    /**
     * Default watcher for changes in config files
     * @param {Stats} current Current Stats of a watched file
     * @param {Stats} prev Previous Stats of a watched file
     * @param {string} source Source of the configuration which has been changed (file path of 'default-config')
     */
    async configFileChangedHandler(current, prev, source) {
        if (current.isFile() || prev.isFile()) {
            const previousConfig = this.get();
            this.resetConfig();
            for (const file of this.getConfigFilesPath()) {
                try {
                    const fileContent = await util_1.default.promisify(fs_1.default.readFile)(file, { encoding: 'utf-8' });
                    const replacedTemplates = (0, config_util_1.replaceConfigTemplates)(fileContent);
                    this.load(replacedTemplates);
                    // tslint:disable-next-line: no-empty
                }
                catch (error) { }
            }
            const currentConfig = this.get();
            if ((0, object_hash_1.default)(previousConfig) !== (0, object_hash_1.default)(currentConfig)) {
                this.notifyConfigUpdated(undefined, undefined, source);
            }
        }
    }
}
exports.LibraryConfig = LibraryConfig;
//# sourceMappingURL=data:application/json;base64, 