"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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.processPanelSrc = processPanelSrc;
exports.processInclude = processInclude;
exports.processPopoverSrc = processPopoverSrc;
const cheerio_1 = __importDefault(require("cheerio"));
const path_1 = __importDefault(require("path"));
const url_parse_1 = __importDefault(require("url-parse"));
const has_1 = __importDefault(require("lodash/has"));
const isEmpty_1 = __importDefault(require("lodash/isEmpty"));
const elements_1 = require("./elements");
const CyclicReferenceError_1 = __importDefault(require("../errors/CyclicReferenceError"));
const fsUtil = __importStar(require("../utils/fsUtil"));
const logger = __importStar(require("../utils/logger"));
const urlUtil = __importStar(require("../utils/urlUtil"));
require('../patches/htmlparser2');
const _ = { has: has_1.default, isEmpty: isEmpty_1.default };
/*
 * Common panel and include helper functions
 */
/**
 * Returns a boolean representing whether the file specified exists.
 */
function _checkAndWarnFileExists(element, context, actualFilePath, pageSources, isOptional = false) {
    if (!fsUtil.fileExists(actualFilePath)) {
        if (isOptional) {
            return false;
        }
        pageSources.missingIncludeSrc.push({
            from: context.cwf,
            to: actualFilePath,
        });
        const error = new Error(`No such file: ${actualFilePath}\nMissing reference in ${context.cwf}`);
        logger.error(error.message);
        (0, elements_1.createErrorNode)(element, error);
        return false;
    }
    return true;
}
function _getBoilerplateFilePath(element, filePath, config) {
    const isBoilerplate = _.has(element.attribs, 'boilerplate');
    if (isBoilerplate) {
        element.attribs.boilerplate = element.attribs.boilerplate || path_1.default.basename(filePath);
        return urlUtil.calculateBoilerplateFilePath(element.attribs.boilerplate, filePath, config);
    }
    return undefined;
}
/**
 * Retrieves several flags and file paths from the src attribute specified in the element.
 */
function _getSrcFlagsAndFilePaths(element, config) {
    const isUrl = urlUtil.isUrl(element.attribs.src);
    // We do this even if the src is not a url to get the hash, if any
    const includeSrc = (0, url_parse_1.default)(element.attribs.src);
    let filePath;
    if (isUrl) {
        filePath = element.attribs.src;
    }
    else {
        const includePath = decodeURIComponent(includeSrc.pathname).replace(/\\/g, path_1.default.sep);
        /*
         If the src starts with the baseUrl (or simply '/' if there is no baseUrl specified),
         get the relative path from the rootPath first,
         then use it to resolve the absolute path of the referenced file on the filesystem.
         */
        const relativePathToFile = path_1.default.posix.relative(`${config.baseUrl}/`, includePath);
        filePath = path_1.default.resolve(config.rootPath, relativePathToFile);
    }
    const boilerplateFilePath = _getBoilerplateFilePath(element, filePath, config);
    const actualFilePath = boilerplateFilePath || filePath;
    return {
        isUrl,
        hash: includeSrc.hash,
        filePath,
        actualFilePath,
    };
}
/*
 * Panels
 */
/**
 * PreProcesses panels with a src attribute specified.
 * Replaces the panel with an error node if the src is invalid.
 * Otherwise, sets the fragment attribute of the panel as parsed from the src,
 * and adds the appropriate include.
 */
function processPanelSrc(node, context, pageSources, config) {
    const hasSrc = _.has(node.attribs, 'src');
    if (!hasSrc) {
        return context;
    }
    const { isUrl, hash, filePath, actualFilePath,
    // We can typecast here as we have checked for src above.
     } = _getSrcFlagsAndFilePaths(node, config);
    const fileExists = _checkAndWarnFileExists(node, context, actualFilePath, pageSources);
    if (!fileExists) {
        return context;
    }
    if (!isUrl && hash) {
        node.attribs.fragment = hash.substring(1);
    }
    const { fragment } = node.attribs;
    const relativePath = fsUtil.setExtension(path_1.default.relative(config.rootPath, filePath), '._include_.html');
    const fullResourcePath = path_1.default.posix.join(`${config.baseUrl}/`, fsUtil.ensurePosix(relativePath));
    node.attribs.src = fragment ? `${fullResourcePath}#${fragment}` : fullResourcePath;
    delete node.attribs.boilerplate;
    pageSources.dynamicIncludeSrc.push({
        from: context.cwf,
        to: actualFilePath,
        asIfTo: filePath,
    });
    return context;
}
/*
 * Includes
 */
function _deleteIncludeAttributes(node) {
    // Delete variable attributes in include tags as they are no longer needed
    // e.g. '<include var-title="..." var-xx="..." />'
    Object.keys(node.attribs).forEach((attribute) => {
        if (attribute.startsWith('var-')) {
            delete node.attribs[attribute];
        }
    });
    delete node.attribs.boilerplate;
    delete node.attribs.src;
    delete node.attribs.inline;
    delete node.attribs.trim;
    delete node.attribs.optional;
    delete node.attribs.omitFrontmatter;
}
function buildGetNextFootnodeNumber() {
    let footnoteNumber = 0;
    function getFootnoteNumber() {
        footnoteNumber += 1;
        return footnoteNumber;
    }
    return getFootnoteNumber;
}
/**
 * PreProcesses includes.
 * Replaces it with an error node if the specified src is invalid,
 * or an empty node if the src is invalid but optional.
 */
function processInclude(node, context, pageSources, variableProcessor, renderMd, renderMdInline, config, siteLinkManager, getNextFootnodeNumber = buildGetNextFootnodeNumber()) {
    if (_.isEmpty(node.attribs.src)) {
        const error = new Error(`Empty src attribute in include in: ${context.cwf}`);
        logger.error(error.message);
        (0, cheerio_1.default)(node).replaceWith((0, elements_1.createErrorNode)(node, error));
        return context;
    }
    const { isUrl, hash, filePath, actualFilePath,
    // We can typecast here as we have checked for src above.
     } = _getSrcFlagsAndFilePaths(node, config);
    // No need to process url contents
    if (isUrl) {
        _deleteIncludeAttributes(node);
        return context;
    }
    const isOptional = _.has(node.attribs, 'optional');
    const fileExists = _checkAndWarnFileExists(node, context, actualFilePath, pageSources, isOptional);
    if (!fileExists) {
        return context;
    }
    const isInline = _.has(node.attribs, 'inline');
    const isTrim = _.has(node.attribs, 'trim');
    const shouldOmitFrontmatter = _.has(node.attribs, 'omitFrontmatter');
    node.name = isInline ? 'span' : 'div';
    pageSources.staticIncludeSrc.push({
        from: context.cwf,
        to: actualFilePath,
    });
    const { nunjucksProcessed, childContext, } = variableProcessor.renderIncludeFile(actualFilePath, pageSources, node, context, filePath);
    if (nunjucksProcessed.trim() === '') {
        const warning = `Empty file included in ${context.cwf}:\n`
            + `File: ${actualFilePath} is empty or contains only whitespace`;
        logger.warn(warning);
    }
    let actualContent = nunjucksProcessed;
    if (fsUtil.isMarkdownFileExt(path_1.default.extname(actualFilePath))) {
        actualContent = isInline
            ? renderMdInline(actualContent)
            : renderMd(actualContent);
    }
    // Process sources with or without hash, retrieving and appending
    // the appropriate children to a wrapped include element
    if (hash) {
        const $ = cheerio_1.default.load(actualContent);
        const actualContentOrNull = $(hash).html();
        actualContent = actualContentOrNull || '';
        if (actualContent !== '') {
            const hashNode = $(hash);
            const footnodeHrefs = hashNode.find('a[aria-describedby="footnote-label"]')
                .map(function () {
                $(this).text(`[${getNextFootnodeNumber()}]`);
                return $(this).attr('href');
            })
                .get();
            if (footnodeHrefs.length > 0) {
                const tempFootnotes = $('<mb-temp-footnotes></mb-temp-footnotes>');
                footnodeHrefs.forEach((href) => {
                    // substring function called to remove the # from the href
                    const listItem = $('<li></li>').attr('id', href.substring(1))
                        .addClass('footnote-item')
                        .html($(`${href}.footnote-item`).html());
                    tempFootnotes.append(listItem);
                });
                hashNode.append(tempFootnotes);
                actualContent = hashNode.html();
            }
        }
        if (actualContentOrNull === null && !isOptional) {
            const error = new Error(`No such segment '${hash}' in file: ${actualFilePath}\n`
                + `Missing reference in ${context.cwf}`);
            logger.error(error.message);
            actualContent = cheerio_1.default.html((0, elements_1.createErrorNode)(node, error));
        }
        else if (actualContentOrNull === '') {
            const warning = `Empty reference in ${context.cwf}:\n`
                + `Segment '${hash}' in file: ${actualFilePath} is empty`;
            logger.warn(warning);
        }
    }
    if (isTrim) {
        actualContent = actualContent.trim();
    }
    const $includeEl = (0, cheerio_1.default)(node);
    $includeEl.empty();
    $includeEl.append(actualContent);
    if (node.children && node.children.length > 0) {
        childContext.addCwfToCallstack(context.cwf);
        childContext.processingOptions.omitFrontmatter = shouldOmitFrontmatter;
        if (childContext.hasExceededMaxCallstackSize()) {
            const error = new CyclicReferenceError_1.default(childContext.callStack);
            logger.error(error.message);
            (0, cheerio_1.default)(node).replaceWith((0, elements_1.createErrorNode)(node, error));
            return context;
        }
    }
    _deleteIncludeAttributes(node);
    siteLinkManager.maintainHashesForInclude(node, context.cwf);
    return childContext;
}
/**
 * PreProcesses popovers with the src attribute.
 * Replaces it with an error node if the specified src is invalid.
 * Else, appends the content to the node.
 */
function processPopoverSrc(node, context, pageSources, variableProcessor, renderMd, config) {
    if (!_.has(node.attribs, 'src')) {
        return context;
    }
    if (_.isEmpty(node.attribs.src)) {
        const error = new Error(`Empty src attribute in popover in: ${context.cwf}`);
        logger.error(error.message);
        (0, cheerio_1.default)(node).replaceWith((0, elements_1.createErrorNode)(node, error));
        return context;
    }
    const { isUrl, hash, filePath, actualFilePath,
    // We can typecast here as we have checked for src above.
     } = _getSrcFlagsAndFilePaths(node, config);
    // No need to process url contents
    if (isUrl) {
        const error = new Error('URLs are not allowed in the \'src\' attribute');
        logger.error(`${error.message}
      File: ${context.cwf}
      URL provided: ${node.attribs.src}
      
      Please check the \`src\` attribute in the popover element.
      Ensure it doesn't contain a URL (e.g., "http://www.example.com").`);
        (0, cheerio_1.default)(node).replaceWith((0, elements_1.createErrorNode)(node, error));
        return context;
    }
    const fileExists = _checkAndWarnFileExists(node, context, actualFilePath, pageSources);
    if (!fileExists) {
        return context;
    }
    pageSources.staticIncludeSrc.push({
        from: context.cwf,
        to: actualFilePath,
    });
    const { nunjucksProcessed, childContext, } = variableProcessor.renderIncludeFile(actualFilePath, pageSources, node, context, filePath);
    let actualContent = nunjucksProcessed;
    if (fsUtil.isMarkdownFileExt(path_1.default.extname(actualFilePath))) {
        actualContent = renderMd(actualContent);
    }
    // Process sources with or without hash, retrieving and appending
    // the appropriate children to a wrapped include element
    if (hash) {
        const $ = cheerio_1.default.load(actualContent);
        actualContent = $(hash).html() || '';
        if (actualContent === '') {
            const error = new Error(`No such segment '${hash}' in file: ${actualFilePath}\n`
                + `Missing reference in ${context.cwf}`);
            logger.error(error.message);
            (0, cheerio_1.default)(node).replaceWith((0, elements_1.createErrorNode)(node, error));
            return context;
        }
    }
    actualContent = actualContent.trim();
    if (node.children.length > 0) {
        childContext.addCwfToCallstack(context.cwf);
        if (childContext.hasExceededMaxCallstackSize()) {
            const error = new CyclicReferenceError_1.default(childContext.callStack);
            logger.error(error.message);
            (0, cheerio_1.default)(node).replaceWith((0, elements_1.createErrorNode)(node, error));
            return context;
        }
    }
    const attributeSlotElement = (0, elements_1.createSlotTemplateNode)('content', actualContent);
    node.children = node.children ? attributeSlotElement.concat(node.children) : attributeSlotElement;
    delete node.attribs.src;
    return childContext;
}
//# sourceMappingURL=includePanelProcessor.js.map