import { getCurrentPageState } from '../state/index.js';
import { showToast, updatePanelPositions } from '../utils/uiHelpers.js';
import { getModelApiClient } from '../api/modelApiFactory.js';
import { getApiEndpoints } from '../api/apiConfig.js';
import { removeStorageItem, setStorageItem, getStorageItem } from '../utils/storageHelpers.js';
import { MODEL_TYPE_DISPLAY_NAMES } from '../utils/constants.js';
import { translate } from '../utils/i18nHelpers.js';
import { FilterPresetManager, EMPTY_WILDCARD_MARKER } from './FilterPresetManager.js';

// LoRA availability statuses available on the recipes page. No statuses
// selected (the default) means no filtering.
const LORA_AVAILABILITY_STATUSES = ['ready', 'missing', 'deleted'];

export class FilterManager {
    constructor(options = {}) {
        this.options = {
            ...options
        };

        this.currentPage = options.page || document.body.dataset.page || 'loras';
        const pageState = getCurrentPageState();

        this.filters = this.initializeFilters(pageState ? pageState.filters : undefined);

        this.filterPanel = document.getElementById('filterPanel');
        this.filterButton = document.getElementById('filterButton');
        this.activeFiltersCount = document.getElementById('activeFiltersCount');
        this.baseModelSearchInput = document.getElementById('baseModelSearchInput');
        this.baseModelOptions = [];
        this.tagsLoaded = false;

        // Tag search state
        this.modelTagsSearchInput = document.getElementById('modelTagsSearchInput');
        this.tagSearchDebounceTimer = null;
        this.tagSearchAbortController = null;
        this.tagSearchQuery = '';

        // Initialize preset manager
        this.presetManager = new FilterPresetManager({
            page: this.currentPage,
            filterManager: this
        });

        this.initialize();

        // Store this instance in the state
        if (pageState) {
            pageState.filterManager = this;
            pageState.filters = this.cloneFilters();
        }
    }

    // Accessor for backward compatibility with activePreset
    get activePreset() {
        return this.presetManager?.activePreset ?? null;
    }

    set activePreset(value) {
        if (this.presetManager) {
            this.presetManager.activePreset = value;
        }
    }

    initialize() {
        this.initializeFilterSearchInputs();

        // Create base model filter tags if they exist
        if (document.getElementById('baseModelTags')) {
            this.createBaseModelTags();
        }

        if (document.getElementById('modelTypeTags')) {
            this.createModelTypeTags();
        }

        // Add click handlers for license filter tags if supported on this page
        if (this.shouldShowLicenseFilters()) {
            this.initializeLicenseFilters();
        }

        // Add click handlers for LoRA availability tags (recipes page only)
        if (this.shouldShowLoraAvailabilityFilter()) {
            this.initializeLoraAvailabilityFilters();
        }

        // Initialize tag logic toggle
        this.initializeTagLogicToggle();

        // Create auto-tag filter section (I2V, T2V, TI2V, Lightning, Turbo)
        this.createAutoTagFilters();

        // Add click handler for filter button
        if (this.filterButton) {
            this.filterButton.addEventListener('click', () => {
                this.toggleFilterPanel();
            });
        }

        // Close filter panel when clicking outside
        document.addEventListener('click', (e) => {
            if (this.filterPanel && !this.filterPanel.contains(e.target) &&
                e.target !== this.filterButton &&
                !this.filterButton.contains(e.target) &&
                !this.filterPanel.classList.contains('hidden')) {
                this.closeFilterPanel();
            }
        });

        // Initialize active filters from localStorage if available
        this.loadFiltersFromStorage();
    }

    initializeTagLogicToggle() {
        const toggleContainer = document.getElementById('tagLogicToggle');
        if (!toggleContainer) return;

        const options = toggleContainer.querySelectorAll('.tag-logic-option');
        
        options.forEach(option => {
            option.addEventListener('click', async () => {
                const value = option.dataset.value;
                if (this.filters.tagLogic === value) return;
                
                this.filters.tagLogic = value;
                this.updateTagLogicToggleUI();
                
                // Auto-apply filter when logic changes
                await this.applyFilters(false);
            });
        });

        // Set initial state
        this.updateTagLogicToggleUI();
    }

    initializeFilterSearchInputs() {
        if (this.baseModelSearchInput) {
            this.baseModelSearchInput.addEventListener('input', () => {
                this.renderBaseModelTags();
            });
        }

        if (this.modelTagsSearchInput) {
            this.modelTagsSearchInput.addEventListener('input', () => {
                clearTimeout(this.tagSearchDebounceTimer);
                this.tagSearchDebounceTimer = setTimeout(() => {
                    this.handleTagSearchInput();
                }, 150);
            });
        }
    }

    handleTagSearchInput() {
        const query = (this.modelTagsSearchInput?.value || '').trim();
        const trimmedQuery = query.toLowerCase();
        if (trimmedQuery === this.tagSearchQuery) return;
        this.tagSearchQuery = trimmedQuery;

        if (!trimmedQuery) {
            // Empty query: reload top tags (default/common view)
            this.loadTopTags();
            return;
        }
        this.searchTags(trimmedQuery);
    }

    async searchTags(query) {
        // Abort any in-flight search request
        if (this.tagSearchAbortController) {
            this.tagSearchAbortController.abort();
        }
        this.tagSearchAbortController = new AbortController();
        const controller = this.tagSearchAbortController;

        try {
            const tagsEndpoint = `${getApiEndpoints(this.currentPage).searchTags}?q=${encodeURIComponent(query)}&limit=20`;
            const response = await fetch(tagsEndpoint, { signal: controller.signal });
            if (!response.ok) throw new Error('Failed to search tags');
            const data = await response.json();
            if (controller.signal.aborted) return; // stale response
            if (data.success && data.tags) {
                this.createTagFilterElements(data.tags);
            } else {
                throw new Error('Invalid response format');
            }
        } catch (error) {
            if (error.name === 'AbortError') return; // expected, ignore
            console.error('Error searching tags:', error);
            const tagsContainer = document.getElementById('modelTagsFilter');
            if (tagsContainer) {
                tagsContainer.innerHTML = '<div class="tags-error">Failed to search tags</div>';
            }
            const emptyState = document.getElementById('modelTagsEmptyState');
            if (emptyState) emptyState.hidden = true;
        }
    }

    getNormalizedSearchQuery(input) {
        return (input?.value || '').trim().toLowerCase();
    }

    updateTagLogicToggleUI() {
        const toggleContainer = document.getElementById('tagLogicToggle');
        if (!toggleContainer) return;

        const options = toggleContainer.querySelectorAll('.tag-logic-option');
        const currentLogic = this.filters.tagLogic || 'any';
        
        options.forEach(option => {
            if (option.dataset.value === currentLogic) {
                option.classList.add('active');
            } else {
                option.classList.remove('active');
            }
        });
    }

    async loadTopTags() {
        // Abort any in-flight tag search request
        if (this.tagSearchAbortController) {
            this.tagSearchAbortController.abort();
            this.tagSearchAbortController = null;
        }
        this.tagSearchQuery = '';

        try {
            // Show loading state
            const tagsContainer = document.getElementById('modelTagsFilter');
            const emptyState = document.getElementById('modelTagsEmptyState');
            if (!tagsContainer) return;
            if (emptyState) emptyState.hidden = true;

            tagsContainer.innerHTML = '<div class="tags-loading">Loading tags...</div>';

            // Determine the API endpoint based on the page type
            const tagsEndpoint = `${getApiEndpoints(this.currentPage).topTags}?limit=20`;

            const response = await fetch(tagsEndpoint);
            if (!response.ok) throw new Error('Failed to fetch tags');

            const data = await response.json();
            if (data.success && data.tags) {
                this.createTagFilterElements(data.tags);

                // After creating tag elements, mark any previously selected ones
                this.updateTagSelections();
            } else {
                throw new Error('Invalid response format');
            }
        } catch (error) {
            console.error('Error loading top tags:', error);
            const tagsContainer = document.getElementById('modelTagsFilter');
            if (tagsContainer) {
                tagsContainer.innerHTML = '<div class="tags-error">Failed to load tags</div>';
            }
        }
    }

    createTagFilterElements(tags) {
        const tagsContainer = document.getElementById('modelTagsFilter');
        const emptyState = document.getElementById('modelTagsEmptyState');
        if (!tagsContainer) return;

        tagsContainer.innerHTML = '';
        if (emptyState) emptyState.hidden = true;

        // Collect existing tag names from the API response
        const existingTagNames = new Set(tags.map(t => t.tag));

        // Collect active filter tags that aren't in the response (excluding __no_tags__)
        const missingSelectedTags = [];
        if (this.filters.tags) {
            Object.keys(this.filters.tags).forEach(tagName => {
                if (tagName.startsWith('__')) return;
                if (!existingTagNames.has(tagName)) {
                    missingSelectedTags.push({ tag: tagName, count: 0 });
                    existingTagNames.add(tagName);
                }
            });
        }

        // Append missing selected tags after the API results so they appear inline
        for (const t of missingSelectedTags) {
            tags.push(t);
        }

        if (!tags.length) {
            if (this.tagSearchQuery) {
                if (emptyState) emptyState.hidden = false;
            } else {
                tagsContainer.innerHTML = `<div class="no-tags">No ${this.currentPage === 'recipes' ? 'recipe ' : ''}tags available</div>`;
            }
            return;
        }

        tags.forEach(tag => {
            const tagEl = document.createElement('div');
            tagEl.className = 'filter-tag tag-filter';
            const tagName = tag.tag;

            if (missingSelectedTags.some(t => t.tag === tagName)) {
                tagEl.classList.add('extra-tag');
            }
            tagEl.dataset.tag = tagName;

            // Show count only if it's > 0 (known count)
            if (tag.count > 0) {
                tagEl.innerHTML = `${tagName} <span class="tag-count">${tag.count}</span>`;
            } else {
                tagEl.textContent = tagName;
            }

            // Add click handler to cycle through tri-state filter and automatically apply
            tagEl.addEventListener('click', async () => {
                const currentState = (this.filters.tags && this.filters.tags[tagName]) || 'none';
                const newState = this.getNextTriStateState(currentState);
                this.setTagFilterState(tagName, newState);
                this.applyTagElementState(tagEl, newState);

                this.updateActiveFiltersCount();

                // Auto-apply filter when tag is clicked
                await this.applyFilters(false);
            });

            tagsContainer.appendChild(tagEl);
        });

        // Add "No tags" as a special filter at the end (skip during search)
        if (!this.tagSearchQuery) {
            const noTagsEl = document.createElement('div');
            noTagsEl.className = 'filter-tag tag-filter special-tag';
            const noTagsLabel = translate('header.filter.noTags', {}, 'No tags');
            const noTagsKey = '__no_tags__';
            noTagsEl.dataset.tag = noTagsKey;
            noTagsEl.innerHTML = noTagsLabel;

            noTagsEl.addEventListener('click', async () => {
                const currentState = (this.filters.tags && this.filters.tags[noTagsKey]) || 'none';
                const newState = this.getNextTriStateState(currentState);
                this.setTagFilterState(noTagsKey, newState);
                this.applyTagElementState(noTagsEl, newState);

                this.updateActiveFiltersCount();

                await this.applyFilters(false);
            });

            tagsContainer.appendChild(noTagsEl);
        }
        this.updateTagSelections();
    }

    initializeLicenseFilters() {
        const licenseTags = document.querySelectorAll('.license-tag');
        licenseTags.forEach(tag => {
            tag.addEventListener('click', async () => {
                const licenseType = tag.dataset.license;

                // Ensure license object exists
                if (!this.filters.license) {
                    this.filters.license = {};
                }

                // Get current state
                let currentState = this.filters.license[licenseType] || 'none'; // none, include, exclude

                // Cycle through states: none -> include -> exclude -> none
                let newState;
                switch (currentState) {
                    case 'none':
                        newState = 'include';
                        tag.classList.remove('exclude');
                        tag.classList.add('active');
                        break;
                    case 'include':
                        newState = 'exclude';
                        tag.classList.remove('active');
                        tag.classList.add('exclude');
                        break;
                    case 'exclude':
                        newState = 'none';
                        tag.classList.remove('active', 'exclude');
                        break;
                }

                // Update filter state
                if (newState === 'none') {
                    delete this.filters.license[licenseType];
                    // Clean up empty license object
                    if (Object.keys(this.filters.license).length === 0) {
                        delete this.filters.license;
                    }
                } else {
                    this.filters.license[licenseType] = newState;
                }

                this.updateActiveFiltersCount();

                // Auto-apply filter when tag is clicked
                await this.applyFilters(false);
            });
        });

        // Update selections based on stored filters
        this.updateLicenseSelections();
    }

    updateLicenseSelections() {
        const licenseTags = document.querySelectorAll('.license-tag');
        licenseTags.forEach(tag => {
            const licenseType = tag.dataset.license;
            const state = (this.filters.license && this.filters.license[licenseType]) || 'none';

            // Reset classes
            tag.classList.remove('active', 'exclude');

            // Apply appropriate class based on state
            switch (state) {
                case 'include':
                    tag.classList.add('active');
                    break;
                case 'exclude':
                    tag.classList.add('exclude');
                    break;
                default:
                    // none state - no classes needed
                    break;
            }
        });
    }

    initializeLoraAvailabilityFilters() {
        const availabilityTags = document.querySelectorAll('.lora-availability-tag');
        availabilityTags.forEach(tag => {
            tag.addEventListener('click', async () => {
                const status = tag.dataset.availability;
                const selected = this.filters.loraAvailability || [];

                if (selected.includes(status)) {
                    this.filters.loraAvailability = selected.filter(value => value !== status);
                    tag.classList.remove('active');
                } else {
                    this.filters.loraAvailability = [...selected, status];
                    tag.classList.add('active');
                }

                this.updateActiveFiltersCount();
                await this.applyFilters(false);
            });
        });

        // Update selections based on stored filters
        this.updateLoraAvailabilitySelections();
    }

    updateLoraAvailabilitySelections() {
        const availabilityTags = document.querySelectorAll('.lora-availability-tag');
        const selected = this.filters.loraAvailability || [];
        availabilityTags.forEach(tag => {
            if (selected.includes(tag.dataset.availability)) {
                tag.classList.add('active');
            } else {
                tag.classList.remove('active');
            }
        });
    }

    createBaseModelTags() {
        const baseModelTagsContainer = document.getElementById('baseModelTags');
        if (!baseModelTagsContainer) return;

        // Set the API endpoint based on current page
        const apiEndpoint = `${getApiEndpoints(this.currentPage).baseModels}?limit=0`;

        // Fetch base models
        fetch(apiEndpoint)
            .then(response => response.json())
            .then(data => {
                if (data.success && data.base_models) {
                    this.baseModelOptions = data.base_models;
                    this.renderBaseModelTags();
                }
            })
            .catch(error => {
                console.error(`Error fetching base models for ${this.currentPage}:`, error);
                baseModelTagsContainer.innerHTML = '<div class="tags-error">Failed to load base models</div>';
            });
    }

    renderBaseModelTags() {
        const baseModelTagsContainer = document.getElementById('baseModelTags');
        const emptyState = document.getElementById('baseModelEmptyState');
        if (!baseModelTagsContainer) return;

        baseModelTagsContainer.innerHTML = '';

        if (!this.baseModelOptions.length) {
            baseModelTagsContainer.innerHTML = '<div class="no-tags">No base models available</div>';
            if (emptyState) {
                emptyState.hidden = true;
            }
            return;
        }

        const query = this.getNormalizedSearchQuery(this.baseModelSearchInput);
        const filteredModels = query
            ? this.baseModelOptions.filter(model => model.name.toLowerCase().includes(query))
            : this.baseModelOptions;

        filteredModels.forEach(model => {
            const tag = document.createElement('div');
            tag.className = 'filter-tag base-model-tag';
            tag.dataset.baseModel = model.name;
            tag.innerHTML = `${model.name} <span class="tag-count">${model.count}</span>`;

            tag.addEventListener('click', async () => {
                tag.classList.toggle('active');

                if (tag.classList.contains('active')) {
                    if (!this.filters.baseModel.includes(model.name)) {
                        this.filters.baseModel.push(model.name);
                    }
                } else {
                    this.filters.baseModel = this.filters.baseModel.filter(m => m !== model.name);
                }

                this.updateActiveFiltersCount();
                await this.applyFilters(false);
            });

            baseModelTagsContainer.appendChild(tag);
        });

        if (emptyState) {
            emptyState.hidden = filteredModels.length > 0;
        }

        this.updateTagSelections();
    }

    async createModelTypeTags() {
        const modelTypeContainer = document.getElementById('modelTypeTags');
        if (!modelTypeContainer) return;

        modelTypeContainer.innerHTML = '<div class="tags-loading">Loading model types...</div>';

        try {
            const response = await fetch(`/api/lm/${this.currentPage}/model-types?limit=20`);
            if (!response.ok) {
                throw new Error('Failed to fetch model types');
            }

            const data = await response.json();
            if (!data.success || !Array.isArray(data.model_types)) {
                throw new Error('Invalid response format');
            }

            const normalizedTypes = data.model_types
                .map(entry => {
                    if (!entry || !entry.type) {
                        return null;
                    }
                    const typeKey = entry.type.toString().trim().toLowerCase();
                    if (!typeKey || !MODEL_TYPE_DISPLAY_NAMES[typeKey]) {
                        return null;
                    }
                    return {
                        type: typeKey,
                        count: Number(entry.count) || 0,
                    };
                })
                .filter(Boolean);

            if (!normalizedTypes.length) {
                modelTypeContainer.innerHTML = '<div class="no-tags">No model types available</div>';
                return;
            }

            modelTypeContainer.innerHTML = '';

            normalizedTypes.forEach(({ type, count }) => {
                const tag = document.createElement('div');
                tag.className = 'filter-tag model-type-tag';
                tag.dataset.modelType = type;
                tag.innerHTML = `${MODEL_TYPE_DISPLAY_NAMES[type]} <span class="tag-count">${count}</span>`;

                if (this.filters.modelTypes.includes(type)) {
                    tag.classList.add('active');
                }

                tag.addEventListener('click', async () => {
                    const isSelected = this.filters.modelTypes.includes(type);
                    if (isSelected) {
                        this.filters.modelTypes = this.filters.modelTypes.filter(value => value !== type);
                        tag.classList.remove('active');
                    } else {
                        this.filters.modelTypes.push(type);
                        tag.classList.add('active');
                    }

                    this.updateActiveFiltersCount();
                    await this.applyFilters(false);
                });

                modelTypeContainer.appendChild(tag);
            });

            this.updateModelTypeSelections();
        } catch (error) {
            console.error('Error loading model types:', error);
            modelTypeContainer.innerHTML = '<div class="tags-error">Failed to load model types</div>';
        }
    }

    AUTO_TAG_FILTER_TAGS = ['I2V', 'T2V', 'TI2V', 'Lightning', 'Turbo'];

    createAutoTagFilters() {
        const container = document.getElementById('autoTagFilterTags');
        if (container) return;

        const modelTypeSection = document.getElementById('modelTypeTags')?.closest('.filter-section');
        if (!modelTypeSection) return;

        const section = document.createElement('div');
        section.className = 'filter-section';
        section.innerHTML = `
            <h4>${translate('header.filter.autoTags', {}, 'Auto Tags')}</h4>
            <div class="filter-tags" id="autoTagFilterTags"></div>
        `;
        modelTypeSection.parentNode.insertBefore(section, modelTypeSection.nextSibling);

        const tagsContainer = document.getElementById('autoTagFilterTags');
        this.AUTO_TAG_FILTER_TAGS.forEach(tag => {
            const el = document.createElement('div');
            el.className = 'filter-tag auto-tag-filter';
            el.dataset.autoTag = tag;
            el.textContent = tag;

            // Restore previous state
            const state = (this.filters.autoTags && this.filters.autoTags[tag]) || 'none';
            this._applyTriState(el, state);

            el.addEventListener('click', async () => {
                const current = (this.filters.autoTags && this.filters.autoTags[tag]) || 'none';
                const next = current === 'none' ? 'include' : current === 'include' ? 'exclude' : 'none';
                if (!this.filters.autoTags) this.filters.autoTags = {};
                if (next === 'none') {
                    delete this.filters.autoTags[tag];
                } else {
                    this.filters.autoTags[tag] = next;
                }
                this._applyTriState(el, next);
                this.updateActiveFiltersCount();
                await this.applyFilters(false);
            });

            tagsContainer.appendChild(el);
        });
    }

    _applyTriState(el, state) {
        el.classList.remove('active', 'exclude');
        if (state === 'include') el.classList.add('active');
        else if (state === 'exclude') el.classList.add('exclude');
    }

    toggleFilterPanel() {
        if (this.filterPanel) {
            const isHidden = this.filterPanel.classList.contains('hidden');

            if (isHidden) {
                // Update panel positions before showing
                updatePanelPositions();

                this.filterPanel.classList.remove('hidden');
                this.filterButton.classList.add('active');
                this.baseModelSearchInput?.focus();

                // Load tags if they haven't been loaded yet
                if (!this.tagsLoaded) {
                    this.loadTopTags();
                    this.tagsLoaded = true;
                }

                // Render presets
                this.presetManager.renderPresets();
            } else {
                this.closeFilterPanel();
            }
        }
    }

    closeFilterPanel() {
        if (this.filterPanel) {
            this.filterPanel.classList.add('hidden');
        }
        if (this.filterButton) {
            this.filterButton.classList.remove('active');
        }
    }

    updateTagSelections() {
        // Update base model tags
        const baseModelTags = document.querySelectorAll('.base-model-tag');
        baseModelTags.forEach(tag => {
            const baseModel = tag.dataset.baseModel;
            if (this.filters.baseModel.includes(baseModel)) {
                tag.classList.add('active');
            } else {
                tag.classList.remove('active');
            }
        });

        // Update model tags
        const modelTags = document.querySelectorAll('.tag-filter');
        modelTags.forEach(tag => {
            const tagName = tag.dataset.tag;
            const state = (this.filters.tags && this.filters.tags[tagName]) || 'none';
            this.applyTagElementState(tag, state);
        });

        // Update license tags if visible on this page
        if (this.shouldShowLicenseFilters()) {
            this.updateLicenseSelections();
        }
        this.updateModelTypeSelections();

        // Update LoRA availability tags if visible on this page
        if (this.shouldShowLoraAvailabilityFilter()) {
            this.updateLoraAvailabilitySelections();
        }

        const autoTagEls = document.querySelectorAll('.auto-tag-filter');
        autoTagEls.forEach(el => {
            const tag = el.dataset.autoTag;
            const state = (this.filters.autoTags && this.filters.autoTags[tag]) || 'none';
            this._applyTriState(el, state);
        });
    }

    updateModelTypeSelections() {
        const typeTags = document.querySelectorAll('.model-type-tag');
        typeTags.forEach(tag => {
            const modelType = tag.dataset.modelType;
            if (this.filters.modelTypes.includes(modelType)) {
                tag.classList.add('active');
            } else {
                tag.classList.remove('active');
            }
        });
    }

    updateActiveFiltersCount() {
        const tagFilterCount = this.filters.tags ? Object.keys(this.filters.tags).length : 0;
        const autoTagFilterCount = this.filters.autoTags ? Object.keys(this.filters.autoTags).length : 0;
        const licenseFilterCount = this.filters.license ? Object.keys(this.filters.license).length : 0;
        const modelTypeFilterCount = this.filters.modelTypes.length;
        // Exclude EMPTY_WILDCARD_MARKER from base model count
        const baseModelCount = this.filters.baseModel.filter(m => m !== EMPTY_WILDCARD_MARKER).length;
        // Active when at least one availability status is deselected
        const loraAvailabilityCount = this.filters.loraAvailability?.length ?? 0;
        const totalActiveFilters = baseModelCount + tagFilterCount + autoTagFilterCount + licenseFilterCount + modelTypeFilterCount + loraAvailabilityCount;

        if (this.activeFiltersCount) {
            if (totalActiveFilters > 0) {
                this.activeFiltersCount.textContent = totalActiveFilters;
                this.activeFiltersCount.style.display = 'inline-flex';
            } else {
                this.activeFiltersCount.style.display = 'none';
            }
        }

        // Update add button state when filters change
        if (this.presetManager) {
            this.presetManager.updateAddButtonState();
        }
    }

    async applyFilters(showToastNotification = true, isPresetApply = false) {
        const pageState = getCurrentPageState();
        const storageKey = `${this.currentPage}_filters`;

        // Save filters to localStorage (exclude EMPTY_WILDCARD_MARKER and transient search)
        const filtersSnapshot = this.cloneFilters();
        // Don't persist EMPTY_WILDCARD_MARKER - it's a runtime-only marker
        filtersSnapshot.baseModel = filtersSnapshot.baseModel.filter(m => m !== EMPTY_WILDCARD_MARKER);
        // Don't persist search - it's transient and managed by SearchManager
        delete filtersSnapshot.search;
        setStorageItem(storageKey, filtersSnapshot);

        // Update state with current filters
        pageState.filters = this.cloneFilters();

        // Deactivate preset if this is a manual filter change (not from applying a preset)
        if (!isPresetApply && this.activePreset) {
            this.activePreset = null;
            this.presetManager.saveActivePreset(); // Persist the cleared state
            this.presetManager.renderPresets(); // Re-render to remove active state
        }

        // Call the appropriate manager's load method based on page type
        if (this.currentPage === 'recipes' && window.recipeManager) {
            await window.recipeManager.loadRecipes(true);
        } else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints') {
            // For models page, reset the page and reload
            await getModelApiClient().loadMoreWithVirtualScroll(true, false);
        }

        // Update filter button to show active state
        if (this.hasActiveFilters()) {
            this.filterButton.classList.add('active');
            if (showToastNotification) {
                const baseModelCount = this.filters.baseModel.length;
                const tagsCount = this.filters.tags ? Object.keys(this.filters.tags).length : 0;

                let message = '';
                if (baseModelCount > 0 && tagsCount > 0) {
                    message = `Filtering by ${baseModelCount} base model${baseModelCount > 1 ? 's' : ''} and ${tagsCount} tag${tagsCount > 1 ? 's' : ''}`;
                } else if (baseModelCount > 0) {
                    message = `Filtering by ${baseModelCount} base model${baseModelCount > 1 ? 's' : ''}`;
                } else if (tagsCount > 0) {
                    message = `Filtering by ${tagsCount} tag${tagsCount > 1 ? 's' : ''}`;
                }

                showToast('toast.filters.applied', { message }, 'success');
            }
        } else {
            this.filterButton.classList.remove('active');
            if (showToastNotification) {
                showToast('toast.filters.cleared', {}, 'info');
            }
        }

        // Refresh duplicates with new filters
        if (window.modelDuplicatesManager) {
            if (window.modelDuplicatesManager.inDuplicateMode) {
                // In duplicate mode: refresh the duplicate list
                await window.modelDuplicatesManager.findDuplicates();
            } else {
                // Not in duplicate mode: just update badge count
                window.modelDuplicatesManager.checkDuplicatesCount();
            }
        }
    }

    async clearFilters() {
        // Clear active preset
        this.activePreset = null;
        this.presetManager.saveActivePreset(); // Persist the cleared state

        // Clear all filters
        this.filters = this.initializeFilters({
            ...this.filters,
            baseModel: [],
            tags: {},
            autoTags: {},
            license: {},
            modelTypes: [],
            loraAvailability: [],
            tagLogic: 'any'
        });

        // Clear tag search input and reset search state
        if (this.modelTagsSearchInput) {
            this.modelTagsSearchInput.value = '';
        }
        this.tagSearchQuery = '';
        if (this.tagSearchAbortController) {
            this.tagSearchAbortController.abort();
            this.tagSearchAbortController = null;
        }

        // Update tag logic toggle UI
        this.updateTagLogicToggleUI();

        // Update state
        const pageState = getCurrentPageState();
        pageState.filters = this.cloneFilters();

        // Update UI
        this.updateTagSelections();
        this.updateActiveFiltersCount();
        // Reload tag area to drop any non-top-20 tags from the deactivated preset
        if (this.tagsLoaded) {
            await this.loadTopTags();
        }
        this.presetManager.renderPresets(); // Re-render to remove active state

        // Remove from local Storage
        const storageKey = `${this.currentPage}_filters`;
        removeStorageItem(storageKey);

        // Update UI
        if (this.hasActiveFilters()) {
            this.filterButton.classList.add('active');
        } else {
            this.filterButton.classList.remove('active');
        }

        // Reload data using the appropriate method for the current page
        if (this.currentPage === 'recipes' && window.recipeManager) {
            await window.recipeManager.loadRecipes(true);
        } else if (this.currentPage === 'loras' || this.currentPage === 'checkpoints' || this.currentPage === 'embeddings') {
            await getModelApiClient().loadMoreWithVirtualScroll(true, true);
        }

        showToast('toast.filters.cleared', {}, 'info');
    }

    loadFiltersFromStorage() {
        const storageKey = `${this.currentPage}_filters`;
        const savedFilters = getStorageItem(storageKey);

        if (savedFilters) {
            try {
                // Ensure backward compatibility with older filter format
                this.filters = this.initializeFilters(savedFilters);

                // Update state with loaded filters
                const pageState = getCurrentPageState();
                pageState.filters = this.cloneFilters();

                this.updateTagSelections();
                this.updateTagLogicToggleUI();
                this.updateActiveFiltersCount();

                if (this.hasActiveFilters()) {
                    this.filterButton.classList.add('active');
                }
            } catch (error) {
                console.error(`Error loading ${this.currentPage} filters from storage:`, error);
            }
        }

        // Restore active preset after loading filters
        this.presetManager.restoreActivePreset();
    }

    hasActiveFilters() {
        const tagCount = this.filters.tags ? Object.keys(this.filters.tags).length : 0;
        const autoTagCount = this.filters.autoTags ? Object.keys(this.filters.autoTags).length : 0;
        const licenseCount = this.filters.license ? Object.keys(this.filters.license).length : 0;
        const modelTypeCount = this.filters.modelTypes.length;
        // Exclude EMPTY_WILDCARD_MARKER from base model count
        const baseModelCount = this.filters.baseModel.filter(m => m !== EMPTY_WILDCARD_MARKER).length;
        const loraAvailabilityCount = this.filters.loraAvailability?.length ?? 0;
        return (
            baseModelCount > 0 ||
            tagCount > 0 ||
            autoTagCount > 0 ||
            licenseCount > 0 ||
            modelTypeCount > 0 ||
            loraAvailabilityCount > 0
        );
    }

    initializeFilters(existingFilters = {}) {
        const source = existingFilters || {};
        return {
            ...source,
            baseModel: Array.isArray(source.baseModel) ? [...source.baseModel] : [],
            tags: this.normalizeTagFilters(source.tags),
            autoTags: this.normalizeTagFilters(source.autoTags),
            license: this.shouldShowLicenseFilters() ? this.normalizeLicenseFilters(source.license) : {},
            modelTypes: this.normalizeModelTypeFilters(source.modelTypes),
            loraAvailability: this.normalizeLoraAvailabilityFilters(source.loraAvailability),
            tagLogic: source.tagLogic || 'any'
        };
    }

    shouldShowLicenseFilters() {
        return this.currentPage !== 'recipes';
    }

    shouldShowLoraAvailabilityFilter() {
        return this.currentPage === 'recipes';
    }

    normalizeLoraAvailabilityFilters(loraAvailability) {
        // Default to no statuses selected (= no filtering)
        if (!Array.isArray(loraAvailability)) {
            return [];
        }

        const seen = new Set();
        return loraAvailability.reduce((acc, status) => {
            if (typeof status !== 'string') {
                return acc;
            }

            const normalized = status.trim().toLowerCase();
            if (!LORA_AVAILABILITY_STATUSES.includes(normalized) || seen.has(normalized)) {
                return acc;
            }

            seen.add(normalized);
            acc.push(normalized);
            return acc;
        }, []);
    }

    normalizeTagFilters(tagFilters) {
        if (!tagFilters) {
            return {};
        }

        if (Array.isArray(tagFilters)) {
            return tagFilters.reduce((acc, tag) => {
                if (typeof tag === 'string' && tag.trim().length > 0) {
                    acc[tag] = 'include';
                }
                return acc;
            }, {});
        }

        if (typeof tagFilters === 'object') {
            const normalized = {};
            Object.entries(tagFilters).forEach(([tag, state]) => {
                if (!tag) {
                    return;
                }
                const normalizedState = typeof state === 'string' ? state.toLowerCase() : '';
                if (normalizedState === 'include' || normalizedState === 'exclude') {
                    normalized[tag] = normalizedState;
                }
            });
            return normalized;
        }

        return {};
    }

    normalizeLicenseFilters(licenseFilters) {
        if (!licenseFilters || typeof licenseFilters !== 'object') {
            return {};
        }

        const normalized = {};
        Object.entries(licenseFilters).forEach(([key, state]) => {
            const normalizedState = typeof state === 'string' ? state.toLowerCase() : '';
            if (normalizedState === 'include' || normalizedState === 'exclude') {
                normalized[key] = normalizedState;
            }
        });
        return normalized;
    }

    normalizeModelTypeFilters(modelTypes) {
        if (!Array.isArray(modelTypes)) {
            return [];
        }

        const seen = new Set();
        return modelTypes.reduce((acc, type) => {
            if (typeof type !== 'string') {
                return acc;
            }

            const normalized = type.trim().toLowerCase();
            if (!normalized || seen.has(normalized)) {
                return acc;
            }

            seen.add(normalized);
            acc.push(normalized);
            return acc;
        }, []);
    }

    cloneFilters() {
        const pageState = getCurrentPageState();
        return {
            ...this.filters,
            baseModel: [...(this.filters.baseModel || [])],
            tags: { ...(this.filters.tags || {}) },
            autoTags: { ...(this.filters.autoTags || {}) },
            license: { ...(this.filters.license || {}) },
            modelTypes: [...(this.filters.modelTypes || [])],
            loraAvailability: [...(this.filters.loraAvailability || [])],
            tagLogic: this.filters.tagLogic || 'any',
            search: pageState?.filters?.search ?? ''
        };
    }

    getNextTriStateState(currentState) {
        switch (currentState) {
            case 'none':
                return 'include';
            case 'include':
                return 'exclude';
            default:
                return 'none';
        }
    }

    setTagFilterState(tagName, state) {
        if (!this.filters.tags) {
            this.filters.tags = {};
        }

        if (state === 'none') {
            delete this.filters.tags[tagName];
        } else {
            this.filters.tags[tagName] = state;
        }
    }

    applyTagElementState(element, state) {
        if (!element) {
            return;
        }

        element.classList.remove('active', 'exclude');
        if (state === 'include') {
            element.classList.add('active');
        } else if (state === 'exclude') {
            element.classList.add('exclude');
        }
    }

    // Preset management delegation methods for backward compatibility
    hasEmptyWildcardResult() {
        return this.presetManager?.hasEmptyWildcardResult() ?? false;
    }
}
