import debounce from 'lodash.debounce';

type DebouncedFunction<T> = {
  (channel: string, arg: T): void;
  cancel: (channel?: string) => void;
  flush: (channel?: string) => void;
};

export function createChannelDebounce<T>(
  func: (channel: string, arg: T) => void,
  wait: number,
): DebouncedFunction<T> {
  const debouncedFunctions = new Map<string, ReturnType<typeof debounce>>();

  const debouncedFunc = (channel: string, arg: T) => {
    // Get or create debounced function for this specific channel
    if (!debouncedFunctions.has(channel)) {
      const channelDebouncedFunc = debounce(
        (latestArg: T) => {
          func(channel, latestArg);
          debouncedFunctions.delete(channel);
        },
        wait,
        {
          leading: false,
          trailing: true,
        },
      );

      debouncedFunctions.set(channel, channelDebouncedFunc);
    }

    // Trigger the debounced processing for this specific channel
    const channelDebouncedFunc = debouncedFunctions.get(channel)!;
    channelDebouncedFunc(arg);
  };

  // Cancel pending operations for a specific channel or all channels
  debouncedFunc.cancel = (channel?: string) => {
    if (channel) {
      const debouncedFn = debouncedFunctions.get(channel);
      if (debouncedFn) {
        debouncedFn.cancel();
        debouncedFunctions.delete(channel);
      }
    } else {
      // Cancel all channels
      debouncedFunctions.forEach(debouncedFn => debouncedFn.cancel());
      debouncedFunctions.clear();
    }
  };

  // Flush pending operations immediately for a specific channel or all channels
  debouncedFunc.flush = (channel?: string) => {
    if (channel) {
      const debouncedFn = debouncedFunctions.get(channel);
      if (debouncedFn) {
        debouncedFn.flush();
      }
    } else {
      // Flush all channels
      debouncedFunctions.forEach(debouncedFn => debouncedFn.flush());
    }
  };

  return debouncedFunc;
}
