import { keyframes } from "@emotion/core";
import styled from "@emotion/styled";
import { Spinner } from "evergreen-ui";
import Fuse from "fuse.js";
import * as _ from "lodash";
import React, { createRef, useEffect, useRef, useState } from "react";
import SimpleBar from "simplebar-react";
import {
  LIGHT_SECONDARY_FIVE,
  LIGHT_SECONDARY_THREE,
  LIGHT_SECONDARY_TWO,
  LIGHT_TERTIARY_THREE,
  Theme,
} from "../../../../shared/colors";
import {
  Command,
  CommandLineModes,
  useCommandLineContext,
} from "../../../providers/CommandLineProvider";
import ClickOutside from "../ClickOutside";
import Input from "../InputWithShortcuts";

type Props = {};

type State = {
  search: string;
};

function CommandLine(props: Props) {
  const context = useCommandLineContext();
  const {
    commands,
    isVisible,
    toggle,
    prompt,
    placeholder,
    isLoading,
    mode,
    addOptions,
    setIsVisible,
    promptCallback,
  } = context;
  const commandLineInputRef = createRef<any>();
  const [search, setSearch] = useState("");
  const [currentIndex, setCurrentIndex] = useState(0);
  const [activeCommand, setActiveCommand] = useState<Command | null>(null);
  const commandRef = useRef<HTMLDivElement>(null);
  const [isTrackingMouse, setIsTrackingMouse] = useState(false);
  const [isPressed, setIsPressed] = useState(false);
  const [animationVisible, setAnimationVisible] = useState(false);

  const currentCommand = commands[currentIndex];

  useEffect(() => {
    if (search && commandLineInputRef.current) {
      commandLineInputRef.current.select();
    }
  }, [isVisible]);

  useEffect(() => {
    setCurrentIndex(0);
  }, [commands]);

  const leaveAnimation = () => {
    setAnimationVisible(true);
  };

  const bubbleUpEscape = () => {
    if (!_.isEmpty(search)) {
      // claer the search first before existing
      setSearch("");
      return;
    } else {
      // previously active command onCnancel
      if (activeCommand?.onCancel) {
        activeCommand.onCancel();
      } else if (currentCommand?.onCancel) {
        currentCommand.onCancel();
      }
      setActiveCommand(null);
      leaveAnimation();
    }
  };

  const handlePromptAction = async () => {
    if (mode !== CommandLineModes.PROMPT) {
      return;
    }

    await promptCallback(search);
  };

  let shownCommmands: Command[] = [];
  if (commands.length > 0 && mode === CommandLineModes.COMMAND) {
    shownCommmands = commands;
  } else if (mode === CommandLineModes.OPTION) {
    shownCommmands = commands.filter((command) => command.type === "OPTION");
  } else if (commands.length === 0 || mode === CommandLineModes.PROMPT) {
    // couldn't find any commands.... show a zero state, like superhuman?
    shownCommmands = [];
  }
  // Filter oout duplicates
  shownCommmands = _.uniqBy(shownCommmands, (command) => command.id);
  // Apply search filters
  if (!_.isEmpty(search)) {
    // run the frontend saerch
    const fuse = new Fuse(shownCommmands, {
      includeScore: true,
      keys: ["title"],
    });
    const result = fuse.search(search);
    shownCommmands = result.map((result) => result.item);
  }
  // Apply ranking by priority
  shownCommmands = _.orderBy(shownCommmands, ["priority"], ["desc"]);

  const updateSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
    setSearch(event.target.value);
  };

  const handleSubmit = (index: number) => {
    // submitting
    const submitCommand = shownCommmands[currentIndex];
    if (submitCommand?.onSelect) {
      submitCommand.onSelect();
      // new active command
      setActiveCommand(submitCommand);
      // clear input
      setSearch("");
    }
  };

  const handleKeyDown = (event: any) => {
    if (event.key === "Enter") {
      setIsPressed(true);
    }
  };

  const handleKeyUp = (event: any) => {
    setIsPressed(false);
    // if start typing, don't track mouse
    setIsTrackingMouse(false);
    if (!isVisible) {
      return;
    }
    const { key } = event;
    let handled = true;
    const totalLength = shownCommmands.length;

    if (key === "Enter") {
      handleSubmit(currentIndex);
    } else if (key === "ArrowDown") {
      if (currentIndex === totalLength - 1) {
        setCurrentIndex(0);
      } else {
        setCurrentIndex((prev) => prev + 1);
      }
      if (commandRef.current) {
        commandRef.current.scrollIntoView({
          behavior: "auto",
          block: "nearest",
        });
      }
    } else if (key === "ArrowUp") {
      if (currentIndex === 0) {
        setCurrentIndex(totalLength - 1);
      } else {
        setCurrentIndex((prev) => prev - 1);
      }
      if (commandRef.current) {
        commandRef.current.scrollIntoView({
          behavior: "auto",
          block: "nearest",
        });
      }
    } else {
      handled = false;
    }
    if (handled) {
      event.stopPropagation();
      event.preventDefault();
    }
  };

  useEffect(() => {
    window.addEventListener("keyup", handleKeyUp);
    window.addEventListener("keydown", handleKeyDown);

    return () => {
      window.removeEventListener("keyup", handleKeyUp);
      window.removeEventListener("keydown", handleKeyDown);
    };
  });

  if (!isVisible) {
    return null;
  }

  if (isLoading) {
    const currentActive = shownCommmands[currentIndex];
    return (
      <Container isPressed={false} visible={isVisible}>
        <CommandLineDisplay>
          <CommandLinePrompt style={{ color: "white" }}>
            {prompt}
          </CommandLinePrompt>
          <CommandItem key={currentActive.id}>
            <p>{currentActive.title}</p>
            <Spinner
              style={{ marginLeft: 8 }}
              theme={{ spinnerColor: "white" }}
            ></Spinner>
          </CommandItem>
        </CommandLineDisplay>
      </Container>
    );
  }

  return (
    <ClickOutside
      onClickOutside={() => {
        toggle();
        // previously active command onCnancel
        if (activeCommand?.onCancel) {
          activeCommand.onCancel();
        } else if (currentCommand?.onCancel) {
          currentCommand.onCancel();
        }
      }}
    >
      <Container
        isPressed={isPressed}
        visible={isVisible && !animationVisible}
        onAnimationEnd={() => {
          // Do this to have the leave animation
          if (animationVisible) {
            setIsVisible(false);
            setAnimationVisible(false);
          }
        }}
      >
        <CommandLineDisplay>
          <CommandLinePrompt style={{ color: "white" }}>
            {prompt}
          </CommandLinePrompt>
          <CommandLineInput
            autoFocus
            ref={commandLineInputRef}
            placeholder={placeholder || "Start typing..."}
            onChange={updateSearch}
            value={search}
            colorTheme={Theme.DARK}
            shortcuts={[
              {
                keys: "cmd+k",
                callback: toggle,
              },
              {
                keys: "esc",
                callback: bubbleUpEscape,
              },
              {
                keys: "enter",
                callback: handlePromptAction,
              },
            ]}
          />
          <SimpleBar
            style={{ maxHeight: 315 }}
            onMouseLeave={() => {
              // if leave, track mouse again
              setIsTrackingMouse(true);
            }}
            onMouseEnter={() => {
              setIsTrackingMouse(true);
            }}
          >
            {shownCommmands.map((command, index) => {
              return (
                <CommandItem
                  onMouseEnter={() => {
                    if (isTrackingMouse) {
                      setCurrentIndex(index);
                    }
                  }}
                  key={command.id}
                  active={index === currentIndex}
                  ref={index === currentIndex ? commandRef : undefined}
                  onClick={() => {
                    handleSubmit(index);
                  }}
                >
                  <p
                    style={{
                      ...(!command.title && { color: LIGHT_SECONDARY_TWO }),
                    }}
                  >
                    {command.title || "Untitled"}
                  </p>
                </CommandItem>
              );
            })}
          </SimpleBar>
        </CommandLineDisplay>
      </Container>
    </ClickOutside>
  );
}

export default CommandLine;

const zoomKeyframe = (visible: boolean) => keyframes`
  from {
    transform: ${!visible ? "scale3d(1, 1, 1)" : "scale3d(.9, .9, .9)"};
    opacity: ${!visible ? 1 : 0};
  }
  to {
    transform: ${!visible ? "scale3d(.9, .9, .9)" : "scale3d(1, 1, 1)"};
    opacity: ${!visible ? 0 : 1};
  }
`;

const Container = styled.div<{ isPressed: boolean; visible: boolean }>`
  display: flex;
  align-self: center;
  position: absolute;
  left: calc(50% - 400px);
  top: ${(props) => (props.isPressed ? "calc(5% + 1px)" : "5%")};
  background-color: #212121;
  margin: auto;
  z-index: 999;
  padding-top: 16px;
  border-radius: 4px;
  box-shadow: ${(props) =>
    props.isPressed
      ? "0 10px 20px 0 rgba(0, 0, 0, 0.1), 0 13px 35px 0 rgba(0, 0, 0, 0.3)"
      : "0 15px 25px 0 rgba(0, 0, 0, 0.1), 0 19px 45px 0 rgba(0, 0, 0, 0.3)"};
  animation: ${(props) => zoomKeyframe(props.visible)} both
    cubic-bezier(0.4, 0, 0, 1.5);
  animation-duration: 0.1s;
  transition: 0.06s ease-in-out;
`;

const CommandLineDisplay = styled.div`
  width: 800px;
  display: flex;
  flex-direction: column;
`;

const CommandLinePrompt = styled.div`
  color: #adadad;
  padding: 0px 24px 8px;
  border-bottom: 1px solid #3c3d3c;
`;

const CommandLineInput = styled(Input)`
  -webkit-appearance: none;
  border: none;
  background-image: none;
  background-color: transparent;
  :focus {
    outline: none;
  }
  ::placeholder {
    color: #a0a0a0;
  }
  color: rgb(244, 244, 246);
  padding: 16px 24px;
  font-size: 24px;
`;

const CommandItem = styled.div<any>`
  display: flex;
  align-items: center;
  background-image: none;
  background-color: ${(props) => (props.active ? "#3C3D3C" : "transparent")};
  padding: ${(props) => (props.active ? "8px 24px 8px 22px" : "8px 24px")};
  cursor: pointer;
  color: ${(props) =>
    props.active ? LIGHT_SECONDARY_FIVE : LIGHT_SECONDARY_THREE};
  ${(props) =>
    props.active && `border-left: 2px solid ${LIGHT_TERTIARY_THREE};`}
  transition: color 0.08s ease-in-out;
`;
