import { ValidationLevel, stitchSchemas } from '@graphql-tools/stitch'
import { httpGraphql } from '@inject/shared/config'
import Keys from '@inject/shared/localstorage/keys'
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 actionLogTodoSchema from './subschemas/actionLogTodo'
import emailReceiptsSchema from './subschemas/emailReceipts'
import readReceiptsSchema from './subschemas/readReceipts'
import reloadSchema from './subschemas/reloadSchema'
import constructRemoteSchema from './subschemas/remoteSchema'
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) {
          notify(`Error: ${error.message}`, { intent: 'danger' })
        },
      }),
      retryExchange({
        initialDelayMs: 1000,
        maxDelayMs: 15000,
        randomDelay: true,
        maxNumberAttempts: Number.POSITIVE_INFINITY,
        retryIf: (error, op) => {
          if (op.kind === 'subscription') {
            return false
          }
          return !!(error.graphQLErrors.length > 0 || error.networkError)
        },
      }),
      // 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({
        schema: stitchSchemas({
          subschemas: [
            constructRemoteSchema(),
            actionLogTodoSchema,
            readReceiptsSchema,
            emailReceiptsSchema,
            reloadSchema,
          ],
          typeDefs: [],
          resolvers: {
            ActionLogType: {
              todo: {
                selectionSet: '{ id }',
                resolve: ({ id }: { id: string }) =>
                  JSON.parse(
                    localStorage.getItem(Keys.getActionLogTodoKey(id)) ||
                      'false'
                  ) as boolean,
              },
              readReceipt: {
                selectionSet: '{ id }',
                resolve: ({ id }: { id: string }) =>
                  localStorage.getItem(Keys.getReadReceiptActionLogKey(id)) ||
                  null,
              },
            },
            DefinitionChannelType: {
              readReceipt: {
                selectionSet: '{ id }',
                resolve: ({ id }: { id: string }) =>
                  JSON.parse(
                    localStorage.getItem(Keys.getReadReceiptChannelKey(id)) ||
                      '[]'
                  ),
              },
            },
            EmailThreadType: {
              readReceipt: {
                selectionSet: '{ id }',
                resolve: ({ id }: { id: string }) =>
                  JSON.parse(
                    localStorage.getItem(
                      Keys.getReadReceiptEmailThreadKey(id)
                    ) || '[]'
                  ),
              },
              archived: {
                selectionSet: '{ id }',
                resolve: ({ id }: { id: string }) =>
                  JSON.parse(
                    localStorage.getItem(Keys.getEmailThreadArchiveKey(id)) ||
                      'false'
                  ) as boolean,
              },
            },
            EmailType: {
              readReceipt: {
                selectionSet: '{ id }',
                resolve: ({ id }: { id: string }) =>
                  localStorage.getItem(Keys.getReadReceiptEmailKey(id)) || null,
              },
              todo: {
                selectionSet: '{ id }',
                resolve: ({ id }: { id: string }) =>
                  JSON.parse(
                    localStorage.getItem(Keys.getEmailTodoKey(id)) || 'false'
                  ) as boolean,
              },
            },
            TeamQuestionnaireStateType: {
              todo: {
                selectionSet: '{ team { id } questionnaire { id } }',
                resolve: ({
                  team,
                  questionnaire,
                }: {
                  team: { id: string }
                  questionnaire: { id: string }
                }) =>
                  JSON.parse(
                    localStorage.getItem(
                      Keys.getQuestionnaireTodoKey(questionnaire.id, team.id)
                    ) || 'false'
                  ) as boolean,
              },
            },
          },
          typeMergingOptions: {
            validationSettings: {
              validationLevel: ValidationLevel.Error,
            },
          },
        }),
      }),
    ].filter(x => x !== null) as Exchange[],
  })

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

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

export { useClient }

export default UrqlClient
