import {createPortal, useEffect, useRef, useState, useId} 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 "./_data";
import {areEqual} from "../../../../helpers";

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

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

/**
 * Get filtered options
 *
 * @param {string} searchTerm Search term
 * @param {object[]} options Options
 * @return {*}
 */
const getFilteredOptions = (searchTerm, options) => {
    if (searchTerm?.length && options?.length) {
        return options.filter(option => option.label.toLowerCase().includes(searchTerm.toLowerCase()))
    } else {
        return options;
    }
}

/**
 * Get value options
 *
 * @param {array} value Value
 * @param {object[]} savedValueOptions Saved value options
 * @param {object[]} options Options
 * @param {object[]} selectedOptions Selected options
 * @return {*}
 */
const getValueOptions = (value, savedValueOptions, options, selectedOptions) => {
    return value.map(item => {
        // Get from options
        const option = options?.find(option => areEqual(option.value, item));
        if (option) {
            return option;
        }
        // Get from saved values options
        const savedOption = savedValueOptions?.find(option => areEqual(option.value, item));
        if (savedOption) {
            return savedOption;
        }
        // Get from selected options
        const selectedOption = selectedOptions?.find(option => areEqual(option.value, item));
        if (selectedOption) {
            return selectedOption;
        }
        // Return default
        return {
            label: item,
            value: item,
        }
    });
}

export default function MultiSelect({
                                        placeholder,
                                        className,
                                        value,
                                        setValue,
                                        options,
                                        savedValueOptions,
                                        optionImageClassName,
                                        tagVarImageClassName,
                                        searchable,
                                        onSearch,
                                        onLoadMore,
                                        onAddNew,
                                        pagination,
                                        validate,
                                        errorMessage,
                                        singleSelect,
                                        children,
                                        disabled,
                                        refreshOptions,
                                        refreshOptionsLabel
                                    }) {
    const [isOpened, setIsOpened] = useState(false);
    const [menuPosition, setMenuPosition] = useState(null);
    const [isSearching, setIsSearching] = useState(false);
    const [searchTerm, setSearchTerm] = useState('');
    const [loadingMore, setLoadingMore] = useState(false);
    const [selectedOptions, setSelectedOptions] = useState([]);
    const [refreshingOptions, setRefreshingOptions] = useState(false);

    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(() => {
        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;
        }
    }

    const valueOptions = value?.length ? getValueOptions(value, savedValueOptions, options, selectedOptions) : [];
    const filteredOptions = typeof onSearch !== 'function' ? getFilteredOptions(searchTerm, options) : options;
    const showLoadMore = typeof onLoadMore === 'function' && pagination.page * pagination.perPage < pagination.total;

    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 = '') => {
        if (typeof onSearch !== 'function' || isSearching) {
            return;
        }
        setIsSearching(true);
        const params = {
            page: 1,
            perPage: pagination.perPage,
        };
        if (searchTerm.trim().length) {
            params.search = searchTerm;
        }
        await onSearch(params);
        setIsSearching(false);
    }

    /**
     * Load more options
     */
    const handleLoadMore = async () => {
        if (disabled || loadingMore) {
            return;
        }
        setLoadingMore(true);
        const params = {
            page: pagination.page + 1,
            perPage: pagination.perPage
        };
        if (searchTerm) {
            params.search = searchTerm;
        }
        await onLoadMore(params);
        setLoadingMore(false);
    }

    /**
     * Add new option click callback
     */
    const handleAddNew = () => {
        if (disabled || typeof onAddNew !== 'function') {
            return;
        }
        setIsOpened(false);
        onAddNew();
    }

    /**
     * Refresh options click callback
     */
    const handleRefreshOptions = async () => {
        if (disabled || refreshingOptions || typeof refreshOptions !== 'function') {
            return;
        }
        setRefreshingOptions(true);
        await refreshOptions();
        setRefreshingOptions(false);
    }

    /**
     * Update value
     *
     * @param {object} option Option data
     */
    const handleOptionClick = (option) => {
        if (disabled || option.disabled) {
            return;
        }
        let newValue;
        if (isSelectedOption(value, option)) {
            newValue = value.filter(item => !areEqual(item, option.value));
            setSelectedOptions(prevState => prevState.filter(item => !areEqual(item.value, option.value)));
        } else {
            if (singleSelect) {
                newValue = [option.value];
                setSelectedOptions([option]);
            } else {
                newValue = [...value, option.value];
                setSelectedOptions(prevState => [...prevState, option]);
            }
        }
        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 option
     *
     * @param {object} option Option data
     * @param {number} i Option index
     * @return {JSX.Element}
     */
    const renderOption = (option, i) => {
        const checked = isSelectedOption(value, option);

        return (
            <div key={i}
                 className={`lbaic-settings-multiselect-menu-item${checked ? ' active' : ''}${option.disabled || disabled ? ' disabled' : ''}`}
                 onClick={(e) => {
                     e.preventDefault();
                     handleOptionClick(option);
                 }}>
                <Checkbox
                    icon={option.icon}
                    image={option.image}
                    imageClassName={optionImageClassName}
                    label={option.label}
                    isChecked={checked}
                    hideCheckbox={singleSelect}
                />
            </div>
        );
    }

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

    return (
        <div
            className={`lbaic-settings-multiselect-holder${className ? ' ' + className : ''}${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}
            >
                {!!children ? <div className='lbaic-settings-multiselect-children'>{children}</div> : null}

                <div className='lbaic-settings-multiselect-in'>
                    <div className='lbaic-settings-multiselect-tag-group'>
                        {valueOptions.map((item, i) => (
                            <TagVar
                                key={i}
                                icon={item.icon}
                                image={item.image}
                                imageClassName={tagVarImageClassName}
                                label={item.label}
                                onClick={() => handleOptionClick(item)}
                            />
                        ))}
                        {searchable && (
                            <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'>
                        {filteredOptions?.length ?
                            filteredOptions.map(renderOption)
                            :
                            (
                                <div className='lbaic-settings-multiselect-menu-item disabled'>
                                    <span
                                        className='lbaic-settings-multiselect-menu-item-label'>{__("No options available.", 'limb-chatbot')}</span>
                                </div>
                            )
                        }
                        {showLoadMore && (
                            <div className='lbaic-settings-multiselect-menu-action'>
                                <button
                                    type='button'
                                    className={`lbaic-settings-button-reset lbaic-settings-multiselect-menu-action-in${loadingMore || disabled ? ' lbaic-settings-button-disabled' : ''}`}
                                    onClick={handleLoadMore}>
                                    {loadingMore && (
                                        <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>
                        )}
                        {typeof onAddNew === 'function' && (
                            <div className='lbaic-settings-multiselect-menu-action'>
                                <button
                                    type='button'
                                    className={`lbaic-settings-button-reset lbaic-settings-multiselect-menu-action-in${disabled ? ' lbaic-settings-button-disabled' : ''}`}
                                    onClick={handleAddNew}>
                                    <span
                                        className='lbaic-settings-multiselect-menu-action-label'>{__("Add new", 'limb-chatbot')}</span>
                                </button>
                            </div>
                        )}
                        {typeof refreshOptions === 'function' && (
                            <div className='lbaic-settings-multiselect-menu-action'>
                                <button
                                    type='button'
                                    className={`lbaic-settings-button-reset lbaic-settings-button lbaic-settings-button-primary lbaic-settings-button-center lbaic-settings-multiselect-menu-action-in${refreshingOptions || disabled ? ' lbaic-settings-button-disabled' : ''}`}
                                    onClick={handleRefreshOptions}>
                                    {refreshingOptions && (
                                        <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'>{refreshOptionsLabel || __("Refresh", 'limb-chatbot')}</span>
                                </button>
                            </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>
    )
}