"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelState = void 0;
const extension_base_1 = require("@autorest/extension-base");
const codegen_1 = require("@azure-tools/codegen");
const tsp_host_1 = require("./tsp-host");
const path_1 = require("path");
class ModelState extends codegen_1.Initializer {
    constructor(service, objectInitializer) {
        super();
        this.service = service;
        this.currentPath = new Array();
        this._debug = false;
        this._verbose = false;
        this.errorCount = 0;
        this.apply(objectInitializer);
    }
    async init(project) {
        if (this.service instanceof tsp_host_1.TspHostImpl) {
            // skip init for tsp
            this.outputFolder = await this.getValue('output-folder', './generated');
            this.initContext(project);
            return this;
        }
        const m = await ModelState.getModel(this.service);
        this.model = m.model;
        this.documentName = m.filename;
        this.initContext(project);
        this._debug = await this.getValue('debug', false);
        this._verbose = await this.getValue('verbose', false);
        return this;
    }
    async initContext(project) {
        this.context = this.context || {
            $config: await this.service.getValue(''),
            $project: project,
            $lib: {
                path: require('path')
            }
        };
        return this;
    }
    async readFile(filename) {
        return this.service.readFile(filename);
    }
    async getValue(key, defaultValue) {
        // check if it's in the model first
        let value = this.model && this.model.language && this.model.language.default ? this.model.language.default[key] : undefined;
        // fall back to the configuration
        if (value == null || value === undefined) {
            value = await this.service.getValue(key);
        }
        // try as a safe eval execution.
        if (value === null || value === undefined) {
            try {
                value = (0, codegen_1.safeEval)(key, this.context);
            }
            catch (_a) {
                value = null;
            }
        }
        if (defaultValue === undefined && value === null) {
            throw new Error(`No value for configuration key '${key}' was provided`);
        }
        if (typeof value === 'string') {
            value = await this.resolveVariables(value);
        }
        // ensure that any content variables are resolved at the end.
        return (value !== null ? value : defaultValue);
    }
    async setValue(key, value) {
        this.model.language.default[key] = value;
    }
    async listInputs(artifactType) {
        return this.service.listInputs(artifactType);
    }
    async protectFiles(path) {
        return this.service.protectFiles(path);
    }
    writeFile(filename, content, sourceMap, artifactType) {
        return this.service.writeFile({ filename: this.outputFolder ? (0, path_1.join)(this.outputFolder, filename) : filename, content: content, sourceMap: sourceMap, artifactType: artifactType });
    }
    message(message) {
        if (message.Channel === extension_base_1.Channel.Debug && this._debug === false) {
            return;
        }
        if (message.Channel === extension_base_1.Channel.Verbose && this._verbose === false) {
            return;
        }
        return this.service.message(message);
    }
    updateConfigurationFile(filename, content) {
        return this.service.UpdateConfigurationFile(filename, content);
    }
    async getConfigurationFile(filename) {
        return this.service.GetConfigurationFile(filename);
    }
    static async getModel(service) {
        const files = await service.listInputs();
        const filename = files[0];
        if (files.length === 0) {
            throw new Error('Inputs missing.');
        }
        return {
            filename,
            model: (0, codegen_1.deserialize)(await service.readFile(filename), filename)
        };
    }
    replacer(key, value) {
        this.cache = this.cache || new Array();
        if (typeof value === 'object' && value !== null) {
            if (this.cache.indexOf(value) !== -1) {
                // Duplicate reference found
                try {
                    // If this value does not reference a parent it can be deduped
                    return JSON.parse(JSON.stringify(value));
                }
                catch (error) {
                    // discard key if value cannot be deduped
                    return;
                }
            }
            // Store value in our collection
            this.cache.push(value);
        }
        return value;
    }
    async resolveVariables(input) {
        let output = input;
        for (const rx of [/\$\((.*?)\)/g, /\$\{(.*?)\}/g]) {
            /* eslint-disable */
            for (let match; match = rx.exec(input);) {
                const text = match[0];
                const inner = match[1];
                let value = await this.getValue(inner, null);
                if (value !== undefined && value !== null) {
                    if (typeof value === 'object') {
                        value = JSON.stringify(value, this.replacer, 2);
                    }
                    if (value === '{}') {
                        value = 'true';
                    }
                    output = output.replace(text, value);
                }
            }
        }
        return output;
    }
    path(...childPath) {
        // this strategy for tracking source path locations
        // has proved fundementally crappy.
        // will be removing this stuff and transitioning to source-track method.
        //const result = new ModelState<T>(this.service, <any>this);
        //result.currentPath = [...this.currentPath, ...childPath];
        // return result;
        return this;
    }
    checkpoint() {
        if (this.errorCount > 0) {
            throw new Error();
        }
    }
    msg(channel, message, key, details) {
        this.message({
            Channel: channel,
            Key: key,
            Source: [
                {
                    document: this.documentName,
                    Position: {
                        path: this.currentPath
                    }
                }
            ],
            Text: message,
            Details: details
        });
    }
    warning(message, key, details) {
        this.msg(extension_base_1.Channel.Warning, message, key, details);
    }
    hint(message, key, details) {
        this.msg(extension_base_1.Channel.Hint, message, key, details);
    }
    error(message, key, details) {
        this.errorCount++;
        this.msg(extension_base_1.Channel.Error, message, key, details);
    }
    fatal(message, key, details) {
        this.errorCount++;
        this.msg(extension_base_1.Channel.Fatal, message, key, details);
    }
    output(channel, message, details) {
        this.message({
            Channel: channel,
            Text: message,
            Details: details
        });
    }
    debug(message, details) {
        this.output(extension_base_1.Channel.Debug, message, details);
    }
    verbose(message, details) {
        this.output(extension_base_1.Channel.Verbose, message, details);
    }
    log(message, details) {
        this.output(extension_base_1.Channel.Information, message, details);
    }
}
exports.ModelState = ModelState;
//# sourceMappingURL=model-state.js.map