import { useCallback, useMemo, useReducer } from 'react';

import { type SegmentationGroupType } from '@material-hu/components/composed-components/peopleExperience/types';

import {
  type FilterState,
  type GroupMetadata,
  type SelectAllState,
  type SerializedFilterState,
} from './types';
import {
  filterReducer,
  getSelectAllState,
  getSelectedCount,
  getSelectedItemsFromMetadata,
  isItemSelected,
  serializeFilterState,
} from './utils';

function useFilterState(
  groupsData: GroupMetadata[],
  appliedFilters: SerializedFilterState,
) {
  const [state, dispatch] = useReducer(
    filterReducer,
    appliedFilters,
    (initial: SerializedFilterState): FilterState => ({
      selectedItems: new Map(
        Object.entries(initial.selectedItems).map(([key, value]) => [
          Number(key),
          new Set(value),
        ]),
      ),
      selectAllGroups: new Set(initial.selectAllGroups),
      deselectedItems: new Map(
        Object.entries(initial.deselectedItems).map(([key, value]) => [
          Number(key),
          new Set(value),
        ]),
      ),
      expandedGroups: new Set(
        Object.entries(initial.selectedItems)
          .filter(
            ([key, value]) =>
              value.length || initial.selectAllGroups.includes(Number(key)),
          )
          .map(([key]) => Number(key)),
      ),
      groupsByType: new Map(
        Object.entries(initial.groupsByType).map(([type, groupIds]) => [
          type as SegmentationGroupType,
          new Set(groupIds),
        ]),
      ),
      isDirty: false,
    }),
  );

  const getGroupMetadata = useCallback(
    (groupId: number): GroupMetadata | null => {
      return groupsData.find(group => group.id === groupId) || null;
    },
    [groupsData],
  );

  const handleToggleItem = useCallback(
    (groupId: number, itemId: number, itemName: string) => {
      dispatch({ type: 'TOGGLE_ITEM', groupId, itemId, itemName });
    },
    [],
  );

  const handleSelectAll = useCallback(
    (groupId: number) => {
      const selectAllState = getSelectAllState(
        state,
        groupId,
        getGroupMetadata(groupId),
      );

      if (selectAllState === 'all') {
        dispatch({ type: 'DESELECT_ALL_GROUP', groupId });
      } else {
        dispatch({ type: 'SELECT_ALL_GROUP', groupId });
      }
    },
    [state, getGroupMetadata],
  );

  const handleSetExpansion = useCallback(
    (groupId: number, expanded: boolean) => {
      dispatch({ type: 'SET_GROUP_EXPANSION', groupId, expanded });
    },
    [],
  );

  const handleResetAll = useCallback(() => {
    dispatch({ type: 'RESET_ALL' });
  }, []);

  const handleRegisterGroupType = useCallback(
    (groupId: number, groupType: SegmentationGroupType) => {
      dispatch({ type: 'REGISTER_GROUP_TYPE', groupId, groupType });
    },
    [],
  );

  const checkItemSelected = useCallback(
    (groupId: number, itemId: number) => {
      return isItemSelected(state, groupId, itemId);
    },
    [state],
  );

  const getGroupSelectAllState = useCallback(
    (groupId: number): SelectAllState => {
      return getSelectAllState(state, groupId, getGroupMetadata(groupId));
    },
    [state, getGroupMetadata],
  );

  const getGroupSelectedCount = useCallback(
    (groupId: number): number => {
      return getSelectedCount(state, groupId, getGroupMetadata(groupId));
    },
    [state, getGroupMetadata],
  );

  const selectedGroupsCount = useMemo(() => {
    const selectedGroups = new Set<number>();

    state.selectAllGroups.forEach(groupId => {
      selectedGroups.add(groupId);
    });

    state.selectedItems.forEach((itemIds, groupId) => {
      if (itemIds.size > 0) {
        selectedGroups.add(groupId);
      }
    });

    return selectedGroups.size;
  }, [state.selectAllGroups, state.selectedItems]);

  const getGroupTotalItems = useCallback(
    (groupId: number): number => {
      return getGroupMetadata(groupId)?.indexCount || 0;
    },
    [getGroupMetadata],
  );

  const getGroupPreviewItems = useCallback(
    (groupId: number): { id: number; name: string }[] => {
      return getSelectedItemsFromMetadata(
        state,
        groupId,
        getGroupMetadata(groupId),
      );
    },
    [state, getGroupMetadata],
  );

  const isGroupOfType = useCallback(
    (groupId: number, type: SegmentationGroupType): boolean => {
      return state.groupsByType.get(type)?.has(groupId) ?? false;
    },
    [state.groupsByType],
  );

  const getGroupType = useCallback(
    (groupId: number): SegmentationGroupType | null => {
      for (const [type, groupIds] of state.groupsByType) {
        if (groupIds.has(groupId)) return type;
      }
      return null;
    },
    [state.groupsByType],
  );

  return {
    state,
    dispatch,
    handlers: {
      toggleItem: handleToggleItem,
      selectAll: handleSelectAll,
      setExpansion: handleSetExpansion,
      resetAll: handleResetAll,
      registerGroupType: handleRegisterGroupType,
    },
    derived: {
      isItemSelected: checkItemSelected,
      getSelectAllState: getGroupSelectAllState,
      getSelectedCount: getGroupSelectedCount,
      selectedGroupsCount,
      getGroupTotalItems,
      getGroupPreviewItems,
      isGroupOfType,
      getGroupType,
    },
    utils: {
      serialize: () => serializeFilterState(state),
    },
  };
}

export default useFilterState;
