import { Plugin } from "prosemirror-state";
import React from "react";
import { getPluginStateOrThrow as getRangePluginStateOrThrow } from "../range/RangePlugin";
import { HighlightRange, RangeType } from "../range/types";
import { EditorSchema } from "../schema";
import { getPluginStateOrThrow as getHighlightWidgetPluginStateOrThrow } from "./HighlightWidgetPlugin";
import { el, isMouseMove } from "../util/dom";
import { PluginDescriptor } from "../util/PluginDescriptor";
import { PortalProviderApi } from "../util/PortalProvider";
import { HighlightHover } from "./react/HighlightHover";
import { ITag } from "@heydovetail/ui-components/lib.es2015/components/Highlighter";

export interface PluginState {
  readonly container: HTMLDivElement;
  readonly timeout: NodeJS.Timer | null;
  readonly mousePosition: { x: number; y: number } | null;
}

function pluginStateEq(a: PluginState, b: PluginState) {
  return JSON.stringify(a) === JSON.stringify(b);
}

const { key, getPluginState, getPluginStateOrThrow, setPluginState } = new PluginDescriptor<PluginState>(
  "HighlightHoverPlugin"
);

export class HighlightHoverPlugin extends Plugin<EditorSchema> {
  constructor(portalProviderApi: PortalProviderApi) {
    super({
      key,

      state: {
        init: (): PluginState => ({
          container: el("div"),
          mousePosition: null,
          timeout: null
        }),
        apply: (tr, curr: PluginState): PluginState => {
          const nextPluginState = getPluginState(tr);
          return nextPluginState !== null ? nextPluginState : curr;
        }
      },

      view: view => {
        const initialPluginState = getPluginStateOrThrow(view.state);
        const container = initialPluginState.container;

        if (view.dom.parentElement !== null) {
          view.dom.parentElement.insertBefore(container, view.dom.parentElement.firstChild);
        }

        return {
          update: (view, prevState) => {
            const prevPluginState = getPluginStateOrThrow(prevState);
            const newPluginState = getPluginStateOrThrow(view.state);

            if (!pluginStateEq(prevPluginState, newPluginState)) {
              const { mousePosition } = newPluginState;

              const { widgetState, tagLookup } = getHighlightWidgetPluginStateOrThrow(view.state);
              const rangeState = getRangePluginStateOrThrow(view.state);

              if (mousePosition !== null && view.dom.parentElement !== null) {
                const position = view.posAtCoords({ left: mousePosition.x, top: mousePosition.y });
                if (
                  position != null &&
                  (widgetState.type !== "tagging" ||
                    (position.pos < widgetState.selection.from || position.pos > widgetState.selection.to))
                ) {
                  const anchorPosition = {
                    x: mousePosition.x - view.dom.parentElement.getBoundingClientRect().left,
                    y: mousePosition.y - view.dom.parentElement.getBoundingClientRect().top
                  };

                  const tagIds = rangeState.sortedRanges
                    .filter<HighlightRange>(
                      (range): range is HighlightRange =>
                        position.pos >= range.from && position.pos <= range.to && range.type === RangeType.HIGHLIGHT
                    )
                    .reduce<string[]>((accum, prev) => [...accum, ...prev.attrs.tagIds], []);

                  portalProviderApi.render(
                    <HighlightHover
                      anchorPosition={anchorPosition}
                      tags={tagIds.map(id => tagLookup.get(id)).filter((tag): tag is ITag => tag !== undefined)}
                    />,
                    container
                  );
                } else {
                  portalProviderApi.destroy(container);
                }
              } else {
                portalProviderApi.destroy(container);
              }
            }
          },
          destroy: () => {
            if (container.parentElement !== null) {
              container.parentElement.removeChild(container);
            }
          }
        };
      },

      props: {
        handleDOMEvents: {
          mousemove: (view, event) => {
            if (isMouseMove(event)) {
              const prevPluginState = getPluginStateOrThrow(view.state);

              if (prevPluginState.timeout !== null) {
                clearTimeout(prevPluginState.timeout);
              }

              setPluginState(view, prevPluginState => ({
                ...prevPluginState,
                mousePosition: null,
                timeout: setTimeout(() => {
                  setPluginState(view, prevPluginState => ({
                    ...prevPluginState,
                    mousePosition: {
                      x: event.clientX,
                      y: event.clientY
                    }
                  }));
                }, 300)
              }));
            }
            return false;
          },
          mouseout: view => {
            const prevPluginState = getPluginStateOrThrow(view.state);

            if (prevPluginState.timeout !== null) {
              clearTimeout(prevPluginState.timeout);
            }

            setPluginState(view, prevPluginState => ({
              ...prevPluginState,
              mousePosition: null,
              timeout: null
            }));
            return false;
          }
        }
      }
    });
  }
}
