import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react'
import { api } from '../api/client.js'

const AuthContext = createContext(null)

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  const refresh = useCallback(async () => {
    try {
      const data = await api.me()
      setUser(data.user)
    } catch {
      setUser(null)
    } finally {
      setLoading(false)
    }
  }, [])

  useEffect(() => {
    refresh()
  }, [refresh])

  // Step 1. Returns { user } on success, or { totpRequired, challenge } when the
  // account has 2FA on (caller then calls loginTotp). `extra` carries honeypot.
  const login = useCallback(async (username, password, extra) => {
    const data = await api.login(username, password, extra)
    if (data.user) setUser(data.user)
    return data
  }, [])

  // Public self-registration (player). Creates the account, sets the session
  // cookie, and returns { user }. `extra` carries the honeypot + optional email.
  const register = useCallback(async (username, password, extra) => {
    const data = await api.register(username, password, extra)
    if (data.user) setUser(data.user)
    return data
  }, [])

  // Step 2 for TOTP users: exchange the challenge + a second factor (TOTP code or a
  // recovery code) for a real session. `extra` carries recoveryCode + the
  // trustDevice/deviceName opt-in. Returns the full payload ({ user,
  // trustLimitReached?, devices? }) so the caller can handle the device-cap prompt.
  const loginTotp = useCallback(async (challenge, code, extra) => {
    const data = await api.loginTotp(challenge, code, extra)
    setUser(data.user)
    return data
  }, [])

  // Step 2 for SSO logins whose account has 2FA on. The pending challenge lives in
  // an httpOnly cookie, so only the code is sent. `extra` carries the trustDevice/
  // deviceName opt-in. Returns the full payload ({ user, returnTo,
  // trustLimitReached?, devices? }) so the caller can handle the device-cap prompt.
  const ssoLoginTotp = useCallback(async (code, extra) => {
    const data = await api.ssoLoginTotp(code, extra)
    setUser(data.user)
    return data
  }, [])

  const logout = useCallback(async () => {
    try {
      await api.logout()
    } finally {
      setUser(null)
    }
  }, [])

  // Memoized so consumers don't re-render on every provider render (the callbacks
  // are already stable via useCallback).
  const value = useMemo(
    () => ({ user, loading, login, register, loginTotp, ssoLoginTotp, logout, refresh }),
    [user, loading, login, register, loginTotp, ssoLoginTotp, logout, refresh],
  )

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

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