import {createPortal, useEffect, useId, useRef, useState} from "@wordpress/element";
import {__} from "@wordpress/i18n";
import useClickOutside from "../../../../../components/hooks/use-click-outside";
import Checkbox from "../../checkbox";
import TagVar from "../../tag-var";
import {MULTISELECT_MENU} from "../../multiselect/_data";
import {GetObjectGroups, GetObjectGroup} from "../../../../rest/wp-objects";
import {handleError} from "../../../../helpers/notifications";
import {areEqual} from "../../../../../helpers";

/**
 * Is selected option
 *
 * @param {any[]} selected Selected values
 * @param {string} identifier Identifier
 * @return {boolean}
 */
const isSelectedOption = (selected, identifier) => {
    if (!selected?.length) {
        return false;
    }

    return selected.findIndex(item => areEqual(item, identifier)) !== -1;
}

/**
 * Get value options from identifiers
 *
 * @param {array} value Value (array of identifiers)
 * @param {object[]} allGroups All groups with objects
 * @param {object[]} selectedOptionsCache Cache of selected options
 * @return {*}
 */
const getValueOptions = (value, allGroups, selectedOptionsCache) => {
    if (!value?.length) {
        return [];
    }

    const options = [];

    value.forEach(identifier => {
        // Special case for "all"
        if (identifier === 'all') {
            options.push({
                label: __("All", 'limb-chatbot'),
                value: 'all',
            });
            return;
        }

        // First, search through all groups to find the object
        let found = null;
        if (allGroups?.length) {
            for (const group of allGroups) {
                if (group.objects?.items?.length) {
                    found = group.objects.items.find(item => areEqual(item.identifier, identifier));
                    if (found) {
                        break;
                    }
                }
            }
        }

        // If not found in groups, check the selected options cache
        if (!found && selectedOptionsCache?.length) {
            found = selectedOptionsCache.find(item => areEqual(item.identifier, identifier));
        }

        // If found, add to options
        if (found) {
            options.push({
                label: found.title,
                value: found.identifier,
                link: found.link,
            });
        } else {
            // Fallback: create option with identifier as label
            options.push({
                label: identifier,
                value: identifier,
            });
        }
    });

    return options;
}

export default function WPObjectsGroupsMultiSelect({
                                                       value,
                                                       setValue,
                                                       placeholder,
                                                       validate,
                                                       errorMessage,
                                                       disabled,
                                                       addNotification,
                                                       setLoading,
                                                       showAllOption = true,
                                                       isDataReady,
                                                       setFetched,
                                                   }) {
    const [isOpened, setIsOpened] = useState(false);
    const [menuPosition, setMenuPosition] = useState(null);
    const [searchTerm, setSearchTerm] = useState('');
    const [groups, setGroups] = useState([]);
    const [groupPaginations, setGroupPaginations] = useState({});
    const [loadingGroups, setLoadingGroups] = useState({});
    const [selectedOptionsCache, setSelectedOptionsCache] = useState([]);

    const multiselectLabelRef = useRef(null);
    const multiselectRef = useRef(null);
    const multiselectHeightRef = useRef(0);
    const multiselectMenuRef = useRef(null);
    const searchInputRef = useRef(null);
    const searchDebounce = useRef(null);

    const fieldId = useId();

    useClickOutside([multiselectLabelRef, multiselectRef, multiselectMenuRef], () => {
        setIsOpened(false);
        resetSearchTerm();
    });

    useEffect(() => {
        // Handle resize event
        const handleResize = () => {
            if (isOpened) {
                setupPosition();
            }
        };

        // Events
        window.addEventListener('resize', handleResize);
        window.addEventListener('scroll', setupPosition, true);

        return () => {
            window.removeEventListener('resize', handleResize);
            window.removeEventListener('scroll', setupPosition, true);
        }
    }, [isOpened]);

    useEffect(() => {
        // Load initial groups without selected identifiers
        if (isDataReady) {
            loadGroups(searchTerm, value);
        }
    }, [isDataReady]);

    useEffect(() => {
        if (!isOpened) {
            return;
        }

        let shouldSetup = false;

        // Case 1: when menu first opens and no position is set
        if (!menuPosition) {
            shouldSetup = true;
        } else {
            // Case 2: when value changes and field height might change
            const rect = multiselectRef.current.getBoundingClientRect();
            if (rect.height !== multiselectHeightRef.current) {
                shouldSetup = true;
            }
        }

        if (shouldSetup) {
            setupPosition();
        }
    }, [isOpened, value]);

    const setupPosition = () => {
        if (multiselectRef.current && multiselectMenuRef.current) {
            const rect = multiselectRef.current.getBoundingClientRect();
            const menuRect = multiselectMenuRef.current.getBoundingClientRect();
            setMenuPosition({
                top: rect.bottom - (rect.bottom + menuRect.height > window.innerHeight ? rect.bottom + menuRect.height - window.innerHeight + 4 : 0),
                left: rect.left,
                width: rect.width - (rect.left + rect.width > window.innerWidth ? rect.left + rect.width - window.innerWidth + 4 : 0)
            });
            // Keep current height info
            multiselectHeightRef.current = rect.height;
        }
    }

    /**
     * Load groups with objects
     *
     * @param {string} search Search term
     * @param {array} selectedIdentifiers Selected identifiers to fetch
     * @return {Promise<void>}
     */
    const loadGroups = async (search = '', selectedIdentifiers = []) => {
        if (setLoading) {
            setLoading(prev => prev + 1);
        }

        const params = {
            'include[]': 'objects',
        };

        if (search.trim().length) {
            params.s = search;
        }

        // Add selected identifiers to fetch their full data
        if (selectedIdentifiers.length > 0) {
            selectedIdentifiers.forEach((id, index) => {
                params[`selected_identifiers[${index}]`] = id;
            });
        }

        try {
            const response = await GetObjectGroups(params);

            if (Array.isArray(response)) {
                // Initialize pagination states for each group
                const paginations = {};
                const initialSelectedItems = [];
                const filteredGroups = [];

                response.forEach(group => {
                    // Skip the selected_objects group from dropdown menu
                    if (group.name === 'selected_objects') {
                        // Only collect items for cache from selected_objects group
                        if (group.objects?.items?.length) {
                            initialSelectedItems.push(...group.objects.items);
                        }
                        return;
                    }

                    // Add to filtered groups
                    filteredGroups.push(group);

                    if (group.objects) {
                        paginations[group.name] = {
                            page: group.objects.page || 1,
                            perPage: group.objects.per_page || 10,
                            total: group.objects.total || 0,
                            totalPages: group.objects.total_pages || 0,
                        };

                        // Collect items that match the selected identifiers
                        if (selectedIdentifiers.length > 0 && group.objects.items?.length) {
                            group.objects.items.forEach(item => {
                                if (selectedIdentifiers.some(id => areEqual(id, item.identifier))) {
                                    initialSelectedItems.push(item);
                                }
                            });
                        }
                    }
                });

                setGroups(filteredGroups);
                setGroupPaginations(paginations);

                // Update selectedOptionsCache with initial selected items
                if (initialSelectedItems.length > 0) {
                    setSelectedOptionsCache(initialSelectedItems);
                }
            }
        } catch (e) {
            handleError(e, addNotification, {
                title: __("Failed to load object groups.", 'limb-chatbot'),
                description: e.message ? __(e.message, 'limb-chatbot') : __("Please check and try again.", 'limb-chatbot'),
            });
        }

        setTimeout(() => {
            typeof setFetched === 'function' && setFetched(true);
            typeof setLoading === 'function' && setLoading(prev => prev - 1);
        }, 200);
    }

    /**
     * Load more objects for a specific group
     *
     * @param {string} groupName Group name
     * @return {Promise<void>}
     */
    const loadMoreForGroup = async (groupName) => {
        if (loadingGroups[groupName]) {
            return;
        }

        setLoadingGroups(prev => ({...prev, [groupName]: true}));

        const currentPagination = groupPaginations[groupName];
        if (!currentPagination) {
            setLoadingGroups(prev => ({...prev, [groupName]: false}));
            return;
        }

        const params = {
            page: currentPagination.page + 1,
            per_page: currentPagination.perPage,
        };

        if (searchTerm.trim().length) {
            params.s = searchTerm;
        }

        try {
            const response = await GetObjectGroup(groupName, params);

            if (response && response.items) {
                // Append items to the specific group
                setGroups(prevGroups => {
                    return prevGroups.map(group => {
                        if (group.name === groupName) {
                            return {
                                ...group,
                                objects: {
                                    items: [...(group.objects?.items || []), ...response.items],
                                    total: response.total,
                                    page: response.page,
                                    per_page: response.per_page,
                                    total_pages: response.total_pages,
                                }
                            };
                        }
                        return group;
                    });
                });

                // Update pagination for this group
                setGroupPaginations(prev => ({
                    ...prev,
                    [groupName]: {
                        page: response.page,
                        perPage: response.per_page,
                        total: response.total,
                        totalPages: response.total_pages,
                    }
                }));
            }
        } catch (e) {
            handleError(e, addNotification, {
                title: __("Failed to load more objects.", 'limb-chatbot'),
                description: e.message ? __(e.message, 'limb-chatbot') : __("Please check and try again.", 'limb-chatbot'),
            });
        }

        setLoadingGroups(prev => ({...prev, [groupName]: false}));
    }

    const valueOptions = value?.length ? getValueOptions(value, groups, selectedOptionsCache) : [];

    const resetSearchTerm = () => {
        if (searchTerm.trim().length) {
            setSearchTerm('');
            handleSearch('');
        }
    }

    /**
     * Toggle menu manually
     */
    const toggleMenuManually = (e) => {
        if (disabled) {
            // Close if opened
            if (isOpened) {
                setIsOpened(false);
            }
            return;
        }
        // Skip the toggle when doing it focus on the input
        if (isOpened && e.target.closest('.lbaic-settings-multiselect-input')) {
            return;
        }
        // Toggle menu
        const opened = !isOpened;
        setIsOpened(opened);
    }

    /**
     * Search an option(s)
     *
     * @param {Event} e Event
     */
    const handleSearchTermChange = (e) => {
        if (disabled) {
            return;
        }
        setSearchTerm(e.target.value);
        // Keep opened if searching
        if (e.target.value) {
            setIsOpened(true);
        }
        // Clear any previously set debounce timer
        clearTimeout(searchDebounce.current);
        // Set a new debounce timer to delay handling the title change
        searchDebounce.current = setTimeout(() => {
            handleSearch(e.target.value);
        }, 400);
    }

    /**
     * Searching
     *
     * @param {string} searchTerm Search term
     * @return {Promise<void>}
     */
    const handleSearch = async (searchTerm = '') => {
        await loadGroups(searchTerm);
    }

    /**
     * Update value
     *
     * @param {string} identifier Identifier
     * @param {object} itemData Optional item data to cache
     */
    const handleOptionClick = (identifier, itemData = null) => {
        if (disabled) {
            return;
        }
        let newValue;

        // Special handling for "all" option
        if (identifier === 'all') {
            if (isSelectedOption(value, 'all')) {
                // Deselecting "all"
                newValue = value.filter(item => !areEqual(item, 'all'));
            } else {
                // Selecting "all" - remove all other selections
                newValue = ['all'];
                // Clear cache since we're selecting all
                setSelectedOptionsCache([]);
            }
        } else {
            // Regular option
            if (isSelectedOption(value, identifier)) {
                // Deselecting
                newValue = value.filter(item => !areEqual(item, identifier));
                // Remove from cache
                setSelectedOptionsCache(prev => prev.filter(item => !areEqual(item.identifier, identifier)));
            } else {
                // Selecting a specific option
                // Remove "all" if it exists
                const currentValue = value?.filter(item => !areEqual(item, 'all')) || [];
                newValue = [...currentValue, identifier];
                // Add to cache if itemData provided
                if (itemData) {
                    setSelectedOptionsCache(prev => {
                        // Check if already in cache
                        const exists = prev.find(item => areEqual(item.identifier, identifier));
                        if (!exists) {
                            return [...prev, itemData];
                        }
                        return prev;
                    });
                }
            }
        }

        setValue(newValue);

        // Validate
        if (typeof validate === 'function') {
            validate(newValue);
        }
    }

    /**
     * Reset value
     *
     * @param {Event} e Event
     */
    const handleReset = (e) => {
        e.stopPropagation();
        // Reset
        const newValue = [];
        setValue(newValue);
        // Validate
        if (typeof validate === 'function') {
            validate(newValue);
        }
    }

    /**
     * Render "All" option
     *
     * @return {JSX.Element}
     */
    const renderAllOption = () => {
        const checked = isSelectedOption(value, 'all');

        return (
            <div
                className={`lbaic-settings-multiselect-menu-item${checked ? ' active' : ''}${disabled ? ' disabled' : ''}`}
                onClick={(e) => {
                    e.preventDefault();
                    handleOptionClick('all', null);
                }}>
                <Checkbox
                    label={__("All", 'limb-chatbot')}
                    isChecked={checked}
                />
            </div>
        );
    }

    /**
     * Render option
     *
     * @param {object} item Item data
     * @param {number} i Item index
     * @return {JSX.Element}
     */
    const renderOption = (item, i) => {
        const checked = isSelectedOption(value, item.identifier);

        return (
            <div key={i}
                 className={`lbaic-settings-multiselect-menu-item${checked ? ' active' : ''}${disabled ? ' disabled' : ''}`}
                 onClick={(e) => {
                     e.preventDefault();
                     handleOptionClick(item.identifier, item);
                 }}>
                <Checkbox
                    label={item.title}
                    isChecked={checked}
                />
            </div>
        );
    }

    /**
     * Render group
     *
     * @param {object} group Group data
     * @param {number} i Group index
     * @return {JSX.Element|null}
     */
    const renderGroup = (group, i) => {
        const hasItems = group.objects?.items?.length > 0;
        const pagination = groupPaginations[group.name];
        const showLoadMore = pagination && (pagination.page * pagination.perPage < pagination.total);
        const isLoadingMore = loadingGroups[group.name];

        if (!hasItems) {
            return null;
        }

        return (
            <div key={i} className="lbaic-settings-multiselect-menu-group">
                <div className="lbaic-settings-multiselect-menu-group-label">
                    <span className='lbaic-settings-multiselect-menu-group-label-in' dangerouslySetInnerHTML={{__html: group.label}} />
                </div>
                <div className="lbaic-settings-multiselect-menu-group-items">
                    {group.objects.items.map(renderOption)}
                </div>
                {showLoadMore && (
                    <div className='lbaic-settings-multiselect-menu-action'>
                        <button
                            type='button'
                            className={`lbaic-settings-button-reset lbaic-settings-multiselect-menu-action-in${isLoadingMore || disabled ? ' lbaic-settings-button-disabled' : ''}`}
                            onClick={() => loadMoreForGroup(group.name)}>
                            {isLoadingMore && (
                                <svg
                                    className='lbaic-settings-button-i lbaic-settings-loading-circle lbaic-settings-button-loading-circle'
                                    xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'>
                                    <use href='#lbaic-settings-circle'/>
                                </svg>
                            )}
                            <span
                                className='lbaic-settings-multiselect-menu-action-label'>{__("Load more", 'limb-chatbot')}</span>
                        </button>
                    </div>
                )}
            </div>
        );
    }

    const position = {
        top: menuPosition?.top || '',
        left: menuPosition?.left || '',
        width: menuPosition?.width || '',
    }

    const hasAnyItems = groups.some(group => group.objects?.items?.length > 0);

    return (
        <div
            className={`lbaic-settings-multiselect-holder${isOpened ? ' opened' : ''}${disabled ? ' lbaic-settings-multiselect-disabled' : ''}${errorMessage ? ' lbaic-settings-multiselect-error' : ''}`}
        >
            <div
                ref={multiselectLabelRef}
                className='lbaic-settings-multiselect-placeholder'
            >
                <label htmlFor={fieldId} className='lbaic-settings-multiselect-placeholder-in'>{placeholder}</label>
            </div>

            <div ref={multiselectRef}
                 className={`lbaic-settings-multiselect lbaic-settings-multiselect-h-42${searchTerm || value?.length ? ' lbaic-settings-multiselect-has-value' : ''}`}
                 onClick={toggleMenuManually}>
                <div className='lbaic-settings-multiselect-in'>
                    <div className='lbaic-settings-multiselect-tag-group'>
                        {valueOptions.map((item, i) => (
                            <TagVar
                                key={i}
                                label={item.label}
                                link={item.link}
                                onClick={() => handleOptionClick(item.value)}
                            />
                        ))}
                        <input
                            id={fieldId}
                            ref={searchInputRef}
                            type='text'
                            value={searchTerm}
                            className='lbaic-settings-multiselect-input'
                            placeholder={isOpened || value?.length ? __("Search...", 'limb-chatbot') : ''}
                            onChange={handleSearchTermChange}
                            onFocus={resetSearchTerm}
                            readOnly={disabled}
                            autoComplete="off"
                        />
                    </div>
                    <div className="lbaic-settings-multiselect-actions">
                        {value?.length > 1 && (
                            <button
                                type="button"
                                className="lbaic-settings-multiselect-reset lbaic-settings-button-reset"
                                onClick={handleReset}>
                                <svg className='lbaic-settings-multiselect-reset-i' xmlns='http://www.w3.org/2000/svg'
                                     fill='none'
                                     viewBox='0 0 24 24'>
                                    <use href='#lbaic-settings-close'/>
                                </svg>
                            </button>
                        )}
                        <div className='lbaic-settings-multiselect-arrow'>
                            <svg className='lbaic-settings-multiselect-arrow-in' xmlns='http://www.w3.org/2000/svg'
                                 fill='none'
                                 viewBox='0 0 24 24'>
                                <use href='#lbaic-settings-arrow'/>
                            </svg>
                        </div>
                    </div>
                </div>
            </div>
            {isOpened && createPortal(
                <div
                    ref={multiselectMenuRef}
                    className='lbaic-settings-multiselect-menu'
                    style={{
                        '--lbaic-settings-multiselect-holder-top': position.top + (typeof position.top === 'number' ? 'px' : ''),
                        '--lbaic-settings-multiselect-holder-left': position.left + (typeof position.left === 'number' ? 'px' : ''),
                        '--lbaic-settings-multiselect-holder-width': position.width + (typeof position.width === 'number' ? 'px' : ''),
                        '--lbaic-settings-multiselect-height': MULTISELECT_MENU.maxHeight + 'px',
                    }}
                >
                    <div
                        className='lbaic-settings-multiselect-menu-in lbaic-settings-scroll-y lbaic-settings-scroll-color'>
                        {showAllOption && renderAllOption()}
                        {hasAnyItems ?
                            groups.map(renderGroup)
                            :
                            (
                                <div className='lbaic-settings-multiselect-menu-item disabled'>
                                    <span
                                        className='lbaic-settings-multiselect-menu-item-label'>{__("No options available.", 'limb-chatbot')}</span>
                                </div>
                            )
                        }
                    </div>
                </div>,
                document.getElementById('lbaic-settings-field-menu')
            )}
            {errorMessage && (
                <div className='lbaic-settings-multiselect-error-message'>
                    <p className='lbaic-settings-multiselect-error-message-in'>{errorMessage}</p>
                </div>
            )}
        </div>
    )
}