/**
 * Markdown toolbar
 */

import { $, $$ } from './utils/dom';

const markdownSyntax = {
  bold: {
    action: wrapSelection,
    options: { prefix: '**', shortcutKey: 'b' },
  },
  italics: {
    action: wrapSelection,
    options: { prefix: '*', shortcutKey: 'i' },
  },
  under: {
    action: wrapSelection,
    options: { prefix: '__', shortcutKey: 'u' },
  },
  spoiler: {
    action: wrapSelection,
    options: { prefix: '||', shortcutKey: 's' },
  },
  code: {
    action: wrapSelectionOrLines,
    options: {
      prefix: '`',
      suffix: '`',
      prefixMultiline: '```\n',
      suffixMultiline: '\n```',
      singleWrap: true,
      shortcutKey: 'e',
    },
  },
  strike: {
    action: wrapSelection,
    options: { prefix: '~~' },
  },
  superscript: {
    action: wrapSelection,
    options: { prefix: '^' },
  },
  subscript: {
    action: wrapSelection,
    options: { prefix: '%' },
  },
  quote: {
    action: wrapLines,
    options: { prefix: '> ' },
  },
  link: {
    action: insertLink,
    options: { shortcutKey: 'l' },
  },
  image: {
    action: insertLink,
    options: { image: true, shortcutKey: 'k' },
  },
  escape: {
    action: escapeSelection,
    options: { escapeChar: '\\' },
  },
};

function getSelections(textarea, linesOnly = false) {
  let { selectionStart, selectionEnd } = textarea,
    selection = textarea.value.substring(selectionStart, selectionEnd),
    leadingSpace = '',
    trailingSpace = '',
    caret;

  const processLinesOnly = linesOnly instanceof RegExp ? linesOnly.test(selection) : linesOnly;
  if (processLinesOnly) {
    const explorer = /\n/g;
    let startNewlineIndex = 0,
      endNewlineIndex = textarea.value.length;
    while (explorer.exec(textarea.value)) {
      const { lastIndex } = explorer;
      if (lastIndex <= selectionStart) {
        startNewlineIndex = lastIndex;
      } else if (lastIndex > selectionEnd) {
        endNewlineIndex = lastIndex - 1;
        break;
      }
    }

    selectionStart = startNewlineIndex;
    const startRemovedValue = textarea.value.substring(selectionStart);
    const startsWithBlankString = startRemovedValue.match(/^[\s\n]+/);
    if (startsWithBlankString) {
      // Offset the selection start to the first non-blank line's first non-blank character, since
      // Some browsers treat selection up to the start of the line as including the end of the
      // previous line
      selectionStart += startsWithBlankString[0].length;
    }
    selectionEnd = endNewlineIndex;
    selection = textarea.value.substring(selectionStart, selectionEnd);
  } else {
    // Deselect trailing space and line break
    for (caret = selection.length - 1; caret > 0; caret--) {
      if (selection[caret] !== ' ' && selection[caret] !== '\n') break;
      trailingSpace = selection[caret] + trailingSpace;
    }
    selection = selection.substring(0, caret + 1);

    // Deselect leading space and line break
    for (caret = 0; caret < selection.length; caret++) {
      if (selection[caret] !== ' ' && selection[caret] !== '\n') break;
      leadingSpace += selection[caret];
    }
    selection = selection.substring(caret);
  }

  return {
    processLinesOnly,
    selectedText: selection,
    beforeSelection: textarea.value.substring(0, selectionStart) + leadingSpace,
    afterSelection: trailingSpace + textarea.value.substring(selectionEnd),
  };
}

function transformSelection(textarea, transformer, eachLine) {
  const { selectedText, beforeSelection, afterSelection, processLinesOnly } = getSelections(textarea, eachLine),
    // For long comments, record scrollbar position to restore it later
    { scrollTop } = textarea;

  const { newText, caretOffset } = transformer(selectedText, processLinesOnly);

  textarea.value = beforeSelection + newText + afterSelection;

  const newSelectionStart =
    caretOffset >= 1
      ? beforeSelection.length + caretOffset
      : textarea.value.length - afterSelection.length - caretOffset;

  textarea.selectionStart = newSelectionStart;
  textarea.selectionEnd = newSelectionStart;
  textarea.scrollTop = scrollTop;
  // Needed for automatic textarea resizing
  textarea.dispatchEvent(new Event('change'));
}

function insertLink(textarea, options) {
  let hyperlink = window.prompt(options.image ? 'Image link:' : 'Link:');
  if (!hyperlink || hyperlink === '') return;

  // Change on-site link to use relative url
  if (!options.image && hyperlink.startsWith(window.location.origin)) {
    hyperlink = hyperlink.substring(window.location.origin.length);
  }

  const prefix = options.image ? '![' : '[',
    suffix = `](${hyperlink})`;

  wrapSelection(textarea, { prefix, suffix });
}

function wrapSelection(textarea, options) {
  transformSelection(textarea, selectedText => {
    const { text = selectedText, prefix = '', suffix = options.prefix } = options,
      emptyText = text === '';
    let newText = text;

    if (!emptyText) {
      newText = text.replace(/(\n{2,})/g, match => {
        return suffix + match + prefix;
      });
    }

    newText = prefix + newText + suffix;

    return {
      newText,
      caretOffset: emptyText ? prefix.length : newText.length,
    };
  });
}

function wrapLines(textarea, options, eachLine = true) {
  transformSelection(
    textarea,
    (selectedText, processLinesOnly) => {
      const { text = selectedText, singleWrap = false } = options,
        prefix = (processLinesOnly && options.prefixMultiline) || options.prefix || '',
        suffix = (processLinesOnly && options.suffixMultiline) || options.suffix || '',
        emptyText = text === '';
      let newText = singleWrap
        ? prefix + text.trim() + suffix
        : text
            .split(/\n/g)
            .map(line => prefix + line.trim() + suffix)
            .join('\n');

      // Force a space at the end of lines with only blockquote markers
      newText = newText.replace(/^((?:>\s+)*)>$/gm, '$1> ');

      return { newText, caretOffset: emptyText ? prefix.length : newText.length };
    },
    eachLine,
  );
}

function wrapSelectionOrLines(textarea, options) {
  wrapLines(textarea, options, /\n/);
}

function escapeSelection(textarea, options) {
  transformSelection(textarea, selectedText => {
    const { text = selectedText } = options,
      emptyText = text === '';

    if (emptyText) return;

    const newText = text.replace(/([*_[\]()^`%\\~<>#|])/g, '\\$1');

    return {
      newText,
      caretOffset: newText.length,
    };
  });
}

function clickHandler(event) {
  const button = event.target.closest('.communication__toolbar__button');
  if (!button) return;
  const toolbar = button.closest('.communication__toolbar'),
    // There may be multiple toolbars present on the page,
    // in the case of image pages with description edit active
    // we target the textarea that shares the same parent as the toolbar
    textarea = $('.js-toolbar-input', toolbar.parentNode),
    id = button.dataset.syntaxId;

  markdownSyntax[id].action(textarea, markdownSyntax[id].options);
  textarea.focus();
}

function canAcceptShortcut(event) {
  let ctrl, otherModifier;

  switch (window.navigator.platform) {
    case 'MacIntel':
      ctrl = event.metaKey;
      otherModifier = event.ctrlKey || event.shiftKey || event.altKey;
      break;
    default:
      ctrl = event.ctrlKey;
      otherModifier = event.metaKey || event.shiftKey || event.altKey;
      break;
  }

  return ctrl && !otherModifier;
}

function shortcutHandler(event) {
  if (!canAcceptShortcut(event)) {
    return;
  }

  const textarea = event.target,
    key = event.key.toLowerCase();

  for (const id in markdownSyntax) {
    if (key === markdownSyntax[id].options.shortcutKey) {
      markdownSyntax[id].action(textarea, markdownSyntax[id].options);
      event.preventDefault();
    }
  }
}

function setupToolbar() {
  $$('.communication__toolbar').forEach(toolbar => {
    toolbar.addEventListener('click', clickHandler);
  });
  $$('.js-toolbar-input').forEach(textarea => {
    textarea.addEventListener('keydown', shortcutHandler);
  });
}

export { setupToolbar };
