import { TablePagination, TablePaginationBaseProps, Theme, Toolbar, useMediaQuery, useTheme } from '@mui/material';
import PropTypes from 'prop-types';
import {
  ComponentPropType,
  ListPaginationContextValue,
  sanitizeListRestProps,
  useListPaginationContext,
  useResourceDefinition,
  useTranslate
} from 'ra-core';
import { FC, ReactElement, memo, useCallback, useEffect, useMemo, useState } from 'react';

import { debounce } from 'lodash';
import { PaginationActions, PaginationActionsProps } from './PaginationActions';

const Pagination: FC<PaginationProps> = memo((props) => {
  const { rowsPerPageOptions = DefaultRowsPerPageOptions, actions, limit = null, ...rest } = props;
  const { isLoading, hasNextPage, page, perPage, total, setPage, setPerPage } = useListPaginationContext(props);
  const translate = useTranslate();
  const isSmall = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
  const [currentPage, setCurrentPage] = useState(page - 1);
  const { hasCreate } = useResourceDefinition(props);
  const [isSelectedPage, setIsSelectedPage] = useState(false);
  const theme = useTheme();

  useEffect(() => {
    if (page !== currentPage + 1 && !isSelectedPage) {
      setCurrentPage(page - 1);
    }
  }, [currentPage, isSelectedPage, page]);

  useEffect(() => {
    setIsSelectedPage(false);
  }, [page]);

  const totalPages = useMemo(() => {
    return total != null ? Math.ceil(total / perPage) : undefined;
  }, [perPage, total]);

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const debouncedPageChange = useCallback(
    debounce((page) => {
      setPage(page + 1);
    }, 500),
    [setPage]
  );

  /**
   * Warning: Material UI's page is 0-based
   */
  const handlePageChange = useCallback(
    (event: React.MouseEvent<HTMLButtonElement, MouseEvent> | null, page: number) => {
      if (!event) {
        return;
      }

      event.preventDefault();
      if (page < 0 || (totalPages !== undefined && page > totalPages - 1)) {
        throw new Error(
          translate('ra.navigation.page_out_of_boundaries', {
            page: page + 1
          })
        );
      }

      const arrowSelected =
        ((event.target as HTMLElement).dataset?.testid || (event.target as HTMLElement).classList?.value) ?? '';
      // check if user is clicking on the arrows or on the numbers
      const isArrowClick =
        arrowSelected.includes('MuiPaginationItem-previousNext') ||
        arrowSelected.includes('NavigateBeforeIcon') ||
        arrowSelected.includes('NavigateNextIcon') ||
        arrowSelected.includes('KeyboardArrowLeftIcon') ||
        arrowSelected.includes('KeyboardArrowRightIcon');

      setCurrentPage(page);
      setIsSelectedPage(true);

      if (isArrowClick) {
        // apply debounced API call for arrows clicks
        debouncedPageChange(page);
      } else {
        // apply immediate API call for number clicks
        setPage(page + 1);
      }
    },
    [debouncedPageChange, setPage, translate, totalPages, setIsSelectedPage]
  );

  const handlePerPageChange = useCallback(
    (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
      setPerPage(Number(event.target.value));
    },
    [setPerPage]
  );

  const labelDisplayedRows = useCallback(
    ({ from, to, count }: { from: number; to: number; count: number }) =>
      count === -1 && hasNextPage
        ? translate('ra.navigation.partial_page_range_info', {
            offsetBegin: from,
            offsetEnd: to,
            _: `%{from}-%{to} of more than %{to}`
          })
        : translate('ra.navigation.page_range_info', {
            offsetBegin: from,
            offsetEnd: to,
            total: count === -1 ? to : count,
            _: `%{from}-%{to} of %{count === -1 ? to : count}`
          }),
    [translate, hasNextPage]
  );

  const labelItem = useCallback(
    (type: string) => translate(`ra.navigation.${type}`, { _: `Go to ${type} page` }),
    [translate]
  );

  if (isLoading) {
    return <Toolbar variant="dense" />;
  }

  // Avoid rendering TablePagination if "page" value is invalid
  if (total === 0 || page < 1 || (totalPages !== undefined && page > totalPages)) {
    if (limit != null && process.env.NODE_ENV === 'development') {
      console.warn(
        'The Pagination limit prop is deprecated. Empty state should be handled by the component displaying data (Datagrid, SimpleList).'
      );
    }
    return null;
  }

  if (isSmall) {
    return (
      <TablePagination
        count={total == null ? -1 : total}
        rowsPerPage={perPage}
        page={currentPage}
        onPageChange={handlePageChange}
        rowsPerPageOptions={emptyArray}
        component="span"
        labelDisplayedRows={labelDisplayedRows}
        sx={{
          justifyContent: hasCreate ? 'flex-start' : 'flex-end',
          display: hasCreate ? 'flex' : 'block',
          ...(props.displayStyle !== 'text' && {
            '.MuiTablePagination-actions': {
              display: 'flex',
              flexDirection: 'row',
              gap: '4px'
            },

            '& button': {
              border: `1px solid ${theme.palette.divider}`
            }
          })
        }}
        {...sanitizeListRestProps(rest)}
      />
    );
  }

  const ActionsComponent = actions
    ? actions // overridden by caller
    : !isLoading && total != null
    ? PaginationActions // regular navigation
    : undefined; // partial navigation (uses default TablePaginationActions)

  return (
    <TablePagination
      count={total == null ? -1 : total}
      rowsPerPage={perPage}
      page={currentPage}
      onPageChange={handlePageChange}
      onRowsPerPageChange={handlePerPageChange}
      ActionsComponent={ActionsComponent as any}
      slotProps={{
        actions: {
          nextButton: {
            disabled: !hasNextPage
          }
        }
      }}
      component="span"
      labelRowsPerPage={translate('ra.navigation.page_rows_per_page')}
      labelDisplayedRows={labelDisplayedRows}
      getItemAriaLabel={labelItem}
      rowsPerPageOptions={rowsPerPageOptions}
      sx={
        props.displayStyle !== 'text'
          ? {
              '.MuiInputBase-root': {
                border: `1px solid ${theme.palette.divider}`,
                borderRadius: '4px'
              },
              '.MuiTablePagination-spacer': {
                display: 'none'
              },
              '.MuiTablePagination-selectLabel': {
                color: theme.palette.text.secondary
              },
              '.MuiTablePagination-actions': {
                flex: 1,
                position: 'absolute',
                right: 24
              },
              '& button': {
                border: `1px solid ${theme.palette.divider}`,
                '&.Mui-selected': {
                  fontSize: '0.875rem',
                  fontWeight: 'regular',
                  backgroundColor: 'trasparent',
                  border: `1px solid ${theme.palette.primary.main}`,
                  color: theme.palette.primary.main
                }
              }
            }
          : {}
      }
      {...sanitizeListRestProps(rest)}
    />
  );
});

Pagination.propTypes = {
  actions: ComponentPropType,
  limit: PropTypes.element,
  rowsPerPageOptions: PropTypes.arrayOf(
    PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.shape({
        label: PropTypes.string.isRequired,
        value: PropTypes.number.isRequired
      })
    ])
  ) as PropTypes.Validator<(number | { label: string; value: number })[] | null | undefined>
};

const DefaultRowsPerPageOptions = [5, 10, 25, 50];
const emptyArray: any[] = [];

interface PaginationProps extends TablePaginationBaseProps, Partial<ListPaginationContextValue> {
  rowsPerPageOptions?: Array<number | { label: string; value: number }>;
  actions?: FC<PaginationActionsProps>;
  limit?: ReactElement;
  displayStyle?: 'text' | 'outlined';
}

export { Pagination };
export type { PaginationProps };
