import { fromMarkdown } from "mdast-util-from-markdown";

const INLINE_FOOTNOTE_PREFIX = "inline-footnote";

const findInlineFootnote = (value, fromIndex = 0) => {
  const start = value.indexOf("^[", fromIndex);

  if (start === -1) {
    return null;
  }

  let depth = 1;

  for (let index = start + 2; index < value.length; index += 1) {
    const character = value[index];

    if (character === "\\" && index + 1 < value.length) {
      index += 1;
      continue;
    }

    if (character === "[") {
      depth += 1;
      continue;
    }

    if (character === "]") {
      depth -= 1;

      if (depth === 0) {
        return {
          start,
          end: index + 1,
          value: value.slice(start + 2, index).trim(),
        };
      }
    }
  }

  return null;
};

const createFootnoteChildren = (value) => {
  const tree = fromMarkdown(value);

  if (tree.children.length > 0) {
    return tree.children;
  }

  return [
    {
      type: "paragraph",
      children: [
        {
          type: "text",
          value,
        },
      ],
    },
  ];
};

const createFootnoteParagraph = (children) => ({
  type: "paragraph",
  children:
    children.length > 0
      ? children
      : [
          {
            type: "text",
            value: "",
          },
        ],
});

const createFootnoteDefinition = (identifier, label, value) => ({
  type: "footnoteDefinition",
  identifier,
  label,
  children: createFootnoteChildren(value),
});

const createFootnoteDefinitionFromChildren = (identifier, label, children) => ({
  type: "footnoteDefinition",
  identifier,
  label,
  children: [createFootnoteParagraph(children)],
});

const createFootnoteReference = (context) => {
  context.count += 1;

  const label = String(context.count);
  const identifier = `${INLINE_FOOTNOTE_PREFIX}-${label}`;

  return {
    identifier,
    label,
    node: {
      type: "footnoteReference",
      identifier,
      label,
    },
  };
};

const findClosingBracket = (value) => {
  for (let index = 0; index < value.length; index += 1) {
    const character = value[index];

    if (character === "\\" && index + 1 < value.length) {
      index += 1;
      continue;
    }

    if (character === "]") {
      return index;
    }
  }

  return -1;
};

const cloneNode = (node) => JSON.parse(JSON.stringify(node));

const splitTextNode = (node, context) => {
  const replacements = [];
  let cursor = 0;
  let match = findInlineFootnote(node.value, cursor);

  while (match) {
    if (match.start > cursor) {
      replacements.push({
        type: "text",
        value: node.value.slice(cursor, match.start),
      });
    }

    if (match.value) {
      const reference = createFootnoteReference(context);
      replacements.push(reference.node);

      context.definitions.push(
        createFootnoteDefinition(reference.identifier, reference.label, match.value),
      );
    } else {
      replacements.push({
        type: "text",
        value: node.value.slice(match.start, match.end),
      });
    }

    cursor = match.end;
    match = findInlineFootnote(node.value, cursor);
  }

  if (cursor < node.value.length) {
    replacements.push({
      type: "text",
      value: node.value.slice(cursor),
    });
  }

  return replacements;
};

const splitAcrossInlineNodes = (children, startIndex, context) => {
  const startNode = children[startIndex];
  const start = startNode.value.indexOf("^[");

  if (start === -1) {
    return null;
  }

  if (findInlineFootnote(startNode.value, start)) {
    return null;
  }

  const footnoteChildren = [];
  const replacements = [];
  const before = startNode.value.slice(0, start);
  const initialText = startNode.value.slice(start + 2);

  if (before) {
    replacements.push({
      type: "text",
      value: before,
    });
  }

  if (initialText) {
    footnoteChildren.push({
      type: "text",
      value: initialText,
    });
  }

  for (let index = startIndex + 1; index < children.length; index += 1) {
    const child = children[index];

    if (child.type === "text") {
      const closingIndex = findClosingBracket(child.value);

      if (closingIndex !== -1) {
        const footnoteText = child.value.slice(0, closingIndex);
        const after = child.value.slice(closingIndex + 1);

        if (footnoteText) {
          footnoteChildren.push({
            type: "text",
            value: footnoteText,
          });
        }

        const reference = createFootnoteReference(context);
        replacements.push(reference.node);

        if (after) {
          replacements.push({
            type: "text",
            value: after,
          });
        }

        context.definitions.push(
          createFootnoteDefinitionFromChildren(
            reference.identifier,
            reference.label,
            footnoteChildren,
          ),
        );

        return {
          consumed: index - startIndex + 1,
          replacements,
        };
      }

      footnoteChildren.push(cloneNode(child));
      continue;
    }

    footnoteChildren.push(cloneNode(child));
  }

  return null;
};

const visitChildren = (node, context) => {
  if (!Array.isArray(node.children)) {
    return;
  }

  for (let index = 0; index < node.children.length; index += 1) {
    const child = node.children[index];

    if (child.type === "text" && child.value.includes("^[")) {
      const acrossNodes = splitAcrossInlineNodes(node.children, index, context);

      if (acrossNodes) {
        node.children.splice(index, acrossNodes.consumed, ...acrossNodes.replacements);
        index += acrossNodes.replacements.length - 1;
        continue;
      }

      const replacements = splitTextNode(child, context);
      node.children.splice(index, 1, ...replacements);
      index += replacements.length - 1;
      continue;
    }

    visitChildren(child, context);
  }
};

const remarkInlineFootnotes = () => (tree) => {
  const context = {
    count: 0,
    definitions: [],
  };

  visitChildren(tree, context);

  if (context.definitions.length > 0) {
    tree.children.push(...context.definitions);
  }
};

export default remarkInlineFootnotes;
