import { ValidationResult } from "@business-logic/types/RuleEngine";
import EnumUtils from "./EnumUtils";

export type ExtractName<T> = { [K in keyof T]: () => K; };

type GetValueOptions = {
	isRequired: boolean;
};

function isNullOrWhiteSpace(input: unknown): boolean {
	if (typeof input !== "string")
		return input == null;
	return input.trim().length === 0;
}

// Matches: '() => _Enums.*****', e.g.: '() => _Enums.ModificationTypes'
// Matches: '() => *****', e.g.: '() => ModificationTypes'
// Returns: EnumName, e.g.: 'ModificationTypes'
function getEnumName(propertySelector: () => object): string {
	const functionString = propertySelector.toString();
	const match = functionString.match(/\(\s*\)\s*=>\s*(?:_?[A-Z]\w*\.)?(\w+)(?::)?/);
	return match ? match[1] : "Unknown";
}

// Matches: 'x => x.*****', e.g.: 'x => x.name', 'x => x.tags'
// Returns: PropertyName, e.g.: 'name', 'tags'
function getNameof<T>(propertySelector: (obj: T) => any): string {
    const propertyString = propertySelector.toString();
    const match = propertyString.match(/(?:=>|return)\s*([\w\s.]+)/);

    if (match && match[1]) {
        const parts = match[1].split('.');
        return parts[parts.length - 1].trim();
    }

    throw new Error(`Invalid property selector: ${propertyString}`);
}

function getValueOrNull<T>(json: any, propertySelector: (obj: T) => any): any | null {
	if (json == null)
		return null;

	const property = getNameof(propertySelector);

	if (json.hasOwnProperty(property))
		return json[property];

	return null;
}

function getValue<T>(json: any, typeName: string, validationResults: ValidationResult[], options: GetValueOptions, propertySelector: (obj: T) => any): any | null {
	const retval = getValueOrNull(json, propertySelector);

	if (retval == null)
		validationResults.push(options.isRequired ? ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} is null but is required!`) : ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is null but is NOT required.`));
	else {
		if (isNullOrWhiteSpace(retval))
			validationResults.push(ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} is empty!`));
		else
			validationResults.push(ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is valid.`));
	}

	return retval;
}

function hasProperty<T>(json: any, typeName: string, validationResults: ValidationResult[], options: GetValueOptions, propertySelector: (obj: ExtractName<T>) => () => keyof T): boolean {
	const retval = json.hasOwnProperty(getNameof(propertySelector));

	if (retval)
		validationResults.push(ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is valid.`));
	else
		validationResults.push(options.isRequired ? ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is not defined but is required!`) : ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} is not defined but is NOT required!`));

	return retval;
}

function getEnumValueOrNull<T>(json: any, enumType: object, propertySelector: (obj: T) => any): any | null {
	if (json == null)
		return null;

	const property = getNameof(propertySelector);
	const retval = EnumUtils.validateEnumProperty(json, property, enumType);

	if (retval != null)
		return retval;
	else if (Object.values(enumType).includes(json[property]))
		return json[property];

	return null;
}

function getEnumValue<T>(json: any, typeName: string, validationResults: ValidationResult[], enumType: object, enumTypeInstance: () => object, options: GetValueOptions, propertySelector: (obj: T) => any): any | null {
	const retval = getEnumValueOrNull(json, enumType, propertySelector);

	if (retval == null)
		validationResults.push(options.isRequired ? ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} [${getEnumName(enumTypeInstance)}] is null but is required!`) : ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} [${getEnumName(enumTypeInstance)}] is null but is NOT required.`));
	else
		validationResults.push(ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is valid.`));

	return retval;
}

export { getEnumName, getEnumValueOrNull, getNameof, getValueOrNull };

const PropertyUtils = {
	hasProperty,
	getValue,
	getEnumValue
};

export default PropertyUtils;