/**
 * GenericTable Component
 *
 * A reusable data grid component that handles fetching, displaying,
 * filtering, and editing entity data using MUI X DataGrid.
 */

import React, { useEffect, useState, useMemo, useCallback } from 'react';
import {
  DataGrid,
  GridRowsProp,
  GridColDef,
  GridRowModesModel,
  GridEventListener,
  GridRowEditStopReasons,
  GridRowModel,
  GridRowSelectionModel,
  GridPaginationModel,
  GridSortModel,
} from '@mui/x-data-grid';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import CardBox from '../CardBox';
import BaseButton from '../BaseButton';
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
import { Field, Form, Formik } from 'formik';
import type { RootState } from '../../stores/store';
import type { Filter, FilterItem, FilterFields } from '../../types/filters';
import type { NotificationState } from '../../types/redux';
import type { AsyncThunk } from '@reduxjs/toolkit';
import type { BaseEntity } from '../../types/entities';

// Matches InternalSliceState from createEntitySlice
interface EntitySliceState<T> {
  [key: string]: T[] | boolean | number | NotificationState | unknown[];
  loading: boolean;
  count: number;
  refetch: boolean;
  notify: NotificationState;
}

// Props interface for GenericTable
interface GenericTableProps<T extends BaseEntity> {
  entityName: string;
  sliceSelector: (state: RootState) => EntitySliceState<T>;
  fetchAction: AsyncThunk<
    T | { rows: T[]; count: number },
    { id?: string; query?: string },
    object
  >;
  updateAction: AsyncThunk<T, { id: string; data: Partial<T> }, object>;
  deleteAction: AsyncThunk<void, string, object>;
  deleteByIdsAction?: AsyncThunk<void, string[], object>;
  setRefetchAction: (refetch: boolean) => { type: string; payload: boolean };
  loadColumnsFunction: (
    onDelete: (id: string) => void,
    entityName: string,
    user: unknown,
  ) => Promise<GridColDef[]>;
  filters: Filter[];
  filterItems: FilterItem[];
  setFilterItems: (items: FilterItem[]) => void;
  extraQuery?: string;
}

function GenericTable<T extends BaseEntity>({
  entityName,
  sliceSelector,
  fetchAction,
  updateAction,
  deleteAction,
  deleteByIdsAction,
  setRefetchAction,
  loadColumnsFunction,
  filters,
  filterItems,
  setFilterItems,
  extraQuery = '',
}: GenericTableProps<T>) {
  const dispatch = useAppDispatch();
  const { currentUser } = useAppSelector((state) => state.auth);
  const entityState = useAppSelector(sliceSelector);

  // Extract state values - rows are stored under the entity name key
  const rows = (entityState[entityName] as T[]) || [];
  const { count, loading, notify, refetch } = entityState;

  // Style selectors
  const focusRing = useAppSelector((state) => state.style.focusRingColor);
  const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
  const corners = useAppSelector((state) => state.style.corners);

  // Local state
  const [columns, setColumns] = useState<GridColDef[]>([]);
  const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
  const [rowSelectionModel, setRowSelectionModel] =
    useState<GridRowSelectionModel>([]);
  const [paginationModel, setPaginationModel] = useState<GridPaginationModel>({
    page: 0,
    pageSize: 10,
  });
  const [sortModel, setSortModel] = useState<GridSortModel>([]);
  const [itemIdToDelete, setItemIdToDelete] = useState<string | null>(null);

  // Control classes for form fields
  const controlClasses =
    'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
    `${bgColor} ${focusRing} ${corners} ` +
    'dark:bg-slate-800 border';

  // Handle delete action
  const onDelete = useCallback((id: string) => {
    setItemIdToDelete(id);
  }, []);

  // Load columns on mount
  useEffect(() => {
    async function loadCols() {
      const cols = await loadColumnsFunction(onDelete, entityName, currentUser);
      setColumns(cols);
    }
    loadCols();
  }, [loadColumnsFunction, entityName, currentUser, onDelete]);

  // Generate filter query string
  const generateFilterRequests = useMemo(() => {
    let request = '';
    filterItems.forEach((item) => {
      const isRangeFilter = filters.find(
        (filter) =>
          filter.title === item.fields.selectedField &&
          (filter.number || filter.date),
      );
      if (isRangeFilter) {
        const from = item.fields.filterValueFrom;
        const to = item.fields.filterValueTo;
        if (from) request += `&${item.fields.selectedField}Range=${from}`;
        if (to) request += `&${item.fields.selectedField}Range=${to}`;
      } else {
        const value = item.fields.filterValue;
        if (value) request += `&${item.fields.selectedField}=${value}`;
      }
    });
    return request;
  }, [filterItems, filters]);

  // Fetch data
  useEffect(() => {
    const sortQuery = sortModel.length
      ? `&sortBy=${sortModel[0].field}&sortOrder=${sortModel[0].sort}`
      : '';
    const query = `?page=${paginationModel.page + 1}&limit=${paginationModel.pageSize}${sortQuery}${generateFilterRequests}${extraQuery}`;
    dispatch(fetchAction({ query }));
  }, [
    dispatch,
    fetchAction,
    paginationModel,
    sortModel,
    generateFilterRequests,
    extraQuery,
  ]);

  // Handle refetch flag
  useEffect(() => {
    if (refetch) {
      dispatch(setRefetchAction(false));
      const sortQuery = sortModel.length
        ? `&sortBy=${sortModel[0].field}&sortOrder=${sortModel[0].sort}`
        : '';
      const query = `?page=${paginationModel.page + 1}&limit=${paginationModel.pageSize}${sortQuery}${generateFilterRequests}${extraQuery}`;
      dispatch(fetchAction({ query }));
    }
  }, [
    refetch,
    dispatch,
    setRefetchAction,
    fetchAction,
    paginationModel,
    sortModel,
    generateFilterRequests,
    extraQuery,
  ]);

  // Show notifications
  useEffect(() => {
    if (notify.showNotification && notify.textNotification) {
      const toastType =
        notify.typeNotification === 'warn'
          ? 'warning'
          : notify.typeNotification || 'info';
      toast(notify.textNotification, {
        type: toastType as 'success' | 'error' | 'info' | 'warning',
      });
    }
  }, [notify]);

  // Handle delete confirmation
  useEffect(() => {
    if (itemIdToDelete) {
      dispatch(deleteAction(itemIdToDelete));
      setItemIdToDelete(null);
    }
  }, [itemIdToDelete, dispatch, deleteAction]);

  // Row edit handlers
  const handleRowEditStop: GridEventListener<'rowEditStop'> = (
    params,
    event,
  ) => {
    if (params.reason === GridRowEditStopReasons.rowFocusOut) {
      event.defaultMuiPrevented = true;
    }
  };

  const processRowUpdate = async (
    newRow: GridRowModel,
  ): Promise<GridRowModel> => {
    await dispatch(
      updateAction({ id: newRow.id as string, data: newRow as Partial<T> }),
    );
    return newRow;
  };

  const handleProcessRowUpdateError = (error: Error) => {
    toast.error(`Error updating row: ${error.message}`);
  };

  // Filter handlers
  const handleFilterChange =
    (id: string) =>
    (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
      const value = e.target.value;
      const name = e.target.name as keyof FilterFields;
      setFilterItems(
        filterItems.map((item) => {
          if (item.id !== id) return item;
          if (name === 'selectedField') {
            return {
              id,
              fields: {
                selectedField: value,
                filterValue: '',
                filterValueFrom: '',
                filterValueTo: '',
              },
            };
          }
          return { id, fields: { ...item.fields, [name]: value } };
        }),
      );
    };

  const deleteFilter = (id: string) => {
    setFilterItems(filterItems.filter((item) => item.id !== id));
  };

  const handleResetFilters = () => {
    setFilterItems([]);
  };

  // Bulk delete handler
  const handleBulkDelete = () => {
    if (deleteByIdsAction && rowSelectionModel.length > 0) {
      dispatch(deleteByIdsAction(rowSelectionModel as string[]));
      setRowSelectionModel([]);
    }
  };

  return (
    <>
      {/* Filter Panel */}
      {filterItems && Array.isArray(filterItems) && filterItems.length > 0 && (
        <CardBox className='mb-4'>
          <Formik
            initialValues={{}}
            onSubmit={() => {
              /* Filters are applied on change, not submit */
            }}
          >
            <Form>
              {filterItems.map((filterItem) => (
                <div key={filterItem.id} className='flex mb-4 gap-3'>
                  <div className='flex flex-col w-full'>
                    <div className='text-gray-500 font-bold text-sm'>
                      Filter
                    </div>
                    <Field
                      className={controlClasses}
                      name='selectedField'
                      component='select'
                      value={filterItem?.fields?.selectedField || ''}
                      onChange={handleFilterChange(filterItem.id)}
                    >
                      <option value=''>Select field</option>
                      {filters.map((filter) => (
                        <option key={filter.title} value={filter.title}>
                          {filter.label}
                        </option>
                      ))}
                    </Field>
                  </div>

                  {/* Enum filter */}
                  {filters.find(
                    (f) => f.title === filterItem?.fields?.selectedField,
                  )?.type === 'enum' ? (
                    <div className='flex flex-col w-full'>
                      <div className='text-gray-500 font-bold text-sm'>
                        Value
                      </div>
                      <Field
                        className={controlClasses}
                        name='filterValue'
                        component='select'
                        value={filterItem?.fields?.filterValue || ''}
                        onChange={handleFilterChange(filterItem.id)}
                      >
                        <option value=''>Select value</option>
                        {filters
                          .find(
                            (f) =>
                              f.title === filterItem?.fields?.selectedField,
                          )
                          ?.options?.map((option) => (
                            <option key={option} value={option}>
                              {option}
                            </option>
                          ))}
                      </Field>
                    </div>
                  ) : (
                    <div className='flex flex-col w-full'>
                      <div className='text-gray-500 font-bold text-sm'>
                        Contains
                      </div>
                      <Field
                        className={controlClasses}
                        name='filterValue'
                        placeholder='Filter value'
                        value={filterItem?.fields?.filterValue || ''}
                        onChange={handleFilterChange(filterItem.id)}
                      />
                    </div>
                  )}

                  <div className='flex flex-col'>
                    <div className='text-gray-500 font-bold text-sm'>
                      Action
                    </div>
                    <BaseButton
                      className='my-2'
                      type='button'
                      color='danger'
                      label='Delete'
                      onClick={() => deleteFilter(filterItem.id)}
                    />
                  </div>
                </div>
              ))}
              <div className='flex gap-3'>
                <BaseButton
                  className='my-2'
                  color='info'
                  label='Reset Filters'
                  onClick={handleResetFilters}
                />
              </div>
            </Form>
          </Formik>
        </CardBox>
      )}

      {/* Bulk Actions */}
      {rowSelectionModel.length > 0 && deleteByIdsAction && (
        <div className='mb-4 flex gap-2'>
          <BaseButton
            color='danger'
            label={`Delete Selected (${rowSelectionModel.length})`}
            onClick={handleBulkDelete}
          />
        </div>
      )}

      {/* Data Grid */}
      <CardBox className='mb-6 overflow-hidden'>
        <div style={{ width: '100%', minHeight: 400 }}>
          <DataGrid
            rows={rows as GridRowsProp}
            columns={columns}
            rowCount={count}
            loading={loading}
            pageSizeOptions={[5, 10, 25, 50]}
            paginationModel={paginationModel}
            paginationMode='server'
            onPaginationModelChange={setPaginationModel}
            sortingMode='server'
            sortModel={sortModel}
            onSortModelChange={setSortModel}
            checkboxSelection
            disableRowSelectionOnClick
            rowSelectionModel={rowSelectionModel}
            onRowSelectionModelChange={setRowSelectionModel}
            editMode='row'
            rowModesModel={rowModesModel}
            onRowModesModelChange={setRowModesModel}
            onRowEditStop={handleRowEditStop}
            processRowUpdate={processRowUpdate}
            onProcessRowUpdateError={handleProcessRowUpdateError}
            getRowId={(row) => row.id}
            sx={{
              border: 'none',
              '& .MuiDataGrid-cell': {
                borderColor: 'rgba(0,0,0,0.1)',
              },
              '& .MuiDataGrid-columnHeaders': {
                backgroundColor: 'rgba(0,0,0,0.02)',
              },
            }}
          />
        </div>
      </CardBox>

      <ToastContainer />
    </>
  );
}

export default GenericTable;
