"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
const source_map_1 = require("source-map");
// Matches <%= expr %>. This does not support structural JavaScript (for/if/...).
const kInterpolateRe = /<%=([\s\S]+?)%>/g;
// Matches <%# text %>. It's a comment and will be entirely ignored.
const kCommentRe = /<%#([\s\S]+?)%>/g;
// Used to match template delimiters.
// <%- expr %>: HTML escape the value.
// <% ... %>: Structural template code.
const kEscapeRe = /<%-([\s\S]+?)%>/g;
const kEvaluateRe = /<%([\s\S]+?)%>/g;
/** Used to map characters to HTML entities. */
const kHtmlEscapes = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;',
    '`': '&#96;',
};
// Used to match HTML entities and HTML characters.
const reUnescapedHtml = new RegExp(`[${Object.keys(kHtmlEscapes).join('')}]`, 'g');
function _positionFor(content, offset) {
    let line = 1;
    let column = 0;
    for (let i = 0; i < offset - 1; i++) {
        if (content[i] == '\n') {
            line++;
            column = 0;
        }
        else {
            column++;
        }
    }
    return {
        line,
        column,
    };
}
/**
 * Given a source text (and a fileName), returns a TemplateAst.
 */
function templateParser(sourceText, fileName) {
    const children = [];
    // Compile the regexp to match each delimiter.
    const reExpressions = [kEscapeRe, kCommentRe, kInterpolateRe, kEvaluateRe];
    const reDelimiters = RegExp(reExpressions.map(x => x.source).join('|') + '|$', 'g');
    const parsed = sourceText.split(reDelimiters);
    let offset = 0;
    // Optimization that uses the fact that the end of a node is always the beginning of the next
    // node, so we keep the positioning of the nodes in memory.
    let start = _positionFor(sourceText, offset);
    let end;
    const increment = reExpressions.length + 1;
    for (let i = 0; i < parsed.length; i += increment) {
        const [content, escape, comment, interpolate, evaluate] = parsed.slice(i, i + increment);
        if (content) {
            end = _positionFor(sourceText, offset + content.length);
            offset += content.length;
            children.push({ kind: 'content', content, start, end });
            start = end;
        }
        if (escape) {
            end = _positionFor(sourceText, offset + escape.length + 5);
            offset += escape.length + 5;
            children.push({ kind: 'escape', expression: escape, start, end });
            start = end;
        }
        if (comment) {
            end = _positionFor(sourceText, offset + comment.length + 5);
            offset += comment.length + 5;
            children.push({ kind: 'comment', text: comment, start, end });
            start = end;
        }
        if (interpolate) {
            end = _positionFor(sourceText, offset + interpolate.length + 5);
            offset += interpolate.length + 5;
            children.push({
                kind: 'interpolate',
                expression: interpolate,
                start,
                end,
            });
            start = end;
        }
        if (evaluate) {
            end = _positionFor(sourceText, offset + evaluate.length + 5);
            offset += evaluate.length + 5;
            children.push({ kind: 'evaluate', expression: evaluate, start, end });
            start = end;
        }
    }
    return {
        fileName,
        content: sourceText,
        children,
    };
}
exports.templateParser = templateParser;
/**
 * Fastest implementation of the templating algorithm. It only add strings and does not bother
 * with source maps.
 */
function templateFast(ast, options) {
    const module = options && options.module ? 'module.exports.default =' : '';
    const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, '\\\\\\\'');
    return `
    return ${module} function(obj) {
      obj || (obj = {});
      let __t;
      let __p = '';
      const __escapes = ${JSON.stringify(kHtmlEscapes)};
      const __escapesre = new RegExp('${reHtmlEscape}', 'g');

      const __e = function(s) {
        return s ? s.replace(__escapesre, function(key) { return __escapes[key]; }) : '';
      };
      with (obj) {
        ${ast.children.map(node => {
        switch (node.kind) {
            case 'content':
                return `__p += ${JSON.stringify(node.content)};`;
            case 'interpolate':
                return `__p += ((__t = (${node.expression})) == null) ? '' : __t;`;
            case 'escape':
                return `__p += __e(${node.expression});`;
            case 'evaluate':
                return node.expression;
        }
    }).join('\n')}
      }

      return __p;
    };
  `;
}
/**
 * Templating algorithm with source map support. The map is outputted as //# sourceMapUrl=...
 */
function templateWithSourceMap(ast, options) {
    const sourceUrl = ast.fileName;
    const module = options && options.module ? 'module.exports.default =' : '';
    const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, '\\\\\\\'');
    const preamble = (new source_map_1.SourceNode(1, 0, sourceUrl, ''))
        .add(new source_map_1.SourceNode(1, 0, sourceUrl, [
        `return ${module} function(obj) {\n`,
        '  obj || (obj = {});\n',
        '  let __t;\n',
        '  let __p = "";\n',
        `  const __escapes = ${JSON.stringify(kHtmlEscapes)};\n`,
        `  const __escapesre = new RegExp('${reHtmlEscape}', 'g');\n`,
        `\n`,
        `  const __e = function(s) { `,
        `    return s ? s.replace(__escapesre, function(key) { return __escapes[key]; }) : '';`,
        `  };\n`,
        `  with (obj) {\n`,
    ]));
    const end = ast.children.length
        ? ast.children[ast.children.length - 1].end
        : { line: 0, column: 0 };
    const nodes = ast.children.reduce((chunk, node) => {
        let code = '';
        switch (node.kind) {
            case 'content':
                code = [
                    new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, '__p = __p'),
                    ...node.content.split('\n').map((line, i, arr) => {
                        return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, '\n    + '
                            + JSON.stringify(line + (i == arr.length - 1 ? '' : '\n')));
                    }),
                    new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, ';\n'),
                ];
                break;
            case 'interpolate':
                code = [
                    new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, '__p += ((__t = '),
                    ...node.expression.split('\n').map((line, i, arr) => {
                        return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, line + ((i == arr.length - 1) ? '' : '\n'));
                    }),
                    new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, ') == null ? "" : __t);\n'),
                ];
                break;
            case 'escape':
                code = [
                    new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, '__p += __e('),
                    ...node.expression.split('\n').map((line, i, arr) => {
                        return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, line + ((i == arr.length - 1) ? '' : '\n'));
                    }),
                    new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, ');\n'),
                ];
                break;
            case 'evaluate':
                code = [
                    ...node.expression.split('\n').map((line, i, arr) => {
                        return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, line + ((i == arr.length - 1) ? '' : '\n'));
                    }),
                    new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, '\n'),
                ];
                break;
        }
        return chunk.add(new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, code));
    }, preamble)
        .add(new source_map_1.SourceNode(end.line, end.column, sourceUrl, [
        '  };\n',
        '\n',
        '  return __p;\n',
        '}\n',
    ]));
    const code = nodes.toStringWithSourceMap({
        file: sourceUrl,
        sourceRoot: options && options.sourceRoot || '.',
    });
    // Set the source content in the source map, otherwise the sourceUrl is not enough
    // to find the content.
    code.map.setSourceContent(sourceUrl, ast.content);
    return code.code
        + '\n//# sourceMappingURL=data:application/json;base64,'
        + Buffer.from(code.map.toString()).toString('base64');
}
/**
 * An equivalent of EJS templates, which is based on John Resig's `tmpl` implementation
 * (http://ejohn.org/blog/javascript-micro-templating/) and Laura Doktorova's doT.js
 * (https://github.com/olado/doT).
 *
 * This version differs from lodash by removing support from ES6 quasi-literals, and making the
 * code slightly simpler to follow. It also does not depend on any third party, which is nice.
 *
 * Finally, it supports SourceMap, if you ever need to debug, which is super nice.
 *
 * @param content The template content.
 * @param options Optional Options. See TemplateOptions for more description.
 * @return {(input: T) => string} A function that accept an input object and returns the content
 *         of the template with the input applied.
 */
function template(content, options) {
    const sourceUrl = options && options.sourceURL || 'ejs';
    const ast = templateParser(content, sourceUrl);
    let source;
    // If there's no need for source map support, we revert back to the fast implementation.
    if (options && options.sourceMap) {
        source = templateWithSourceMap(ast, options);
    }
    else {
        source = templateFast(ast, options);
    }
    // We pass a dummy module in case the module option is passed. If `module: true` is passed, we
    // need to only use the source, not the function itself. Otherwise expect a module object to be
    // passed, and we use that one.
    const fn = Function('module', source);
    const module = options && options.module
        ? (options.module === true ? { exports: {} } : options.module)
        : null;
    const result = fn(module);
    // Provide the compiled function's source by its `toString` method or
    // the `source` property as a convenience for inlining compiled templates.
    result.source = source;
    return result;
}
exports.template = template;
