import {useEffect, useRef, useState} from "@wordpress/element";
import {__} from "@wordpress/i18n";
import {
    ALL_SETTINGS_DATA_KEY,
    ALL_SETTINGS_ERRORS_DATA_KEY,
    discardChanges,
    getAllSettingsChanges,
    getInitialData,
    getSettingsDataToUpdate,
    getSettingsLocalKey,
    isAllSettingsHasError,
    setSettingsDataToUpdate,
    setupSettings
} from "../../../../helpers";
import useUnsavedChangesWarning from "../unsaved-changes-warning";
import {GetSettings, UpdateSettings} from "../../../../rest/settings";
import {handleError, handleSuccess, handleWarning} from "../../../../helpers/notifications";
import confirm from "../../../../helpers/confirm";
import ContentLoading from "../../settings/containers/content/loading";
import ContentBodyInner from "../../settings/containers/content/body-inner";
import ContentFooter from "../../settings/containers/content/footer";
import {areDeepEqual, ifArrayExists} from "../../../../../helpers";

export default function TabSettings({
                                        name,
                                        settings,
                                        keysPrefix,
                                        validateField,
                                        tabLoading,
                                        contentLoading,
                                        shouldUpdatePreviewData,
                                        previewDataSpecialKeys,
                                        dataFetched,
                                        updateSaving,
                                        setChatbotPreview,
                                        notifications,
                                        footerChildren,
                                        onDbDataReady,
                                        initialDataRef,
                                        children
                                    }) {
    // Actions states
    const [loading, setLoading] = useState(1);
    const [isDataFetched, setIsDataFetched] = useState(false);
    const [saving, setSaving] = useState(false);
    const [previewSaving, setPreviewSaving] = useState(false);
    const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
    // Initial data - use provided ref or create internal one
    const initialData = initialDataRef || useRef(getInitialData(settings));
    // Preview data
    const prevPreviewData = useRef(null);
    // Preview data save debounce
    const previewDataSaveDebounce = useRef(null);

    // Check unsaved changes before leaving the page
    useUnsavedChangesWarning(hasUnsavedChanges);

    useEffect(() => {
        fetchData();
    }, []);

    const isLoading = (loading + (tabLoading || 0)) > 0;
    const saveButtonDisabled = isLoading || saving || !hasUnsavedChanges;

    useEffect(() => {
        if (isDataFetched) {
            // Local storage
            sessionStorage.setItem(`lbaic.${name}`, JSON.stringify(getInitialData(settings)));
            // Update chatbot preview data
            if (typeof setChatbotPreview === 'function') {
                const dataForPreview = {
                    utility: {},
                };
                for (const key in settings) {
                    if (key === 'status') {
                        dataForPreview.status = +settings[key][0];
                    } else {
                        dataForPreview.utility[key] = settings[key][0];
                    }
                }
                // Update state
                setChatbotPreview(prevState => {
                    let hasChanges = false;
                    // Check status
                    if ('status' in dataForPreview) {
                        if (dataForPreview.status !== prevState.status) {
                            hasChanges = true;
                        }
                    }
                    // Check utility
                    if (!hasChanges) {
                        for (const key in dataForPreview.utility) {
                            if (!areDeepEqual(dataForPreview.utility[key], prevState.utility?.[key])) {
                                hasChanges = true;
                                break;
                            }
                        }
                    }
                    if (hasChanges) {
                        return {
                            ...prevState,
                            status: 'status' in dataForPreview ? dataForPreview.status : prevState.status,
                            utility: {
                                ...prevState.utility,
                                ...dataForPreview.utility,
                            },
                            _updateTimestamp: Date.now()
                        }
                    }
                    return prevState;
                });
            }
            // Set settings data, check has unsaved changes
            const preparedSettings = LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.prepareDataToUpdate', settings, name);
            const dataToSave = setSettingsDataToUpdate(preparedSettings, initialData.current, `${keysPrefix}.`);
            setHasUnsavedChanges(dataToSave?.length > 0);
            // Save preview data
            if (shouldUpdatePreviewData) {
                savePreviewData();
            }
        }
    }, [
        isDataFetched,
        // Settings values
        ...Object.keys(settings).map(key => settings[key][0])
    ]);

    useEffect(() => {
        if (typeof dataFetched === 'function') {
            dataFetched(isDataFetched);
        }
        // Check errors if asking
        const params = new URLSearchParams(window.location.search);
        if (params.get('check_errors')) {
            isSettingsFieldsValid();
        }
    }, [isDataFetched]);

    useEffect(() => {
        if (typeof updateSaving === 'function') {
            updateSaving(saving);
        }
    }, [saving]);

    /**
     * Save preview data
     */
    const savePreviewData = () => {
        clearTimeout(previewDataSaveDebounce.current);
        previewDataSaveDebounce.current = setTimeout(async () => {
            setPreviewSaving(true);
            // Get data to update
            const initial = JSON.parse(JSON.stringify(initialData.current));
            if (prevPreviewData.current?.length) {
                prevPreviewData.current.forEach(item => {
                    const key = getSettingsLocalKey(item.key, keysPrefix);
                    initial[key] = item.value;
                });
            }
            // Prepare settings
            const preparedSettings = LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.preview.prepareDataToUpdate',
                // Get settings of the specific keys if any
                previewDataSpecialKeys?.length ?
                    Object.fromEntries(
                        previewDataSpecialKeys.map(key => [key, settings[key]]).filter(([_, value]) => value !== undefined)
                    )
                    :
                    settings,
                name
            );
            let data = LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.preview.dataToUpdate', getSettingsDataToUpdate(preparedSettings, initial, `${keysPrefix}.`, '.preview'), name);
            if (data.length || prevPreviewData.current?.length) {
                if (!data.length) {
                    // Save all changes
                    const allData = getSettingsDataToUpdate(preparedSettings, {}, `${keysPrefix}.`, '.preview');
                    data = allData.filter(item => prevPreviewData.current.find(prevItem => prevItem.key === item.key));
                }
                if (data.length) {
                    if (!ifArrayExists(data, prevPreviewData.current)) {
                        LimbChatbot.Hooks.doAction('preview_settings_on_saving_process', true);
                        try {
                            await UpdateSettings(LimbChatbot.rest.url, LimbChatbot.rest.nonce, data, {
                                preview: 1
                            });
                        } catch (e) {
                            handleError(e, notifications.set, {
                                title: __("Failed to save changes for preview.", 'limb-chatbot'),
                                description: e.message ? __(e.message, 'limb-chatbot') : __("Please try again.", 'limb-chatbot'),
                            });
                        }
                        prevPreviewData.current = data;
                        LimbChatbot.Hooks.doAction('preview_settings_on_saving_process', false);
                    }
                }
            }
            setPreviewSaving(false);
        }, 800);
    }

    /**
     * Get saved settings
     */
    const fetchData = async () => {
        setLoading(prev => prev + 1);
        try {
            const keyPrefix = keysPrefix + '.';
            const res = await GetSettings(LimbChatbot.rest.url, LimbChatbot.rest.nonce, {
                key: Object.keys(settings).map(key => keyPrefix + key),
            });
            setupSettings(settings, `lbaic.${name}`, keyPrefix, res, initialData);
        } catch (e) {
            handleError(e, notifications.set, {
                title: __("Failed to retrieve data.", 'limb-chatbot'),
                description: e.message ? __(e.message, 'limb-chatbot') : __("Please check your connection and try again.", 'limb-chatbot'),
            });
        }
        // On data ready
        if (onDbDataReady) {
            onDbDataReady(initialData.current);
        }
        setTimeout(() => {
            setIsDataFetched(true);
            setLoading(prev => prev - 2); // Since the loading from the very beginning is 1
        }, 200);
    }

    /**
     * Is settings fields valid
     *
     * @return {boolean}
     */
    const isSettingsFieldsValid = () => {
        // Validate all fields
        if (typeof validateField === 'function') {
            const settingsKeys = Object.keys(settings);
            let hasErrors = false;
            // Check all settings of the tab
            for (const key of settingsKeys) {
                if (!validateField(key, settings[key][0])) {
                    hasErrors = true;
                }
            }
            // If there is an error
            if (hasErrors) {
                return false;
            }
        }

        return true;
    }

    /**
     * Save settings
     */
    const save = async () => {
        if (saveButtonDisabled) {
            return;
        }
        // Check all settings error
        if (!isSettingsFieldsValid() || isAllSettingsHasError(notifications.set)) {
            return;
        }
        setSaving(true);
        // Reset preview data save as no need to save
        clearTimeout(previewDataSaveDebounce.current);
        prevPreviewData.current = null;
        // Get data to update
        const data = LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.dataToUpdate', getAllSettingsChanges(), name);
        if (data.length) {
            try {
                const res = await UpdateSettings(LimbChatbot.rest.url, LimbChatbot.rest.nonce, data);
                // Make the state as initial
                initialData.current = getInitialData(LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.saved', settings));
                // Reset session storage
                sessionStorage.removeItem(`lbaic.${name}`);
                sessionStorage.removeItem(ALL_SETTINGS_DATA_KEY);
                sessionStorage.removeItem(ALL_SETTINGS_ERRORS_DATA_KEY);
                // Notify
                handleSuccess(notifications.set, {
                    title: __("Data saved successfully.", 'limb-chatbot'),
                });
                // Reset unsaved changes state
                setHasUnsavedChanges(false);
            } catch (e) {
                handleError(e, notifications.set, {
                    title: __("Failed to save changes.", 'limb-chatbot'),
                    description: e.message ? __(e.message, 'limb-chatbot') : __("Please try again.", 'limb-chatbot'),
                });
            }
            setSaving(false);
            // On data ready
            if (onDbDataReady) {
                onDbDataReady(initialData.current);
            }
        } else {
            setSaving(false);
            handleWarning(false, notifications.set, {
                title: __("Attention", 'limb-chatbot'),
                description: __("There is no changes to save.", 'limb-chatbot'),
            });
        }
    }

    /**
     * Discard changes
     */
    const discard = async () => {
        if (await confirm(__("Are you sure you want to discard the changes?", 'limb-chatbot'))) {
            discardChanges(settings, initialData.current);
            setHasUnsavedChanges(false);
        }
    }

    return <>
        <ContentBodyInner>
            {((isLoading && !isDataFetched) || contentLoading) && <ContentLoading/>}
            {children}
        </ContentBodyInner>
        <ContentFooter save={save} discard={discard} loading={isLoading} saving={saving} previewSaving={previewSaving}
                       hasUnsavedChanges={hasUnsavedChanges}>
            {footerChildren}
        </ContentFooter>
    </>
}