import {ClaimActionType, IRequiredClaim} from '@essential-projects/core_contracts';
import {IRequiresClaimDecorator, MetadataType} from '@essential-projects/metadata_contracts';
import {MetadataProvider} from './../provider';

export function requiresClaim(claim: IRequiredClaim, namespace?: string): IRequiresClaimDecorator {

  // if this is decorating a class, typeof args[0] is the type of the decorated class
  return function requiresClaimFactory(...args: Array<any>): typeof args[0] | void | PropertyDescriptor {

    switch (args.length) {
      case 1:
        return classRequiresClaim.apply(this, [args[0], claim, namespace]);
      case 2:
        return propertyRequiresClaim.apply(this, [args[0], args[1], claim, namespace]);
      case 3:
        if (typeof args[2] === 'number') {
          // this is a parameter descriptor
          // those should not support claims
        }

        return methodRequiresClaim.apply(this, [args[0], args[1], args[2], claim, namespace]);
      default:
        throw new Error('Decorators are not valid here!');
    }
  };
}

function classRequiresClaim(target: any, claim: IRequiredClaim, namespace?: string): typeof target {

  const type: string = target.prototype ? target.prototype.constructor.name : target.constructor.name;

  if (!claim.claim) {
    const claimString: string = `${type}`;
    claim.claim = claimString;
  }

  MetadataProvider.setForType(MetadataType.RequiredClaim, claim, namespace, type);

  return target;
}

function propertyRequiresClaim(target: any, key: string, claim: IRequiredClaim, namespace?: string): void {

  const type: string = target.prototype ? target.prototype.constructor.name : target.constructor.name;

  if (!claim.claim) {
    const claimString: string = `${type}.${key}`;
    claim.claim = claimString;
  }

  MetadataProvider.setForType(MetadataType.RequiredClaim, claim, namespace, type, key);
}

function methodRequiresClaim(target: any, key: string, descriptor: any, claim: IRequiredClaim, namespace?: string): PropertyDescriptor {

  const type: string = target.prototype ? target.prototype.constructor.name : target.constructor.name;

  if (!claim.claim) {
    const claimString: string = `${type}.${key}`;
    claim.claim = claimString;
  }

  MetadataProvider.setForType(MetadataType.RequiredClaim, claim, namespace, type, key);

  return descriptor;
}
