import { debounce as lodashDebounce } from 'lodash';

/* eslint-disable @typescript-eslint/no-explicit-any */
/**
 * Options for configuring the Debounce decorator
 */
export interface DebounceOptions {
  /**
   * Number of milliseconds to delay execution.
   * @default 0
   */
  wait?: number;

  /**
   * Specify invoking on the leading edge of the timeout.
   * @default false
   */
  leading?: boolean;

  /**
   * Specify invoking on the trailing edge of the timeout.
   * @default true
   */
  trailing?: boolean;

  /**
   * Maximum time the function is allowed to be delayed before it's invoked.
   */
  maxWait?: number;
}

/**
 * Decorator that debounces method calls using lodash's debounce function.
 * Delays invoking the decorated method until after wait milliseconds have elapsed
 * since the last time the debounced method was invoked.
 *
 * @param options Configuration options for the debounce behavior
 * @example
 * ```typescript
 * class SearchService {
 *   // Basic debouncing with 300ms delay
 *   @Debounce({ wait: 300 })
 *   search(query: string): void {
 *     console.log('Searching for:', query);
 *   }
 *
 *   // Debounce with leading edge execution
 *   @Debounce({ wait: 500, leading: true, trailing: false })
 *   saveData(data: any): void {
 *     console.log('Saving:', data);
 *   }
 *
 *   // With maximum wait time
 *   @Debounce({ wait: 1000, maxWait: 5000 })
 *   autoSave(): void {
 *     console.log('Auto-saving...');
 *   }
 * }
 * ```
 */
export function Debounce<T extends (...args: any[]) => any>(
  options: DebounceOptions = {},
): MethodDecorator {
  const debouncedFunctionMap = new WeakMap<object, ReturnType<typeof lodashDebounce>>();

  return function (
    _target: any,
    _propertyKey: string | symbol,
    descriptor: PropertyDescriptor,
  ): PropertyDescriptor {
    const originalMethod = descriptor.value;

    descriptor.value = function (this: any, ...args: Parameters<T>): void {
      // Get or create debounced function for this instance
      if (!debouncedFunctionMap.has(this)) {
        const debouncedFn = lodashDebounce(
          originalMethod.bind(this),
          options.wait ?? 0,
          {
            leading: options.leading ?? false,
            trailing: options.trailing ?? true,
            maxWait: options.maxWait,
          },
        );
        debouncedFunctionMap.set(this, debouncedFn);
      }

      const debouncedFn = debouncedFunctionMap.get(this);
      if (debouncedFn) {
        debouncedFn(...args);
      }
    };

    return descriptor;
  };
}