import { type ChangeEvent, useState } from 'react';
import { type InfiniteData } from 'react-query';

import { type AxiosResponse } from 'axios';
import { orderBy as orderByLodash } from 'lodash-es';
import ArrowDropDownIcon from '@material-hu/icons/material/ArrowDropDown';
import TableCell, { type TableCellProps } from '@material-hu/mui/TableCell';
import TableSortLabel, {
  type TableSortLabelProps,
} from '@material-hu/mui/TableSortLabel';

import { type ItemPermissions } from './modulePermissions';

const SEARCH_STRING_LIMIT = 255;

export type Pagination = {
  page: number;
  limit: number;
  order?: string | undefined;
  orderBy?: string | undefined;
  offset?: number;

  // Some accept q, some accept search
  search?: string | undefined;
  q?: string | undefined | null;
};

export type BasePaginatedResponse<T> = {
  items: T[];
};

export type PaginatedResponse<T> = BasePaginatedResponse<T> & {
  count: number;
  totalPages: number;
  page: number;
  limit: number;
  offset: number;
  cursor?: string;
  itemActions?: ItemPermissions[];
};

export type CursorPaginatedResponse<T> = BasePaginatedResponse<T> & {
  cursor: string | null;
  count?: number;
};

type PaginationParamsBase = {
  page: number;
  limit: number;
  order?: string;
  orderBy?: string;
};

export type PaginationParams = PaginationParamsBase & {
  search?: string;
};

export const formatPagination = (
  pagination: Pagination,
  searchName = 'search',
) => {
  if (!pagination) return {};

  const { page, search, ...rest } = pagination;

  return {
    ...rest,
    [searchName]: search
      ? search?.substring(0, SEARCH_STRING_LIMIT)
      : undefined,
    page: page + 1,
  };
};

export const formatPaginationWithOffset = (pagination: Pagination) => {
  if (!pagination) return {};

  const { page, ...rest } = pagination;

  return {
    ...rest,
    offset: page * rest.limit,
  };
};

export function useTableCheckboxes<T extends { id: number }>(items: T[]) {
  const [selected, setSelected] = useState<number[]>([]);
  const someSelected = selected.length > 0;
  const allSelected = selected.length === items?.length;
  const someButNotAllSelected = someSelected && !allSelected;

  const handleSelectAll = (event: ChangeEvent<HTMLInputElement>) => {
    setSelected(event.target.checked ? items.map(form => form.id) : []);
  };

  const handleSelect = (itemId: number) => {
    if (!selected.includes(itemId)) {
      setSelected(prevSelected => [...prevSelected, itemId]);
    } else {
      setSelected(prevSelected => prevSelected.filter(id => id !== itemId));
    }
  };

  const selectAll = () => {
    setSelected(items.map(form => form.id));
  };

  const unselectAll = () => {
    setSelected([]);
  };

  return {
    handleSelectAll,
    handleSelect,
    selected,
    someSelected,
    allSelected,
    someButNotAllSelected,
    selectAll,
    unselectAll,
  };
}

export const useTableSorting = <T,>(
  defaultOrderBy: string,
  defaultOrder: TableSortLabelProps['direction'],
) => {
  const [orderBy, setOrderBy] = useState(defaultOrderBy);
  const [order, setOrder] = useState(defaultOrder);
  const [valueSelector, setValueSelector] = useState<Function | null>(null);

  const createSortHandler = (property: string, selector?: Function) => () => {
    const isAsc = orderBy === property && order === 'asc';
    setOrder(isAsc ? 'desc' : 'asc');
    setOrderBy(property);
    setValueSelector(selector ? () => selector : null);
  };

  // this component replaces the TableCell inside TableHeader
  const TableSortingHeader = ({
    children,
    id,
    selector,
    ...rest
  }: TableCellProps & { id: string; selector?: (value: T) => any }) => (
    <TableCell
      {...rest}
      sortDirection={orderBy === id ? order : false}
    >
      <TableSortLabel
        active={orderBy === id}
        direction={orderBy === id ? order : 'asc'}
        onClick={() => createSortHandler(id, selector)()}
        IconComponent={ArrowDropDownIcon}
      >
        {children}
      </TableSortLabel>
    </TableCell>
  );

  // this function sorts the items using the currently selected sorting options
  const sortFunction = (items: T[]) =>
    orderByLodash(
      items,
      [
        i => {
          const value = valueSelector ? valueSelector(i) : (i as any)[orderBy];
          if (typeof value === 'number') {
            return value;
          }
          return (value ?? '').toString().toLowerCase();
        },
      ],
      [order as 'asc' | 'desc'],
    ) as T[];

  return {
    TableSortingHeader,
    sortFunction,
  };
};

export const getServerPaginationParams = (serverPagination: any) => ({
  order: serverPagination.order,
  orderBy: serverPagination.orderBy,
  ...serverPagination.pagination,
});

export type InfiniteResponse<T> = InfiniteData<
  AxiosResponse<BasePaginatedResponse<T>>
>;

/**
 * @description
 * This function takes an infinite query and returns a flat array.
 * This is useful when you want to display all items in a single list.
 *
 * @param infinite - The infinite query object
 * @returns A flat array of all items in all pages
 */
export function flatPages<T>(infinite: InfiniteResponse<T> | undefined | null) {
  return infinite?.pages?.flatMap(page => page.data.items) || [];
}

/**
 * Scrolls to the top of an element.
 */
export const handleScrollToTop = (elementId: string) => {
  const element = document.getElementById(elementId);
  if (!element) return;
  element.scrollIntoView({ block: 'start', behavior: 'smooth' });
};
