import { useEffect, useRef, useState } from "react";
import type { GroupBase, InputActionMeta, MenuProps, OptionsOrGroups } from "react-select";
import { components } from "react-select";

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

import * as S from "./style";

const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
	const {
		name,
		value,
		error,
		disabled,
		onChange,
		entity,
		entityId,
		site,
		lang,
		selectedContent,
		mandatory,
		placeholder,
		filter,
		options,
		source,
		type,
		maxWidth,
		filterLang,
		languages,
		contentLanguages,
		forceLanguage,
	} = props;

	const initialState: IState = {
		items: [],
		inputText: "",
	};

	const [state, setState] = useState(initialState);
	const menuRef = useRef<HTMLDivElement | null>(null);

	const className = error ? `react-select-error ${type}` : type;
	const isSearchable = type !== "inline";

	// biome-ignore lint/correctness/useExhaustiveDependencies: lang es necesaria para recargar opciones al cambiar idioma
	useEffect(() => {
		const isPage = entity === "pages";
		const isCategories = entity === "categories";
		const languagesIds = Array.isArray(contentLanguages)
			? languages?.filter((language) => contentLanguages.includes(language.locale)).map((language) => language.id)
			: contentLanguages === "all"
				? languages?.map((language) => language.id)
				: [];

		const getItems = async () => {
			let data = [];
			try {
				if (entity) {
					let result = null;
					if (site && !isCategories) {
						const fullOptions = languagesIds?.length ? { ...options, languagesIds } : options;
						result =
							isPage && selectedContent
								? await selects.getSelectSiteItems(site.id, entity, fullOptions, selectedContent.id, forceLanguage)
								: await selects.getSelectSiteItems(site.id, entity, fullOptions, undefined, forceLanguage);
					} else {
						result = await selects.getSelectItems(entity, entityId, filterLang);
					}

					if (result && isReqOk(result.status)) {
						data = result.data;
					}
				}

				if (source) {
					const siteID = site ? site.id : null;
					const result = await checkgroups.getCheckGroupItems(siteID, source, false, languagesIds);
					if (result && isReqOk(result.status)) {
						data = result.data.map((opt: ICheck) => {
							return { value: opt.value, label: opt.title };
						});
					}
				}
			} catch (e) {
				console.log(e);
			}
			return data;
		};

		let isMounted = true;
		getItems()
			.then((result: IOption[]) => {
				const label = placeholder || "Empty";
				const emptyOption = { value: null, label };
				const optionValues: IOption[] = mandatory ? result : [emptyOption, ...result];
				const filteredOptions: IOption[] = filter
					? optionValues.filter((e: IOption) => !filter.includes(e.value))
					: optionValues;
				if (isMounted) setState((state) => ({ ...state, items: filteredOptions }));
			})
			.catch((apiError) => console.log(apiError));
		return () => {
			isMounted = false;
		};
	}, [
		entity,
		site,
		lang,
		selectedContent,
		entityId,
		mandatory,
		placeholder,
		filter,
		source,
		options,
		contentLanguages,
		languages,
		filterLang,
		forceLanguage,
	]);

	const handleChange = (selectedValue: unknown | null) => {
		const newValue = selectedValue ? (selectedValue as ICheck | IOption).value : null;
		onChange(newValue);
	};

	const filterOptions = (inputValue: string) => {
		return state.items.filter((i: IOption) => i.label.toLowerCase().includes(inputValue.toLowerCase()));
	};

	const handleInputChange = (inputText: string, meta: InputActionMeta) => {
		if (meta.action !== "input-blur" && meta.action !== "menu-close") {
			setState((state) => ({ ...state, inputText }));
		}
	};

	const loadOptions = (
		inputValue: string,
		callback: (options: OptionsOrGroups<unknown, GroupBase<unknown>>) => void,
	) => {
		callback(filterOptions(inputValue));
	};

	const getObjectValue = (val: string | number | null | undefined, options: IOption[]) => {
		const fixedVal = Array.isArray(val) ? val[0] : val;
		const selectedValue = fixedVal && typeof fixedVal === "object" ? fixedVal.id : fixedVal;

		return options?.find((option: IOption) => option.value === selectedValue);
	};

	const handleKeyDown = (e: React.KeyboardEvent) => {
		if (!menuRef.current || isSearchable) return;

		const char = e.key.toLowerCase();
		if (!/^[a-z]$/i.test(char)) return;

		const optionNodes = menuRef.current.querySelectorAll<HTMLDivElement>(".react-select__option");

		const targetNode = Array.from(optionNodes).find((node) => node.innerText.trim().toLowerCase().startsWith(char));

		if (targetNode) {
			targetNode.scrollIntoView({
				block: "nearest",
			});
		}
	};

	const Menu = (menuProps: MenuProps<unknown, false, GroupBase<unknown>>) => (
		<div ref={menuRef}>
			<components.Menu {...menuProps} />
		</div>
	);

	return (
		<div data-testid="asyncSelect">
			<S.StyledSelect
				name={name}
				value={getObjectValue(value, state.items) || ""}
				cacheOptions
				loadOptions={loadOptions}
				defaultOptions={state.items}
				onInputChange={handleInputChange}
				onChange={handleChange}
				classNamePrefix="react-select"
				error={error}
				isDisabled={disabled}
				className={className}
				required={mandatory}
				isClearable={false}
				isSearchable={isSearchable}
				maxWidth={maxWidth}
				inputValue={state.inputText}
				components={{ Menu }}
				onKeyDown={handleKeyDown}
			/>
		</div>
	);
};

interface IState {
	items: IOption[];
	inputText: string;
}

interface IOption {
	value: number | string | null;
	label: string;
	language_id?: number;
}

interface ICheck {
	value: number | string | null;
	name: string;
	title: string;
}
export interface IAsyncSelectProps {
	name: string;
	value?: string | number | null;
	entity?: string;
	entityId?: string | number;
	error?: boolean;
	disabled?: boolean;
	onChange: (value: string | number | null) => void;
	site?: ISite | null;
	lang?: string;
	selectedContent?: IPage;
	mandatory?: boolean;
	placeholder?: string;
	filter?: (string | number | null)[];
	options?: Record<string, unknown>;
	source?: string;
	type?: string;
	maxWidth?: number;
	filterLang?: number;
	languages?: ILanguage[];
	contentLanguages?: "current" | "all" | string[];
	forceLanguage?: number;
}

export default AsyncSelect;
