import {
  CacheValueType,
  NullableType,
  RequestInstance,
  ExtractResponseType,
  ExtractErrorType,
  ExtractAdapterReturnType,
  Dispatcher,
  ExtractAdapterType,
  ExtractAdapterExtraType,
  ResponseDetailsType,
} from "@hyper-fetch/core";

import { initialState, UseTrackedStateType } from "helpers";

export const getDetailsState = (
  state?: UseTrackedStateType<RequestInstance>,
  details?: Partial<ResponseDetailsType>,
): ResponseDetailsType => {
  return {
    retries: state?.retries || 0,
    timestamp: +new Date(),
    isCanceled: false,
    isOffline: false,
    ...details,
  };
};

export const isStaleCacheData = (cacheTime: number, cacheTimestamp: NullableType<Date | number>) => {
  if (!cacheTimestamp) return true;
  return +new Date() > +cacheTimestamp + cacheTime;
};

export const getValidCacheData = <T extends RequestInstance>(
  request: T,
  initialData: NullableType<Partial<ExtractAdapterReturnType<T>>>,
  cacheData: NullableType<CacheValueType<ExtractResponseType<T>, ExtractErrorType<T>>>,
): CacheValueType<ExtractResponseType<T>, ExtractErrorType<T>> | null => {
  const isStale = isStaleCacheData(request.cacheTime, cacheData?.timestamp);

  if (!isStale && cacheData) {
    return cacheData;
  }

  if (initialData) {
    return {
      data: null,
      error: null,
      status: null,
      success: null,
      extra: null,
      ...((initialData || {}) as Partial<ExtractAdapterReturnType<T>>),
      ...getDetailsState(),
      cacheTime: 1000,
      clearKey: request.client.cache.clearKey,
      garbageCollection: request.garbageCollection,
    };
  }

  return null;
};

export const getTimestamp = (timestamp?: NullableType<number | Date>) => {
  return timestamp ? new Date(timestamp) : null;
};

export const getIsInitiallyLoading = ({
  queryKey,
  dispatcher,
  hasState,
  revalidate,
  disabled,
}: {
  queryKey: string;
  dispatcher: Dispatcher;
  hasState: boolean;
  revalidate?: boolean;
  disabled?: boolean;
}) => {
  if (!revalidate && hasState) {
    return false;
  }

  const queue = dispatcher.getQueue(queryKey);
  const isInitiallyLoading = dispatcher.hasRunningRequests(queryKey) || (!queue.stopped && disabled === false);

  return isInitiallyLoading;
};

export const getInitialState = <T extends RequestInstance>({
  initialResponse,
  dispatcher,
  request,
  disabled,
  revalidate,
}: {
  initialResponse: NullableType<Partial<ExtractAdapterReturnType<T>>>;
  dispatcher: Dispatcher;
  request: T;
  /**
   * useFetch only
   */
  disabled?: boolean;
  /**
   * useFetch only
   */
  revalidate?: boolean;
}): UseTrackedStateType<T> => {
  const { client, cacheKey, responseMapper } = request;
  const { cache } = client;

  const cacheData = cache.get<
    ExtractResponseType<T>,
    ExtractErrorType<T>,
    ExtractAdapterExtraType<ExtractAdapterType<T>>
  >(cacheKey);
  const cacheState = getValidCacheData<T>(request, initialResponse, cacheData);

  const initialLoading = getIsInitiallyLoading({
    queryKey: request.queueKey,
    dispatcher,
    disabled,
    revalidate,
    hasState: !!cacheState,
  });

  if (cacheState) {
    const mappedData = responseMapper ? responseMapper(cacheState) : cacheState;
    if (mappedData instanceof Promise) {
      // For the async mapper we cannot return async values
      // So we have return the initial state instead
      return initialState;
    }
    return {
      data: mappedData.data,
      error: mappedData.error,
      status: mappedData.status,
      success: mappedData.success,
      extra: mappedData.extra,
      retries: cacheState.retries,
      timestamp: getTimestamp(cacheState.timestamp),
      loading: initialLoading,
    };
  }

  return {
    data: initialState.data,
    error: initialState.error,
    status: initialState.status,
    success: initialState.success,
    extra: request.client.defaultExtra,
    retries: initialState.retries,
    timestamp: getTimestamp(initialState.timestamp),
    loading: initialLoading,
  };
};
