import katex from 'katex';
import renderMathInElement from 'katex/dist/contrib/auto-render';
import showdown from 'showdown';
import asciimathToTex from './asciimath-to-tex';

if (process.env.TARGET === 'cjs') {
  const { JSDOM } = require('jsdom');
  const jsdom = new JSDOM();
  global.DOMParser = jsdom.window.DOMParser;
  global.document = jsdom.window.document;
}

/**
 * @param {object} opts
 * @param {NodeListOf<Element>} opts.elements
 * @param opts.config
 * @param {boolean} opts.isAsciimath
 */
function renderBlockElements({ elements, config, isAsciimath }) {
  if (!elements.length) {
    return;
  }

  elements.forEach(element => {
    const input = element.textContent || element.innerText;
    const latex = isAsciimath ? asciimathToTex(input) : input;
    const html = katex.renderToString(latex, config);
    element.parentNode.outerHTML = `<span title="${input.trim()}">${html}</span>`;
  });
}

/**
 * https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
 * @param {string} str
 * @returns {string} regexp escaped string
 */
function escapeRegExp(str) {
  return str.replace(/[-[\]/{}()*+?.\\$^|]/g, '\\$&');
}

// katex config
const getConfig = (config = {}) => ({
  displayMode: true,
  throwOnError: false, // fail silently
  errorColor: '#ff0000',
  ...config,
  delimiters: [
    { left: '$$', right: '$$', display: false, asciimath: false },
    { left: '~', right: '~', display: false, asciimath: true },
  ].concat(config.delimiters || []),
});

const showdownKatex = userConfig => () => {
  const parser = new DOMParser();
  const config = getConfig(userConfig);
  const asciimathDelimiters = config.delimiters
    .filter(item => item.asciimath)
    .map(({ left, right }) => {
      const l = escapeRegExp(left)
      const r = escapeRegExp(right)
      const test = new RegExp(
        `(?:${l})([^${l}${r}]+)(?:${r})`,
        'g',
      );
      const replacer = (match, asciimath) => {
        return `${left}${asciimathToTex(asciimath)}${right}`;
      };
      return { test, replacer };
    });

  return [
    {
      type: 'output',
      filter(html = '') {
        const wrapper = parser.parseFromString(html, 'text/html').body;
        if (asciimathDelimiters.length) {
          wrapper.querySelectorAll(':not(code):not(pre)').forEach(el => {
            const textNodes = [...el.childNodes].filter(
              node => {
                if (node.nodeName === '#text') {
                  return node.nodeValue.trim()
                }
                if (node.nodeName === 'CODE') {
                  return node.innerText.trim()
                }
              },
            )
            textNodes.forEach(node => {
              const newText = asciimathDelimiters.reduce(
                (acc, { test, replacer }) => acc.replace(test, replacer),
                node.nodeValue || node.innerText
              )
              if (node.nodeName === '#text') {
                node.nodeValue = newText
              } else {
                node.innerText = newText;
              }
            })
          })
        }

        // find the math in code blocks
        const latex = wrapper.querySelectorAll('code.latex.language-latex');
        const asciimath = wrapper.querySelectorAll(
          'code.asciimath.language-asciimath',
        );

        renderBlockElements({ elements: latex, config });
        renderBlockElements({ elements: asciimath, config, isAsciimath: true });
        renderMathInElement(wrapper, config);

        return wrapper.innerHTML;
      },
    },
  ];
};

// register extension with default config
showdown.extension('showdown-katex', showdownKatex());

export default showdownKatex;
