import React, {
  CSSProperties,
  FormEvent,
  forwardRef,
  MutableRefObject,
  ReactElement,
  Ref,
  useEffect,
  useRef,
} from "react";
import classnames from "classnames";
import FocusLock from "react-focus-lock";
import {
  Manager as PopperManager,
  Reference as PopperReference,
  Popper,
} from "react-popper-2";
import { bem } from "../../utilities/bem";
import { useOutsideClick, useKeyPress } from "../../hooks";
import { Icon, ICON_TYPE } from "../Icon";
import { Flex } from "../Flex";
import { Box } from "../Box";
import { Portal } from "../Portal";
import { PopoverMenu } from "../PopoverMenu";
import { Heading } from "../Heading";
import { Input, INPUT_ICON_POSITION } from "../Input";
import { Link } from "../Link";
import { Divider } from "../Divider";
import { ORIENTATION, KEY_CODE, PopperPlacement } from "../../types";
import { CheckboxProps } from "../Checkbox";

const cn = "TableHeaderCell";

// Typescript can't actually enforce children having certain props,
// so this interface doesn't work. TODO: Handle this some other way
interface FilterProps extends CheckboxProps {
  label: string;
}
type ElementRefType = MutableRefObject<HTMLElement | undefined>;

interface FilterDropdownMenuButtonProps {
  onSetVisible: (isVisible: boolean) => void;
}

interface FilterDropdownButtonProps {
  isVisible: boolean;
  filterApplied: boolean;
}

interface FilterDropdownMenuProps {
  hideFilterSearch: boolean;
  searchInputValue: string;
  onFilterInputChange: (e: FormEvent<HTMLInputElement>) => void;
  onFilterSelectAll: () => void;
  onFilterClear: () => void;
  filterOptions?: ReactElement<FilterProps>[];
  filterControl?: ReactElement;
}

interface FilterDropdownProps
  extends FilterDropdownMenuButtonProps,
    FilterDropdownMenuProps,
    FilterDropdownButtonProps {
  className?: string;
}

interface FilterMenuProps
  extends FilterDropdownMenuButtonProps,
    FilterDropdownMenuProps {
  menuRef: ElementRefType;
  buttonRef: ElementRefType;
  placement: PopperPlacement;
  style: CSSProperties;
  className?: string;
}

interface FilterButtonProps
  extends FilterDropdownMenuButtonProps,
    FilterDropdownButtonProps {}

const FilterToggleButton = forwardRef(
  (
    { onSetVisible, isVisible, filterApplied }: FilterButtonProps,
    ref: Ref<HTMLButtonElement>,
  ) => {
    const handleButtonClick = () => {
      onSetVisible(!isVisible);
    };

    return (
      <button
        type="button"
        ref={ref}
        onClick={handleButtonClick}
        className={bem(cn, { e: "iconButton" })}
      >
        <Icon
          icon={ICON_TYPE.FILTER}
          className={classnames(bem(cn, { e: "filterIcon" }), {
            isActive: isVisible || filterApplied,
          })}
        />
      </button>
    );
  },
);

const FilterMenu = forwardRef(
  (
    {
      style,
      placement,
      onSetVisible,
      menuRef,
      buttonRef,
      hideFilterSearch,
      searchInputValue,
      onFilterInputChange,
      onFilterSelectAll,
      onFilterClear,
      filterOptions,
      filterControl,
      className,
    }: FilterMenuProps,
    ref: Ref<HTMLDivElement>,
  ) => {
    const closeDropdown = () => {
      onSetVisible(false);
    };

    useOutsideClick([menuRef, buttonRef], () => {
      closeDropdown();
    });

    useKeyPress(KEY_CODE.ESC, () => {
      closeDropdown();
    });

    const inputRef: Ref<HTMLInputElement> = useRef(null);
    useEffect(() => {
      if (inputRef.current !== null) {
        inputRef.current.focus();
      }
    }, []);

    return (
      <PopoverMenu
        className={classnames(bem(cn, { e: "filterMenu" }), className)}
        ref={ref}
        style={style}
        data-placement={placement}
      >
        <FocusLock autoFocus>
          <Flex className={bem(cn, { e: "filterMenuHeader" })}>
            <Heading.H4>Filter</Heading.H4>
            <Flex
              as="button"
              className={bem(cn, { e: "closeFilter" })}
              onClick={closeDropdown}
            >
              <Icon icon={ICON_TYPE.TIMES} />
            </Flex>
          </Flex>
          {!hideFilterSearch && (
            <>
              <Input
                type="text"
                icon={ICON_TYPE.SEARCH}
                iconPosition={INPUT_ICON_POSITION.RIGHT}
                className={bem(cn, { e: "filterMenuInput" })}
                placeholder="Search"
                value={searchInputValue}
                onChange={onFilterInputChange}
                ref={inputRef}
              />

              <Flex className={bem(cn, { e: "filterMenuLinksWrapper" })}>
                <Link
                  as="button"
                  onClick={onFilterSelectAll}
                  className={bem(cn, { e: "filterMenuLink" })}
                >
                  Select All
                </Link>
                <Divider
                  direction={ORIENTATION.VERTICAL}
                  className={bem(cn, { e: "filterMenuLinkDivider" })}
                />
                <Link
                  as="button"
                  onClick={onFilterClear}
                  className={bem(cn, { e: "filterMenuLink" })}
                >
                  Clear
                </Link>
              </Flex>
            </>
          )}
          <Box className={bem(cn, { e: "filtersScroller" })}>
            <Box className={bem(cn, { e: "filtersWrapper" })}>
              {filterOptions || filterControl}
            </Box>
          </Box>
        </FocusLock>
      </PopoverMenu>
    );
  },
);

export const FilterDropdown = ({
  isVisible = false,
  filterApplied = false,
  onSetVisible,
  searchInputValue,
  onFilterInputChange,
  onFilterSelectAll,
  onFilterClear,
  filterOptions,
  filterControl,
  hideFilterSearch,
  className,
}: FilterDropdownProps) => {
  const menuRef: ElementRefType = React.useRef();
  const buttonRef: ElementRefType = React.useRef();
  const getPopperElement = (popperElement: HTMLDivElement) => {
    menuRef.current = popperElement;
  };
  const getButtonElement = (buttonElement: HTMLButtonElement) => {
    buttonRef.current = buttonElement;
  };

  return (
    <PopperManager>
      {isVisible && (
        <Portal>
          <Popper
            placement="bottom"
            innerRef={getPopperElement}
            modifiers={[
              {
                name: "offset",
                options: {
                  offset: [0, 5],
                },
              },
            ]}
          >
            {({ ref, placement, style }) => (
              <FilterMenu
                ref={ref}
                buttonRef={buttonRef}
                menuRef={menuRef}
                placement={placement}
                className={className}
                style={style}
                onSetVisible={onSetVisible}
                searchInputValue={searchInputValue}
                onFilterInputChange={onFilterInputChange}
                onFilterSelectAll={onFilterSelectAll}
                onFilterClear={onFilterClear}
                filterOptions={filterOptions}
                filterControl={filterControl}
                hideFilterSearch={hideFilterSearch}
              />
            )}
          </Popper>
        </Portal>
      )}
      <PopperReference innerRef={getButtonElement}>
        {({ ref }) => (
          <FilterToggleButton
            ref={ref}
            onSetVisible={onSetVisible}
            isVisible={isVisible}
            filterApplied={filterApplied}
          />
        )}
      </PopperReference>
    </PopperManager>
  );
};
