import {useCallback, useEffect, useMemo, useRef, useState} from "@wordpress/element";
import {__, sprintf} from "@wordpress/i18n";
import {handleError, handleWarning} from "../../../../../../../helpers/notifications";
import {handlePaginationAfterDeletion} from "../../../../../../../helpers";
import {useBackgroundProcess} from "../../../../../../contexts/background-process";
import AddSources from "../add-sources";
import Checkbox from "../../../../../../fields/checkbox";
import Footer from "../footer";
import FilterForm from "../filter";
import {GetJobs, GetJob, CancelJob} from "../../../../../../../rest/jobs";
import Table from "../../../../../../sections/table";
import Empty from "../../../../containers/content/empty";
import ContentLoading from "../../../../containers/content/loading";
import ContentBodyInner from "../../../../containers/content/body-inner";
import Container from "../../../../containers/content/container";
import confirm from "../../../../../../../helpers/confirm";
import useUnsavedChangesWarning from "../../../../../components/unsaved-changes-warning";
import {DATASET_STATUS_INDEXED, DATASET_TYPE_KNOWLEDGE, GetDatasets} from "../../../../../../../rest/datasets";
import {parseDateString} from "../../../../../../../../helpers";
import {renderName, renderSource, renderStorage, renderAIProvider, isOldRow} from "../_renderers";
import RealTimeRelativeTime from "../../../../../../fields/real-time-relative-time";
import {
    DGEN_PROCESS_KEY_PREFIX,
    DIN_PROCESS_KEY_PREFIX
} from "../../../../../../../data/bg-processes";
import {DATASET_PROCESSES_INFO} from "../_data";
import DSDetails from "../row-details";
import DatasetEntries from "../_components/dataset-entries";
import DatasetSourceContentEditor from "../_components/dataset-source-content-editor";
import DatasetRowActions from "../row-actions";
import {
    NOTIF_AUTOCLOSE_TIMEOUT,
    NOTIF_QUICK_AUTOCLOSE_TIMEOUT,
    NOTIF_ERROR_AUTOCLOSE_TIMEOUT
} from "../../../../../../containers/notification-layout";
import Chip from "../../../../../../fields/chip";
import {GetSettings, GetSetting, DeleteSetting} from "../../../../../../../rest/settings";
import {SETTINGS_UTILITIES_KEY_PREFIX} from "../../../../../../../data/settings";
import {GetModel} from "../../../../../../../rest/models";
import SitemapLinksPopup from "../_components/sitemap-links-popup";

/**
 * Check is all selected
 *
 * @param {number[]|string[]} selectedItems Selected items IDs (can include "all")
 * @param {object[]} datasets Datasets
 * @return {boolean}
 */
const checkIsAllSelected = (selectedItems, datasets) => {
    // Handle "all" state
    if (selectedItems.length === 1 && selectedItems[0] === "all") {
        return true;
    }

    const selected = datasets.every(item => selectedItems.includes(item.id));
    let all = selectedItems.length > 0;
    if (datasets.length > 0) {
        all &&= selected;
    }

    return all;
}

/**
 * Check is partially selected
 *
 * @param {number[]|string[]} selectedItems Selected items IDs (can include "all")
 * @param {object[]} datasets Datasets
 * @return {boolean}
 */
const checkIsPartiallySelected = (selectedItems, datasets) => {
    // "all" state is not considered partial
    if (selectedItems.length === 1 && selectedItems[0] === "all") {
        return false;
    }
    return datasets.some(item => selectedItems.includes(item.id));
}

/**
 * Is row disabled
 *
 * @param {object} row Row data
 * @param {number} index Row index
 * @param {object} processState Process state
 * @return {boolean}
 */
const isRowDisabled = (row, index, processState) => {
    // Case 1: Row is part of ongoing dataset_sync or dataset_delete process
    if (processState?.datasetIds?.includes(row.id)) {
        return true;
    }

    // Case 2: If the row isn't yet indexed, it is still invalid
    if (row.status !== DATASET_STATUS_INDEXED) {
        return true;
    }

    return false;
}

/**
 * Check if notifications have errors
 *
 * @param {object} notifications Notifications context
 * @return {boolean}
 */
const hasNotificationErrors = (notifications) => {
    return notifications.get.some(notification => notification.type === 'error');
}

export default function KnowledgeDSItems({notifications}) {
    // Get background process context
    const {
        // Process state
        processState,

        // Progress states
        generatingProgress,
        syncingProgress,
        deletingProgress,

        // Loading states
        saving,
        pausing,
        canceling,
        preparing,
        syncing,
        deleting,

        // Functions from context
        checkActiveProcess,
        pauseProcess,
        resumeProcess,
        cancelProcess,
        setProcessState,
        createJob,
        followJob,

        // Setters from context
        setGeneratingProgress,
        setSyncingProgress,
        setDeletingProgress,
        setSaving,
        setPausing,
        setCanceling,
        setPreparing,
        setSyncing,
        setDeleting,
    } = useBackgroundProcess();

    // Local component states
    const [isDataFetched, setIsDataFetched] = useState(false);
    const [showLoading, setShowLoading] = useState(false);
    const [datasetIdForDetails, setDatasetIdForDetails] = useState(false);
    const [loading, setLoading] = useState(0);
    const [filteringLoading, setFilteringLoading] = useState(false);
    const [formData, setFormData] = useState(null);
    const [addingEditing, setAddingEditing] = useState(false);
    const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
    // Data
    const giFormRef = useRef(null);
    const [datasetsPagination, setDatasetsPagination] = useState({
        page: 1,
        perPage: LimbChatbot.screen_options?.lbaic_items_per_page || 10,
        total: 0
    });
    const [datasetsOrder, setDatasetsOrder] = useState({
        orderBy: 'updated_at',
        order: 'desc',
    });
    const [datasets, setDatasets] = useState([]);
    const [selectedDSItems, setSelectedDSItems] = useState([]);
    const [kbAIProviderId, setKbAIProviderId] = useState(null);

    // Filter states
    const [showFilter, setShowFilter] = useState(false);
    const [filterSearch, setFilterSearch] = useState('');
    const [filterSourceType, setFilterSourceType] = useState(null);
    const [filterSynced, setFilterSynced] = useState(null);
    const [filterAIProviderId, setFilterAIProviderId] = useState(null);
    const [filterVectorIndexId, setFilterVectorIndexId] = useState(null);

    // Sitemap states
    const [showSitemapPopup, setShowSitemapPopup] = useState(false);
    const [sitemapUrls, setSitemapUrls] = useState([]);
    const [sitemapJobId, setSitemapJobId] = useState(null);
    const [sitemapJobRunning, setSitemapJobRunning] = useState(false);
    const [sitemapJobProgress, setSitemapJobProgress] = useState(0);
    const [sitemapJobError, setSitemapJobError] = useState(false);

    // Check unsaved changes before leaving the page
    useUnsavedChangesWarning((formData?.id && hasUnsavedChanges) || saving);

    /**
     * Auto close notifications
     */
    const autoCloseNotifications = useCallback((timeout = NOTIF_AUTOCLOSE_TIMEOUT) => {
        notifications.set(prevState => prevState.map(item => ({
            ...item,
            autoClose: true,
            timeout,
        })));
    }, [notifications]);


    /**
     * Fetch chatbot AI provider setting
     */
    const fetchKnowledgeSettingsAIProvider = useCallback(async () => {
        try {
            const settings = await GetSettings(
                LimbChatbot.rest.url,
                LimbChatbot.rest.nonce,
                {
                    key: [`${SETTINGS_UTILITIES_KEY_PREFIX}chatbot.kb_ai_model_id`, `${SETTINGS_UTILITIES_KEY_PREFIX}embedding.default.ai_provider_id`],
                }
            );
            // Check knowledge AI model
            const kbAIModelId = settings?.find(setting => setting.key === `${SETTINGS_UTILITIES_KEY_PREFIX}chatbot.kb_ai_model_id`)?.value;
            if (kbAIModelId) {
                const model = await GetModel(LimbChatbot.rest.url, LimbChatbot.rest.nonce, kbAIModelId);
                if (model?.id) {
                    setKbAIProviderId(model.ai_provider_id);
                    return;
                }
            }
            // Check embedding utility default ai provider
            const embeddingAIProviderId = settings?.find(setting => setting.key === `${SETTINGS_UTILITIES_KEY_PREFIX}embedding.default.ai_provider_id`)?.value;
            if (embeddingAIProviderId) {
                setKbAIProviderId(embeddingAIProviderId);
            }
        } catch (e) {
            // Handle error silently or log it
            handleError(e);
        }
    }, []);

    useEffect(() => {
        // Always check page query params
        checkPageQueryParams();
        // Hide all permanent notifications
        autoCloseNotifications(NOTIF_QUICK_AUTOCLOSE_TIMEOUT);
        // Fetch chatbot AI provider
        fetchKnowledgeSettingsAIProvider();
        // Fetch datasets and check for ongoing processes
        fetchDSItems(1, datasetsPagination.perPage, datasetsOrder);
        checkForOngoingProcesses();

        return () => {
            // Hide all permanent notifications
            autoCloseNotifications(NOTIF_QUICK_AUTOCLOSE_TIMEOUT)
        }
    }, []);

    useEffect(() => {
        // Fetch when order changes
        fetchDSItems(1, datasetsPagination.perPage, datasetsOrder);
    }, [datasetsOrder]);

    // Check if any filters are active
    const hasActiveFilters = useMemo(() => {
        return !!(
            filterSearch ||
            filterSourceType !== null ||
            filterSynced !== null ||
            filterAIProviderId !== null ||
            filterVectorIndexId !== null
        );
    }, [filterSearch, filterSourceType, filterSynced, filterAIProviderId, filterVectorIndexId]);

    // Memoize computed selection states
    const isAllSelected = useMemo(() =>
            checkIsAllSelected(selectedDSItems, datasets),
        [selectedDSItems, datasets]
    );
    const isPartiallySelected = useMemo(() =>
            !isAllSelected && checkIsPartiallySelected(selectedDSItems, datasets),
        [isAllSelected, selectedDSItems, datasets]
    );

    // Check if all items on current page are selected (not including "all" state)
    const isAllPageSelected = useMemo(() => {
        if (selectedDSItems.length === 1 && selectedDSItems[0] === "all") {
            return true;
        }
        if (datasets.length === 0) {
            return false;
        }
        return datasets.every(item => selectedDSItems.includes(item.id));
    }, [selectedDSItems, datasets]);

    // Current page items count
    const currentPageItemsCount = useMemo(() => {
        return datasets.length;
    }, [datasets]);

    // Auto-convert to "all" state when all page items are selected and total equals page items
    useEffect(() => {
        const isAllState = selectedDSItems.length === 1 && selectedDSItems[0] === "all";
        if (isAllPageSelected && !isAllState) {
            const totalCount = datasetsPagination.total;
            if (totalCount === currentPageItemsCount && currentPageItemsCount > 0) {
                // All items are selected and there are no more items beyond current page
                // Automatically set to "all" state for consistency
                setSelectedDSItems(["all"]);
            }
        }
    }, [isAllPageSelected, selectedDSItems.length, datasetsPagination.total, currentPageItemsCount]);

    /**
     * Handle Import Sitemap button click
     */
    const handleLoadSitemap = useCallback(async () => {
        // Validate sitemap URL
        if (!giFormRef.current?.validateSitemap()) {
            return;
        }

        const sitemapUrl = giFormRef.current?.getSitemapUrl();
        if (!sitemapUrl) {
            return;
        }

        // Open popup
        setShowSitemapPopup(true);
        setSitemapUrls([]);
        setSitemapJobProgress(0);
        setSitemapJobRunning(true);
        setSitemapJobError(false);

        try {
            // Create sitemap scrape job
            const job = await createJob({
                type: 'sitemap_scrape',
                sub_type: 'informational',
                config: {
                    chatbot_uuid: null,
                    sitemap_url: sitemapUrl
                },
                chatbot_uuid: 'default'
            });

            setSitemapJobId(job.id);

            // Follow the job
            await followJob({
                jobId: job.id,
                processType: 'sitemap_scrape',
                setProgressState: (progress) => {
                    setSitemapJobProgress(progress);
                },
                onProgressUpdate: async () => {
                    // Fetch sitemap URLs from settings
                    try {
                        const sitemapUrlsSetting = await GetSetting(
                            LimbChatbot.rest.url,
                            LimbChatbot.rest.nonce,
                            `${SETTINGS_UTILITIES_KEY_PREFIX}chatbot.sitemap_urls`
                        );
                        if (sitemapUrlsSetting?.value && Array.isArray(sitemapUrlsSetting.value)) {
                            setSitemapUrls(sitemapUrlsSetting.value);
                        }
                    } catch (e) {
                        handleError(e, notifications.set, {
                            title: __("Failed to fetch sitemap URLs.", 'limb-chatbot'),
                            description: e.message ? __(e.message, 'limb-chatbot') : __("Please try again.", 'limb-chatbot'),
                        });
                    }
                },
                onComplete: async () => {
                    setSitemapJobRunning(false);
                    // Fetch final sitemap URLs
                    try {
                        await DeleteSetting(LimbChatbot.rest.url, LimbChatbot.rest.nonce, `${SETTINGS_UTILITIES_KEY_PREFIX}chatbot.sitemap_urls`);
                    } catch (e) {
                        handleError(e);
                    }
                    // Auto-close notifications
                    const timeout = hasNotificationErrors(notifications) ? NOTIF_ERROR_AUTOCLOSE_TIMEOUT : NOTIF_AUTOCLOSE_TIMEOUT;
                    autoCloseNotifications(timeout);
                },
                resetProgress: () => {
                    setSitemapJobProgress(0);
                },
                statusMessages: {
                    complete: __("Sitemap loaded successfully.", 'limb-chatbot'),
                    pause: __("Sitemap loading has been paused.", 'limb-chatbot'),
                    cancel: __("Sitemap loading has been cancelled.", 'limb-chatbot')
                }
            });
        } catch (e) {
            setSitemapJobRunning(false);
            setSitemapJobError(true);
            handleError(e, notifications.set, {
                title: __("Failed to import sitemap.", 'limb-chatbot'),
                description: e.message ? __(e.message, 'limb-chatbot') : __("Please check the URL and try again.", 'limb-chatbot'),
            });
        }
    }, [notifications, createJob, followJob, autoCloseNotifications]);

    /**
     * Handle sitemap popup close
     */
    const handleCloseSitemapPopup = useCallback(async () => {
        // Cancel job if still running
        if (sitemapJobRunning && sitemapJobId) {
            try {
                await CancelJob(sitemapJobId);
            } catch (e) {
                handleError(e);
            }
        }
        setShowSitemapPopup(false);
        setSitemapUrls([]);
        setSitemapJobId(null);
        setSitemapJobRunning(false);
        setSitemapJobProgress(0);
        setSitemapJobError(false);
    }, [sitemapJobRunning, sitemapJobId]);

    /**
     * Handle sitemap URL deletion from popup
     */
    const handleDeleteSitemapUrl = useCallback((updatedUrls) => {
        setSitemapUrls(updatedUrls);
    }, []);

    /**
     * Generic function to run any dataset process
     * Creates a job and follows it
     *
     * @param {string} processType Process type (dataset_generating, dataset_sync, dataset_delete)
     * @param {number[]} ids Dataset IDs (optional for form-based processes)
     * @param {object} jobConfig Job configuration object
     * @param {function} followFunction Function to follow the process
     * @param {function} setLoadingFunction Function to set loading state
     * @param {function} cleanupFunction Function to clean up after process
     * @return {Promise<void>}
     */
    const runDatasetProcess = useCallback(async (processType, ids = null, jobConfig = null, followFunction, setLoadingFunction, cleanupFunction = null) => {
        // Check if process is active
        if (!checkActiveProcess(processType)) {
            return;
        }

        // Set loading state
        setLoadingFunction(ids ? [...ids] : true);

        try {
            // Set preparing state to disable all buttons
            setPreparing(true);

            // Create job
            const jobData = {
                type: processType,
                sub_type: 'informational', // Sources page
                config: jobConfig || {},
                chatbot_uuid: 'default'
            };

            // Add dataset_ids for dataset-based processes
            if (ids && ids.length > 0) {
                // Convert ["all"] to string "all" for dataset_ids
                if (ids.length === 1 && ids[0] === "all") {
                    jobData.config.dataset_ids = "all";
                } else {
                    jobData.config.dataset_ids = ids;
                }
            }

            const job = await createJob(jobData);

            setProcessState(null);

            // Wait 800ms before starting to follow the process
            setTimeout(async () => {
                setPreparing(false); // Clear preparing state when process actually starts
                await followFunction(job.id, ids);
                if (cleanupFunction) {
                    cleanupFunction();
                }
                setLoadingFunction(ids ? [] : false);
            }, 800);

        } catch (e) {
            setPreparing(false); // Clear preparing state on error
            handleError(e, notifications.set, {
                title: sprintf(__("Failed to start %s process.", 'limb-chatbot'), DATASET_PROCESSES_INFO[processType].title.toLowerCase()),
                description: e.message ? __(e.message, 'limb-chatbot') : __("Please check and try again.", 'limb-chatbot'),
            });
            setLoadingFunction(ids ? [] : false);
        }
    }, [checkActiveProcess, setPreparing, createJob, setProcessState, notifications]);

    /**
     * Toggle generating/indexing form
     *
     * @param {boolean} show Show
     */
    const toggleGIForm = useCallback((show) => {
        setAddingEditing(show);
    }, []);

    /**
     * Cancel form (close the add/edit form)
     */
    const cancelForm = useCallback(() => {
        toggleGIForm(false);
    }, [toggleGIForm]);

    /**
     * Show from to add datasets
     */
    const showAddDatasetsForm = () => {
        setShowLoading(true);
        setFormData(null);
        toggleGIForm(true);
    }

    /**
     * Toggle filter form
     */
    const toggleFilterForm = useCallback(() => {
        setShowFilter(prev => !prev);
    }, []);

    /**
     * Discard all filters (reset filter values)
     */
    const discardFilters = useCallback(() => {
        setFilterSearch('');
        setFilterSourceType(null);
        setFilterSynced(null);
        setFilterAIProviderId(null);
        setFilterVectorIndexId(null);
    }, []);

    /**
     * Check page query params
     */
    const checkPageQueryParams = useCallback(() => {
        const searchParams = new URLSearchParams(window.location.search);
        // Dataset ID
        const datasetId = searchParams.get('item_id');
        if (datasetId) {
            setDatasetIdForDetails(+datasetId);
        } else {
            setDatasetIdForDetails(false);
        }
    }, []);

    /**
     * Get datasets
     */
    const fetchDSItems = useCallback(async (page, perPage, params = {}, showLoading = true, settings = null) => {
        showLoading && setLoading(prev => prev + 1);
        try {
            // Request params
            const orderby = params.orderBy || 'updated_at';
            const order = params.order || 'desc';
            const reqParams = {
                page: page,
                per_page: perPage,
                orderby,
                order,
                type: DATASET_TYPE_KNOWLEDGE,
                include: ['metas', 'storage', 'source_object', 'source_url', 'ai_provider_id', 'dataset_entries_count']
            }
            // Add filter params
            if (filterSearch) {
                reqParams.search = filterSearch;
                reqParams.search_fields = ['name'];
            }
            if (filterSourceType) {
                reqParams.source_type = filterSourceType;
            }
            if (filterSynced !== null) {
                reqParams.synced = filterSynced;
            }
            if (filterAIProviderId) {
                reqParams.ai_provider_id = filterAIProviderId;
            }
            if (filterVectorIndexId) {
                reqParams.vector_index_type = filterVectorIndexId;
            }
            // Include storage, source_object if not skipped
            if (settings?.skipInclude?.length) {
                reqParams.include = reqParams.include.filter(item => !settings.skipInclude.includes(item));
            }
            // Get datasets
            const res = await GetDatasets(LimbChatbot.rest.url, LimbChatbot.rest.nonce, reqParams);
            // Update datasets state
            if (res.items?.length) {
                if (settings?.skipInclude?.length) {
                    // Merge first-level data with existing once to not lose storage and source_object
                    setDatasets(prevState => res.items.map(item => {
                        const found = prevState.find(di => di.id === item.id);
                        if (found) {
                            return {
                                ...found,
                                ...item
                            };
                        }

                        return item;
                    }));
                } else {
                    setDatasets(res.items);
                }
            } else {
                setDatasets([]);
            }
            // Pagination state
            setDatasetsPagination(prevState => ({
                ...prevState,
                page: page,
                perPage: perPage,
                total: +res.total,
            }));
            // Table ordering state - use functional update to compare with current state, not stale closure
            setDatasetsOrder(prevState => {
                if (prevState.orderBy !== orderby || prevState.order !== order) {
                    return {
                        ...prevState,
                        orderBy: orderby,
                        order,
                    };
                }
                return prevState; // Return same reference to prevent re-render
            });

            const activeProcessDatasetIds = processState?.datasetIds || [];
            setSelectedDSItems(prevState => {
                // Preserve "all" state
                if (prevState.length === 1 && prevState[0] === "all") {
                    return prevState;
                }
                return prevState.filter(item => res.items.findIndex(i => i.id === item) !== -1 || activeProcessDatasetIds.includes(item));
            });
        } catch (e) {
            handleError(e, notifications.set, {
                title: __("Failed to retrieve items data.", 'limb-chatbot'),
                description: e.message ? __(e.message, 'limb-chatbot') : __("Please check your connection and try again.", 'limb-chatbot'),
            });
        }
        setIsDataFetched(true);
        showLoading && setLoading(prev => prev - 1);
    }, [notifications, filterSearch, filterSourceType, filterSynced, filterAIProviderId, filterVectorIndexId]);

    /**
     * Apply filters (fetch datasets with current filter values)
     */
    const applyFilters = useCallback(async () => {
        setFilteringLoading(true);
        try {
            await fetchDSItems(1, datasetsPagination.perPage, datasetsOrder, false);
        } finally {
            setFilteringLoading(false);
        }
    }, [fetchDSItems, datasetsPagination.perPage, datasetsOrder]);

    /**
     * Toggle dataset entries
     *
     * @param {number} id Dataset ID
     */
    const toggleDatasetEntries = useCallback((id = false) => {
        const url = new URL(window.location);
        if (id) {
            // Open
            url.searchParams.set('item_id', id);
            setDatasetIdForDetails(id);
        } else {
            // Close
            url.searchParams.delete('item_id');
            setDatasetIdForDetails(false);
            // Update datasets state
            fetchDSItems(datasetsPagination.page, datasetsPagination.perPage, datasetsOrder);
        }
        window.history.pushState({}, '', url);
    }, [fetchDSItems, datasetsPagination.page, datasetsPagination.perPage, datasetsOrder]);

    /**
     * Toggle dataset source content editor
     *
     * @param {number} id Dataset ID
     */
    const toggleDatasetSourceContentEditor = useCallback((id = false) => {
        const url = new URL(window.location);
        if (id) {
            // Open
            url.searchParams.set('item_id', id);
            setDatasetIdForDetails(id);
        } else {
            // Close
            url.searchParams.delete('item_id');
            setDatasetIdForDetails(false);
            // Update datasets state
            fetchDSItems(datasetsPagination.page, datasetsPagination.perPage, datasetsOrder);
        }
        window.history.pushState({}, '', url);
    }, [fetchDSItems, datasetsPagination.page, datasetsPagination.perPage, datasetsOrder]);

    /**
     * Set bg process form state from job config
     *
     * @param {object} jobConfig Job config object (required)
     * @param {string} processType Process type (for determining which config keys to use)
     * @return {Promise<void>}
     */
    const setBgProcessFormState = async (jobConfig, processType = null) => {
        if (!jobConfig) {
            return;
        }

        try {
            const data = {
                generating: {},
                indexing: {},
            };

            // Extract form data from job config
            // Generating config
            if (jobConfig.source !== undefined) data.generating.source = jobConfig.source;
            if (jobConfig.post_type !== undefined) data.generating.post_type = jobConfig.post_type;
            if (jobConfig.limit !== undefined) data.generating.limit = jobConfig.limit;
            if (jobConfig.files !== undefined) data.generating.files = jobConfig.files;
            if (jobConfig.q_a !== undefined) data.generating.q_a = jobConfig.q_a;
            if (jobConfig.text !== undefined) data.generating.text = jobConfig.text;
            if (jobConfig.ai_model_id !== undefined) data.generating.ai_model_id = jobConfig.ai_model_id;
            if (jobConfig.config_id !== undefined) data.generating.config_id = jobConfig.config_id;

            // Indexing config (from indexing_ prefixed keys)
            if (jobConfig.indexing_ai_model_id !== undefined) {
                data.indexing.ai_model_id = jobConfig.indexing_ai_model_id;
            }
            if (jobConfig.indexing_config_id !== undefined) {
                data.indexing.config_id = jobConfig.indexing_config_id;
            }
            if (jobConfig.indexing_dimension !== undefined) {
                data.indexing.dimension = jobConfig.indexing_dimension;
            } else if (jobConfig.dimension !== undefined) {
                data.indexing.dimension = jobConfig.dimension;
            }
            if (jobConfig.indexing_vector_index_type !== undefined) {
                data.indexing.vector_index_type = jobConfig.indexing_vector_index_type;
            } else if (jobConfig.vector_index_type !== undefined) {
                data.indexing.vector_index_type = jobConfig.vector_index_type;
            }
            if (jobConfig.indexing_vector_index_config_id !== undefined) {
                data.indexing.vector_index_config_id = jobConfig.indexing_vector_index_config_id;
            } else if (jobConfig.vector_index_config_id !== undefined) {
                data.indexing.vector_index_config_id = jobConfig.vector_index_config_id;
            }
            if (jobConfig.vector_index_id !== undefined) {
                data.indexing.vector_index_id = jobConfig.vector_index_id;
            }

            setFormData(data);
        } catch (e) {
            handleError(e);
        }
    }

    /**
     * Handle dataset-based processes (sync, delete)
     */
    const handleDatasetBasedProcess = async (processType, status, datasetIds, startedAt, startedFrom = null, jobId = null) => {
        if (!datasetIds?.length) {
            handleError({
                message: `The ${processType} process isn't complete, but there are no items to continue.`,
                data: datasetIds,
            }, notifications.set, {
                title: sprintf(__("%s process invalid state.", 'limb-chatbot'), DATASET_PROCESSES_INFO[processType].title),
                description: sprintf(__("%s process isn't complete, but there are no items to continue.", 'limb-chatbot'), DATASET_PROCESSES_INFO[processType].title),
            });
            return;
        }

        setSelectedDSItems([...datasetIds]);

        if (status === 'pause') {
            setProcessState({
                type: processType,
                status: 'pause',
                startedAt,
                datasetIds,
                started_from: startedFrom,
                jobId: jobId,
            });
            handleWarning({
                message: `The ${processType} process has been paused.`,
                data: datasetIds
            }, notifications.set, {
                title: sprintf(__("%s process has been paused.", 'limb-chatbot'), DATASET_PROCESSES_INFO[processType].title),
                description: __("Please continue the process to complete.", 'limb-chatbot'),
            });
        } else {
            // Set appropriate state and follow process
            const setterMap = {
                'dataset_sync': setSyncing,
                'dataset_delete': setDeleting,
            };
            const followMap = {
                'dataset_sync': followSyncBackgroundProcess,
                'dataset_delete': followDeleteBackgroundProcess,
            };

            // Set the process state
            setProcessState({
                type: processType,
                status: 'start',
                startedAt,
                datasetIds,
                started_from: startedFrom,
                jobId: jobId,
            });
            setterMap[processType]([...datasetIds]);
            await followMap[processType](jobId, [...datasetIds]);
            setSelectedDSItems([]);
            setterMap[processType]([]);
        }
    };

    /**
     * Handle form-based processes (generating)
     */
    const handleFormBasedProcess = async (processType, status, datasetIds = null, startedAt, startedFrom = null, jobId = null) => {
        // Set state of the form from job config (only if we have a jobId)
        if (jobId) {
            try {
                const job = await GetJob(jobId);
                if (job?.config) {
                    await setBgProcessFormState(job.config, processType);
                }
            } catch (e) {
                handleError(e);
            }
        }
        // Show the form if dataset generating process is going
        if (processType === 'dataset_generating') {
            toggleGIForm(true);
        }

        setProcessState({
            type: processType,
            status,
            startedAt,
            datasetIds,
            started_from: startedFrom,
            jobId: jobId,
        });

        if (status === 'start' && jobId) {
            // Follow the appropriate process
            // Note: saving state is managed by onComplete callbacks, not here
            if (processType === 'dataset_generating') {
                await followDatasetGeneratingBackgroundProcess(jobId);
            }
        }
    };

    /**
     * Check to follow ongoing processes
     * Uses GET /jobs endpoint with sub_type filter
     *
     * @return {Promise<void>}
     */
    const checkForOngoingProcesses = async () => {
        setLoading(prev => prev + 1);
        try {
            // Get jobs for this page (sub_type = 'sources')
            const res = await GetJobs({
                chatbot_uuid: 'default',
                sub_type: 'informational'
            });
            setLoading(prev => prev - 1);

            if (!res?.items?.length) {
                return;
            }

            // Process each active job
            for (const job of res.items) {
                const jobStatus = job.status;
                const jobType = job.type;
                const progress = job.progress_percent || 0;
                const datasetIds = job.config?.dataset_ids || null;
                const startedAt = job.started_at ? parseDateString(job.started_at) : null;

                // Only process jobs that are active or paused
                if (jobStatus !== 'processing' &&
                    jobStatus !== 'generating_tasks' &&
                    jobStatus !== 'pending' &&
                    jobStatus !== 'paused') {
                    continue;
                }

                // Set progress state
                switch (jobType) {
                    case 'dataset_generating':
                        setGeneratingProgress(progress);
                        break;
                    case 'dataset_sync':
                        setSyncingProgress(progress);
                        break;
                    case 'dataset_delete':
                        setDeletingProgress(progress);
                        break;
                }

                // Map job status to process status
                const processStatus = jobStatus === 'paused' ? 'pause' :
                    (jobStatus === 'processing' || jobStatus === 'generating_tasks' || jobStatus === 'pending') ? 'start' : null;

                if (!processStatus) {
                    continue;
                }

                // Handle based on process type
                if (jobType === 'dataset_generating') {
                    // Form-based process
                    await handleFormBasedProcess(jobType, processStatus, datasetIds, startedAt, null, job.id);
                } else {
                    // Dataset-based process (sync, delete)
                    await handleDatasetBasedProcess(jobType, processStatus, datasetIds, startedAt, null, job.id);
                }
            }
        } catch (e) {
            setLoading(prev => prev - 1);
            handleError(e);
        }
    }

    /**
     * Check/Uncheck all datasets
     */
    const toggleAll = () => {
        if (processState?.type) {
            return;
        }
        const isAllState = selectedDSItems.length === 1 && selectedDSItems[0] === "all";
        if (isAllSelected || isAllState) {
            setSelectedDSItems([]);
        } else {
            setSelectedDSItems([...datasets.map(item => item.id)]);
        }
    }

    /**
     * Toggle select all / clear selection
     */
    const toggleSelectAll = () => {
        if (processState?.type) {
            return;
        }
        const isAllState = selectedDSItems.length === 1 && selectedDSItems[0] === "all";
        if (isAllState) {
            setSelectedDSItems([]);
        } else {
            setSelectedDSItems(["all"]);
        }
    }

    /**
     * Helper function to convert form settings data to job config
     *
     * @param {object[]} settingsData Settings data from form
     * @return {object} Job config object
     */
    const convertFormDataToJobConfig = useCallback((settingsData) => {
        const config = {};

        // Extract generating config
        const generatingSettings = settingsData.filter(item => item.key.startsWith(DGEN_PROCESS_KEY_PREFIX));
        generatingSettings.forEach(setting => {
            const key = setting.key.replace(DGEN_PROCESS_KEY_PREFIX, '');
            config[key] = setting.value;
        });

        // Extract indexing config
        const indexingSettings = settingsData.filter(item => item.key.startsWith(DIN_PROCESS_KEY_PREFIX));
        indexingSettings.forEach(setting => {
            const key = setting.key.replace(DIN_PROCESS_KEY_PREFIX, '');
            // Map indexing config keys with indexing_ prefix for generating job
            if (key === 'ai_model_id') {
                config.indexing_ai_model_id = setting.value;
            } else if (key === 'config_id') {
                config.indexing_config_id = setting.value;
            } else if (key === 'dimension') {
                config.indexing_dimension = setting.value;
            } else if (key === 'vector_index_type') {
                config.indexing_vector_index_type = setting.value;
            } else if (key === 'vector_index_config_id') {
                config.indexing_vector_index_config_id = setting.value;
            }
        });

        return config;
    }, []);

    /**
     * Follow delete background process
     *
     * @param {number} jobId Job ID
     * @param {number[]} datasetIds Dataset IDs being processed
     * @param {object} params More params
     * @return {Promise<void>}
     */
    const followDeleteBackgroundProcess = async (jobId, datasetIds, params = null) => {
        await followJob({
            jobId,
            processType: 'dataset_delete',
            setProgressState: (v) => {
                setDeletingProgress(v);
            },
            onComplete: async () => {
                setProcessState(null);
                setDeleting([]);
                // Refresh datasets list to remove deleted items
                await fetchDSItems(datasetsPagination.page, datasetsPagination.perPage, datasetsOrder, false);
                // Handle pagination after deletion
                handlePaginationAfterDeletion({
                    currentItemsCount: datasets.length,
                    deletedCount: datasetIds.length,
                    pagination: datasetsPagination,
                    fetchFunction: fetchDSItems,
                    order: datasetsOrder,
                    setItems: setDatasets,
                    setPagination: setDatasetsPagination,
                    deletedIds: datasetIds
                });
                // Hide all notifications with appropriate timeout based on errors
                const timeout = hasNotificationErrors(notifications) ? NOTIF_ERROR_AUTOCLOSE_TIMEOUT : NOTIF_AUTOCLOSE_TIMEOUT;
                autoCloseNotifications(timeout);
            },
            onProgressUpdate: async () => {
                // Update table data during deletion
                await fetchDSItems(datasetsPagination.page, datasetsPagination.perPage, datasetsOrder, false, {
                    skipInclude: ['storage', 'source_object', 'source_url'],
                });
            },
            resetProgress: () => setDeletingProgress(false),
            statusMessages: {
                complete: DATASET_PROCESSES_INFO['dataset_delete'].success,
                pause: sprintf(__("%s process has been paused.", 'limb-chatbot'), DATASET_PROCESSES_INFO['dataset_delete'].processName),
                cancel: sprintf(__("%s process has been cancelled.", 'limb-chatbot'), DATASET_PROCESSES_INFO['dataset_delete'].processName)
            }
        });
    }

    const runDatasetDelete = async (ids) => {
        if (!ids?.length) {
            return;
        }
        await runDatasetProcess(
            'dataset_delete',
            ids,
            {}, // No additional config needed for delete
            followDeleteBackgroundProcess,
            setDeleting,
            () => setSelectedDSItems([])
        );
    }

    /**
     * Delete dataset
     *
     * @param {object} dataset Dataset
     * @param {number} index Index
     */
    const deleteDSItem = async (dataset, index) => {
        if (await confirm(__("Are you sure you want to delete the item?", 'limb-chatbot'))) {
            // Add to selected items to show footer buttons
            setSelectedDSItems([dataset.id]);
            runDatasetDelete([dataset.id]);
        }
    }

    /**
     * Delete selected datasets
     */
    const deleteSelected = async () => {
        // Check if there's a paused process to resume
        if (processState && processState.status === 'pause') {
            const isDeleteProcess = processState.type === 'dataset_delete';

            if (isDeleteProcess) {
                // Resume the paused delete process (don't create a new job)
                await resumeProcess();
                return;
            }
        }

        // Normal flow: confirm and delete
        if (selectedDSItems.length > 0 && await confirm(__("Are you sure you want to delete the selected ones?", 'limb-chatbot'))) {
            runDatasetDelete(selectedDSItems);
        }
    }

    /**
     * Follow sync background process
     *
     * @param {number} jobId Job ID
     * @param {number[]} datasetIds Dataset IDs being processed
     * @param {object} params More params
     * @return {Promise<void>}
     */
    const followSyncBackgroundProcess = async (jobId, datasetIds, params = null) => {
        await followJob({
            jobId,
            processType: 'dataset_sync',
            setProgressState: (v) => {
                setSyncingProgress(v);
            },
            onComplete: async () => {
                setSyncing([]);
            },
            onProgressUpdate: async () => {
                await fetchDSItems(datasetsPagination.page, datasetsPagination.perPage, datasetsOrder, false, {
                    skipInclude: ['storage', 'source_object', 'source_url'],
                });
            },
            resetProgress: () => setSyncingProgress(false),
            statusMessages: {
                complete: DATASET_PROCESSES_INFO['dataset_sync'].success,
                pause: sprintf(__("%s process has been paused.", 'limb-chatbot'), DATASET_PROCESSES_INFO['dataset_sync'].processName),
                cancel: sprintf(__("%s process has been cancelled.", 'limb-chatbot'), DATASET_PROCESSES_INFO['dataset_sync'].processName)
            }
        });
    }

    /**
     * Run sync process
     *
     * @param {number[]} ids Dataset IDs
     * @return {Promise<void>}
     */
    const runDatasetSync = async (ids) => {
        if (!ids?.length) {
            return;
        }
        await runDatasetProcess(
            'dataset_sync',
            ids,
            {}, // No additional config needed for sync
            followSyncBackgroundProcess,
            setSyncing
        );
    }

    /**
     * Sync
     *
     * @param {object} dataset Dataset
     */
    const sync = async (dataset) => {
        // Add to selected items to show footer buttons
        setSelectedDSItems([dataset.id]);
        await runDatasetSync([dataset.id]);
    }

    /**
     * Sync selected datasets
     */
    const syncSelected = async () => {
        // Check if there's a paused process to resume
        if (processState && processState.status === 'pause') {
            const isSyncProcess = processState.type === 'dataset_sync';

            if (isSyncProcess) {
                // Resume the paused sync process (don't create a new job)
                await resumeProcess();
                return;
            }
        }

        // Normal flow: sync selected items
        await runDatasetSync(selectedDSItems);
    }

    /**
     * Follow a generating background process
     *
     * @param {number} jobId Job ID
     * @param {object} params More params
     *
     * @return {Promise<void>}
     */
    const followDatasetGeneratingBackgroundProcess = async (jobId, params = null) => {
        await followJob({
            jobId,
            processType: 'dataset_generating',
            setProgressState: (v) => {
                setGeneratingProgress(v);
            },
            onComplete: async () => {
                setSaving(false);
                // Refresh datasets list to show updated state (remove warnings, show correct status)
                await fetchDSItems(1, datasetsPagination.perPage, {
                    orderBy: 'updated_at',
                    order: 'desc',
                }, false);
                // Always close form when process ends
                toggleGIForm(false);
                // Auto-close notifications
                const timeout = hasNotificationErrors(notifications) ? NOTIF_ERROR_AUTOCLOSE_TIMEOUT : NOTIF_AUTOCLOSE_TIMEOUT;
                autoCloseNotifications(timeout);
            },
            onProgressUpdate: async () => {
                await fetchDSItems(1, datasetsPagination.perPage, {
                    orderBy: 'updated_at',
                    order: 'desc',
                }, false);
            },
            statusMessages: {
                complete: DATASET_PROCESSES_INFO['dataset_generating'].success,
                pause: sprintf(__("%s process has been paused.", 'limb-chatbot'), DATASET_PROCESSES_INFO['dataset_generating'].processName),
                cancel: sprintf(__("%s process has been cancelled.", 'limb-chatbot'), DATASET_PROCESSES_INFO['dataset_generating'].processName)
            },
            resetProgress: () => {
                setGeneratingProgress(false);
            },
        });
    }

    /**
     * Handle Learn button click from sitemap popup
     */
    const handleLearnFromSitemap = useCallback(async (urls) => {
        if (!urls || urls.length === 0) {
            return;
        }

        // Close popup
        setShowSitemapPopup(false);

        // Get form data with URLs
        const dataToSave = await giFormRef.current?.getDataToSave();
        if (!dataToSave) {
            return;
        }

        setSaving(true);
        try {
            // Convert form data to job config
            const generatingJobConfig = convertFormDataToJobConfig(dataToSave.data);

            // Override source to 'url' and set urls
            generatingJobConfig.source = 'url';
            generatingJobConfig.urls = urls;

            // Create generating job
            const generatingJob = await createJob({
                type: 'dataset_generating',
                sub_type: 'informational',
                config: generatingJobConfig,
                chatbot_uuid: 'default'
            });

            setProcessState(null);

            // Follow the generating job
            await followDatasetGeneratingBackgroundProcess(generatingJob.id);
        } catch (e) {
            setSaving(false);
            handleError(e, notifications.set, {
                title: __("Failed to create job.", 'limb-chatbot'),
                description: e.message ? __(e.message, 'limb-chatbot') : __("Please check and try again.", 'limb-chatbot'),
            });
        }
    }, [notifications, createJob, followDatasetGeneratingBackgroundProcess, convertFormDataToJobConfig, setProcessState, setSaving]);

    /**
     * Save index
     */
    const save = async () => {
        // Handle resume for paused processes
        if (processState && processState.status === 'pause') {
            if (processState.type === 'dataset_generating') {
                // Resume the paused process
                await resumeProcess();
            } else {
                handleError({
                    message: "Unknown process requested to resume.",
                    data: processState
                }, notifications.set, {
                    title: __("Unknown process requested to resume.", 'limb-chatbot'),
                    description: __("Please check and try again.", 'limb-chatbot'),
                });
            }
            return;
        }

        // Get form data
        const dataToSave = await giFormRef.current?.getDataToSave();
        if (!dataToSave) {
            // Errors are already shown
            return;
        }

        setSaving(true);
        try {
            // Reset filters when starting a new generating job (new datasets will be generated)
            discardFilters();

            // Convert form data to job config
            const generatingJobConfig = convertFormDataToJobConfig(dataToSave.data);

            // Create generating job
            const generatingJob = await createJob({
                type: 'dataset_generating',
                sub_type: 'informational',
                config: generatingJobConfig,
                chatbot_uuid: 'default'
            });

            // Don't set saving to false here - keep it true until the process completes
            // setSaving(false) will be called in onComplete callbacks
            setProcessState(null);

            // Follow the generating job
            await followDatasetGeneratingBackgroundProcess(generatingJob.id);
        } catch (e) {
            setSaving(false);
            handleError(e, notifications.set, {
                title: __("Failed to create job.", 'limb-chatbot'),
                description: e.message ? __(e.message, 'limb-chatbot') : __("Please check and try again.", 'limb-chatbot'),
            });
        }
    }

    const tableStructure = {
        columns: [
            {
                id: 'check',
                label: false,
                className: 'lbaic-settings-table-card-checkbox lbaic-settings-table-card-header-checkbox',
                render: () => {
                    const isAllState = selectedDSItems.length === 1 && selectedDSItems[0] === "all";
                    return <Checkbox
                        isChecked={isAllSelected || isAllState}
                        isIntermediateChecked={isPartiallySelected && !isAllState}
                        toggleValue={toggleAll}/>
                },
                value: {
                    className: 'lbaic-settings-table-card-checkbox lbaic-settings-table-card-body-checkbox',
                    render: (row, index) => {
                        const isChecked = selectedDSItems.indexOf(row.id) !== -1 || (selectedDSItems.length === 1 && selectedDSItems[0] === "all");
                        return <Checkbox
                            isChecked={isChecked}
                            toggleValue={() => {
                                // If "all" is selected, convert to actual IDs first
                                let currentSelected = selectedDSItems;
                                if (selectedDSItems.length === 1 && selectedDSItems[0] === "all") {
                                    currentSelected = [...datasets.map(item => item.id)];
                                }

                                const i = currentSelected.indexOf(row.id);
                                if (i === -1) {
                                    setSelectedDSItems([...currentSelected, row.id]);
                                } else {
                                    setSelectedDSItems(currentSelected.filter(item => item !== row.id));
                                }
                            }}/>
                    }
                },
            },
            {
                id: 'name',
                label: __("Name", 'limb-chatbot'),
                sortable: true,
                className: 'lbaic-settings-table-card-header-input',
                render: null,
                value: {
                    className: (row, index) => {
                        const classes = ['lbaic-settings-table-card-body-input', 'lbaic-settings-cursor-pointer'];

                        if (row.source_type && row.source_type !== 'q_a') {
                            classes.push('lbaic-settings-table-card-body-input-parent');
                        }

                        return classes.join(' ');
                    },
                    render: (row, index) => renderName(row, index, {
                        preparing,
                        processState
                    }),
                },
                detailsToggler: true,
            },
            {
                id: 'type',
                label: __("Type", 'limb-chatbot'),
                className: 'lbaic-settings-table-card-header-type',
                render: null,
                value: {
                    className: 'lbaic-settings-table-card-body-type',
                    render: (row, index) => renderSource(row),
                },
            },
            {
                id: 'ai',
                label: __("AI", 'limb-chatbot'),
                className: 'lbaic-settings-table-card-header-ai',
                render: null,
                value: {
                    className: 'lbaic-settings-table-card-body-ai',
                    render: (row, index) => renderAIProvider(row, kbAIProviderId),
                },
            },
            {
                id: 'storage',
                label: __("Storage", 'limb-chatbot'),
                className: 'lbaic-settings-table-card-header-storage',
                render: null,
                value: {
                    className: 'lbaic-settings-table-card-body-storage',
                    render: (row, index) => renderStorage(row)
                },
            },
            {
                id: 'updated_at',
                label: __("Updated at", 'limb-chatbot'),
                sortable: true,
                className: 'lbaic-settings-table-card-header-type',
                render: null,
                value: {
                    className: 'lbaic-settings-table-card-body-last-updated',
                    render: (row, index) => {
                        return <Chip>
                            <RealTimeRelativeTime
                                dateString={row.updated_at}
                                className='lbaic-settings-chip-label'
                            />
                        </Chip>
                    },
                },
            },
            {
                id: 'actions',
                label: false,
                className: 'lbaic-settings-table-card-header-actions',
                render: null,
                value: {
                    className: 'lbaic-settings-table-card-floating lbaic-settings-table-card-body-actions',
                    render: (row, index) => {
                        const rowDisabled = isRowDisabled(row, index, processState);

                        return <DatasetRowActions
                            row={row}
                            index={index}
                            processState={processState}
                            syncing={syncing}
                            deleting={deleting}
                            preparing={preparing}
                            onSync={sync}
                            onDelete={deleteDSItem}
                            isRowDisabled={rowDisabled}
                        />
                    }
                },
            },
        ],
        row: {
            className: (row, index) => {
                const classes = ['lbaic-settings-table-accordion-body-in'];
                // Errors
                const errors = row.metas?.length ? row.metas.filter(item => item.meta_key === 'error').map(item => item.meta_value) : [];
                if (errors.length) {
                    classes.push('error');
                }
                return classes.join(' ');
            },
            details: {
                show: (row, index) => {
                    if (row.source_type === 'q_a') {
                        return true;
                    }

                    return false;
                },
                render: (row, index, show) => {
                    return (
                        <DSDetails
                            dataset={row}
                            datasetIndex={index}
                            show={show}
                            notifications={notifications}
                        />
                    );
                }
            },
            togglerCallback: (row, index) => {
                if (isOldRow(row)) {
                    toggleDatasetEntries(row.id);
                } else {
                    // Show new edit screen for source content
                    toggleDatasetSourceContentEditor(row.id);
                }
            },
            disabled: (row, index) => isRowDisabled(row, index, processState)
        },
        disableRowColumns: ['name', 'type', 'ai', 'storage', 'updated_at']
    };

    if (datasetIdForDetails) {
        // Find the dataset to determine if it's old or new
        const dataset = datasets.find(d => d.id === datasetIdForDetails);
        const isOld = dataset ? isOldRow(dataset) : false;

        if (isOld) {
            return (
                <DatasetEntries
                    datasetId={datasetIdForDetails}
                    cancel={toggleDatasetEntries}
                    notifications={notifications}
                />
            );
        } else {
            return (
                <DatasetSourceContentEditor
                    datasetId={datasetIdForDetails}
                    cancel={toggleDatasetSourceContentEditor}
                    notifications={notifications}
                />
            );
        }
    }

    return (
        <>
            <ContentBodyInner>
                {(showLoading || (loading > 0 && !isDataFetched)) ? <ContentLoading/> : null}
                <Container className="lbaic-settings-a-ei">
                        {addingEditing &&
                            <AddSources ref={giFormRef}
                                        formData={formData}
                                        saving={Boolean(processState?.type)}
                                        setLoading={setLoading}
                                        setShowLoading={setShowLoading}
                                        updateHasUnsavedChanges={setHasUnsavedChanges}
                                        notifications={notifications}/>}
                        {showFilter && !addingEditing &&
                            <FilterForm
                                searchValue={filterSearch}
                                setSearchValue={setFilterSearch}
                                sourceTypeValue={filterSourceType}
                                setSourceTypeValue={setFilterSourceType}
                                syncedValue={filterSynced}
                                setSyncedValue={setFilterSynced}
                                aiProviderIdValue={filterAIProviderId}
                                setAIProviderIdValue={setFilterAIProviderId}
                                vectorIndexIdValue={filterVectorIndexId}
                                setVectorIndexIdValue={setFilterVectorIndexId}
                                disabled={!!processState?.type || preparing}
                            />}
                        {
                            datasets.length > 0 ? (
                                <Table
                                    className='lbaic-settings-a-es-table lbaic-settings-scroll-style lbaic-settings-scroll-x lbaic-settings-table-card-has-details lbaic-settings-table-card-kb-items'
                                    structure={tableStructure}
                                    data={datasets}
                                    loading={loading > 0 || !!processState?.type}
                                    pagination={datasetsPagination}
                                    order={{get: datasetsOrder, set: setDatasetsOrder}}
                                    _callback={fetchDSItems}>
                                </Table>
                            ) : (
                                isDataFetched && !addingEditing && !datasetsPagination.total && (
                                    hasActiveFilters ? (
                                        <Empty
                                            title={__("No sources found", 'limb-chatbot')}
                                            icon="tab-indexes-active"
                                        />
                                    ) : (
                                        <Empty title={__("No sources yet", 'limb-chatbot')}
                                               subtitle={__("Start by adding your first one", 'limb-chatbot')}
                                               icon="tab-indexes-active">
                                            <div className='lbaic-settings-empty-actions'>
                                                <button onClick={showAddDatasetsForm}
                                                        disabled={!!processState?.type || preparing}
                                                        className='lbaic-settings-button lbaic-settings-button-center lbaic-settings-button-h-40 lbaic-settings-button-primary'>
                                            <span
                                                className='lbaic-settings-button-label'>{__("Add source", 'limb-chatbot')}</span>
                                                </button>
                                            </div>
                                        </Empty>
                                    )
                                )
                            )
                        }
                    </Container>
            </ContentBodyInner>
            <Footer add={showAddDatasetsForm}
                    save={save}
                    resume={resumeProcess}
                    pause={pauseProcess}
                    cancel={cancelProcess}
                    cancelForm={cancelForm}
                    deleteSelected={deleteSelected}
                    syncSelected={syncSelected}
                    addingEditing={addingEditing}
                    processState={processState}
                    generatingProgress={generatingProgress}
                    syncingProgress={syncingProgress}
                    deletingProgress={deletingProgress}
                    totalCount={datasetsPagination.total}
                    loading={loading} saving={saving} syncing={syncing}
                    deleting={deleting} pausing={pausing} canceling={canceling}
                    preparing={preparing}
                    selectedItems={selectedDSItems}
                    isAllPageSelected={isAllPageSelected}
                    onToggleSelectAll={toggleSelectAll}
                    currentPageItemsCount={currentPageItemsCount}
                    showFilter={showFilter}
                    toggleFilter={toggleFilterForm}
                    hasActiveFilters={hasActiveFilters}
                    applyFilters={applyFilters}
                    filteringLoading={filteringLoading}
                    giFormRef={giFormRef}
                    onLoadSitemap={handleLoadSitemap}/>
            {showSitemapPopup && (
                <SitemapLinksPopup
                    close={handleCloseSitemapPopup}
                    sitemapUrls={sitemapUrls}
                    onDeleteUrl={handleDeleteSitemapUrl}
                    isJobRunning={sitemapJobRunning}
                    jobProgress={sitemapJobProgress}
                    jobError={sitemapJobError}
                    onLearn={handleLearnFromSitemap}
                    notifications={notifications}
                />
            )}
        </>
    );
}