import { Plugin } from "prosemirror-state";
import React from "react";
import { style } from "typestyle-react";
import { UxCommand } from "../constants";
import { EditorSchema, schema } from "../schema";
import { el } from "../util/dom";
import { PluginDescriptor } from "../util/PluginDescriptor";
import { PortalProviderApi } from "../util/PortalProvider";
import { uxCommands } from "../uxCommands";
import { isBqActive, isHActive, isMarkActive, isOlActive, isUlActive } from "./isActive";
import { ToolbarStalker } from "./react/ToolbarStalker";

export interface PluginState {
  readonly container: HTMLDivElement;
  readonly active: {
    readonly b: boolean;
    readonly bq: boolean;
    readonly h1: boolean;
    readonly h2: boolean;
    readonly i: boolean;
    readonly ol: boolean;
    readonly s: boolean;
    readonly u: boolean;
    readonly ul: boolean;
  };
}

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

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

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

      state: {
        init: (): PluginState => ({
          container: el(
            "div",
            // Elements that are `position:sticky` are only free to move around
            // within their parent. This means that if we want the toolbar to
            // stalk the full height of the editor, a simple approach is to make
            // it a sibling to the editor content.
            //
            // 56px is the height of the (sticky) NoteHeader in Dovetail
            // 32px is the gap we want between the NoteHeader and the toolbar
            // Thus 56 + 32 = 88
            style({ position: ["-webkit-sticky", "sticky"], top: 88 })
          ),
          active: {
            b: false,
            bq: false,
            h1: false,
            h2: false,
            i: false,
            ol: false,
            s: false,
            u: false,
            ul: false
          }
        }),
        apply(tr, cur: PluginState, _oldState, newState): PluginState {
          const nextPluginState = getPluginState(tr);
          return nextPluginState !== null
            ? nextPluginState
            : tr.docChanged || tr.selectionSet
              ? {
                  ...cur,
                  active: {
                    b: isMarkActive(newState, schema.marks.b),
                    bq: isBqActive(newState),
                    h1: isHActive(newState, 1),
                    h2: isHActive(newState, 2),
                    i: isMarkActive(newState, schema.marks.i),
                    ol: isOlActive(newState),
                    s: isMarkActive(newState, schema.marks.s),
                    u: isMarkActive(newState, schema.marks.u),
                    ul: isUlActive(newState)
                  }
                }
              : cur;
        }
      },

      view: view => {
        const initialPluginState = getPluginStateOrThrow(view.state);
        const container = initialPluginState.container;
        const handleUxCommand = (uxCommand: UxCommand) => uxCommands[uxCommand](view.state, tr => view.dispatch(tr));

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

        if (view.someProp("editable") == null || view.someProp("editable")(view.state)) {
          portalProviderApi.render(
            <ToolbarStalker onUxCommand={handleUxCommand} pluginState={initialPluginState} />,
            container
          );
        }

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

            if (
              (view.someProp("editable") == null || view.someProp("editable")(view.state)) &&
              !pluginStateEq(prevPluginState, newPluginState)
            ) {
              portalProviderApi.render(
                <ToolbarStalker pluginState={newPluginState} onUxCommand={handleUxCommand} />,
                container
              );
            }
          },
          destroy: () => {
            if (container.parentElement !== null) {
              container.parentElement.removeChild(container);
            }
            portalProviderApi.destroy(container);
          }
        };
      }
    });
  }
}
