import { ChangeEvent, Component, createRef } from "react";

import { FormContext, IFormContext } from "..";
// elements
import Validators, { IValidationTypes } from "../Validators/Validators";

export type IError = {
	message: string;
	validator: string;
	value: string | number | readonly string[];
	args: any[];
	isErrored?: (hasError: boolean) => void;
};

export type INewBasefieldProps = {
	onChange?: (event: ChangeEvent<HTMLInputElement & HTMLSelectElement & HTMLTextAreaElement>) => void;
	name: string;
	regex?: RegExp;
	onCancel?: () => void;
	disableValidation?: boolean;
	onErrors?: (errors: IError[]) => void;
	fieldRef?: React.RefObject<any>;
};

export type IProps = IValidationTypes & React.InputHTMLAttributes<HTMLInputElement> & INewBasefieldProps;

type IState = {
	value?: string | number | readonly string[];
	errors: IError[];
};

export default abstract class BaseField<P extends IProps> extends Component<P, IState> {
	public static override contextType = FormContext;
	public override context: IFormContext | null = null;
	public fieldRef: React.RefObject<any> = createRef();

	static defaultProps: Partial<IProps> = {
		disableValidation: false,
	};

	constructor(props: P) {
		super(props);
		this.onChange = this.onChange.bind(this);
		this.validate = this.validate.bind(this);

		this.state = {
			value: this.props.value ?? this.props.defaultValue ?? "",
			errors: [],
		};
	}

	public override componentDidMount() {
		this.context?.setField(this.props.name, this);
	}

	public override componentDidUpdate(prevProps: P) {
		if (prevProps.value !== this.props.value || prevProps.defaultValue !== this.props.defaultValue) {
			this.setState({ value: this.props.value ?? this.props.defaultValue ?? "" });
		}
	}

	public override componentWillUnmount() {
		this.context?.unSetField(this.props.name);
	}

	public async onBlur(event: React.FocusEvent<HTMLInputElement, Element>) {
		// this.validate();
		// if (this.props.onBlur) {
		// 	this.props.onBlur(event);
		// }
	}

	public async validate(isOnSubmit?: boolean) {
		if (this.props.disableValidation) return;
		if (this.props.readOnly) return;

		const errorArray: IError[] = [];
		const props: { [key: string]: any } = this.props;
		const validators = Object.entries(Validators).filter(([key]) => props[key]);

		const isValidable = isOnSubmit
			? this.props.required || (this.state.value && this.state.value !== "")
			: this.state.value && this.state.value !== "";

		if (isValidable) {
			const validations = await Promise.all(
				validators.map(async ([key, validator]) => {
					const validation = await (validator.validate as any)(this.state.value, ...(props[key].args ?? []));
					if (props[key].isErrored) {
						props[key].isErrored(!validation);
					}
					return [key, validator, validation];
				}),
			);

			const unValidateds = validations.filter(([key, validator, validation]) => !validation);
			const errors: IError[] = unValidateds.map(([key, unValidated]) => {
				let message = unValidated.message;
				if (typeof props[key] === "object" && props[key].message) message = props[key].message;
				return { message, validator: key, value: this.state.value!, args: props[key].args ?? [] };
			});

			errorArray.push(...errors);
		} else {
			validators.forEach(async ([key]) => {
				if (props[key].isErrored) {
					props[key].isErrored(false);
				}
			});
		}

		this.setState({ errors: errorArray });
		this.onErrors(errorArray);
		return errorArray;
	}

	public setErrors(errors: IError[]) {
		this.setState({ ...this.state, errors });
	}

	/**
	 * It is automatically called by the parent form when the user cancelled the
	 * form and all of its changes.
	 *
	 * Override the method for custom cancelling logic, or pass a custom onCancel
	 * callback.
	 */
	public cancel() {
		if (this.props.onCancel) {
			this.props.onCancel();
		}
	}

	public onErrors(errors: IError[]) {
		if (this.props.onErrors) {
			this.props.onErrors(errors);
		}
	}

	protected onChange(event: ChangeEvent<HTMLInputElement & HTMLSelectElement & HTMLTextAreaElement>) {
		if (this.props.regex) {
			if (!this.props.regex.test(event.currentTarget.value)) {
				event.currentTarget.value = event.currentTarget.value.substring(0, event.currentTarget.value.length - 1);
			}
		}
		this.setState({ value: event.currentTarget.value }, () => {
			this.validate();
			this.context?.onFieldChange(this.props.name, this);
		});
		if (this.props.onChange) {
			this.props.onChange(event);
		}
	}
}
