'use client';

import { createContext, useContext, useCallback, useEffect, useRef, useState, ReactNode } from 'react';

interface BrandingData {
  site_title: string;
  site_primary_color: string;
  site_logo_url: string;
  site_favicon_url: string;
  site_font_family: string;
  site_tagline: string;
  site_support_email: string;
  site_marketing_url: string;
}

interface BrandingContextValue {
  branding: BrandingData;
  loaded: boolean;
  refreshBranding: () => Promise<void>;
}

const DEFAULT_BRANDING: BrandingData = {
  site_title: 'RestroAgent',
  site_primary_color: '',
  site_logo_url: '',
  site_favicon_url: '',
  site_font_family: '',
  site_tagline: '',
  site_support_email: '',
  site_marketing_url: '',
};

const BrandingContext = createContext<BrandingContextValue>({
  branding: DEFAULT_BRANDING,
  loaded: false,
  refreshBranding: async () => {},
});

function hexToHslString(hex: string): string | null {
  const m = hex.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
  if (!m) return null;
  const r = parseInt(m[1], 16) / 255;
  const g = parseInt(m[2], 16) / 255;
  const b = parseInt(m[3], 16) / 255;
  const max = Math.max(r, g, b), min = Math.min(r, g, b);
  let h = 0, s = 0;
  const l = (max + min) / 2;
  if (max !== min) {
    const d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    switch (max) {
      case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
      case g: h = ((b - r) / d + 2) / 6; break;
      default: h = ((r - g) / d + 4) / 6; break;
    }
  }
  return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
}

function applyBrandingToDOM(merged: BrandingData) {
  if (merged.site_primary_color) {
    const hsl = hexToHslString(merged.site_primary_color);
    if (hsl) document.documentElement.style.setProperty('--primary', hsl);
  }
  if (merged.site_title) {
    document.title = merged.site_title;
  }
  if (merged.site_favicon_url) {
    // The favicon is set server-side via generateMetadata() in app/layout.tsx
    // and served by app/favicon.ico/route.ts on first paint. This client-side
    // swap is a fallback for live updates (e.g. an admin changes branding
    // mid-session) and is not on the critical first-paint path.
    let link = document.querySelector<HTMLLinkElement>('link[rel="icon"]');
    if (!link) {
      link = document.createElement('link');
      link.rel = 'icon';
      document.head.appendChild(link);
    }
    link.href = merged.site_favicon_url;
  }
  // The brand-font <link> is installed once by the inline bootstrap script
  // in app/layout.tsx (outside React's reconciler) so we don't touch it on
  // first mount. Live updates are handled by updateBrandFontLink() below,
  // which runs only when refreshBranding() detects a real font change.
}

function updateBrandFontLink(family: string) {
  if (!family) return;
  const href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@400;500;600;700&display=swap`;
  const existing = document.getElementById('ra-brand-font') as HTMLLinkElement | null;
  if (existing) {
    if (existing.href !== href) existing.href = href;
  } else {
    const link = document.createElement('link');
    link.id = 'ra-brand-font';
    link.rel = 'stylesheet';
    link.href = href;
    document.head.appendChild(link);
  }
  const fontStack = `'${family}', sans-serif`;
  document.documentElement.style.fontFamily = fontStack;
  document.body.style.fontFamily = fontStack;
}

export function BrandingProvider({
  children,
  initialBranding,
  initialVersion,
}: {
  children: ReactNode;
  initialBranding?: Partial<BrandingData>;
  initialVersion?: string;
}) {
  // Seed from server-provided values so consumers see real branding on first
  // render — no flash from DEFAULT_BRANDING. We mark loaded=true immediately
  // when initial values are supplied; the network fetch only runs as an
  // explicit refresh path (refreshBranding()) for live mid-session updates.
  const hasInitial = !!initialBranding;
  const seeded: BrandingData = {
    ...DEFAULT_BRANDING,
    ...(initialBranding ?? {}),
  };
  const [branding, setBranding] = useState<BrandingData>(seeded);
  const [loaded, setLoaded] = useState(hasInitial);
  const versionRef = useRef<string | null>(initialVersion ?? null);

  const lastFontFamilyRef = useRef<string>(seeded.site_font_family || '');

  const fetchBranding = useCallback(async () => {
    try {
      const r = await fetch('/api/public/branding', { cache: 'no-store' });
      const data = await r.json() as { branding: Record<string, string> };
      const b = data.branding ?? {};
      const merged: BrandingData = {
        site_title: b.site_title || DEFAULT_BRANDING.site_title,
        site_primary_color: b.site_primary_color || '',
        site_logo_url: b.site_logo_url || '',
        site_favicon_url: b.site_favicon_url || '',
        site_font_family: b.site_font_family || '',
        site_tagline: b.site_tagline || '',
        site_support_email: b.site_support_email || '',
        site_marketing_url: b.site_marketing_url || '',
      };
      setBranding(merged);
      applyBrandingToDOM(merged);
      // Live font swaps: only touch the <link> when the family actually
      // changed. The initial link is owned by the inline bootstrap script
      // in app/layout.tsx, not React or this effect.
      if (merged.site_font_family && merged.site_font_family !== lastFontFamilyRef.current) {
        updateBrandFontLink(merged.site_font_family);
        lastFontFamilyRef.current = merged.site_font_family;
      }
      setLoaded(true);
    } catch {
      setLoaded(true);
    }
  }, []);

  useEffect(() => {
    // Only fetch on mount if we weren't seeded from the server. Otherwise
    // first paint already has correct branding and refreshBranding() is the
    // explicit path for live updates.
    if (!hasInitial) fetchBranding();
  }, [hasInitial, fetchBranding]);

  // Live update: poll a lightweight version endpoint and refresh branding
  // whenever the cached server version changes (e.g. an admin saves the
  // Branding page, which calls revalidateTag('branding') and bumps the
  // returned version on the next read). Pause polling when the tab is
  // hidden to avoid waking sleeping tabs.
  useEffect(() => {
    if (typeof window === 'undefined') return;
    let cancelled = false;
    let timer: ReturnType<typeof setTimeout> | null = null;

    const POLL_MS = 5000;

    const tick = async () => {
      if (cancelled) return;
      if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {
        timer = setTimeout(tick, POLL_MS);
        return;
      }
      try {
        const r = await fetch('/api/public/branding/version', { cache: 'no-store' });
        if (r.ok) {
          const data = (await r.json()) as { version?: string };
          const next = data.version ?? null;
          const prev = versionRef.current;
          if (next && prev && next !== prev) {
            versionRef.current = next;
            await fetchBranding();
          } else if (next && !prev) {
            versionRef.current = next;
          }
        }
      } catch {
        /* keep polling — server may be transiently unavailable */
      }
      if (!cancelled) timer = setTimeout(tick, POLL_MS);
    };

    timer = setTimeout(tick, POLL_MS);

    const onVisible = () => {
      if (document.visibilityState === 'visible') {
        // Re-check immediately when the tab regains focus.
        if (timer) clearTimeout(timer);
        timer = setTimeout(tick, 0);
      }
    };
    document.addEventListener('visibilitychange', onVisible);

    return () => {
      cancelled = true;
      if (timer) clearTimeout(timer);
      document.removeEventListener('visibilitychange', onVisible);
    };
  }, [fetchBranding]);

  return (
    <BrandingContext.Provider value={{ branding, loaded, refreshBranding: fetchBranding }}>
      {children}
    </BrandingContext.Provider>
  );
}

export function useBranding(): BrandingContextValue {
  return useContext(BrandingContext);
}
