"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const dom = require("./dom.js");
const dompurify = require("./dompurify/dompurify.js");
const event = require("./event.js");
const formattedTextRenderer = require("./formattedTextRenderer.js");
const mouseEvent = require("./mouseEvent.js");
const iconLabels$1 = require("./ui/iconLabel/iconLabels.js");
const async = require("../common/async.js");
const cancellation = require("../common/cancellation.js");
const errors = require("../common/errors.js");
const event$1 = require("../common/event.js");
const htmlContent = require("../common/htmlContent.js");
const iconLabels = require("../common/iconLabels.js");
const idGenerator = require("../common/idGenerator.js");
const lifecycle = require("../common/lifecycle.js");
const marked = require("../common/marked/marked.js");
const marshalling = require("../common/marshalling.js");
const network = require("../common/network.js");
const objects = require("../common/objects.js");
const resources = require("../common/resources.js");
const strings = require("../common/strings.js");
const uri = require("../common/uri.js");
function renderMarkdown(markdown, options = {}, markedOptions = {}) {
  var _a;
  const disposables = new lifecycle.DisposableStore();
  let isDisposed = false;
  const cts = disposables.add(new cancellation.CancellationTokenSource());
  const element = formattedTextRenderer.createElement(options);
  const _uriMassage = function(part) {
    let data;
    try {
      data = marshalling.parse(decodeURIComponent(part));
    } catch (e) {
    }
    if (!data) {
      return part;
    }
    data = objects.cloneAndChange(data, (value2) => {
      if (markdown.uris && markdown.uris[value2]) {
        return uri.URI.revive(markdown.uris[value2]);
      } else {
        return void 0;
      }
    });
    return encodeURIComponent(JSON.stringify(data));
  };
  const _href = function(href, isDomUri) {
    const data = markdown.uris && markdown.uris[href];
    let uri$1 = uri.URI.revive(data);
    if (isDomUri) {
      if (href.startsWith(network.Schemas.data + ":")) {
        return href;
      }
      if (!uri$1) {
        uri$1 = uri.URI.parse(href);
      }
      return network.FileAccess.asBrowserUri(uri$1).toString(true);
    }
    if (!uri$1) {
      return href;
    }
    if (uri.URI.parse(href).toString() === uri$1.toString()) {
      return href;
    }
    if (uri$1.query) {
      uri$1 = uri$1.with({ query: _uriMassage(uri$1.query) });
    }
    return uri$1.toString();
  };
  let signalInnerHTML;
  const withInnerHTML = new Promise((c) => signalInnerHTML = c);
  const renderer = new marked.marked.Renderer();
  renderer.image = (href, title, text) => {
    let dimensions = [];
    let attributes = [];
    if (href) {
      ({ href, dimensions } = htmlContent.parseHrefAndDimensions(href));
      attributes.push(`src="${href}"`);
    }
    if (text) {
      attributes.push(`alt="${text}"`);
    }
    if (title) {
      attributes.push(`title="${title}"`);
    }
    if (dimensions.length) {
      attributes = attributes.concat(dimensions);
    }
    return "<img " + attributes.join(" ") + ">";
  };
  renderer.link = (href, title, text) => {
    if (typeof href !== "string") {
      return "";
    }
    if (href === text) {
      text = htmlContent.removeMarkdownEscapes(text);
    }
    href = _href(href, false);
    if (markdown.baseUri) {
      href = resolveWithBaseUri(uri.URI.from(markdown.baseUri), href);
    }
    title = typeof title === "string" ? htmlContent.removeMarkdownEscapes(title) : "";
    href = htmlContent.removeMarkdownEscapes(href);
    if (!href || /^data:|javascript:/i.test(href) || /^command:/i.test(href) && !markdown.isTrusted || /^command:(\/\/\/)?_workbench\.downloadResource/i.test(href)) {
      return text;
    } else {
      href = href.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
      return `<a data-href="${href}" title="${title || href}">${text}</a>`;
    }
  };
  renderer.paragraph = (text) => {
    return `<p>${text}</p>`;
  };
  if (options.codeBlockRenderer) {
    renderer.code = (code, lang) => {
      const value2 = options.codeBlockRenderer(lang !== null && lang !== void 0 ? lang : "", code);
      const id = idGenerator.defaultGenerator.nextId();
      async.raceCancellation(Promise.all([value2, withInnerHTML]), cts.token).then((values) => {
        var _a2;
        if (!isDisposed && values) {
          const span = element.querySelector(`div[data-code="${id}"]`);
          if (span) {
            dom.reset(span, values[0]);
          }
          (_a2 = options.asyncRenderCallback) === null || _a2 === void 0 ? void 0 : _a2.call(options);
        }
      }).catch(() => {
      });
      return `<div class="code" data-code="${id}">${strings.escape(code)}</div>`;
    };
  }
  if (options.actionHandler) {
    const onClick = options.actionHandler.disposables.add(new event.DomEmitter(element, "click"));
    const onAuxClick = options.actionHandler.disposables.add(new event.DomEmitter(element, "auxclick"));
    options.actionHandler.disposables.add(event$1.Event.any(onClick.event, onAuxClick.event)((e) => {
      const mouseEvent$1 = new mouseEvent.StandardMouseEvent(e);
      if (!mouseEvent$1.leftButton && !mouseEvent$1.middleButton) {
        return;
      }
      let target = mouseEvent$1.target;
      if (target.tagName !== "A") {
        target = target.parentElement;
        if (!target || target.tagName !== "A") {
          return;
        }
      }
      try {
        let href = target.dataset["href"];
        if (href) {
          if (markdown.baseUri) {
            href = resolveWithBaseUri(uri.URI.from(markdown.baseUri), href);
          }
          options.actionHandler.callback(href, mouseEvent$1);
        }
      } catch (err) {
        errors.onUnexpectedError(err);
      } finally {
        mouseEvent$1.preventDefault();
      }
    }));
  }
  if (!markdown.supportHtml) {
    markedOptions.sanitizer = (html) => {
      const match = markdown.isTrusted ? html.match(/^(<span[^>]+>)|(<\/\s*span>)$/) : void 0;
      return match ? html : "";
    };
    markedOptions.sanitize = true;
    markedOptions.silent = true;
  }
  markedOptions.renderer = renderer;
  let value = (_a = markdown.value) !== null && _a !== void 0 ? _a : "";
  if (value.length > 1e5) {
    value = `${value.substr(0, 1e5)}…`;
  }
  if (markdown.supportThemeIcons) {
    value = iconLabels.markdownEscapeEscapedIcons(value);
  }
  let renderedMarkdown = marked.marked.parse(value, markedOptions);
  if (markdown.supportThemeIcons) {
    const elements = iconLabels$1.renderLabelWithIcons(renderedMarkdown);
    renderedMarkdown = elements.map((e) => typeof e === "string" ? e : e.outerHTML).join("");
  }
  const htmlParser = new DOMParser();
  const markdownHtmlDoc = htmlParser.parseFromString(sanitizeRenderedMarkdown(markdown, renderedMarkdown), "text/html");
  markdownHtmlDoc.body.querySelectorAll("img").forEach((img) => {
    const src = img.getAttribute("src");
    if (src) {
      let href = src;
      try {
        if (markdown.baseUri) {
          href = resolveWithBaseUri(uri.URI.from(markdown.baseUri), href);
        }
      } catch (err) {
      }
      img.src = _href(href, true);
    }
  });
  element.innerHTML = sanitizeRenderedMarkdown(markdown, markdownHtmlDoc.body.innerHTML);
  signalInnerHTML();
  if (options.asyncRenderCallback) {
    for (const img of element.getElementsByTagName("img")) {
      const listener = disposables.add(dom.addDisposableListener(img, "load", () => {
        listener.dispose();
        options.asyncRenderCallback();
      }));
    }
  }
  return {
    element,
    dispose: () => {
      isDisposed = true;
      cts.cancel();
      disposables.dispose();
    }
  };
}
function resolveWithBaseUri(baseUri, href) {
  const hasScheme = /^\w[\w\d+.-]*:/.test(href);
  if (hasScheme) {
    return href;
  }
  if (baseUri.path.endsWith("/")) {
    return resources.resolvePath(baseUri, href).toString();
  } else {
    return resources.resolvePath(resources.dirname(baseUri), href).toString();
  }
}
function sanitizeRenderedMarkdown(options, renderedMarkdown) {
  const { config, allowedSchemes } = getSanitizerOptions(options);
  dompurify.addHook("uponSanitizeAttribute", (element, e) => {
    if (e.attrName === "style" || e.attrName === "class") {
      if (element.tagName === "SPAN") {
        if (e.attrName === "style") {
          e.keepAttr = /^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/.test(e.attrValue);
          return;
        } else if (e.attrName === "class") {
          e.keepAttr = /^codicon codicon-[a-z\-]+( codicon-modifier-[a-z\-]+)?$/.test(e.attrValue);
          return;
        }
      }
      e.keepAttr = false;
      return;
    }
  });
  const anchor = document.createElement("a");
  dompurify.addHook("afterSanitizeAttributes", (node) => {
    for (const attr of ["href", "src"]) {
      if (node.hasAttribute(attr)) {
        anchor.href = node.getAttribute(attr);
        if (!allowedSchemes.includes(anchor.protocol.replace(/:$/, ""))) {
          node.removeAttribute(attr);
        }
      }
    }
  });
  try {
    return dompurify.sanitize(renderedMarkdown, Object.assign(Object.assign({}, config), { RETURN_TRUSTED_TYPE: true }));
  } finally {
    dompurify.removeHook("uponSanitizeAttribute");
    dompurify.removeHook("afterSanitizeAttributes");
  }
}
function getSanitizerOptions(options) {
  const allowedSchemes = [
    network.Schemas.http,
    network.Schemas.https,
    network.Schemas.mailto,
    network.Schemas.data,
    network.Schemas.file,
    network.Schemas.vscodeFileResource,
    network.Schemas.vscodeRemote,
    network.Schemas.vscodeRemoteResource
  ];
  if (options.isTrusted) {
    allowedSchemes.push(network.Schemas.command);
  }
  return {
    config: {
      // allowedTags should included everything that markdown renders to.
      // Since we have our own sanitize function for marked, it's possible we missed some tag so let dompurify make sure.
      // HTML tags that can result from markdown are from reading https://spec.commonmark.org/0.29/
      // HTML table tags that can result from markdown are from https://github.github.com/gfm/#tables-extension-
      ALLOWED_TAGS: ["ul", "li", "p", "b", "i", "code", "blockquote", "ol", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "em", "pre", "table", "thead", "tbody", "tr", "th", "td", "div", "del", "a", "strong", "br", "img", "span"],
      ALLOWED_ATTR: ["href", "data-href", "target", "title", "src", "alt", "class", "style", "data-code", "width", "height", "align"],
      ALLOW_UNKNOWN_PROTOCOLS: true
    },
    allowedSchemes
  };
}
exports.renderMarkdown = renderMarkdown;
