"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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeProcessor = void 0;
const path_1 = __importDefault(require("path"));
const cheerio_1 = __importDefault(require("cheerio"));
const htmlparser2_1 = __importDefault(require("htmlparser2"));
const bluebird_1 = __importDefault(require("bluebird"));
const isArray_1 = __importDefault(require("lodash/isArray"));
const has_1 = __importDefault(require("lodash/has"));
const constants_1 = require("../constants");
const Context_1 = require("./Context");
const elements_1 = require("./elements");
const fsUtil_1 = require("../utils/fsUtil");
const logger = __importStar(require("../utils/logger"));
const linkProcessor = __importStar(require("./linkProcessor"));
const warnings_1 = require("./warnings");
const vueSlotSyntaxProcessor_1 = require("./vueSlotSyntaxProcessor");
const MdAttributeRenderer_1 = require("./MdAttributeRenderer");
const MarkdownProcessor_1 = require("./MarkdownProcessor");
const scriptAndStyleTagProcessor_1 = require("./scriptAndStyleTagProcessor");
const includePanelProcessor_1 = require("./includePanelProcessor");
const siteAndPageNavProcessor_1 = require("./siteAndPageNavProcessor");
const codeblockProcessor_1 = require("./codeblockProcessor");
const headerProcessor_1 = require("./headerProcessor");
const FootnoteProcessor_1 = require("./FootnoteProcessor");
const CustomListIconProcessor_1 = require("./CustomListIconProcessor");
const fm = require('fastmatter');
const _ = {
    isArray: isArray_1.default,
    has: has_1.default,
};
// Load our htmlparser2 patch for supporting "special" tags
require('../patches/htmlparser2');
const FRONTMATTER_FENCE = '---';
cheerio_1.default.prototype.options.decodeEntities = false; // Don't escape HTML entities
class NodeProcessor {
    constructor(config, pageSources, variableProcessor, pluginManager, siteLinkManager, userScriptsAndStyles, docId = '') {
        this.config = config;
        this.pageSources = pageSources;
        this.variableProcessor = variableProcessor;
        this.pluginManager = pluginManager;
        this.siteLinkManager = siteLinkManager;
        this.userScriptsAndStyles = userScriptsAndStyles;
        this.frontmatter = {};
        this.headTop = [];
        this.headBottom = [];
        this.scriptBottom = [];
        this.footnoteProcessor = new FootnoteProcessor_1.FootnoteProcessor();
        this.pageNavProcessor = new siteAndPageNavProcessor_1.PageNavProcessor();
        this.processedModals = {};
        this.markdownProcessor = new MarkdownProcessor_1.MarkdownProcessor(docId);
        this.mdAttributeRenderer = new MdAttributeRenderer_1.MdAttributeRenderer(this.markdownProcessor);
    }
    /*
     * Private utility functions
     */
    static _trimNodes(nodeOrText) {
        if (NodeProcessor._isText(nodeOrText))
            return;
        const node = nodeOrText;
        if (node.name === 'pre' || node.name === 'code') {
            return;
        }
        if (node.children) {
            for (let n = 0; n < node.children.length; n += 1) {
                const child = node.children[n];
                if (child.type === 'comment'
                    || (child.type === 'text' && n === node.children.length - 1 && !/\S/.test(child.data))) {
                    node.children.splice(n, 1);
                    n -= 1;
                }
                else if (child.type === 'tag') {
                    NodeProcessor._trimNodes(child);
                }
            }
        }
    }
    static _isText(node) {
        return node.type === 'text' || node.type === 'comment';
    }
    /*
     * Frontmatter collection
     */
    _processFrontmatter(node, context) {
        let currentFrontmatter = {};
        const frontmatter = (0, cheerio_1.default)(node);
        if (!context.processingOptions.omitFrontmatter && frontmatter.text().trim()) {
            // Retrieves the frontmatter from either the first frontmatter element
            // or from a frontmatter element that includes from another file
            // The latter case will result in the data being wrapped in a div
            const frontmatterIncludeDiv = frontmatter.find('div');
            const frontmatterData = frontmatterIncludeDiv.length
                ? frontmatterIncludeDiv[0].children[0].data
                : frontmatter[0].children[0].data;
            const frontmatterWrapped = `${FRONTMATTER_FENCE}\n${frontmatterData}\n${FRONTMATTER_FENCE}`;
            currentFrontmatter = fm(frontmatterWrapped).attributes;
        }
        this.frontmatter = Object.assign(Object.assign({}, this.frontmatter), currentFrontmatter);
        (0, cheerio_1.default)(node).remove();
    }
    /*
     * Layout element collection
     */
    static collectLayoutEl(node) {
        const $ = (0, cheerio_1.default)(node);
        const html = $.html();
        $.remove();
        return html;
    }
    /**
     * Removes the node if modal id already exists, processes node otherwise
     */
    processModal(node) {
        if (this.processedModals[node.attribs.id]) {
            (0, cheerio_1.default)(node).remove();
        }
        else {
            this.processedModals[node.attribs.id] = true;
            this.mdAttributeRenderer.processModalAttributes(node);
        }
    }
    /*
     * API
     */
    processNode(nodeOrText, context) {
        try {
            if (NodeProcessor._isText(nodeOrText))
                return context;
            const node = nodeOrText;
            (0, vueSlotSyntaxProcessor_1.transformOldSlotSyntax)(node);
            (0, vueSlotSyntaxProcessor_1.shiftSlotNodeDeeper)(node);
            // log warnings for conflicting attributes
            if (_.has(warnings_1.warnConflictingAtributesMap, node.name)) {
                warnings_1.warnConflictingAtributesMap[node.name](node);
            }
            switch (node.name) {
                case 'frontmatter':
                    this._processFrontmatter(node, context);
                    break;
                case 'body':
                    // eslint-disable-next-line no-console
                    console.warn(`<body> tag found in ${node.attribs[constants_1.ATTRIB_CWF]}. This may cause formatting errors.`);
                    break;
                case 'include':
                    this.markdownProcessor.docId += 1; // used in markdown-it-footnotes
                    return (0, includePanelProcessor_1.processInclude)(node, context, this.pageSources, this.variableProcessor, (text) => this.markdownProcessor.renderMd(text), (text) => this.markdownProcessor.renderMdInline(text), this.config);
                case 'panel':
                    this.mdAttributeRenderer.processPanelAttributes(node);
                    return (0, includePanelProcessor_1.processPanelSrc)(node, context, this.pageSources, this.config);
                case 'question':
                    this.mdAttributeRenderer.processQuestion(node);
                    break;
                case 'ul':
                    (0, CustomListIconProcessor_1.processUlNode)(node);
                    break;
                case 'q-option':
                    this.mdAttributeRenderer.processQOption(node);
                    break;
                case 'quiz':
                    this.mdAttributeRenderer.processQuiz(node);
                    break;
                case 'popover':
                    this.mdAttributeRenderer.processPopoverAttributes(node);
                    return (0, includePanelProcessor_1.processPopoverSrc)(node, context, this.pageSources, this.variableProcessor, (text) => this.markdownProcessor.renderMd(text), this.config);
                case 'tooltip':
                    this.mdAttributeRenderer.processTooltip(node);
                    break;
                case 'modal':
                    this.processModal(node);
                    break;
                case 'tab':
                case 'tab-group':
                    this.mdAttributeRenderer.processTabAttributes(node);
                    break;
                case 'box':
                    this.mdAttributeRenderer.processBoxAttributes(node);
                    break;
                case 'dropdown':
                    this.mdAttributeRenderer.processDropdownAttributes(node);
                    break;
                case 'thumbnail':
                    this.mdAttributeRenderer.processThumbnailAttributes(node);
                    break;
                case 'page-nav':
                    this.pageNavProcessor.renderPageNav(node);
                    break;
                case 'page-nav-print':
                    siteAndPageNavProcessor_1.PageNavProcessor.transformPrintContainer(node);
                    break;
                case 'site-nav':
                    (0, siteAndPageNavProcessor_1.renderSiteNav)(node);
                    break;
                case 'mb-temp-footnotes':
                    this.footnoteProcessor.processMbTempFootnotes(node);
                    break;
                case 'script':
                case 'style':
                    (0, scriptAndStyleTagProcessor_1.processScriptAndStyleTag)(node, this.userScriptsAndStyles);
                    break;
                case 'scroll-top-button':
                    this.mdAttributeRenderer.processScrollTopButtonAttributes(node);
                    break;
                case 'a-point':
                    this.mdAttributeRenderer.processAnnotationPointAttributes(node);
                    break;
                case 'code':
                    (0, codeblockProcessor_1.setCodeLineNumbers)(node, this.config.codeLineNumbers);
                // fall through
                case 'annotation': // Annotations are added automatically by KaTeX when rendering math formulae.
                case 'eq': // markdown-it-texmath html tag
                case 'eqn': // markdown-it-texmath html tag
                case 'thumb': // image
                    /*
                     * These are not components from MarkBind Vue components.
                     * We have to add 'v-pre' to let Vue know to ignore this tag and not compile it.
                     *
                     * Although there won't be warnings if we use production Vue, it is still good to add this.
                     */
                    if (!_.has(node.attribs, 'v-pre')) {
                        node.attribs['v-pre'] = '';
                    }
                    break;
                default:
                    break;
            }
        }
        catch (error) {
            logger.error(error);
        }
        return context;
    }
    postProcessNode(nodeOrText) {
        if (NodeProcessor._isText(nodeOrText))
            return;
        const node = nodeOrText;
        try {
            switch (node.name) {
                case 'pre':
                    (0, codeblockProcessor_1.highlightCodeBlock)(node);
                    break;
                case 'panel':
                    (0, headerProcessor_1.assignPanelId)(node);
                    break;
                case 'head-top': {
                    const collected = NodeProcessor.collectLayoutEl(node);
                    if (collected) {
                        this.headTop.push(collected);
                    }
                    break;
                }
                case 'head-bottom': {
                    const collected = NodeProcessor.collectLayoutEl(node);
                    if (collected) {
                        this.headBottom.push(collected);
                    }
                    break;
                }
                case 'script-bottom': {
                    const collected = NodeProcessor.collectLayoutEl(node);
                    if (collected) {
                        this.scriptBottom.push(collected);
                    }
                    break;
                }
                default:
                    break;
            }
        }
        catch (error) {
            logger.error(error);
        }
        if (node.attribs) {
            delete node.attribs[constants_1.ATTRIB_CWF];
        }
    }
    traverse(dom, context) {
        if (NodeProcessor._isText(dom)) {
            return dom;
        }
        const node = dom;
        node.name = node.name.toLowerCase();
        if (linkProcessor.hasTagLink(node)) {
            linkProcessor.convertRelativeLinks(node, context.cwf, this.config.rootPath, this.config.baseUrl);
            linkProcessor.convertMdExtToHtmlExt(node);
            if (this.config.intrasiteLinkValidation.enabled) {
                this.siteLinkManager.collectIntraLinkToValidate(node, context.cwf);
            }
            linkProcessor.collectSource(node, this.config.rootPath, this.config.baseUrl, this.pageSources);
        }
        switch (node.name) {
            case 'md':
                node.name = 'span';
                (0, cheerio_1.default)(node).html(this.markdownProcessor.renderMdInline(cheerio_1.default.html(node.children)));
                break;
            case 'markdown':
                node.name = 'div';
                (0, cheerio_1.default)(node).html(this.markdownProcessor.renderMd(cheerio_1.default.html(node.children)));
                break;
            default:
                break;
        }
        // eslint-disable-next-line no-param-reassign
        context = this.processNode(node, context);
        this.pluginManager.processNode(node);
        if (node.children) {
            node.children.forEach((child) => {
                this.traverse(child, context);
            });
        }
        this.postProcessNode(node);
        (0, siteAndPageNavProcessor_1.addSitePageNavPortal)(node);
        const isHeadingTag = (/^h[1-6]$/).test(node.name);
        if (isHeadingTag && !node.attribs.id) {
            (0, headerProcessor_1.setHeadingId)(node, this.config);
        }
        this.pluginManager.postProcessNode(node);
        return node;
    }
    process(file, content, cwf = file, extraVariables = {}) {
        const context = new Context_1.Context(cwf, [], extraVariables, {});
        return new bluebird_1.default((resolve, reject) => {
            const handler = new htmlparser2_1.default.DomHandler((error, dom) => {
                if (error) {
                    reject(error);
                    return;
                }
                const mainHtmlNodes = dom.map((d) => {
                    let processed;
                    try {
                        processed = this.traverse(d, context);
                    }
                    catch (err) {
                        err.message += `\nError while rendering '${file}'`;
                        logger.error(err);
                        processed = (0, elements_1.createErrorNode)(d, err);
                    }
                    return processed;
                });
                mainHtmlNodes.forEach(d => NodeProcessor._trimNodes(d));
                const footnotesHtml = this.footnoteProcessor.combineFootnotes(node => this.processNode(node, new Context_1.Context(cwf, [], extraVariables, {})));
                const mainHtml = (0, cheerio_1.default)(mainHtmlNodes).html();
                const mainHtmlWithUniqPageNavUuid = this.pageNavProcessor.finalizePageNavUuid(mainHtml, mainHtmlNodes, footnotesHtml);
                resolve(mainHtmlWithUniqPageNavUuid + footnotesHtml);
            });
            const parser = new htmlparser2_1.default.Parser(handler);
            const fileExt = path_1.default.extname(file);
            if ((0, fsUtil_1.isMarkdownFileExt)(fileExt)) {
                const renderedContent = this.markdownProcessor.renderMd(content);
                // Wrap with <root> as $.remove() does not work on top level nodes
                parser.parseComplete(`<root>${renderedContent}</root>`);
            }
            else if (fileExt === '.html') {
                parser.parseComplete(`<root>${content}</root>`);
            }
            else {
                const error = new Error(`Unsupported File Extension: '${fileExt}'`);
                reject(error);
            }
        });
    }
}
exports.NodeProcessor = NodeProcessor;
//# sourceMappingURL=NodeProcessor.js.map