import { Fragment, useCallback, useMemo, useRef, useState } from 'react';

import SelectableListItem from '@composed-components/SelectableListItem';
import Button from '@design-system/Buttons/Button';
import Search from '@design-system/Inputs/Search';
import Menu from '@design-system/Menu';
import Skeleton from '@design-system/Skeleton';
import Tooltip from '@design-system/Tooltip';
import Stack from '@mui/material/Stack';
import { useTheme } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import { IconChevronDown, IconExclamationCircle } from '@tabler/icons-react';

import DefaultList from './components/DefaultList';
import { MENU_LIST_ITEMS_WIDTH } from './constants';
import {
  type BaseItemProps,
  type BaseListRendererProps,
  type MenuListItemsProps,
} from './types';
import {
  getTooltipTitle as defaultGetTooltipTitle,
  getTriggerTitle as defaultGetTriggerTitle,
} from './utils';

export type DefaultListRendererItemType = { id: number; name: string };

const defaultRenderList = <ItemType extends BaseItemProps>(
  props: BaseListRendererProps<ItemType>,
) => {
  return (
    <DefaultList
      items={props.items}
      selected={props.selected}
      maxSelection={props.maxSelection}
      onItemClick={props.onItemClick}
      disabled={props.disabled}
    />
  );
};

const MenuListItems = <ItemType extends DefaultListRendererItemType>({
  disabled,
  error,
  errorText,
  getTooltipTitle = defaultGetTooltipTitle,
  getTriggerTitle = defaultGetTriggerTitle,
  items,
  loading,
  maxSelection,
  onChange,
  renderList = defaultRenderList,
  showTooltip = true,
  slotProps,
  sx,
  title,
  value = [],
  showSelection: initialShowSelection = false,
  onSearchChange,
}: MenuListItemsProps<ItemType>) => {
  const theme = useTheme();
  const [showSelection, setShowSelection] = useState(false);
  const [searchValue, setSearchValue] = useState('');
  const anchorRef = useRef<HTMLButtonElement>(null);
  const [open, setOpen] = useState(false);

  const selected = useMemo(() => new Set(value.map(item => item.id)), [value]);

  const filteredItems = useMemo(() => {
    if (onSearchChange) {
      // If `onSearchChange` is provided, use it to filter the items
      return items?.filter(item =>
        showSelection
          ? selected.has(item.id)
          : item.name.toLowerCase().includes(searchValue.toLowerCase()),
      );
    }

    return (
      items?.filter(item =>
        !showSelection
          ? item.name.toLowerCase().includes(searchValue.toLowerCase())
          : selected.has(item.id) &&
            item.name.toLowerCase().includes(searchValue.toLowerCase()),
      ) || []
    );
  }, [items, searchValue, showSelection, selected]);

  const selectedItems = useMemo(() => {
    // When onSearchChange is provided, items only contains current search results
    // and won't include selected items that don't match the current search term.
    // value already holds the full selected item objects, so use it directly.
    if (onSearchChange) return value ?? [];
    return items.filter(item => selected.has(item.id));
  }, [items, selected, value, onSearchChange]);

  const handleOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
    setSearchValue('');
  };

  const handleMenuItemClick = useCallback(
    (optionId: number) => {
      if (optionId === 0) {
        setShowSelection(prev => !prev);
        return;
      }

      const newSet = new Set(selected);

      if (newSet.size === 1 && maxSelection && maxSelection === 1) {
        setOpen(false);
        const selectedItem = items.find(item => item.id === optionId);
        onChange?.(selectedItem ? [selectedItem] : []);
        return;
      }

      if (newSet.has(optionId)) {
        newSet.delete(optionId);
      } else {
        if (maxSelection && newSet.size >= maxSelection) {
          return;
        }

        newSet.add(optionId);
      }

      if (newSet.size === 0) {
        setShowSelection(false);
      }

      const newSelectedItems = Array.from(newSet)
        .map(
          id =>
            items.find(item => item.id === id) ??
            (value ?? []).find(item => item.id === id),
        )
        .filter(Boolean) as ItemType[];

      onChange?.(newSelectedItems);

      if (maxSelection && maxSelection === 1) {
        setOpen(false);
      }
    },
    [maxSelection, items, onChange, selected, value],
  );

  const handleSearchChange = useCallback(
    (newSeachValue: string) => {
      setSearchValue(newSeachValue);
      if (onSearchChange) {
        onSearchChange(newSeachValue);
      }
    },
    [onSearchChange],
  );

  const tooltipTitle = useMemo(
    () => getTooltipTitle?.(selectedItems) || title,
    [selectedItems],
  );

  const triggerTitle = useMemo(
    () => getTriggerTitle(selectedItems),
    [selectedItems],
  );

  if (loading) {
    return (
      <Skeleton
        variant="text"
        sx={{ flex: 1 }}
        height={58}
      />
    );
  }

  const TriggerTitleWrapper = showTooltip ? Tooltip : Fragment;

  const shouldShowSelection = items.length > 10 && initialShowSelection;

  return (
    <Stack
      sx={{
        flexDirection: 'row',
        alignItems: 'center',
        gap: 1,
        flex: 1,
        minWidth: '0',
        ...(disabled && {
          cursor: 'not-allowed !important',
        }),
        ...sx,
      }}
    >
      <Button
        ref={anchorRef}
        onClick={handleOpen}
        variant="secondary"
        endIcon={<IconChevronDown size={16} />}
        disabled={disabled}
        sx={{
          flex: 1,
          minWidth: '0',
        }}
        {...slotProps?.triggerButton}
      >
        <TriggerTitleWrapper
          description={tooltipTitle}
          disableTooltip={!tooltipTitle}
          delay={300}
          slotProps={{
            popper: {
              disablePortal: false,
            },
          }}
          {...slotProps?.triggerTooltip}
        >
          <Stack
            sx={{
              display: 'block',
              overflow: 'hidden',
              textOverflow: 'ellipsis',
              whiteSpace: 'nowrap',
              textAlign: 'start',
              alignItems: 'flex-start',
              justifyContent: 'flex-start',
              maxWidth: '100%',
            }}
          >
            {triggerTitle}
          </Stack>
        </TriggerTitleWrapper>
      </Button>
      {error && errorText && (
        <Tooltip
          description={errorText}
          disableTooltip={!errorText}
          direction="top"
        >
          <IconExclamationCircle
            color={theme.palette.new.text.feedback.error}
          />
        </Tooltip>
      )}
      <Menu
        open={open}
        onClose={handleClose}
        anchorEl={anchorRef.current}
        sx={{
          width: MENU_LIST_ITEMS_WIDTH,
          '& ul.MuiStack-root': {
            overflowX: 'auto',
          },
          ...slotProps?.menu?.sx,
        }}
        {...slotProps?.menu}
      >
        <Stack
          sx={{
            padding: 2,
            backgroundColor: theme.palette.new.background.elements.default,
            flex: 0,
            position: 'sticky',
            top: 0,
            left: 0,
            zIndex: 10,
            borderTopLeftRadius: 2,
            borderTopRightRadius: 2,
          }}
        >
          <Search
            onChange={handleSearchChange}
            value={searchValue}
            variant="custom"
          />
        </Stack>

        {shouldShowSelection && (
          <SelectableListItem
            id="show-selection"
            selected={showSelection}
            onSelect={() => setShowSelection(prev => !prev)}
            {...slotProps?.showSelection}
            sx={{
              paddingY: 2,
              backgroundColor: theme.palette.new.background.elements.default,
              ...slotProps?.showSelection?.sx,
            }}
          >
            {slotProps?.showSelection?.children || (
              <Typography
                variant="globalS"
                sx={{ fontWeight: 'fontWeightSemiBold' }}
              >
                Show Selection
              </Typography>
            )}
          </SelectableListItem>
        )}

        {renderList({
          items: filteredItems,
          selected,
          maxSelection,
          onItemClick: handleMenuItemClick,
          showSelection,
          disabled: Boolean(
            maxSelection && maxSelection > 1 && selected.size >= maxSelection,
          ),
        })}
      </Menu>
    </Stack>
  );
};

export type { MenuListItemsProps };

export default MenuListItems;
