import React from "react";
import katex from "katex";
import DomParser, { Node } from "dom-parser";
import { FText, KatexWrapper } from "./styled";

export interface TextNode {
  type: "text";
  value: string;
}

export interface FormulaNode {
  type: "formula";
  value: string;
  node: Node | null;
  block?: boolean;
}

export type Nodes = Array<TextNode | FormulaNode>;

function getKatex(formula: string) {
  try {
    const katexHtml = katex.renderToString(formula);
    const p = new DomParser();
    const dom = p.parseFromString(`<div id="root">${katexHtml}</div>`);
    return dom.getElementById("root");
  } catch (error) {
    console.error(formula, error);
    return null;
  }
}

export function getTextAndFormulas(content: string): Nodes {
  const nodes: Nodes = [];
  const { length } = content;
  let i = 0;
  let holdingText = "";
  const inlineRegex = /^\$([^$]+)\$/;
  const blockRegex = /^\$\$([^$]+)\$\$/;

  do {
    const tailing = content.slice(i);
    let matched: RegExpMatchArray | null = null;

    matched = tailing.match(inlineRegex) || tailing.match(blockRegex);

    if (matched) {
      if (holdingText.length > 0) {
        nodes.push({
          type: "text",
          value: holdingText,
        });
      }
      holdingText = "";

      if (matched[1]) {
        const formula = matched[1].trim();
        nodes.push({
          type: "formula",
          value: formula,
          node: getKatex(formula),
        });
      }
      i += matched[0].length;
    } else {
      holdingText += tailing.charAt(0);
      i++;
    }
  } while (i < length);

  if (holdingText.length > 0) {
    nodes.push({
      type: "text",
      value: holdingText,
    });
  }
  return nodes;
}

function getStyles(styleString: string) {
  const output: Record<string, any> = {};
  const camelize = function camelize(str: string) {
    return str.replace(/(?:^|[-])(\w)/g, (a, c) => {
      c = a.substring(0, 1) === "-" ? c.toUpperCase() : c;
      return c || "";
    });
  };

  const style = styleString.split(";");

  for (let i = 0; i < style.length; ++i) {
    const rule = style[i].trim();

    if (rule) {
      const ruleParts = rule.split(":");
      const key = camelize(ruleParts[0].trim());
      output[key] = ruleParts[1].trim();
    }
  }

  return output;
}

function walkChildNodes(childNodes: Node[]) {
  if (!childNodes || childNodes.length === 0) {
    return null;
  }
  return childNodes.map((node, index) => {
    if (["div", "span"].includes(node.nodeName)) {
      const cls: string[] = ["span"];
      let style = {};
      node.attributes.forEach((attr: any) => {
        if (attr.name === "class") {
          cls.push(...attr.value.split(/\s+/));
        } else if (attr.name === "style") {
          style = getStyles(attr.value);
        }
      });

      return (
        <span key={String(index)} style={style} className={cls.join(" ")}>
          {walkChildNodes(node.childNodes)}
        </span>
      );
    } else if (node.nodeName === "#text") {
      return <span key={String(index)}>{(node as any).text}</span>;
    }
    return null;
  });
}

function renderFormulaNode(node: FormulaNode) {
  if (!node.node) {
    return null;
  }

  return <KatexWrapper block={node.block}>{walkChildNodes(node.node.childNodes)}</KatexWrapper>;
}

export function renderNodes(nodes: Nodes) {
  return nodes.map(node => {
    if (node.type === "text") {
      return <FText>{node.value}</FText>;
    } else {
      return renderFormulaNode(node);
    }
  });
}

export function renderFormula(formula: string, block = false) {
  const node: FormulaNode = {
    type: "formula",
    value: formula,
    node: getKatex(formula),
    block,
  };
  return node ? renderFormulaNode(node) : null;
}
