import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
import {
    createObjectValidator,
    isOfArrayTypeValidator,
    isOfOptionalTypeValidator,
    isOfTypeNumber,
    isOfTypeNumberArray,
    isOfTypeString,
    isOfTypeStringArray,
} from '../../../business/helpers/typeValidation';

export enum KnownTypes {
    NUMBER = 'NUMBER',
    NUMBER_ARRAY = 'NUMBER_ARRAY',
    STRING = 'STRING',
    STRING_ARRAY = 'STRING_ARRAY',
    FILE_ASSET_REFERENCE = 'FILE_ASSET_REFERENCE',
    FILE_ASSET_REFERENCE_ARRAY = 'FILE_ASSET_REFERENCE_ARRAY',
}

export function IsAnyOfTypes(types: KnownTypes[], validationOptions?: ValidationOptions) {
    return (object: unknown, propertyName: string) => {
        registerDecorator({
            constraints: [types],
            options: validationOptions,
            propertyName,
            target: object?.constructor as (...args: any) => any,
            validator: IsAnyOfTypesConstraint,
        });
    };
}

@ValidatorConstraint({ name: 'IsAnyOfTypes' })
class IsAnyOfTypesConstraint implements ValidatorConstraintInterface {
    private isFileAssetReference = createObjectValidator<{
        id: number;
        name?: string;
    }>({
        id: isOfTypeNumber,
        name: isOfOptionalTypeValidator(isOfTypeString),
    });

    valueIsOfType = (value: any, type: string): boolean => {
        const typeToIsOfTypeFunction: { [key in KnownTypes]: (value: any) => boolean } = {
            [KnownTypes.NUMBER]: isOfTypeNumber,
            [KnownTypes.NUMBER_ARRAY]: isOfTypeNumberArray,
            [KnownTypes.STRING]: isOfTypeString,
            [KnownTypes.STRING_ARRAY]: isOfTypeStringArray,
            [KnownTypes.FILE_ASSET_REFERENCE]: this.isFileAssetReference,
            [KnownTypes.FILE_ASSET_REFERENCE_ARRAY]: isOfArrayTypeValidator(this.isFileAssetReference),
        };
        const isOfTypeFunction: ((value: any) => boolean) | undefined = typeToIsOfTypeFunction[type as KnownTypes];
        if (!isOfTypeFunction) return false;
        return isOfTypeFunction(value);
    };

    validate(value: any, args: ValidationArguments): boolean {
        const types = args.constraints[0] as string[];
        return types.some((type) => this.valueIsOfType(value, type));
    }

    defaultMessage(args?: ValidationArguments): string {
        const propertyName = args?.property as string;
        const types = args?.constraints[0] as string[];
        return `${propertyName} must be any of types ${types.join(',')}`;
    }
}
