import {
  Add as MUIAddIcon,
  Delete as MUIDeleteIcon,
  Done as MUIDoneIcon,
  Edit as MUIEditIcon,
} from '@mui/icons-material';
import {
  Box as MUIBox,
  List as MUIList,
  ListItem as MUIListItem,
  ListItemButton,
  ListItemIcon as MUIListItemIcon,
  SxProps,
  Theme,
} from '@mui/material';
import {
  FC,
  ForwardedRef,
  forwardRef,
  useCallback,
  useImperativeHandle,
  useMemo,
  useState,
} from 'react';
import { v4 as uuidv4 } from 'uuid';

import { BLUE, BORDER_RADIUS, GREY, RED } from '../lib/consts/DEFAULT_THEME';

import Checkbox from './Checkbox';
import Divider from './Divider';
import FlexBox from './FlexBox';
import IconButton from './IconButton';
import { BodyText } from './Text';

const List = forwardRef(
  <T,>(
    {
      allowCheckAll: isAllowCheckAll = false,
      allowEdit: isAllowEdit = false,
      allowItemButton: isAllowItemButton = false,
      disableDelete = false,
      edit: isEdit = false,
      flexBoxProps,
      getListItemCheckboxProps,
      header,
      headerSpacing = '.3em',
      initialCheckAll = false,
      insertHeader: isInsertHeader = true,
      listEmpty,
      listItemIconMinWidth = '56px',
      listItemKeyPrefix = uuidv4(),
      listItemProps: { sx: listItemSx, ...restListItemProps } = {},
      listItems,
      listProps: { sx: listSx, ...restListProps } = {},
      onAdd,
      onDelete,
      onEdit,
      onAllCheckboxChange,
      onItemCheckboxChange,
      onItemClick,
      renderListItem = (key) => <BodyText>{key}</BodyText>,
      renderListItemCheckboxState,
      scroll: isScroll = false,
      // Input props that depend on other input props.
      allowAddItem: isAllowAddItem = isAllowEdit,
      allowCheckItem: isAllowCheckItem = isAllowEdit,
      allowDelete: isAllowDelete = isAllowEdit,
      allowEditItem: isAllowEditItem = isAllowEdit,
    }: ListProps<T>,
    ref: ForwardedRef<ListForwardedRefContent>,
  ) => {
    const [isCheckAll, setIsCheckAll] = useState<boolean>(initialCheckAll);

    const checkAllMinWidth = useMemo(
      () => `calc(${listItemIconMinWidth} - ${headerSpacing})`,
      [headerSpacing, listItemIconMinWidth],
    );

    const addItemButton = useMemo(
      () =>
        isAllowAddItem ? (
          <IconButton onClick={onAdd} size="small">
            <MUIAddIcon />
          </IconButton>
        ) : undefined,
      [isAllowAddItem, onAdd],
    );
    const deleteItemButton = useMemo(
      () =>
        isEdit && isAllowDelete ? (
          <IconButton
            disabled={disableDelete}
            onClick={onDelete}
            size="small"
            sx={{
              backgroundColor: RED,
              color: GREY,

              '&:hover': { backgroundColor: `${RED}F0` },
            }}
          >
            <MUIDeleteIcon />
          </IconButton>
        ) : undefined,
      [disableDelete, isAllowDelete, isEdit, onDelete],
    );
    const editItemButton = useMemo(() => {
      if (isAllowEditItem) {
        return (
          <IconButton onClick={onEdit} size="small">
            {isEdit ? <MUIDoneIcon sx={{ color: BLUE }} /> : <MUIEditIcon />}
          </IconButton>
        );
      }

      return undefined;
    }, [isAllowEditItem, isEdit, onEdit]);
    const checkAllElement = useMemo(() => {
      let element;

      if (isEdit && isAllowCheckItem) {
        element = isAllowCheckAll ? (
          <MUIBox sx={{ minWidth: checkAllMinWidth }}>
            <Checkbox
              checked={isCheckAll}
              edge="start"
              onChange={(...args) => {
                const [, isChecked] = args;

                onAllCheckboxChange?.call(null, ...args);
                setIsCheckAll(isChecked);
              }}
            />
          </MUIBox>
        ) : (
          <Divider sx={{ minWidth: checkAllMinWidth }} />
        );
      }

      return element;
    }, [
      checkAllMinWidth,
      isAllowCheckAll,
      isAllowCheckItem,
      isCheckAll,
      isEdit,
      onAllCheckboxChange,
    ]);
    const headerElement = useMemo(() => {
      const headerType = typeof header;

      return isInsertHeader && header ? (
        <FlexBox row spacing={headerSpacing} sx={{ height: '2.4em' }}>
          {checkAllElement}
          {['boolean', 'string'].includes(headerType) ? (
            <>
              {headerType === 'string' && <BodyText>{header}</BodyText>}
              <Divider sx={{ flexGrow: 1 }} />
            </>
          ) : (
            header
          )}
          {deleteItemButton}
          {editItemButton}
          {addItemButton}
        </FlexBox>
      ) : (
        header
      );
    }, [
      addItemButton,
      checkAllElement,
      deleteItemButton,
      editItemButton,
      header,
      headerSpacing,
      isInsertHeader,
    ]);
    const listEmptyElement = useMemo(
      () =>
        typeof listEmpty === 'string' ? (
          <BodyText align="center">{listEmpty}</BodyText>
        ) : (
          listEmpty
        ),
      [listEmpty],
    );

    const listItemCheckbox = useCallback(
      (key: string, checked?: boolean, props?: CheckboxProps) =>
        isEdit && isAllowCheckItem ? (
          <MUIListItemIcon sx={{ minWidth: listItemIconMinWidth }}>
            <Checkbox
              checked={checked}
              edge="start"
              onChange={(...args) =>
                onItemCheckboxChange?.call(null, key, ...args)
              }
              {...props}
            />
          </MUIListItemIcon>
        ) : undefined,
      [isAllowCheckItem, isEdit, listItemIconMinWidth, onItemCheckboxChange],
    );

    const listItemElements = useMemo(() => {
      let result = listEmptyElement;

      if (listItems) {
        const entries = Object.entries(listItems);

        if (entries.length > 0) {
          result = entries.map(([key, value]) => {
            const listItem = renderListItem(key, value);

            return (
              <MUIListItem
                {...restListItemProps}
                key={`${listItemKeyPrefix}-${key}`}
                sx={{ paddingLeft: 0, paddingRight: 0, ...listItemSx }}
              >
                {listItemCheckbox(
                  key,
                  renderListItemCheckboxState?.call(null, key, value),
                  getListItemCheckboxProps?.call(null, key, value),
                )}
                {isAllowItemButton ? (
                  <ListItemButton
                    onClick={(...args) => {
                      onItemClick?.call(null, value, key, ...args);
                    }}
                    sx={{ borderRadius: BORDER_RADIUS }}
                  >
                    {listItem}
                  </ListItemButton>
                ) : (
                  listItem
                )}
              </MUIListItem>
            );
          });
        }
      }

      return result;
    }, [
      listEmptyElement,
      listItems,
      renderListItem,
      restListItemProps,
      listItemKeyPrefix,
      listItemSx,
      listItemCheckbox,
      renderListItemCheckboxState,
      getListItemCheckboxProps,
      isAllowItemButton,
      onItemClick,
    ]);
    const listScrollSx: SxProps<Theme> | undefined = useMemo(
      () => (isScroll ? { maxHeight: '100%', overflowY: 'scroll' } : undefined),
      [isScroll],
    );

    useImperativeHandle(
      ref,
      () => ({
        setCheckAll: (value) => setIsCheckAll(value),
      }),
      [],
    );

    return (
      <FlexBox spacing={0} {...flexBoxProps}>
        {headerElement}
        <MUIList
          {...restListProps}
          sx={{ paddingBottom: 0, paddingTop: 0, ...listScrollSx, ...listSx }}
        >
          {listItemElements}
        </MUIList>
      </FlexBox>
    );
  },
);

List.displayName = 'List';

export default List as <T>(
  props: ListProps<T> & { ref?: ForwardedRef<ListForwardedRefContent> },
) => ReturnType<FC<ListProps<T>>>;
