const attr_name = "[a-zA-Z_:@][a-zA-Z0-9:._-]*";
const unquoted = "[^\"'=<>`\\x00-\\x20]+";
const single_quoted = "'[^']*'";
const double_quoted = '"[^"]*"';
const attr_value = "(?:" + unquoted + "|" + single_quoted + "|" + double_quoted + ")";
const attribute = "(?:\\s+" + attr_name + "(?:\\s*=\\s*" + attr_value + ")?)";
const open_tag = "<[A-Za-z][A-Za-z0-9\\-]*" + attribute + "*\\s*\\/?>";
const close_tag = "<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>";
const comment = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->";
const processing = "<[?][\\s\\S]*?[?]>";
const declaration = "<![A-Z]+\\s+[^>]*>";
const cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>";
const HTML_TAG_RE = new RegExp(
  "^(?:" + open_tag + "|" + close_tag + "|" + comment + "|" + processing + "|" + declaration + "|" + cdata + ")"
);
const HTML_OPEN_CLOSE_TAG_RE = new RegExp(
  "^(?:" + open_tag + "|" + close_tag + ")"
);
const HTML_SELF_CLOSING_TAG_RE = new RegExp(
  "^<[A-Za-z][A-Za-z0-9\\-]*" + attribute + "*\\s*\\/>"
);
const HTML_OPEN_AND_CLOSE_TAG_IN_THE_SAME_LINE_RE = new RegExp(
  "^<([A-Za-z][A-Za-z0-9\\-]*)" + attribute + "*\\s*>.*<\\/\\1\\s*>"
);

const TAGS_BLOCK = [
  "address",
  "article",
  "aside",
  "base",
  "basefont",
  "blockquote",
  "body",
  "caption",
  "center",
  "col",
  "colgroup",
  "dd",
  "details",
  "dialog",
  "dir",
  "div",
  "dl",
  "dt",
  "fieldset",
  "figcaption",
  "figure",
  "footer",
  "form",
  "frame",
  "frameset",
  "h1",
  "h2",
  "h3",
  "h4",
  "h5",
  "h6",
  "head",
  "header",
  "hr",
  "html",
  "iframe",
  "legend",
  "li",
  "link",
  "main",
  "menu",
  "menuitem",
  "nav",
  "noframes",
  "ol",
  "optgroup",
  "option",
  "p",
  "param",
  "search",
  "section",
  "summary",
  "table",
  "tbody",
  "td",
  "tfoot",
  "th",
  "thead",
  "title",
  "tr",
  "track",
  "ul"
];
const TAGS_INLINE = [
  "a",
  "abbr",
  "acronym",
  "audio",
  "b",
  "bdi",
  "bdo",
  "big",
  "br",
  "button",
  "canvas",
  "cite",
  "code",
  "data",
  "datalist",
  "del",
  "dfn",
  "em",
  "embed",
  "i",
  "iframe",
  "img",
  "input",
  "ins",
  "kbd",
  "label",
  "map",
  "mark",
  "meter",
  "noscript",
  "object",
  "output",
  "picture",
  "progress",
  "q",
  "ruby",
  "s",
  "samp",
  "script",
  "select",
  "slot",
  "small",
  "span",
  "strong",
  "sub",
  "sup",
  "svg",
  "template",
  "textarea",
  "time",
  "u",
  "tt",
  "var",
  "video",
  "wbr"
];
const TAGS_VUE_RESERVED = [
  "template",
  "component",
  "transition",
  "transition-group",
  "keep-alive",
  "slot",
  "teleport"
];

const createHtmlSequences = ({
  blockTags,
  inlineTags
}) => {
  const forceBlockTags = [...blockTags, ...TAGS_BLOCK];
  const forceInlineTags = [
    ...inlineTags,
    ...TAGS_INLINE.filter((item) => !TAGS_VUE_RESERVED.includes(item))
  ];
  const HTML_SEQUENCES = [
    [/^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true],
    [/^<!--/, /-->/, true],
    [/^<\?/, /\?>/, true],
    [/^<![A-Z]/, />/, true],
    [/^<!\[CDATA\[/, /\]\]>/, true],
    // MODIFIED: Support extra block tags from user options
    [
      new RegExp("^</?(" + forceBlockTags.join("|") + ")(?=(\\s|/?>|$))", "i"),
      /^$/,
      true
    ],
    // ADDED: Matching component tags (all unknown tags) (i = 6)
    [
      new RegExp(
        "^</?(?!(" + forceInlineTags.join("|") + ")(?![\\w-]))[A-Za-z][A-Za-z0-9\\-]*(?=(\\s|/?>|$))"
      ),
      /^$/,
      true
    ],
    // this line is to treat a line that only have a single self-closed html tag
    // as html_block, even if it's a self-closed inline tag
    // MODIFIED: Tweak the original HTML_OPEN_CLOSE_TAG_RE
    [new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + "\\s*$"), /^$/, false]
  ];
  return HTML_SEQUENCES;
};
const createHtmlBlockRule = (options) => {
  const HTML_SEQUENCES = createHtmlSequences(options);
  return (state, startLine, endLine, silent) => {
    let i;
    let nextLine;
    let lineText;
    let pos = state.bMarks[startLine] + state.tShift[startLine];
    let max = state.eMarks[startLine];
    if (state.sCount[startLine] - state.blkIndent >= 4) {
      return false;
    }
    if (!state.md.options.html) {
      return false;
    }
    if (state.src.charCodeAt(pos) !== 60) {
      return false;
    }
    lineText = state.src.slice(pos, max);
    for (i = 0; i < HTML_SEQUENCES.length; i++) {
      if (HTML_SEQUENCES[i][0].test(lineText)) {
        break;
      }
    }
    if (i === HTML_SEQUENCES.length) {
      return false;
    }
    if (silent) {
      return HTML_SEQUENCES[i][2];
    }
    if (i === 6) {
      const match = (
        // if the component tag is self-closing
        lineText.match(HTML_SELF_CLOSING_TAG_RE) ?? // or has open and close tag in the same line
        lineText.match(HTML_OPEN_AND_CLOSE_TAG_IN_THE_SAME_LINE_RE)
      );
      if (match) {
        state.line = startLine + 1;
        let token2 = state.push("html_inline", "", 0);
        token2.content = match[0];
        token2.map = [startLine, state.line];
        token2 = state.push("inline", "", 0);
        token2.content = lineText.slice(match[0].length);
        token2.map = [startLine, state.line];
        token2.children = [];
        return true;
      }
    }
    nextLine = startLine + 1;
    if (!HTML_SEQUENCES[i][1].test(lineText)) {
      for (; nextLine < endLine; nextLine++) {
        if (state.sCount[nextLine] < state.blkIndent) {
          break;
        }
        pos = state.bMarks[nextLine] + state.tShift[nextLine];
        max = state.eMarks[nextLine];
        lineText = state.src.slice(pos, max);
        if (HTML_SEQUENCES[i][1].test(lineText)) {
          if (lineText.length !== 0) {
            nextLine++;
          }
          break;
        }
      }
    }
    state.line = nextLine;
    const token = state.push("html_block", "", 0);
    token.map = [startLine, nextLine];
    token.content = state.getLines(startLine, nextLine, state.blkIndent, true);
    return true;
  };
};

const isLetter = (ch) => {
  const lc = ch | 32;
  return lc >= 97 && lc <= 122;
};
const htmlInlineRule = (state, silent) => {
  const { pos } = state;
  if (!state.md.options.html) {
    return false;
  }
  const max = state.posMax;
  if (state.src.charCodeAt(pos) !== 60 || pos + 2 >= max) {
    return false;
  }
  const ch = state.src.charCodeAt(pos + 1);
  if (ch !== 33 && ch !== 63 && ch !== 47 && !isLetter(ch)) {
    return false;
  }
  const match = state.src.slice(pos).match(HTML_TAG_RE);
  if (!match) {
    return false;
  }
  if (!silent) {
    const token = state.push("html_inline", "", 0);
    token.content = state.src.slice(pos, pos + match[0].length);
  }
  state.pos += match[0].length;
  return true;
};

const componentPlugin = (md, { blockTags = [], inlineTags = [] } = {}) => {
  const htmlBlockRule = createHtmlBlockRule({
    blockTags,
    inlineTags
  });
  md.block.ruler.at("html_block", htmlBlockRule, {
    alt: ["paragraph", "reference", "blockquote"]
  });
  md.inline.ruler.at("html_inline", htmlInlineRule);
};

export { HTML_OPEN_AND_CLOSE_TAG_IN_THE_SAME_LINE_RE, HTML_OPEN_CLOSE_TAG_RE, HTML_SELF_CLOSING_TAG_RE, HTML_TAG_RE, TAGS_BLOCK, TAGS_INLINE, TAGS_VUE_RESERVED, componentPlugin, createHtmlBlockRule, htmlInlineRule };
