"use client";

/**
 * AnimationPreferencesProvider
 *
 * Centralized manager for user animation / motion preferences:
 * - prefersReducedMotion (boolean)
 * - animationSpeedMultiplier (0.25x – 4x)
 *
 * Responsibilities:
 * 1. Hydrate from (priority):
 *    - Inline early localStorage value (if already written by an inline script in layout)
 *    - Existing localStorage value
 *    - Server value (tRPC - user profile)
 *    - Initial props (e.g. server-fetched)
 *    - System media query (prefers-reduced-motion)
 * 2. Apply preferences to:
 *    - documentElement class list (adds / removes .user-reduce-motion)
 *    - CSS custom properties: --animation-speed-fast/normal/slow
 * 3. Persist to localStorage
 * 4. Sync to server via tRPC mutation (debounced & resilient)
 *
 * Usage:
 *  <AnimationPreferencesProvider
 *     initial={{
 *       prefersReducedMotion: serverValue.prefersReducedMotion,
 *       animationSpeedMultiplier: serverValue.animationSpeedMultiplier
 *     }}
 *  >
 *     <App />
 *  </AnimationPreferencesProvider>
 *
 *  const {
 *    prefersReducedMotion,
 *    animationSpeedMultiplier,
 *    updatePreferences,
 *    isUpdating
 *  } = useAnimationPreferences();
 *
 *  updatePreferences({ animationSpeedMultiplier: 1.5 });
 *
 * NOTE: After integrating this provider, remove the duplicated logic
 * from the settings page (SettingsContent) and call updatePreferences()
 * instead of directly manipulating DOM / CSS variables there.
 */

import React, {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useRef,
  useState,
} from "react";
import { api } from "~/trpc/react";
import { authClient } from "~/lib/auth-client";

type AnimationPreferences = {
  prefersReducedMotion: boolean;
  animationSpeedMultiplier: number;
};

type PartialPrefs = Partial<AnimationPreferences>;

interface AnimationPreferencesContextValue extends AnimationPreferences {
  updatePreferences: (patch: PartialPrefs, opts?: { sync?: boolean }) => void;
  setPrefersReducedMotion: (val: boolean) => void;
  setAnimationSpeedMultiplier: (val: number) => void;
  isUpdating: boolean;
  lastSyncedAt: number | null;
}

interface AnimationPreferencesProviderProps {
  children: React.ReactNode;
  /**
   * Optional initial values (e.g. from server / tRPC prefetch).
   */
  initial?: PartialPrefs;
  /**
   * Disable auto-sync to server (mostly for test environments).
   */
  autoSync?: boolean;
}

const STORAGE_KEY = "bv.animation.prefs";
const MIN_SPEED = 0.25;
const MAX_SPEED = 4;
const DEFAULT_SPEED = 1;
const DEFAULT_PREFERS_REDUCED = false;

const AnimationPreferencesContext =
  createContext<AnimationPreferencesContextValue | null>(null);

function clampSpeed(value: number): number {
  if (Number.isNaN(value)) return DEFAULT_SPEED;
  return Math.min(MAX_SPEED, Math.max(MIN_SPEED, value));
}

function readLocalStorage(): PartialPrefs | null {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as PartialPrefs;
    if (
      typeof parsed === "object" &&
      parsed !== null &&
      ("prefersReducedMotion" in parsed || "animationSpeedMultiplier" in parsed)
    ) {
      return {
        prefersReducedMotion:
          typeof parsed.prefersReducedMotion === "boolean"
            ? parsed.prefersReducedMotion
            : undefined,
        animationSpeedMultiplier:
          typeof parsed.animationSpeedMultiplier === "number"
            ? clampSpeed(parsed.animationSpeedMultiplier)
            : undefined,
      };
    }
    return null;
  } catch {
    return null;
  }
}

function writeLocalStorage(prefs: AnimationPreferences) {
  try {
    localStorage.setItem(
      STORAGE_KEY,
      JSON.stringify({
        prefersReducedMotion: prefs.prefersReducedMotion,
        animationSpeedMultiplier: prefs.animationSpeedMultiplier,
      }),
    );
  } catch {
    // Fail silently; storage may be unavailable
  }
}

function applyPreferencesToDOM(prefs: AnimationPreferences) {
  if (typeof document === "undefined") return;
  const root = document.documentElement;

  // Class toggle
  if (prefs.prefersReducedMotion) {
    root.classList.add("user-reduce-motion");
  } else {
    root.classList.remove("user-reduce-motion");
  }

  // Derive effective speeds
  const multiplier = prefs.animationSpeedMultiplier || 1;

  const fast = prefs.prefersReducedMotion
    ? 0.01
    : parseFloat((0.15 / multiplier).toFixed(4));
  const normal = prefs.prefersReducedMotion
    ? 0.01
    : parseFloat((0.3 / multiplier).toFixed(4));
  const slow = prefs.prefersReducedMotion
    ? 0.01
    : parseFloat((0.5 / multiplier).toFixed(4));

  root.style.setProperty("--animation-speed-fast", `${fast}s`);
  root.style.setProperty("--animation-speed-normal", `${normal}s`);
  root.style.setProperty("--animation-speed-slow", `${slow}s`);
}

/**
 * Provider component
 */
export function AnimationPreferencesProvider({
  children,
  initial,
  autoSync = true,
}: AnimationPreferencesProviderProps) {
  const updateMutation = api.settings.updateAnimationPreferences.useMutation();
  const { data: session } = authClient.useSession();
  const isAuthed = !!session?.user;
  // Server query only when authenticated
  const { data: serverPrefs } = api.settings.getAnimationPreferences.useQuery(
    undefined,
    {
      enabled: isAuthed,
      refetchOnWindowFocus: false,
      staleTime: 60_000,
    },
  );

  const [prefersReducedMotion, setPrefersReducedMotion] = useState<boolean>(
    initial?.prefersReducedMotion ?? DEFAULT_PREFERS_REDUCED,
  );
  const [animationSpeedMultiplier, setAnimationSpeedMultiplier] =
    useState<number>(
      clampSpeed(initial?.animationSpeedMultiplier ?? DEFAULT_SPEED),
    );
  const [lastSyncedAt, setLastSyncedAt] = useState<number | null>(null);
  const pendingSyncRef = useRef<PartialPrefs | null>(null);
  const isHydratedRef = useRef(false);
  const serverHydratedRef = useRef(false);
  const [isUpdating, setIsUpdating] = useState(false);

  // Hydration: run once on mount (local + system + initial)
  useEffect(() => {
    if (typeof window === "undefined") return;
    const stored = readLocalStorage();

    const systemReduced =
      window.matchMedia &&
      window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    const finalPrefers =
      stored?.prefersReducedMotion ??
      initial?.prefersReducedMotion ??
      systemReduced ??
      DEFAULT_PREFERS_REDUCED;
    const finalSpeed = clampSpeed(
      stored?.animationSpeedMultiplier ??
      initial?.animationSpeedMultiplier ??
      DEFAULT_SPEED,
    );

    setPrefersReducedMotion(finalPrefers);
    setAnimationSpeedMultiplier(finalSpeed);
    applyPreferencesToDOM({
      prefersReducedMotion: finalPrefers,
      animationSpeedMultiplier: finalSpeed,
    });
    isHydratedRef.current = true;
  }, [initial?.prefersReducedMotion, initial?.animationSpeedMultiplier]);

  /**
   * Core updater
   */
  const performUpdate = useCallback(
    (patch: PartialPrefs, opts?: { sync?: boolean }) => {
      setIsUpdating(true);
      setPrefersReducedMotion((prev) => patch.prefersReducedMotion ?? prev);
      setAnimationSpeedMultiplier((prev) =>
        clampSpeed(patch.animationSpeedMultiplier ?? prev),
      );

      // Normalize patch (avoid mutating the original function argument directly)
      const normalizedPatch: PartialPrefs = { ...patch };

      // If user enables reduced motion, force the animation speed multiplier to 1x (unless already specified)
      if (
        normalizedPatch.prefersReducedMotion === true &&
        normalizedPatch.animationSpeedMultiplier === undefined &&
        animationSpeedMultiplier !== 1
      ) {
        normalizedPatch.animationSpeedMultiplier = 1;
      }

      const nextReduced =
        normalizedPatch.prefersReducedMotion ?? prefersReducedMotion;

      let nextSpeed = clampSpeed(
        normalizedPatch.animationSpeedMultiplier ?? animationSpeedMultiplier,
      );

      // Enforce 1x when reduced motion is active
      if (nextReduced && nextSpeed !== 1) {
        nextSpeed = 1;
        normalizedPatch.animationSpeedMultiplier ??= 1;
      }

      const newPrefs: AnimationPreferences = {
        prefersReducedMotion: nextReduced,
        animationSpeedMultiplier: nextSpeed,
      };

      // Apply to DOM immediately
      applyPreferencesToDOM(newPrefs);

      // Persist locally
      writeLocalStorage(newPrefs);

      // Optionally sync to server
      const shouldSync = opts?.sync ?? autoSync;

      if (shouldSync && isAuthed) {
        pendingSyncRef.current = {
          prefersReducedMotion: patch.prefersReducedMotion,
          animationSpeedMultiplier: patch.animationSpeedMultiplier,
        };
        updateMutation.mutate(
          {
            ...(normalizedPatch.prefersReducedMotion !== undefined && {
              prefersReducedMotion: normalizedPatch.prefersReducedMotion,
            }),
            ...(normalizedPatch.animationSpeedMultiplier !== undefined && {
              animationSpeedMultiplier: clampSpeed(
                normalizedPatch.animationSpeedMultiplier,
              ),
            }),
          },
          {
            onSuccess: () => {
              setLastSyncedAt(Date.now());
              pendingSyncRef.current = null;
              setIsUpdating(false);
            },
            onError: () => {
              setIsUpdating(false);
            },
          },
        );
      } else {
        setIsUpdating(false);
      }
    },
    [
      prefersReducedMotion,
      animationSpeedMultiplier,
      autoSync,
      updateMutation,
      isAuthed,
    ],
  );

  // Secondary hydration: apply server values if they differ AND user hasn't customized locally yet.
  useEffect(() => {
    if (!isHydratedRef.current) return;
    if (serverHydratedRef.current) return;
    if (!isAuthed || !serverPrefs) return;

    const localIsDefault =
      prefersReducedMotion === DEFAULT_PREFERS_REDUCED &&
      animationSpeedMultiplier === DEFAULT_SPEED;

    const differs =
      serverPrefs.prefersReducedMotion !== prefersReducedMotion ||
      serverPrefs.animationSpeedMultiplier !== animationSpeedMultiplier;

    if (localIsDefault || differs) {
      performUpdate(
        {
          prefersReducedMotion: serverPrefs.prefersReducedMotion,
          animationSpeedMultiplier: serverPrefs.animationSpeedMultiplier,
        },
        { sync: false }, // Do not echo immediately back to server
      );
    }
    serverHydratedRef.current = true;
  }, [
    serverPrefs,
    performUpdate,
    prefersReducedMotion,
    animationSpeedMultiplier,
    isAuthed,
  ]);

  const updatePreferences = useCallback<
    AnimationPreferencesContextValue["updatePreferences"]
  >(
    (patch, opts) => {
      performUpdate(patch, opts);
    },
    [performUpdate],
  );

  // Dedicated setters (they sync by default)
  const handleSetReduced = useCallback(
    (val: boolean) => {
      updatePreferences({ prefersReducedMotion: val });
    },
    [updatePreferences],
  );

  const handleSetSpeed = useCallback(
    (val: number) => {
      updatePreferences({ animationSpeedMultiplier: clampSpeed(val) });
    },
    [updatePreferences],
  );

  const value: AnimationPreferencesContextValue = {
    prefersReducedMotion,
    animationSpeedMultiplier,
    updatePreferences,
    setPrefersReducedMotion: handleSetReduced,
    setAnimationSpeedMultiplier: handleSetSpeed,
    isUpdating: isUpdating || updateMutation.isPending,
    lastSyncedAt,
  };

  return (
    <AnimationPreferencesContext.Provider value={value}>
      {children}
    </AnimationPreferencesContext.Provider>
  );
}

/**
 * Hook consumer
 */
export function useAnimationPreferences(): AnimationPreferencesContextValue {
  const ctx = useContext(AnimationPreferencesContext);
  if (!ctx) {
    throw new Error(
      "useAnimationPreferences must be used within an AnimationPreferencesProvider",
    );
  }
  return ctx;
}

/**
 * Helper: Inline script snippet (for layout) to minimize FOUC.
 * (Not executed here—copy the string contents into a <script dangerouslySetInnerHTML={{__html: ...}} /> early in <head>.)
 *
 * Example usage in layout.tsx (before loading CSS-heavy content):
 *
 * <script
 *   dangerouslySetInnerHTML={{ __html: getInlineAnimationPrefsScript() }}
 * />
 */
export function getInlineAnimationPrefsScript(): string {
  return `
(function(){
  try {
    var STORAGE_KEY = '${STORAGE_KEY}';
    var raw = localStorage.getItem(STORAGE_KEY);
    var prefersReduced = false;
    var speed = 1;
    if (raw) {
      try {
        var parsed = JSON.parse(raw);
        if (typeof parsed.prefersReducedMotion === 'boolean') {
          prefersReduced = parsed.prefersReducedMotion;
        } else {
          // fallback to system preference if available
          prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
        }
        if (typeof parsed.animationSpeedMultiplier === 'number') {
          speed = parsed.animationSpeedMultiplier;
          if (isNaN(speed) || speed < ${MIN_SPEED} || speed > ${MAX_SPEED}) speed = 1;
        }
      } catch (_e) {}
    } else {
      prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    }
    var root = document.documentElement;
    if (prefersReduced) root.classList.add('user-reduce-motion');
    function apply(fast, normal, slow){
      root.style.setProperty('--animation-speed-fast', fast + 's');
      root.style.setProperty('--animation-speed-normal', normal + 's');
      root.style.setProperty('--animation-speed-slow', slow + 's');
    }
    if (prefersReduced) {
      apply(0.01,0.01,0.01);
    } else {
      var fast = (0.15 / speed).toFixed(4);
      var normal = (0.30 / speed).toFixed(4);
      var slow = (0.50 / speed).toFixed(4);
      apply(fast, normal, slow);
    }
  } catch(_e){}
})();`.trim();
}
