import type { ReactiveVar } from '@apollo/client'
import { makeVar, useReactiveVar } from '@apollo/client'
import { useLocalStorageState } from 'ahooks'
import type { SetState } from 'ahooks/lib/createUseStorageState'
import { useEffect } from 'react'

export { makeVar, useReactiveVar }

export type { ReactiveVar }

interface UseLocalStorageStateOptions<T> {
  onChange?: (value: T) => void
  serializer?: (value: T) => string
  deserializer?: (value: string) => T
}

/**
 * Combines Apollo reactive variable with a local storage state.
 * It's value is synced across tabs.
 *
 * Returns the value of the provided reactive variable and
 * a function to set the value.
 */
export const useLocalStorageReactiveVar = <T>(
  reactiveVar: ReactiveVar<T>,
  key: string,
  options?: UseLocalStorageStateOptions<T>
): readonly [T, (value?: SetState<T> | undefined) => void] => {
  const reactiveVarValue = useReactiveVar(reactiveVar)
  const [localStorageValue, setLocalStorageValue] = useLocalStorageState(key, {
    defaultValue: reactiveVarValue,
    listenStorageChange: true,
    serializer: options?.serializer,
    deserializer: options?.deserializer,
  })

  useEffect(() => {
    /*
     * there has already been a value in the local storage,
     * but the reactiveVar has not been initialized
     */
    if (localStorageValue !== reactiveVarValue) {
      reactiveVar(localStorageValue)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  /*
   * localStorageValue is synced automatically using the useLocalStorageState hook,
   * this useEffect syncs reactiveVar with localStorageValue
   */
  useEffect(() => {
    if (localStorageValue !== undefined) {
      options?.onChange?.(localStorageValue)
      reactiveVar(localStorageValue)
    }
  }, [localStorageValue, options, reactiveVar])

  return [reactiveVarValue, setLocalStorageValue]
}
