import { getResolveSpecClient } from '@warkypublic/resolvespec-js';
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';

import type { AdapterConfig, AdapterRef } from './types';

import { useGriddyStore } from '../core/GriddyStore';
import { applyCursor, buildOptions } from './mapOptions';

export const ResolveSpecAdapter = forwardRef<AdapterRef, AdapterConfig>(
  function ResolveSpecAdapter(props, ref) {
    const {
      autoFetch = true,
      baseUrl,
      columnMap,
      computedColumns,
      cursorField = 'id',
      customOperators,
      debounceMs = 300,
      defaultOptions,
      entity,
      mode = 'cursor',
      pageSize = 25,
      preload,
      schema,
      token,
    } = props;

    const sorting = useGriddyStore((s) => s.sorting ?? []);
    const columnFilters = useGriddyStore((s) => s.columnFilters ?? []);
    const pagination = useGriddyStore((s) => s.pagination);
    const setData = useGriddyStore((s) => s.setData);
    const appendData = useGriddyStore((s) => s.appendData);
    const setDataCount = useGriddyStore((s) => s.setDataCount);
    const setIsLoading = useGriddyStore((s) => s.setIsLoading);
    const setError = useGriddyStore((s) => s.setError);
    const setInfiniteScroll = useGriddyStore((s) => s.setInfiniteScroll);

    const clientRef = useRef(getResolveSpecClient({ baseUrl, token }));
    const debounceRef = useRef<null | ReturnType<typeof setTimeout>>(null);
    const mountedRef = useRef(true);

    // Cursor state (only used in cursor mode)
    const cursorRef = useRef<null | string>(null);
    const hasMoreRef = useRef(true);
    const [cursorLoading, setCursorLoading] = useState(false);

    // Update client if baseUrl/token changes
    useEffect(() => {
      clientRef.current = getResolveSpecClient({ baseUrl, token });
    }, [baseUrl, token]);

    useEffect(() => {
      mountedRef.current = true;
      return () => {
        mountedRef.current = false;
      };
    }, []);

    // ─── Offset mode fetch (original behavior) ───
    const fetchDataOffset = useCallback(async () => {
      if (!mountedRef.current) return;

      setIsLoading(true);
      setError(null);

      try {
        const paginationState = pagination?.enabled
          ? { pageIndex: 0, pageSize: pagination.pageSize }
          : undefined;

        const options = buildOptions(
          sorting,
          columnFilters,
          paginationState,
          columnMap,
          defaultOptions
        );

        if (preload) options.preload = preload;
        if (computedColumns) options.computedColumns = computedColumns;
        if (customOperators) options.customOperators = customOperators;

        const response = await clientRef.current.read(schema, entity, undefined, options);

        if (!mountedRef.current) return;

        if (response.success) {
          setData(Array.isArray(response.data) ? response.data : [response.data]);
          if (response.metadata?.total != null) {
            setDataCount(response.metadata.total);
          }
        } else if (response.error) {
          setError(new Error(response.error.message ?? 'Request failed'));
        }
      } catch (err) {
        if (mountedRef.current) {
          setError(err instanceof Error ? err : new Error(String(err)));
        }
      } finally {
        if (mountedRef.current) {
          setIsLoading(false);
        }
      }
    }, [
      sorting,
      columnFilters,
      pagination,
      columnMap,
      defaultOptions,
      preload,
      computedColumns,
      customOperators,
      schema,
      entity,
      setData,
      setDataCount,
      setIsLoading,
      setError,
    ]);

    // ─── Cursor mode fetch ───
    const fetchCursorPage = useCallback(
      async (cursor: null | string, isAppend: boolean) => {
        if (!mountedRef.current) return;

        if (isAppend) {
          setCursorLoading(true);
        } else {
          setIsLoading(true);
        }
        setError(null);

        try {
          const options = buildOptions(
            sorting,
            columnFilters,
            undefined,
            columnMap,
            defaultOptions
          );

          if (preload) options.preload = preload;
          if (computedColumns) options.computedColumns = computedColumns;
          if (customOperators) options.customOperators = customOperators;

          const cursorOptions = applyCursor(options, cursor, pageSize);
          const response = await clientRef.current.read(schema, entity, undefined, cursorOptions);

          if (!mountedRef.current) return;

          if (response.success) {
            const rows = Array.isArray(response.data) ? response.data : [response.data];

            if (isAppend) {
              appendData(rows);
            } else {
              setData(rows);
            }

            if (response.metadata?.total != null) {
              setDataCount(response.metadata.total);
            }

            // Extract cursor from last row
            if (rows.length > 0) {
              const lastRow = rows[rows.length - 1];
              cursorRef.current =
                lastRow?.[cursorField] != null ? String(lastRow[cursorField]) : null;
            }

            // Determine hasMore
            hasMoreRef.current = rows.length >= pageSize;
          } else if (response.error) {
            setError(new Error(response.error.message ?? 'Request failed'));
          }
        } catch (err) {
          if (mountedRef.current) {
            setError(err instanceof Error ? err : new Error(String(err)));
          }
        } finally {
          if (mountedRef.current) {
            if (isAppend) {
              setCursorLoading(false);
            } else {
              setIsLoading(false);
            }
          }
        }
      },
      [
        sorting,
        columnFilters,
        columnMap,
        defaultOptions,
        preload,
        computedColumns,
        customOperators,
        schema,
        entity,
        pageSize,
        cursorField,
        setData,
        appendData,
        setDataCount,
        setIsLoading,
        setError,
      ]
    );

    const fetchNextPage = useCallback(() => {
      if (!hasMoreRef.current || cursorLoading) return;
      fetchCursorPage(cursorRef.current, true);
    }, [cursorLoading, fetchCursorPage]);

    const resetAndFetch = useCallback(async () => {
      cursorRef.current = null;
      hasMoreRef.current = true;
      await fetchCursorPage(null, false);
    }, [fetchCursorPage]);

    // ─── Unified fetch dispatch ───
    const fetchData = mode === 'cursor' ? resetAndFetch : fetchDataOffset;

    // ─── Infinite scroll config sync (cursor mode only) ───
    useEffect(() => {
      // Skip infinite scroll if not in cursor mode OR if pagination is explicitly enabled
      if (mode !== 'cursor' || pagination?.enabled) {
        setInfiniteScroll(undefined);
        return;
      }

      setInfiniteScroll({
        enabled: true,
        hasMore: hasMoreRef.current,
        isLoading: cursorLoading,
        onLoadMore: fetchNextPage,
        threshold: 10,
      });
    }, [mode, pagination?.enabled, cursorLoading, fetchNextPage, setInfiniteScroll]);

    // Cleanup infinite scroll on unmount
    useEffect(() => {
      return () => {
        setInfiniteScroll(undefined);
      };
    }, [setInfiniteScroll]);

    useImperativeHandle(ref, () => ({ refetch: fetchData }), [fetchData]);

    // Auto-fetch on mount
    const initialFetchDone = useRef(false);
    useEffect(() => {
      if (autoFetch && !initialFetchDone.current) {
        initialFetchDone.current = true;
        fetchData();
      }
    }, [autoFetch, fetchData]);

    // Debounced re-fetch on state changes (skip initial)
    const prevDepsRef = useRef<null | string>(null);
    useEffect(() => {
      const depsKey =
        mode === 'cursor'
          ? JSON.stringify({ columnFilters, sorting })
          : JSON.stringify({ columnFilters, pagination, sorting });

      if (prevDepsRef.current === null) {
        prevDepsRef.current = depsKey;
        return;
      }

      if (prevDepsRef.current === depsKey) return;
      prevDepsRef.current = depsKey;

      if (debounceRef.current) clearTimeout(debounceRef.current);
      debounceRef.current = setTimeout(fetchData, debounceMs);

      return () => {
        if (debounceRef.current) clearTimeout(debounceRef.current);
      };
    }, [sorting, columnFilters, pagination, debounceMs, fetchData, mode]);

    return null;
  }
);
