import { mathjax } from 'mathjax-full/js/mathjax';
import { MathJax } from 'mathjax-full/js/components/global';
import { AssistiveMmlHandler } from 'mathjax-full/js/a11y/assistive-mml';
import { EnrichHandler } from 'mathjax-full/js/a11y/semantic-enrich';
import { MenuHandler } from 'mathjax-full/js/ui/menu/MenuHandler';
import { FindMathML } from 'mathjax-full/js/input/mathml/FindMathML';
import { MathML } from 'mathjax-full/js/input/mathml';
import { TeX } from 'mathjax-full/js/input/tex';
import { CHTML } from 'mathjax-full/js/output/chtml';
import { RegisterHTMLHandler } from 'mathjax-full/js/handlers/html';
import { browserAdaptor } from 'mathjax-full/js/adaptors/browserAdaptor';
import { AllPackages } from 'mathjax-full/js/input/tex/AllPackages';
import { engineReady } from 'speech-rule-engine/js/common/system';
import { CHTMLWrapper } from 'mathjax-full/js/output/chtml/Wrapper';
import _ from 'lodash';
import { AbstractMmlNode } from 'mathjax-full/js/core/MmlTree/MmlNode';
import debug from 'debug';
import { MmlFactory } from 'mathjax-full/js/core/MmlTree/MmlFactory';
import { SerializedMmlVisitor } from 'mathjax-full/js/core/MmlTree/SerializedMmlVisitor';
import { CHTMLWrapperFactory } from 'mathjax-full/js/output/chtml/WrapperFactory';
import { CHTMLmspace } from 'mathjax-full/js/output/chtml/Wrappers/mspace';
import { HTMLDomStrings } from 'mathjax-full/js/handlers/html/HTMLDomStrings';
import { MathMLToLaTeX } from '@pie-framework/mathml-to-latex';

function _extends() {
  _extends = Object.assign || function (target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];

      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }

    return target;
  };

  return _extends.apply(this, arguments);
}

const reduceText = (acc, n) => {
  if (n.node && n.node.kind === 'text') {
    acc += n.node.text;
  }

  return acc;
};

class Line {
  constructor() {
    this.kind = 'line';
  }

  get columns() {
    return [];
  }

}
class Row {
  constructor(columns, operator) {
    this.kind = 'row';
    this.operator = operator;
    this.columns = columns;
  }

  pad(count, direction = 'right') {
    if (count < this.columns.length) {
      throw new Error('no');
    }

    const diff = count - this.columns.length;

    const padding = _.times(diff).map(() => '__pad__');

    return direction === 'right' ? [...padding, ...this.columns] : [...this.columns, ...padding];
  }

}

const mathNodeToCharArray = mn => {
  const text = mn.childNodes.reduce(reduceText, '');
  return text.split('');
};
/**
 * Convert child a column entry
 * @param {*} child
 * @return an array of column content
 */


const toColumnArray = child => {
  if (!child || !child.kind) {
    return [];
  }

  if (child.kind === 'msrow') {
    throw new Error('msrow in msrow?');
  }

  if (child.kind === 'mo') {
    // We are going to treat this operator as a text array.
    // It's probably going to be a decimal point
    // eslint-disable-next-line no-console
    console.warn('mo that is not 1st node in msrow?');
    return mathNodeToCharArray(child); // throw new Error('mo must be first child of msrow');
  }

  if (child.kind === 'mn') {
    return mathNodeToCharArray(child);
  }

  if (child.toCHTML) {
    return child;
  }
};
/**
 *  convert mstack chtml childNodes into a Row
 *  @param child chtml child node of mstack
 *  @return Row | Line
 */


const rowStack = child => {
  if (!child || !child.kind) {
    return;
  }

  if (child.kind === 'msrow') {
    if (!child.childNodes || child.childNodes.length === 0) {
      return new Row([]);
    }

    const f = _.first(child.childNodes);

    const nodes = f && f.kind === 'mo' ? _.tail(child.childNodes) : child.childNodes;

    const columns = _.flatten(nodes.map(toColumnArray));

    return new Row(columns, f.kind === 'mo' ? f : undefined);
  }

  if (child.kind === 'mn') {
    const columns = mathNodeToCharArray(child);
    return new Row(columns, undefined);
  }

  if (child.kind === 'mo') {
    // eslint-disable-next-line no-console
    console.warn('mo on its own row?');
    return new Row([], child);
  }

  if (child.kind === 'msline') {
    return new Line();
  }

  if (child.toCHTML) {
    return new Row([child]);
  }
};
/** convert MathJax chtml tree to Row[]
 * @param mstack the root of the mathjax chtml tree
 * @return Row[]
 */


const getStackData = mstack => {
  if (!mstack || !mstack.childNodes) {
    return [];
  }

  return _.compact(mstack.childNodes.map(rowStack));
};
class CHTMLmstack extends CHTMLWrapper {
  constructor(factory, node, parent = null) {
    super(factory, node, parent);
    this.ce = this.adaptor.document.createElement.bind(this.adaptor.document);
  }

  toCHTML(parent) {
    const chtml = this.standardCHTMLnode(parent);
    const stackData = getStackData(this); // console.log('stackData', stackData);

    const maxCols = stackData.reduce((acc, r) => {
      if (r && r.columns.length > acc) {
        acc = r.columns.length;
      }

      return acc;
    }, 0);
    const table = this.ce('table');
    chtml.appendChild(table);
    stackData.forEach(row => {
      const tr = this.ce('tr');
      table.appendChild(tr);

      if (row.kind === 'row') {
        const td = this.ce('td');
        tr.appendChild(td);

        if (row.operator && row.operator.toCHTML) {
          td.setAttribute('class', 'inner');
          row.operator.toCHTML(td);
        } else {
          td.textContent = '';
        } // align right for now:


        const cols = row.pad(maxCols, 'right');
        cols.forEach(c => {
          const t = this.ce('td');
          tr.appendChild(t);

          if (c === '__pad__') {
            t.textContent = '';
          } else if (typeof c === 'string') {
            t.textContent = c;
          } else if (c.kind === 'none') {
            t.textContent = '';
          } else if (c.toCHTML) {
            t.setAttribute('class', 'inner');
            c.toCHTML(t);
          }
        });
      } else if (row.kind === 'line') {
        const td = this.ce('td');
        tr.appendChild(td);
        td.setAttribute('colspan', maxCols + 1);
        td.setAttribute('class', 'mjx-line');
        td.textContent = '';
      }
    });
  }

}
CHTMLmstack.styles = {
  'mjx-mstack > table': {
    'line-height': 'initial',
    border: 'solid 0px red',
    'border-spacing': '0em',
    'border-collapse': 'separate'
  },
  'mjx-mstack > table > tr': {
    'line-height': 'initial'
  },
  'mjx-mstack > table > tr > td': {
    // padding: '1.2rem',
    border: 'solid 0px blue',
    'font-family': 'sans-serif',
    'line-height': 'initial'
  },
  'mjx-mstack > table > tr > td.inner': {
    'font-family': 'inherit'
  },
  'mjx-mstack > table > tr > .mjx-line': {
    padding: 0,
    'border-top': 'solid 1px black'
  },
  '.TEX-A': {
    'font-family': 'MJXZERO, MJXTEX !important'
  }
};

class MmlNone extends AbstractMmlNode {
  get kind() {
    return 'none';
  }

}
class MmlMstack extends AbstractMmlNode {
  get kind() {
    return 'mstack';
  }

}
class MmlMsrow extends AbstractMmlNode {
  get kind() {
    return 'msrow';
  }

}
class MmlMsline extends AbstractMmlNode {
  get kind() {
    return 'msline';
  }

}

const chtmlNodes = {
  mstack: CHTMLmstack
};
const mmlNodes = {
  mstack: MmlMstack,
  msline: MmlMsline,
  msrow: MmlMsrow,
  none: MmlNone
};

const BracketTypes = {};
BracketTypes.ROUND_BRACKETS = 'round_brackets';
BracketTypes.SQUARE_BRACKETS = 'square_brackets';
BracketTypes.DOLLAR = 'dollar';
BracketTypes.DOUBLE_DOLLAR = 'double_dollar';
const PAIRS = {
  [BracketTypes.ROUND_BRACKETS]: ['\\(', '\\)'],
  [BracketTypes.SQUARE_BRACKETS]: ['\\[', '\\]'],
  [BracketTypes.DOLLAR]: ['$', '$'],
  [BracketTypes.DOUBLE_DOLLAR]: ['$$', '$$']
};
const wrapMath = (content, wrapType) => {
  if (wrapType === BracketTypes.SQUARE_BRACKETS) {
    console.warn('\\[...\\] is not supported yet'); // eslint-disable-line

    wrapType = BracketTypes.ROUND_BRACKETS;
  }

  if (wrapType === BracketTypes.DOUBLE_DOLLAR) {
    console.warn('$$...$$ is not supported yet'); // eslint-disable-line

    wrapType = BracketTypes.DOLLAR;
  }

  const [start, end] = PAIRS[wrapType] || PAIRS[BracketTypes.ROUND_BRACKETS];
  return `${start}${content}${end}`;
};
const unWrapMath = content => {
  const displayStyleIndex = content.indexOf('\\displaystyle');

  if (displayStyleIndex !== -1) {
    console.warn('\\displaystyle is not supported - removing'); // eslint-disable-line

    content = content.replace('\\displaystyle', '').trim();
  }

  if (content.startsWith('$$') && content.endsWith('$$')) {
    console.warn('$$ syntax is not yet supported'); // eslint-disable-line

    return {
      unwrapped: content.substring(2, content.length - 2),
      wrapType: BracketTypes.DOLLAR
    };
  }

  if (content.startsWith('$') && content.endsWith('$')) {
    return {
      unwrapped: content.substring(1, content.length - 1),
      wrapType: BracketTypes.DOLLAR
    };
  }

  if (content.startsWith('\\[') && content.endsWith('\\]')) {
    console.warn('\\[..\\] syntax is not yet supported'); // eslint-disable-line

    return {
      unwrapped: content.substring(2, content.length - 2),
      wrapType: BracketTypes.ROUND_BRACKETS
    };
  }

  if (content.startsWith('\\(') && content.endsWith('\\)')) {
    return {
      unwrapped: content.substring(2, content.length - 2),
      wrapType: BracketTypes.ROUND_BRACKETS
    };
  }

  return {
    unwrapped: content,
    wrapType: BracketTypes.ROUND_BRACKETS
  };
};

if (typeof window !== 'undefined') {
  RegisterHTMLHandler(browserAdaptor());
}

let sreReady = false;
engineReady().then(() => {
  sreReady = true;
}); // import pkg from '../../package.json';
const visitor = new SerializedMmlVisitor();

const toMMl = node => visitor.visitTree(node);

const log = debug('pie-lib:math-rendering');
const NEWLINE_BLOCK_REGEX = /\\embed\{newLine\}\[\]/g;
const NEWLINE_LATEX = '\\newline ';

const getGlobal = () => {
  // TODO does it make sense to use version?
  // const key = `${pkg.name}@${pkg.version.split('.')[0]}`;
  // It looks like Ed made this change when he switched from mathjax3 to mathjax-full
  // I think it was supposed to make sure version 1 (using mathjax3) is not used
  // in combination with version 2 (using mathjax-full)
  // TODO higher level wrappers use this instance of math-rendering, and if 2 different instances are used, math rendering is not working
  //  so I will hardcode this for now until a better solution is found
  const key = '@pie-lib/math-rendering@2';

  if (typeof window !== 'undefined') {
    if (!window[key]) {
      window[key] = {};
    }

    return window[key];
  } else {
    return {};
  }
};
/** Add temporary support for a global singleDollar override
 *  <code>
 *   // This will enable single dollar rendering
 *   window.pie = window.pie || {};
 *   window.pie.mathRendering =  {useSingleDollar: true };
 *  </code>
 */


const defaultOpts = () => getGlobal().opts || {};

const fixMathElement = element => {
  if (element.dataset.mathHandled) {
    return;
  }

  let property = 'innerText';

  if (element.textContent) {
    property = 'textContent';
  }

  if (element[property]) {
    element[property] = wrapMath(unWrapMath(element[property]).unwrapped); // because mathquill doesn't understand line breaks, sometimes we end up with custom elements on prompts/rationale/etc.
    // we need to replace the custom embedded elements with valid latex that Mathjax can understand

    element[property] = element[property].replace(NEWLINE_BLOCK_REGEX, NEWLINE_LATEX);
    element.dataset.mathHandled = true;
  }
};
const fixMathElements = (el = document) => {
  const mathElements = el.querySelectorAll('[data-latex]');
  mathElements.forEach(item => fixMathElement(item));
};

const adjustMathMLStyle = (el = document) => {
  const nodes = el.querySelectorAll('math');
  nodes.forEach(node => node.setAttribute('displaystyle', 'true'));
};

class myFindMathML extends FindMathML {
  processMath(set) {
    const adaptor = this.adaptor;

    for (const mml of Array.from(set)) {
      if (adaptor.kind(adaptor.parent(mml)) === 'mjx-assistive-mml') {
        set.delete(mml);
      }
    }

    return super.processMath(set);
  }

}

const createMathMLInstance = (opts, docProvided = document) => {
  opts = opts || defaultOpts();

  if (opts.useSingleDollar) {
    // eslint-disable-next-line
    console.warn('[math-rendering] using $ is not advisable, please use $$..$$ or \\(...\\)');
  }

  const packages = AllPackages.filter(name => name !== 'bussproofs'); // Bussproofs needs an output jax
  // The autoload extension predefines all the macros from the extensions that haven't been loaded already
  // so that they automatically load the needed extension when they are first used

  packages.push('autoload');
  const macros = {
    parallelogram: '\\lower.2em{\\Huge\\unicode{x25B1}}',
    overarc: '\\overparen',
    napprox: '\\not\\approx',
    longdiv: '\\enclose{longdiv}'
  };
  const texConfig = opts.useSingleDollar ? {
    packages,
    macros,
    inlineMath: [['$', '$'], ['\\(', '\\)']],
    processEscapes: true
  } : {
    packages,
    macros
  };
  const mmlConfig = {
    parseError: function (node) {
      // function to process parsing errors
      // eslint-disable-next-line no-console
      console.log('error:', node);
      this.error(this.adaptor.textContent(node).replace(/\n.*/g, ''));
    },
    FindMathML: new myFindMathML()
  };
  let cachedMathjax;

  if (MathJax && MathJax.version !== mathjax.version) {
    // handling other MathJax version on the page
    // replacing it temporarily with the version we have
    window.MathJax._ = window.MathJax._ || {};
    window.MathJax.config = window.MathJax.config || {};
    cachedMathjax = window.MathJax;
    Object.assign(MathJax, mathjax);
  }

  const fontURL = `https://unpkg.com/mathjax-full@${mathjax.version}/ts/output/chtml/fonts/tex-woff-v2`;
  const htmlConfig = {
    fontURL,
    wrapperFactory: new CHTMLWrapperFactory(_extends({}, CHTMLWrapperFactory.defaultNodes, chtmlNodes))
  };
  const mml = new MathML(mmlConfig);
  const customMmlFactory = new MmlFactory(_extends({}, MmlFactory.defaultNodes, mmlNodes));
  const classFactory = EnrichHandler(MenuHandler(AssistiveMmlHandler(mathjax.handlers.handlesDocument(docProvided))), mml);
  const html = classFactory.create(docProvided, {
    compileError: (mj, math, err) => {
      // eslint-disable-next-line no-console
      console.log('bad math?:', math); // eslint-disable-next-line no-console

      console.error(err);
    },
    typesetError: function (doc, math, err) {
      // eslint-disable-next-line no-console
      console.log('typeset error'); // eslint-disable-next-line no-console

      console.error(err);
      doc.typesetError(math, err);
    },
    sre: {
      speech: 'deep'
    },
    enrichSpeech: 'deep',
    InputJax: [new TeX(texConfig), mml],
    OutputJax: new CHTML(htmlConfig),
    DomStrings: new HTMLDomStrings({
      skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code', 'annotation', 'annotation-xml', 'mjx-assistive-mml', 'mjx-container']
    })
  }); // Note: we must set this *after* mathjax.document (no idea why)

  mml.setMmlFactory(customMmlFactory);

  if (cachedMathjax) {
    // if we have a cached version, we replace it here
    window.MathJax = cachedMathjax;
  }

  return html;
};

let enrichSpeechInitialized = false;

const bootstrap = opts => {
  if (typeof window === 'undefined') {
    return {
      Typeset: () => ({})
    };
  }

  const html = createMathMLInstance(opts);
  return {
    version: mathjax.version,
    html: html,
    Typeset: function (...elements) {
      const attemptRender = (temporary = false) => {
        var _updatedDocument$math, _updatedDocument$math2;

        let updatedDocument = this.html.findMath(elements.length ? {
          elements
        } : {}).compile();

        if (!temporary && sreReady) {
          updatedDocument = updatedDocument.enrich();
        }

        updatedDocument = updatedDocument.getMetrics().typeset().assistiveMml().attachSpeech().addMenu().updateDocument();

        if (!enrichSpeechInitialized && typeof ((_updatedDocument$math = updatedDocument.math.list) == null ? void 0 : (_updatedDocument$math2 = _updatedDocument$math.next) == null ? void 0 : _updatedDocument$math2.data) === 'object') {
          enrichSpeechInitialized = true;
        }

        try {
          const list = updatedDocument.math.list;

          if (list) {
            for (let item = list.next; typeof item.data !== 'symbol'; item = item.next) {
              const mathMl = toMMl(item.data.root);
              const parsedMathMl = mathMl.replaceAll('\n', '');
              item.data.typesetRoot.setAttribute('data-mathml', parsedMathMl);
              item.data.typesetRoot.setAttribute('tabindex', '-1');
            }
          }
        } catch (e) {
          // eslint-disable-next-line no-console
          console.error(e.toString());
        }

        updatedDocument.clear();
      };

      if (!enrichSpeechInitialized) {
        attemptRender(true);
      }

      mathjax.handleRetriesFor(() => {
        attemptRender();
      });
    }
  };
};

const renderMath = (el, renderOpts) => {
  var _getGlobal$instance, _getGlobal$instance2;

  if (window && window.MathJax && window.MathJax.customKey && window.MathJax.customKey == '@pie-lib/math-rendering-accessible@1') {
    return;
  }

  const isString = typeof el === 'string';
  let executeOn = document.body;

  if (isString) {
    const div = document.createElement('div');
    div.innerHTML = el;
    executeOn = div;
  }

  fixMathElements(executeOn);
  adjustMathMLStyle(executeOn);

  if (isString) {
    const html = createMathMLInstance(undefined, executeOn);
    const updatedDocument = html.findMath().compile().getMetrics().typeset().updateDocument();
    const list = updatedDocument.math.list;
    const item = list.next;

    if (!item) {
      return '';
    }

    const mathMl = toMMl(item.data.root);
    const parsedMathMl = mathMl.replaceAll('\n', '');
    return parsedMathMl;
  }

  if (!getGlobal().instance) {
    getGlobal().instance = bootstrap(renderOpts);
  }

  if (!el) {
    log('el is undefined');
    return;
  }

  if (el instanceof Element && (_getGlobal$instance = getGlobal().instance) != null && _getGlobal$instance.Typeset) {
    getGlobal().instance.Typeset(el);
  } else if (el.length && (_getGlobal$instance2 = getGlobal().instance) != null && _getGlobal$instance2.Typeset) {
    const arr = Array.from(el);
    getGlobal().instance.Typeset(...arr);
  }
};
/**
 * This style is added to overried default styling of mjx-mspace Mathjax tag
 * In mathjax src code \newline latex gets parsed to <mjx-mspace></mjx-mspace>,
 * but has the default style
 * 'mjx-mspace': {
  "display": 'in-line',
  "text-align": 'left'
} which prevents it from showing as a newline value
 */


CHTMLmspace.styles = {
  'mjx-mspace': {
    display: 'block',
    'text-align': 'center',
    height: '5px'
  }
};

var mmlToLatex = (mathml => MathMLToLaTeX.convert(mathml));

export { mmlToLatex, renderMath, unWrapMath, wrapMath };
//# sourceMappingURL=index.js.map
