/**
 * Debounce hook for delaying function execution
 */

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

interface DebounceOptions {
  leading?: boolean;
  trailing?: boolean;
  maxWait?: number;
}

interface DebouncedState<T> {
  debouncedValue: T;
  isDebouncing: boolean;
}

interface DebouncedActions {
  cancel: () => void;
  flush: () => void;
}

const DEFAULT_OPTIONS: Required<DebounceOptions> = {
  leading: false,
  trailing: true,
  maxWait: Infinity,
};

// Simple debounce hook for values
export const useDebounce = <T>(
  value: T,
  delay: number,
  options: DebounceOptions = {}
): DebouncedState<T> & DebouncedActions => {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);
  const [isDebouncing, setIsDebouncing] = useState(false);
  
  const config = { ...DEFAULT_OPTIONS, ...options };
  const timeoutRef = useRef<number | null>(null);
  const maxWaitTimeoutRef = useRef<number | null>(null);
  const lastCallTimeRef = useRef<number>(0);
  const lastInvokeTimeRef = useRef<number>(0);
  const leadingRef = useRef<boolean>(false);

  // Cancel pending debounce
  const cancel = useCallback(() => {
    if (timeoutRef.current) {
      window.clearTimeout(timeoutRef.current);
      timeoutRef.current = null;
    }
    if (maxWaitTimeoutRef.current) {
      window.clearTimeout(maxWaitTimeoutRef.current);
      maxWaitTimeoutRef.current = null;
    }
    setIsDebouncing(false);
    leadingRef.current = false;
  }, []);

  // Flush (execute immediately)
  const flush = useCallback(() => {
    if (timeoutRef.current || maxWaitTimeoutRef.current) {
      setDebouncedValue(value);
      lastInvokeTimeRef.current = Date.now();
      setIsDebouncing(false);
      cancel();
    }
  }, [value, cancel]);

  // Main debounce effect
  useEffect(() => {
    const now = Date.now();
    lastCallTimeRef.current = now;

    // Leading edge
    if (config.leading && !leadingRef.current) {
      setDebouncedValue(value);
      lastInvokeTimeRef.current = now;
      leadingRef.current = true;
      
      if (!config.trailing) {
        return;
      }
    }

    setIsDebouncing(true);

    // Clear existing timeouts
    if (timeoutRef.current) {
      window.clearTimeout(timeoutRef.current);
    }
    if (maxWaitTimeoutRef.current) {
      window.clearTimeout(maxWaitTimeoutRef.current);
    }

    // Set up trailing timeout
    if (config.trailing) {
      timeoutRef.current = window.setTimeout(() => {
        if (lastCallTimeRef.current === now) {
          setDebouncedValue(value);
          lastInvokeTimeRef.current = Date.now();
          setIsDebouncing(false);
          leadingRef.current = false;
        }
      }, delay);
    }

    // Set up max wait timeout
    if (config.maxWait !== Infinity) {
      const timeSinceLastInvoke = now - lastInvokeTimeRef.current;
      const remainingWait = config.maxWait - timeSinceLastInvoke;
      
      if (remainingWait <= 0) {
        setDebouncedValue(value);
        lastInvokeTimeRef.current = now;
        setIsDebouncing(false);
        leadingRef.current = false;
      } else {
        maxWaitTimeoutRef.current = window.setTimeout(() => {
          setDebouncedValue(value);
          lastInvokeTimeRef.current = Date.now();
          setIsDebouncing(false);
          leadingRef.current = false;
        }, remainingWait);
      }
    }

    return cancel;
  }, [value, delay, config.leading, config.trailing, config.maxWait, cancel]);

  return {
    debouncedValue,
    isDebouncing,
    cancel,
    flush,
  };
};

// Advanced debounce hook for functions
export const useDebouncedCallback = <T extends (...args: any[]) => any>(
  callback: T,
  delay: number,
  options: DebounceOptions = {}
): {
  debouncedCallback: T;
  isDebouncing: boolean;
  cancel: () => void;
  flush: () => void;
} => {
  const [isDebouncing, setIsDebouncing] = useState(false);
  
  const config = { ...DEFAULT_OPTIONS, ...options };
  const callbackRef = useRef(callback);
  const timeoutRef = useRef<number | null>(null);
  const maxWaitTimeoutRef = useRef<number | null>(null);
  const argsRef = useRef<Parameters<T> | null>(null);
  const lastCallTimeRef = useRef<number>(0);
  const lastInvokeTimeRef = useRef<number>(0);
  const leadingRef = useRef<boolean>(false);

  // Update callback ref
  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);

  // Cancel pending execution
  const cancel = useCallback(() => {
    if (timeoutRef.current) {
      window.clearTimeout(timeoutRef.current);
      timeoutRef.current = null;
    }
    if (maxWaitTimeoutRef.current) {
      window.clearTimeout(maxWaitTimeoutRef.current);
      maxWaitTimeoutRef.current = null;
    }
    setIsDebouncing(false);
    leadingRef.current = false;
    argsRef.current = null;
  }, []);

  // Flush (execute immediately)
  const flush = useCallback(() => {
    if (argsRef.current && (timeoutRef.current || maxWaitTimeoutRef.current)) {
      const result = callbackRef.current(...argsRef.current);
      lastInvokeTimeRef.current = Date.now();
      setIsDebouncing(false);
      cancel();
      return result;
    }
  }, [cancel]);

  // Execute function
  const invokeFunc = useCallback((args: Parameters<T>) => {
    const result = callbackRef.current(...args);
    lastInvokeTimeRef.current = Date.now();
    setIsDebouncing(false);
    leadingRef.current = false;
    return result;
  }, []);

  // Debounced callback
  const debouncedCallback = useCallback(
    ((...args: Parameters<T>) => {
      const now = Date.now();
      argsRef.current = args;
      lastCallTimeRef.current = now;

      // Leading edge
      if (config.leading && !leadingRef.current) {
        const result = invokeFunc(args);
        leadingRef.current = true;
        
        if (!config.trailing) {
          return result;
        }
      }

      setIsDebouncing(true);

      // Clear existing timeouts
      if (timeoutRef.current) {
        window.clearTimeout(timeoutRef.current);
      }
      if (maxWaitTimeoutRef.current) {
        window.clearTimeout(maxWaitTimeoutRef.current);
      }

      // Set up trailing timeout
      if (config.trailing) {
        timeoutRef.current = window.setTimeout(() => {
          if (argsRef.current && lastCallTimeRef.current === now) {
            invokeFunc(argsRef.current);
          }
        }, delay);
      }

      // Set up max wait timeout
      if (config.maxWait !== Infinity) {
        const timeSinceLastInvoke = now - lastInvokeTimeRef.current;
        const remainingWait = config.maxWait - timeSinceLastInvoke;
        
        if (remainingWait <= 0) {
          return invokeFunc(args);
        } else {
          maxWaitTimeoutRef.current = window.setTimeout(() => {
            if (argsRef.current) {
              invokeFunc(argsRef.current);
            }
          }, remainingWait);
        }
      }
    }) as T,
    [delay, config.leading, config.trailing, config.maxWait, invokeFunc]
  );

  // Cleanup on unmount
  useEffect(() => {
    return cancel;
  }, [cancel]);

  return {
    debouncedCallback,
    isDebouncing,
    cancel,
    flush,
  };
};