import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import remarkGfm from "remark-gfm";
import rehypeParse from "rehype-parse";
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
import rehypeStringify from "rehype-stringify";
import rehypeRaw from "rehype-raw";
import { visit } from "unist-util-visit";
import { sanitizeStyle } from "./sanitize-style";
import { createLazyLoadImagesPlugin } from "./image-markdown-plugin";
import { createLinksPlugin } from "./link-markdown-plugin";
import { createRemoveEmptyParagraphsPlugin } from "./remove-empty-paragraphs-plugin";
/**
 * Takes a string as input and returns a new string
 * where the text has been converted to HTML.
 *
 * If the text is formatted with .md markdown, it will
 * be transformed to HTML.
 *
 * If the text already is in HTML it will be sanitized and
 * "dangerous" tags such as <script> will be removed.
 *
 * @param text - The string to convert.
 * @param options - Options for the conversions.
 * @returns The resulting HTML.
 */
export async function markdownToHTML(text, options) {
    var _a;
    if (options === null || options === void 0 ? void 0 : options.forceHardLineBreaks) {
        text = text.replaceAll(/(?<!\\)([\n\r])/g, '  $1');
    }
    const file = await unified()
        .use(remarkParse)
        .use(remarkGfm)
        .use(remarkRehype, { allowDangerousHtml: true })
        .use(rehypeRaw)
        .use(createLinksPlugin())
        .use(rehypeSanitize, Object.assign({}, getWhiteList((_a = options === null || options === void 0 ? void 0 : options.whitelist) !== null && _a !== void 0 ? _a : [])))
        .use(() => {
        return (tree) => {
            // Run the sanitizeStyle function on all elements, to sanitize
            // the value of the `style` attribute, if there is one.
            visit(tree, 'element', sanitizeStyle);
        };
    })
        .use(createRemoveEmptyParagraphsPlugin(options === null || options === void 0 ? void 0 : options.removeEmptyParagraphs))
        .use(createLazyLoadImagesPlugin(options === null || options === void 0 ? void 0 : options.lazyLoadImages))
        .use(rehypeStringify)
        .process(text);
    return file.toString();
}
/**
 * Sanitizes a given HTML string by removing dangerous tags and attributes.
 *
 * @param html - The string containing HTML to sanitize.
 * @param whitelist - Optional whitelist of custom components.
 * @returns The sanitized HTML string.
 */
export async function sanitizeHTML(html, whitelist) {
    const file = await unified()
        .use(rehypeParse)
        .use(rehypeSanitize, Object.assign({}, getWhiteList(whitelist !== null && whitelist !== void 0 ? whitelist : [])))
        .use(() => {
        return (tree) => {
            // Run the sanitizeStyle function on all elements, to sanitize
            // the value of the `style` attribute, if there is one.
            visit(tree, 'element', sanitizeStyle);
        };
    })
        .use(rehypeStringify)
        .process(html);
    return file.toString();
}
function getWhiteList(allowedComponents) {
    var _a, _b, _c, _d;
    const defaultSchemaClone = [...((_a = defaultSchema.attributes['*']) !== null && _a !== void 0 ? _a : [])];
    const asteriskAttributeWhitelist = defaultSchemaClone.filter((attr) => {
        return attr !== 'height';
    });
    asteriskAttributeWhitelist.push('style');
    const whitelist = Object.assign(Object.assign({}, defaultSchema), { strip: [...((_b = defaultSchema.strip) !== null && _b !== void 0 ? _b : []), 'style'], tagNames: [
            ...(defaultSchema.tagNames || []),
            ...allowedComponents.map((component) => component.tagName),
        ], attributes: Object.assign(Object.assign({}, defaultSchema.attributes), { p: [
                ...((_c = defaultSchema.attributes.p) !== null && _c !== void 0 ? _c : []),
                ['className', 'MsoNormal'],
            ], a: [...((_d = defaultSchema.attributes.a) !== null && _d !== void 0 ? _d : []), 'referrerpolicy'], '*': asteriskAttributeWhitelist }) });
    for (const component of allowedComponents) {
        whitelist.attributes[component.tagName] = component.attributes;
    }
    return whitelist;
}
