import { Component, createRef } from 'react';
import styles from '@patternfly/react-styles/css/components/Toolbar/toolbar';
import { css } from '@patternfly/react-styles';
import { ToolbarContext } from './ToolbarUtils';
import { ToolbarLabelGroupContent } from './ToolbarLabelGroupContent';
import { formatBreakpointMods, canUseDOM, getBreakpoint } from '../../helpers/util';
import { getResizeObserver } from '../../helpers/resizeObserver';
import { getOUIAProps, OUIAProps } from '../../helpers';
import { SSRSafeIds } from '../../helpers/SSRSafeIds/SSRSafeIds';
import { PageContext } from '../Page/PageContext';

export enum ToolbarColorVariant {
  default = 'default',
  primary = 'primary',
  secondary = 'secondary',
  noBackground = 'no-background'
}

export interface ToolbarProps extends React.HTMLProps<HTMLDivElement>, OUIAProps {
  /** Optional callback for clearing all filters in the toolbar */
  clearAllFilters?: () => void;
  /** Text to display in the clear all filters button */
  clearFiltersButtonText?: string;
  /** Custom content appended to the filter generated label group. To maintain spacing and styling, each node should be wrapped in a ToolbarItem or ToolbarGroup. This property will remove the default "Clear all filters" button. */
  customLabelGroupContent?: React.ReactNode;
  /** The breakpoint at which the listed filters in label groups are collapsed down to a summary */
  collapseListedFiltersBreakpoint?: 'all' | 'md' | 'lg' | 'xl' | '2xl';
  /** Flag indicating if a data toolbar toggle group's expandable content is expanded */
  isExpanded?: boolean;
  /** A callback for setting the isExpanded flag */
  toggleIsExpanded?: () => void;
  /** Classes applied to root element of the data toolbar */
  className?: string;
  /** Content to be rendered as rows in the data toolbar */
  children?: React.ReactNode;
  /** Id of the data toolbar */
  id?: string;
  /** Flag indicating if the toolbar is a container for CSS container queries */
  isContainer?: boolean;
  /** Flag indicating the toolbar height should expand to the full height of the container */
  isFullHeight?: boolean;
  /** Flag indicating the toolbar is static */
  isStatic?: boolean;
  /** Flag indicating the toolbar should stick to the top of its container. This property applies both the sticky position and styling. */
  isSticky?: boolean;
  /** @beta Flag indicating the toolbar should have sticky positioning to the top of its container */
  isStickyBase?: boolean;
  /** @beta Flag indicating the toolbar should have stuck styling, when the toolbar is not at the top of the scroll container */
  isStickyStuck?: boolean;
  /** @beta Flag indicating the toolbar has a vertical orientation */
  isVertical?: boolean;
  /** Insets at various breakpoints. */
  inset?: {
    default?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl';
    sm?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl';
    md?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl';
    lg?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl';
    xl?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl';
    '2xl'?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl';
  };
  /** Text to display in the total number of applied filters ToolbarFilter */
  numberOfFiltersText?: (numberOfFilters: number) => string;
  /** Value to overwrite the randomly generated data-ouia-component-id.*/
  ouiaId?: number | string;
  /** Set the value of data-ouia-safe. Only set to true when the component is in a static state, i.e. no animations are occurring. At all other times, this value must be false. */
  ouiaSafe?: boolean;
  /** Background color variant of the toolbar */
  colorVariant?: ToolbarColorVariant | 'default' | 'no-background' | 'primary' | 'secondary';
  /** Flag indicating the toolbar padding is removed */
  hasNoPadding?: boolean;
}

export interface ToolbarState {
  /** Flag used if the user has opted NOT to manage the 'isExpanded' state of the toggle group.
   *  Indicates whether or not the toggle group is expanded. */
  isManagedToggleExpanded: boolean;
  /** Object managing information about how many labels are in each label group */
  filterInfo: FilterInfo;
  /** Used to keep track of window width so we can collapse expanded content when window is resizing */
  windowWidth: number;
}

interface FilterInfo {
  [key: string]: number;
}

class Toolbar extends Component<ToolbarProps, ToolbarState> {
  static displayName = 'Toolbar';
  labelGroupContentRef = createRef<HTMLDivElement>();
  toolbarRef = createRef<HTMLDivElement>();
  resizeObserver: () => void = () => {};
  containerBreakpoint: ReturnType<typeof getBreakpoint>;
  staticFilterInfo = {};
  hasNoPadding = false;
  state = {
    isManagedToggleExpanded: false,
    filterInfo: {},
    windowWidth: 1200
  };

  isToggleManaged = () => !(this.props.isExpanded || !!this.props.toggleIsExpanded);

  toggleIsExpanded = () => {
    this.setState((prevState) => ({
      isManagedToggleExpanded: !prevState.isManagedToggleExpanded
    }));
  };

  closeExpandableContent = (e?: any) => {
    if (e && e.target.innerWidth !== this.state.windowWidth) {
      this.setState(() => ({
        isManagedToggleExpanded: false,
        windowWidth: e.target.innerWidth
      }));
    }
  };

  closeExpandableContentOnContainerResize = () => {
    const containerWidth = this.toolbarRef.current?.clientWidth;
    if (!containerWidth) {
      return;
    }

    const breakpoint = getBreakpoint(containerWidth);
    const isInitialMeasurement = this.containerBreakpoint === undefined;
    if (breakpoint !== this.containerBreakpoint) {
      this.containerBreakpoint = breakpoint;
      if (!isInitialMeasurement && this.state.isManagedToggleExpanded) {
        this.setState({ isManagedToggleExpanded: false });
      }
    }
  };

  setupResizeHandling = () => {
    if (!this.isToggleManaged() || !canUseDOM) {
      return;
    }

    this.containerBreakpoint = undefined;
    const reference = this.props.isContainer ? this.toolbarRef.current : undefined;
    const handler = this.props.isContainer ? this.closeExpandableContentOnContainerResize : this.closeExpandableContent;

    this.resizeObserver = getResizeObserver(reference, handler, true);
  };

  cleanupResizeHandling = () => {
    this.resizeObserver();
    this.resizeObserver = () => {};
  };

  componentDidMount() {
    if (canUseDOM) {
      this.setState({ windowWidth: window.innerWidth });
    }
    this.setupResizeHandling();
  }

  componentDidUpdate(prevProps: ToolbarProps) {
    const wasToggleManaged = !(prevProps.isExpanded || !!prevProps.toggleIsExpanded);
    const isToggleManaged = this.isToggleManaged();

    if (prevProps.isContainer !== this.props.isContainer || wasToggleManaged !== isToggleManaged) {
      this.cleanupResizeHandling();
      this.setupResizeHandling();
    }
  }

  componentWillUnmount() {
    this.cleanupResizeHandling();
  }

  updateNumberFilters = (categoryName: string, numberOfFilters: number) => {
    const filterInfoToUpdate: FilterInfo = { ...this.staticFilterInfo };
    if (!filterInfoToUpdate.hasOwnProperty(categoryName) || filterInfoToUpdate[categoryName] !== numberOfFilters) {
      filterInfoToUpdate[categoryName] = numberOfFilters;
      this.staticFilterInfo = filterInfoToUpdate;
      this.setState({ filterInfo: filterInfoToUpdate });
    }
  };

  getNumberOfFilters = () =>
    Object.values(this.state.filterInfo).reduce((acc: any, cur: any) => acc + cur, 0) as number;

  renderToolbar = (randomId: string, generatedOuiaId?: string) => {
    const {
      hasNoPadding,
      clearAllFilters,
      clearFiltersButtonText,
      collapseListedFiltersBreakpoint,
      isExpanded: isExpandedProp,
      toggleIsExpanded,
      className,
      children,
      isContainer,
      isFullHeight,
      isStatic,
      isStickyBase,
      isStickyStuck,
      inset,
      isSticky,
      isVertical,
      ouiaId,
      numberOfFiltersText,
      customLabelGroupContent,
      colorVariant = ToolbarColorVariant.default,
      ...props
    } = this.props;

    const { isManagedToggleExpanded } = this.state;

    const isToggleManaged = this.isToggleManaged();
    const isExpanded = isToggleManaged ? isManagedToggleExpanded : isExpandedProp;
    const numberOfFilters = this.getNumberOfFilters();
    const showClearFiltersButton = numberOfFilters > 0;

    return (
      <PageContext.Consumer>
        {(pageContext) => {
          const toolbarContent = (
            <div
              className={css(
                styles.toolbar,
                hasNoPadding && styles.modifiers.noPadding,
                isContainer && styles.modifiers.container,
                isFullHeight && styles.modifiers.fullHeight,
                isStatic && styles.modifiers.static,
                isSticky && styles.modifiers.sticky,
                isStickyBase && styles.modifiers.stickyBase,
                isStickyStuck && styles.modifiers.stickyStuck,
                isVertical && styles.modifiers.vertical,
                formatBreakpointMods(
                  inset,
                  styles,
                  '',
                  !isContainer ? pageContext.getBreakpoint(pageContext.width) : undefined
                ),
                colorVariant === 'primary' && styles.modifiers.primary,
                colorVariant === 'secondary' && styles.modifiers.secondary,
                colorVariant === 'no-background' && styles.modifiers.noBackground,
                className
              )}
              id={randomId}
              ref={this.toolbarRef}
              {...getOUIAProps(Toolbar.displayName, ouiaId !== undefined ? ouiaId : generatedOuiaId)}
              {...props}
            >
              <ToolbarContext.Provider
                value={{
                  isExpanded,
                  toggleIsExpanded: isToggleManaged ? this.toggleIsExpanded : toggleIsExpanded,
                  labelGroupContentRef: this.labelGroupContentRef,
                  updateNumberFilters: this.updateNumberFilters,
                  numberOfFilters,
                  clearAllFilters,
                  clearFiltersButtonText,
                  showClearFiltersButton,
                  toolbarId: randomId,
                  customLabelGroupContent,
                  isContainer,
                  toolbarRef: this.toolbarRef
                }}
              >
                {children}
                <ToolbarLabelGroupContent
                  isExpanded={isExpanded}
                  labelGroupContentRef={this.labelGroupContentRef}
                  clearAllFilters={clearAllFilters}
                  showClearFiltersButton={showClearFiltersButton}
                  clearFiltersButtonText={clearFiltersButtonText}
                  numberOfFilters={numberOfFilters}
                  numberOfFiltersText={numberOfFiltersText}
                  collapseListedFiltersBreakpoint={collapseListedFiltersBreakpoint}
                  customLabelGroupContent={customLabelGroupContent}
                />
              </ToolbarContext.Provider>
            </div>
          );

          return isContainer ? (
            <PageContext.Provider value={{ ...pageContext, width: null }}>{toolbarContent}</PageContext.Provider>
          ) : (
            toolbarContent
          );
        }}
      </PageContext.Consumer>
    );
  };

  render() {
    return (
      <SSRSafeIds prefix="pf-random-id-" ouiaComponentType="Toolbar">
        {(generatedId, generatedOuiaId) => this.renderToolbar(this.props.id || generatedId, generatedOuiaId)}
      </SSRSafeIds>
    );
  }
}

export { Toolbar };
