'use client'

import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'

import { dirFor, type Locale } from '@/lib/i18n/config'
import {
  createTranslator,
  formatDate,
  formatDateTime,
  formatDuration,
  formatMw,
  formatNumber,
  formatPercent,
  formatRelative,
  formatTime,
  type Messages,
  type Translator,
} from '@/lib/i18n/translate'

interface I18nValue {
  locale: Locale
  dir: 'rtl' | 'ltr'
  t: Translator
  n: (value: number, options?: Intl.NumberFormatOptions) => string
  mw: (value: number) => string
  pct: (value: number, digits?: number) => string
  dateTime: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string
  date: (value: Date | number) => string
  time: (value: Date | number) => string
  relative: (value: Date | number) => string
  duration: (minutes: number) => string
}

const I18nContext = createContext<I18nValue | null>(null)

/**
 * Makes the active dictionary available to client components.
 *
 * The whole dictionary is passed down rather than fetched, so a language switch is an
 * instant re-render with no loading state — which matters on a wall display that should
 * never flash empty.
 *
 * It also owns the reference instant for relative times, and that is a correctness matter
 * rather than a convenience. "Two seconds ago" rendered on the server and "three seconds
 * ago" rendered a moment later in the browser are different text for the same node, which
 * is a hydration failure — and `'use client'` does not exempt a component from it, because
 * a client component still renders on the server for the first HTML. So the reference is
 * pinned to the instant the server rendered, and only after mount does it start following
 * the real clock. The first client render is then byte-identical to the server's.
 */
export function I18nProvider({
  locale,
  messages,
  renderedAt,
  children,
}: {
  locale: Locale
  messages: Messages
  /** The instant the server rendered. Pinned through hydration, then released. */
  renderedAt: number
  children: ReactNode
}) {
  // Null until mounted, which is exactly when the pinned reference must give way.
  const [liveClock, setLiveClock] = useState<number | null>(null)

  useEffect(() => {
    setLiveClock(Date.now())
    // Relative labels are read at render time, so this only needs to be often enough that
    // "a minute ago" does not sit there for an hour.
    const timer = setInterval(() => setLiveClock(Date.now()), 30_000)
    return () => clearInterval(timer)
  }, [])

  const value = useMemo<I18nValue>(() => {
    const t = createTranslator(messages)
    return {
      locale,
      dir: dirFor(locale),
      t,
      n: (v, options) => formatNumber(locale, v, options),
      mw: (v) => formatMw(locale, v),
      pct: (v, digits) => formatPercent(locale, v, digits),
      dateTime: (v, options) => formatDateTime(locale, v, options),
      date: (v) => formatDate(locale, v),
      time: (v) => formatTime(locale, v),
      // Pinned to the server's instant until mount; live afterwards.
      relative: (v) => formatRelative(locale, v, liveClock ?? renderedAt),
      duration: (minutes) => formatDuration(t, minutes),
    }
  }, [locale, messages, liveClock, renderedAt])

  return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
}

export function useI18n(): I18nValue {
  const value = useContext(I18nContext)
  if (!value) throw new Error('useI18n must be used inside <I18nProvider>')
  return value
}
