// Deprecated Decorators

// ClassDecorator

// PropertyDecorator

// MethodDecorator
// measure the time of an execution
function MeasureExecutionTime() {
  return function (cls: any, propertyKey: string, descriptor: PropertyDescriptor) {
    // save reference to to original method
    const originalMethod = descriptor.value;

    // override the original function
    descriptor.value = function (...args: any[]) {
      const start = performance.now();

      // call original function
      const result = originalMethod.apply(this, args);

      const finish = performance.now();

      console.log(
        `Execution time of function ${cls.name}.${propertyKey}(): ${finish - start} milliseconds`
      );

      // return the result of the execution
      return result;
    };
  };
}

// wont work! https://github.com/microsoft/TypeScript/issues/3661
// @MeasureExecutionTime()
function test() {
  console.log();
}

class Test {
  @MeasureExecutionTime()
  static run() {
    console.log();
  }
}

// test function
Test.run();

// Deprecated
function DeprecatedClass(cls: any, propertyKey?: string, descriptor?: PropertyDescriptor): any {
  console.log(cls, propertyKey, descriptor);
  console.warn(`you are using a deprecated class ${cls.name}`);
  return cls;
}

function DeprecatedFunction(cls: any, propertyKey: string, descriptor: PropertyDescriptor): any {
  console.log(cls, propertyKey, descriptor);
  console.warn(`you are using a deprecated method ${propertyKey}`);
  return descriptor.value;
}

function DeprecatedProperty(cls: any, propertyKey: string, descriptor?: PropertyDescriptor): any {
  console.log(cls, propertyKey, descriptor);
  console.warn(`you are using a deprecated property ${propertyKey}`);
  return cls[propertyKey];
}

/**
 * @deprecated
 */
@DeprecatedClass
class HardlyDeprecated {
  @DeprecatedProperty
  moreDeprecated?: boolean;

  @DeprecatedFunction
  callMeDeprecated() {
    console.log("called me!");
  }
}

const hd = new HardlyDeprecated();
hd.moreDeprecated;
hd.moreDeprecated = true;
hd.callMeDeprecated();

// Multi Decorators

/*
Multi DecoratorsAs such, the following steps are performed when evaluating multiple decorators on a single declaration in TypeScript:
The expressions for each decorator are evaluated top-to-bottom.
The results are then called as functions from bottom-to-top.
*/

/*
Decorator Evaluation
There is a well defined order to how decorators applied to various declarations inside of a class are applied:

Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each instance member.
Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each static member.
Parameter Decorators are applied for the constructor.
Class Decorators are applied for the class.
*/

// examples

function CountExecutions() {
  return function (cls: any, propertyKey: string, descriptor: PropertyDescriptor): any {
    const originalMethod = descriptor.value;

    let executionCount = 0;

    descriptor.value = function (...args: any[]) {
      executionCount++;
      console.log(`this is run ${executionCount} of this function`);
      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

class Test2 {
  @CountExecutions()
  static run() {
    console.log("a");
  }

  @CountExecutions()
  run() {
    console.log("a");
  }
}

// https://blog.logrocket.com/a-practical-guide-to-typescript-decorators/
// https://www.digitalocean.com/community/tutorials/how-to-use-decorators-in-typescript
// https://saul-mirone.github.io/a-complete-guide-to-typescript-decorator/

Playground