import { useEffect, useRef } from 'react';

import { Gradient } from '../lib/meshGradient';
import { channelsToHex } from '../lib/theme/color';
import { useAppSelector } from '../store/hooks';
import { selectEffectiveTheme, selectThemeVariant } from '../store/themeSlice';

/**
 * Animated WebGL mesh gradient background (Stripe-style), tinted by the active
 * theme. The four gradient stops are derived from the theme's primary ramp +
 * surface token (read as hex from the resolved CSS variables), so the backdrop
 * follows Matrix green / HAL red / Ocean blue / etc. It re-initialises whenever
 * the active theme or its light/dark variant changes.
 *
 * Renders behind the dotted-canvas overlay so dots remain visible on top, and
 * catches WebGL errors gracefully (Tauri WebView can lack a GPU context).
 */
export default function MeshGradient() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  // Re-init the gradient when the effective theme's mesh-driving tokens change.
  const effectiveTheme = useAppSelector(selectEffectiveTheme);
  const variant = useAppSelector(selectThemeVariant);
  const tokenSignature = [
    effectiveTheme.id,
    effectiveTheme.isDark ? 'dark' : 'light',
    effectiveTheme.colors['primary-700'] ?? '',
    effectiveTheme.colors['primary-300'] ?? '',
    effectiveTheme.colors.surface ?? '',
    effectiveTheme.colors['primary-500'] ?? '',
  ].join('|');

  useEffect(() => {
    let gradient: InstanceType<typeof Gradient> | null = null;
    let raf = 0;
    let disposed = false;

    const disconnectGradient = () => {
      try {
        if (gradient) {
          gradient.disconnect();
          gradient.pause();
        }
      } catch {
        // Cleanup is best-effort.
      } finally {
        gradient = null;
      }
    };

    // Only animate when the app window is actually visible AND focused, and the
    // user hasn't asked for reduced motion. A full-screen WebGL shader that keeps
    // rendering every frame while the app merely sits open in the background
    // (occluded/blurred) or minimized is pure wasted GPU/CPU — the sustained
    // load users see as the machine running hot with nothing happening (#3524).
    const prefersReducedMotion = () =>
      typeof window !== 'undefined' && typeof window.matchMedia === 'function'
        ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
        : false;

    const applyPlayState = () => {
      if (!gradient) return;
      const shouldAnimate = !document.hidden && document.hasFocus() && !prefersReducedMotion();
      // Read the gradient's own play flag so we never double-schedule its rAF
      // loop (calling play() while already playing starts a second loop).
      const playing = gradient.conf?.playing ?? false;
      if (shouldAnimate === playing) return;
      try {
        // Only (re)start once the WebGL mesh actually initialized. On a no-GPU /
        // headless environment `connect()` leaves `conf.playing` truthy without
        // ever creating `mesh`, so resuming here would schedule `animate`, which
        // dereferences `mesh.material` on the next frame and throws — exactly the
        // environment this component is meant to tolerate (#3524). `pause()` is
        // always safe (it just clears the flag / cancels any rAF).
        if (shouldAnimate) {
          if (gradient.mesh) gradient.play();
        } else {
          gradient.pause();
        }
      } catch {
        // Play-state control is best-effort.
      }
    };

    const start = () => {
      if (disposed) return;
      const root = document.documentElement;
      const canvas = canvasRef.current;
      if (!canvas) return;
      disconnectGradient();
      const hex = (token: string, fallback: string) => {
        const v = window.getComputedStyle(root).getPropertyValue(`--${token}`).trim();
        return v ? channelsToHex(v) : fallback;
      };
      // Accent-led stops + the surface base, so the mesh reads on any theme.
      canvas.style.setProperty('--gradient-color-1', hex('primary-700', '#0019d9'));
      canvas.style.setProperty('--gradient-color-2', hex('primary-300', '#b5d5ff'));
      canvas.style.setProperty('--gradient-color-3', hex('surface', '#ffffff'));
      canvas.style.setProperty('--gradient-color-4', hex('primary-500', '#4fa4ff'));
      console.debug('[theme] mesh gradient stops applied', { themeId: effectiveTheme.id, variant });

      try {
        gradient = new Gradient();
        gradient.initGradient('#mesh-gradient');
        // Honor the current visibility/focus/reduced-motion state right away, so
        // a gradient created while the window is backgrounded never animates.
        applyPlayState();
      } catch (err) {
        console.warn('[MeshGradient] WebGL init failed, gradient disabled:', err);
        gradient = null;
      }
    };

    const scheduleStart = () => {
      window.cancelAnimationFrame(raf);
      // Defer one frame so ThemeProvider (an ancestor) has applied the theme's
      // CSS variables before we read them — child effects run before parents.
      raf = window.requestAnimationFrame(start);
    };

    scheduleStart();

    // Pause/resume the animation as the window gains or loses visibility/focus
    // (and when the reduced-motion preference flips), so it only ever burns
    // cycles while the user is actually looking at a focused OpenHuman window.
    const onPlayStateChange = () => applyPlayState();
    document.addEventListener('visibilitychange', onPlayStateChange);
    window.addEventListener('focus', onPlayStateChange);
    window.addEventListener('blur', onPlayStateChange);
    let removeMotionListener: (() => void) | undefined;
    if (typeof window !== 'undefined' && window.matchMedia) {
      const motionMq = window.matchMedia('(prefers-reduced-motion: reduce)');
      if (motionMq.addEventListener) {
        motionMq.addEventListener('change', onPlayStateChange);
        removeMotionListener = () => motionMq.removeEventListener('change', onPlayStateChange);
      } else {
        motionMq.addListener(onPlayStateChange);
        removeMotionListener = () => motionMq.removeListener(onPlayStateChange);
      }
    }

    let removeSystemListener: (() => void) | undefined;
    if (variant === 'system' && typeof window !== 'undefined' && window.matchMedia) {
      const mq = window.matchMedia('(prefers-color-scheme: dark)');
      const listener = () => scheduleStart();
      if (mq.addEventListener) {
        mq.addEventListener('change', listener);
        removeSystemListener = () => mq.removeEventListener('change', listener);
      } else {
        mq.addListener(listener);
        removeSystemListener = () => mq.removeListener(listener);
      }
    }

    return () => {
      disposed = true;
      removeSystemListener?.();
      removeMotionListener?.();
      document.removeEventListener('visibilitychange', onPlayStateChange);
      window.removeEventListener('focus', onPlayStateChange);
      window.removeEventListener('blur', onPlayStateChange);
      window.cancelAnimationFrame(raf);
      disconnectGradient();
    };
  }, [effectiveTheme.id, tokenSignature, variant]);

  return (
    <canvas
      ref={canvasRef}
      id="mesh-gradient"
      data-transition-in
      className="absolute inset-0 w-full h-full opacity-10"
    />
  );
}
