"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
exports.escapeHtml = escapeHtml;
exports.other = void 0;
var _marked = require("marked");
const other = exports.other = {
  escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,
  escapeTest: /[&<>"']/,
  notSpaceStart: /^\S*/,
  endingNewline: /\n$/,
  escapeReplace: /[&<>"']/g,
  escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,
  completeFencedCode: /^ {0,3}(`{3,}|~{3,})([\s\S]*?)\n {0,3}\1[ \n\t]*$/
};
const escapeReplacements = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  "'": '&#39;'
};
const getEscapeReplacement = ch => escapeReplacements[ch];
function escapeHtml(html, encode) {
  if (encode) {
    if (other.escapeTest.test(html)) {
      return html.replace(other.escapeReplace, getEscapeReplacement);
    }
  } else {
    if (other.escapeTestNoEncode.test(html)) {
      return html.replace(other.escapeReplaceNoEncode, getEscapeReplacement);
    }
  }
  return html;
}
class Parser {
  options;
  markdownInstance;
  constructor(options = {}) {
    const {
      markedConfig = {}
    } = options;
    this.options = options;
    this.markdownInstance = new _marked.Marked();
    this.configureLinkRenderer();
    this.configureParagraphRenderer();
    this.configureCodeRenderer();
    // user config at last
    this.markdownInstance.use(markedConfig);
  }
  configureLinkRenderer() {
    if (!this.options.openLinksInNewTab) return;
    const renderer = {
      link({
        href,
        title,
        tokens
      }) {
        const text = this.parser.parseInline(tokens);
        const titleAttr = title ? ` title="${title}"` : '';
        return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
      }
    };
    this.markdownInstance.use({
      renderer
    });
  }
  configureParagraphRenderer() {
    const {
      paragraphTag
    } = this.options;
    if (!paragraphTag) return;
    const renderer = {
      paragraph({
        tokens
      }) {
        return `<${paragraphTag}>${this.parser.parseInline(tokens)}</${paragraphTag}>\n`;
      }
    };
    this.markdownInstance.use({
      renderer
    });
  }
  configureCodeRenderer() {
    const renderer = {
      code({
        text,
        raw,
        lang,
        escaped,
        codeBlockStyle
      }) {
        const langString = (lang || '').match(other.notSpaceStart)?.[0];
        const code = `${text.replace(other.endingNewline, '')}\n`;
        const isIndentedCode = codeBlockStyle === 'indented';
        // if code is indented, it's done because it has no end tag
        const streamStatus = isIndentedCode || other.completeFencedCode.test(raw) ? 'done' : 'loading';
        const escapedCode = escaped ? code : escapeHtml(code, true);
        const classAttr = langString ? ` class="language-${escapeHtml(langString)}"` : '';
        return `<pre><code data-block="true" data-state="${streamStatus}"${classAttr}>${escapedCode}</code></pre>\n`;
      }
    };
    this.markdownInstance.use({
      renderer
    });
  }
  protectCustomTags(content) {
    const placeholders = new Map();
    const customTagNames = Object.keys(this.options.components || {});
    if (customTagNames.length === 0) {
      return {
        protected: content,
        placeholders
      };
    }
    let placeholderIndex = 0;
    const tagNamePattern = customTagNames.map(name => name.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
    const openTagRegex = new RegExp(`<(${tagNamePattern})(?:\\s[^>]*)?>`, 'gi');
    const closeTagRegex = new RegExp(`</(${tagNamePattern})>`, 'gi');
    const positions = [];
    let match;
    openTagRegex.lastIndex = 0;
    match = openTagRegex.exec(content);
    while (match !== null) {
      positions.push({
        index: match.index,
        type: 'open',
        tagName: match[1].toLowerCase(),
        match: match[0]
      });
      match = openTagRegex.exec(content);
    }
    closeTagRegex.lastIndex = 0;
    match = closeTagRegex.exec(content);
    while (match !== null) {
      positions.push({
        index: match.index,
        type: 'close',
        tagName: match[1].toLowerCase(),
        match: match[0]
      });
      match = closeTagRegex.exec(content);
    }
    positions.sort((a, b) => a.index - b.index);
    const stack = [];
    const result = [];
    let lastIndex = 0;
    positions.forEach(pos => {
      if (pos.type === 'open') {
        // Self-closing tags don't have inner content, so they shouldn't be pushed to the stack.
        if (!pos.match.endsWith('/>')) {
          stack.push({
            tagName: pos.tagName,
            start: pos.index,
            openTag: pos.match
          });
        }
      } else if (pos.type === 'close' && stack.length > 0 && stack[stack.length - 1].tagName === pos.tagName) {
        const open = stack.pop();
        if (stack.length === 0) {
          const startPos = open.start;
          const endPos = pos.index + pos.match.length;
          const openTag = open.openTag;
          const closeTag = pos.match;
          const innerContent = content.slice(startPos + openTag.length, pos.index);
          if (lastIndex < startPos) {
            result.push(content.slice(lastIndex, startPos));
          }
          if (innerContent.includes('\n\n')) {
            const protectedInner = innerContent.replace(/\n\n/g, () => {
              const ph = `__X_MD_PLACEHOLDER_${placeholderIndex++}__`;
              placeholders.set(ph, '\n\n');
              return ph;
            });
            result.push(openTag + protectedInner + closeTag);
          } else {
            result.push(openTag + innerContent + closeTag);
          }
          lastIndex = endPos;
        }
      }
    });
    if (lastIndex < content.length) {
      result.push(content.slice(lastIndex));
    }
    return {
      protected: result.join(''),
      placeholders
    };
  }
  restorePlaceholders(content, placeholders) {
    if (placeholders.size === 0) {
      return content;
    }
    return content.replace(/__X_MD_PLACEHOLDER_\d+__/g, match => placeholders.get(match) ?? match);
  }
  parse(content) {
    if (this.options.protectCustomTagNewlines) {
      const {
        protected: protectedContent,
        placeholders
      } = this.protectCustomTags(content);
      const parsed = this.markdownInstance.parse(protectedContent);
      return this.restorePlaceholders(parsed, placeholders);
    }
    return this.markdownInstance.parse(content);
  }
}
var _default = exports.default = Parser;