import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useRef,
  useState,
  type ReactNode,
} from 'react'
import type { BrowserOAuthClient, OAuthSession } from '@atproto/oauth-client-browser'
import { recordAcceptedTerms } from '~/lib/api-client'

type AuthContextValue = {
  session: OAuthSession | null
  loading: boolean
  error: string | null
  signIn: (handle: string) => Promise<void>
  signOut: () => Promise<void>
}

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

export function AuthProvider({ children }: { children: ReactNode }) {
  const [session, setSession] = useState<OAuthSession | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const clientRef = useRef<BrowserOAuthClient | null>(null)

  useEffect(() => {
    // Dynamic import instead of a static import at the top of this file.
    // @atproto/oauth-client-browser instantiates BrowserRuntimeImplementation at
    // module evaluation time, and that constructor throws synchronously if
    // window.crypto.subtle is unavailable (non-HTTPS, non-localhost origins).
    // A static import would cause the entire bundle chunk to fail to evaluate
    // before React mounts, making the error uncatchable and leaving the
    // pre-rendered loading spinner on screen forever. A dynamic import runs
    // inside the promise chain, so a thrown module-level error becomes a
    // rejection that .catch() can handle normally.
    import('./oauth-client')
      .then(({ createOAuthClient }) => {
        const client = createOAuthClient({ onDelete: () => setSession(null) })
        clientRef.current = client
        return client.init()
      })
      .then((result) => {
        if (result?.session) {
          setSession(result.session)
          // result is only defined on a fresh OAuth callback (not session restoration),
          // so this fires once per actual sign-in.
          recordAcceptedTerms(result.session).catch(() => {})
        }
      })
      .catch((err: unknown) => {
        setError(err instanceof Error ? err.message : 'Authentication failed')
      })
      .finally(() => setLoading(false))
  }, [])

  const signIn = useCallback(async (handle: string) => {
    if (!clientRef.current) throw new Error('OAuth client not ready')
    await clientRef.current.signIn(handle)
    // Never reached — signIn redirects the browser
  }, [])

  const signOut = useCallback(async () => {
    if (session) await session.signOut()
    setSession(null)
    setError(null)
    window.history.replaceState(null, '', window.location.pathname)
  }, [session])

  return (
    <AuthContext.Provider value={{ session, loading, error, signIn, signOut }}>
      {children}
    </AuthContext.Provider>
  )
}

export function useAuth() {
  const ctx = useContext(AuthContext)
  if (!ctx) throw new Error('useAuth must be used within AuthProvider')
  return ctx
}
