'use client'

import {
  accentTokensToCssVars,
  generateAdminThemeTokens,
  type ColorMode,
  type SidebarDensity,
} from '@actuate-media/cms-core/appearance'
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'

/**
 * Admin appearance engine.
 *
 * Owns the live, DOM-applied admin theme: color mode (light/dark/system),
 * accent color, sidebar density, content font, reduced motion, and the
 * resolved admin brand logos used by the sidebar. State is mirrored to
 * localStorage so a reload paints instantly without a render-blocking fetch;
 * the Appearance tab pushes the DB-backed values in via `applyAppearance` on
 * load and save, making the database the source of truth and localStorage the
 * warm cache.
 *
 * Backward compatible: `useTheme()` still exposes `{ theme, resolvedTheme,
 * setTheme }`; the extra fields are additive.
 */

/** Resolved admin logo URLs (cached for the sidebar to paint without a fetch). */
export interface AdminBrandCache {
  primaryUrl: string | null
  markUrl: string | null
  darkUrl: string | null
  initials: string
}

interface ThemeContextValue {
  theme: ColorMode
  resolvedTheme: 'light' | 'dark'
  setTheme: (theme: ColorMode) => void
  accent: string
  setAccent: (accent: string) => void
  density: SidebarDensity
  setDensity: (density: SidebarDensity) => void
  font: string
  setFont: (font: string) => void
  reduceMotion: boolean
  setReduceMotion: (value: boolean) => void
  adminBrand: AdminBrandCache
  setAdminBrand: (brand: AdminBrandCache) => void
  /** Push a full settings snapshot live (used by the Appearance tab). */
  applyAppearance: (snapshot: AppearanceSnapshot) => void
}

export interface AppearanceSnapshot {
  theme?: ColorMode
  accent?: string
  density?: SidebarDensity
  font?: string
  reduceMotion?: boolean
  adminBrand?: AdminBrandCache
}

/** Curated content-font options. Applied as family strings (no web-font load). */
export const AVAILABLE_FONTS = ['DM Sans', 'Inter', 'Geist', 'System UI'] as const

const SYSTEM_STACK = "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"

const FONT_STACKS: Record<string, string> = {
  'DM Sans': `'DM Sans', ${SYSTEM_STACK}`,
  Inter: `'Inter', ${SYSTEM_STACK}`,
  Geist: `'Geist', ${SYSTEM_STACK}`,
  'System UI': SYSTEM_STACK,
}

function fontStack(font: string): string {
  return FONT_STACKS[font] ?? `'${font}', ${SYSTEM_STACK}`
}

const DEFAULT_BRAND: AdminBrandCache = {
  primaryUrl: null,
  markUrl: null,
  darkUrl: null,
  initials: 'A',
}

const ThemeContext = createContext<ThemeContextValue>({
  theme: 'system',
  resolvedTheme: 'light',
  setTheme: () => {},
  accent: 'purple',
  setAccent: () => {},
  density: 'default',
  setDensity: () => {},
  font: 'DM Sans',
  setFont: () => {},
  reduceMotion: false,
  setReduceMotion: () => {},
  adminBrand: DEFAULT_BRAND,
  setAdminBrand: () => {},
  applyAppearance: () => {},
})

export function useTheme() {
  return useContext(ThemeContext)
}

function getSystemTheme(): 'light' | 'dark' {
  if (typeof window === 'undefined') return 'light'
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}

function resolveTheme(theme: ColorMode): 'light' | 'dark' {
  return theme === 'system' ? getSystemTheme() : theme
}

const KEY_THEME = 'actuate-theme'
const KEY_ACCENT = 'actuate-accent'
const KEY_DENSITY = 'actuate-density'
const KEY_FONT = 'actuate-font'
const KEY_MOTION = 'actuate-reduce-motion'
const KEY_BRAND = 'actuate-admin-brand'

function adminRootEl(): HTMLElement | null {
  return document.querySelector('.actuate-admin')
}

function applyColorMode(resolved: 'light' | 'dark') {
  const el = adminRootEl()
  if (!el) return
  el.classList.toggle('dark', resolved === 'dark')
}

function applyAccent(accent: string, resolved: 'light' | 'dark') {
  const el = adminRootEl()
  if (!el) return
  const vars = accentTokensToCssVars(
    generateAdminThemeTokens({ adminAccentColor: accent }, resolved),
  )
  for (const [name, value] of Object.entries(vars)) el.style.setProperty(name, value)
}

function applyDensity(density: SidebarDensity) {
  const el = adminRootEl()
  if (!el) return
  el.setAttribute('data-density', density)
}

function applyFont(font: string) {
  const el = adminRootEl()
  if (!el) return
  el.style.setProperty('--actuate-admin-font', fontStack(font))
}

function applyReduceMotion(value: boolean) {
  const el = adminRootEl()
  if (!el) return
  el.classList.toggle('reduce-motion', value)
}

interface ThemeProviderProps {
  children: ReactNode
  /** When true, default to dark mode if the user hasn't explicitly chosen one. */
  defaultDarkMode?: boolean
}

export function ThemeProvider({ children, defaultDarkMode }: ThemeProviderProps) {
  const [theme, setThemeState] = useState<ColorMode>('system')
  const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light')
  const [accent, setAccentState] = useState('purple')
  const [density, setDensityState] = useState<SidebarDensity>('default')
  const [font, setFontState] = useState('DM Sans')
  const [reduceMotion, setReduceMotionState] = useState(false)
  const [adminBrand, setAdminBrandState] = useState<AdminBrandCache>(DEFAULT_BRAND)

  // Hydrate from localStorage on mount and apply everything to the DOM.
  useEffect(() => {
    const storedTheme = localStorage.getItem(KEY_THEME) as ColorMode | null
    let initialTheme: ColorMode
    if (storedTheme && ['light', 'dark', 'system'].includes(storedTheme)) {
      initialTheme = storedTheme
    } else if (defaultDarkMode === true) {
      initialTheme = 'dark'
    } else if (defaultDarkMode === false) {
      initialTheme = 'light'
    } else {
      initialTheme = 'system'
    }
    const resolved = resolveTheme(initialTheme)

    const storedAccent = localStorage.getItem(KEY_ACCENT) || 'purple'
    const storedDensity = (localStorage.getItem(KEY_DENSITY) as SidebarDensity | null) || 'default'
    const storedFont = localStorage.getItem(KEY_FONT) || 'DM Sans'
    const storedMotion = localStorage.getItem(KEY_MOTION) === 'true'
    let storedBrand = DEFAULT_BRAND
    try {
      const raw = localStorage.getItem(KEY_BRAND)
      if (raw) storedBrand = { ...DEFAULT_BRAND, ...JSON.parse(raw) }
    } catch {
      // Ignore malformed cache.
    }

    setThemeState(initialTheme)
    setResolvedTheme(resolved)
    setAccentState(storedAccent)
    setDensityState(storedDensity)
    setFontState(storedFont)
    setReduceMotionState(storedMotion)
    setAdminBrandState(storedBrand)

    applyColorMode(resolved)
    applyAccent(storedAccent, resolved)
    applyDensity(storedDensity)
    applyFont(storedFont)
    applyReduceMotion(storedMotion)
  }, [defaultDarkMode])

  // Follow the OS color scheme while in `system` mode (re-applies accent too,
  // since presets differ between light and dark).
  useEffect(() => {
    const mq = window.matchMedia('(prefers-color-scheme: dark)')
    const handler = () => {
      if (theme === 'system') {
        const resolved = getSystemTheme()
        setResolvedTheme(resolved)
        applyColorMode(resolved)
        applyAccent(accent, resolved)
      }
    }
    mq.addEventListener('change', handler)
    return () => mq.removeEventListener('change', handler)
  }, [theme, accent])

  const setTheme = useCallback(
    (next: ColorMode) => {
      setThemeState(next)
      localStorage.setItem(KEY_THEME, next)
      const resolved = resolveTheme(next)
      setResolvedTheme(resolved)
      applyColorMode(resolved)
      applyAccent(accent, resolved)
    },
    [accent],
  )

  const setAccent = useCallback(
    (next: string) => {
      setAccentState(next)
      localStorage.setItem(KEY_ACCENT, next)
      applyAccent(next, resolvedTheme)
    },
    [resolvedTheme],
  )

  const setDensity = useCallback((next: SidebarDensity) => {
    setDensityState(next)
    localStorage.setItem(KEY_DENSITY, next)
    applyDensity(next)
  }, [])

  const setFont = useCallback((next: string) => {
    setFontState(next)
    localStorage.setItem(KEY_FONT, next)
    applyFont(next)
  }, [])

  const setReduceMotion = useCallback((next: boolean) => {
    setReduceMotionState(next)
    localStorage.setItem(KEY_MOTION, String(next))
    applyReduceMotion(next)
  }, [])

  const setAdminBrand = useCallback((next: AdminBrandCache) => {
    setAdminBrandState(next)
    localStorage.setItem(KEY_BRAND, JSON.stringify(next))
  }, [])

  // Apply a full snapshot live (Appearance tab on load/save). Persists to
  // localStorage so the choice survives reloads even before the next fetch.
  const applyAppearance = useCallback(
    (snapshot: AppearanceSnapshot) => {
      if (snapshot.theme !== undefined) setTheme(snapshot.theme)
      if (snapshot.accent !== undefined) setAccent(snapshot.accent)
      if (snapshot.density !== undefined) setDensity(snapshot.density)
      if (snapshot.font !== undefined) setFont(snapshot.font)
      if (snapshot.reduceMotion !== undefined) setReduceMotion(snapshot.reduceMotion)
      if (snapshot.adminBrand !== undefined) setAdminBrand(snapshot.adminBrand)
    },
    [setTheme, setAccent, setDensity, setFont, setReduceMotion, setAdminBrand],
  )

  return (
    <ThemeContext.Provider
      value={{
        theme,
        resolvedTheme,
        setTheme,
        accent,
        setAccent,
        density,
        setDensity,
        font,
        setFont,
        reduceMotion,
        setReduceMotion,
        adminBrand,
        setAdminBrand,
        applyAppearance,
      }}
    >
      {children}
    </ThemeContext.Provider>
  )
}
