import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";

import { checkgroups } from "@ax/api";
import { isReqOk } from "@ax/helpers";
import type { ICheck, ILanguage, ISite } from "@ax/types";

import * as S from "./style";

const AsyncCheckGroup = (props: IAsyncCheckGroup): JSX.Element => {
	const {
		onChange,
		value,
		site,
		source,
		disabled,
		error,
		handleValidation,
		fullHeight,
		allLanguages = false,
		contentType = "data",
		languages,
		contentLanguages,
		allOption = false,
	} = props;

	const safeValue = useMemo(() => (value && Array.isArray(value) ? value : []), [value]);
	const [options, setOptions] = useState<ICheckValue[]>([]);
	const [allSelected, setAllSelected] = useState<boolean>(false);

	const onChangeRef = useRef(onChange);
	useEffect(() => {
		onChangeRef.current = onChange;
	}, [onChange]);

	const languagesIds = useMemo(
		() =>
			Array.isArray(contentLanguages)
				? languages?.filter((lang) => contentLanguages.includes(lang.locale)).map((lang) => lang.id)
				: contentLanguages === "all"
					? languages?.map((lang) => lang.id)
					: [],
		[contentLanguages, languages],
	);

	const isCheckValue = useCallback((object: any): object is ICheckValue => "value" in object, []);

	const getAllIds = useCallback((results: ICheckValue[]): number[] => {
		return results.reduce((acc: number[], current) => {
			acc.push(current.value);
			const children = getAllIds(current.children || []);
			if (children.length) {
				acc.push(...children);
			}
			return acc;
		}, []);
	}, []);

	// biome-ignore lint/correctness/useExhaustiveDependencies: safeValue, getAllIds, isCheckValue captured in closure; only refetch when API params change
	useEffect((): any => {
		let isSubscribed = true;

		const getItems = async (): Promise<ICheckValue[]> => {
			try {
				let result = null;
				const siteID = site ? site.id : "global";
				result =
					contentType === "formCategory"
						? await checkgroups.getFormCategories(siteID, source)
						: await checkgroups.getCheckGroupItems(siteID, source, allLanguages, languagesIds);
				if (result && isReqOk(result.status)) {
					return result.data;
				}
			} catch (e) {
				console.log(e);
			}
			return [];
		};

		getItems()
			.then((result) => {
				if (isSubscribed) {
					setOptions(result);
					if (safeValue.length > 0 && safeValue.length === result.length) {
						setAllSelected(true);
					}
					if (safeValue.length > 0 && isCheckValue(safeValue[0])) {
						// clean array of old values and set translations
						const resultIDs = result && getAllIds(result);
						const fixedState = safeValue.reduce((acc: ICheckValue[], current: ICheckValue) => {
							if (resultIDs.includes(current.value)) {
								return acc.concat(current);
							}
							const trad = result.find((value: ICheckValue) =>
								value.dataLanguages?.find((data: any) => data.id === current.value),
							);
							if (trad) {
								return acc.concat(trad);
							}
							return acc;
						}, []);
						onChangeRef.current(fixedState);
					}
				}
			})
			.catch((apiError) => console.log(apiError));

		return () => {
			isSubscribed = false;
		};
	}, [site, source, allLanguages, contentType, languagesIds]);

	const filterOptions = useCallback((options: ICheckValue[], ids: number[]) => {
		return options.reduce((acc: ICheckValue[], current) => {
			if (ids.includes(current.value)) {
				acc.push(current);
			}
			const children = filterOptions(current.children || [], ids);
			if (children.length) {
				acc.push(...children);
			}
			return acc;
		}, []);
	}, []);

	const handleChange = useCallback(
		(newValue: ICheck) => {
			const arrayIds = safeValue.map((e: any) => (typeof e === "number" ? e : e.value));

			let newArray: number[] = [];
			const isInArray = arrayIds.find((e: any) => e === newValue.value);

			if (isInArray && !newValue.isChecked) {
				newArray = arrayIds.filter((e: any) => e !== newValue.value);
			}

			if (!isInArray && newValue.isChecked) {
				newArray = [...arrayIds, newValue.value];
			}

			const newArrayObject = filterOptions(options, newArray);

			if (newArrayObject.length > 0 && newArrayObject.length === options.length) {
				setAllSelected(true);
			} else {
				setAllSelected(false);
			}

			onChangeRef.current(newArrayObject);
			error && handleValidation && handleValidation(newArrayObject);
		},
		[safeValue, options, filterOptions, error, handleValidation],
	);

	const handleCheckAll = useCallback(() => {
		setAllSelected((state) => !state);

		if (!allSelected) {
			const values = options.map((option) => {
				return { ...option, isChecked: true };
			});
			onChangeRef.current(values);
		} else {
			onChangeRef.current([]);
		}
	}, [allSelected, options]);

	const isChecked = useCallback(
		(id: number): boolean =>
			!!safeValue.find((e: ICheckValue | number) => (typeof e === "number" ? e === id : e.value === id)),
		[safeValue],
	);

	const renderChecks = useCallback(
		(options: ICheckValue[]): JSX.Element[] => {
			return options.map((item) => {
				return (
					<React.Fragment key={`${item.name}${item.value}`}>
						{item.type === "group" ? (
							<S.GroupTitle>{item.title}</S.GroupTitle>
						) : (
							<S.StyledCheckField
								onChange={handleChange}
								checked={isChecked(item.value)}
								value={item.value}
								title={item.title}
								name={item.name}
								disabled={item.disabled || disabled}
								error={item.error}
							/>
						)}
						{item.children && <S.GroupWrapper>{renderChecks(item.children)}</S.GroupWrapper>}
					</React.Fragment>
				);
			});
		},
		[handleChange, isChecked, disabled],
	);

	return (
		<S.FieldGroup full={fullHeight}>
			{allOption && (
				<S.AllCheckField
					onChange={handleCheckAll}
					checked={allSelected}
					value=""
					title="Select All options"
					name="allOption"
					disabled={disabled}
					indeterminate={!!value?.length && value.length < options.length}
				/>
			)}
			{renderChecks(options)}
		</S.FieldGroup>
	);
};

interface ICheckValue {
	value: number;
	name: string;
	title: string;
	dataLanguages?: { id: number; language: number }[];
	type: "category" | "group";
	children?: ICheckValue[];
	selectable?: boolean;
	disabled?: boolean;
	error?: boolean;
}

export interface IAsyncCheckGroup {
	value: any[] | null;
	site?: ISite | null;
	source: string;
	onChange: (value: ICheckValue[]) => void;
	disabled?: boolean;
	error?: boolean;
	handleValidation?: (value: string | any[]) => void;
	validators?: Record<string, unknown>;
	fullHeight?: boolean;
	allLanguages?: boolean;
	contentType: "data" | "formCategory";
	languages?: ILanguage[];
	contentLanguages?: "current" | "all" | string[];
	allOption?: boolean;
}

export default AsyncCheckGroup;
