"use strict";
/*
 * edge
 *
 * (c) Harminder Virk <virk@adonisjs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Template = exports.safeValue = exports.escape = exports.SafeValue = void 0;
const macroable_1 = require("macroable");
const edge_error_1 = require("edge-error");
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
const Props_1 = require("../Component/Props");
/**
 * An instance of this class passed to the escape
 * method ensures that underlying value is never
 * escaped.
 */
class SafeValue {
    constructor(value) {
        this.value = value;
    }
}
exports.SafeValue = SafeValue;
/**
 * Escapes a given string
 */
function escape(input) {
    return input instanceof SafeValue ? input.value : helpers_1.string.escapeHTML(String(input));
}
exports.escape = escape;
/**
 * Mark value as safe and not to be escaped
 */
function safeValue(value) {
    return new SafeValue(value);
}
exports.safeValue = safeValue;
/**
 * The template is used to compile and run templates. Also the instance
 * of template is passed during runtime to render `dynamic partials`
 * and `dynamic components`.
 */
class Template extends macroable_1.Macroable {
    constructor(compiler, globals, locals, processor) {
        super();
        this.compiler = compiler;
        this.processor = processor;
        this.sharedState = utils_1.lodash.merge({}, globals, locals);
    }
    /**
     * Wraps template to a function
     */
    wrapToFunction(template, ...localVariables) {
        const args = ['template', 'state', '$context'].concat(localVariables);
        if (this.compiler.async) {
            return new Function('', `return async function template (${args.join(',')}) { ${template} }`)();
        }
        return new Function('', `return function template (${args.join(',')}) { ${template} }`)();
    }
    /**
     * Trims top and bottom new lines from the content
     */
    trimTopBottomNewLines(value) {
        return value.replace(/^\n|^\r\n/, '').replace(/\n$|\r\n$/, '');
    }
    /**
     * Render a compiled template with state
     */
    renderCompiled(compiledTemplate, state) {
        const templateState = Object.assign({}, this.sharedState, state);
        const $context = {};
        /**
         * Process template as a promise.
         */
        if (this.compiler.async) {
            return this.wrapToFunction(compiledTemplate)(this, templateState, $context).then((output) => {
                output = this.trimTopBottomNewLines(output);
                return this.processor.executeOutput({ output, template: this, state: templateState });
            });
        }
        const output = this.trimTopBottomNewLines(this.wrapToFunction(compiledTemplate)(this, templateState, $context));
        return this.processor.executeOutput({ output, template: this, state: templateState });
    }
    /**
     * Render a partial
     *
     * ```js
     * const partialFn = template.compilePartial('includes/user')
     *
     * // render and use output
     * partialFn(template, state, ctx)
     * ```
     */
    compilePartial(templatePath, ...localVariables) {
        const { template: compiledTemplate } = this.compiler.compile(templatePath, localVariables, true);
        return this.wrapToFunction(compiledTemplate, ...localVariables);
    }
    /**
     * Render a component
     *
     * ```js
     * const componentFn = template.compileComponent('components/button')
     *
     * // render and use output
     * componentFn(template, template.getComponentState(props, slots, caller), ctx)
     * ```
     */
    compileComponent(templatePath, ...localVariables) {
        const { template: compiledTemplate } = this.compiler.compile(templatePath, localVariables);
        return this.wrapToFunction(compiledTemplate, ...localVariables);
    }
    /**
     * Returns the isolated state for a given component
     */
    getComponentState(props, slots, caller) {
        return Object.assign({}, this.sharedState, props, {
            $slots: slots,
            $caller: caller,
            $props: new Props_1.Props(props),
        });
    }
    /**
     * Render a template with it's state.
     *
     * ```js
     * template.render('welcome', { key: 'value' })
     * ```
     */
    render(template, state) {
        let { template: compiledTemplate } = this.compiler.compile(template);
        return this.renderCompiled(compiledTemplate, state);
    }
    /**
     * Render template from a raw string
     *
     * ```js
     * template.renderRaw('Hello {{ username }}', { username: 'virk' })
     * ```
     */
    renderRaw(contents, state, templatePath) {
        let { template: compiledTemplate } = this.compiler.compileRaw(contents, templatePath);
        return this.renderCompiled(compiledTemplate, state);
    }
    /**
     * Escapes the value to be HTML safe. Only strings are escaped
     * and rest all values will be returned as it is.
     */
    escape(input) {
        return escape(input);
    }
    /**
     * Raise an error
     */
    newError(errorMessage, filename, lineNumber, column) {
        throw new edge_error_1.EdgeError(errorMessage, 'E_RUNTIME_EXCEPTION', {
            filename: filename,
            line: lineNumber,
            col: column,
        });
    }
    /**
     * Rethrows the runtime exception by re-constructing the error message
     * to point back to the original filename
     */
    reThrow(error, filename, lineNumber) {
        if (error instanceof edge_error_1.EdgeError) {
            throw error;
        }
        const message = error.message.replace(/state\./, '');
        throw new edge_error_1.EdgeError(message, 'E_RUNTIME_EXCEPTION', {
            filename: filename,
            line: lineNumber,
            col: 0,
        });
    }
}
/**
 * Required by Macroable
 */
Template.macros = {};
Template.getters = {};
exports.Template = Template;
