import {useMemo} from 'react';
import {ApolloClient, HttpLink, InMemoryCache} from '@apollo/client';
import {setContext} from '@apollo/client/link/context';
import {onError} from '@apollo/client/link/error';
import merge from 'deepmerge';
import isEqual from 'lodash/isEqual';
import useAuthStore from '../stores/useAuthStore';

const {STRAPI_URL = ''} = process.env;

export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';

export let apolloClient;

const httpLink = new HttpLink({
  uri: `${STRAPI_URL}/graphql`, // Server URL (must be absolute)
  credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
});

const handleError = onError(({graphQLErrors = [], ...details}) => {
  const [error] = graphQLErrors;
  console.error({graphQLErrors, error, details});

  if (error?.message === 'Invalid token.') {
    useAuthStore.getState().logout();
  } else if (error?.message == 'Forbidden') {
    window.location.href = '/auth/login';
  }
});

const createApolloClient = token => {
  return new ApolloClient({
    ssrMode: typeof window === 'undefined',
    link: setContext((_, {headers}) => {
      // return the headers to the context so httpLink can read them
      return {
        headers: {
          ...headers,
          authorization: token ? `Bearer ${token}` : '',
        },
      };
    })
      .concat(handleError)
      .concat(httpLink),
    cache: new InMemoryCache(),
  });
};

export const initializeApollo = (initialState: object, token: string) => {
  const _apolloClient = createApolloClient(token);

  // If your page has Next.js data fetching methods that use Apollo Client, the initial state gets hydrated here
  if (initialState) {
    // Get existing cache, loaded during client side data fetching
    const existingCache = _apolloClient.extract();

    // Merge the existing cache into data passed from getStaticProps/getServerSideProps
    const data = merge(initialState, existingCache, {
      // combine arrays using object equality (like in sets)
      arrayMerge: (destinationArray, sourceArray) => [
        ...sourceArray,
        ...destinationArray.filter(d => sourceArray.every(s => !isEqual(d, s))),
      ],
    });

    // Restore the cache with the merged data
    _apolloClient.cache.restore(data);
  }
  // For SSG and SSR always create a new Apollo Client
  if (typeof window === 'undefined') return _apolloClient;
  // Create the Apollo Client once in the client
  if (!apolloClient) apolloClient = _apolloClient;

  return _apolloClient;
};

export const addApolloState = (client, pageProps) => {
  if (pageProps?.props) {
    pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
  }

  return pageProps;
};

export const useApollo = pageProps => {
  const state = pageProps[APOLLO_STATE_PROP_NAME];
  const token = useAuthStore(s => s.token);
  return useMemo(() => {
    const newClient = initializeApollo(state, token);
    apolloClient = newClient;
    return newClient;
  }, [state, token]);
};
