import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import type { LoginInput, User } from '../schemas/auth'
import { services } from '../services'
import { session } from './session'
import { activity } from './activity'
import { refreshAuth } from '../api'

type AuthContextValue = {
  user: User | null
  authenticated: boolean
  loading: boolean
  login: (input: LoginInput) => Promise<void>
  logout: () => Promise<void>
}

const AuthContext = createContext<AuthContextValue | null>(null)

export function AuthProvider({ children }: { children: ReactNode }) {
  const queryClient = useQueryClient()
  const [token, setToken] = useState(() => session.get())
  useEffect(() => session.subscribe(() => setToken(session.get())), [])
  useEffect(() => token ? activity.start(() => { void refreshAuth().catch(() => undefined) }) : undefined, [token])
  const me = useQuery({
    queryKey: ['auth', 'me'],
    queryFn: services.auth.me,
    enabled: Boolean(token),
    retry: false,
  })

  const value = useMemo<AuthContextValue>(
    () => ({
      user: me.data || null,
      authenticated: Boolean(token && me.data),
      loading: Boolean(token) && me.isPending,
      login: async (input) => {
        session.set(await services.auth.login(input))
        activity.record(true)
        await queryClient.invalidateQueries({ queryKey: ['auth', 'me'] })
      },
      logout: async () => {
        try {
          await services.auth.logout()
        } finally {
          session.clear()
          activity.clear()
          queryClient.clear()
        }
      },
    }),
    [me.data, me.isPending, queryClient, token],
  )

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}

// Provider and hook intentionally share the private context.
// eslint-disable-next-line react-refresh/only-export-components
export function useAuth() {
  const value = useContext(AuthContext)
  if (!value) throw new Error('useAuth must be used inside AuthProvider')
  return value
}
