import React from "react";
import ReactDOM from "react-dom";
import Fab from "@material-ui/core/Fab";
import Tooltip from "@material-ui/core/Tooltip";
import { FaArrowDown } from "react-icons/fa";
import IconBug from "./icon-bug.png";
import Box from "@material-ui/core/Box";
import { SubmissionForm } from "./SubmissionForm";
import styled, { css } from "styled-components";
import { PageCapture, capture } from "./PageCapture";
import {
  BugButtonApp,
  BugButtonSubmissionContainer,
  BugButtonAppUIState,
  BugButtonLifecycleEvents,
  HasExtra,
} from "./BugButtonApp";
import { Instructions } from "./Instructions";
import Paper from "@material-ui/core/Paper";
import { PinBoard } from "./pinboard/PinBoard";
import { motion, AnimatePresence, useAnimation } from "framer-motion";
import Debug from "debug";
import { ThemeOptions } from "@material-ui/core";
import useDimensions from "react-cool-dimensions";
import { SmallScreenBreakpointWidth } from "./constants";

const debug = Debug("userbugreport:BugButton");

/**
 * Animated full screen starts off screen.
 */
const FullScreen = styled(motion.div)`
  font-family: ${(props) => props.theme.typography.fontFamily};
  position: fixed;
  z-index: ${(props) => props.theme.zIndex.tooltip - 2};
  top: -100vw;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: ${(props) => props.theme.palette.background.paper};
  display: grid;
  place-items: center;
`;

/**
 * Fill the page and Z order up to overlay, creating a kind
 * of modal.
 */
const Modal = styled(motion.div)`
  font-family: ${(props) => props.theme.typography.fontFamily};
  z-index: ${(props) => props.theme.zIndex.tooltip - 1};
  width: 100%;
  height: 100%;
  display: grid;
  align-items: stretch;
  justify-items: stretch;
  grid-template-columns: ${({ small }) =>
    small ? "1em auto 0em 1em" : "1em auto 15em 1em"};
  grid-template-rows: 1em 15em auto 1fr 1em;
  grid-template-areas:
    ". . .  ."
    ". form instructions ."
    ". barrier barrier ."
    ". page page ."
    ". . .  .";
  grid-gap: ${(props) => props.theme.spacing(1)}px;
  background-color: ${(props) => props.theme.palette.background.paper};
`;

const Barrier = styled(Box)`
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  grid-area: barrier;
`;
/**
 * Get that bug to fill up the containing button well.
 */
const ScaledBug = () => (
  <img src={IconBug} style={{ width: "60%", height: "60%" }} />
);

/**
 * The instructions.
 */
const InstructionsArea = styled(Box)`
  grid-area: instructions;
  margin: ${(props) => props.theme.spacing(1)}px;
`;

// theoretically could have more positions
const Position = styled.div`
  ${(props) =>
    props.position === "bottom-right" &&
    css`
      position: fixed;
      bottom: 10px;
      right: 10px;
    `};
`;

type BugButtonCustomization = {
  /**
   * Override the instructions with a custom react component
   */
  instructions?: React.Component;
  /**
   * Exposes complete MUI Theme support.
   * https://material-ui.com/customization/palette/
   */
  theme?: ThemeOptions;
  pinStyle?: React.CSSProperties;
  noteStyle?: React.CSSProperties;
};

type ModalHolderProps = {};

/**
 * Hook up a modal holder wrapper to the root of the DOM, and then use that
 * to set a portal to make sure the modal is held at the very start of the page
 * at the highest possible Z.
 */
export const ModalHolder: React.FC<ModalHolderProps> = ({ children }) => {
  const { uiState } = BugButtonSubmissionContainer.useContainer();

  const backgroundControls = useAnimation();

  /**
   * Sequenced animations on state changes.
   */
  React.useEffect(() => {
    const animateEntry = async () => {
      await backgroundControls.start({ opacity: 1, top: 0 });
    };
    const animateSubmit = async () => {
      await backgroundControls.start({ top: "-100vh" });
    };
    const animateCancel = async () => {
      await backgroundControls.start({ top: "-100vh" });
    };
    if (uiState === BugButtonAppUIState.EnteringData) animateEntry();
    if (uiState === BugButtonAppUIState.Submitting) animateSubmit();
    if (uiState === BugButtonAppUIState.Cancelled) animateCancel();
  }, [uiState]);
  // it is expected we have a place to anchor this
  const modalHolder = document.getElementById("userbugbuttondialog");
  return ReactDOM.createPortal(
    <AnimatePresence>
      <FullScreen
        key="ModalHolder"
        animate={backgroundControls}
        exit={{ opacity: 0, display: "none" }}
      >
        {children}
      </FullScreen>
    </AnimatePresence>,
    modalHolder
  );
};

/**
 * Nested display dialog props.
 */
type BugButtonDialogProps = BugButtonCustomization & {
  /**
   * Style pass through to the form, used to place in grids.
   */
  style?: React.CSSProperties;
  /**
   * Auto capture -- this is really just for testing.
   */
  autoCapture?: boolean;
};

/**
 * Dialog is a separate component so we can visualize it for responsive-ness.
 */
export const BugButtonDialog: React.FC<BugButtonDialogProps> = (
  props: BugButtonDialogProps
) => {
  const {
    uiState,
    screenshotSize,
    setScreenshotSize,
  } = BugButtonSubmissionContainer.useContainer();
  debug("render dialog", props, uiState);
  const { ref, width } = useDimensions();

  const InstructionsContent = () => {
    if (props.instructions) {
      return <>{props.instructions}</>;
    } else {
      return <Instructions />;
    }
  };

  return (
    <Modal key="modal" ref={ref} small={width < SmallScreenBreakpointWidth}>
      <SubmissionForm
        style={{
          gridArea: "form",
        }}
      />

      <Barrier>
        <FaArrowDown />
        <span>Screenshot Below</span>
        <FaArrowDown />
      </Barrier>
      {width > SmallScreenBreakpointWidth && (
        <InstructionsArea>
          <InstructionsContent />
        </InstructionsArea>
      )}
      <Paper elevation={2} style={{ gridArea: "page" }}>
        <PageCapture
          autoCapture={props.autoCapture}
          onResizeScreenshotToFit={setScreenshotSize}
        />
      </Paper>
      {screenshotSize?.height && screenshotSize?.width && (
        <PinBoard
          {...props}
          style={{
            gridArea: "page",
            width: screenshotSize.width,
            height: screenshotSize.height,
            placeSelf: "center",
          }}
        />
      )}
    </Modal>
  );
};

/**
 * BugButton is configured with these props.
 *
 * Documentation in website/docs/components/bugbutton.mdx
 */
export type BugButtonProps = BugButtonLifecycleEvents &
  HasExtra &
  BugButtonCustomization & {
    onBugButtonClicked?: () => Promise<void>;
    id: string;
    apikey: string;
  };
/**
 * The bug button, contained inside our app.
 */
const BugButtonInApp: React.FC<BugButtonProps> = (
  props: React.PropsWithChildren<BugButtonProps>
) => {
  const {
    uiState,
    setLifecycleEvents,
    setUIState,
    rawScreenshot,
    setRawScreenshot,
    setId,
    setApikey,
    setServerURL,
    setUserbugreport,
    setLocation,
  } = BugButtonSubmissionContainer.useContainer();
  debug("render button", props, uiState);

  // grab the page before launch
  const captureAndShow = async () => {
    setLocation(window.location);
    setRawScreenshot(await capture());
  };

  // and chained effect so we don't -- capture ourselves...
  // do don't set the state to really show the dialog until the
  // imagae capture is done
  React.useEffect(() => {
    if (rawScreenshot) {
      setUIState(BugButtonAppUIState.EnteringData);
    }
  }, [rawScreenshot]);

  // the submission needs to know the API key
  React.useEffect(() => {
    setId(props.id);
    setApikey(props.apikey);
    setServerURL(props.serverURL);
    setUserbugreport(props.userbugreport);
  }, [uiState, props.id, props.apikey, props.serverURL, props.userbugreport]);

  // debugging, saves clicking every time you reload
  React.useEffect(() => {
    debug("hooking events");
    setLifecycleEvents(props);
  }, []);

  // show our button, or if somebody passed replacement content
  // show that instead, the click surface wraps this display
  // so a customizer doesn't need to know any event handling
  const buttonVisuals = () => {
    // eslint-disable-next-line react/prop-types
    const { children } = props;
    if (children) {
      return <>{children}</>;
    } else {
      // default visuals
      return (
        <Tooltip title={"Submit a bug!"}>
          <Position position={props.position}>
            <Fab>
              <ScaledBug />
            </Fab>
          </Position>
        </Tooltip>
      );
    }
  };

  const onClick = async () => {
    // before even
    if (props.onBugButtonClicked) await props.onBugButtonClicked();
    // actual kickoff of the screen capture
    await captureAndShow();
  };

  return (
    <>
      <div onClick={onClick}>{buttonVisuals()}</div>
      {[
        BugButtonAppUIState.EnteringData,
        BugButtonAppUIState.Submitting,
        BugButtonAppUIState.Cancelled,
      ].includes(uiState) && (
        <ModalHolder>
          <BugButtonDialog {...props} />
        </ModalHolder>
      )}
    </>
  );
};

/**
 * BugButton will create a clickable element on your page to start a user
 * bug report.
 *
 * By default, this button is simply inserted as:
 * ```
 * <BugButton/>
 * ```
 * and it will draw a simple 'bug' to be clicked.
 *
 * You can supply any child elements, and replace the default 'bug':
 * ```
 * <BugButton>
 * ...
 * </BugButton>
 * ```
 *
 */
export const BugButton: React.FC<BugButtonProps> = (
  props: React.PropsWithChildren<BugButtonProps>
) => {
  debug("render", props);

  /**
   * The dialog needs to be really top most, so create it a DOM element
   * to serve as a portal.
   *
   * This was discovered when putting the button in a Docusaurus page that is
   * body>nav -- display:sticky
   * body>div -- bug button icon was here, floating
   * body>footer
   *
   * The net effect was the dialog was contained by body>div, and body>nav
   * came 'above it' in Z.
   */
  React.useEffect(() => {
    debug("attaching modal holder");
    let modalHolder = document.getElementById("userbugbuttondialog");
    if (!modalHolder) {
      modalHolder = document.createElement("div");
      modalHolder.id = "userbugbuttondialog";
      document.querySelector("body").prepend(modalHolder);
    }
  }, []);

  // set up an app and then delegate
  return (
    <BugButtonApp theme={props.theme}>
      <BugButtonInApp {...props}></BugButtonInApp>
    </BugButtonApp>
  );
};
