import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "@wordpress/element";
import {__, sprintf} from "@wordpress/i18n";
import {getInitialData, getNavLinkTo, getSettingsDataToUpdate} from "../../../../../../../helpers";
import {handleError, handleWarning} from "../../../../../../../helpers/notifications";
import Dropdown from "../../../../../../fields/dropdown";
import {required, validate} from "../../../../../../../../validations";
import {GetDefaultSettings} from "../../../../../../../helpers/rest";
import {GetSettings} from "../../../../../../../rest/settings";
import WPObjectsMultiSelect from "../../../../../../fields/specific-fields/wp-objects-multi-select";
import {GetPostTypes} from "../../../../../../../rest/wp-objects";
import {DGEN_PROCESS_KEY_PREFIX, DIN_PROCESS_KEY_PREFIX} from "../../../../../../../data/bg-processes";
import {WP_POST_TYPES_ICONS} from "./_data";
import {SETTINGS_UTILITIES_KEY_PREFIX} from "../../../../../../../data/settings";
import Description from "../../../../../../sections/description";

/**
 * Get prepared settings
 *
 * @param {object} settings Settings
 * @param {string[]} skipKeys Settings keys to skip
 * @param {object[]} moreData Append more data
 * @return {{}|*}
 */
const getPreparedSettings = (settings, skipKeys = [], moreData = []) => {
    try {
        // All keys
        let keys = Object.keys(settings);
        // Skip keys
        if (skipKeys?.length > 0) {
            keys = keys.filter(item => !skipKeys.includes(item));
        }
        // Data
        const data = keys.reduce((obj, key) => {
            obj[key] = settings[key];
            return obj;
        }, {});
        // More data
        if (moreData?.length > 0) {
            for (const datum of moreData) {
                data[datum.key] = [datum.value, null]; // Keeping our state structure
            }
        }

        return data;
    } catch (e) {
        return settings;
    }
}

const ACTIONABLE_SOURCE = 'cpt';

const AddEdit = forwardRef(({
                                formData,
                                saving,
                                setLoading,
                                setShowLoading,
                                updateHasUnsavedChanges,
                                notifications
                            }, ref) => {
    // Actions states
    const [isDataSet, setIsDataSet] = useState(false);
    // Select options
    const [postTypes, setPostTypes] = useState([]);
    // Dataset generating settings
    const dgSettings = {
        'source': useState(ACTIONABLE_SOURCE),
        'post_type': useState(''),
        'limit': useState([]),
    };
    // Dataset indexing settings
    const diSettings = {
        'ai_model_id': useState(''),
        'config_id': useState(''),
        'vector_index_type': useState(''),
        'vector_index_config_id': useState(''),
        'dimension': useState(''),
    };
    // Errors
    const errors = {
        // Dataset generating
        'source': useState(false),
        'post_type': useState(false),
        'limit': useState(false),
        // Dataset indexing
        'di_ai_model_id': useState(false),
        'di_config_id': useState(false),
        'vector_index_type': useState(false),
        'vector_index_config_id': useState(false),
        'dimension': useState(false),
    };
    // Initial data
    const dgInitialData = useRef(getInitialData(dgSettings));
    const diInitialData = useRef(getInitialData(diSettings));

    const [diAIDefaultSettingsChecked, setDIAIDefaultSettingsChecked] = useState(false);

    const diDefaultSettings = useRef();

    useImperativeHandle(ref, () => ({
        async getDataToSave() {
            // Validate
            let hasError = false;
            // Dataset generating fields validation
            for (const item of Object.keys(dgSettings)) {
                // Check for non-standard key/values
                const errStates = [];
                errStates.push({
                    key: item,
                    value: dgSettings[item][0],
                });
                // Validate
                for (const errState of errStates) {
                    if (!validateField(errState.key, errState.value)) {
                        hasError = true;
                    }
                }
            }
            // Dataset indexing fields validation
            for (const item of Object.keys(diSettings)) {
                // Check for non-standard key/values
                const errStates = [];
                if (item === 'ai_model_id' || item === 'config_id') {
                    // AI settings
                    errStates.push({
                        key: 'di_' + item,
                        value: diSettings[item][0]
                    });
                } else {
                    // Others, standard
                    errStates.push({
                        key: item,
                        value: diSettings[item][0],
                    });
                }
                // Validate
                for (const errState of errStates) {
                    if (!validateField(errState.key, errState.value)) {
                        hasError = true;
                    }
                }
            }
            // Continue if validation passed
            if (hasError) {
                return false;
            }
            // Check source type
            const dgSkipKeys = [];
            // Dataset generating process data
            const dgDataToSave = LimbChatbot.Hooks.applyFilters(
                'lbaic.admin.page.settings.knowledge.items.dataset-generating.dataToSave',
                getSettingsDataToUpdate(
                    getPreparedSettings(
                        dgSettings,
                        dgSkipKeys
                    ),
                    {},
                    DGEN_PROCESS_KEY_PREFIX
                )
            );
            // Check limit value
            const limitIndex = dgDataToSave.findIndex(item => item.key === `${DGEN_PROCESS_KEY_PREFIX}limit`);
            if (limitIndex !== -1) {
                const value = dgDataToSave[limitIndex].value;
                if (Array.isArray(value)) {
                    if (value.length === 1 && value[0] === 'all') {
                        dgDataToSave[limitIndex].value = 'all';
                    }
                } else if (value === 'all') {
                    dgDataToSave[limitIndex].value = 'all';
                }
            }
            // Dataset indexing process data
            let diDataToSave = LimbChatbot.Hooks.applyFilters(
                'lbaic.admin.page.settings.knowledge.items.dataset-indexing.dataToSave',
                getSettingsDataToUpdate(
                    getPreparedSettings(
                        diSettings
                    ),
                    {},
                    DIN_PROCESS_KEY_PREFIX
                )
            );
            if (diSettings['vector_index_type'][0] === 'local') {
                diDataToSave = diDataToSave.filter(item => item.key !== `${DIN_PROCESS_KEY_PREFIX}vector_index_config_id`);
            }
            return {
                data: [...dgDataToSave, ...diDataToSave],
            };
        },
        processCompleted() {
            dgSettings['limit'][1]([]);
        },
    }));

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

    useEffect(() => {
        checkFormData(formData);
    }, [formData]);

    /**
     * Setup initial data
     */
    useEffect(() => {
        if (isDataSet) {
            // Setup default settings
            setupDatasetIndexingAIDefaultSettings();
        }
    }, [isDataSet]);

    useEffect(() => {
        if (isDataSet && diAIDefaultSettingsChecked) {
            // Set initial data
            dgInitialData.current = getInitialData(dgSettings);
            diInitialData.current = getInitialData(diSettings);
        }
    }, [isDataSet, diAIDefaultSettingsChecked]);

    /**
     * Has unsaved changes
     */
    useEffect(() => {
        if (!updateHasUnsavedChanges) {
            return;
        }
        if (isDataSet && diAIDefaultSettingsChecked) {
            const dgDataToSave = getSettingsDataToUpdate(dgSettings, dgInitialData.current, DGEN_PROCESS_KEY_PREFIX);
            const diDataToSave = getSettingsDataToUpdate(diSettings, diInitialData.current, DIN_PROCESS_KEY_PREFIX);
            updateHasUnsavedChanges(dgDataToSave?.length > 0 || diDataToSave?.length > 0);
        } else {
            updateHasUnsavedChanges(false);
        }
    }, [isDataSet, dgSettings, diSettings, diAIDefaultSettingsChecked, updateHasUnsavedChanges]);

    /**
     * Validate field
     *
     * @param {string} name Field name
     * @param {any} value Field value
     * @return {boolean}
     */
    const validateField = (name, value) => {
        try {
            let validations = [];

            switch (name) {
                case 'post_type':
                case 'limit':
                    validations = [required];
                    break;
                default:
                    return true;
            }
            const res = validate(value, validations);
            // Update field errors state
            errors[name][1](!res.valid ? res.message : false);

            return res.valid;
        } catch (e) {
            handleError(e, notifications.set, {
                title: __("Field validation failed.", 'limb-chatbot'),
                description: e.message ? __(e.message, 'limb-chatbot') : __("Something went wrong.", 'limb-chatbot'),
            });
            return false;
        }
    }

    /**
     * Get post types
     *
     * @return {Promise<void>}
     */
    const getPostTypes = async () => {
        const res = await GetPostTypes();
        setPostTypes(res.map(item => ({
            label: item.name,
            value: item.slug,
            icon: WP_POST_TYPES_ICONS.includes(item.slug) ? item.slug : 'settings',
        })));
    }

    /**
     * Check form data
     *
     * @param {object|null} defaultData Form default data
     */
    const checkFormData = (defaultData) => {
        setLoading(prev => prev + 1);
        /**
         * Dataset generating settings
         */
        if (defaultData?.generating) {
            const keys = Object.keys(defaultData.generating);
            if (keys.length > 0) {
                for (const key of keys) {
                    if (key in dgSettings) {
                        dgSettings[key][1](defaultData.generating[key]);
                    }
                }
            }
        }
        /**
         * Dataset indexing settings
         */
        if (defaultData?.indexing) {
            const keys = Object.keys(defaultData.indexing);
            if (keys.length > 0) {
                for (const key of keys) {
                    if (key in diSettings) {
                        diSettings[key][1](defaultData.indexing[key]);
                    }
                }
            }
        }
        // Force recommendations source value
        if (dgSettings['source'][0] !== ACTIONABLE_SOURCE) {
            dgSettings['source'][1](ACTIONABLE_SOURCE);
        }
        setIsDataSet(true);
        setLoading(prev => prev - 1);
        setShowLoading(false);
    }

    /**
     * Setup post/dataset indexing default settings
     *
     * @return {Promise<void>}
     */
    const setupDatasetIndexingAIDefaultSettings = async () => {
        const dataNotDefined = [
            'ai_model_id',
            'config_id',
            'dimension',
            'vector_index_type',
            'vector_index_config_id'
        ];
        let aiModelId = diSettings['ai_model_id'][0];
        const chatbotSettingsPrefix = `${SETTINGS_UTILITIES_KEY_PREFIX}chatbot.`;
        const chatbotSettings = [];
        const chatbotKnowledgeSettingsPrefix = `${SETTINGS_UTILITIES_KEY_PREFIX}chatbot.kb_`;
        const chatbotKnowledgeSettings = [];
        try {
            // Check data existence
            for (const key of dataNotDefined) {
                if (diSettings[key][0]) {
                    dataNotDefined.splice(dataNotDefined.indexOf(key), 1);
                }
            }
            // Get data that aren't defined yet
            if (dataNotDefined.length) {
                const settings = await GetSettings(
                    LimbChatbot.rest.url,
                    LimbChatbot.rest.nonce,
                    {
                        key: [
                            ...dataNotDefined.map(item => chatbotKnowledgeSettingsPrefix + item),
                            chatbotSettingsPrefix + 'ai_provider_id',
                            chatbotSettingsPrefix + 'config_id',
                        ],
                    }
                );
                // Check knowledge settings
                if (settings?.length) {
                    for (const setting of settings) {
                        if (setting.key.indexOf(chatbotKnowledgeSettingsPrefix) !== -1) {
                            chatbotKnowledgeSettings.push(setting);
                        } else {
                            chatbotSettings.push(setting);
                        }
                    }
                }
            }
        } catch (e) {
            handleError(e);
        }

        // Check knowledge settings first
        if (chatbotKnowledgeSettings.length) {
            for (const item of chatbotKnowledgeSettings) {
                if (!item.value) {
                    continue;
                }
                const key = item.key.replace(chatbotKnowledgeSettingsPrefix, '');
                // Data found
                const index = dataNotDefined.indexOf(key);
                if (index !== -1) {
                    dataNotDefined.splice(index, 1);
                }
                // Update settings state
                diSettings[key][1](item.value);
                // Model ID
                if (key === 'ai_model_id') {
                    aiModelId = item.value;
                }
                // Vector index type
                if (key === 'vector_index_type') {
                    if (item.value === 'local') {
                        const index = dataNotDefined.indexOf('vector_index_config_id');
                        if (index !== -1) {
                            dataNotDefined.splice(index, 1);
                        }
                    }
                }
            }
        }

        // If all data defined then nothing to do
        if (!dataNotDefined.length) {
            setDIAIDefaultSettingsChecked(true);
            return;
        }

        // Check embedding utility settings
        const embeddingSettingsInfo = [
            {
                key: 'ai_model_id',
                defaultValue: '',
                validationKey: 'di_ai_model_id',
            },
            {
                key: 'config_id',
                defaultValue: '',
                validationKey: 'di_config_id',
            },
            {
                key: 'dimension',
                defaultValue: 1536,
                validationKey: 'dimension',
            }
        ];
        // Get the utility settings if it hasn't already
        if (!diDefaultSettings.current) {
            diDefaultSettings.current = await GetDefaultSettings(
                'embedding',
                embeddingSettingsInfo
            );
        }
        // Setup default values
        for (const item of embeddingSettingsInfo) {
            if (!diDefaultSettings.current[item.key]) {
                continue;
            }
            const index = dataNotDefined.indexOf(item.key);
            if (index !== -1) {
                dataNotDefined.splice(index, 1);
                // Update settings state
                const value = diDefaultSettings.current[item.key];
                diSettings[item.key][1](value);
                // Validate
                validateField(item.validationKey, value);
                // Model ID
                if (item.key === 'ai_model_id') {
                    aiModelId = value;
                }
            }
        }

        // If all data defined then nothing to do
        if (!dataNotDefined.length) {
            setDIAIDefaultSettingsChecked(true);
            return;
        }

        // Check chatbot settings if config not defined yet, as from the chatbot we can get only that one
        if (chatbotSettings.length && dataNotDefined.includes('config_id')) {
            const aiProviderId = chatbotSettings.find(item => item.key === chatbotSettingsPrefix + 'ai_provider_id')?.value;
            const configId = chatbotSettings.find(item => item.key === chatbotSettingsPrefix + 'config_id')?.value;
            if (configId) {
                // Check if the current config AI provider same as the DI model's AI provider
                if (aiModelId) {
                    try {
                        const res = await GetModel(LimbChatbot.rest.url, LimbChatbot.rest.nonce, aiModelId);
                        if (res?.id) {
                            if (res.ai_provider_id === aiProviderId) {
                                diSettings['config_id'][1](configId);
                                // Remove from dataNotDefined
                                dataNotDefined.splice(dataNotDefined.indexOf('config_id'), 1);
                            }
                        }
                    } catch (e) {
                        handleError(e);
                    }
                }
            }

        }

        // If all data defined then nothing to do
        if (!dataNotDefined.length) {
            setDIAIDefaultSettingsChecked(true);
            return;
        }

        // Check dimension
        const dimensionIndex = dataNotDefined.indexOf('dimension');
        if (dimensionIndex !== -1) {
            diSettings['dimension'][1](1536);
            dataNotDefined.splice(dimensionIndex, 1);
        }

        // Check storage type
        const vectorIndexType = dataNotDefined.indexOf('vector_index_type');
        if (vectorIndexType !== -1) {
            diSettings['vector_index_type'][1]('local');
            dataNotDefined.splice(vectorIndexType, 1);
            // Check storage config ID
            diSettings['vector_index_config_id'][1]('');
            const index = dataNotDefined.indexOf('vector_index_config_id');
            if (index !== -1) {
                dataNotDefined.splice(index, 1);
            }
        }

        // If all data defined then nothing to do
        if (!dataNotDefined.length) {
            setDIAIDefaultSettingsChecked(true);
            return;
        }

        handleWarning(null, notifications.set, {
            title: __("Knowledge settings are missing.", 'limb-chatbot'),
            description: sprintf(
                __("Please check the %s to be able to run learn process.", 'limb-chatbot'),
                `<a href="${getNavLinkTo('knowledge', 'settings')}">${__("Knowledge Settings", 'limb-chatbot')}<svg class="lbaic-settings-table-card-body-i lbaic-settings-table-card-body-input-i" fill="none" viewBox="0 0 24 24"><use href="#lbaic-settings-external-arrow" class="lbaic-settings-external-arrow"/><use href="#lbaic-external-box"/></svg></a>`
            ),
        });

        // Now all available things are defined
        setDIAIDefaultSettingsChecked(true);
    }

    return <>
        <div className='lbaic-settings-a-ei-body'>
            <div className='lbaic-settings-column'>
                <div className='lbaic-settings-column-in'>
                    <Dropdown
                        value={dgSettings['post_type'][0]}
                        setValue={dgSettings['post_type'][1]}
                        options={postTypes}
                        placeholder={__("Post type", 'limb-chatbot')}
                        validate={(value) => validateField('post_type', value)}
                        errorMessage={errors['post_type'][0]}
                        disabled={saving}
                        iconClassName='lbaic-settings-dropdown-kb-icon'
                    />
                    <Description>{__("Select which WordPress content type to choose from.", 'limb-chatbot')} <a href="https://wpaichatbot.com/documentation/teaching-your-chatbot/recommending-custom-post-types/#collecting-recommendations" target="_blank">{__("Learn more", 'limb-chatbot')}</a></Description>
                </div>
                <div className='lbaic-settings-column-in'></div>
            </div>
            {Boolean(dgSettings['post_type'][0]) && (
                <div className='lbaic-settings-column'>
                    <div className='lbaic-settings-column-in'>
                        <WPObjectsMultiSelect
                            limit={dgSettings['limit'][0]}
                            setLimit={dgSettings['limit'][1]}
                            isDataSet={isDataSet}
                            postType={dgSettings['post_type'][0]}
                            validateField={(value) => validateField('limit', value)}
                            errors={errors['limit'][0]}
                            setLoading={setLoading}
                            saving={saving}
                            addNotification={notifications.set}
                            showAllOption={true}
                        />
                        <Description>{__("Select the specific posts to use for recommendations.", 'limb-chatbot')} <a href="https://wpaichatbot.com/documentation/teaching-your-chatbot/recommending-custom-post-types/#collecting-recommendations" target="_blank">{__("Learn more", 'limb-chatbot')}</a></Description>
                    </div>
                </div>
            )}
        </div>
    </>
});

export default AddEdit;