import { Mark } from 'prosemirror-model';
import React, { useContext, useState, useEffect, useMemo, useCallback } from 'react';
import { each, uniqBy, sortBy } from 'lodash';
import { WaxContext } from 'wax-prosemirror-core';
import { DocumentHelpers } from 'wax-prosemirror-utilities';
import BoxList from './BoxList';

export default ({ area }) => {
  const {
    view,
    view: { main },
    app,
    activeView,
  } = useContext(WaxContext);
  const commentPlugin = app.PmPlugins.get('commentPlugin');
  const [marksNodes, setMarksNodes] = useState([]);
  const [position, setPosition] = useState();

  const setTops = useCallback(() => {
    const result = [];
    let markNodeEl = null;
    let annotationTop = 0;
    let boxHeight = 0;
    let top = 0;
    const allCommentsTop = [];

    const nodesMarksToIterrate = marksNodes[area] === 'main' ? sortBy(marksNodes[area], ['pos']) : marksNodes[area];

    each(nodesMarksToIterrate, (markNode, pos) => {
      const WaxSurface = main.dom.getBoundingClientRect();
      const id = markNode instanceof Mark ? markNode.attrs.id : markNode.node.attrs.id;

      const activeComment = commentPlugin.getState(activeView.state).comment;

      let isActive = false;
      if (activeComment && id === activeComment.attrs.id) isActive = true;

      // annotation top
      if (area === 'main') {
        markNodeEl = document.querySelector(`[data-id="${id}"]`);
        if (markNodeEl) annotationTop = markNodeEl.getBoundingClientRect().top - WaxSurface.top;
      } else {
        const panelWrapper = document.getElementsByClassName('panelWrapper');
        const panelWrapperHeight = panelWrapper[0].getBoundingClientRect().height;
        markNodeEl = document.querySelector('#notes-container').querySelector(`[data-id="${id}"]`);
        if (markNodeEl) annotationTop = markNodeEl.getBoundingClientRect().top - panelWrapperHeight - 50;
      }

      // get height of this markNode box
      const boxEl = document.querySelector(`div[data-box="${id}"]`);
      if (boxEl) boxHeight = parseInt(boxEl.offsetHeight, 10);

      // where the box should move to
      top = annotationTop;

      // if the above comment box has already taken up the height, move down
      if (pos > 0) {
        const previousBox = marksNodes[area][pos - 1];
        const previousEndHeight = previousBox.endHeight;
        if (annotationTop < previousEndHeight) {
          top = previousEndHeight + 2;
        }
      }
      // store where the box ends to be aware of overlaps in the next box
      markNode.endHeight = top + boxHeight + 2;
      result[pos] = top;
      allCommentsTop.push({ [id]: result[pos] });

      // if active, move as many boxes above as needed to bring it to the annotation's height
      if (isActive) {
        markNode.endHeight = annotationTop + boxHeight + 2;
        result[pos] = annotationTop;
        allCommentsTop[pos][id] = result[pos];
        let b = true;
        let i = pos;

        // first one active, none above
        if (i === 0) b = false;

        while (b) {
          const boxAbove = marksNodes[area][i - 1];
          const boxAboveEnds = boxAbove.endHeight;
          const currentTop = result[i];

          const doesOverlap = boxAboveEnds > currentTop;

          if (doesOverlap) {
            const overlap = boxAboveEnds - currentTop;
            result[i - 1] -= overlap;

            const previousMarkNode =
              marksNodes[area][i - 1] instanceof Mark
                ? marksNodes[area][i - 1].attrs.id
                : marksNodes[area][i - 1].node.attrs.id;

            allCommentsTop[i - 1][previousMarkNode] = result[i - 1];
          }

          if (!doesOverlap) b = false;
          if (i <= 1) b = false;
          i -= 1;
        }
      }
    });

    return allCommentsTop;
  });

  useEffect(() => {
    setMarksNodes(updateMarks(view));
    setPosition(setTops());
  }, [JSON.stringify(updateMarks(view)), JSON.stringify(setTops())]);

  const CommentTrackComponent = useMemo(
    () => <BoxList commentsTracks={marksNodes[area] || []} area={area} view={main} position={position} />,
    [marksNodes[area] || [], position],
  );
  return <>{CommentTrackComponent}</>;
};

//  TODO if allInlineNodes and allBlockNodes count don't change, do not compute again
const updateMarks = view => {
  if (view.main) {
    const allInlineNodes = [];

    Object.keys(view).forEach(eachView => {
      allInlineNodes.push(...DocumentHelpers.findInlineNodes(view[eachView].state.doc));
    });

    const allBlockNodes = DocumentHelpers.findBlockNodes(view.main.state.doc);
    const finalMarks = [];
    const finalNodes = [];

    allInlineNodes.map(node => {
      if (node.node.marks.length > 0) {
        node.node.marks.filter(mark => {
          if (
            mark.type.name === 'comment' ||
            mark.type.name === 'insertion' ||
            mark.type.name === 'deletion' ||
            mark.type.name === 'format_change'
          ) {
            mark.pos = node.pos;
            finalMarks.push(mark);
          }
        });
      }
    });

    allBlockNodes.map(node => {
      if (node.node.attrs.track && node.node.attrs.track.length > 0) {
        finalNodes.push(node);
      }
    });

    const nodesAndMarks = [...uniqBy(finalMarks, 'attrs.id'), ...finalNodes];

    const groupedMarkNodes = {};

    nodesAndMarks.forEach(markNode => {
      const markNodeAttrs = markNode.attrs ? markNode.attrs : markNode.node.attrs;

      if (!groupedMarkNodes[markNodeAttrs.group]) {
        groupedMarkNodes[markNodeAttrs.group] = [markNode];
      } else {
        groupedMarkNodes[markNodeAttrs.group].push(markNode);
      }
    });
    return groupedMarkNodes;
  }
  return [];
};