import { createInertiaApp, router } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'
import type { ComponentType } from 'react'

import '@/styles/app.css'
import { TooltipProvider } from '@/components/ui/tooltip'
import { setLocale } from '@/lib/i18n'
import type { Catalog, Locale } from '@/lib/i18n'
import { setRoutes } from '@/lib/routes'
import type { RouteMap } from '@/lib/routes'
import { applyTheme } from '@/lib/theme'
import type { ThemePreference } from '@/lib/theme'

// Las páginas se cargan bajo demanda: cada vista viaja sólo cuando se visita.
const pages = import.meta.glob<{ default: ComponentType }>('./pages/**/*.tsx')

// Los catálogos también: el idioma que no se está leyendo no se descarga.
const catalogs = import.meta.glob<{ default: Catalog }>('./locales/*.json')

interface Preferences {
  language: Locale
  theme: ThemePreference
}

/**
 * Deja el idioma instalado.
 *
 * El catálogo viaja aparte, así que esto es asíncrono y el arranque lo espera:
 * una pantalla dibujada antes de que llegue su catálogo reventaría en la primera
 * clave que pida. Una navegación posterior no espera a nadie porque el idioma ya
 * está instalado, y **cambiar de idioma no navega: recarga** —lo hace el perfil,
 * que es el único lugar donde se cambia—, así que vuelve a pasar por acá con el
 * documento todavía en blanco.
 */
let installed: Locale | null = null

async function installLocale(language: Locale): Promise<void> {
  if (installed === language) return
  const module = await catalogs[`./locales/${language}.json`]()
  setLocale(language, module.default)
  installed = language
}

// Las rutas publicadas se guardan en un módulo y no en el árbol de React para
// que `route()` se pueda llamar desde cualquier lado, no sólo desde dentro de un
// componente. Se refrescan en cada navegación porque el servidor es quien decide
// qué se publica, y eso puede cambiar entre despliegues.
//
// El tema viaja por el mismo canal y por el mismo motivo: lo decide la cuenta, y
// eso puede cambiar entre una pantalla y la siguiente. El idioma no se refresca
// acá: cambiarlo recarga la página entera, y entonces pasa por el arranque.
router.on('navigate', (event) => {
  const props = event.detail.page.props
  setRoutes(props.routes as RouteMap)

  const preferences = props.preferences as Preferences | undefined
  if (preferences) applyTheme(preferences.theme)
})

/**
 * Las preferencias de la página inicial, leídas del documento.
 *
 * Se leen de acá y no de `setup()` porque para entonces ya es tarde: el catálogo
 * tiene que estar cargado **antes** de que se dibuje la primera pantalla, y
 * `setup()` es justamente quien la dibuja.
 *
 * **La página viaja en el contenido del `<script>`, no en el atributo.**
 * `inertia-django` rinde `<script data-page="app" type="application/json">{…}</script>`
 * seguido del div de montaje: ahí `data-page` es un marcador que nombra al div y
 * su valor es la cadena «app». La forma de Laravel —el JSON adentro del
 * atributo— es la que casi todos los ejemplos muestran, y leerla así devuelve
 * «app» y revienta el `JSON.parse` en la primera línea del arranque.
 *
 * Nunca tira: si el documento no trae la página, se arranca en inglés y con el
 * tema que ya pintó la plantilla. Es el camino de una respuesta rara —una
 * pantalla de error, una redirección a medias— y dejar la aplicación sin montar
 * por eso convierte un problema de servidor en una pantalla en blanco.
 */
function initialPreferences(): Preferences | null {
  const mount = document.querySelector<HTMLScriptElement>('script[data-page]')
  const serialized = mount?.textContent?.trim()
  if (!serialized) return null

  try {
    const page = JSON.parse(serialized) as { props?: { preferences?: Preferences } }
    return page.props?.preferences ?? null
  } catch {
    return null
  }
}

async function boot(): Promise<void> {
  const preferences = initialPreferences()

  if (preferences) {
    // El tema ya lo pintó la plantilla base; esto deja al módulo sabiendo cuál
    // es —lo necesitan los avisos flotantes— y engancha el seguimiento del
    // sistema cuando corresponde.
    applyTheme(preferences.theme)
    await installLocale(preferences.language)
  } else {
    await installLocale('en')
  }

  await createInertiaApp({
  // Inertia trae por defecto los nombres de Laravel para el testigo anti-CSRF.
  // Django usa otros, y con los de fábrica toda petición que escribe recibe un
  // 403 sin explicación: el testigo viaja en una cookie que el servidor no lee.
    http: {
      xsrfCookieName: 'csrftoken',
      xsrfHeaderName: 'X-CSRFToken',
    },

    resolve: (name) => {
      const page = pages[`./pages/${name}.tsx`]
      if (!page) {
        // Que el servidor pida una página que el frontend no tiene es un error de
        // programación, no una condición que el usuario deba interpretar.
        throw new Error(
          `Page "${name}" does not exist in frontend/pages. ` +
            `Check the name the Django view returns.`
        )
      }
      // Inertia espera el componente, no el módulo que lo contiene.
      return page().then((module) => module.default)
    },

    setup({ el, App, props }) {
      // La primera carga no dispara «navigate», así que las rutas se toman de la
      // página inicial: sin esto, el primer render no podría resolver ningún enlace.
      setRoutes(props.initialPage.props.routes as RouteMap)
      // El proveedor de globos va en la raíz y no en cada pantalla: los globos de
      // la barra lateral aparecen cuando está plegada a íconos, y sin un proveedor
      // por encima el componente de Radix rompe el render entero en vez de
      // degradarse a no mostrar el globo.
      createRoot(el).render(
        <TooltipProvider delayDuration={300}>
          <App {...props} />
        </TooltipProvider>
      )
    },
  })
}

void boot()
