import styled from "@emotion/styled";
import html2canvas from "html2canvas";
import { inject, observer } from "mobx-react";
import React, { useCallback, useEffect, useRef } from "react";
import { useMutation } from "react-apollo";
import { v4 } from "uuid";
import { LIGHT_SECONDARY_FIVE } from "../../../shared/colors";
import {
  SlideAirtable,
  SlideBoard,
  SlideComment,
  SlideConnection,
  SlideIdea,
  SlideObjectType,
  SlideRectangle,
  SlideText,
} from "../../../shared/types";
import { CreateFrame, CurrentUser_me } from "../../graphql/generated/types";
import { CREATE_FRAME } from "../../graphql/mutations";
import { SocketActions, StoreProps } from "../../platform/SlideshowStore";
import withPlatform, { PlatformProps } from "../../platform/withPlatform";
import {
  CommandLineModes,
  CommandLineSource,
  CommandPriority,
  ProwessCommands,
  useCommandLineContext,
} from "../../providers/CommandLineProvider";
import MouseList from "../mouse/MouseList";
import Airtable from "./Airtable";
import Board from "./Board";
import {
  canvasStyleDictionary,
  setStyle,
  setUpNonRenderStyles,
  snapToFrame,
  snapToItem,
} from "./canvasStyle";
import {
  clamp,
  INTERNAL_CANVAS_HEIGHT,
  INTERNAL_CANVAS_WIDTH,
} from "./canvasUtils";
import Comment from "./Comment";
import Connection from "./Connection";
import Idea from "./Idea";
import PendingConnection from "./PendingConnection";
import Rectangle from "./Rectangle";
import SelectRectangle from "./SelectRectangle";
import { setGlobalStyle } from "./style";
import Text from "./Text";
import {
  addWindowListeners,
  currentKeys,
  CurrentScreen,
  deleteConnections,
  getCanvasPosition,
  removeStore,
  removeWindowListeners,
  setStore,
} from "./windowListeners";

type Props = {
  user: CurrentUser_me;
} & PlatformProps &
  StoreProps;

function Canvas(props: Props) {
  const containerRef = useRef<HTMLDivElement>(null);
  const canvasRef = useRef<HTMLDivElement>(null);
  const currentSlide = props.store.slides.get(props.store.currentSlide.get()!)!;
  const {
    isVisible,
    toggle,
    addCommands,
    setPrompt,
    setIsVisible,
    addOptions,
    setMode,
    setPromptCallback,
    setPlaceholder,
    clearCommandsFromSource,
  } = useCommandLineContext();

  const [createFrame] = useMutation<CreateFrame>(CREATE_FRAME);

  const commands = [
    {
      id: ProwessCommands.CREATE_FRAME,
      priority: CommandPriority.LOW,
      source: CommandLineSource.CANVAS,
      title: "Create Frame",
      shortcut: "f",
      onSelect: () => {
        setPrompt("Add a view name");
        setIsVisible(true);
        setMode(CommandLineModes.PROMPT);
        function promptCallback() {
          return async (prompt: string) => {
            if (!prompt) {
              resetCommands();
              return null;
            }
            setIsVisible(false);
            setPlaceholder("Start typing...");
            const containerBox = containerRef.current!.getBoundingClientRect();

            const centerTop =
              canvasStyleDictionary[currentSlide.id].top -
              containerBox.height / 2;
            const centerLeft =
              canvasStyleDictionary[currentSlide.id].left -
              containerBox.width / 2;

            await createFrame({
              variables: {
                input: {
                  title: prompt,
                  top: centerTop,
                  left: centerLeft,
                  scale: canvasStyleDictionary[currentSlide.id].scale,
                  slideshowId: props.store.roomId.get()!,
                  slideId: currentSlide.id,
                },
              },
              /*
              TODO: This doesn't work anymore because it causes the parent component
              wrapping the mobx store to update, which changes the store.
              update: (cache, result) => {
                const frame = result!.data!.createFrame!;
                const currentSlideshowId = props.store.roomId.get()!;
                const currentSlideshow = props.user.team.slideshows.find(
                  (slideshow) => slideshow.id === currentSlideshowId
                  )!;
                  
                  const oldData = cache.readQuery<CurrentUser>({
                    query: CURRENT_USER,
                  });
                  const newData = produce(oldData, (draft) => {
                    const slideshow = draft!.me!.team.slideshows.find(
                      (slideshow) => slideshow.id === currentSlideshowId
                      )!;

                      slideshow.frames = [...slideshow.frames, frame];
                    });

                    cache.writeQuery({
                  query: CURRENT_USER,
                  data: newData,
                });
              },
              */
            }).catch((err) => console.log(err));
            resetCommands();
            return null;
          };
        }
        setPromptCallback(promptCallback);
        setPlaceholder("Your frame name");
        setIsVisible(true);
      },
      onCancel: () => resetCommands(),
    },
    {
      id: ProwessCommands.GOTO_FRAME,
      priority: CommandPriority.LOW,
      source: CommandLineSource.CANVAS,
      title: "Snap to Frame",
      shortcut: "g",
      onSelect: () => {
        setPrompt("Choose a frame");
        setPlaceholder("Frame name");
        setIsVisible(true);
        setMode(CommandLineModes.OPTION);
        const currentSlideshowId = props.store.roomId.get()!;
        const currentSlideshow = props.user.team.slideshows.find(
          (slideshow) => slideshow.id === currentSlideshowId
        )!;
        const currentSlideId = props.store.currentSlide.get()!;
        const containerBox = containerRef.current!.getBoundingClientRect();
        addOptions([
          ...currentSlideshow.frames.map((frame) => {
            return {
              id: frame.id,
              title: frame.title,
              type: CommandLineModes.OPTION,
              source: CommandLineSource.CANVAS,
              priority: CommandPriority.HIGH,
              onSelect: () => {
                snapToFrame(frame, containerBox);
                setIsVisible(false);
                resetCommands();
              },
              onCancel: () => resetCommands(),
            };
          }),
        ]);
      },
      onCancel: () => resetCommands(),
    },
  ];

  const resetCommands = () => {
    setMode(CommandLineModes.COMMAND);
    clearCommandsFromSource(CommandLineSource.CANVAS);
    // re-add top-level commands
    addCommands(commands);
    setPrompt("Prowess Command");
    setPlaceholder("Start typing...");
  };

  useEffect(() => {
    setUpNonRenderStyles(currentSlide.id);

    props.manager.bind(
      "t",
      () => {
        props.store.createItemSelector.set(SlideObjectType.TEXT);
      },
      "Canvas",
      "Create a text field",
      1
    );
    props.manager.bind(
      "a",
      () => {
        props.store.createItemSelector.set(SlideObjectType.AIRTABLE);
      },
      "Canvas",
      "Create an airtable embed",
      1
    );
    props.manager.bind(
      "c",
      () => {
        props.store.createItemSelector.set(SlideObjectType.COMMENT);
      },
      "Canvas",
      "Create a comment",
      1
    );
    props.manager.bind(
      "r",
      () => {
        props.store.createItemSelector.set(SlideObjectType.RECTANGLE);
      },
      "Canvas",
      "Create a rectangle",
      1
    );
    props.manager.bind(
      "b",
      () => props.store.createItemSelector.set(SlideObjectType.BOARD),
      "Canvas",
      "Create a board",
      1
    );
    props.manager.bind(
      "esc",
      () => {
        if (isVisible) {
          toggle();
          return;
        }
        props.store.createItemSelector.set(null);
      },
      "Canvas",
      "Remove any create selection.",
      1
    );
    props.manager.bind(
      "del",
      () => {
        deleteConnections(
          props.store.selectedSlideElement.get()!,
          currentSlide.id,
          props.store
        );
        props.store.emit(SocketActions.DELETE_SLIDE_OBJECT, {
          id: props.store.selectedSlideElement.get()!,
          slideId: currentSlide.id,
        });
        props.store.setSelectedSlideElement(null);
      },
      "Canvas",
      "Delete current slide element",
      1
    );
    props.manager.bind(
      "command+f",
      (e: MouseEvent) => {
        const ideas = Object.entries(currentSlide.content)
          .filter(([key, slideItem]) => {
            return slideItem.type === SlideObjectType.IDEA;
          })
          .map(([key, slideItem]) => ({
            id: slideItem.id,
            title: (slideItem as SlideIdea).title,
            type: SlideObjectType.IDEA,
            x: slideItem.x,
            y: slideItem.y,
            width: (slideItem as SlideIdea).width,
            height: (slideItem as SlideIdea).height,
          }));

        const texts = Object.entries(currentSlide.content)
          .filter(([key, slideItem]) => {
            return slideItem.type === SlideObjectType.TEXT;
          })
          .map(([key, slideItem]) => ({
            id: slideItem.id,
            title: (slideItem as SlideText).text,
            type: SlideObjectType.TEXT,
            x: slideItem.x,
            y: slideItem.y,
            width: (slideItem as SlideText).width,
            height: (slideItem as SlideText).height,
          }));

        const frames = props.user.team.slideshows
          .find((slideshow) => slideshow.id === props.store.roomId.get()!)!
          .frames.filter(
            (frame) => frame.slide.id === props.store.currentSlide.get()!
          )
          .map((frame) => ({
            id: frame.id,
            title: frame.title,
            type: "FRAME",
            top: frame.top,
            left: frame.left,
            scale: frame.scale,
            slide: frame.slide,
          }));

        const ideasAndFrames = [...ideas, ...texts, ...frames];
        const containerBox = containerRef.current!.getBoundingClientRect();
        setPrompt("Search");
        setPlaceholder("Keyword");
        setMode(CommandLineModes.OPTION);
        addOptions(
          ideasAndFrames.map((item) => {
            return {
              id: item.id,
              title: item.title || "Untitled",
              type: CommandLineModes.OPTION,
              source: CommandLineSource.CANVAS,
              priority: CommandPriority.HIGH,
              onSelect: () => {
                snapToItem(item, containerBox, props.store);
                setIsVisible(false);
                resetCommands();
              },
              onCancel: () => resetCommands(),
            };
          })
        );
        setIsVisible(true);
      },
      "Canvas",
      "Search for an element",
      1
    );
    setStore(props.store);
    addWindowListeners();
    addCommands(commands);

    return () => {
      props.manager.unbind("t", "Canvas");
      props.manager.unbind("c", "Canvas");
      props.manager.unbind("a", "Canvas");
      props.manager.unbind("r", "Canvas");
      props.manager.unbind("b", "Canvas");
      props.manager.unbind("esc", "Canvas");
      props.manager.unbind("del", "Canvas");
      props.manager.unbind("command+f", "Canvas");
      removeStore();
      removeWindowListeners();
    };
  }, []);

  const onMouseMove = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
    if (!canvasStyleDictionary[currentSlide.id]) {
      return;
    }
    const top =
      (e.clientY - canvasStyleDictionary[currentSlide.id].top) /
      canvasStyleDictionary[currentSlide.id].scale;
    const left =
      (e.clientX - canvasStyleDictionary[currentSlide.id].left) /
      canvasStyleDictionary[currentSlide.id].scale;

    props.store.emit(SocketActions.MOUSE_MOVEMENT, {
      x: left,
      y: top,
      userId: props.user.id,
      timestamp: Date.now(),
    });
    e.persist();
  }, []);

  const onCanvasClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
    const canvasPosition = getCanvasPosition(e, props.store);
    const createItemSelector = props.store.createItemSelector.get();
    if (!createItemSelector) {
      return null;
    }
    const newElementId = v4();
    const x =
      (e.clientX - canvasStyleDictionary[currentSlide.id].left) /
      canvasStyleDictionary[currentSlide.id].scale;
    const y =
      (e.clientY - canvasStyleDictionary[currentSlide.id].top) /
      canvasStyleDictionary[currentSlide.id].scale;
    if (createItemSelector === SlideObjectType.TEXT) {
      props.store.emit(SocketActions.CREATE_SLIDE_OBJECT, {
        id: newElementId,
        slideId: props.store.currentSlide.get(),
        type: SlideObjectType.TEXT,
        text: "Hello world!",
        fontSize: 16,
        x,
        y,
        width: 200,
        height: 50,
      });
    } else if (createItemSelector === SlideObjectType.COMMENT) {
      props.store.emit(SocketActions.CREATE_SLIDE_OBJECT, {
        id: newElementId,
        slideId: props.store.currentSlide.get(),
        type: SlideObjectType.COMMENT,
        x,
        y,
      });
    } else if (createItemSelector === SlideObjectType.AIRTABLE) {
      props.store.emit(SocketActions.CREATE_SLIDE_OBJECT, {
        id: newElementId,
        slideId: props.store.currentSlide.get(),
        type: SlideObjectType.AIRTABLE,
        x,
        y,
      });
    } else if (createItemSelector === SlideObjectType.RECTANGLE) {
      props.store.emit(SocketActions.CREATE_SLIDE_OBJECT, {
        id: newElementId,
        slideId: props.store.currentSlide.get(),
        type: SlideObjectType.RECTANGLE,
        x,
        y,
        width: 100,
        height: 100,
      });
    } else if (createItemSelector === SlideObjectType.BOARD) {
      props.store.emit(SocketActions.CREATE_SLIDE_OBJECT, {
        id: newElementId,
        slideId: props.store.currentSlide.get(),
        type: SlideObjectType.BOARD,
        x,
        y,
        width: 500,
        height: 500,
      });
    }

    props.store.setSelectedSlideElement(newElementId);
    props.store.createItemSelector.set(null);
  }, []);

  const translateAndZoom = useCallback(
    (event: React.WheelEvent) => {
      event.preventDefault();
      event.stopPropagation();

      const viewRect = containerRef.current?.getBoundingClientRect()!;

      const maxHorizontal =
        (canvasStyleDictionary[currentSlide.id].scale * INTERNAL_CANVAS_WIDTH -
          viewRect.width!) *
        -1;
      const maxVertical =
        (canvasStyleDictionary[currentSlide.id].scale * INTERNAL_CANVAS_HEIGHT -
          viewRect.height!) *
        -1;

      if (event.ctrlKey || event.metaKey) {
        const coordChange = (coordinate: number, scaleRatio: number) => {
          return scaleRatio * coordinate - coordinate;
        };

        const scaleFromPoint = (
          newScale: number,
          focalPoint: { x: number; y: number }
        ) => {
          const previousScale = canvasStyleDictionary[currentSlide.id].scale;
          const scaleRatio = newScale / previousScale;

          const focalPointDelta = {
            x: coordChange(focalPoint.x, scaleRatio),
            y: coordChange(focalPoint.y, scaleRatio),
          };

          const newTranslation = {
            x: canvasStyleDictionary[currentSlide.id].left - focalPointDelta.x,
            y: canvasStyleDictionary[currentSlide.id].top - focalPointDelta.y,
          };

          const newMaxHorizontal =
            (newScale * INTERNAL_CANVAS_WIDTH - viewRect.width!) * -1;
          const newMaxVertical =
            (newScale * INTERNAL_CANVAS_HEIGHT - viewRect.height!) * -1;

          setStyle(currentSlide.id, {
            scale: newScale,
            top: clamp(newMaxVertical, newTranslation.y, 0),
            left: clamp(newMaxHorizontal, newTranslation.x, 0),
          });
        };

        const translatedOrigin = () => {
          return {
            x: viewRect.left + canvasStyleDictionary[currentSlide.id].left,
            y: viewRect.top + canvasStyleDictionary[currentSlide.id].top,
          };
        };

        const clientPosToTranslatedPos = ({
          x,
          y,
        }: {
          x: number;
          y: number;
        }) => {
          const origin = translatedOrigin();
          return {
            x: x - origin.x,
            y: y - origin.y,
          };
        };

        const previousScale = canvasStyleDictionary[currentSlide.id].scale;
        // TODO: limits

        const scaleChange = 2 ** (event.deltaY * 0.002);
        const newScale = clamp(0.25, previousScale + (1 - scaleChange), 4);

        const mousePosition = clientPosToTranslatedPos({
          x: event.clientX,
          y: event.clientY,
        });

        scaleFromPoint(newScale, mousePosition);
        return;
      }

      setStyle(currentSlide.id, {
        scale: canvasStyleDictionary[currentSlide.id].scale,
        top: clamp(
          maxVertical,
          canvasStyleDictionary[currentSlide.id].top + event.deltaY * -1,
          0
        ),
        left: clamp(
          maxHorizontal,
          canvasStyleDictionary[currentSlide.id].left + event.deltaX * -1,
          0
        ),
      });
    },
    [currentSlide]
  );

  const onMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
    if (
      e.button === 1 ||
      (e.button === 0 && currentKeys["Space"]) /* spacebar */
    ) {
      CurrentScreen.screenX = e.screenX;
      CurrentScreen.screenY = e.screenY;
      props.store.isCanvasDragging.set(true);
      setGlobalStyle(`body {
        cursor: grab;
      }`);
      e.preventDefault();
    } else if (e.button === 0) {
      // Absolute position on screen
      // TODO: No multiselect right now so no need for window drag
      // const currentPosition = getCanvasPosition(e, props.store);
      // CurrentScreen.canvasX = currentPosition.x;
      // CurrentScreen.canvasY = currentPosition.y;
      // props.store.isSelectDragging.set(true);
    }
  }, []);

  if (!currentSlide) {
    return null;
  }
  const currentSlideshowId = props.store.roomId.get()!;
  const currentSlideshow = props.user.team.slideshows.find(
    (slideshow) => slideshow.id === currentSlideshowId
  )!;
  const relevantFrames = currentSlideshow.frames.filter(
    (frame) => frame.slide.id === currentSlide.id
  );

  return (
    <Container
      id="canvas-container"
      ref={containerRef}
      onWheel={translateAndZoom}
    >
      <InternalCanvas
        id="internal-canvas"
        ref={canvasRef}
        className={`canvas-${currentSlide.id}`}
        onMouseDown={onMouseDown}
        onMouseMove={onMouseMove}
        onClick={onCanvasClick}
      >
        {Object.keys(currentSlide.content).map((slideObjectKey, index) => {
          const currentSlideObject = currentSlide.content[slideObjectKey];
          switch (currentSlideObject.type) {
            case SlideObjectType.TEXT:
              return (
                <Text
                  store={props.store}
                  key={slideObjectKey}
                  slideText={currentSlideObject as SlideText}
                />
              );
            case SlideObjectType.AIRTABLE:
              return null;
              return (
                <Airtable
                  store={props.store}
                  key={slideObjectKey}
                  slideObject={currentSlideObject as SlideAirtable}
                />
              );
            case SlideObjectType.COMMENT:
              return (
                <Comment
                  store={props.store}
                  key={slideObjectKey}
                  user={props.user}
                  slideComment={currentSlideObject as SlideComment}
                />
              );
            case SlideObjectType.RECTANGLE:
              return (
                <Rectangle
                  store={props.store}
                  key={slideObjectKey}
                  slideRectangle={currentSlideObject as SlideRectangle}
                />
              );
            case SlideObjectType.BOARD:
              const boardItems = Object.entries(currentSlide.content)
                .filter(([key, slideItem]) => {
                  return (
                    slideItem.type === SlideObjectType.IDEA &&
                    (slideItem as SlideIdea).boardId === slideObjectKey
                  );
                })
                .map(([key, slideItem]) => slideItem);

              return (
                <Board
                  store={props.store}
                  key={slideObjectKey}
                  slideBoard={currentSlideObject as SlideBoard}
                  boardItems={boardItems as SlideIdea[]}
                />
              );
            case SlideObjectType.IDEA:
              if ((currentSlideObject as SlideIdea).boardId) {
                return null;
              }
              return (
                <Idea
                  store={props.store}
                  key={slideObjectKey}
                  slideIdea={currentSlideObject as SlideIdea}
                />
              );
            default:
              console.log("UNHANDLED");
              return null;
          }
        })}
        <MouseList
          container={canvasRef}
          user={props.user}
          store={props.store}
        />
        {relevantFrames.map((frame) => (
          <div
            key={frame.id}
            style={{
              position: "absolute",
              top: (frame.top / frame.scale) * -1,
              left: (frame.left / frame.scale) * -1,
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
              transform: "translate(-50%, -50%)",
            }}
          >
            <div
              style={{ fontSize: 24, color: "rgb(0,0,0,.08)", marginBottom: 4 }}
            >
              {frame.title}
            </div>
            <div
              key={frame.id}
              style={{
                height: 10,
                width: 10,
                borderRadius: 5,
                backgroundColor: "rgb(0,0,0,.08)",
                pointerEvents: "none",
              }}
            ></div>
          </div>
        ))}
        <svg
          width="100%"
          height="100%"
          style={{ position: "absolute", pointerEvents: "none" }}
        >
          <defs>
            <marker
              id="arrowhead"
              viewBox="0 0 10 10"
              refX="3"
              refY="5"
              markerWidth="6"
              markerHeight="6"
              orient="auto"
            >
              <path d="M 0 0 L 10 5 L 0 10 z" />
            </marker>
          </defs>
          {Object.keys(currentSlide.content).map((slideObjectKey, index) => {
            const currentSlideObject = currentSlide.content[slideObjectKey];

            if (currentSlideObject.type === SlideObjectType.CONNECTION) {
              return (
                <Connection
                  slideConnection={currentSlideObject as SlideConnection}
                  key={slideObjectKey}
                  store={props.store}
                />
              );
            }

            return null;
          })}
          {/* Canvas layer for arrows */}
          {props.store.currentConnectingElement.get() && (
            <PendingConnection store={props.store} />
          )}
        </svg>
        <button
          onClick={async (e) => {
            console.log("OOOOOO");
            const canvas = await html2canvas(containerRef.current!);
            console.log("canvas");
            console.log(canvas);
            const dataCanvas = canvas.toDataURL();
            console.log(dataCanvas);
          }}
          style={{
            position: "absolute",
            top: 100,
            left: 100,
          }}
        >
          Snapshot of the canvas!
        </button>
        <SelectRectangle user={props.user} store={props.store} />
      </InternalCanvas>
    </Container>
  );
}

export default withPlatform(inject("store")(observer(Canvas)));

const InternalCanvas = styled.div`
  position: absolute;
  display: flex;
  width: ${INTERNAL_CANVAS_WIDTH}px;
  height: ${INTERNAL_CANVAS_HEIGHT}px;
  background-color: ${LIGHT_SECONDARY_FIVE};
`;

const Container = styled.div`
  position: relative;
  width: 100%;
  height: 100%;
  background-color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
`;
