import { httpGraphql } from '@inject/shared/config'
import { notify } from '@inject/shared/notification/engine'
import { executeExchange } from '@urql/exchange-execute'
import { retryExchange } from '@urql/exchange-retry'
import { useMemo, type FC, type PropsWithChildren } from 'react'
import type { Exchange } from 'urql'
import { Client, Provider, mapExchange, useClient } from 'urql'
import { sessionid } from '../connection/sessionid'
import { schema } from './schema'
import cacheExchange from './worker/cacheConsumer'
import pipeLogger from './worker/pipeLogger'

const constructClient = () =>
  new Client({
    url: httpGraphql(window.VITE_HTTPS_HOST ?? 'localhost:8000'),
    exchanges: [
      mapExchange({
        onError(error, op) {
          notify(
            `Error: ${error.message}`,
            JSON.stringify({
              _error: error,
              _operation: op,
            }),
            { intent: 'danger' }
          )
          if (
            error.graphQLErrors.some(e => e.message === 'Authentication failed')
          ) {
            sessionid('')
          }
        },
      }),
      retryExchange({
        initialDelayMs: 1000,
        maxDelayMs: 15000,
        randomDelay: true,
        maxNumberAttempts: Number.POSITIVE_INFINITY,
        retryIf: (error, op) => {
          // in case there are network issues, attempt to retry
          if (error.networkError) {
            console.warn(
              'GraphQL network error, retrying...:',
              error.networkError
            )
            return true
          }
          // in case it is a subscription, don't even bother retrying
          if (op.kind === 'subscription') {
            return false
          }
          // in case the context of query is launched from elsewhere than the client modules, retry
          if (
            !/^\/(trainee|instructor|analyst)/.exec(window.location.pathname)
          ) {
            return false
          }
          // rethrow for any graphQLerrors
          if (error.graphQLErrors.length > 0) {
            console.warn(
              'GraphQL Backend error, retrying...',
              error.graphQLErrors
            )
            return false
          }
          return false
        },
      }),
      // TODO: add proper typing for meta.env.mode from Vite and make it global

      // @ts-ignore
      import.meta.env.MODE !== 'production' ? pipeLogger('pre-cache') : null,
      cacheExchange,
      executeExchange({
        context: () => ({
          exerciseId: window.location.pathname.split('/')[2],
        }),
        schema,
      }),
    ].filter(exchange => exchange !== null) as Exchange[],
  })

const UrqlClient: FC<PropsWithChildren> = ({ children }) => {
  const clientValue = useMemo(() => constructClient(), [])

  return <Provider value={clientValue}>{children}</Provider>
}

export { readFragment } from 'gql.tada'
export type { FragmentOf } from 'gql.tada'
export { useClient }

export default UrqlClient
