import * as React from "react";
import { useGetKeyById } from "./useGetKey";
import { usePagination } from "./usePagination";
import { useGetSet } from "./useStore";

export interface Result<R> {
  data: R;
  error: Error | null;
  loading: boolean;
  mutate: () => void;
}

export function useDatabase<Q, R>(
  id: string,
  initialPageSize: number = 25,
  intialPage: number = 1,
  queryFn: (limit: number, offset: number) => Q,
  fetcherFn: (p: Q, signal?: AbortSignal) => Promise<R>,
  autoload?: boolean,
): Result<R> {
  type State = {
    data: R | null;
    count: number | null;
    error: Error | null;
    loading: boolean;
  };
  const key = useGetKeyById(id);
  const [{ data, count, error, loading }, setState] = useGetSet<State>(key, {
    data: null,
    count: null,
    error: null,
    loading: autoload ?? true,
  });
  const {
    pageSize,
    after,
    currentPage,
    setCurrentPage,
    setPageSize,
    totalPages,
    hasPrevious,
    hasNext,
    nextPage,
    previousPage,
  } = usePagination(count || 1, initialPageSize ?? 25, intialPage ?? 1);

  const signalRef = React.useRef<AbortController | null>(null);
  const queryRef = React.useRef<string>("");

  const run = React.useCallback((query: Q) => {
    setState({ loading: true });
    fetcherFn(query, signalRef.current?.signal)
      .then(
        (data) => {
          if (data && (data as any)?.$$count !== undefined) {
            setState({ data, count: (data as any).$$count });
          } else if (data) {
            setState({ data });
          }
          if (data && (data as any)?.$$tags !== undefined) {
            document.dispatchEvent(
              new CustomEvent("REGISTER_TAGS", {
                detail: (data as any).$$tags,
              }),
            );
          }
        },
        (err) => {
          if (err?.name !== "AbortError") {
            setState({ error: err });
          }
        },
      )
      .finally(() => setState({ loading: false }));
  }, []);

  const maybeMutate = React.useCallback(() => {
    const query = queryFn(pageSize, after);
    const queryKey = JSON.stringify(query);
    if (queryRef.current !== queryKey) {
      signalRef.current?.abort();
      signalRef.current = new AbortController();
      queryRef.current = queryKey;
      run(query);
    }
  }, [queryFn, pageSize, after, run]);

  const mutate = React.useCallback(() => {
    queryRef.current = "";
    maybeMutate();
  }, [maybeMutate]);

  React.useEffect(
    function revalidateOnReconnect() {
      if (autoload === false) return;
      const controller = new AbortController();
      window.addEventListener("online", mutate, {
        signal: controller.signal,
      });
      return () => controller.abort();
    },
    [mutate, autoload],
  );

  React.useEffect(
    function revalidateOnVisibilityChange() {
      if (autoload === false) return;
      const controller = new AbortController();
      window.addEventListener("visibilitychange", mutate, {
        signal: controller.signal,
      });
      return () => controller.abort();
    },
    [mutate, autoload],
  );

  React.useEffect(() => {
    if (autoload === false) return;
    maybeMutate();
  }, undefined);

  const pagination = React.useMemo(
    () => ({
      pageSize,
      after,
      currentPage,
      setCurrentPage,
      setPageSize,
      totalPages,
      hasPrevious,
      hasNext,
      nextPage,
      previousPage,
    }),
    [
      pageSize,
      after,
      currentPage,
      setCurrentPage,
      setPageSize,
      totalPages,
      hasPrevious,
      hasNext,
      nextPage,
      previousPage,
    ],
  );

  return React.useMemo(
    () => ({
      data,
      count,
      error,
      loading,
      mutate,
      pagination,
    }),
    [data, count, error, loading, mutate, pagination],
  ) as any;
}

export function useDatabaseItem<Q, R>(
  id: string,
  rowId: string,
  queryFn: (rowId: string) => Q,
  fetcherFn: (p: Q, signal?: AbortSignal) => Promise<R>,
  autoload?: boolean,
): Result<R> {
  type State = {
    data: R | null;
    error: Error | null;
    loading: boolean;
  };
  const key = useGetKeyById(id);
  const [{ data, error, loading }, setState] = useGetSet<State>(key, {
    data: null,
    error: null,
    loading: autoload ?? true,
  });

  const signalRef = React.useRef<AbortController | null>(null);
  const queryRef = React.useRef<string>("");

  const run = React.useCallback((query: Q) => {
    setState({ loading: true });
    fetcherFn(query, signalRef.current?.signal)
      .then(
        (data) => {
          if (data) {
            setState({ data });
          }
          if (data && (data as any)?.$$tags !== undefined) {
            document.dispatchEvent(
              new CustomEvent("REGISTER_TAGS", {
                detail: (data as any).$$tags,
              }),
            );
          }
        },
        (err) => {
          if (err?.name !== "AbortError") {
            setState({ error: err });
          }
        },
      )
      .finally(() => setState({ loading: false }));
  }, []);

  const maybeMutate = React.useCallback(() => {
    const query = queryFn(rowId);
    const queryKey = JSON.stringify(query);
    if (queryRef.current !== queryKey) {
      signalRef.current?.abort();
      signalRef.current = new AbortController();
      queryRef.current = queryKey;
      run(query);
    }
  }, [queryFn, rowId, run]);

  const mutate = React.useCallback(() => {
    queryRef.current = "";
    maybeMutate();
  }, [maybeMutate]);

  React.useEffect(
    function revalidateOnReconnect() {
      if (autoload === false) return;
      const controller = new AbortController();
      window.addEventListener("online", mutate, {
        signal: controller.signal,
      });
      return () => controller.abort();
    },
    [mutate, autoload],
  );

  React.useEffect(
    function revalidateOnVisibilityChange() {
      if (autoload === false) return;
      const controller = new AbortController();
      window.addEventListener("visibilitychange", mutate, {
        signal: controller.signal,
      });
      return () => controller.abort();
    },
    [mutate, autoload],
  );

  React.useEffect(() => {
    if (autoload === false) return;
    maybeMutate();
  }, undefined);

  return React.useMemo(
    () => ({
      data,
      error,
      loading,
      mutate,
    }),
    [data, error, loading, mutate],
  ) as any;
}
