import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from 'react'
import type { Principal } from '@platform-modules/auth'
import type { AuthClient } from './client.js'
import { AuthProviderError } from './errors.js'

export interface AuthContextValue {
  client: AuthClient
  user: Principal | null
  loading: boolean
  error: Error | null
  reload: () => void
  setUser: (user: Principal | null) => void
}

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

export function useAuthContext(hook: string): AuthContextValue {
  const ctx = useContext(AuthContext)
  if (!ctx) throw new AuthProviderError(hook)
  return ctx
}

/** Holds the current Principal snapshot and refreshes it on mount. */
export function AuthProvider({ client, children }: { client: AuthClient; children: ReactNode }) {
  const [user, setUser] = useState<Principal | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)
  const [reloadToken, setReloadToken] = useState(0)

  const clientRef = useRef(client)
  clientRef.current = client

  const reload = useCallback(() => {
    setReloadToken((t) => t + 1)
  }, [])

  useEffect(() => {
    let cancelled = false
    setLoading(true)
    setError(null)

    void clientRef.current
      .getCurrentUser()
      .then((principal) => {
        if (!cancelled) {
          setUser(principal)
          setLoading(false)
        }
      })
      .catch((e: unknown) => {
        if (!cancelled) {
          setError(e instanceof Error ? e : new Error(String(e)))
          setLoading(false)
        }
      })

    return () => {
      cancelled = true
    }
  }, [reloadToken])

  const value = useMemo(
    () => ({
      client,
      user,
      loading,
      error,
      reload,
      setUser,
    }),
    [client, user, loading, error, reload],
  )

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