import fs from "node:fs";
import path from "node:path";
export default class Template {
    constructor() {
        this.views = "./views";
        this.globals = {};
        this.debug = false;
        this.cache = true;
        this.templates = new Map();
    }
    setDebug(debug) {
        this.debug = debug;
    }
    setViews(views) {
        this.views = views;
        this.readdir(views);
    }
    setGlobals(globals) {
        this.globals = globals;
    }
    setCache(cache) {
        this.cache = cache;
    }
    readdir(dir, root = dir) {
        for (const d of fs.readdirSync(`${path.resolve()}/${dir}`)) {
            if (d.endsWith(".html")) {
                const file = path.parse(`${dir.replace(this.views, "")}/${d}`);
                const t_dir = file.dir.substring(1);
                const t_file = file.name;
                this.getTemplate(!t_dir ? t_file : `${t_dir}/${t_file}`);
            }
            else {
                this.readdir(`${dir}/${d}`, root);
            }
        }
    }
    getTemplate(tpl) {
        let template;
        let cache = false;
        if (this.cache && this.templates.has(tpl)) {
            template = this.templates.get(tpl);
            cache = true;
        }
        else {
            template = {
                code: fs.readFileSync(`${this.views}/${tpl}.html`, "utf-8"),
                cached: new Date(),
            };
            this.templates.set(tpl, template);
        }
        return [
            template.code,
            this.debug ? `<!-- ${tpl}.html ${cache ? `cached ${template.cached}` : "not cached"} -->` : "",
        ].join("");
    }
    render(file, data = {}, locals = {}) {
        data = Object.assign(Object.assign(Object.assign({}, data), locals), this.globals);
        try {
            const code = 'with(_data){const __html = [];' +
                '__html.push(`' +
                this.getTemplate(file)
                    .replace(/[\t]/g, " ")
                    .split("`")
                    .join("\\`")
                    .replace(/{{--(.*?)--}}/g, "")
                    .replace(/{{(.+?)}}/g, "`, $1, `")
                    .replace(/{!!(.+?)!!}/g, "`, this.escape($1), `")
                    .replace(/@js/g, "`);")
                    .replace(/@endjs/g, ";__html.push(`")
                    .replace(/@mtime\((.*?)\)/g, "`);__html.push(this.getMtime('$1'));__html.push(`")
                    .replace(/@include\((.*?)\)/g, (_, inc) => this.render(inc, data))
                    .replace(/@for\((.*?)\)$/gm, "`); for($1) { __html.push(`")
                    .replace(/@endfor/g, "`); } __html.push(`")
                    .replace(/@each\((.*?) as (.*?)\)/g, "`); this.forEach($1, ($2, key) => { __html.push(`")
                    .replace(/@endeach/g, "`); }); __html.push(`")
                    .replace(/@elseif\((.*?)\)(\)?)/g, "`); } else if ($1$2) { __html.push(`")
                    .replace(/@if\((.*?)\)(\)?)/g, "`); if ($1$2) { __html.push(`")
                    .replace(/@else/g, "`); } else { __html.push(`")
                    .replace(/@endif/g, "`); } __html.push(`") +
                "`); return __html.join('').replace(/\\n\\s*\\n/g, '\\n'); }";
            return new Function("_data", code).bind({
                escape: this.escape,
                forEach: this.forEach,
                getMtime: this.getMtime,
            })(data);
        }
        catch (err) {
            console.log(file, err.message);
            return this.debug ? `${err.message} in ${file}` : "";
        }
    }
    escape(str) {
        return (str + "")
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#39;")
            .replace(/{/g, "&#123;")
            .replace(/}/g, "&#125;");
    }
    forEach(o, f) {
        if (Array.isArray(o)) {
            o.forEach(f);
        }
        else if (typeof o === "object") {
            Object.keys(o).forEach((k) => f.call(null, o[k], k));
        }
        else {
            throw new Error(`${o} is not an iterable object`);
        }
    }
    getMtime(file) {
        try {
            return +`${fs.statSync(path.normalize(process.cwd() + file)).mtimeMs}`.split(".")[0];
        }
        catch (err) {
            console.log(err);
            return 0;
        }
    }
}