import {
  DataTableFilterEvent,
  DataTableFilterMeta,
  DataTableFilterMetaData,
  DataTablePageEvent,
  DataTableSortEvent,
} from "primereact/datatable";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Refresh } from "../../hooks";
import {
  DataTableProviderConfig,
  DataTableProviderProps,
  DataTableState,
  ExportOptions,
  Yii2DataProviderResponse,
} from "./props";
import { goGet } from "../../utils/goFetch";
import { useQueryParamState } from "../../hooks/UseQueryParamState.strict";

export function useDataTableProvider<TModel, TResponse>(
  endpoint: string,
  model: string,
  refresh?: Refresh,
  config?: DataTableProviderConfig<TModel, TResponse>,
): DataTableProviderProps<TModel, TResponse> {
  const DEFAULT_ROWS = 20;

  const pagination = useMemo(() => {
    return config?.pagination === undefined
      ? {
          rowsPerPageOptions: undefined,
        }
      : config.pagination;
  }, [config?.pagination]);

  const initState = {
    first: 0,
    rows: pagination !== false ? DEFAULT_ROWS : undefined,
    page: 0,
    sortField: undefined,
    sortOrder: null,
    filters: undefined,
    ...config?.initState,
  };

  // const [latestRequest, setLatestRequest] = useQueryParamState<string>(
  //   model,
  //   (val) => {
  //     return btoa(val);
  //   },
  //   (paramVal) => atob(paramVal),
  // );
  const latestRequest = useRef<string>("");
  const latestRefresh = useRef<Refresh>(undefined);
  const [totalRecords, setTotalRecords] = useState<number>();
  const [exportOptions, setExportOptions] = useState<ExportOptions>({
    endpoint: undefined,
  });

  const [loading, setLoading] = useState(false);
  const [rawData, setRawData] = useState<TResponse>();
  // const [lazyState, setLazyState] = useState<DataTableState>(initState);
  const [lazyState, setLazyState] = useQueryParamState<DataTableState>(
    model,
    (val) => {
      const initJSON = JSON.stringify(initState);
      const valJSON = JSON.stringify(val);
      if (initJSON === valJSON) return;
      return btoa(valJSON);
    },
    (paramVal) => (paramVal ? JSON.parse(atob(paramVal)) : initState),
  );

  const getProvider: (
    res: TResponse | undefined,
  ) => Yii2DataProviderResponse<TModel> | undefined = useCallback(
    (res: TResponse | undefined) => {
      if (!res) return undefined;
      if (config?.providerPath) {
        return config.providerPath(res);
      } else {
        return (res as any).dataProvider;
      }
    },
    [config],
  );

  const load: () => Promise<TResponse | false> = async () => {
    if (!lazyState) {
      return false;
    }
    try {
      //Build URL
      const params = new URLSearchParams();

      //Pagination
      if (pagination !== false) {
        params.append(
          "page",
          String(
            Math.floor(
              (lazyState.first ?? 0) / (lazyState.rows ?? DEFAULT_ROWS),
            ) + (config?.zeroBasedPagination ? 0 : 1),
          ),
        );
        params.append("pageSize", String(lazyState.rows ?? DEFAULT_ROWS));
      }

      //Sorting
      if (lazyState.sortField && lazyState.sortOrder) {
        params.append(
          "sort",
          `${lazyState.sortOrder === -1 ? "-" : ""}${lazyState.sortField}`,
        );
      }

      //Filtering
      for (const [field, filter] of Object.entries(lazyState.filters ?? [])) {
        const filterValue = (filter as DataTableFilterMetaData).value;
        if (filterValue !== undefined && filterValue !== null) {
          // recreate GET params as Yii would expect
          params.append(`${model}[${field}]`, filterValue.toString());
        }
      }

      //Any additional params
      if (config?.additionalParams) {
        for (const [key, value] of Object.entries(config.additionalParams)) {
          if (value !== undefined) {
            params.append(key, value?.toString());
          }
        }
      }

      const sendEndpoint = endpoint.includes("?")
        ? `${endpoint}&${params.toString()}`
        : `${endpoint}?${params.toString()}`;

      if (
        sendEndpoint === latestRequest.current &&
        refresh === latestRefresh.current
      ) {
        //If endpoint hasn't changed and refresh wasn't prompted, abort fetch.
        return false;
      }

      setLoading(true);
      //Fetch is proceeding - store state to prevent unnecessary requests.
      latestRequest.current = sendEndpoint;
      // setLatestRequest(sendEndpoint);
      latestRefresh.current = refresh;

      setExportOptions((prev) => {
        const data = { ...prev };
        const url = new URL(sendEndpoint, "http://dummy");

        // Remove unwanted params
        params.delete("page");
        params.delete("pageSize");

        // Add/overwrite export=1
        params.set("export", "1");

        data.endpoint =
          url.pathname + (params.toString() ? `?${params.toString()}` : "");
        return data;
      });

      //Perform request
      const response = (await goGet(sendEndpoint)) as TResponse;

      if (sendEndpoint !== latestRequest.current) {
        //Ignore if not latest request

        //Should only be hit if theres a new request busy, so loading will still be set to false.
        //TODO: Potential spot for infinite load if other request causes
        return false;
      }

      const dataProvider = getProvider(response);

      if (dataProvider) {
        setTotalRecords(dataProvider.totalCount);
      }
      setRawData(response);
      setLoading(false);
      return response;
    } catch (error) {
      console.error("Error fetching data:", error);
      setLoading(false);
      throw error;
    } finally {
    }
  };

  useEffect(() => {
    load();
  }, [lazyState, refresh, config?.additionalParams]);

  const onPage = useCallback((event: DataTablePageEvent) => {
    setLazyState((old) => ({ ...old!, ...event }));
  }, []);

  const onSort = useCallback((event: DataTableSortEvent) => {
    setLazyState((old) => ({ ...old!, ...event }));
  }, []);

  const onFilter = useCallback((event: DataTableFilterEvent) => {
    event["first"] = 0;
    setLazyState((old) => ({ ...old!, ...event }));
  }, []);

  const setFilters = useCallback((filters: DataTableFilterMeta) => {
    setLazyState((old) => {
      return { ...old!, filters: { ...old?.filters, ...filters } };
    });
  }, []);

  return {
    ...lazyState!,
    totalRecords,
    value: getProvider(rawData)?.models ?? [],
    rawData,
    loading,
    onPage,
    onSort,
    onFilter,
    lazy: true,
    rowsPerPageOptions:
      pagination !== false ? pagination.rowsPerPageOptions : undefined,
    paginator: pagination === false ? false : true,
    exportOptions,
    setFilters,
  };
}
