import { escape } from './escape.mjs';
import { attempt } from '../function/attempt.mjs';
import { defaults } from '../object/defaults.mjs';
import { toString } from '../util/toString.mjs';

const esTemplateRegExp = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
const unEscapedRegExp = /['\n\r\u2028\u2029\\]/g;
const noMatchExp = /($^)/;
const escapeMap = new Map([
    ['\\', '\\'],
    ["'", "'"],
    ['\n', 'n'],
    ['\r', 'r'],
    ['\u2028', 'u2028'],
    ['\u2029', 'u2029'],
]);
function escapeString(match) {
    return `\\${escapeMap.get(match)}`;
}
const defaultInterpolateRegExp = /<%=([\s\S]+?)%>/g;
const templateSettings = {
    escape: /<%-([\s\S]+?)%>/g,
    evaluate: /<%([\s\S]+?)%>/g,
    interpolate: defaultInterpolateRegExp,
    variable: '',
    imports: {
        _: {
            escape,
            template,
        },
    },
};
function template(string, options, guard) {
    string = toString(string);
    if (guard) {
        options = templateSettings;
    }
    options = defaults({ ...options }, templateSettings);
    const delimitersRegExp = new RegExp([
        options.escape?.source ?? noMatchExp.source,
        options.interpolate?.source ?? noMatchExp.source,
        options.interpolate === defaultInterpolateRegExp ? esTemplateRegExp.source : noMatchExp.source,
        options.evaluate?.source ?? noMatchExp.source,
        '$',
    ].join('|'), 'g');
    let lastIndex = 0;
    let isEvaluated = false;
    let source = `__p += ''`;
    for (const match of string.matchAll(delimitersRegExp)) {
        const [fullMatch, escapeValue, interpolateValue, esTemplateValue, evaluateValue] = match;
        const { index } = match;
        source += ` + '${string.slice(lastIndex, index).replace(unEscapedRegExp, escapeString)}'`;
        if (escapeValue) {
            source += ` + _.escape(${escapeValue})`;
        }
        if (interpolateValue) {
            source += ` + ((${interpolateValue}) == null ? '' : ${interpolateValue})`;
        }
        else if (esTemplateValue) {
            source += ` + ((${esTemplateValue}) == null ? '' : ${esTemplateValue})`;
        }
        if (evaluateValue) {
            source += `;\n${evaluateValue};\n __p += ''`;
            isEvaluated = true;
        }
        lastIndex = index + fullMatch.length;
    }
    const imports = defaults({ ...options.imports }, templateSettings.imports);
    const importsKeys = Object.keys(imports);
    const importValues = Object.values(imports);
    const sourceURL = `//# sourceURL=${options.sourceURL ? String(options.sourceURL).replace(/[\r\n]/g, ' ') : `es-toolkit.templateSource[${Date.now()}]`}\n`;
    const compiledFunction = `function(${options.variable || 'obj'}) {
    let __p = '';
    ${options.variable ? '' : 'if (obj == null) { obj = {}; }'}
    ${isEvaluated ? `function print() { __p += Array.prototype.join.call(arguments, ''); }` : ''}
    ${options.variable ? source : `with(obj) {\n${source}\n}`}
    return __p;
  }`;
    const result = attempt(() => new Function(...importsKeys, `${sourceURL}return ${compiledFunction}`)(...importValues));
    result.source = compiledFunction;
    if (result instanceof Error) {
        throw result;
    }
    return result;
}

export { template, templateSettings };
