import { Arr } from '@ephox/katamari';

import * as NodeType from '../dom/NodeType';
import * as Zwsp from '../text/Zwsp';

import * as CaretContainer from './CaretContainer';
import CaretPosition from './CaretPosition';

interface TrimCount {
  readonly count: number;
  readonly text: string;
}

const isElement = NodeType.isElement;
const isText = NodeType.isText;

const removeNode = (node: Node): void => {
  const parentNode = node.parentNode;
  if (parentNode) {
    parentNode.removeChild(node);
  }
};

const trimCount = (text: string): TrimCount => {
  const trimmedText = Zwsp.trim(text);
  return {
    count: text.length - trimmedText.length,
    text: trimmedText
  };
};

const deleteZwspChars = (caretContainer: Text): void => {
  // We use the Text.deleteData API here so as to preserve selection offsets
  let idx: number;
  while ((idx = caretContainer.data.lastIndexOf(Zwsp.ZWSP)) !== -1) {
    caretContainer.deleteData(idx, 1);
  }
};

const removeUnchanged = (caretContainer: Node, pos: CaretPosition): CaretPosition => {
  remove(caretContainer);
  return pos;
};

const removeTextAndReposition = (caretContainer: Text, pos: CaretPosition): CaretPosition => {
  const before = trimCount(caretContainer.data.substr(0, pos.offset()));
  const after = trimCount(caretContainer.data.substr(pos.offset()));
  const text = before.text + after.text;

  if (text.length > 0) {
    deleteZwspChars(caretContainer);
    return CaretPosition(caretContainer, pos.offset() - before.count);
  } else {
    return pos;
  }
};

const removeElementAndReposition = (caretContainer: Node, pos: CaretPosition): CaretPosition => {
  const parentNode = pos.container();
  const newPosition = Arr.indexOf(Arr.from(parentNode.childNodes), caretContainer).map((index) => {
    return index < pos.offset() ? CaretPosition(parentNode, pos.offset() - 1) : pos;
  }).getOr(pos);
  remove(caretContainer);
  return newPosition;
};

const removeTextCaretContainer = (caretContainer: Node, pos: CaretPosition): CaretPosition =>
  isText(caretContainer) && pos.container() === caretContainer ? removeTextAndReposition(caretContainer, pos) : removeUnchanged(caretContainer, pos);

const removeElementCaretContainer = (caretContainer: Node, pos: CaretPosition): CaretPosition =>
  pos.container() === caretContainer.parentNode ? removeElementAndReposition(caretContainer, pos) : removeUnchanged(caretContainer, pos);

const removeAndReposition = (container: Node, pos: CaretPosition): CaretPosition =>
  CaretPosition.isTextPosition(pos) ? removeTextCaretContainer(container, pos) : removeElementCaretContainer(container, pos);

const remove = (caretContainerNode: Node | null): void => {
  if (isElement(caretContainerNode) && CaretContainer.isCaretContainer(caretContainerNode)) {
    if (CaretContainer.hasContent(caretContainerNode)) {
      caretContainerNode.removeAttribute('data-mce-caret');
    } else {
      removeNode(caretContainerNode);
    }
  }

  if (isText(caretContainerNode)) {
    deleteZwspChars(caretContainerNode);
    if (caretContainerNode.data.length === 0) {
      removeNode(caretContainerNode);
    }
  }
};

export {
  removeAndReposition,
  remove
};
