/**
 * useGoogleFonts Hook
 *
 * Dynamically loads Google fonts when they are used in typography settings.
 * Maintains a set of loaded fonts to avoid duplicate loading.
 * Uses FontFace API to detect when fonts are ready for rendering.
 */

import { useEffect, useCallback, useState } from "react";
import { isGoogleFont, getGoogleFontUrl } from "@/types";

// Global set to track loaded fonts across all instances
const loadedFonts = new Set<string>();
// Global set to track fonts that are ready (fully loaded and available)
const readyFonts = new Set<string>();
// Callbacks waiting for font to be ready
const fontReadyCallbacks = new Map<string, Set<() => void>>();
// Pending fonts to be loaded in next batch
const pendingFonts = new Set<string>();
// Timer for batching font loads
let batchTimer: NodeJS.Timeout | null = null;
// Batch delay in milliseconds
const BATCH_DELAY = 50;

/**
 * Hook to manage Google font loading.
 * Automatically loads a Google font when the fontFamily value changes.
 * Returns true when the font is ready for rendering.
 *
 * @param fontFamily - The current font family value
 * @returns Whether the font is ready for rendering
 */
export function useGoogleFont(fontFamily: string): boolean {
  const [isReady, setIsReady] = useState(() => {
    // Check if already ready
    if (!fontFamily || !isGoogleFont(fontFamily)) {
      return true; // Non-Google fonts are always ready
    }
    return readyFonts.has(fontFamily);
  });

  useEffect(() => {
    if (!fontFamily || !isGoogleFont(fontFamily)) {
      setIsReady(true);
      return;
    }

    // Check if already ready
    if (readyFonts.has(fontFamily)) {
      setIsReady(true);
      return;
    }

    // Set not ready while loading
    setIsReady(false);

    // Register callback for when font is ready
    const callback = () => setIsReady(true);

    if (!fontReadyCallbacks.has(fontFamily)) {
      fontReadyCallbacks.set(fontFamily, new Set());
    }
    fontReadyCallbacks.get(fontFamily)!.add(callback);

    // Load the font
    loadGoogleFont(fontFamily);

    // Cleanup
    return () => {
      fontReadyCallbacks.get(fontFamily)?.delete(callback);
    };
  }, [fontFamily]);

  return isReady;
}

/**
 * Hook to manage multiple Google fonts.
 * Useful when you need to load fonts from multiple typography settings.
 *
 * @param fontFamilies - Array of font family values
 * @returns Whether all fonts are ready
 */
export function useGoogleFonts(fontFamilies: string[]): boolean {
  const [allReady, setAllReady] = useState(() => {
    return fontFamilies.every((f) => !f || !isGoogleFont(f) || readyFonts.has(f));
  });

  useEffect(() => {
    const googleFonts = fontFamilies.filter((f) => f && isGoogleFont(f));

    if (googleFonts.length === 0) {
      setAllReady(true);
      return;
    }

    // Check if all already ready
    if (googleFonts.every((f) => readyFonts.has(f))) {
      setAllReady(true);
      return;
    }

    setAllReady(false);

    // Track how many fonts we're waiting for
    let pendingCount = googleFonts.filter((f) => !readyFonts.has(f)).length;

    const checkAllReady = () => {
      pendingCount--;
      if (pendingCount <= 0) {
        setAllReady(true);
      }
    };

    // Register callbacks and load fonts
    const callbacks: Array<{ font: string; callback: () => void }> = [];

    googleFonts.forEach((fontFamily) => {
      if (!readyFonts.has(fontFamily)) {
        if (!fontReadyCallbacks.has(fontFamily)) {
          fontReadyCallbacks.set(fontFamily, new Set());
        }
        fontReadyCallbacks.get(fontFamily)!.add(checkAllReady);
        callbacks.push({ font: fontFamily, callback: checkAllReady });
        loadGoogleFont(fontFamily);
      }
    });

    // Cleanup
    return () => {
      callbacks.forEach(({ font, callback }) => {
        fontReadyCallbacks.get(font)?.delete(callback);
      });
    };
  }, [fontFamilies]);

  return allReady;
}

/**
 * Hook that returns a function to load Google fonts on demand.
 * Useful when you need to load a font based on user interaction.
 */
export function useGoogleFontLoader(): (fontFamily: string) => void {
  return useCallback((fontFamily: string) => {
    if (fontFamily && isGoogleFont(fontFamily)) {
      loadGoogleFont(fontFamily);
    }
  }, []);
}

/**
 * Load a Google font by batching it with other fonts.
 * Multiple fonts requested within a short time window are combined into a single URL.
 * Uses FontFace API to detect when font is actually ready.
 *
 * @param fontName - The Google font name (e.g., "Roboto", "Open Sans")
 */
export function loadGoogleFont(fontName: string): void {
  // Skip if already loaded or pending
  if (loadedFonts.has(fontName)) {
    return;
  }

  // Mark as loaded to prevent duplicate requests
  loadedFonts.add(fontName);

  // Add to pending batch
  pendingFonts.add(fontName);

  // Clear existing timer and set new one for batching
  if (batchTimer) {
    clearTimeout(batchTimer);
  }

  batchTimer = setTimeout(() => {
    loadPendingFonts();
    batchTimer = null;
  }, BATCH_DELAY);
}

/**
 * Load all pending fonts in a single batched request.
 */
function loadPendingFonts(): void {
  if (pendingFonts.size === 0) {
    return;
  }

  const fontsToLoad = Array.from(pendingFonts);
  pendingFonts.clear();

  // Build combined Google Fonts URL
  const familyParams = fontsToLoad.map((fontName) => {
    const encodedName = fontName.replace(/ /g, "+");
    return `family=${encodedName}:wght@100;200;300;400;500;600;700;800;900`;
  });

  const url = `https://fonts.googleapis.com/css2?${familyParams.join("&")}&display=swap`;

  // Create single link element for all fonts
  const link = document.createElement("link");
  link.rel = "stylesheet";
  link.href = url;
  link.dataset.pboFonts = fontsToLoad.join(",");

  // Add to head
  document.head.appendChild(link);

  // Check when fonts are ready
  fontsToLoad.forEach((fontName) => {
    checkFontReady(fontName);
  });
}

/**
 * Check if a font is ready using FontFace API.
 */
function checkFontReady(fontName: string): void {
  // Use document.fonts.check to see if the font is available
  if (document.fonts.check(`16px "${fontName}"`)) {
    markFontReady(fontName);
  } else {
    // Font not ready yet, wait for fonts to load
    document.fonts.ready.then(() => {
      // Double-check after fonts.ready
      if (document.fonts.check(`16px "${fontName}"`)) {
        markFontReady(fontName);
      } else {
        // Still not ready, poll a few more times
        let attempts = 0;
        const pollInterval = setInterval(() => {
          attempts++;
          if (document.fonts.check(`16px "${fontName}"`) || attempts > 20) {
            clearInterval(pollInterval);
            markFontReady(fontName);
          }
        }, 100);
      }
    });
  }
}

/**
 * Mark a font as ready and notify all waiting callbacks
 */
function markFontReady(fontName: string): void {
  if (readyFonts.has(fontName)) return;

  readyFonts.add(fontName);

  // Notify all waiting callbacks
  const callbacks = fontReadyCallbacks.get(fontName);
  if (callbacks) {
    callbacks.forEach((callback) => callback());
    callbacks.clear();
  }
}

/**
 * Check if a Google font has been loaded.
 *
 * @param fontName - The Google font name
 * @returns True if the font has been loaded
 */
export function isGoogleFontLoaded(fontName: string): boolean {
  return loadedFonts.has(fontName);
}

/**
 * Check if a Google font is ready for rendering.
 *
 * @param fontName - The Google font name
 * @returns True if the font is ready
 */
export function isGoogleFontReady(fontName: string): boolean {
  return readyFonts.has(fontName);
}

/**
 * Get all loaded Google fonts.
 *
 * @returns Array of loaded font names
 */
export function getLoadedGoogleFonts(): string[] {
  return Array.from(loadedFonts);
}

/**
 * Preload multiple Google fonts.
 * Useful for preloading fonts used in existing layouts.
 *
 * @param fontNames - Array of Google font names
 */
export function preloadGoogleFonts(fontNames: string[]): void {
  fontNames.forEach((fontName) => {
    if (isGoogleFont(fontName)) {
      loadGoogleFont(fontName);
    }
  });
}

/**
 * Preload all available Google fonts from oppbAdmin.
 * Should be called once on app initialization.
 */
export function preloadAllGoogleFonts(): void {
  const googleFonts = window.oppbAdmin?.fonts?.google || [];
  const fontNames = googleFonts.map((font) => font.value);
  preloadGoogleFonts(fontNames);
}

/**
 * Hook to preload all Google fonts on mount.
 * Use this once at the app root level.
 */
export function usePreloadAllGoogleFonts(): void {
  useEffect(() => {
    preloadAllGoogleFonts();
  }, []);
}

export default useGoogleFont;
