import React, { ReactNode } from "react";

import BaseField, { IProps as IBaseFieldProps } from "./BaseField";

export type IBaseField = BaseField<IBaseFieldProps>;

export type IFormContext = {
	setField: (name: string, field: IBaseField) => void;
	unSetField: (name: string) => void;
	onFieldChange: (name: string, field: IBaseField) => void;
	onFieldFocusChange: (name: string, field: IBaseField, focused: boolean) => void;
	isInputFocused: (name: string) => boolean;
	hasOneFocusedInput: () => boolean;
};

type IFields = {
	[key: string]: IBaseField;
};

type IState = {
	inputFocused: {
		name: string;
		field: IBaseField;
		focused: boolean;
	};
};

export type IProps = {
	onFieldFocusChange?: (name: string, field: IBaseField, focused: boolean) => void;
	onFieldChange?: (name: string, field: IBaseField) => void;
	onSubmit?: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
	className?: string;
	children?: ReactNode;
};

export const FormContext = React.createContext<IFormContext>({
	setField: () => {},
	unSetField: () => {},
	onFieldChange: () => {},
	onFieldFocusChange: () => {},
	isInputFocused: () => false,
	hasOneFocusedInput: () => false,
});

export default class Form extends React.Component<IProps, IState> {
	protected fields: IFields = {};
	private formRef: React.RefObject<HTMLFormElement>;

	constructor(props: IProps) {
		super(props);

		this.state = {
			inputFocused: {
				name: "",
				field: {} as IBaseField,
				focused: false,
			},
		};

		this.setField = this.setField.bind(this);
		this.unSetField = this.unSetField.bind(this);
		this.onFieldChange = this.onFieldChange.bind(this);
		this.onSubmit = this.onSubmit.bind(this);
		this.formRef = React.createRef();
		this.onFieldFocusChange = this.onFieldFocusChange.bind(this);
		this.isInputFocused = this.isInputFocused.bind(this);
		this.hasOneFocusedInput = this.hasOneFocusedInput.bind(this);
	}

	public override render() {
		return (
			<FormContext.Provider
				value={{
					setField: this.setField,
					unSetField: this.unSetField,
					onFieldChange: this.onFieldChange,
					onFieldFocusChange: this.onFieldFocusChange,
					isInputFocused: this.isInputFocused,
					hasOneFocusedInput: this.hasOneFocusedInput,
				}}>
				<form className={this.props.className} ref={this.formRef} onSubmit={this.onSubmit}>
					{this.props.children}
				</form>
			</FormContext.Provider>
		);
	}

	public async onSubmit(e: React.FormEvent<HTMLFormElement> | null) {
		e?.preventDefault();
		const allChildren = this.getAllChildrenFields(e);
		const elementsValues = allChildren
			.filter((e) => {
				return e.getAttribute("type") !== "radio" && e.getAttribute("type") !== "checkbox";
			})
			.reduce((obj, element) => ({ ...obj, [element.getAttribute("name") ?? ""]: (element as any).value }), {});

		const radioInputs = allChildren.filter((e) => e.getAttribute("type") === "radio").filter((e) => (e as any).checked);
		const radioInputsValues = radioInputs.reduce(
			(obj, element) => ({ ...obj, [element.getAttribute("name") ?? ""]: (element as any).value }),
			{},
		);

		const checkBoxesInput = allChildren.filter((e) => e.getAttribute("type") === "checkbox").filter((e) => (e as any).checked);
		const checkBoxesValues = checkBoxesInput.reduce((obj, element) => {
			const inputName = element.getAttribute("name") ?? "";
			const inputValue = (element as any).value as string;
			const newValue = ((obj as any)[inputName] as string[]) ?? [];
			newValue.push(inputValue);
			return {
				...obj,
				[inputName]: newValue,
			};
		}, {});

		const allInputs = {
			...elementsValues,
			...radioInputsValues,
			...checkBoxesValues,
		};

		// Deleting empty input
		delete (allInputs as any)[""];
		if (this.props.onSubmit) {
			this.props.onSubmit(e, allInputs);
		}

		return { values: elementsValues };
	}

	protected setField(name: string, field: IBaseField) {
		this.fields[name] = field;
	}

	protected unSetField(name: string) {
		delete this.fields[name];
	}

	protected hasOneFocusedInput() {
		return this.state.inputFocused.focused;
	}

	protected isInputFocused(name: string) {
		return this.state.inputFocused.name === name && this.state.inputFocused.focused;
	}

	protected onFieldFocusChange(name: string, field: IBaseField, focused: boolean) {
		this.setState({
			inputFocused: {
				name,
				field,
				focused,
			},
		});

		if (this.props.onFieldFocusChange) {
			this.props.onFieldFocusChange(name, field, focused);
		}
	}

	protected onFieldChange(name: string, field: IBaseField) {
		if (this.props.onFieldChange) {
			this.props.onFieldChange(name, field);
		}
	}

	private getAllChildrenFields(e: React.FormEvent<HTMLFormElement> | null): Element[] {
		return Array.from(((e?.target as HTMLFormElement) ?? this.formRef.current).elements);
	}
}
