/**
 * Converts a simplified decorator to a proper decorator function
 * @param decorator The simplified decorator function
 */
function decorate (decorator: (fn: Function, key: string) => Function): Function {
  return (_target: any, key: string, descriptor: any) => {
    let fnKey: string | null = null;
    let fn: Function | null = null;

    if (typeof descriptor.value === 'function') {
      fnKey = 'value';
      fn = descriptor.value;
    } else if (typeof descriptor.get === 'function') {
      fnKey = 'get';
      fn = descriptor.get;
    }

    if (!fn || !fnKey) {
      throw new Error('not supported');
    }

    descriptor[fnKey] = decorator(fn, key);
  };
}

/**
 * Prevents a function or method from being called multiple times
 * within a given window, delaying the execution by the given delay.
 * @param delay The minimum amount of time between function calls
 */
export function debounce (delay: number): Function {
  return decorate((fn, key) => {
    const timerKey = `$debounce$${key}`;

    return function (this: any, ...args: any[]) {
      clearTimeout(this[timerKey]);
      this[timerKey] = setTimeout(() => fn.apply(this, args), delay);
    };
  });
}