import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Datalist } from '../datalist/Datalist';
import { DatalistRow } from '../datalist/DatalistRow';
import { DatalistCol } from '../datalist/DatalistCol';
import { SearchBar } from '../../molecules/searchBar/SearchBar';
import { Button } from '../../atoms/button/Button';
import { Icon } from '../../atoms/icon/Icon';
import { FormControlSelect } from '../../atoms/formControlSelect/FormControlSelect';
import { DatalistColActions } from '../datalist/DatalistColActions';
import { FormCheck } from '../../atoms/formCheck/FormCheck';
import { PagePopin } from '../pagePopin/PagePopin';
import { PagePopinActionButton } from '../pagePopin/PagePopinActionButton';
import { SearchEmptyState } from '../../molecules/searchEmptyState/SearchEmptyState';
import { EmptyState } from '../../molecules/emptyState/EmptyState';
import { Modal } from '../modal/Modal';
import { ModalBody } from '../modal/ModalBody';
import { ModalCloseButton } from '../modal/ModalCloseButton';
import { ModalHeader } from '../modal/ModalHeader';
import { ModalTitle } from '../modal/ModalTitle';

const propTypes = {
  /**
   * The array of items that are available to the seletion.
   */
  items: PropTypes.arrayOf(PropTypes.object),

  /**
   * The array of items that are already selected.
   */
  addedItems: PropTypes.arrayOf(PropTypes.object),

  /**
   * The search filter function.
   */
  searchFilter: PropTypes.func,

  /**
   * The search bar placeholder.
   */
  searchPlaceholder: PropTypes.string,

  /**
   * Tell to the component to clear the SearchBar on new item addition.
   */
  isClearingSearchOnAdd: PropTypes.bool,

  /**
   * Tell to the component that it should hide the checkboxes and removal buttons.
   */
  isReadOnly: PropTypes.bool,

  /**
   * Tell to the component to focus the SearchBar on mount.
   */
  isFocusingSearchOnMount: PropTypes.bool,

  /**
   * The label of the button that toggle the addition form.
   */
  addButtonLabel: PropTypes.node,

  /**
   * The label when there is no more items to add.
   */
  addButtonLabelEmpty: PropTypes.node,

  /**
   * The title of the addition form.
   */
  addFormTitle: PropTypes.node,

  /**
   * The placeholder of the addition form control select.
   */
  addFormSelectPlaceholder: PropTypes.string,

  /**
   * Function that takes an item, and should return the label that will be used
   * by the FormControlSelect.
   */
  addFormSelectLabelMapping: PropTypes.func,

  /**
   * The label of the submittion button of the addition form.
   */
  addFormButtonLabel: PropTypes.node,

  /**
   * Props to forward to empty state.
   */
  emptyStateProps: PropTypes.object,

  /**
   * Props to forward to search empty state.
   */
  searchEmptyStateProps: PropTypes.object,

  /**
   * Function that will give two parameter to render the row: item, index.
   */
  children: PropTypes.func,

  /**
   * The object's key that will be used in filter and map to identify the unicity of an object.
   * If the values in the object's key are not unique, the DatalistRow will encounter not unique
   * key problem.
   */
  uniqueObjectKey: PropTypes.string,

  /**
   * The component used for the root node.
   * Either a string to use a DOM element or a component.
   */
  tag: PropTypes.elementType,

  /**
   * The callback function called on items addition. The items are given as
   * a parameter.
   */
  onAdd: PropTypes.func,
  /**
   * The callback function called on items removal. The items are given as a
   * parameter.
   */
  onRemove: PropTypes.func,

  /**
   * Callback when the addition form is opening.
   */
  onAddFormOpen: PropTypes.func,

  /**
   * Callback when the addition form is closing.
   */
  onAddFormClose: PropTypes.func,

  /**
   * Object that will be given to PagePopin.
   */
  pagePopinProps: PropTypes.object,

  /**
   * The delay of the highlight of newly added items.
   */
  highlightDelay: PropTypes.number,
};

const defaultProps = {
  items: [],
  addedItems: [],
  searchFilter: () => true,
  searchPlaceholder: 'Find...',
  isClearingSearchOnAdd: false,
  isFocusingSearchOnMount: false,
  isReadOnly: false,
  addButtonLabel: 'Add items',
  addButtonLabelEmpty: 'No items to add',
  addFormTitle: '',
  addFormSelectPlaceholder: 'Search to add...',
  addFormSelectLabelMapping: null,
  addFormButtonLabel: 'Add',
  emptyStateProps: { content: 'No items available' },
  searchEmptyStateProps: { entity: 'item' },
  children: () => {},
  tag: 'div',
  onAdd: () => {},
  onRemove: () => {},
  onAddFormOpen: () => {},
  onAddFormClose: () => {},
  pagePopinProps: {},
  highlightDelay: 1000,
  uniqueObjectKey: 'id',
};

/**
 * Set the focus on an input when the condition is met.
 * @param {import('react').RefObject} ref The ref to focus when the condition is met.
 * @param {boolean} condition The condition to met for the focus to be set.
 */
const useFocus = (ref, condition = true) => {
  useEffect(() => {
    if (condition && ref && ref.current) {
      ref.current.focus();
    }
  }, [ref.current, condition]);
};

/**
 * Tell if an array or a string is empty or not.
 * @param {Array<any>|string} element The array or string to check the length
 */
const isEmpty = (element) => (element.length === 0);

export const TransferDatalist = ({
  items,
  addedItems,
  uniqueObjectKey,
  searchFilter,
  searchPlaceholder,
  isClearingSearchOnAdd,
  isReadOnly,
  isFocusingSearchOnMount,
  addButtonLabel,
  addButtonLabelEmpty,
  addFormTitle,
  addFormSelectPlaceholder,
  addFormSelectLabelMapping,
  addFormButtonLabel,
  emptyStateProps,
  searchEmptyStateProps,
  children,
  onAdd,
  onRemove,
  onAddFormOpen,
  onAddFormClose,
  pagePopinProps,
  tag: Tag,
  highlightDelay,
}) => {
  const [search, setSearch] = useState('');
  const [isAddFormShown, setIsAddFormShown] = useState(false);
  const [itemsToAdd, setItemsToAdd] = useState([]);
  const [itemsToRemove, setItemsToRemove] = useState([]);
  const [activeItems, setActiveItems] = useState([]);
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  const addFormSelectRef = useRef(undefined);
  const searchRef = useRef(undefined);

  // Handle the focus of the FormControlSelect
  useFocus(addFormSelectRef, isAddFormShown);
  // Handle the focus of the SearchBar
  useFocus(searchRef, !isAddFormShown && isFocusingSearchOnMount);

  // Handle the blue border effect on newly added items.
  useEffect(() => {
    let timer;

    if (activeItems.length) {
      timer = setTimeout(() => setActiveItems([]), highlightDelay);
    }

    return () => {
      clearTimeout(timer);
    };
  }, [activeItems]);

  const classes = classnames(
    'sui-o-transfer-datalist',
    isAddFormShown ? 'as--add-form-open' : '',
  );

  const filteredAddedItems = addedItems.filter((item) => searchFilter(item, search));

  const areItemsEqual = (item) => (it) => it[uniqueObjectKey] === item[uniqueObjectKey];
  const areItemsNotEqual = (item) => (it) => it[uniqueObjectKey] !== item[uniqueObjectKey];

  const getItemsWithoutItem = (item) => (itemList) => itemList.filter(areItemsNotEqual(item));

  const availableItems = items
    .filter((item) => addedItems.every(areItemsNotEqual(item)))
    .map((item) => ({
      label: addFormSelectLabelMapping
        ? addFormSelectLabelMapping(item) : item[uniqueObjectKey],
      value: item[uniqueObjectKey],
      item,
    }));

  const handleAddFormOpen = () => {
    setIsAddFormShown(true);
    onAddFormOpen();
  };

  const handleAddFormClose = () => {
    setIsAddFormShown(false);
    onAddFormClose();
  };

  const handleAdd = () => {
    const newItems = itemsToAdd.map((item) => item.item);
    onAdd(newItems);
    setItemsToAdd([]);
    handleAddFormClose();
    setActiveItems(newItems);

    if (isClearingSearchOnAdd) {
      setSearch('');
    }
  };

  const handleRemove = (item) => {
    onRemove([item]);
    setItemsToRemove(getItemsWithoutItem(item));
  };

  const handleRemoveBulk = () => {
    onRemove(itemsToRemove);
    setItemsToRemove([]);
  };

  const handleCheckItem = (item, isChecked) => {
    if (isChecked) {
      setItemsToRemove((s) => [...s, item]);
    } else {
      setItemsToRemove(getItemsWithoutItem(item));
    }
  };

  const isSelectAllChecked = () => {
    const mapAndSort = (a) => a.map((item) => item[uniqueObjectKey]).sort();

    const addedItemsuniqueValues = mapAndSort(filteredAddedItems);
    const itemsToRemoveUniqueValue = mapAndSort(itemsToRemove);

    if (isEmpty(addedItemsuniqueValues)) {
      return false;
    }

    return JSON.stringify(addedItemsuniqueValues) === JSON.stringify(itemsToRemoveUniqueValue);
  };

  const handleSelectAll = () => {
    if (isSelectAllChecked()) {
      setItemsToRemove([]);
    } else {
      setItemsToRemove(filteredAddedItems);
    }
  };

  const handleKeyDown = (e) => {
    if (!isMenuOpen && e.key === 'Enter') {
      handleAdd();
    }
  };

  const isSelectAllIndeterminate = () => (
    itemsToRemove.length > 0 && itemsToRemove.length < filteredAddedItems.length
  );

  const isTabIndexable = (condition) => (condition ? '' : '-1');

  return (
    <>
      <Modal isOpen={isAddFormShown} onClose={handleAddFormClose} size="lg" isClickOutsideDisabled>
        <ModalHeader>
          <ModalCloseButton />

          <ModalTitle>{addFormTitle}</ModalTitle>
        </ModalHeader>

        <ModalBody>
          <div className="sui-g-grid as--auto@xs as--bottom as--no-wrap@xs">
            <div className="sui-g-grid__item as--fill@xs sui-o-transfer-datalist__form-grid-item-select">
              <FormControlSelect
                menuPortalTarget={document.body}
                options={availableItems}
                placeholder={addFormSelectPlaceholder}
                value={itemsToAdd}
                onChange={(values) => setItemsToAdd(values)}
                onKeyDown={handleKeyDown}
                onMenuOpen={() => setIsMenuOpen(true)}
                onMenuClose={() => setIsMenuOpen(false)}
                isMulti
                ref={addFormSelectRef}
                tabIndex={isTabIndexable(isAddFormShown)}
                data-testid="transfer-datalist-form-control-select"
              />
            </div>

            <div className="sui-g-grid__item as--no-flex@xs">
              <Button
                color="primary"
                onClick={handleAdd}
                tabIndex={isTabIndexable(isAddFormShown)}
                minWidth="lg"
                data-testid="transfer-datalist-add-button"
              >
                {addFormButtonLabel}
              </Button>
            </div>
          </div>
        </ModalBody>
      </Modal>

      <Tag className={classes}>
        <div className="sui-o-transfer-datalist__header">
          <div className="sui-o-transfer-datalist__header-actions">
            <FormCheck
              className="sui-h-m-none"
              disabled={isEmpty(filteredAddedItems) || isReadOnly}
              isIndeterminate={isSelectAllIndeterminate()}
              checked={isSelectAllChecked()}
              onChange={handleSelectAll}
              name="transfer-datalist-select-all-form-check"
            />

            {!isEmpty(addedItems) && (
              <SearchBar
                placeholder={searchPlaceholder}
                onChange={setSearch}
                resultsLength={filteredAddedItems.length}
                value={search}
                ref={searchRef}
                data-testid="transfer-datalist-search-bar"
              />
            )}
          </div>

          <div className="sui-o-transfer-datalist__header-add">
            {availableItems && availableItems.length && !isReadOnly ? (
              <Button
                color="secondary"
                onClick={handleAddFormOpen}
                isContextual
                data-testid="transfer-datalist-open-add-modal-button"
              >
                <Icon name="plus" size="sm" position="start" />

                {addButtonLabel}
              </Button>
            ) : (
              <div className="sui-o-transfer-datalist__header-add-empty">
                {addButtonLabelEmpty}
              </div>
            )}
          </div>
        </div>
        <div className="sui-o-transfer-datalist__content">
          {filteredAddedItems.length > 0 && (
            <Datalist>
              {filteredAddedItems.map((item, index) => (
                <DatalistRow
                  isActive={!!activeItems.find(areItemsEqual(item))}
                  key={item[uniqueObjectKey]}
                  align="middle"
                >
                  <DatalistCol size="xs" level="primary">
                    <FormCheck
                      className="sui-h-m-none"
                      onChange={(e) => handleCheckItem(item, e.target.checked)}
                      checked={!!itemsToRemove.find(areItemsEqual(item))}
                      disabled={isReadOnly}
                      name={`transfer-datalist-form-check-${item[uniqueObjectKey]}`}
                    />
                  </DatalistCol>

                  {children(item, index)}

                  <DatalistColActions size="sm">
                    <Button
                      size="sm"
                      onClick={() => handleRemove(item)}
                      isSquare
                      isDisabled={isReadOnly}
                      data-testid="transfer-datalist-remove-element-button"
                    >
                      <Icon name="minus" />
                    </Button>
                  </DatalistColActions>
                </DatalistRow>
              ))}
            </Datalist>
          )}

          {(isEmpty(filteredAddedItems) && !isEmpty(search)) && (
            <SearchEmptyState className="sui-o-transfer-datalist__content-empty" searchTerm={search} {...searchEmptyStateProps} />
          )}

          {(isEmpty(filteredAddedItems) && isEmpty(search)) && (
            <EmptyState className="sui-o-transfer-datalist__content-empty" {...emptyStateProps} />
          )}
        </div>
      </Tag>

      <PagePopin
        selected={itemsToRemove.length}
        onClose={() => setItemsToRemove([])}
        {...pagePopinProps}
        actions={(
          <PagePopinActionButton
            icon={<Icon name="minus" />}
            onClick={handleRemoveBulk}
            data-testid="transfer-datalist-page-popin-button"
          >
            Remove
          </PagePopinActionButton>
        )}
      />
    </>
  );
};

TransferDatalist.propTypes = propTypes;
TransferDatalist.defaultProps = defaultProps;
