/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0 */

import {
  HelpProvider,
  LayoutProvider,
  LayoutProviderProps,
  LayoutState,
  ManagedHelpPanelRenderer,
  useLayoutContext,
} from './components';
import {
  NotificationProvider,
  NotificationProviderProps,
  NotificationsRenderer,
} from '../../providers/NotificationProvider';
import { SideNavigationProps } from 'aws-northstar/components/SideNavigation';
import { Sidebar, SidebarType } from './components/Sidebar';
import { Theme, makeStyles } from '@material-ui/core/styles';
import Box from 'aws-northstar/layouts/Box';
import IconButton from '@material-ui/core/IconButton';
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
import LoadingIndicator from 'aws-northstar/components/LoadingIndicator';
import MenuIcon from '@material-ui/icons/Menu';
import Overlay from 'aws-northstar/components/Overlay';
import React, { FunctionComponent, ReactElement, ReactNode, useCallback } from 'react';
import clsx from 'clsx';

export * from './components';

export interface AppLayoutProviderProps {
  notificationConfig?: NotificationProviderProps;
  layoutConfig?: LayoutProviderProps;
}

export const AppLayoutProvider: FunctionComponent<AppLayoutProviderProps> = ({
  children,
  notificationConfig,
  layoutConfig,
}) => {
  return (
    <NotificationProvider {...notificationConfig}>
      <LayoutProvider {...layoutConfig}>
        <HelpProvider>{children}</HelpProvider>
      </LayoutProvider>
    </NotificationProvider>
  );
};

export interface AppLayoutProps extends AppLayoutProviderProps {
  /** The header */
  header: ReactNode;
  /** SideNavigation drawer. */
  navigation?: ReactElement<SideNavigationProps>;
  /** Breadcrumbs should be defined whithin this region in order to benefit from the responsive breadcrumb pattern. */
  breadcrumbs?: ReactNode;
  /** Whether to display in Progress global overlay */
  inProgress?: boolean;

  /**
   * Whether to render help panel
   * @default true
   */
  helpPanel?: boolean;

  /**
   * Height Of Header in pixel when custom header is used.
   * By default, 65px will be used for the <a href='/#/Components/Header'>NorthStar Header</a>. */
  headerHeightInPx?: number;
}

export const AppLayout: FunctionComponent<AppLayoutProps> = ({ children, ...props }) => {
  return (
    <AppLayoutProvider {...props}>
      <BaseAppLayout {...props}>{children}</BaseAppLayout>
    </AppLayoutProvider>
  );
};

/**
 * Basic layout for application, with place holder for header, navigation area, content area, breadcrumbs and tools/help panel.
 * It should be placed as the top most component in main content area. There should not be any spacing around it, it consumes
 * 100% of the width and height of the main content area, providing its own scrolling behavior.
 * By default it comes with a padding inside the content region. It can be removed by setting prop paddingContentArea == false.
 */
export const BaseAppLayout: FunctionComponent<AppLayoutProps> = ({
  children,
  header,
  navigation,
  breadcrumbs,
  inProgress = false,
  headerHeightInPx = 65,
  helpPanel = true,
}) => {
  const { layout, updateLayout, clientContext, contentRef } = useLayoutContext();

  const setIsSideNavigationOpen = useCallback(
    (open: boolean) => {
      updateLayout((draft) => {
        draft.sideNavigation = open;
      });
    },
    [updateLayout],
  );

  const setIsHelpPanelOpen = useCallback(
    (open: boolean) => {
      updateLayout((draft) => {
        draft.helpPanel = open;
      });
    },
    [updateLayout],
  );

  const classes = useStyles({
    sideNavigationState: layout.sideNavigation,
    helpPanelState: layout.helpPanel,
    inProgress,
    headerHeightInPx,
  });

  const renderNavigationIcon = useCallback(
    (rootClassname: string) => {
      return (
        <IconButton
          color="inherit"
          aria-label="open navigation drawer"
          data-testid="open-nav-drawer"
          onClick={() => setIsSideNavigationOpen(true)}
          classes={{
            root: clsx(rootClassname, classes.menu),
          }}
        >
          <MenuIcon />
        </IconButton>
      );
    },
    [classes, updateLayout, setIsHelpPanelOpen],
  );

  const renderInfoIcon = useCallback(
    (rootClassname: string) => {
      return (
        <IconButton
          color="inherit"
          aria-label="open help panel drawer"
          data-testid="open-helppanel-drawer"
          onClick={() => setIsHelpPanelOpen(true)}
          classes={{
            root: clsx(rootClassname, classes.menu),
          }}
        >
          <InfoOutlinedIcon />
        </IconButton>
      );
    },
    [classes, setIsHelpPanelOpen],
  );

  const { fullMode } = clientContext;

  return (
    <Box className={classes.root}>
      <Box className={classes.header}>
        {header}
        {!fullMode && (navigation || helpPanel) && (
          <Box className={classes.menuBar}>
            {navigation && <Box className={classes.menuBarNavIcon}>{renderNavigationIcon(classes.menuBarIcon)}</Box>}
            <Box width="100%" />
            {helpPanel && <Box className={classes.menuBarInfoIcon}>{renderInfoIcon(classes.menuBarIcon)}</Box>}
          </Box>
        )}
      </Box>
      <Box className={classes.main}>
        <Box className={classes.leftPane}>
          <Sidebar
            isOpen={layout.sideNavigation === true}
            setIsOpen={setIsSideNavigationOpen}
            type={SidebarType.SIDE_NAVIGATION}
            displayIcon={fullMode}
            renderIcon={renderNavigationIcon}
          >
            {navigation}
          </Sidebar>
        </Box>
        <div className={classes.content} ref={contentRef}>
          <NotificationsRenderer />
          <Box
            tabIndex={0}
            position="relative"
            className={clsx(classes.mainContent, {
              [classes.contentPadding]: layout.paddedContent,
            })}
          >
            {breadcrumbs && layout.breadcrumbs && <Box className={classes.breadcrumbsContainer}>{breadcrumbs}</Box>}
            <main>{children}</main>
            {inProgress && (
              <Overlay>
                <LoadingIndicator size="large" />
              </Overlay>
            )}
          </Box>
        </div>
        <Box className={classes.rightPane}>
          {helpPanel && (
            <Sidebar
              isOpen={layout.helpPanel === true}
              setIsOpen={setIsHelpPanelOpen}
              type={SidebarType.HELP_PANEL}
              displayIcon={fullMode}
              renderIcon={renderInfoIcon}
            >
              <ManagedHelpPanelRenderer />
            </Sidebar>
          )}
        </Box>
      </Box>
    </Box>
  );
};

export default AppLayout;

interface StyleProps {
  sideNavigationState: LayoutState;
  helpPanelState: LayoutState;
  inProgress: boolean;
  headerHeightInPx: number;
}

const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
  root: {
    height: '100vh',
    margin: '0',
    overflow: 'hidden',
    display: 'flex',
    flexDirection: 'column',
  },
  header: {
    flex: 0,
    height: ({ headerHeightInPx }) => headerHeightInPx,
  },
  main: {
    display: 'flex',
    flexDirection: 'row',
    flex: 1,
    height: ({ headerHeightInPx }) => `calc(100vh - ${headerHeightInPx}px)`,
  },
  content: {
    display: 'flex',
    flexDirection: 'column',
    flex: 1,
    // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
    minWidth: 100,
    minHeight: 100,
    overflow: 'auto',
    boxSizing: 'border-box',
  },
  contentPadding: {
    [theme.breakpoints.up('sm')]: {
      padding: `${theme.spacing(2)}px ${theme.spacing(4)}px`,
    },
  },
  leftPane: {
    position: 'relative',
    flex: 0,
    backgroundColor: theme.palette.background.paper,
  },
  rightPane: {
    position: 'relative',
    flex: 0,
    backgroundColor: theme.palette.background.paper,
  },
  mainContent: {
    display: 'flex',
    flexDirection: 'column',
    flexGrow: 1,
    justifySelf: 'stretch',
    alignSelf: 'stretch',
    // border: '2px dotted lime',
    '&:focus': {
      outline: 'none',
    },
    '& > main': {
      display: 'flex',
      flexDirection: 'column',
      flexGrow: 1,
      justifySelf: 'stretch',
      alignSelf: 'stretch',
      minWidth: 0,
      minHeight: 0,
      // border: '2px dashed red',

      // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
      '& div': {
        minWidth: 0,
        minHeight: 0,
        boxSizing: 'border-box',
      },
      '& .MuiTable-root *': {
        textOverflow: 'ellipsis',
        overflow: 'hidden',
      },
    },
  },
  breadcrumbsContainer: {
    flex: 0,
    display: 'none',
    marginBottom: theme.spacing(4),
    [theme.breakpoints.up('sm')]: {
      display: 'block',
    },
  },
  menu: {
    position: 'absolute',
    [theme.breakpoints.up('sm')]: {
      position: 'absolute',
      top: '20px',
      paddingRight: 0,
    },
  },
  menuBar: {
    display: 'flex',
    boxShadow: '0 2px 1px -1px rgba(0,28,36,.3)',
  },
  menuBarIcon: {
    padding: theme.spacing(2),
  },
  menuBarNavIcon: {
    flexShrink: 1,
    borderRight: `1px solid ${theme.palette.grey['400']}`,
  },
  menuBarInfoIcon: {
    flexShrink: 1,
    borderLeft: `1px solid ${theme.palette.grey['400']}`,
  },
}));