"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.pageVueServerRenderer = void 0;
/* eslint-disable import/no-import-module-exports */
/*
  Note: the function `requireFromString` causes eslint to detect a false-positive
  due to the usage of `module`. Hence, the use of the above `eslint-disable`.
*/
const Vue = __importStar(require("vue"));
const vue_1 = require("vue");
const server_renderer_1 = require("vue/server-renderer");
const compiler_sfc_1 = require("vue/compiler-sfc");
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const logger = __importStar(require("../utils/logger"));
const PluginManager_1 = require("../plugins/PluginManager");
/* eslint-enable import/no-import-module-exports */
let bundle = require('@markbind/core-web/dist/js/vueCommonAppFactory.min');
let customElementTagsCache;
/**
 * Retrieves the set of tags that should be treated as custom elements by the Vue compiler.
 * These are tags defined in plugins with isCustomElement: true.
 */
function getCustomElementTags() {
    if (customElementTagsCache) {
        return customElementTagsCache;
    }
    customElementTagsCache = new Set(Object.entries(PluginManager_1.PluginManager.tagConfig)
        .filter(([, config]) => config.isCustomElement)
        .map(([tagName]) => tagName));
    return customElementTagsCache;
}
/**
 * Compiles a Vue page template into a JavaScript function returning render function
 * and saves it as a script file so that the browser can access to
 * generate VDOM during Vue mounting and conduct patching.
 *
 * This is to avoid the overhead of compiling the page into Vue application
 * on the client's browser (alleviates FOUC). It is also the pre-requisite to enable SSR.
 * See https://github.com/vuejs/core/blob/main/packages/compiler-core/src/options.ts for
 * compilerOptionTypes
 *
 * @param content Page content to be compiled into Vue app
 */
function compileVuePageCreateAndReturnScript(content, pageConfig, pageAsset) {
    return __awaiter(this, void 0, void 0, function* () {
        const customElementTags = getCustomElementTags();
        const compilerOptions = {
            runtimeModuleName: 'vue',
            runtimeGlobalName: 'Vue',
            mode: 'function',
            whitespace: 'preserve',
            isCustomElement: tag => customElementTags.has(tag),
        };
        const templateOptions = {
            source: content,
            filename: pageConfig.sourcePath,
            id: pageConfig.sourcePath,
            compilerOptions,
        };
        const compiled = (0, compiler_sfc_1.compileTemplate)(templateOptions);
        const outputContent = compiled.code;
        const scriptContent = `
    const renderFn = new Function(${JSON.stringify(outputContent)});
    var render = renderFn();
  `;
        // Get script file name
        const pageHtmlFileName = path_1.default.basename(pageConfig.resultPath, '.html');
        const scriptFileName = `${pageHtmlFileName}.page-vue-render.js`;
        /*
         * Add the script file path for this page's render function to the page's assets (to populate page.njk).
         * The script file path is the same as the page's file path.
         */
        pageAsset.pageVueRenderJs = scriptFileName;
        // Get script's absolute file path to output script file
        const dirName = path_1.default.dirname(pageConfig.resultPath);
        const filePath = path_1.default.join(dirName, scriptFileName);
        yield fs_extra_1.default.outputFile(filePath, scriptContent);
        return outputContent;
    });
}
/**
 * Referenced from stackOverflow:
 * https://stackoverflow.com/questions/17581830/load-node-js-module-from-string-in-memory
 *
 * Credits to Dominic
 */
function requireFromString(src, filename) {
    const m = new module.constructor();
    m.paths = module.paths; // without this, we won't be able to require Vue in the string module
    m._compile(src, filename);
    return m.exports;
}
/**
 * Renders Vue page app into html string (Vue SSR).
 * This function will install the MarkBindVue plugin and render the built Vue page content into html string.
 */
function renderVuePage(renderFn) {
    return __awaiter(this, void 0, void 0, function* () {
        const { MarkBindVue, appFactory } = bundle;
        const { plugin } = MarkBindVue;
        // Pass in Vue which is expected to be available globally
        // eslint-disable-next-line @typescript-eslint/no-implied-eval
        const render = new Function('Vue', renderFn)(Vue);
        const app = (0, vue_1.createSSRApp)(Object.assign({ render }, appFactory()));
        app.use(plugin);
        app.config.compilerOptions.whitespace = 'preserve';
        const html = yield (0, server_renderer_1.renderToString)(app);
        const wrappedHtml = `<div id="app">${html}</div>`;
        return wrappedHtml;
    });
}
// hold the mapping of sourcePath to latest built pages (for hot-reload dev purposes)
const pageEntries = {};
function savePageRenderFnForHotReload(page, pageNav, renderFn) {
    const pageEntry = {
        page,
        pageNav,
        renderFn,
    };
    pageEntries[page.pageConfig.sourcePath] = pageEntry;
}
/**
 * Retrieves the latest updated MarkBindVue bundle from webpack compiler watcher,
 * re-render all the built pages, and output the page html files.
 * This function will only be used in development mode (for MarkBindVue bundle hot-reloading purposes).
 */
function updateMarkBindVueBundle(newBundle) {
    return __awaiter(this, void 0, void 0, function* () {
        logger.info(`Changes detected in MarkBind Vue Source Files:
Bundle is regenerated by webpack and built pages are re-rendered with the latest bundle.`);
        // reassign the latest updated MarkBindVue bundle
        bundle = requireFromString(newBundle, '');
        Object.values(pageEntries).forEach((pageEntry) => __awaiter(this, void 0, void 0, function* () {
            const { page, renderFn } = pageEntry;
            const renderedVuePageContent = yield renderVuePage(renderFn);
            page.writeOutputFile(renderedVuePageContent);
        }));
    });
}
exports.pageVueServerRenderer = {
    compileVuePageCreateAndReturnScript,
    renderVuePage,
    updateMarkBindVueBundle,
    savePageRenderFnForHotReload,
};
//# sourceMappingURL=PageVueServerRenderer.js.map