import { AllSelection, NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
import { hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
import { closest } from './dom';

/**
 * Checks if node is an empty paragraph.
 */
export function isEmptyParagraph(node) {
  return !!node && node.type.name === 'paragraph' && !node.childCount;
}
export const stepHasSlice = step => step && step.hasOwnProperty('slice');

/**
 * Checks whether a given step is adding nodes of given nodeTypes
 *
 * @param step Step to check
 * @param nodeTypes NodeTypes being added
 */
export function stepAddsOneOf(step, nodeTypes) {
  let adds = false;
  if (!stepHasSlice(step)) {
    return adds;
  }
  step.slice.content.descendants(node => {
    if (nodeTypes.has(node.type)) {
      adds = true;
    }
    return !adds;
  });
  return adds;
}
export const extractSliceFromStep = step => {
  if (!(step instanceof ReplaceStep) && !(step instanceof ReplaceAroundStep)) {
    return null;
  }

  // @ts-ignore This is by design. Slice is a private property, but accesible, from ReplaceStep.
  // However, we need to read it to found if the step was adding a newline
  const slice = step.slice;
  return slice;
};
export const isTextSelection = selection => selection instanceof TextSelection;
export const isElementInTableCell = element => {
  return closest(element, 'td') || closest(element, 'th');
};
export const isLastItemMediaGroup = node => {
  const {
    content
  } = node;
  return !!content.lastChild && content.lastChild.type.name === 'mediaGroup';
};
export const setNodeSelection = (view, pos) => {
  const {
    state,
    dispatch
  } = view;
  if (!isFinite(pos)) {
    return;
  }
  const tr = state.tr.setSelection(NodeSelection.create(state.doc, pos));
  dispatch(tr);
};
export function setTextSelection(view, anchor, head) {
  const {
    state,
    dispatch
  } = view;
  const tr = state.tr.setSelection(TextSelection.create(state.doc, anchor, head));
  dispatch(tr);
}
export function setAllSelection(view) {
  const {
    state,
    dispatch
  } = view;
  const tr = state.tr.setSelection(new AllSelection(view.state.doc));
  dispatch(tr);
}
export function setCellSelection(view, anchor, head) {
  const {
    state,
    dispatch
  } = view;
  dispatch(state.tr.setSelection(CellSelection.create(state.doc, anchor, head)));
}
export function nonNullable(value) {
  return value !== null && value !== undefined;
}

// checks if the given position is within the ProseMirror document
export const isValidPosition = (pos, state) => {
  if (typeof pos !== 'number') {
    return false;
  }
  if (pos >= 0 && pos <= state.doc.resolve(0).end()) {
    return true;
  }
  return false;
};
export const isInLayoutColumn = state => {
  return hasParentNodeOfType(state.schema.nodes.layoutSection)(state.selection);
};
export function filterChildrenBetween(doc, from, to, predicate) {
  const results = [];
  doc.nodesBetween(from, to, (node, pos, parent) => {
    if (predicate(node, pos, parent)) {
      results.push({
        node,
        pos
      });
    }
  });
  return results;
}
export const removeBlockMarks = (state, marks) => {
  const {
    selection,
    schema
  } = state;
  let {
    tr
  } = state;

  // Marks might not exist in Schema
  const marksToRemove = marks.filter(Boolean);
  if (marksToRemove.length === 0) {
    return undefined;
  }

  /** Saves an extra dispatch */
  let blockMarksExists = false;
  const hasMark = mark => marksToRemove.indexOf(mark.type) > -1;
  /**
   * When you need to toggle the selection
   * when another type which does not allow alignment is applied
   */
  state.doc.nodesBetween(selection.from, selection.to, (node, pos) => {
    if (node.type === schema.nodes.paragraph && node.marks.some(hasMark)) {
      blockMarksExists = true;
      const resolvedPos = state.doc.resolve(pos);
      const withoutBlockMarks = node.marks.filter(not(hasMark));
      tr = tr.setNodeMarkup(resolvedPos.pos, undefined, node.attrs, withoutBlockMarks);
    }
  });
  return blockMarksExists ? tr : undefined;
};
const not = fn => arg => !fn(arg);