// packages/commerce-cart-react/src/CartProvider.tsx
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from 'react'
import type { Cart } from '@platform-modules/commerce-cart'
import type { CartClient } from './client.js'
import { CartProviderError } from './errors.js'
import { useCartMutations } from './useCartMutations.js'

export interface CartContextValue {
  client: CartClient
  cart: Cart | null
  loading: boolean
  error: Error | null
  reload: () => void
  /** Internal — the Provider's setter; the mutation engine (Task 7) drives it. */
  setCart: (cart: Cart) => void
  /** Internal — live cart for the mutation engine's optimistic base (avoids stale closures). */
  cartRef: React.MutableRefObject<Cart | null>
  /** Mutation surface (Task 7). */
  actions: import('./useCartMutations.js').CartMutations
}

const CartContext = createContext<CartContextValue | null>(null)

export function useCartContext(hook: string): CartContextValue {
  const ctx = useContext(CartContext)
  if (!ctx) throw new CartProviderError(hook)
  return ctx
}

export function CartProvider({
  client,
  initialCart,
  children,
}: {
  client: CartClient
  initialCart?: Cart
  children: ReactNode
}) {
  const hasSeed = initialCart !== undefined
  const [cart, setCart] = useState<Cart | null>(hasSeed ? initialCart : null)
  const [loading, setLoading] = useState<boolean>(!hasSeed)
  const [error, setError] = useState<Error | null>(null)
  const [reloadToken, setReloadToken] = useState(0)

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

  const cartRef = useRef<Cart | null>(cart)
  cartRef.current = cart

  // The reloadToken we already hold data for — init to the seed's token so the seeded
  // mount (and any StrictMode dev re-invoke of it) skips the fetch; reload() bumps the
  // token → mismatch → fetch. SETTLED-KEY, never a one-shot flag (Chesterton's Fence:
  // a flag flips on the first dev setup, so StrictMode's second setup fetches over the seed).
  const settledTokenRef = useRef<number | null>(hasSeed ? 0 : null)

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

  useEffect(() => {
    if (settledTokenRef.current === reloadToken) return
    let cancelled = false
    setLoading(true)
    setError(null)
    void clientRef.current
      .getCart()
      .then((c) => {
        if (!cancelled) {
          settledTokenRef.current = reloadToken
          setCart(c)
          setLoading(false)
        }
      })
      .catch((e: unknown) => {
        if (!cancelled) {
          settledTokenRef.current = reloadToken
          setError(e instanceof Error ? e : new Error(String(e)))
          setLoading(false)
        }
      })
    return () => {
      cancelled = true
    }
  }, [reloadToken])

  const actions = useCartMutations({ client, cartRef, setCart })

  const value = useMemo<CartContextValue>(
    () => ({ client, cart, loading, error, reload, setCart, cartRef, actions }),
    [client, cart, loading, error, reload, actions],
  )

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