import { Cell } from '@tanstack/react-table';
import { useEffect, useRef, useState } from 'react';
import Select, { GroupBase, SelectInstance, StylesConfig } from 'react-select';
import { MultiSelectMetaData } from '../types';
import { customToast } from '../../toast';
import { areArraysSame } from '../helpers';
import { useFocusHandler } from '../focus-handler/focus-handler-provider';
import { useTableContext } from '../table-context/table-context-provider';
import { CellTypes } from '../constants';
import { replaceSpacesWithHyphens } from '../../_utils/utils';
import { commonColors } from '@trail-ui/theme';

interface EditableMultiSelect<TData> {
  cell: Cell<TData, unknown>;
  onCancel: () => void;
}

export default function EditableMultiSelect<TData>({ cell, onCancel }: EditableMultiSelect<TData>) {
  const [value, setValue] = useState<MultiSelectMetaData<TData>['options'][0]['value'][]>([]);
  const [initialValue, setInitialValue] = useState<
    MultiSelectMetaData<TData>['options'][0]['value'][]
  >([]);
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  const selectRef =
    useRef<
      SelectInstance<
        MultiSelectMetaData<TData>['options'][0],
        true,
        GroupBase<MultiSelectMetaData<TData>['options'][0]>
      >
    >(null);

  const { updateData, tableRef, tableName, doesRowHeaderExist } = useTableContext<TData>(cell);
  const { isCurrentCellFocused } = useFocusHandler({
    cellType: CellTypes.dataCell,
    columnIndex: cell.column.getIndex(),
    groupId: null,
    rowId: cell.row.id,
    rowIndex: cell.row.index,
  });

  const metaData = cell.column.columnDef.meta as MultiSelectMetaData<TData>;

  const customStyles: StylesConfig<MultiSelectMetaData<TData>['options'][0], true> = {
    control: (base, state) => ({
      ...base,
      borderColor: state.isFocused ? '#5928ED' : '#ccc',
      boxShadow: state.isFocused ? '0 0 0 1px #5928ED' : 'none',
      '&:hover': {
        borderColor: '#5928ED',
      },
    }),
    option: (base, state) => ({
      ...base,
      cursor: 'pointer',
      backgroundColor: state.isSelected
        ? '#5928ED'
        : state.isFocused
          ? commonColors.purple[100]
          : 'white',
      color: state.isSelected ? 'white' : '#000',
      ':active': {
        backgroundColor: '#5928ED66',
      },
    }),
  };

  const cancelAndExit = () => {
    setValue(cell.getValue() as MultiSelectMetaData<TData>['options'][0]['value'][]);
    requestAnimationFrame(() => {
      onCancel();
    });
  };

  const saveAndExit = () => {
    const isEmpty = !areArraysSame(value, initialValue) && value.length === 0;
    // throw error if the value is empty
    if (isEmpty) {
      customToast('Value cannot be empty', 'error');
      cancelAndExit();
      return;
    }

    if (!areArraysSame(value, initialValue)) {
      updateData(value);
    }

    requestAnimationFrame(() => {
      onCancel();
    });
  };

  function onKeyDown(e: React.KeyboardEvent) {
    if (e.key === 'Enter') {
      e.preventDefault();
      // throw error if value is empty
      saveAndExit();
    } else if (e.key === 'Escape' && !isMenuOpen) {
      cancelAndExit();
      e.preventDefault();
      e.stopPropagation();
    }
  }

  useEffect(() => {
    setInitialValue(cell.getValue() as MultiSelectMetaData<TData>['options'][0]['value'][]);
    setValue(cell.getValue() as MultiSelectMetaData<TData>['options'][0]['value'][]);
    selectRef.current?.focus();

    const inputElement = selectRef.current?.inputRef;
    if (inputElement) {
      inputElement.removeAttribute('aria-readonly');
      inputElement.removeAttribute('aria-autocomplete');
      inputElement.setAttribute('aria-haspopup', 'listbox');
    }
  }, []);

  useEffect(() => {
    if (isCurrentCellFocused && tableRef.current?.contains(document.activeElement)) {
      selectRef.current?.focus();
    }
  }, [isCurrentCellFocused]);

  useEffect(() => {
    function saveData(e: MouseEvent) {
      if (
        !selectRef.current?.menuListRef?.contains(e.target as Node) &&
        !selectRef.current?.inputRef?.contains(e.target as Node) &&
        !selectRef.current?.controlRef?.contains(e.target as Node)
      ) {
        if (areArraysSame(initialValue, value)) {
          cancelAndExit();
        } else {
          saveAndExit();
        }
      }
    }

    document.addEventListener('mousedown', saveData);

    return () => document.removeEventListener('mousedown', saveData);
  }, [initialValue, value]);

  return (
    <div>
      <p role="alert" className="sr-only" aria-live="polite">
        Use Up and Down arrow keys to navigate through the options, press Space to select the
        currently focused option, press delete/backspace to deselect an option, press Escape to
        close the multiselect dropdown or exit the editing mode, press Enter to save the selected
        option and exit the editing mode.
      </p>

      <Select<MultiSelectMetaData<TData>['options'][0], true>
        ref={selectRef}
        {...(metaData.label
          ? { label: metaData.label(cell.row.original) }
          : {
              'aria-labelledby': `${doesRowHeaderExist ? cell.row.id : ''} ${replaceSpacesWithHyphens(tableName)}-${cell.column.getIndex()}`,
            })}
        placeholder={metaData.placeholder}
        styles={{
          ...customStyles,
          ...(metaData.customStyles || {}),
        }}
        className={`max-w-[${cell.column.columnDef.size}px] w-full shadow-red-500 focus:outline-purple-700 focus:[outline-width:1px]`}
        isMulti={true}
        isSearchable={false}
        isClearable={false}
        closeMenuOnSelect={false}
        tabSelectsValue={false}
        menuPosition="fixed"
        defaultValue={{ label: '', value: '' }}
        value={metaData.options.filter((opt) => value.includes(opt.value))}
        onChange={(selectedOptions) => {
          setValue(selectedOptions.map((opt) => opt.value));
        }}
        onKeyDown={onKeyDown}
        options={metaData.options}
        onMenuClose={() => setIsMenuOpen(false)}
        onMenuOpen={() => setIsMenuOpen(true)}
      />
    </div>
  );
}
