/**
 * TEAM: frontend_infra
 *
 * @flow
 */

import * as React from "react";
import {StyleSheet, css} from "aphrodite";
import SideNavItem from "./SideNavItem";
import SideNavSection from "./SideNavSection";
import SideNavToggleButton, {TOGGLE_BUTTON_SIZE} from "./SideNavToggleButton";
import Text from "../Text";
import Highlighter from "../Highlighter";
import useHover from "../hooks/useHover";
import colors from "../colors";
import whitespace from "../styles/whitespace";
import SideNavContext from "./SideNavContext";

// in pixels
export const CLOSED_WIDTH = 64;
export const OPEN_WIDTH = 224;
export const NAV_PADDING = 12;

// in milliseconds
const HOVER_DELAY = 0;
const TRANSITION_DURATION = 250;
const SLIDE_EASE = "cubic-bezier(0, 0.55, 0.45, 1)"; // circ ease-out

type Props = {|
  /** The title to indicate the side nav's purpose */
  +title: React.Node,
  /** Nav items route the user to their url when clicked */
  +children?: React.ChildrenArray<
    | React.Element<typeof SideNavItem | typeof SideNavSection>
    | void
    | null
    | boolean
  >,
  /** Keep the SideNav in expanded mode meaning it can't be collapsed, in this mode, icons for menu items can be disabled */
  +expandedMode?: boolean | {disableIcons: boolean, ...},
  /** Give sideNav a zIndex, defaults to '1' */
  +zIndex?: number,
  /** Typically router.pathname, used for route matching for SideNavItems */
  +pathname: string,
|};

/**
 * @short SideNav is a layout nav for quick access navigation links
 * @category Navigation
 * @status Stable
 * @brandStatus V2
 */
export default class SideNav extends React.PureComponent<Props, {...}> {
  static Item: typeof SideNavItem = SideNavItem;

  static Section: typeof SideNavSection = SideNavSection;

  render(): React.Node {
    return <InnerSideNav {...this.props} />;
  }
}

// Inner component to use hooks, we use a class Component to make use of static
function InnerSideNav({
  title,
  children,
  expandedMode = false,
  zIndex = 1,
  pathname,
}: Props) {
  const [isHovered, onMouseEnter, onMouseLeave] = useHover(HOVER_DELAY);
  const [isLocked, setIsLocked] = React.useState(false);
  const isLayoutExpanded = isLocked || !!expandedMode;
  const isNavExpanded = isHovered || isLayoutExpanded;
  const toggleLock = React.useCallback(() => {
    if (!isLocked) {
      setIsLocked(true);
      return;
    }

    setIsLocked(false);
    onMouseLeave();
  }, [isLocked, onMouseLeave]);
  const [highlightedElement, setHighlightedElement] = React.useState();

  return (
    <SideNavContext.Provider
      value={{
        isExpanded: isNavExpanded,
        areIconsEnabled: !(
          typeof expandedMode === "object" && expandedMode.disableIcons
        ),
        setHighlightedElement,
        pathname,
      }}
    >
      <div
        className={css(styles.layoutContainer)}
        style={{
          zIndex,
          ...(!isLayoutExpanded
            ? closedStyles.layoutContainer
            : openStyles.layoutContainer),
        }}
      >
        <div
          className={css(styles.nav)}
          style={getNavStyle(isNavExpanded, isLocked)}
          onMouseLeave={onMouseLeave}
        >
          <div className={css(styles.header)}>
            <div
              className={css(styles.title)}
              style={isNavExpanded ? openStyles.title : closedStyles.title}
            >
              <Text scale="title" weight="bold" color="white">
                {title}
              </Text>
            </div>
            {!expandedMode && (
              <div
                className={css(styles.toggleButton)}
                style={!isNavExpanded ? closedStyles.toggleButton : {}}
              >
                <SideNavToggleButton
                  isOpen={isLayoutExpanded}
                  onClick={toggleLock}
                />
              </div>
            )}
          </div>
          {/* We only want the navbar to auto-expand when hovering the content area, if it
              expands on hovering the header for example, we mark it harder for users to
              click the lock expand button
          */}
          <div className={css(styles.contentArea)} onMouseEnter={onMouseEnter}>
            <nav className={css(styles.itemsListWrapper)}>
              <div
                className={css(styles.itemsList)}
                style={
                  isNavExpanded ? openStyles.itemsList : closedStyles.itemsList
                }
              >
                <Highlighter
                  highlightedElement={highlightedElement}
                  color="grey40"
                >
                  {children}
                </Highlighter>
              </div>
            </nav>
          </div>
        </div>
      </div>
    </SideNavContext.Provider>
  );
}

function getNavStyle(isNavExpanded: boolean, isLocked: boolean) {
  if (isNavExpanded && !isLocked) {
    return {
      boxShadow: "0 0 20px rgba(57, 65, 77, 0.15)",
    };
  }

  if (!isNavExpanded) {
    return closedStyles.nav;
  }

  return {};
}

// Note: These have to be inlined due to aphrodite not applying styles on first render
const openStyles = {
  layoutContainer: {
    width: OPEN_WIDTH,
  },
  itemsList: {
    transform: `translateX(0px)`,
  },
  title: {
    opacity: 1,
  },
};

// Note: These have to be inlined due to aphrodite not applying styles on first render
const closedStyles = {
  layoutContainer: {
    width: CLOSED_WIDTH,
  },
  nav: {
    transform: `translateX(-${OPEN_WIDTH - CLOSED_WIDTH}px)`,
  },
  toggleButton: {
    transform: `translateX(-${
      (CLOSED_WIDTH - TOGGLE_BUTTON_SIZE) / 2 - NAV_PADDING
    }px)`, //  centers the close nav button
  },
  itemsList: {
    transform: `translateX(${OPEN_WIDTH - CLOSED_WIDTH}px)`,
  },
  title: {
    opacity: 0,
  },
};

const styles = StyleSheet.create({
  layoutContainer: {
    position: "relative",
    height: "100%",
    transition: `width ${TRANSITION_DURATION}ms ${SLIDE_EASE}`,
  },

  nav: {
    position: "absolute",
    display: "flex",
    flexDirection: "column",
    alignItems: "stretch",
    top: 0,
    left: 0,
    width: OPEN_WIDTH,
    background: colors.grey60,
    height: "100%",
    overflow: "hidden",
    transition: `transform ${TRANSITION_DURATION}ms ${SLIDE_EASE}`,
    boxShadow:
      "0 16px 10px 0 rgba(0,0,0,0.14), 0 11px 18px 0 rgba(0,0,0,0.12), 0 13px 5px -1px rgba(0,0,0,0.2)",
  },

  header: {
    display: "flex",
    width: "100%",
    minHeight: "30px",
    justifyContent: "space-between",
    marginBottom: "16px",
    flex: "0 0 auto",
    padding: `${NAV_PADDING}px ${NAV_PADDING}px 0 ${NAV_PADDING}px`,
  },

  contentArea: {
    width: "100%",
    marginBottom: "16px",
    flex: "1 1 auto",
    padding: `0 ${NAV_PADDING}px ${NAV_PADDING}px ${NAV_PADDING}px`,
  },

  title: {
    flex: "1 1 auto",
    alignSelf: "center",
    margin: `auto ${whitespace.m}px`, // Handles two line titles gracefully thanks to "margin: auto" interaction with flexbox
    position: "relative",
    transition: `opacity ${TRANSITION_DURATION / 2}ms ease-out`, // We fade out the title in case it's long enough to show up a little bit when the sidebar is collapsed
  },

  toggleButton: {
    flex: "0 0 auto", // Prevent button width from being affected by flexbox sibling items
    position: "relative",
    transition: `transform ${TRANSITION_DURATION}ms ${SLIDE_EASE}`,
  },

  itemsListWrapper: {
    position: "relative",
    overflowX: "hidden",
    width: "100%",
  },

  itemsList: {
    display: "flex",
    flexDirection: "column",
    margin: 0,
    padding: 0,
    transition: `transform ${TRANSITION_DURATION}ms ${SLIDE_EASE}`,
    width: "100%",
  },
});
