/**
 * Embedding Settings JavaScript
 * Handles the embedding configuration UI and API calls
 */

// Available providers and models loaded from API
let providerOptions = [];
let availableModels = {};
let originalValues = {};
let autoSaveListenersAttached = false;

// safeFetch is now provided by utils/safe-fetch.js loaded in base.html
// escapeHtml is the canonical window.escapeHtml from security/xss-protection.js
// (loaded first via base.html). Do NOT reintroduce a local fallback — it would
// be weaker (e.g. not escape "/") and risk a duplicate-const SyntaxError (#3706).

/**
 * Initialize the page
 */
document.addEventListener('DOMContentLoaded', function() {
    // Load available models first, then settings
    loadAvailableModels().then(() => {
        // After models are loaded, load current settings
        loadCurrentSettings();
    });

    // Setup provider change handler
    document.getElementById('embedding-provider').addEventListener('change', function() {
        updateModelOptions();
        toggleOllamaFields();
    });

    // Setup model change handler
    document.getElementById('embedding-model').addEventListener('change', updateModelDescription);

    // Setup Ollama URL change handler
    document.getElementById('ollama-url').addEventListener('input', function() {
        // Mark as changed if needed
    });
});

/**
 * Load available embedding providers and models
 */
async function loadAvailableModels() {
    try {
        const response = await safeFetch('/library/api/rag/models');
        const data = await response.json();

        if (data.success) {
            providerOptions = data.provider_options || [];
            availableModels = data.providers || {};

            // Populate provider dropdown
            populateProviders();

            // Update models for current provider (don't select yet, wait for settings)
            updateModelOptions();

            // Update provider information
            updateProviderInfo();
        } else {
            showError('Failed to load available models: ' + data.error);
        }
    } catch (error) {
        SafeLogger.error('Error loading models:', error.message || error);
        showError('Failed to load available models');
    }
}

/**
 * Load current RAG settings from database
 */
async function loadCurrentSettings() {
    try {
        const response = await safeFetch('/library/api/rag/settings');
        const data = await response.json();

        if (data.success && data.settings) {
            const settings = data.settings;

            // Set provider
            const providerSelect = document.getElementById('embedding-provider');
            if (settings.embedding_provider) {
                providerSelect.value = settings.embedding_provider;
            }

            // Update models for this provider
            updateModelOptions();

            // Set model
            const modelSelect = document.getElementById('embedding-model');
            if (settings.embedding_model) {
                modelSelect.value = settings.embedding_model;
                updateModelDescription();
            }

            // Set chunk size and overlap
            if (settings.chunk_size) {
                document.getElementById('chunk-size').value = settings.chunk_size;
            }
            if (settings.chunk_overlap) {
                document.getElementById('chunk-overlap').value = settings.chunk_overlap;
            }

            // Set new advanced settings
            if (settings.splitter_type) {
                document.getElementById('splitter-type').value = settings.splitter_type;
            }
            if (settings.distance_metric) {
                document.getElementById('distance-metric').value = settings.distance_metric;
            }
            if (settings.index_type) {
                document.getElementById('index-type').value = settings.index_type;
            }
            if (settings.normalize_vectors !== undefined) {
                document.getElementById('normalize-vectors').checked = settings.normalize_vectors;
            }
            if (settings.text_separators) {
                const textSepsEl = document.getElementById('text-separators');
                if (typeof settings.text_separators === 'string') {
                    textSepsEl.value = settings.text_separators;
                    validateTextSeparators(textSepsEl);
                } else if (settings.text_separators != null) {
                    textSepsEl.value = JSON.stringify(settings.text_separators);
                }
            }

            // Load Ollama URL from global settings
            await loadOllamaUrl();

            // Load Ollama embeddings num_ctx from global settings
            await loadOllamaNumCtx();

            // Show/hide Ollama URL field based on provider
            toggleOllamaFields();

            // Update the saved defaults display
            renderSavedDefaults(settings);

            // Store original values for change tracking (read from DOM after all fields are set)
            originalValues = {
                'local_search_embedding_provider': document.getElementById('embedding-provider').value,
                'local_search_embedding_model': document.getElementById('embedding-model').value,
                'local_search_chunk_size': parseInt(document.getElementById('chunk-size').value, 10) || 1000,
                'local_search_chunk_overlap': parseInt(document.getElementById('chunk-overlap').value, 10) || 200,
                'local_search_splitter_type': document.getElementById('splitter-type').value,
                'local_search_distance_metric': document.getElementById('distance-metric').value,
                'local_search_index_type': document.getElementById('index-type').value,
                'local_search_normalize_vectors': document.getElementById('normalize-vectors').checked,
                'local_search_text_separators': (function() {
                    try {
                        const val = document.getElementById('text-separators').value;
                        return val ? JSON.parse(val) : LDR_CONSTANTS.DEFAULT_LOCAL_SEARCH_TEXT_SEPARATORS;
                    }
                    catch {
                        return document.getElementById('text-separators').value;
                    }
                })(),
                'embeddings.ollama.url': document.getElementById('ollama-url').value,
                'embeddings.ollama.num_ctx': (function() {
                    const v = parseInt(document.getElementById('ollama-num-ctx').value, 10);
                    return Number.isNaN(v) ? 8192 : v;
                })()
            };

            // Attach auto-save listeners after original values are captured
            attachAutoSaveListeners();
        }
    } catch (error) {
        SafeLogger.error('Error loading current settings:', error);
        // Don't show error to user - just use defaults
    }
}

/**
 * Render the saved default settings display
 */
function renderSavedDefaults(settings) {
    const container = document.getElementById('saved-default-settings');
    if (!container) return;

    // Get provider display name
    const providerLabels = {
        'sentence_transformers': 'Sentence Transformers (Local)',
        'ollama': 'Ollama (Local)',
        'openai': 'OpenAI API'
    };
    const providerLabel = providerLabels[settings.embedding_provider] || settings.embedding_provider;

    // bearer:disable javascript_lang_dangerous_insert_html
    container.innerHTML = `
        <div class="ldr-info-item">
            <span class="ldr-info-label">Provider:</span>
            <span class="ldr-info-value">${escapeHtml(providerLabel)}</span>
        </div>
        <div class="ldr-info-item">
            <span class="ldr-info-label">Embedding Model:</span>
            <span class="ldr-info-value">${escapeHtml(settings.embedding_model || '')}</span>
        </div>
        <div class="ldr-info-item">
            <span class="ldr-info-label">Chunk Size:</span>
            <span class="ldr-info-value">${escapeHtml(String(settings.chunk_size ?? 1000))} characters</span>
        </div>
        <div class="ldr-info-item">
            <span class="ldr-info-label">Chunk Overlap:</span>
            <span class="ldr-info-value">${escapeHtml(String(settings.chunk_overlap ?? 200))} characters</span>
        </div>
        <div class="ldr-info-item">
            <span class="ldr-info-label">Splitter Type:</span>
            <span class="ldr-info-value">${escapeHtml(settings.splitter_type || 'recursive')}</span>
        </div>
        <div class="ldr-info-item">
            <span class="ldr-info-label">Distance Metric:</span>
            <span class="ldr-info-value">${escapeHtml(settings.distance_metric || 'cosine')}</span>
        </div>
        <div class="ldr-info-item">
            <span class="ldr-info-label">Index Type:</span>
            <span class="ldr-info-value">${escapeHtml(settings.index_type || 'flat')}</span>
        </div>
    `;
}

/**
 * Compare two values for equality, handling type coercion and objects
 */
function areValuesEqual(a, b) {
    if (a === b) return true;
    // Handle string/number comparison (e.g. "1000" vs 1000)
    if (String(a) === String(b)) return true;
    // Handle JSON comparison for arrays/objects
    try {
        return JSON.stringify(a) === JSON.stringify(b);
    } catch {
        return false;
    }
}

/**
 * Validate the text separators textarea.
 * @param {HTMLTextAreaElement} el - The text separators element
 * @returns {boolean} - Whether the value is valid
 */
function validateTextSeparators(el) {
    const rawValue = el.value.trim();

    // Clear previous error state
    el.classList.remove('ldr-field-invalid');
    const errorId = el.id + '-error';
    let errorElement = document.getElementById(errorId);
    if (errorElement) {
        errorElement.style.display = 'none';
        errorElement.textContent = '';
    }

    if (!rawValue) {
        return true;
    }

    try {
        const value = JSON.parse(rawValue);
        if (!Array.isArray(value)) {
            el.classList.add('ldr-field-invalid');
            if (!errorElement) {
                errorElement = document.createElement('div');
                errorElement.id = errorId;
                errorElement.className = 'ldr-field-error';
                errorElement.setAttribute('aria-live', 'polite');
                el.parentNode.insertBefore(errorElement, el.nextSibling);
            }
            errorElement.textContent = 'Text separators must be a JSON array';
            errorElement.style.display = 'block';
            return false;
        }
        return true;
    } catch {
        el.classList.add('ldr-field-invalid');
        if (!errorElement) {
            errorElement = document.createElement('div');
            errorElement.id = errorId;
            errorElement.className = 'ldr-field-error';
            errorElement.setAttribute('aria-live', 'polite');
            el.parentNode.insertBefore(errorElement, el.nextSibling);
        }
        errorElement.textContent = 'Invalid JSON format for text separators';
        errorElement.style.display = 'block';
        return false;
    }
}

/**
 * Format a value for display in notifications
 */
function formatValueForDisplay(value) {
    if (value === null || value === undefined || value === '') {
        return 'empty';
    } else if (typeof value === 'boolean') {
        return value ? 'enabled' : 'disabled';
    } else if (Array.isArray(value)) {
        return JSON.stringify(value);
    } else if (typeof value === 'string' && value.length > 30) {
        return value.substring(0, 28) + '...';
    }
    return String(value);
}

/**
 * Save a single setting via the settings API and show a notification
 * @param {string} key - The settings database key (e.g. 'local_search_embedding_provider')
 * @param {*} value - The new value to save
 * @param {string} displayName - Human-readable name for the notification
 * @param {*} oldValue - Previous value for old → new display
 */
async function saveSetting(key, value, displayName, oldValue) {
    // Skip no-ops
    if (oldValue !== undefined && areValuesEqual(value, oldValue)) {
        return;
    }

    try {
        const csrfToken = window.api ? window.api.getCsrfToken() : '';
        const response = await safeFetch('/settings/api/' + key, {
            method: 'PUT',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': csrfToken
            },
            body: JSON.stringify({ value })
        });

        const data = await response.json();
        if (data.error) {
            if (window.ui && window.ui.showMessage) {
                window.ui.showMessage('Failed to save ' + displayName + ': ' + data.error, 'error');
            } else {
                showError('Failed to save ' + displayName + ': ' + data.error);
            }
            return;
        }

        // Show success notification with old → new
        const oldDisplay = formatValueForDisplay(oldValue);
        const newDisplay = formatValueForDisplay(value);
        const message = displayName + ': ' + oldDisplay + ' \u2192 ' + newDisplay;

        if (window.ui && window.ui.showMessage) {
            window.ui.showMessage(message, 'success', 6000);
        } else {
            showSuccess(message);
        }

        // Update tracked original value
        originalValues[key] = value;

        // Refresh the saved defaults panel
        refreshSavedDefaults();
    } catch (error) {
        SafeLogger.error('Error saving setting ' + key + ':', error);
        if (window.ui && window.ui.showMessage) {
            window.ui.showMessage('Failed to save ' + displayName, 'error');
        } else {
            showError('Failed to save ' + displayName);
        }
    }
}

/**
 * Refresh the saved defaults display from current form values
 */
function refreshSavedDefaults() {
    let textSeps;
    try { textSeps = JSON.parse(document.getElementById('text-separators').value); }
    catch { textSeps = []; }

    renderSavedDefaults({
        embedding_provider: document.getElementById('embedding-provider').value,
        embedding_model: document.getElementById('embedding-model').value,
        chunk_size: parseInt(document.getElementById('chunk-size').value, 10) || 1000,
        chunk_overlap: parseInt(document.getElementById('chunk-overlap').value, 10) || 200,
        splitter_type: document.getElementById('splitter-type').value,
        distance_metric: document.getElementById('distance-metric').value,
        index_type: document.getElementById('index-type').value,
        normalize_vectors: document.getElementById('normalize-vectors').checked,
        text_separators: textSeps
    });
}

/**
 * Attach auto-save event listeners to all form elements.
 * Called after original values are captured from the loaded settings.
 */
function attachAutoSaveListeners() {
    if (autoSaveListenersAttached) return;
    autoSaveListenersAttached = true;

    // Provider dropdown - change (immediate)
    document.getElementById('embedding-provider').addEventListener('change', async function() {
        const value = this.value;
        const oldValue = originalValues['local_search_embedding_provider'];
        await saveSetting('local_search_embedding_provider', value, 'Embedding provider', oldValue);
        // Refresh provider status since the active provider changed
        await loadAvailableModels();
    });

    // Model dropdown - change (immediate)
    document.getElementById('embedding-model').addEventListener('change', function() {
        const value = this.value;
        const oldValue = originalValues['local_search_embedding_model'];
        saveSetting('local_search_embedding_model', value, 'Embedding model', oldValue);
    });

    // Chunk size - blur / Enter
    const chunkSizeEl = document.getElementById('chunk-size');
    function saveChunkSize() {
        const value = parseInt(chunkSizeEl.value, 10);
        if (isNaN(value) || value < 100 || value > 5000) return;
        const oldValue = originalValues['local_search_chunk_size'];
        saveSetting('local_search_chunk_size', value, 'Chunk size', oldValue);
    }
    chunkSizeEl.addEventListener('blur', saveChunkSize);
    chunkSizeEl.addEventListener('keydown', function(e) {
        if (e.key === 'Enter') { e.preventDefault(); saveChunkSize(); }
    });

    // Chunk overlap - blur / Enter
    const chunkOverlapEl = document.getElementById('chunk-overlap');
    function saveChunkOverlap() {
        const value = parseInt(chunkOverlapEl.value, 10);
        if (isNaN(value) || value < 0 || value > 1000) return;
        const oldValue = originalValues['local_search_chunk_overlap'];
        saveSetting('local_search_chunk_overlap', value, 'Chunk overlap', oldValue);
    }
    chunkOverlapEl.addEventListener('blur', saveChunkOverlap);
    chunkOverlapEl.addEventListener('keydown', function(e) {
        if (e.key === 'Enter') { e.preventDefault(); saveChunkOverlap(); }
    });

    // Splitter type - change (immediate)
    document.getElementById('splitter-type').addEventListener('change', function() {
        const value = this.value;
        const oldValue = originalValues['local_search_splitter_type'];
        saveSetting('local_search_splitter_type', value, 'Splitter type', oldValue);
    });

    // Distance metric - change (immediate)
    document.getElementById('distance-metric').addEventListener('change', function() {
        const value = this.value;
        const oldValue = originalValues['local_search_distance_metric'];
        saveSetting('local_search_distance_metric', value, 'Distance metric', oldValue);
    });

    // Index type - change (immediate)
    document.getElementById('index-type').addEventListener('change', function() {
        const value = this.value;
        const oldValue = originalValues['local_search_index_type'];
        saveSetting('local_search_index_type', value, 'Index type', oldValue);
    });

    // Normalize vectors - change (immediate)
    document.getElementById('normalize-vectors').addEventListener('change', function() {
        const value = this.checked;
        const oldValue = originalValues['local_search_normalize_vectors'];
        saveSetting('local_search_normalize_vectors', value, 'Normalize vectors', oldValue);
    });

    // Text separators - blur
    document.getElementById('text-separators').addEventListener('blur', function() {
        const rawValue = this.value.trim();
        const oldValue = originalValues['local_search_text_separators'];

        const isValid = validateTextSeparators(this);
        if (!isValid) {
            return;
        }

        if (!rawValue) {
            // Empty textarea acts as "reset to defaults" — save the default
            // array so a stale customization in the DB is overwritten. This
            // mirrors the behavior of the removed handleConfigSubmit path.
            saveSetting('local_search_text_separators', LDR_CONSTANTS.DEFAULT_LOCAL_SEARCH_TEXT_SEPARATORS, 'Text separators', oldValue);
            return;
        }

        const value = JSON.parse(rawValue);
        saveSetting('local_search_text_separators', value, 'Text separators', oldValue);
    });

    // Ollama URL - blur / Enter
    const ollamaUrlEl = document.getElementById('ollama-url');
    async function saveOllamaUrlAuto() {
        const value = ollamaUrlEl.value.trim();
        const oldValue = originalValues['embeddings.ollama.url'];
        await saveSetting('embeddings.ollama.url', value, 'Ollama URL', oldValue);
        // Refresh provider status since URL change affects reachability
        await loadAvailableModels();
    }
    ollamaUrlEl.addEventListener('blur', saveOllamaUrlAuto);
    ollamaUrlEl.addEventListener('keydown', function(e) {
        if (e.key === 'Enter') { e.preventDefault(); saveOllamaUrlAuto(); }
    });

    // Ollama embeddings num_ctx - blur / Enter
    const ollamaNumCtxEl = document.getElementById('ollama-num-ctx');
    function saveOllamaNumCtx() {
        const value = parseInt(ollamaNumCtxEl.value, 10);
        if (isNaN(value) || value < 512 || value > 131072) return;
        const oldValue = originalValues['embeddings.ollama.num_ctx'];
        saveSetting('embeddings.ollama.num_ctx', value, 'Ollama embeddings num_ctx', oldValue);
    }
    ollamaNumCtxEl.addEventListener('blur', saveOllamaNumCtx);
    ollamaNumCtxEl.addEventListener('keydown', function(e) {
        if (e.key === 'Enter') { e.preventDefault(); saveOllamaNumCtx(); }
    });
}

/**
 * Populate provider dropdown
 */
function populateProviders() {
    const providerSelect = document.getElementById('embedding-provider');
    const currentValue = providerSelect.value;

    // Clear existing options
    providerSelect.innerHTML = '';

    // Add provider options
    providerOptions.forEach(provider => {
        const option = document.createElement('option');
        option.value = provider.value;
        option.textContent = provider.available === false
            ? provider.label + ' (unavailable)'
            : provider.label;
        providerSelect.appendChild(option);
    });

    // Restore previous value if it exists
    if (currentValue && Array.from(providerSelect.options).some(opt => opt.value === currentValue)) {
        providerSelect.value = currentValue;
    } else if (providerSelect.options.length > 0) {
        providerSelect.value = providerSelect.options[0].value;
    }
}

/**
 * Update model dropdown based on selected provider
 */
function updateModelOptions() {
    const provider = document.getElementById('embedding-provider').value;
    const modelSelect = document.getElementById('embedding-model');
    const descriptionSpan = document.getElementById('model-description');

    // Remove any previous provider warning
    const oldWarning = document.getElementById('provider-unavailable-warning');
    if (oldWarning) oldWarning.remove();

    // Check if selected provider is unavailable
    const providerInfo = providerOptions.find(function(p) { return p.value === provider; });
    if (providerInfo && providerInfo.available === false) {
        const warning = document.createElement('div');
        warning.id = 'provider-unavailable-warning';
        warning.className = 'ldr-alert ldr-alert-danger';
        warning.style.marginTop = '8px';
        // bearer:disable javascript_lang_dangerous_insert_html
        warning.innerHTML = '<i class="fas fa-exclamation-triangle"></i> ' +
            escapeHtml(providerInfo.label) +
            ' is not reachable. Check the URL or service and try again.';
        const providerSelect = document.getElementById('embedding-provider');
        providerSelect.parentNode.insertBefore(warning, providerSelect.nextSibling);
    }

    // Preserve the user's current pick across rebuilds — without this the
    // synthetic change dispatch below would overwrite the saved model with
    // whatever option lands at index 0 (issue #3863).
    const previousValue = modelSelect.value;

    // Clear existing options
    modelSelect.innerHTML = '';

    // Add models for selected provider
    const models = availableModels[provider] || [];
    models.forEach(modelData => {
        const option = document.createElement('option');
        option.value = modelData.value;

        // Mark embedding compatibility when the provider supplies it
        if (modelData.is_embedding === true) {
            option.textContent = modelData.label + ' (Embedding)';
            option.style.color = 'var(--success-color)';
        } else if (modelData.is_embedding === false) {
            option.textContent = modelData.label + ' (LLM — may not support embeddings)';
            option.style.color = 'var(--warning-color)';
        } else {
            option.textContent = modelData.label;
        }

        modelSelect.appendChild(option);
    });

    if (models.length === 0) {
        const placeholder = document.createElement('option');
        placeholder.value = '';
        placeholder.textContent = 'No models available';
        placeholder.disabled = true;
        modelSelect.appendChild(placeholder);
    }

    // Restore the previously selected model if it still exists in the rebuilt
    // list; otherwise fall back to the first non-disabled option.
    if (previousValue && Array.from(modelSelect.options).some(opt => opt.value === previousValue && !opt.disabled)) {
        modelSelect.value = previousValue;
    } else if (modelSelect.options.length > 0 && !modelSelect.options[0].disabled) {
        modelSelect.value = modelSelect.options[0].value;
    }

    // Update description and model hint
    updateModelDescription();
    updateModelHint();

    // Auto-save the newly selected model when provider changes
    if (autoSaveListenersAttached && models.length > 0) {
        modelSelect.dispatchEvent(new Event('change'));
    }

    // Add change handler for model selection (remove old handler first)
    modelSelect.removeEventListener('change', onModelChange);
    modelSelect.addEventListener('change', onModelChange);
}

/**
 * Combined handler for model dropdown changes.
 */
function onModelChange() {
    updateModelDescription();
    updateModelHint();
}

/**
 * Show a contextual hint below the model dropdown based on the
 * selected model's embedding compatibility.
 */
function updateModelHint() {
    // Remove previous hint
    const oldHint = document.getElementById('model-embedding-hint');
    if (oldHint) oldHint.remove();

    const provider = document.getElementById('embedding-provider').value;
    const modelSelect = document.getElementById('embedding-model');
    const models = availableModels[provider] || [];
    const selected = models.find(function(m) { return m.value === modelSelect.value; });

    if (!selected || selected.is_embedding === undefined) return;

    const hint = document.createElement('small');
    hint.id = 'model-embedding-hint';
    hint.className = 'ldr-form-text';
    hint.style.display = 'block';
    hint.style.marginTop = '4px';

    if (selected.is_embedding === true) {
        hint.style.color = 'var(--success-color)';
        // bearer:disable javascript_lang_dangerous_insert_html
        hint.innerHTML = '<i class="fas fa-check-circle"></i> Dedicated embedding model &mdash; recommended for search and RAG.';
    } else {
        hint.style.color = 'var(--warning-color)';
        // bearer:disable javascript_lang_dangerous_insert_html
        hint.innerHTML = '<i class="fas fa-exclamation-circle"></i> This is an LLM, not a dedicated embedding model. ' +
            'Some LLMs can still produce embeddings but results vary. Use <strong>Test Embedding Model</strong> to verify.';
    }

    modelSelect.parentNode.appendChild(hint);
}

/**
 * Update model description text
 */
function updateModelDescription() {
    const provider = document.getElementById('embedding-provider').value;
    const modelSelect = document.getElementById('embedding-model');
    const descriptionSpan = document.getElementById('model-description');

    // Get selected model's label which contains the description
    const selectedOption = modelSelect.options[modelSelect.selectedIndex];
    if (selectedOption) {
        // Extract description from label (after the dash)
        const label = selectedOption.textContent;
        const parts = label.split(' - ');
        if (parts.length > 1) {
            descriptionSpan.textContent = parts.slice(1).join(' - ');
        } else {
            descriptionSpan.textContent = '';
        }
    } else {
        descriptionSpan.textContent = '';
    }
}

/**
 * Update provider information display
 */
function updateProviderInfo() {
    const providerInfo = document.getElementById('provider-info');

    let infoHTML = '';

    providerOptions.forEach(provider => {
        const providerKey = provider.value;
        const models = availableModels[providerKey] || [];

        // Add provider-specific notes
        let providerNote = '';
        if (providerKey === 'ollama') {
            const embeddingCount = models.filter(function(m) { return m.is_embedding === true; }).length;
            providerNote = `
                <div class="ldr-alert ldr-alert-info" style="margin-top: 10px; padding: 8px 12px; font-size: 0.85em;">
                    <i class="fas fa-info-circle"></i>
                    <strong>${embeddingCount} embedding model${embeddingCount !== 1 ? 's' : ''}</strong> detected
                    out of ${models.length} total.
                    <br><br>
                    <strong>Dedicated embedding models</strong> (e.g. nomic-embed-text, mxbai-embed-large)
                    are optimized for search and produce compact, high-quality vectors.
                    <br><br>
                    <strong>Some LLMs can also generate embeddings</strong> via Ollama's embed API,
                    but not all do &mdash; for example deepseek-r1 and nemotron work,
                    while qwen3 does not. LLM embeddings tend to have much higher dimensions
                    and are not optimized for similarity search.
                    <br><br>
                    Use the <strong>Test Embedding Model</strong> button above to verify
                    your selection works before saving.
                </div>
            `;
        }

        let statusIcon, statusText, statusClass;
        if (provider.available === false) {
            statusIcon = 'fa-exclamation-triangle';
            statusText = 'Not reachable';
            statusClass = 'ldr-provider-status-unavailable';
        } else {
            statusIcon = 'fa-check-circle';
            statusText = 'Ready';
            statusClass = '';
        }

        const embCount = models.filter(function(m) { return m.is_embedding === true; }).length;
        const modelCountLabel = embCount > 0
            ? `${embCount} embedding / ${models.length} total`
            : `${models.length}`;

        infoHTML += `
            <div class="ldr-stat-card">
                <h4>${escapeHtml(provider.label)}</h4>
                <p><strong>Models Available:</strong> ${modelCountLabel}</p>
                <div class="ldr-provider-status ${statusClass}">
                    <i class="fas ${statusIcon}"></i> ${statusText}
                </div>
                ${providerNote}
            </div>
        `;
    });

    // bearer:disable javascript_lang_dangerous_insert_html
    // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: variable built from escaped/numeric values above
    providerInfo.innerHTML = infoHTML;
}

/**
 * Test configuration by sending a real embedding request
 */
async function testConfiguration() {
    const provider = document.getElementById('embedding-provider').value;
    const model = document.getElementById('embedding-model').value;
    const testBtn = document.getElementById('test-config-btn');
    const testResult = document.getElementById('test-result');

    if (!provider || !model) {
        showError('Please select a provider and model first');
        return;
    }

    // Disable button during test
    testBtn.disabled = true;
    testBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Testing...';

    testResult.style.display = 'block';
    testResult.innerHTML = '<div class="ldr-alert ldr-alert-info"><i class="fas fa-spinner fa-spin"></i> Testing embedding model... For local providers this may take a moment while the model is loaded into memory.</div>';

    try {
        const csrfToken = window.api ? window.api.getCsrfToken() : '';

        // Send test embedding request with selected configuration
        const response = await safeFetch('/library/api/rag/test-embedding', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': csrfToken
            },
            body: JSON.stringify({
                provider,
                model,
                test_text: 'This is a test sentence to verify the embedding model is working correctly.'
            })
        });

        const data = await response.json();

        if (data.success) {
            const responseTime = parseInt(data.response_time_ms, 10);
            const slowHint = responseTime > 3000
                ? '<br><i class="fas fa-info-circle"></i> <em>Slow response time may be due to initial model loading. Test again for a more accurate measurement.</em>'
                : '';
            // bearer:disable javascript_lang_dangerous_insert_html
            // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: all interpolations use escapeHtml/esc, numeric coercion, or hardcoded strings
            testResult.innerHTML = `
                <div class="ldr-alert ldr-alert-success">
                    <i class="fas fa-check-circle"></i> <strong>Test Passed!</strong><br>
                    Model: ${window.XSSProtection.escapeHtml(model)}<br>
                    Provider: ${window.XSSProtection.escapeHtml(provider)}<br>
                    Embedding dimension: ${window.XSSProtection.escapeHtml(String(data.dimension))}<br>
                    Response time: ${window.XSSProtection.escapeHtml(String(data.response_time_ms))}ms
                    ${slowHint}
                </div>
            `;
            showSuccess('Embedding test passed!');
        } else {
            // bearer:disable javascript_lang_dangerous_insert_html
            testResult.innerHTML = `
                <div class="ldr-alert ldr-alert-danger">
                    <i class="fas fa-times-circle"></i> <strong>Test Failed!</strong><br>
                    Error: ${window.XSSProtection.escapeHtml(data.error || 'Unknown error')}
                </div>
            `;
            // Safe: showError escapes internally
            showError('Embedding test failed: ' + (data.error || 'Unknown error'));
        }
    } catch (error) {
        // bearer:disable javascript_lang_dangerous_insert_html
        testResult.innerHTML = `
            <div class="ldr-alert ldr-alert-danger">
                <i class="fas fa-times-circle"></i> <strong>Test Failed!</strong><br>
                Error: ${window.XSSProtection.escapeHtml(error.message)}
            </div>
        `;
        // Safe: showError escapes internally
        showError('Test failed: ' + error.message);
    } finally {
        // Re-enable button
        testBtn.disabled = false;
        testBtn.innerHTML = '<i class="fas fa-play"></i> Test Embedding Model';
    }
}

/**
 * Show success message.
 * @param {string} message - Raw, unescaped text. HTML-escaping is handled internally.
 */
function showSuccess(message) {
    const alertDiv = document.createElement('div');
    alertDiv.className = 'ldr-alert ldr-alert-success';
    // Escape message before including in HTML template
    // bearer:disable javascript_lang_dangerous_insert_html
    alertDiv.innerHTML = `<i class="fas fa-check-circle"></i>${escapeHtml(message)}`;

    // Insert at the top of the container
    const container = document.querySelector('.ldr-library-container');
    container.insertBefore(alertDiv, container.firstChild);

    // Auto-remove after 5 seconds
    setTimeout(() => {
        if (alertDiv.parentNode) {
            alertDiv.parentNode.removeChild(alertDiv);
        }
    }, 5000);
}

/**
 * Show info message.
 * @param {string} message - Raw, unescaped text. HTML-escaping is handled internally.
 */
function showInfo(message) {
    const alertDiv = document.createElement('div');
    alertDiv.className = 'ldr-alert ldr-alert-info';
    // Escape message before including in HTML template
    // bearer:disable javascript_lang_dangerous_insert_html
    alertDiv.innerHTML = `<i class="fas fa-info-circle"></i>${escapeHtml(message)}`;

    // Insert at the top of the container
    const container = document.querySelector('.ldr-library-container');
    container.insertBefore(alertDiv, container.firstChild);

    // Auto-remove after 5 seconds
    setTimeout(() => {
        if (alertDiv.parentNode) {
            alertDiv.parentNode.removeChild(alertDiv);
        }
    }, 5000);
}

/**
 * Show error message.
 * @param {string} message - Raw, unescaped text. HTML-escaping is handled internally.
 */
function showError(message) {
    const alertDiv = document.createElement('div');
    alertDiv.className = 'ldr-alert ldr-alert-danger';
    // Escape message before including in HTML template
    // bearer:disable javascript_lang_dangerous_insert_html
    alertDiv.innerHTML = `<i class="fas fa-exclamation-triangle"></i>${escapeHtml(message)}`;

    // Insert at the top of the container
    const container = document.querySelector('.ldr-library-container');
    container.insertBefore(alertDiv, container.firstChild);

    // Auto-remove after 5 seconds
    setTimeout(() => {
        if (alertDiv.parentNode) {
            alertDiv.parentNode.removeChild(alertDiv);
        }
    }, 5000);
}

/**
 * Toggle Ollama-specific fields (URL, num_ctx) based on selected provider
 */
function toggleOllamaFields() {
    const provider = document.getElementById('embedding-provider').value;
    const display = provider === 'ollama' ? 'block' : 'none';

    const ollamaUrlGroup = document.getElementById('ollama-url-group');
    if (ollamaUrlGroup) ollamaUrlGroup.style.display = display;

    const ollamaNumCtxGroup = document.getElementById('ollama-num-ctx-group');
    if (ollamaNumCtxGroup) ollamaNumCtxGroup.style.display = display;
}

/**
 * Load Ollama URL from settings
 */
async function loadOllamaUrl() {
    try {
        const response = await safeFetch('/settings/api/embeddings.ollama.url');
        const data = await response.json();

        if (data && data.value) {
            document.getElementById('ollama-url').value = data.value;
        }
    } catch (error) {
        SafeLogger.error('Error loading Ollama URL:', error);
    }
}

/**
 * Load Ollama embeddings num_ctx from settings
 */
async function loadOllamaNumCtx() {
    try {
        const response = await safeFetch('/settings/api/embeddings.ollama.num_ctx');
        const data = await response.json();

        if (data && data.value !== undefined && data.value !== null) {
            document.getElementById('ollama-num-ctx').value = data.value;
        }
    } catch (error) {
        SafeLogger.error('Error loading Ollama num_ctx:', error);
    }
}
