import {
  fork,
  put,
  take,
  call,
  spawn,
  takeEvery,
  select
} from "redux-saga/effects";
import { eventChannel } from "redux-saga";
import { mapKeys } from "lodash";
import {
  FILE_ITEM_RIGHT_CLICKED,
  FileItemRightClicked,
  fileItemContextMenuDeleteClicked,
  fileItemContextMenuCopyPathClicked,
  fileItemContextMenuOpenClicked,
  fileItemContextMenuRenameClicked,
  CANVAS_RIGHT_CLICKED,
  PCLayerRightClicked,
  syntheticNodeContextMenuWrapInSlotClicked,
  syntheticNodeContextMenuSelectParentClicked,
  syntheticNodeContextMenuSelectSourceNodeClicked,
  syntheticNodeContextMenuConvertToComponentClicked,
  syntheticNodeContextMenuWrapInElementClicked,
  syntheticNodeContextMenuConvertToStyleMixinClicked,
  syntheticNodeContextMenuRemoveClicked,
  EDITOR_TAB_RIGHT_CLICKED,
  EditorTabClicked,
  syntheticNodeContextMenuConvertTextStylesToMixinClicked,
  syntheticNodeContextMenuRenameClicked,
  syntheticNodeContextMenuShowInCanvasClicked,
  moduleContextMenuCloseOptionClicked,
  PC_LAYER_RIGHT_CLICKED,
  editorTabContextMenuOpenInBottomTabOptionClicked,
  syntheticNodeContextMenuCopyClicked,
  syntheticNodeContextMenuPasteClicked
} from "../actions";
import {
  ContextMenuItem,
  ContextMenuOptionType,
  ContextMenuOption,
  RootState,
  getCanvasMouseTargetNodeIdFromPoint,
  getCanvasMouseTargetNodeId
} from "../state";
import { Point, FSItemTagNames, EMPTY_OBJECT } from "tandem-common";
import {
  getSyntheticNodeById,
  getSyntheticSourceNode,
  PCSourceTagNames,
  getPCNodeContentNode,
  getSyntheticInspectorNode,
  getSyntheticVisibleNodeDocument,
  syntheticNodeIsInShadow,
  getPCNodeModule,
  SyntheticNode,
  getInspectorSyntheticNode,
  inspectorNodeInShadow,
  extendsComponent,
  hasTextStyles,
  getInspectorContentNode,
  SYNTHETIC_DOCUMENT_NODE_NAME,
  getSyntheticDocumentDependencyUri,
  SyntheticDocument
} from "paperclip";

export type ShortcutSagaOptions = {
  openContextMenu: (anchor: Point, options: ContextMenuOption[]) => void;
};

type OpenSyntheticNodeContextMenuOptions = {
  showRenameLabelOption?: boolean;
};

export const createShortcutSaga = ({
  openContextMenu
}: ShortcutSagaOptions) => {
  return function*() {
    yield takeEvery(
      FILE_ITEM_RIGHT_CLICKED,
      function* handleFileItemRightClick({
        event,
        item
      }: FileItemRightClicked) {
        yield call(
          openContextMenu,
          {
            left: event.pageX,
            top: event.pageY
          },
          [
            {
              type: ContextMenuOptionType.GROUP,
              options: [
                {
                  type: ContextMenuOptionType.ITEM,
                  label: "Copy Path",
                  action: fileItemContextMenuCopyPathClicked(item)
                },
                {
                  type: ContextMenuOptionType.ITEM,
                  label:
                    item.name === FSItemTagNames.DIRECTORY
                      ? "Open in Finder"
                      : "Open in Text Editor",
                  action: fileItemContextMenuOpenClicked(item)
                }
              ] as ContextMenuItem[]
            },
            {
              type: ContextMenuOptionType.GROUP,
              options: [
                {
                  type: ContextMenuOptionType.ITEM,
                  label: "Rename",
                  action: fileItemContextMenuRenameClicked(item)
                },
                {
                  type: ContextMenuOptionType.ITEM,
                  label: "Delete",
                  action: fileItemContextMenuDeleteClicked(item)
                }
              ]
            }
          ]
        );
      }
    );

    yield takeEvery(
      EDITOR_TAB_RIGHT_CLICKED,
      function* handleEditorRightClicked({ event, uri }: EditorTabClicked) {
        yield call(
          handleModuleRightClicked,
          {
            left: event.pageX,
            top: event.pageY
          },
          uri
        );
      }
    );

    function* handleModuleRightClicked(point: Point, uri: string) {
      yield call(openContextMenu, point, [
        {
          type: ContextMenuOptionType.ITEM,
          label: "Close",
          action: moduleContextMenuCloseOptionClicked(uri)
        },
        {
          type: ContextMenuOptionType.ITEM,
          label: "Open in Bottom Tab",
          action: editorTabContextMenuOpenInBottomTabOptionClicked(uri)
        }
      ]);
    }

    yield takeEvery(CANVAS_RIGHT_CLICKED, function* handleFileItemRightClick({
      event,
      item
    }: FileItemRightClicked) {
      const state: RootState = yield select();
      const targetNodeId = getCanvasMouseTargetNodeId(state, event);
      if (targetNodeId) {
        yield call(
          openCanvasSyntheticNodeContextMenu,
          targetNodeId,
          event,
          state
        );
      }
    });

    function* openCanvasSyntheticNodeContextMenu(
      targetNodeId: string,
      event: React.MouseEvent<any>,
      state: RootState
    ) {
      const ownerWindow = (event.nativeEvent.target as HTMLDivElement)
        .ownerDocument.defaultView;
      const parent = ownerWindow.top;
      const ownerIframe = Array.from(
        parent.document.querySelectorAll("iframe")
      ).find((iframe: HTMLIFrameElement) => {
        return iframe.contentDocument === ownerWindow.document;
      });

      const rect = ownerIframe.getBoundingClientRect();

      yield call(
        openSyntheticNodeContextMenu,
        getSyntheticNodeById(targetNodeId, state.documents),
        {
          left: event.pageX + rect.left,
          top: event.pageY + rect.top
        },
        state
      );
    }

    yield takeEvery(PC_LAYER_RIGHT_CLICKED, function* handleFileItemRightClick({
      event,
      item
    }: PCLayerRightClicked) {
      // this will happen for
      if (!item) {
        console.warn(
          `ModuleContextMenuOptionClicked dispatched without an inspectorNode`
        );
        return;
      }

      const state: RootState = yield select();
      const node = getInspectorSyntheticNode(item, state.documents);

      // maybe shadow
      if (!node) {
        return;
      }

      yield call(
        openSyntheticNodeContextMenu,
        node,
        {
          left: event.pageX,
          top: event.pageY
        },
        state,
        { showRenameLabelOption: true }
      );
    });

    function* openSyntheticNodeContextMenu(
      node: SyntheticNode,
      point: Point,
      state: RootState,
      {
        showRenameLabelOption
      }: OpenSyntheticNodeContextMenuOptions = EMPTY_OBJECT
    ) {
      // TODO - need to have options here for handling document: close, move to bottom tab
      if (node.name === SYNTHETIC_DOCUMENT_NODE_NAME) {
        console.warn(`Cannot open context menu for documents (yet)`);
        return yield call(
          handleModuleRightClicked,
          point,
          getSyntheticDocumentDependencyUri(
            node as SyntheticDocument,
            state.graph
          )
        );
        return;
      }

      const syntheticNode = getSyntheticNodeById(node.id, state.documents);
      const sourceNode = getSyntheticSourceNode(syntheticNode, state.graph);
      const syntheticDocument = getSyntheticVisibleNodeDocument(
        syntheticNode.id,
        state.documents
      );
      const inspectorNode = getSyntheticInspectorNode(
        syntheticNode,
        syntheticDocument,
        state.sourceNodeInspector,
        state.graph
      );
      const contentNode = getPCNodeContentNode(
        sourceNode.id,
        getPCNodeModule(sourceNode.id, state.graph)
      );

      const inspectorContentNode = getInspectorContentNode(
        inspectorNode,
        state.sourceNodeInspector
      );

      yield call(openContextMenu, point, [
        syntheticNodeIsInShadow(syntheticNode, syntheticDocument, state.graph)
          ? {
              type: ContextMenuOptionType.ITEM,
              label: "Hide",
              action: syntheticNodeContextMenuRemoveClicked(syntheticNode)
            }
          : {
              type: ContextMenuOptionType.GROUP,
              options: [
                showRenameLabelOption
                  ? {
                      type: ContextMenuOptionType.ITEM,
                      label: "Rename",
                      action: syntheticNodeContextMenuRenameClicked(
                        syntheticNode
                      )
                    }
                  : null,
                {
                  type: ContextMenuOptionType.ITEM,
                  label: "Remove",
                  action: syntheticNodeContextMenuRemoveClicked(syntheticNode)
                },
                {
                  type: ContextMenuOptionType.ITEM,
                  label: "Copy",
                  action: syntheticNodeContextMenuCopyClicked(syntheticNode)
                },
                {
                  type: ContextMenuOptionType.ITEM,
                  label: "Paste",
                  action: syntheticNodeContextMenuPasteClicked(syntheticNode)
                },
                sourceNode.name !== PCSourceTagNames.COMPONENT &&
                !inspectorNodeInShadow(inspectorNode, state.sourceNodeInspector)
                  ? {
                      type: ContextMenuOptionType.ITEM,
                      label: "Convert to Component",
                      action: syntheticNodeContextMenuConvertToComponentClicked(
                        syntheticNode
                      )
                    }
                  : null,

                sourceNode.name !== PCSourceTagNames.COMPONENT &&
                !inspectorNodeInShadow(inspectorNode, state.sourceNodeInspector)
                  ? {
                      type: ContextMenuOptionType.ITEM,
                      label: "Wrap in Element",
                      action: syntheticNodeContextMenuWrapInElementClicked(
                        syntheticNode
                      )
                    }
                  : null,
                contentNode.name === PCSourceTagNames.COMPONENT &&
                contentNode.id !== sourceNode.id &&
                !inspectorNodeInShadow(inspectorNode, state.sourceNodeInspector)
                  ? {
                      type: ContextMenuOptionType.ITEM,
                      label: "Wrap in Slot",
                      action: syntheticNodeContextMenuWrapInSlotClicked(
                        syntheticNode
                      )
                    }
                  : null,

                sourceNode.name === PCSourceTagNames.COMPONENT ||
                sourceNode.name === PCSourceTagNames.COMPONENT_INSTANCE ||
                sourceNode.name === PCSourceTagNames.ELEMENT ||
                sourceNode.name === PCSourceTagNames.TEXT
                  ? {
                      type: ContextMenuOptionType.ITEM,
                      label: "Move All Styles to Mixin",
                      action: syntheticNodeContextMenuConvertToStyleMixinClicked(
                        syntheticNode
                      )
                    }
                  : null,

                (sourceNode.name === PCSourceTagNames.COMPONENT ||
                  sourceNode.name === PCSourceTagNames.COMPONENT_INSTANCE ||
                  sourceNode.name === PCSourceTagNames.ELEMENT ||
                  sourceNode.name === PCSourceTagNames.TEXT) &&
                hasTextStyles(
                  inspectorNode,
                  state.sourceNodeInspector,
                  state.selectedVariant,
                  state.graph
                )
                  ? {
                      type: ContextMenuOptionType.ITEM,
                      label: "Move Text Styles to Mixin",
                      action: syntheticNodeContextMenuConvertTextStylesToMixinClicked(
                        syntheticNode
                      )
                    }
                  : null
              ].filter(Boolean) as ContextMenuItem[]
            },
        {
          type: ContextMenuOptionType.GROUP,
          options: [
            contentNode.id !== sourceNode.id
              ? {
                  type: ContextMenuOptionType.ITEM,
                  label: "Select Parent",
                  action: syntheticNodeContextMenuSelectParentClicked(
                    syntheticNode
                  )
                }
              : null,
            inspectorNodeInShadow(inspectorNode, inspectorContentNode) ||
            extendsComponent(sourceNode)
              ? {
                  type: ContextMenuOptionType.ITEM,
                  label: "Select Source Layer",
                  action: syntheticNodeContextMenuSelectSourceNodeClicked(
                    syntheticNode
                  )
                }
              : null,
            {
              type: ContextMenuOptionType.ITEM,
              label: "Center in Canvas",
              action: syntheticNodeContextMenuShowInCanvasClicked(syntheticNode)
            }
          ].filter(Boolean) as ContextMenuItem[]
        }
      ].filter(Boolean) as ContextMenuOption[]);
    }
  };
};

export function* shortcutSaga() {
  // yield fork(handleFileItemRightClick);
  // yield fork(mapHotkeys({
  //   // artboard
  //   "a": wrapDispatch(SHORTCUT_A_KEY_DOWN),
  //   // rectangle
  //   "r": wrapDispatch(SHORTCUT_R_KEY_DOWN),
  //   // text
  //   "t": wrapDispatch(SHORTCUT_T_KEY_DOWN),
  //   // artboard
  //   "escape": wrapDispatch(SHORTCUT_ESCAPE_KEY_DOWN),
  //   // artboard
  //   "backspace": wrapDispatch(SHORTCUT_DELETE_KEY_DOWN)
  // }));
}

const wrapDispatch = (type: string) =>
  function*(sourceEvent) {
    // yield put(shortcutKeyDown(type));
  };

const mapHotkeys = (map: {
  [identifier: string]: (event: KeyboardEvent) => any;
}) =>
  function*() {
    const ordererdMap = mapKeys(map, (value: any, key: string) =>
      key
        .split(" ")
        .sort()
        .join(" ")
    );
    const keysDown: string[] = [];

    const chan = yield eventChannel(emit => {
      document.addEventListener("keydown", (event: KeyboardEvent) => {
        if (keysDown.indexOf(event.key) === -1) {
          keysDown.push(event.key);
        }
        const handler =
          ordererdMap[
            keysDown
              .join(" ")
              .toLocaleLowerCase()
              .split(" ")
              .sort()
              .join(" ")
          ];
        if (handler) {
          emit(call(handler, event));
        }
      });

      document.addEventListener("keyup", (event: KeyboardEvent) => {
        keysDown.splice(keysDown.indexOf(event.key), 1);
      });
      return () => {};
    });

    while (1) {
      const action = yield take(chan);
      yield spawn(function*() {
        yield action;
      });
    }
  };
