"use client";

import * as React from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import ListItemIcon from "@mui/material/ListItemIcon";
import Checkbox from "@mui/material/Checkbox";
import IconButton from "@mui/material/Fab";
import Divider from "@mui/material/Divider";
import ChevronLeft from "@mui/icons-material/ChevronLeft";
import ChevronRight from "@mui/icons-material/ChevronRight";
import intersection from "lodash/intersection";
import differenceBy from "lodash/differenceBy";
import Stack from "@mui/material/Stack";
import Paper from "@mui/material/Paper";
import Typography from "@mui/material/Typography";
import ListSubheader from "@mui/material/ListSubheader";
import groupBy from "lodash/groupBy";
import uniqBy from "lodash/uniqBy";
import { useTranslation } from "react-i18next";
import union from "lodash/union";
import intersectionBy from "lodash/intersectionBy";

export interface LabelGroup {
  id: number;
  name: string;
}

export interface LabelWithId {
  id: number;
  label: string;
  group?: LabelGroup;
}

export interface TransferListProps {
  allItems: LabelWithId[];
  selectedItems: LabelWithId[];
  onChange: (selectedItems: LabelWithId[]) => void;
  allItemsLabel: string;
  selectedItemsLabel: string;
}

interface ItemListProps {
  items: LabelWithId[];
  checkedItems: LabelWithId[];
  label: string;
  handleToggleAll: (
    items: LabelWithId[],
    checkedItems: LabelWithId[],
  ) => React.MouseEventHandler;
  handleToggleAllInGroup: (
    groupItems: LabelWithId[],
    checkedItems: LabelWithId[],
  ) => React.MouseEventHandler;
  handleToggle: (item: LabelWithId) => React.MouseEventHandler;
}

const ItemList: React.FC<ItemListProps> = ({
  items,
  checkedItems,
  label,
  handleToggle,
  handleToggleAll,
  handleToggleAllInGroup,
}) => {
  const { t } = useTranslation();
  const groups: LabelGroup[] = uniqBy(
    [
      ...items.reduce<LabelGroup[]>((acc, item) => {
        return item.group ? [...acc, item.group] : acc;
      }, []),
      // Items with no group
      { id: 0, name: t("Ungrouped") },
    ],
    "id",
  );
  const groupedItems = groupBy(items, (item) => item.group?.id ?? 0);

  return (
    <Paper sx={{ width: "100%" }} variant="outlined">
      <List
        sx={{
          height: 400,
          bgcolor: "background.paper",
          overflow: "auto",
        }}
        disablePadding
        dense
        component="ul"
        subheader={
          <ListSubheader
            sx={{ zIndex: 2 }}
            disableGutters
            component="li"
            onClick={handleToggleAll(items, checkedItems)}
          >
            <Stack direction="row" paddingY={1} paddingX={2}>
              <ListItemIcon>
                <Checkbox
                  checked={
                    checkedItems.length === items.length && items.length !== 0
                  }
                  indeterminate={
                    checkedItems.length !== items.length &&
                    checkedItems.length !== 0
                  }
                  disabled={items.length === 0}
                />
              </ListItemIcon>
              <Stack>
                <Typography variant="subtitle2">{label}</Typography>
                <Typography variant="caption">{`${checkedItems.length}/${
                  items.length
                } ${t("selected")}`}</Typography>
              </Stack>
            </Stack>
            <Divider />
          </ListSubheader>
        }
      >
        {groups.map((group) => {
          const groupItems = groupedItems[group.id];
          const selectedGroupItems = intersectionBy(
            checkedItems,
            groupItems,
            "id",
          );
          if (!groupItems) return null;

          return (
            <React.Fragment key={group.id}>
              <ListSubheader
                disableSticky
                onClick={handleToggleAllInGroup(groupItems, checkedItems)}
                sx={{
                  paddingBlock: 2,
                  lineHeight: 1.8,
                  display: "flex",
                  alignItems: "center",
                }}
              >
                <ListItemIcon>
                  <Checkbox
                    checked={selectedGroupItems.length === groupItems.length}
                    indeterminate={
                      selectedGroupItems.length !== groupItems.length &&
                      selectedGroupItems.length !== 0
                    }
                  />
                </ListItemIcon>
                {group.name}
              </ListSubheader>
              {groupItems.map((item) => {
                return (
                  <ListItem key={item.id} onClick={handleToggle(item)}>
                    <ListItemIcon>
                      <Checkbox
                        checked={checkedItems.includes(item)}
                        tabIndex={-1}
                      />
                    </ListItemIcon>
                    <ListItemText primary={item.label} />
                  </ListItem>
                );
              })}
            </React.Fragment>
          );
        })}
      </List>
    </Paper>
  );
};

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

  const [checkedList, setCheckedList] = React.useState<LabelWithId[]>([]);
  const [leftList, setLeftList] = React.useState<LabelWithId[]>(
    differenceBy(allItems, selectedItems, "id"),
  );

  React.useEffect(() => {
    setLeftList(differenceBy(allItems, selectedItems, "id"));
  }, [allItems, selectedItems]);

  const rightList = selectedItems;

  const leftListChecked = intersection(checkedList, leftList);
  const rightListChecked = intersection(checkedList, rightList);

  const handleToggle = React.useCallback(
    (value: LabelWithId) => () => {
      const isChecked = checkedList.includes(value);
      const newCheckedList = isChecked
        ? differenceBy(checkedList, [value], "id")
        : [...checkedList, value];

      setCheckedList(newCheckedList);
    },
    [checkedList],
  );

  const handleToggleAll = React.useCallback(
    (items: LabelWithId[], checkedItems: LabelWithId[]) => () => {
      if (checkedItems.length === items.length) {
        setCheckedList(differenceBy(checkedList, checkedItems, "id"));
      } else {
        setCheckedList([...checkedList, ...items]);
      }
    },
    [checkedList],
  );

  const handleCheckedRight = () => {
    onChange([...selectedItems, ...leftListChecked].sort(compareFn));
    setLeftList(differenceBy(leftList, leftListChecked, "id").sort(compareFn));
    setCheckedList(differenceBy(checkedList, leftListChecked, "id"));
  };

  const handleCheckedLeft = () => {
    setLeftList([...leftList, ...rightListChecked].sort(compareFn));
    onChange(differenceBy(rightList, rightListChecked, "id").sort(compareFn));
    setCheckedList(differenceBy(checkedList, rightListChecked, "id"));
  };

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

  return (
    <Stack spacing={2} direction="row" alignItems="center" position="relative">
      <ItemList
        items={leftList}
        checkedItems={leftListChecked}
        label={allItemsLabel}
        handleToggleAll={handleToggleAll}
        handleToggle={handleToggle}
        handleToggleAllInGroup={handleToggleAllInGroup}
      />
      <ItemList
        items={rightList}
        checkedItems={rightListChecked}
        label={selectedItemsLabel}
        handleToggleAll={handleToggleAll}
        handleToggle={handleToggle}
        handleToggleAllInGroup={handleToggleAllInGroup}
      />
      <Stack
        spacing={1}
        position="absolute"
        margin="0 !important"
        left="50%"
        sx={{ transform: "translateX(-50%)" }}
      >
        <IconButton
          color="secondary"
          size="small"
          onClick={handleCheckedRight}
          disabled={leftListChecked.length === 0}
        >
          <ChevronRight />
        </IconButton>
        <IconButton
          color="secondary"
          size="small"
          onClick={handleCheckedLeft}
          disabled={rightListChecked.length === 0}
        >
          <ChevronLeft />
        </IconButton>
      </Stack>
    </Stack>
  );
};

export default TransferList;
