"use client";

import {
  Box,
  Checkbox,
  Divider,
  FormControl,
  InputLabel,
  ListItemIcon,
  ListItemText,
  ListSubheader,
  MenuItem,
  Select,
  SelectChangeEvent,
  Stack,
  Typography,
} from "@mui/material";
import React, { useCallback } from "react";
import { LabelGroup, LabelWithId, TransferListProps } from "./TransferList";
import { useTranslation } from "react-i18next";
import uniqBy from "lodash/uniqBy";
import groupBy from "lodash/groupBy";
import intersectionBy from "lodash/intersectionBy";
import union from "lodash/union";
import differenceBy from "lodash/differenceBy";

export const MultiSelectList: React.FC<TransferListProps> = ({
  allItems,
  selectedItems,
  selectedItemsLabel,
  allItemsLabel,
  onChange,
}) => {
  // Keep a map for the original order of items
  const sortMap = React.useMemo(() => {
    return allItems.reduce<{ [id: string]: LabelWithId & { index: number } }>(
      (acc, item, index) => ({ ...acc, [item.id]: { ...item, index } }),
      {},
    );
  }, [allItems]);
  const compareFn = React.useCallback(
    (a: number, b: number) => sortMap[a].index - sortMap[b].index,
    [sortMap],
  );

  const handleChange = useCallback(
    (event: SelectChangeEvent<number[]>) => {
      const {
        target: { value },
      } = event;
      const selectedValues =
        typeof value === "string" ? [Number(value)] : value;

      onChange(allItems.filter((item) => selectedValues.includes(item.id)));
    },
    [allItems, onChange],
  );

  const handleToggleAll = useCallback(() => {
    if (selectedItems.length === allItems.length) {
      onChange([]);
    } else {
      onChange(allItems);
    }
  }, [allItems, onChange, selectedItems.length]);

  const handleToggleAllInGroup = useCallback(
    (groupItems: LabelWithId[]) => () => {
      const selectedGroupItems = intersectionBy(
        selectedItems,
        groupItems,
        "id",
      );
      if (selectedGroupItems.length !== groupItems.length) {
        onChange(union(selectedItems, groupItems));
      } else {
        onChange(differenceBy(selectedItems, groupItems, "id"));
      }
    },
    [onChange, selectedItems],
  );

  const { t } = useTranslation();
  const groups: LabelGroup[] = uniqBy(
    [
      ...allItems.reduce<LabelGroup[]>((acc, item) => {
        return item.group ? [...acc, item.group] : acc;
      }, []),
      // Items with no group
      { id: 0, name: t("Ungrouped") },
    ],
    "id",
  );
  const groupedItems = groupBy(allItems, (item) => item.group?.id ?? 0);

  return (
    <Box>
      <FormControl fullWidth>
        <InputLabel>{selectedItemsLabel}</InputLabel>
        <Select
          multiple
          value={selectedItems.map((item) => item.id)}
          onChange={handleChange}
          renderValue={(values) => {
            return (
              <Stack spacing={2}>
                {values.toSorted(compareFn).map((value) => (
                  <Typography key={value} whiteSpace="normal">
                    {sortMap[value]?.label}
                  </Typography>
                ))}
              </Stack>
            );
          }}
          MenuProps={{
            slotProps: {
              paper: {
                sx: { maxHeight: 400 },
              },
            },
            anchorOrigin: {
              vertical: "top",
              horizontal: "left",
            },
            transformOrigin: {
              vertical: "top",
              horizontal: "left",
            },
          }}
        >
          <ListSubheader disableGutters sx={{ zIndex: 1 }}>
            <Stack
              direction="row"
              paddingY={1}
              paddingX={3}
              onClick={handleToggleAll}
            >
              <ListItemIcon>
                <Checkbox
                  disableRipple
                  checked={
                    selectedItems.length === allItems.length &&
                    allItems.length !== 0
                  }
                  indeterminate={
                    selectedItems.length !== allItems.length &&
                    selectedItems.length !== 0
                  }
                />
              </ListItemIcon>
              <Stack>
                <Typography variant="subtitle2">{allItemsLabel}</Typography>
                <Typography variant="caption">{`${selectedItems.length}/${allItems.length} selected`}</Typography>
              </Stack>
            </Stack>
            <Divider />
          </ListSubheader>
          {groups.flatMap((group) => {
            const groupItems = groupedItems[group.id];
            const selectedGroupItems = intersectionBy(
              selectedItems,
              groupItems,
              "id",
            );
            if (!groupItems) return null;

            return [
              <ListSubheader disableSticky key={`${group.id}-${group.name}`}>
                <Stack
                  onClick={handleToggleAllInGroup(groupItems)}
                  direction="row"
                  paddingX={1}
                >
                  <Checkbox
                    disableRipple
                    checked={selectedGroupItems.length === groupItems.length}
                    indeterminate={
                      selectedGroupItems.length !== groupItems.length &&
                      selectedGroupItems.length !== 0
                    }
                  />
                  {group.name}
                </Stack>
              </ListSubheader>,
              ...groupItems.map((item) => {
                return (
                  <MenuItem key={item.id} value={item.id} disableRipple>
                    <Checkbox
                      checked={Boolean(
                        selectedItems.find(
                          (selected) => selected.id === item.id,
                        ),
                      )}
                      disableRipple
                    />
                    <ListItemText sx={{ whiteSpace: "normal" }}>
                      {item.label}
                    </ListItemText>
                  </MenuItem>
                );
              }),
            ];
          })}
        </Select>
      </FormControl>
    </Box>
  );
};

export default MultiSelectList;
