import React, { useEffect, useRef, useState } from 'react';
import isNull from 'lodash/isNull';

import Button from '@leafygreen-ui/button';
import { css, cx } from '@leafygreen-ui/emotion';
import { usePrevious } from '@leafygreen-ui/hooks';
// leafygreen-ui
// @ts-expect-error
import PlusIcon from '@leafygreen-ui/icon/dist/Plus';
import LeafyGreenProvider, {
  useDarkMode,
} from '@leafygreen-ui/leafygreen-provider';
import {
  FocusableMenuItem,
  Menu,
  MenuItem,
  MenuSeparator,
} from '@leafygreen-ui/menu';

import { useMongoNavContext } from '../../MongoNavContext';
// mongo-nav
import { useOnElementClick } from '../../on-element-click-provider';
import {
  CurrentProjectInterface,
  Mode,
  MongoNavInterface,
  NavElement,
  ProjectInterface,
  URLS,
} from '../../types';
// mongo-select
import MongoSelectInput from '../mongo-select-helpers/Input';
import { onKeyDown } from '../mongo-select-helpers/onKeyDown';
import {
  menuContainerStyle,
  menuItemContainerStyle,
  searchInputStyle,
} from '../styles';
import { BaseMongoSelectProps } from '../types';

import {
  buttonHoverThemeStyle,
  emptyStateStyle,
  menuSeparatorStyles,
  projectButtonContainerStyle,
  projectMenuContainerStyle,
} from './ProjectSelect.styles';
import { ProjectSelectTrigger } from './ProjectSelectTrigger';

// types
interface ProjectMongoSelectProps extends BaseMongoSelectProps {
  data?: Array<ProjectInterface>;
  current?: CurrentProjectInterface;
  constructProjectURL: NonNullable<MongoNavInterface['constructProjectURL']>;
  urls: Partial<URLS['mongoSelect']>;
}

interface ProjectFilterResponse {
  cid: string;
  gn: string;
}

function ProjectSelect({
  current,
  mode,
  urls,
  data = [],
  onChange: onChangeProp,
  constructProjectURL,
  hosts = {},
  admin = false,
  className,
  ...rest
}: ProjectMongoSelectProps) {
  const [value, setValue] = useState('');
  const [filteredData, setFilteredData] = useState(data);
  const [isFetching, setIsFetching] = useState(false);
  const [open, setOpen] = useState(false);
  const wasOpen = usePrevious(open);
  const onElementClick = useOnElementClick();
  const { darkMode, theme } = useDarkMode();
  const { isLoading } = useMongoNavContext();
  const buttonRef = useRef<HTMLButtonElement>(null);

  const isFiltered = value !== '';
  const isAdminSearch = isFiltered && admin && mode === Mode.Production;

  const isDisabled = isLoading || isNull(current);

  const renderedData = isFiltered ? filteredData : data;

  const toggleOpen = () => {
    setOpen(curr => !curr);
    if (!open) {
      setValue('');
    }
  };

  const hasOnChangeProp = !!onChangeProp;

  useEffect(() => {
    const filterData = () => {
      const normalizedValue = value.toLowerCase();
      const filtered = data?.filter(
        datum =>
          datum.projectName.toLowerCase().indexOf(normalizedValue) !== -1,
      );

      setFilteredData(filtered);
    };

    // Skip if we're not filtering
    if (!isFiltered) {
      return;
    }

    // defer all behavior to user-provided filtering
    if (hasOnChangeProp) {
      return;
    }

    // serverside filtering (admin users only)
    if (isAdminSearch) {
      setIsFetching(true);

      const controller = new AbortController();

      const fetchProjectsAsAdmin = (searchTerm = '') => {
        const queryString = searchTerm ? `?term=${searchTerm}` : '';
        const endpointURI = `${hosts.cloud}/user/shared/projects/search${queryString}`;
        return fetch(endpointURI, {
          credentials: 'include',
          mode: 'cors',
          method: 'GET',
          signal: controller.signal,
        });
      };

      fetchProjectsAsAdmin(value)
        .then(response => response.json())
        .then((response: Array<ProjectFilterResponse>) => {
          const data = response.map(({ cid: projectId, gn: projectName }) => ({
            projectId,
            projectName,
          }));

          setFilteredData(data);
          setIsFetching(false);
        })
        .catch(console.error);

      return () => {
        controller.abort();
      };
    }

    // clientside filtering (default)
    filterData();
  }, [isFiltered, hasOnChangeProp, isAdminSearch, hosts.cloud, data, value]);

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setValue(value);

    if (open === true && wasOpen !== open) {
      // Opt out of type-checking as we just want to trigger the onElementClick handler
      onElementClick(NavElement.ProjectNavProjectSelectSearch)(e as any);
    }

    return onChangeProp?.({
      value,
      setData: setFilteredData,
      event: e,
    });
  };

  const renderProjectOption = (datum: ProjectInterface) => {
    const { projectId, projectName } = datum;
    const isActive = projectId ? projectId === current?.projectId : false;

    return (
      <MenuItem
        data-testid="project-option"
        key={projectId}
        active={isActive}
        className={cx(menuItemContainerStyle)}
        href={constructProjectURL(datum)}
        onKeyDown={onKeyDown}
      >
        {projectName}
      </MenuItem>
    );
  };

  return (
    <div
      className={cx(
        css`
          position: relative;
        `,
        className,
      )}
    >
      <ProjectSelectTrigger
        ref={buttonRef}
        open={open}
        onClick={toggleOpen}
        loading={isLoading}
        disabled={isDisabled}
        current={current}
        {...rest}
      />
      <Menu
        open={open}
        setOpen={toggleOpen}
        usePortal={false}
        refEl={buttonRef}
        spacing={8}
        maxHeight={384}
        justify="start"
        data-testid="project-select-project-list"
        className={cx(menuContainerStyle, projectMenuContainerStyle)}
      >
        <FocusableMenuItem>
          <MongoSelectInput
            data-testid="project-filter-input"
            variant="project"
            onChange={onChange}
            onKeyDown={onKeyDown}
            value={value}
            className={searchInputStyle}
          />
        </FocusableMenuItem>

        <div role="none">
          {isAdminSearch && isFetching && (
            <MenuItem className={emptyStateStyle}>Searching...</MenuItem>
          )}
          {!isFetching && renderedData.length === 0 && (
            <MenuItem className={emptyStateStyle}>No matches found.</MenuItem>
          )}
          {renderedData?.map(renderProjectOption)}
        </div>

        <MenuSeparator className={menuSeparatorStyles} />

        <li
          onKeyDown={onKeyDown}
          role="none"
          className={projectButtonContainerStyle}
        >
          <LeafyGreenProvider darkMode={!darkMode}>
            <FocusableMenuItem>
              <Button
                size="small"
                href={urls.viewAllProjects as string}
                data-testid="project-select-view-all-projects"
                onClick={onElementClick(NavElement.ProjectNavViewAllProjects)}
                className={buttonHoverThemeStyle[theme]}
              >
                View All Projects
              </Button>
            </FocusableMenuItem>
            <FocusableMenuItem>
              <Button
                size="small"
                href={urls.newProject as string}
                data-testid="project-select-add-new-project"
                onClick={onElementClick(NavElement.ProjectNavAddProject)}
                leftGlyph={<PlusIcon />}
                className={buttonHoverThemeStyle[theme]}
              >
                New Project
              </Button>
            </FocusableMenuItem>
          </LeafyGreenProvider>
        </li>
      </Menu>
    </div>
  );
}

ProjectSelect.displayName = 'ProjectSelect';

export default ProjectSelect;
