/**
 * qBit Manage Web UI - Configuration Form Component
 * Dynamic form generation for different configuration sections
 */
import { API } from '../api.js';
import { showToast } from '../utils/toast.js';
import { get, query, queryAll } from '../utils/dom.js';
import { CLOSE_ICON_SVG, EYE_ICON_SVG, EYE_SLASH_ICON_SVG } from '../utils/icons.js';
import { generateSectionHTML } from '../utils/form-renderer.js';
import { getNestedValue, setNestedValue, isValidHost } from '../utils/utils.js';
import { getAvailableCategories, populateCategoryDropdowns } from '../utils/categories.js';
// Import all section schemas
import { commandsSchema } from '../config-schemas/commands.js';
import { qbtSchema } from '../config-schemas/qbt.js';
import { settingsSchema } from '../config-schemas/settings.js';
import { directorySchema } from '../config-schemas/directory.js';
import { catSchema } from '../config-schemas/cat.js';
import { catChangeSchema } from '../config-schemas/cat_change.js';
import { trackerSchema } from '../config-schemas/tracker.js';
import { nohardlinksSchema } from '../config-schemas/nohardlinks.js';
import { shareLimitsSchema } from '../config-schemas/share_limits.js';
import { recyclebinSchema } from '../config-schemas/recyclebin.js';
import { orphanedSchema } from '../config-schemas/orphaned.js';
import { notificationsSchema } from '../config-schemas/notifications.js';
import { ShareLimitsComponent } from './share-limits.js';
import { escapeHtml } from '../utils/utils.js';
class ConfigForm {
    constructor(options = {}) {
        this.container = options.container;
        this.onDataChange = options.onDataChange || (() => {});
        this.onValidationChange = options.onValidationChange || (() => {});
        this.api = new API();
        this.currentSection = null;
        this.currentData = {};
        this.originalData = {}; // Store original data for reset
        this.initialSectionData = {}; // Store initial data per section
        this.validationState = { valid: true, errors: [], warnings: [] };
        this.shareLimitsComponent = null; // Store reference to share limits component
        // Store bound function references for proper event listener management
        this.boundHandleInputChange = this.handleInputChange.bind(this);
        // Map section names to their imported schemas
        this.schemas = {
            commands: commandsSchema,
            qbt: qbtSchema,
            settings: settingsSchema,
            directory: directorySchema,
            cat: catSchema,
            cat_change: catChangeSchema,
            tracker: trackerSchema,
            nohardlinks: nohardlinksSchema,
            share_limits: shareLimitsSchema,
            recyclebin: recyclebinSchema,
            orphaned: orphanedSchema,
            notifications: notificationsSchema,
        };
        this.init();
        this.bindEvents(); // Bind events once during initialization
    }
    init() {
        // No schema loading needed, as they are imported directly
    }
    async loadSection(sectionName, data = {}) {
        this.currentSection = sectionName;
        // Deep copy and preprocess data to ensure 'tag' is always an array
        this.currentData = this._preprocessComplexObjectData(sectionName, data);
        // Store initial data only once per section
        if (!this.initialSectionData[sectionName]) {
            this.initialSectionData[sectionName] = JSON.parse(JSON.stringify(this.currentData));
        }
        // Always reset to initial data when loading a section
        this.originalData = JSON.parse(JSON.stringify(this.initialSectionData[sectionName]));
        // Store original format for nohardlinks for bidirectional conversion
        if (sectionName === 'nohardlinks' && Array.isArray(data.nohardlinks_categories)) {
            this.currentData._originalNohardlinksFormat = 'array';
        } else if (sectionName === 'nohardlinks' && typeof data.nohardlinks_categories === 'object' && data.nohardlinks_categories !== null) {
            this.currentData._originalNohardlinksFormat = 'object';
        }
        // Ensure "Uncategorized" category exists for 'cat' section
        if (sectionName === 'cat') {
            if (!this.currentData.hasOwnProperty('Uncategorized')) {
                this.currentData['Uncategorized'] = [];
            }
        }
        await this.renderSection();
        // Removed this.validateSection() from here to prevent premature validation display
    }
    async renderSection() {
        if (!this.container || !this.currentSection) return;
        const sectionConfig = this.schemas[this.currentSection];
        if (!sectionConfig) {
            console.error(`No schema found for section: ${this.currentSection}`);
            this.container.innerHTML = `
Error: Configuration schema not found for section "${escapeHtml(this.currentSection)}".
`;
            return;
        }
        const html = generateSectionHTML(sectionConfig, this.currentData);
        this.container.innerHTML = html;
        // Re-bind input events after rendering new content
        this._bindInputEvents();
        // Initialize ShareLimitsComponent for share_limits section
        if (this.currentSection === 'share_limits') {
            // Wait for documentation components to be created before initializing ShareLimitsComponent
            setTimeout(() => {
                const shareLimitsContainer = this.container.querySelector('.share-limits-config');
                if (shareLimitsContainer) {
                    this.shareLimitsComponent = new ShareLimitsComponent(
                        shareLimitsContainer,
                        this.currentData,
                        (newData) => {
                            this.currentData = newData;
                            this.onDataChange(this.currentData);
                            this._dispatchDirtyEvent();
                        }
                    );
                }
            }, 150); // Wait slightly longer than the documentation component creation (100ms)
        }
        // Initialize lazy loading for notifications section
        if (this.currentSection === 'notifications') {
            this.initializeLazyLoading();
        }
        // Populate category dropdowns after rendering is complete
        await this.populateCategoryDropdowns();
    }
    initializeLazyLoading() {
        const lazyContainers = this.container.querySelectorAll('.function-webhooks-lazy');
        lazyContainers.forEach(container => {
            const placeholder = container.querySelector('.lazy-load-placeholder');
            const content = container.querySelector('.lazy-content');
            if (placeholder && content) {
                placeholder.addEventListener('click', () => {
                    placeholder.style.display = 'none';
                    content.classList.remove('hidden');
                });
            }
        });
    }
    bindEvents() {
        if (!this.container) return;
        this._bindInputEvents();
        this._bindClickEvents();
    }
    // This function should only be called once during initialization
    _bindClickEvents() {
        this.container.addEventListener('click', (e) => {
            // Handle clicks on SVG elements inside buttons by finding the closest button
            let targetElement = e.target;
            if (e.target.tagName === 'svg' || e.target.tagName === 'path') {
                const closestButton = e.target.closest('button');
                if (closestButton) {
                    targetElement = closestButton;
                }
            }
            if (targetElement.classList.contains('add-array-item')) {
                this.addArrayItem(targetElement.dataset.field);
            } else if (targetElement.classList.contains('remove-array-item')) {
                this.removeArrayItem(targetElement.closest('.array-item'));
            } else if (targetElement.classList.contains('add-category-btn')) {
                if (this.schemas[this.currentSection].type === 'dynamic-key-value-list') {
                    this.addCategory();
                }
            } else if (targetElement.classList.contains('remove-category-btn')) {
                if (this.schemas[this.currentSection].type === 'dynamic-key-value-list') {
                    this.removeCategory(targetElement.closest('.key-value-item'));
                }
            } else if (targetElement.classList.contains('password-toggle')) {
                this.togglePasswordVisibility(targetElement.dataset.target);
            } else if (targetElement.classList.contains('add-complex-object-item-btn')) {
                this.addComplexObjectItem();
            } else if (targetElement.classList.contains('remove-complex-object-item')) {
                this.removeComplexObjectItem(targetElement.dataset.key);
            } else if (targetElement.id === 'reset-section-btn') {
                this.resetSection();
            } else if (targetElement.id === 'validate-section-btn') {
                this.validateSection();
            } else if (targetElement.classList.contains('apply-to-all-btn')) {
                e.preventDefault();
                e.stopPropagation();
                const action = targetElement.dataset.action;
                if (action === 'apply-to-all') {
                    this.applyToAllWebhooks();
                }
            }
        });
    }
    _bindInputEvents() {
        // Remove existing listeners to prevent duplicates
        this.container.removeEventListener('input', this.boundHandleInputChange);
        this.container.removeEventListener('change', this.boundHandleInputChange);
        // Add new listeners using the stored bound reference
        this.container.addEventListener('input', this.boundHandleInputChange);
        this.container.addEventListener('change', this.boundHandleInputChange);
    }
    _bindClickEvents() {
        this.container.addEventListener('click', (e) => {
            // Handle clicks on SVG elements inside buttons by finding the closest button
            let targetElement = e.target;
            if (e.target.tagName === 'svg' || e.target.tagName === 'path') {
                const closestButton = e.target.closest('button');
                if (closestButton) {
                    targetElement = closestButton;
                }
            }
            if (targetElement.classList.contains('add-array-item')) {
                this.addArrayItem(targetElement.dataset.field);
            } else if (targetElement.classList.contains('remove-array-item')) {
                this.removeArrayItem(targetElement.closest('.array-item'));
            } else if (targetElement.classList.contains('add-category-btn')) {
                if (this.schemas[this.currentSection].type === 'dynamic-key-value-list') {
                    this.addCategory();
                }
            } else if (targetElement.classList.contains('remove-category-btn')) {
                if (this.schemas[this.currentSection].type === 'dynamic-key-value-list') {
                    this.removeCategory(targetElement.closest('.key-value-item'));
                }
            } else if (targetElement.classList.contains('password-toggle')) {
                this.togglePasswordVisibility(targetElement.dataset.target);
            } else if (targetElement.classList.contains('add-complex-object-item-btn')) {
                this.addComplexObjectItem();
            } else if (targetElement.classList.contains('remove-complex-object-item')) {
                this.removeComplexObjectItem(targetElement.dataset.key);
            } else if (targetElement.id === 'reset-section-btn') {
                this.resetSection();
            } else if (targetElement.id === 'validate-section-btn') {
                this.validateSection();
            } else if (targetElement.classList.contains('apply-to-all-btn')) {
                e.preventDefault();
                e.stopPropagation();
                const action = targetElement.dataset.action;
                if (action === 'apply-to-all') {
                    this.applyToAllWebhooks();
                }
            }
        });
    }
    handleInputChange(e) {
        // Ignore events from section headers
        if (e.target.classList.contains('section-subheader')) {
            return;
        }
        // Handle category key/value inputs specifically, only if the current section is a dynamic-key-value-list
        if ((e.target.classList.contains('category-key') || e.target.classList.contains('category-value')) &&
            this.schemas[this.currentSection].type === 'dynamic-key-value-list') {
            this.updateCategoryValue(e.target);
            return; // Exit after handling category specific logic
        }
        // Handle dynamic_select_text field changes
        if (e.target.classList.contains('dynamic-select')) {
            this.handleDynamicSelectChange(e.target);
            return;
        }
        // Handle complex object key input and dropdown specifically
        if (e.target.classList.contains('complex-object-key') || e.target.classList.contains('complex-object-key-dropdown')) {
            this.updateComplexObjectKey(e.target);
            return; // Exit after handling complex object key specific logic
        }
        const fieldName = e.target.name || e.target.dataset.field;
        if (!fieldName) {
            return;
        }
        // Check if this is a complex object field (e.g., "trackerKey::propName")
        const isComplexObjectField = fieldName.includes('::');
        let entryKey, propName;
        if (isComplexObjectField) {
            [entryKey, propName] = fieldName.split('::');
        }
        let value;
        if (e.target.type === 'checkbox') {
            value = e.target.checked;
        } else if (e.target.type === 'number') {
            value = e.target.value ? parseFloat(e.target.value) : null;
        } else if (e.target.classList.contains('array-item-input')) {
            const fullFieldName = e.target.name;
            const match = fullFieldName.match(/(.*)\[(\d+)\]$/);
            if (match) {
                const baseFieldName = match[1];
                const index = match[2];
                this.updateArrayValue(baseFieldName, index, e.target.value);
            } else {
                this.updateArrayValue(fieldName, e.target.dataset.index, e.target.value);
            }
            return;
        } else {
            value = e.target.value;
        }
        if (isComplexObjectField) {
            // Handle flat string values (like categories)
            if (propName === 'value' && this.schemas[this.currentSection].flatStringValues) {
                // For flat string values, store the value directly
                this.currentData[entryKey] = value;
            } else {
                // Handle object-based complex fields
                // Only create the object if we have a non-empty value to set
                if (value !== '' && value !== null && value !== undefined) {
                    if (!this.currentData[entryKey]) {
                        this.currentData[entryKey] = {};
                    }
                    this.currentData[entryKey][propName] = value;
                } else {
                    // Remove empty string values instead of setting them
                    if (this.currentData[entryKey]) {
                        delete this.currentData[entryKey][propName];
                        // If the entry object is now empty, remove it entirely
                        if (Object.keys(this.currentData[entryKey]).length === 0) {
                            delete this.currentData[entryKey];
                        }
                    }
                }
            }
        } else {
            // For regular fields, remove empty values instead of setting them
            if (value === '' || value === null || value === undefined) {
                // Use the existing setNestedValue logic to delete the field
                setNestedValue(this.currentData, fieldName, null);
            } else {
                setNestedValue(this.currentData, fieldName, value);
            }
        }
        this.onDataChange(this.currentData);
        this.validateField(fieldName, value);
        // Dispatch an event to notify that the form section is dirty
        this._dispatchDirtyEvent();
    }
    _dispatchDirtyEvent() {
        const dirtyEvent = new CustomEvent('form-dirty', {
            detail: { section: this.currentSection },
            bubbles: true,
            composed: true
        });
        this.container.dispatchEvent(dirtyEvent);
    }
    updateArrayValue(fieldName, index, value) {
        const isComplexObjectField = fieldName.includes('::');
        let currentArray;
        if (isComplexObjectField) {
            const [entryKey, propName] = fieldName.split('::');
            if (!this.currentData[entryKey]) {
                this.currentData[entryKey] = {};
            }
            currentArray = this.currentData[entryKey][propName] || [];
            currentArray[parseInt(index)] = value;
            this.currentData[entryKey][propName] = currentArray;
        } else {
            // Handle Uncategorized array
            if (fieldName === 'Uncategorized') {
                currentArray = this.currentData[fieldName] || [];
                if (!Array.isArray(currentArray)) {
                    currentArray = [currentArray];
                }
            } else {
                currentArray = getNestedValue(this.currentData, fieldName) || [];
            }
            currentArray[parseInt(index)] = value;
            setNestedValue(this.currentData, fieldName, currentArray);
        }
        this.onDataChange(this.currentData);
        this._dispatchDirtyEvent();
    }
    updateCategoryValue(input) {
        const item = input.closest('.key-value-item');
        const keyInput = item.querySelector('.category-key');
        const valueInput = item.querySelector('.category-value');
        const oldKey = item.dataset.key;
        const newKey = keyInput.value;
        const value = valueInput.value;
        if (!this.currentData) {
            this.currentData = {};
        }
        // Remove old key if changed
        if (oldKey && oldKey !== newKey) {
            delete this.currentData[oldKey];
        }
        // Set new value
        if (newKey) {
            this.currentData[newKey] = value;
            item.dataset.key = newKey;
        }
        this.onDataChange(this.currentData);
        this._dispatchDirtyEvent();
    }
    shouldArrayItemUseCategoryDropdown(fieldName) {
        // Get the field schema to check if array items should use category dropdown
        const fieldSchema = this.getFieldSchema(fieldName);
        if (!fieldSchema || fieldSchema.type !== 'array') {
            return false;
        }
        // Check if the array items have useCategoryDropdown flag
        return fieldSchema.items && fieldSchema.items.useCategoryDropdown === true;
    }
    getFieldSchema(fieldName) {
        if (!this.schema || !this.schema.fields) {
            return null;
        }
        // Handle complex object fields (containing ::)
        if (fieldName.includes('::')) {
            const [entryKey, propName] = fieldName.split('::');
            // Find the complex object field in schema
            // For share limits, we need to find the field that has the matching name
            for (const field of this.schema.fields) {
                if (field.name === entryKey && field.type === 'object' && field.properties && field.properties[propName]) {
                    return field.properties[propName];
                }
            }
            return null;
        }
        // Handle regular fields
        for (const field of this.schema.fields) {
            if (field.name === fieldName) {
                return field;
            }
        }
        return null;
    }
    addArrayItem(fieldName) {
        const arrayField = this.container.querySelector(`[data-field="${fieldName}"] .array-items`);
        let currentArray;
        if (fieldName.includes('::')) {
            const [entryKey, propName] = fieldName.split('::');
            if (!this.currentData[entryKey]) {
                this.currentData[entryKey] = {};
            }
            currentArray = this.currentData[entryKey][propName] || [];
            currentArray.push('');
            this.currentData[entryKey][propName] = currentArray;
        } else {
            if (fieldName === 'Uncategorized') {
                currentArray = this.currentData[fieldName] || [];
                if (!Array.isArray(currentArray)) {
                    currentArray = [currentArray];
                }
            } else {
                currentArray = getNestedValue(this.currentData, fieldName) || [];
            }
            currentArray.push('');
            setNestedValue(this.currentData, fieldName, currentArray);
        }
        const newIndex = currentArray.length - 1;
        // Check if this array field should use category dropdowns
        const shouldUseCategoryDropdown = this.shouldArrayItemUseCategoryDropdown(fieldName);
        let inputHTML;
        if (shouldUseCategoryDropdown) {
            inputHTML = `
                
            `;
        } else {
            inputHTML = `
                
            `;
        }
        const itemHTML = `
            
                
                
                    ${inputHTML}
                    
                
             
        `;
        arrayField.insertAdjacentHTML('beforeend', itemHTML);
        // If we added a category dropdown, populate it
        if (shouldUseCategoryDropdown) {
            this.populateCategoryDropdowns();
        }
        this.onDataChange(this.currentData);
        this._dispatchDirtyEvent();
    }
    removeArrayItem(item) {
        const fieldName = item.querySelector('.array-item-input').dataset.field;
        const index = parseInt(item.dataset.index);
        if (fieldName.includes('::')) {
            const [entryKey, propName] = fieldName.split('::');
            if (this.currentData[entryKey] && this.currentData[entryKey][propName]) {
                this.currentData[entryKey][propName].splice(index, 1);
            }
        } else {
            let currentArray;
            if (fieldName === 'Uncategorized') {
                currentArray = this.currentData[fieldName] || [];
                if (!Array.isArray(currentArray)) {
                    currentArray = [currentArray];
                }
            } else {
                currentArray = getNestedValue(this.currentData, fieldName) || [];
            }
            currentArray.splice(index, 1);
            setNestedValue(this.currentData, fieldName, currentArray);
        }
        item.remove();
        // Update indices for remaining items
        const arrayItems = this.container.querySelectorAll(`[data-field="${fieldName}"] .array-item`);
        arrayItems.forEach((arrayItem, newIndex) => {
            arrayItem.dataset.index = newIndex;
            const input = arrayItem.querySelector('.array-item-input');
            input.dataset.index = newIndex;
        });
        this.onDataChange(this.currentData);
        this._dispatchDirtyEvent();
    }
    addCategory() {
        // Check if this is a complex-object type (new category schema) or dynamic-key-value-list (old schema)
        const sectionConfig = this.schemas[this.currentSection];
        if (sectionConfig.type === 'complex-object') {
            // Use the same pattern as addComplexObjectItem for consistency
            this.addComplexObjectItem();
            return;
        }
        // Legacy code for dynamic-key-value-list type (cat_change schema)
        const categoriesContainer = this.container.querySelector('.key-value-items');
        const newKey = `category-${Date.now()}`;
        // Check if this config should use category dropdowns
        const isCatChange = this.currentSection === 'cat_change';
        const useDropdownForKey = sectionConfig.useCategoryDropdown && isCatChange;
        const useDropdownForValue = isCatChange && sectionConfig.fields && sectionConfig.fields[0] &&
                                   sectionConfig.fields[0].properties && sectionConfig.fields[0].properties.new_category &&
                                   sectionConfig.fields[0].properties.new_category.useCategoryDropdown;
        let keyInputHTML, valueInputHTML;
        // Generate key input (old category)
        if (useDropdownForKey) {
            keyInputHTML = `
                
            `;
        } else {
            keyInputHTML = `
                
            `;
        }
        // Generate value input (new category or save path)
        if (useDropdownForValue) {
            valueInputHTML = `
                
            `;
        } else {
            valueInputHTML = `
                
            `;
        }
        const itemHTML = `
            
        `;
        categoriesContainer.insertAdjacentHTML('beforeend', itemHTML);
        if (!this.currentData) {
            this.currentData = {};
        }
        this.currentData[newKey] = '';
        // Populate dropdowns for the newly added item if needed
        if (useDropdownForKey || useDropdownForValue) {
            this.populateCategoryDropdowns();
        }
        this.onDataChange(this.currentData);
        this._dispatchDirtyEvent();
    }
    removeCategory(item) {
        const key = item.dataset.key;
        if (this.currentData && key) {
            delete this.currentData[key];
        }
        item.remove();
        this.onDataChange(this.currentData);
        this._dispatchDirtyEvent();
    }
    async applyToAllWebhooks() {
        // Get the selected value from the dropdown
        const valueField = this.container.querySelector('[name="apply_to_all_value"]');
        if (!valueField) {
            showToast('Could not find apply to all value field', 'error');
            return;
        }
        let value;
        if (valueField.value === 'custom') {
            // Only show one prompt for custom URL
            const customUrl = prompt('Enter custom webhook URL:');
            if (customUrl === null) return;
            value = customUrl;
        } else {
            value = valueField.value;
        }
        // Combine both regular and function webhooks
        const fields = [
            'webhooks.error',
            'webhooks.run_start',
            'webhooks.run_end',
            'webhooks.function.recheck',
            'webhooks.function.cat_update',
            'webhooks.function.tag_update',
            'webhooks.function.rem_unregistered',
            'webhooks.function.rem_orphaned',
            'webhooks.function.tag_nohardlinks',
            'webhooks.function.tag_tracker_error',
            'webhooks.function.share_limits',
            'webhooks.function.cleanup_dirs'
        ];
        fields.forEach(field => {
            setNestedValue(this.currentData, field, value);
        });
        // Notify of data change and mark as dirty
        this.onDataChange(this.currentData);
        this._dispatchDirtyEvent();
        // Re-render the form to reflect changes
        await this.renderSection();
        showToast('Applied to all webhooks!', 'success');
    }
    async addComplexObjectItem() {
        const sectionConfig = this.schemas[this.currentSection];
        if (sectionConfig.type !== 'object' && sectionConfig.type !== 'complex-object') {
            console.error('addComplexObjectItem called for a non-object or non-complex-object schema type.');
            return;
        }
        let newKey;
        // Check if this schema should use category dropdown
        if (sectionConfig.useCategoryDropdown) {
            newKey = await this.showCategoryDropdownModal();
            if (!newKey) {
                return; // User cancelled or no selection made
            }
        } else {
            // Use schema's keyLabel and keyDescription for dynamic prompt text
            const keyLabel = sectionConfig.keyLabel || 'Tracker URL';
            const keyDescription = sectionConfig.keyDescription || keyLabel;
            const promptText = `Enter ${keyDescription}:`;
            newKey = prompt(promptText);
            if (!newKey) {
                return;
            }
        }
        // Check if key already exists
        if (this.currentData[newKey]) {
            const keyLabel = sectionConfig.keyLabel || 'Entry';
            showToast(`A ${keyLabel} with this name already exists.`, 'error');
            return;
        }
        // Handle flat string values (like categories)
        if (sectionConfig.flatStringValues) {
            if (newKey === 'Uncategorized') {
                this.currentData[newKey] = [];
            } else {
                const defaultSchema = sectionConfig.additionalProperties || sectionConfig.patternProperties[".*"];
                const defaultValue = defaultSchema?.default || '';
                this.currentData[newKey] = defaultValue;
            }
        } else {
            // Initialize with default values based on schema for object entries
            const newEntry = {};
            if (this.currentSection === 'nohardlinks') {
                newEntry.exclude_tags = [];
                newEntry.ignore_root_dir = true;
            } else {
                const defaultSchema = sectionConfig.additionalProperties || sectionConfig.patternProperties["^(?!other$).*$"];
                if (defaultSchema && defaultSchema.properties) {
                    Object.entries(defaultSchema.properties).forEach(([propName, propSchema]) => {
                        if (propSchema.default !== undefined) {
                            newEntry[propName] = propSchema.default;
                        } else if (propSchema.type === 'array') {
                            newEntry[propName] = [];
                        } else if (propSchema.oneOf) { // For 'tag' field
                            const stringSchema = propSchema.oneOf.find(s => s.type === 'string');
                            if (stringSchema && stringSchema.default !== undefined) {
                                newEntry[propName] = stringSchema.default;
                            } else {
                                newEntry[propName] = ''; // Default to empty string for 'tag'
                            }
                        } else if (defaultSchema.required && defaultSchema.required.includes(propName)) {
                            // Only set required fields to empty string, leave optional fields undefined
                            newEntry[propName] = '';
                        }
                        // Optional fields are left undefined and will not be included in the object
                    });
                }
            }
            this.currentData[newKey] = newEntry;
        }
        await this.renderSection(); // Re-render to show the new item
        this.onDataChange(this.currentData);
        this._dispatchDirtyEvent();
    }
    async removeComplexObjectItem(keyToRemove) {
        if (confirm(`Are you sure you want to remove the entry "${keyToRemove}"?`)) {
            delete this.currentData[keyToRemove];
            await this.renderSection(); // Re-render to remove the item
            this.onDataChange(this.currentData);
            this._dispatchDirtyEvent();
        }
    }
    async showCategoryDropdownModal() {
        return new Promise((resolve) => {
            // Get available categories from the API or stored data
            this.getAvailableCategories().then(categories => {
                if (!categories || categories.length === 0) {
                    showToast('No categories available. Please configure categories first.', 'warning');
                    resolve(null);
                    return;
                }
                // Create modal HTML
                const modalHTML = `
                    
                        
                            
                            
                                
                                    
                                    
                                
                             
                            
                         
                     
                `;
                // Add modal to DOM
                document.body.insertAdjacentHTML('beforeend', modalHTML);
                const modal = document.getElementById('category-dropdown-modal');
                const select = document.getElementById('category-select');
                const confirmBtn = modal.querySelector('.modal-confirm');
                const cancelBtn = modal.querySelector('.modal-cancel');
                const closeBtn = modal.querySelector('.modal-close');
                // Enable/disable confirm button based on selection
                select.addEventListener('change', () => {
                    confirmBtn.disabled = !select.value;
                });
                // Handle confirm
                confirmBtn.addEventListener('click', () => {
                    const selectedCategory = select.value;
                    modal.remove();
                    resolve(selectedCategory);
                });
                // Handle cancel/close
                const closeModal = () => {
                    modal.remove();
                    resolve(null);
                };
                cancelBtn.addEventListener('click', closeModal);
                closeBtn.addEventListener('click', closeModal);
                modal.addEventListener('click', (e) => {
                    if (e.target === modal) {
                        closeModal();
                    }
                });
                // Handle escape key
                const handleEscape = (e) => {
                    if (e.key === 'Escape') {
                        closeModal();
                        document.removeEventListener('keydown', handleEscape);
                    }
                };
                document.addEventListener('keydown', handleEscape);
            });
        });
    }
    async getAvailableCategories() {
        return getAvailableCategories();
    }
    async populateCategoryDropdowns() {
        // Find all category dropdowns in the current form
        const complexObjectDropdowns = this.container.querySelectorAll('.complex-object-key-dropdown');
        // Handle complex object key dropdowns (these have special logic)
        if (complexObjectDropdowns.length > 0) {
            try {
                const categories = await this.getAvailableCategories();
                complexObjectDropdowns.forEach(dropdown => {
                    const currentValue = dropdown.dataset.originalKey;
                    // Clear existing options except the current one
                    dropdown.innerHTML = '';
                    // Add current value as selected option
                    const currentOption = document.createElement('option');
                    currentOption.value = currentValue;
                    currentOption.textContent = currentValue;
                    currentOption.selected = true;
                    dropdown.appendChild(currentOption);
                    // Add other available categories
                    categories.forEach(category => {
                        if (category !== currentValue) {
                            const option = document.createElement('option');
                            option.value = category;
                            option.textContent = category;
                            dropdown.appendChild(option);
                        }
                    });
                });
            } catch (error) {
                console.error('Error populating complex object category dropdowns:', error);
            }
        }
        // Handle regular field category dropdowns using the utility function
        await populateCategoryDropdowns(this.container);
    }
    updateComplexObjectKey(input) {
        const item = input.closest('.complex-object-item');
        const originalKey = input.dataset.originalKey;
        const newKey = input.value;
        if (originalKey === newKey) return;
        if (this.currentData[newKey]) {
            showToast('An entry with this key already exists. Please choose a different key.', 'error');
            input.value = originalKey; // Revert input
            return;
        }
        // Create a new object with the updated key
        const updatedData = {};
        Object.entries(this.currentData).forEach(([key, value]) => {
            if (key === originalKey) {
                updatedData[newKey] = value;
            } else {
                updatedData[key] = value;
            }
        });
        this.currentData = updatedData;
        item.dataset.key = newKey; // Update data-key attribute
        input.dataset.originalKey = newKey; // Update original-key dataset
        this.onDataChange(this.currentData);
        this._dispatchDirtyEvent();
    }
    togglePasswordVisibility(targetId) {
        const input = get(targetId);
        const button = query(`[data-target="${targetId}"]`);
        if (!input) {
            console.error('Password input not found:', targetId);
            return;
        }
        if (!button) {
            console.error('Password toggle button not found for input:', targetId);
            return;
        }
        if (input.type === 'password') {
            input.type = 'text';
            button.innerHTML = EYE_SLASH_ICON_SVG;
        } else {
            input.type = 'password';
            button.innerHTML = EYE_ICON_SVG;
        }
    }
    handleDynamicSelectChange(selectElement) {
        const container = selectElement.closest('.dynamic-select-text-group');
        if (!container) {
            return;
        }
        const textInput = container.querySelector('.dynamic-text-input');
        const hiddenInput = container.querySelector('.dynamic-hidden-input');
        if (!textInput || !hiddenInput) {
            return;
        }
        // Performance optimization: use requestAnimationFrame for DOM updates
        requestAnimationFrame(() => {
            const updateValue = () => {
                if (selectElement.value === 'webhook') {
                    textInput.style.display = 'block';
                    textInput.required = true;
                    hiddenInput.value = textInput.value;
                } else {
                    textInput.style.display = 'none';
                    textInput.required = false;
                    textInput.value = '';
                    hiddenInput.value = selectElement.value;
                }
            };
            // Update the display and values
            updateValue();
            // Add input listener to text field if not already added
            if (!textInput.hasAttribute('data-listener-added')) {
                // Debounce text input to reduce excessive updates
                let debounceTimer;
                textInput.addEventListener('input', () => {
                    clearTimeout(debounceTimer);
                    debounceTimer = setTimeout(() => {
                        if (selectElement.value === 'webhook') {
                            hiddenInput.value = textInput.value;
                        }
                    }, 150); // 150ms debounce
                });
                textInput.setAttribute('data-listener-added', 'true');
            }
            // Trigger form change event for the hidden input
            const changeEvent = new Event('input', { bubbles: true });
            hiddenInput.dispatchEvent(changeEvent);
        });
    }
    resetSection() {
        if (confirm('Are you sure you want to reset this section? All changes will be lost.')) {
            this.currentData = JSON.parse(JSON.stringify(this.originalData)); // Revert to original data
            this.loadSection(this.currentSection, this.originalData); // Load section with original data
            this.onDataChange(this.currentData);
            // Dispatch an event to notify that the form section has been reset
            const resetEvent = new CustomEvent('form-reset', {
                detail: { section: this.currentSection },
                bubbles: true,
                composed: true
            });
            this.container.dispatchEvent(resetEvent);
        }
    }
    /**
     * Preprocesses complex object data to ensure array fields (like 'tag') are always arrays.
     * @param {string} sectionName - The name of the current section.
     * @param {object} data - The raw data loaded for the section.
     * @returns {object} The preprocessed data.
     */
    _preprocessComplexObjectData(sectionName, data) {
        const processedData = JSON.parse(JSON.stringify(data)); // Deep copy to avoid modifying original data
        // Skip preprocessing for multi-root-object sections
        const sectionConfig = this.schemas[sectionName];
        if (sectionConfig && sectionConfig.type === 'multi-root-object') {
            return processedData;
        }
        if (sectionConfig && sectionConfig.type === 'fixed-object-config') {
            const mainFieldName = sectionConfig.fields[0]?.name;
            const mainFieldProperties = sectionConfig.fields[0]?.properties || {};
            const sectionData = data[mainFieldName] || {};
            if (sectionName === 'nohardlinks') {
                if (Array.isArray(sectionData)) {
                    // Handle array format: ["RadarrComplete", "RadarrComplete4k", ...]
                    const newNohardlinksCategories = {};
                    sectionData.forEach(categoryItem => {
                        if (typeof categoryItem === 'string') {
                            // Simple string category name
                            newNohardlinksCategories[categoryItem] = {
                                exclude_tags: [],
                                ignore_root_dir: true
                            };
                        } else if (typeof categoryItem === 'object') {
                            // Object with category name as key and properties as value
                            // Format: [{ "RadarrComplete": { exclude_tags: [...], ignore_root_dir: true } }]
                            for (const [categoryName, categoryProps] of Object.entries(categoryItem)) {
                                newNohardlinksCategories[categoryName] = {
                                    exclude_tags: categoryProps?.exclude_tags || [],
                                    ignore_root_dir: categoryProps?.ignore_root_dir !== undefined ? categoryProps.ignore_root_dir : true
                                };
                            }
                        }
                    });
                    processedData[mainFieldName] = newNohardlinksCategories;
                } else if (typeof sectionData === 'object' && sectionData !== null) {
                    // Handle object format: { "RadarrComplete": { ... }, ... }
                    const newNohardlinksCategories = {};
                    Object.entries(sectionData).forEach(([categoryName, categoryProps]) => {
                        newNohardlinksCategories[categoryName] = {
                            exclude_tags: categoryProps?.exclude_tags || [],
                            ignore_root_dir: categoryProps?.ignore_root_dir !== undefined ? categoryProps.ignore_root_dir : true
                        };
                    });
                    processedData[mainFieldName] = newNohardlinksCategories;
                }
            } else if (mainFieldName) {
                 // For other fixed-object-config sections, iterate through properties
                 Object.entries(sectionData).forEach(([entryKey, entryValue]) => {
                    const entryProperties = mainFieldProperties[entryKey]?.properties;
                    if (entryProperties) {
                        Object.entries(entryValue).forEach(([propName, propValue]) => {
                            if (entryProperties[propName]?.type === 'array' && !Array.isArray(propValue)) {
                                processedData[mainFieldName][entryKey][propName] = propValue ? [propValue] : [];
                            }
                        });
                    }
                });
            }
        } else if (sectionConfig && sectionConfig.type === 'complex-object' && sectionConfig.patternProperties) {
            // Handle special case for nohardlinks which can have both array and object formats
            if (sectionName === 'nohardlinks') {
                // Check if the data is in array format and convert it to object format
                if (Array.isArray(processedData)) {
                    const newNohardlinksData = {};
                    processedData.forEach(categoryItem => {
                        if (typeof categoryItem === 'string') {
                            // Simple string category name
                            newNohardlinksData[categoryItem] = {
                                exclude_tags: [],
                                ignore_root_dir: true
                            };
                        } else if (typeof categoryItem === 'object') {
                            // Object with category name as key and properties as value
                            for (const [categoryName, categoryProps] of Object.entries(categoryItem)) {
                                newNohardlinksData[categoryName] = {
                                    exclude_tags: categoryProps?.exclude_tags || [],
                                    ignore_root_dir: categoryProps?.ignore_root_dir !== undefined ? categoryProps.ignore_root_dir : true
                                };
                            }
                        }
                    });
                    return newNohardlinksData;
                } else if (typeof processedData === 'object' && processedData !== null) {
                    // Handle object format: ensure all entries have proper structure
                    Object.entries(processedData).forEach(([categoryName, categoryProps]) => {
                        // Handle cases where category props are null, undefined, or an empty object
                        if (categoryProps === null || categoryProps === undefined || (typeof categoryProps === 'object' && Object.keys(categoryProps).length === 0)) {
                            processedData[categoryName] = {
                                exclude_tags: [],
                                ignore_root_dir: true
                            };
                        } else if (typeof categoryProps === 'object') {
                            processedData[categoryName] = {
                                exclude_tags: categoryProps.exclude_tags || [],
                                ignore_root_dir: categoryProps.ignore_root_dir !== undefined ? categoryProps.ignore_root_dir : true
                            };
                        }
                    });
                }
            } else {
                // This is for sections like 'tracker'
                Object.entries(processedData).forEach(([entryKey, entryValue]) => {
                    if (entryValue && typeof entryValue === 'object') {
                        // Find the matching schema for the current entry
                        let schemaProperties;
                        if (entryKey === 'other' && sectionConfig.patternProperties.other) {
                            schemaProperties = sectionConfig.patternProperties.other.properties;
                        } else if (sectionConfig.patternProperties["^(?!other$).*$"]) {
                            schemaProperties = sectionConfig.patternProperties["^(?!other$).*$"].properties;
                        } else if (sectionConfig.patternProperties[".*"]) {
                            schemaProperties = sectionConfig.patternProperties[".*"].properties;
                        }
                        if (schemaProperties) {
                            Object.entries(schemaProperties).forEach(([propName, propSchema]) => {
                                if (propSchema.type === 'array' && !Array.isArray(entryValue[propName])) {
                                    entryValue[propName] = entryValue[propName] ? [entryValue[propName]] : [];
                                } else if (propSchema.oneOf) { // Handle fields like 'tag'
                                    const isArray = propSchema.oneOf.some(s => s.type === 'array');
                                    if (isArray && !Array.isArray(entryValue[propName])) {
                                        entryValue[propName] = entryValue[propName] ? [entryValue[propName]] : [];
                                    }
                                } else if (propSchema.type === 'string' && (entryValue[propName] === null || entryValue[propName] === undefined || (typeof entryValue[propName] === 'object' && Object.keys(entryValue[propName]).length === 0))) {
                                    // Handle optional string fields that might be empty objects - convert to empty string
                                    entryValue[propName] = '';
                                }
                            });
                        }
                    }
                });
            }
        }
        return processedData;
    }
    _postprocessDataForSave(sectionName, data) {
        const sectionConfig = this.schemas[sectionName];
        if (sectionConfig && sectionConfig.type === 'multi-root-object') {
            const finalData = {};
            Object.keys(data).forEach(key => {
                // Filter out UI-only fields for notifications section
                if (sectionName === 'notifications' && key === 'apply_to_all_value') {
                    return; // Skip this field
                }
                this._setNestedValue(finalData, key, data[key]);
            });
            return finalData;
        }
        const processedData = { ...data };
        // Filter out UI-only fields for notifications section
        if (sectionName === 'notifications') {
            delete processedData.apply_to_all_value;
        }
        if (sectionName === 'nohardlinks' && processedData._originalNohardlinksFormat === 'array' && typeof processedData.nohardlinks_categories === 'object') {
            processedData.nohardlinks_categories = Object.keys(processedData.nohardlinks_categories);
        }
        delete processedData._originalNohardlinksFormat;
        return processedData;
    }
    /**
     * Helper method to flatten nested objects into dot notation
     * @param {object} obj - The object to flatten
     * @param {string} prefix - The prefix for the keys
     * @param {object} result - The result object to populate
     */
    _flattenObject(obj, prefix, result) {
        Object.keys(obj).forEach(key => {
            const value = obj[key];
            const newKey = prefix ? `${prefix}.${key}` : key;
            if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
                // Recursively flatten nested objects
                this._flattenObject(value, newKey, result);
            } else {
                // Set the flattened key-value pair
                result[newKey] = value;
            }
        });
    }
    /**
     * Helper method to set nested values in an object using dot notation
     * @param {object} obj - The target object
     * @param {string} path - The dot notation path (e.g., 'function.cat_update')
     * @param {*} value - The value to set
     */
    _setNestedValue(obj, path, value) {
        const parts = path.split('.');
        let current = obj;
        for (let i = 0; i < parts.length - 1; i++) {
            const part = parts[i];
            // Prevent prototype pollution
            if (part === '__proto__' || part === 'constructor' || part === 'prototype') {
                return; // Or throw new Error('Prototype pollution attempt detected');
            }
            // If the current part doesn't exist or isn't a plain object, create a new object
            if (!Object.prototype.hasOwnProperty.call(current, part) ||
                typeof current[part] !== 'object' ||
                current[part] === null) {
                current[part] = {};
            }
            current = current[part];
        }
        // Prevent prototype pollution on the final property
        const lastPart = parts[parts.length - 1];
        if (lastPart === '__proto__' || lastPart === 'constructor' || lastPart === 'prototype') {
            return; // Or throw new Error('Prototype pollution attempt detected');
        }
        current[lastPart] = value;
    }
    /**
     * Recursively removes empty strings, empty objects, and empty arrays from a configuration object.
     * @param {object} obj - The object to clean up.
     * @returns {object} The cleaned up object.
     */
    /**
     * Collects all current form values for a section, not just dirty ones
     * This is used for sections like commands where we want to save all values
     */
    collectAllFormValues(sectionName) {
        const sectionConfig = this.schemas[sectionName];
        if (!sectionConfig || !sectionConfig.fields) {
            return {};
        }
        const allValues = {};
        // Iterate through all fields in the schema
        sectionConfig.fields.forEach(field => {
            if (field.name && field.type !== 'documentation' && field.type !== 'section_header') {
                // Find the corresponding form input
                const input = this.container.querySelector(`[name="${field.name}"]`);
                if (input) {
                    let value;
                    if (input.type === 'checkbox') {
                        value = input.checked;
                    } else if (input.type === 'number') {
                        value = input.value ? parseFloat(input.value) : null;
                    } else {
                        value = input.value;
                    }
                    // Handle default values for boolean fields
                    if (field.type === 'boolean' && value === null) {
                        value = field.default || false;
                    }
                    allValues[field.name] = value;
                } else if (field.default !== undefined) {
                    // Use default value if input not found
                    allValues[field.name] = field.default;
                }
            }
        });
        return allValues;
    }
    cleanupEmptyValues(obj) {
        if (obj === null || obj === undefined) {
            return null;
        }
        if (typeof obj !== 'object') {
            return obj;
        }
        if (Array.isArray(obj)) {
            const newArr = obj
                .map(v => this.cleanupEmptyValues(v))
                .filter(v => v !== null && v !== '' && (!Array.isArray(v) || v.length > 0));
            return newArr.length > 0 ? newArr : null;
        }
        const newObj = {};
        for (const key in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                // Skip internal properties and UI-only fields
                if (key === '_originalNohardlinksFormat' || key === 'apply_to_all_value') continue;
                const value = this.cleanupEmptyValues(obj[key]);
                // Special handling for notification sections - preserve them even if they appear empty
                // This ensures that sections like 'notifiarr', 'apprise', 'webhooks' are not removed
                if (['notifiarr', 'apprise', 'webhooks'].includes(key)) {
                    // Always preserve these sections, even if they appear empty
                    newObj[key] = value || {};
                } else if (value !== null && value !== '' && (!Array.isArray(value) || value.length > 0)) {
                    newObj[key] = value;
                }
            }
        }
        // Don't return null for empty objects if they contain notification sections
        // or if this might be a root-level config object
        const hasNotificationSections = ['notifiarr', 'apprise', 'webhooks'].some(section => section in newObj);
        if (hasNotificationSections || Object.keys(newObj).length > 0) {
            return newObj;
        }
        return null;
    }
    async validateSection() {
        const sectionConfig = this.schemas[this.currentSection];
        this.validationState = { valid: true, errors: [], warnings: [] };
        if (!sectionConfig) {
            this.updateValidationDisplay();
            return;
        }
        // We will validate the entire currentData object, as some sections
        // are nested (e.g., nohardlinks.nohardlinks_categories)
        const dataToValidate = this.currentData;
        (sectionConfig.fields || []).forEach(field => {
            this.validateField(field.name, getNestedValue(dataToValidate, field.name));
        });
        // Specific validation for complex object types
        if (sectionConfig.type === "complex-object") {
            // Find properties to validate
        }
        this.onValidationChange(this.validationState);
        this.updateValidationDisplay();
        if (this.validationState.errors.length === 0) {
            // Perform backend validation which may add default values
            try {
                const response = await this.api.validateConfig(this.currentSection, this.currentData);
                if (response.valid) {
                    // Check if config was modified during validation
                    if (response.config_modified) {
                        showToast('Configuration validated successfully! Default values have been added.', 'success');
                        // Reload the configuration data from the server to reflect changes
                        try {
                            const configResponse = await this.api.getConfig(this.currentSection);
                            if (configResponse && configResponse.data) {
                                // Update current data with the modified config
                                this.currentData = this._preprocessComplexObjectData(this.currentSection, configResponse.data);
                                // Store initial data only once per section
                                if (!this.initialSectionData[this.currentSection]) {
                                    this.initialSectionData[this.currentSection] = JSON.parse(JSON.stringify(this.currentData));
                                }
                                // Always reset to initial data when loading a section
                                this.originalData = JSON.parse(JSON.stringify(this.initialSectionData[this.currentSection]));
                                // Re-render the section with updated data
                                await this.renderSection();
                                // Notify parent component of data change
                                this.onDataChange(this.currentData);
                            }
                        } catch (reloadError) {
                            console.error('Error reloading config after validation:', reloadError);
                            showToast('Configuration validated but failed to reload updated data.', 'warning');
                        }
                    } else {
                        showToast('Configuration validated successfully!', 'success');
                    }
                } else {
                    // Handle validation errors from backend
                    this.validationState.valid = false;
                    this.validationState.errors = response.errors || [];
                    this.validationState.warnings = response.warnings || [];
                    this.onValidationChange(this.validationState);
                    this.updateValidationDisplay();
                }
            } catch (error) {
                console.error('Error during backend validation:', error);
                showToast('Failed to validate configuration with backend.', 'error');
            }
        }
    }
    validateField(fieldName, value) {
        // This is a simplified validation logic.
        // A more robust implementation would deeply check the schema.
        const sectionConfig = this.schemas[this.currentSection];
        if (!sectionConfig) return;
        let fieldSchema;
        if (sectionConfig.type === 'fixed-object-config') {
            // For schemas like nohardlinks, the fields are under a nested property
            const mainFieldName = sectionConfig.fields[0]?.name;
            const mainFieldProperties = sectionConfig.fields[0]?.properties || {};
            // This is still not quite right for deeply nested fields.
            // For now, let's assume simple structure within fixed-object-config
            fieldSchema = mainFieldProperties[fieldName];
        } else {
            fieldSchema = sectionConfig.fields?.find(f => f.name === fieldName);
        }
        if (fieldSchema && fieldSchema.required && (value === null || value === undefined || value === '')) {
            const error = `Field "${fieldSchema.label}" is required.`;
            if (!this.validationState.errors.includes(error)) {
                this.validationState.errors.push(error);
            }
            this.validationState.valid = false;
        }
        // Example custom validation for qbt host
        if (this.currentSection === 'qbt' && fieldName === 'host' && value && !isValidHost(value)) {
            const error = `Invalid host format for "${fieldName}". Should be http(s)://hostname:port.`;
            if (!this.validationState.errors.includes(error)) {
                this.validationState.errors.push(error);
            }
             this.validationState.valid = false;
        }
    }
    updateValidationDisplay() {
        // Clear previous messages
        queryAll('.field-validation').forEach(el => el.textContent = '');
        const sectionValidationEl = this.container.querySelector('.section-validation');
        if (sectionValidationEl) {
            sectionValidationEl.innerHTML = '';
        }
        if (this.validationState.errors.length > 0) {
            const errorList = this.validationState.errors.map(err => `${err}`).join('');
            if (sectionValidationEl) {
                sectionValidationEl.innerHTML = ``;
            }
        }
         if (this.validationState.warnings.length > 0) {
            const warningList = this.validationState.warnings.map(warn => `${warn}`).join('');
            if (sectionValidationEl) {
                sectionValidationEl.innerHTML += ``;
            }
        }
    }
}
export { ConfigForm };