const path = require("path");
const Handlebars = require("handlebars");
const {checkType, checkInstance, resolveFileOrString} = require("./Utility");
const {Config} = require("./Config");
const templateGlobal = {};
const ext = ".hbs";

/**
 * HTML templates for visualizations in Jupyter
 */
class HtmlTemplate {
    /**
     * Create a HTML template from a file or string that can be rendered to HTML later using `toHtml`
     *
     * @param  {string} template The name of a file to load or a string representing a template
     * @param  {Object} opts     Options for the template
     * @returns {HtmlTemplate}    The newly created template
     */
    constructor(template, opts = {}) {
        let basedir = Config.get("html-template-dir");

        // load contents of file
        this.rawTemplate = resolveFileOrString(template, {basedir, ext});

        // compile the template
        this.hbTemplate = Handlebars.compile(this.rawTemplate);

        // save required data
        if (opts.requiredData) {
            checkInstance("HtmlTemplate.constructor", "opts.requiredData", opts.requiredData, Array);
            this.requiredData = opts.requiredData;
        }
    }

    /**
     * Convert data to HTML using the template
     *
     * @param  {Object} userData An object containing the data to be used in the template
     * @returns {string}          The HTML of the rendered template
     */
    toHtml(userData = {}) {
        checkType("HtmlTemplate.toHtml", "userData", userData, "object");

        // create data object
        let data = {... userData};
        data.templateGlobal = templateGlobal;

        // TODO: validate data

        // run previously compiled handlebars template
        return this.hbTemplate(data);
    }

    /**
     * Set a global data value that will be passed as part of the data object in all templates as `templateGlobal`.
     *
     * @param {string} key The property to set in `templateGlobal`
     * @param {any} val The value to assign to the key
     */
    static setGlobal(key, val) {
        checkType("HtmlTemplate.setGlobal", "key", key, "string");
        if (Object.keys(templateGlobal).includes(key)) {
            throw new TypeError(`HtmlTemplate.setGlobal: '${key}' already exists`);
        }

        templateGlobal[key] = val;
    }

    /**
     * Return previously set global values. Mostly used for testing.
     *
     * @returns {object} Object containing all set globals.
     */
    static getGlobals() {
        return templateGlobal;
    }

    /**
     * Remove all previously set global values. Mostly used for testing.
     */
    static resetGlobals() {
        for (let key of Object.keys(templateGlobal)) {
            delete templateGlobal[key];
        }
    }

    /**
     * Initialize the templating system
     *
     * @returns {Promise} A Promise that resolves with initialization is complete
     */
    static async init() {
        let basedir = Config.get("html-template-dir");

        // load helpers
        let helperObj = require(path.resolve(basedir, "helpers"));
        Object.keys(helperObj).forEach((name) => Handlebars.registerHelper(name, helperObj[name]));

        // load partials
        let partials = require(path.resolve(basedir, "partials"));
        Object.keys(partials).forEach((name) => Handlebars.registerPartial(name, resolveFileOrString(partials[name], {basedir, ext})));
    }

    /**
     * Shutdown the templating system
     *
     * @returns {Promise} A Promise that resolves when initialization is complete
     */
    static async shutdown() {
        // XXX: this will break if the config changes after init; maybe each template should have a different Handlebars instance
        let basedir = Config.get("html-template-dir");

        // unload helpers
        let helperObj = require(path.resolve(basedir, "helpers"));
        Object.keys(helperObj).forEach((name) => Handlebars.unregisterHelper(name));

        // unload partials
        let partials = require(path.resolve(basedir, "partials"));
        Object.keys(partials).forEach((name) => Handlebars.unregisterPartial(name));

        // remove globals
        HtmlTemplate.resetGlobals();
    }
}

module.exports = {
    HtmlTemplate,
};