import { EMOTES_48px as EMOTES } from 'constants/emotes';
import visit from 'unist-util-visit';

const EMOTE_NODE_TYPE = 'emote';
const RE_EMOTE = /:\+1:|:-1:|:[\w-]+:/;

// ***************************************************************************
// Tokenize emote
// ***************************************************************************

function findNextEmote(value, fromIndex, strictlyFromIndex) {
  let begin = 0;

  while (begin < value.length) {
    const match = value.substring(begin).match(RE_EMOTE);

    if (!match) return null;

    match.index += begin;

    if (strictlyFromIndex && match.index !== fromIndex) {
      if (match.index > fromIndex) {
        // Already gone past desired index. Skip the rest.
        return null;
      } else {
        // Next match might fit 'fromIndex'.
        begin = match.index + match[0].length;
        continue;
      }
    }

    if (fromIndex > 0 && fromIndex > match.index && fromIndex < match.index + match[0].length) {
      // Skip previously-rejected word
      // This assumes that a non-zero 'fromIndex' means that a previous lookup has failed.
      begin = match.index + match[0].length;
      continue;
    }

    const str = match[0];

    if (EMOTES.some(({ name }) => str.toUpperCase() === name)) {
      // Profit!
      return { text: str, index: match.index };
    }

    if (strictlyFromIndex && match.index >= fromIndex) {
      return null; // Since it failed and we've gone past the desired index, skip the rest.
    }

    begin = match.index + match[0].length;
  }

  return null;
}

function locateEmote(value, fromIndex) {
  const emote = findNextEmote(value, fromIndex, false);
  return emote ? emote.index : -1;
}

// Generate 'emote' markdown node
const createEmoteNode = (text) => ({
  type: EMOTE_NODE_TYPE,
  value: text,
  children: [{ type: 'text', value: text }],
});

// Generate a markdown image from emote
function tokenizeEmote(eat, value, silent) {
  if (silent) return true;

  const emote = findNextEmote(value, 0, true);
  if (emote) {
    try {
      const text = emote.text;
      return eat(text)(createEmoteNode(text));
    } catch (e) {}
  }
}

tokenizeEmote.locator = locateEmote;

export function inlineEmote() {
  const Parser = this.Parser;
  const tokenizers = Parser.prototype.inlineTokenizers;
  const methods = Parser.prototype.inlineMethods;

  // Add an inline tokenizer (defined in the following example).
  tokenizers.emote = tokenizeEmote;

  // Run it just before `text`.
  methods.splice(methods.indexOf('text'), 0, 'emote');
}

// ***************************************************************************
// Format emote
// ***************************************************************************

const transformer = (node, index, parent) => {
  if (node.type === EMOTE_NODE_TYPE && parent && parent.type === 'paragraph') {
    const emoteStr = node.value;
    const emote = EMOTES.find(({ name }) => emoteStr.toUpperCase() === name);

    node.type = 'image';
    node.url = emote.url;
    node.title = emoteStr;
    node.children = [{ type: 'text', value: emoteStr }];
    if (!node.data || !node.data.hProperties) {
      // Create new node data
      node.data = {
        hProperties: { emote: true },
      };
    } else if (node.data.hProperties) {
      // Don't overwrite current attributes
      node.data.hProperties = {
        emote: true,
        ...node.data.hProperties,
      };
    }
  }
};

const transform = (tree) => visit(tree, [EMOTE_NODE_TYPE], transformer);

export const formattedEmote = () => transform;