<!--
Copyright (C) 2026 Stefy Lanza <stefy@nexlab.net>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
-->
{% extends "base.html" %}

{% block title %}My Providers - AISBF Dashboard{% endblock %}

{% block content %}
<h2 style="margin-bottom: 30px;" data-i18n="user_providers_page.title">My Providers</h2>

{% if success %}
<div class="alert alert-success">{{ success }}</div>
{% endif %}

{% if error %}
<div class="alert alert-error">{{ error }}</div>
{% endif %}

<!-- New Provider Form (hidden by default) -->
<div id="new-provider-form" style="display: none; background: var(--bg-panel); border: 2px solid var(--color-link); border-radius: 8px; padding: 20px; margin-bottom: 20px;">
    <h3 style="margin: 0 0 20px 0; color: var(--color-link);" data-i18n="user_providers_page.add_new">Add New Provider</h3>
    
    <div class="form-group">
        <label data-i18n="providers.provider_key_label">Provider Key (unique identifier, e.g., "gemini", "openai", "kiro")</label>
        <input type="text" id="new-provider-key" placeholder="Enter provider key" style="width: 100%;">
        <small style="color: var(--color-muted); display: block; margin-top: 5px;">This will be used as the provider ID in the configuration and API endpoints</small>
    </div>
    
    <div class="form-group">
        <label data-i18n="providers.provider_type">Provider Type</label>
        <select id="new-provider-type" onchange="updateNewProviderDefaults()">
            <option value="openai">OpenAI</option>
            <option value="google">Google</option>
            <option value="anthropic">Anthropic</option>
            <option value="ollama">Ollama</option>
            <option value="kiro">Kiro (Amazon Q Developer)</option>
            <option value="claude">Claude (OAuth2)</option>
            <option value="kilocode">Kilocode (OAuth2)</option>
            <option value="qwen">Qwen (OAuth2)</option>
            <option value="codex">Codex (OpenAI OAuth2)</option>
        </select>
        <small style="color: var(--color-muted); display: block; margin-top: 5px;">Select the type of provider to configure appropriate settings</small>
    </div>
    
    <div id="new-provider-type-info" style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
        <p style="margin: 0; color: var(--color-muted);" id="new-provider-type-description">
            OpenAI-compatible provider. Uses standard API key authentication.
        </p>
    </div>

    <div id="new-provider-claude-warning" style="background: var(--alert-info-bg); border: 2px solid var(--alert-info-border); border-radius: 8px; padding: 20px; margin-bottom: 15px; display: none;">
        <h4 style="margin: 0 0 10px 0; color: var(--alert-info-fg);">⚠️ Important Notice for Claude Provider</h4>
        <p style="margin: 0; color: var(--color-text);">
            Claude.ai policies state that unofficial clients are not allowed. By using AISBF as a client in <strong>HTTP API / OAuth2 mode</strong>, you acknowledge that:
            <br><br>• While we do our best to mimic the claude-code CLI, using an unofficial client can lead to account suspension or cancellation
            <br>• Claude.ai detects prompt patterns to identify unofficial client usage, so it may not work with complex agents and prompts
            <br>• You use this software at your own risk and responsibility
            <br>• We are not affiliated with Anthropic and cannot guarantee compatibility or continued functionality
            <br><br>✅ <strong>CLI mode is different:</strong> when "Use Claude CLI mode" is enabled, AISBF proxies requests through the official <code>claude</code> binary using <code>claude -p</code>, which is the intended programmatic use of the official Anthropic CLI and is permitted by Claude's terms of service.
        </p>
    </div>

    <div id="new-provider-extension-prompt" style="display: none; background: #2a1a00; border: 2px solid #f59e0b; border-radius: 8px; padding: 15px; margin-bottom: 15px;">
        <h5 style="margin: 0 0 8px 0; color: #f59e0b;">🧩 Chrome Extension Required for Claude OAuth2</h5>
        <p style="margin: 0 0 10px 0; color: #fcd34d; font-size: 0.9em;">
            Claude's OAuth2 flow redirects your browser to <code style="background:#1a1000;padding:1px 4px;border-radius:3px;">http://localhost:54545/callback</code>
            to deliver the auth token. When you access AISBF remotely, that callback goes to <em>your machine</em>, not the server.
            <br><br>
            The AISBF Chrome Extension intercepts that callback and forwards it to this server automatically.
            Once installed, it self-configures — no setup needed. Reload this page after installing.
        </p>
        <a id="new-provider-extension-download" href="" download
           style="display: inline-block; padding: 8px 16px; background: #2d6a4f; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 600;">
            ⬇️ Download Chrome Extension (.zip)
        </a>
        <small style="color: var(--color-muted); display: block; margin-top: 8px;">
            In Chrome: go to <strong>chrome://extensions</strong>, enable Developer Mode, click "Load unpacked", and select the extracted folder.
        </small>
    </div>

    <div style="display: flex; gap: 10px;">
        <button type="button" class="btn" onclick="confirmAddProvider()">Create Provider</button>
        <button type="button" class="btn btn-secondary" onclick="cancelAddProvider()">Cancel</button>
    </div>
</div>

<div style="margin-bottom: 15px; display: flex; gap: 10px; align-items: center;">
    <input type="text" id="providers-search" placeholder="Search providers by key or name..."
           oninput="providerSearchFilter = this.value; currentProviderPage = 0; renderProvidersList();"
           style="flex:1;padding:8px;border:1px solid var(--color-border);border-radius:3px;background:var(--bg-page);color:var(--color-text);font-size:14px;">
    <span id="providers-count" style="color:var(--color-muted);font-size:13px;white-space:nowrap;"></span>
</div>

<div id="providers-list" style="margin-bottom: 10px;">
    <!-- Provider list will be rendered here -->
</div>

<div id="providers-pagination" style="margin-bottom: 20px;"></div>

<button type="button" class="btn" id="add-provider-btn" onclick="showAddProviderForm()" style="margin-top: 10px;">Add Provider</button>
<div style="display: flex; gap: 10px; margin-top: 20px;">
    <button type="button" class="btn" onclick="saveProviders()">Save Configuration</button>
    <a href="{{ url_for(request, '/dashboard') }}" class="btn btn-secondary">Cancel</a>
</div>

<style>
.provider-item {
    animation: fadeIn 0.3s;
}

.provider-header:hover {
    background: var(--bg-accent);
}

@keyframes fadeIn {
    from { opacity: 0; transform: translateY(-10px); }
    to { opacity: 1; transform: translateY(0); }
}

.form-group {
    margin-bottom: 15px;
}

.form-group label {
    display: block;
    margin-bottom: 5px;
    font-weight: 500;
    color: var(--color-text);
}

.form-group input[type="text"],
.form-group input[type="password"],
.form-group input[type="number"],
.form-group select {
    width: 100%;
    padding: 8px;
    border: 1px solid var(--bg-accent);
    border-radius: 3px;
    font-size: 14px;
    background: var(--bg-page);
    color: var(--color-text);
}

.form-group input[type="checkbox"] {
    margin-right: 5px;
}
</style>
{% endblock %}

{% block extra_js %}
<script src="{{ url_for(request, '/dashboard/static/dragsort.js') }}"></script>
<script>
const CLAUDE_CLI_MODE = {{ 'true' if claude_cli_mode else 'false' }};
const IS_LOCAL_CLIENT = {{ 'true' if is_local_client else 'false' }};
const BASE_PATH = {{ (request.scope.get('root_path', '') or '') | tojson }};
// Marker used by the AISBF Chrome Extension to auto-detect this page and configure itself.
window.AISBF_PROVIDERS_PAGE = { serverUrl: window.location.origin + BASE_PATH };

// Global cache settings cache (pun intended)
let cacheSettings = [];

async function setCacheSetting(provider_id, model_name, enabled) {
    try {
        const response = await fetch('{{ url_for(request, "/dashboard/api/cache-settings") }}', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                provider_id: provider_id || null,
                model_name: model_name || null,
                cache_enabled: enabled
            })
        });
        
        if (response.ok) {
            showToast('Cache setting updated', 'success');
            // Refresh cache settings
            await loadCacheSettings();
        } else {
            showToast('Failed to update cache setting', 'danger');
        }
    } catch (error) {
        console.error('Error updating cache setting:', error);
        showToast('Error updating cache setting', 'danger');
    }
}

async function loadCacheSettings() {
    try {
        const response = await fetch('{{ url_for(request, "/dashboard/api/cache-settings") }}', {
            credentials: 'same-origin'
        });
        const data = await response.json();
        cacheSettings = data.settings || [];
    } catch (error) {
        console.error('Error loading cache settings:', error);
    }
}

// Check if cache is enabled for a provider/model
function isCacheEnabled(provider_id, model_name = null) {
    // Check model-level setting first
    if (model_name) {
        const modelSetting = cacheSettings.find(s => s.provider_id === provider_id && s.model_name === model_name);
        if (modelSetting) return modelSetting.cache_enabled;
    }
    
    // Check provider-level setting
    const providerSetting = cacheSettings.find(s => s.provider_id === provider_id && s.model_name === null);
    if (providerSetting) return providerSetting.cache_enabled;
    
    // Check global setting
    const globalSetting = cacheSettings.find(s => s.provider_id === null && s.model_name === null);
    if (globalSetting) return globalSetting.cache_enabled;
    
    // Default: enabled
    return true;
}

function showToast(message, type) {
    const alertDiv = document.createElement('div');
    alertDiv.style.cssText = `
        position: fixed;
        top: 20px;
        left: 50%;
        transform: translateX(-50%);
        z-index: 10000;
        min-width: 400px;
        max-width: calc(100vw - 16px);
        padding: 20px 30px;
        border-radius: 6px;
        box-shadow: 0 4px 16px rgba(0,0,0,0.4);
        ${type === 'success' ? 'background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%); color: white; border: 2px solid #27ae60;' : ''}
        ${type === 'danger'  ? 'background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); color: white; border: 2px solid #e74c3c;' : ''}
        ${type === 'warning' ? 'background: linear-gradient(135deg, #f39c12 0%, #e67e22 100%); color: white; border: 2px solid #f39c12;' : ''}
    `;
    const icon = type === 'success' ? 'fa-check-circle' : type === 'danger' ? 'fa-times-circle' : 'fa-exclamation-triangle';
    alertDiv.innerHTML = `<i class="fas ${icon}" style="font-size: 24px; margin-right: 10px; vertical-align: middle;"></i><span style="vertical-align: middle;">${message}</span>`;
    if (!document.getElementById('alertAnimations')) {
        const style = document.createElement('style');
        style.id = 'alertAnimations';
        style.textContent = `
            @keyframes slideDown { from { opacity:0; transform:translateX(-50%) translateY(-20px); } to { opacity:1; transform:translateX(-50%) translateY(0); } }
            @keyframes slideUp   { from { opacity:1; transform:translateX(-50%) translateY(0); } to { opacity:0; transform:translateX(-50%) translateY(-20px); } }
        `;
        document.head.appendChild(style);
    }
    alertDiv.style.animation = 'slideDown 0.3s ease-out';
    document.body.appendChild(alertDiv);
    setTimeout(() => {
        alertDiv.style.animation = 'slideUp 0.3s ease-out';
        setTimeout(() => alertDiv.remove(), 300);
    }, 4000);
}

function escHtmlAttr(s) {
    return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}

async function apiCall(method, url, body) {
    const opts = { method, headers: { 'Content-Type': 'application/json' } };
    if (body !== undefined) opts.body = JSON.stringify(body);
    const r = await fetch(url, opts);
    return r.json();
}

let providersData = {};
let expandedProviders = new Set();
let currentProviderPage = 0;
const PROVIDERS_PAGE_SIZE = 10;
let providerSearchFilter = '';
let rawProviders = {{ user_providers_json | replace("</script>", "<\\/script>") | safe }};
let providerMasterOrder = [];
let _providerDS = null;

// Convert user providers format to the format expected by the UI
rawProviders.forEach(provider => {
    providersData[provider.provider_id] = provider.config;
});
providerMasterOrder = rawProviders.map(p => p.provider_id);

// Chunk size: 512KB chunks for maximum compatibility with restrictive proxies
const CHUNK_SIZE = 512 * 1024;

// ===== Provider Usage Indicators =====
const _usageCache = {};
const USAGE_POLL_INTERVAL = 15000;

function _providerSupportsUsage(key) {
    const p = providersData[key];
    return p && p.type === 'codex';
}

function _fmtSeconds(s) {
    if (!s || s <= 0) return '0s';
    const h = Math.floor(s/3600), m = Math.floor((s%3600)/60), sec = s%60;
    if (h > 0) return `${h}h${m > 0 ? ' '+m+'m' : ''}`;
    if (m > 0) return `${m}m${sec > 0 ? ' '+sec+'s' : ''}`;
    return `${sec}s`;
}

function _windowLabel(seconds) {
    if (!seconds || seconds <= 0) return 'Window';
    if (seconds < 120) return 'Session';
    const MIN = 60, HOUR = 3600, DAY = 86400, WEEK = 604800, MONTH = 2592000;
    if (seconds % MONTH === 0) return seconds === MONTH ? 'Monthly' : `${seconds/MONTH}mo`;
    if (seconds % WEEK  === 0) return seconds === WEEK  ? 'Weekly'  : `${seconds/WEEK}wk`;
    if (seconds % DAY   === 0) return seconds === DAY   ? 'Daily'   : `${seconds/DAY}d`;
    if (seconds % HOUR  === 0) return seconds === HOUR  ? 'Hourly'  : `${seconds/HOUR}h`;
    if (seconds % MIN   === 0) return `${seconds/MIN}min`;
    if (seconds >= MONTH * 0.9) return `${Math.round(seconds/MONTH)}mo`;
    if (seconds >= WEEK  * 0.9) return 'Weekly';
    if (seconds >= DAY)         return `${Math.round(seconds/DAY)}d`;
    if (seconds >= HOUR)        return `${Math.round(seconds/HOUR)}h`;
    return `${Math.round(seconds/MIN)}min`;
}

function _fmtResetAt(ts) {
    if (!ts) return '';
    const diff = Math.round((new Date(ts * 1000) - Date.now()) / 1000);
    if (diff <= 0) return 'now';
    return 'in ' + _fmtSeconds(diff);
}

function _usageBarHtml(pct, color) {
    const c = color || (pct >= 90 ? '#ef4444' : pct >= 70 ? '#f59e0b' : '#22c55e');
    return `<div style="height:6px;background:var(--color-border);border-radius:3px;overflow:hidden;min-width:60px;flex:1;">
        <div style="height:100%;width:${Math.min(pct,100)}%;background:${c};transition:width 0.4s;"></div>
    </div>`;
}

function _allWindows(rl) {
    const windows = [];
    if (rl.primary_window) windows.push(rl.primary_window);
    if (rl.secondary_window) windows.push(rl.secondary_window);
    if (Array.isArray(rl.additional_rate_limits)) windows.push(...rl.additional_rate_limits);
    return windows;
}

function _renderUsageCompact(usage) {
    if (!usage) return '<span style="color:var(--color-muted);font-size:11px;">No data</span>';
    const parts = [];
    const plan = usage.plan_type ? `<span style="font-size:10px;color:var(--color-muted);text-transform:uppercase;letter-spacing:0.5px;">${escHtmlAttr(usage.plan_type)}</span>` : '';
    const rl = usage.rate_limit;
    if (rl) {
        _allWindows(rl).forEach(w => {
            const pct = w.used_percent || 0;
            let countStr = '';
            if (w.num_requests_used != null && w.num_requests_limit != null) {
                countStr = ` (${w.num_requests_used.toLocaleString()}/${w.num_requests_limit.toLocaleString()})`;
            }
            parts.push(`<div style="display:flex;align-items:center;gap:4px;font-size:11px;">
                <span style="color:var(--color-muted);white-space:nowrap;">${_windowLabel(w.limit_window_seconds)}:</span>
                ${_usageBarHtml(pct)}
                <span style="color:var(--color-text);white-space:nowrap;">${pct}%${countStr}</span>
            </div>`);
        });
        if (rl.limit_reached) parts.push('<span style="color:#ef4444;font-size:11px;font-weight:600;">LIMIT REACHED</span>');
    }
    return `<div style="display:flex;flex-direction:column;gap:3px;min-width:140px;">${plan}${parts.join('')}</div>`;
}

function _renderUsageFull(usage) {
    if (!usage) return '<p style="color:var(--color-muted);font-size:13px;">Usage data unavailable.</p>';
    let html = '';
    if (usage.plan_type) {
        html += `<div style="margin-bottom:10px;"><span style="background:var(--color-link);color:#fff;padding:2px 8px;border-radius:3px;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;">${escHtmlAttr(usage.plan_type)}</span></div>`;
    }
    const rl = usage.rate_limit;
    if (rl) {
        if (rl.limit_reached) html += `<div style="background:#7f1d1d;color:#fca5a5;padding:8px 12px;border-radius:4px;margin-bottom:10px;font-size:13px;">Rate limit reached${usage.rate_limit_reached_type ? ': ' + escHtmlAttr(usage.rate_limit_reached_type) : ''}</div>`;
        const renderWindow = (w) => {
            if (!w) return '';
            const pct = w.used_percent || 0;
            const color = pct >= 90 ? '#ef4444' : pct >= 70 ? '#f59e0b' : '#22c55e';
            const label = _windowLabel(w.limit_window_seconds);
            let countsHtml = '';
            if (w.num_requests_used != null && w.num_requests_limit != null) {
                countsHtml += `<span>Requests: ${w.num_requests_used.toLocaleString()} / ${w.num_requests_limit.toLocaleString()}</span>`;
            }
            if (w.num_tokens_used != null && w.num_tokens_limit != null) {
                countsHtml += `<span>Tokens: ${w.num_tokens_used.toLocaleString()} / ${w.num_tokens_limit.toLocaleString()}</span>`;
            }
            return `<div style="margin-bottom:12px;">
                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
                    <span style="font-size:13px;font-weight:600;">${label}</span>
                    <span style="font-size:12px;color:${color};font-weight:700;">${pct}%</span>
                </div>
                <div style="height:8px;background:var(--color-border);border-radius:4px;overflow:hidden;margin-bottom:4px;">
                    <div style="height:100%;width:${Math.min(pct,100)}%;background:${color};transition:width 0.4s;border-radius:4px;"></div>
                </div>
                <div style="display:flex;flex-wrap:wrap;gap:6px;font-size:11px;color:var(--color-muted);">
                    <span>Window: ${_fmtSeconds(w.limit_window_seconds)}</span>
                    <span>Resets ${_fmtResetAt(w.reset_at)}</span>
                    ${countsHtml}
                </div>
            </div>`;
        };
        _allWindows(rl).forEach(w => { html += renderWindow(w); });
    }
    const cr = usage.credits;
    if (cr) {
        const bal = cr.balance != null ? escHtmlAttr(String(cr.balance)) : (cr.unlimited ? 'Unlimited' : 'None');
        html += `<div style="font-size:12px;color:var(--color-muted);margin-top:8px;">Credits: ${bal}</div>`;
    }
    return html || '<p style="color:var(--color-muted);font-size:13px;">No usage data available.</p>';
}

async function fetchProviderUsage(key) {
    try {
        const resp = await fetch(`${BASE_PATH}/dashboard/api/provider/${encodeURIComponent(key)}/usage`);
        if (!resp.ok) return null;
        const data = await resp.json();
        if (data.supported === false) return {supported: false};
        _usageCache[key] = {data: data.usage, ts: Date.now()};
        return data.usage;
    } catch(e) { return null; }
}

function updateUsageBadge(key) {
    const badge = document.getElementById(`usage-badge-${key}`);
    if (badge) { const c = _usageCache[key]; if (c && c.data) badge.innerHTML = _renderUsageCompact(c.data); }
    const full = document.getElementById(`usage-full-${key}`);
    if (full) { const c = _usageCache[key]; if (c && c.data) full.innerHTML = _renderUsageFull(c.data); }
}

async function refreshAllUsage() {
    for (const key of Object.keys(providersData)) {
        if (!_providerSupportsUsage(key)) continue;
        const c = _usageCache[key];
        if (c && (Date.now() - c.ts) < USAGE_POLL_INTERVAL - 1000) continue;
        const data = await fetchProviderUsage(key);
        if (data && data !== null) updateUsageBadge(key);
    }
}

setInterval(refreshAllUsage, USAGE_POLL_INTERVAL);

// Generic chunked upload handler for all providers
async function uploadFileChunked(providerKey, fileType, file, configObject) {
    if (!file) return;
    
    // Ensure provider is expanded so status element exists
    if (!expandedProviders.has(providerKey)) {
        toggleProvider(providerKey);
    }
    
    // Map config object names to status element prefixes
    const statusPrefixMap = {
        'kiro_config': 'kiro',
        'kilo_config': 'kilo',
        'claude_config': 'claude',
        'qwen_config': 'qwen',
        'codex_config': 'codex'
    };
    
    const statusPrefix = statusPrefixMap[configObject] || configObject.replace('_config', '');
    const statusEl = document.getElementById(`${statusPrefix}-upload-status-${providerKey}`);
    statusEl.innerHTML = `<div style="color: var(--color-link);">${window.i18n.interpolate(window.i18n.t('providers.uploading_file'), {pct: 0})}</div>`;
    
    try {
        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        
        for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {
            const start = (chunkNumber - 1) * CHUNK_SIZE;
            const end = Math.min(start + CHUNK_SIZE, file.size);
            const chunk = file.slice(start, end);
            
            const progress = Math.round((chunkNumber / totalChunks) * 100);
            statusEl.innerHTML = `<div style="color: var(--color-link);">${window.i18n.interpolate(window.i18n.t('providers.uploading_file'), {pct: progress})}</div>`;
            
            const formData = new FormData();
            formData.append('provider_key', providerKey);
            formData.append('file_type', fileType);
            formData.append('file_name', file.name);
            formData.append('total_size', file.size);
            formData.append('chunk_number', chunkNumber);
            formData.append('total_chunks', totalChunks);
            formData.append('file', chunk);
            
            const response = await fetch(BASE_PATH + '/dashboard/providers/upload-auth-file/chunk', {
                method: 'POST',
                body: formData
            });
            
            const result = await response.json();
            
            if (!result.success) {
                throw new Error(result.error || 'Upload failed');
            }
            
            // When upload is complete
            if (result.complete) {
                const savedPath = result.file_path || result.stored_filename || file.name;
                statusEl.innerHTML = `<div style="color: #4ade80; display: flex; align-items: center; gap: 6px;"><span style="font-size: 1.1em;">&#10003;</span> Saved: <code style="background: var(--bg-panel); padding: 1px 6px; border-radius: 3px; font-size: 0.9em;">${savedPath}</code></div>`;
                showToast(`File uploaded successfully: ${savedPath}`, 'success');

                // Update the config with the full file path (not just filename)
                if (!providersData[providerKey][configObject]) {
                    providersData[providerKey][configObject] = {};
                }
                // Use file_path from server response (tilde format: ~/.aisbf/...)
                providersData[providerKey][configObject][fileType] = result.file_path || result.stored_filename;
                setTimeout(() => renderProvidersList(), 2500);
                return;
            }
        }
    } catch (e) {
        statusEl.innerHTML = `<div style="color: #f87171;">${window.i18n.interpolate(window.i18n.t('providers.upload_failed'), {error: e.message})}</div>`;
        showToast(`Upload failed: ${e.message}`, 'danger');
    }
}

// Upload handler for Kiro credential files
async function uploadKiroFile(providerKey, fileType, file) {
    await uploadFileChunked(providerKey, fileType, file, 'kiro_config');
}

// Upload handler for Kilo credential files
async function uploadKiloFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'kilo_config');
}

// Upload handler for Claude credential files
async function uploadClaudeFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'claude_config');
}

// Upload handler for Claude CLI credentials file (.credentials.json from ~/.claude/)
async function uploadClaudeCliFile(providerKey, file) {
    if (!file) return;
    if (!expandedProviders.has(providerKey)) toggleProvider(providerKey);
    const statusEl = document.getElementById(`claude-cli-upload-status-${providerKey}`);
    if (!statusEl) return;
    statusEl.innerHTML = `<div style="color: var(--color-link);">${window.i18n.interpolate(window.i18n.t('providers.uploading_cli'), {pct: 0})}</div>`;
    try {
        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {
            const start = (chunkNumber - 1) * CHUNK_SIZE;
            const chunk = file.slice(start, Math.min(start + CHUNK_SIZE, file.size));
            const progress = Math.round((chunkNumber / totalChunks) * 100);
            statusEl.innerHTML = `<div style="color: var(--color-link);">${window.i18n.interpolate(window.i18n.t('providers.uploading_cli'), {pct: progress})}</div>`;
            const formData = new FormData();
            formData.append('provider_key', providerKey);
            formData.append('file_type', 'cli_credentials');
            formData.append('file_name', file.name);
            formData.append('total_size', file.size);
            formData.append('chunk_number', chunkNumber);
            formData.append('total_chunks', totalChunks);
            formData.append('file', chunk);
            const response = await fetch(BASE_PATH + '/dashboard/providers/upload-auth-file/chunk', { method: 'POST', body: formData });
            const result = await response.json();
            if (!result.success) throw new Error(result.error || 'Upload failed');
            if (result.complete) {
                statusEl.innerHTML = `<div style="color: #4ade80; display: flex; align-items: center; gap: 6px;"><span style="font-size: 1.1em;">&#10003;</span> ${window.i18n.interpolate(window.i18n.t('providers.cli_creds_saved'), {name: file.name})}</div>`;
                showToast(window.i18n.interpolate(window.i18n.t('providers.cli_creds_saved'), {name: file.name}), 'success');
                return;
            }
        }
    } catch (e) {
        statusEl.innerHTML = `<div style="color: #f87171;">${window.i18n.interpolate(window.i18n.t('providers.upload_failed'), {error: e.message})}</div>`;
        showToast(`Upload failed: ${e.message}`, 'danger');
    }
}

// Upload handler for Qwen credential files
async function uploadQwenFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'qwen_config');
}

// Upload handler for Codex credential files
async function uploadCodexFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'codex_config');
}

function _providerFilteredKeys() {
    return providerSearchFilter
        ? providerMasterOrder.filter(k => k.toLowerCase().includes(providerSearchFilter.toLowerCase()) ||
              (providersData[k] && (providersData[k].name || '').toLowerCase().includes(providerSearchFilter.toLowerCase())))
        : providerMasterOrder.filter(k => k in providersData);
}

function renderProvidersList() {
    const container = document.getElementById('providers-list');
    if (!container) return;

    const filteredKeys = _providerFilteredKeys();
    const total = filteredKeys.length;
    const totalPages = Math.max(1, Math.ceil(total / PROVIDERS_PAGE_SIZE));
    if (currentProviderPage >= totalPages) currentProviderPage = totalPages - 1;

    const pageKeys = filteredKeys.slice(currentProviderPage * PROVIDERS_PAGE_SIZE, (currentProviderPage + 1) * PROVIDERS_PAGE_SIZE);

    const countEl = document.getElementById('providers-count');
    if (countEl) countEl.textContent = window.i18n.interpolate(window.i18n.t(total !== 1 ? 'providers.provider_count_plural' : 'providers.provider_count_singular'), {n: total});

    const hasPrev = totalPages > 1 && currentProviderPage > 0;
    const hasNext = totalPages > 1 && currentProviderPage < totalPages - 1;

    container.innerHTML = `<div id="providers-list-page-prev" class="ds-sentinel${hasPrev ? ' ds-visible' : ''}">⬆ Drop here to move to previous page</div>`;

    if (pageKeys.length === 0) {
        container.innerHTML += `<p style="color:var(--color-muted);">${window.i18n.t('providers.no_providers')}</p>`;
    } else {
        pageKeys.forEach(key => {
            const provider = providersData[key];
            const providerItem = document.createElement('div');
            providerItem.className = 'provider-item';
            providerItem.dataset.sortKey = key;
            providerItem.style.cssText = 'border: 1px solid var(--color-border); margin-bottom: 10px; border-radius: 5px; background: var(--bg-page);';

            const isExpanded = expandedProviders.has(key);
            const safeKey = key.replace(/\\/g, '\\\\').replace(/'/g, "\\'");

            const supportsUsage = _providerSupportsUsage(key);
            const cachedUsage = _usageCache[key];
            const compactBadge = supportsUsage ? `<div id="usage-badge-${escHtmlAttr(key)}" onclick="event.stopPropagation();" style="margin-left:8px;">${cachedUsage && cachedUsage.data ? _renderUsageCompact(cachedUsage.data) : '<span style="color:var(--color-muted);font-size:11px;">loading…</span>'}</div>` : '';

            providerItem.innerHTML = `
                <div class="provider-header" onclick="toggleProvider('${safeKey}')" style="padding: 15px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; user-select: none; flex-wrap: wrap; gap: 8px;">
                    <div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
                        <span class="drag-handle" onclick="event.stopPropagation();" title="Drag to reorder">⠿</span>
                        <span style="font-size: 18px;">${isExpanded ? '▼' : '▶'}</span>
                        <strong style="font-size: 16px;">${escHtmlAttr(key)}</strong>
                        <span style="color: var(--color-muted); font-size: 14px;">(${escHtmlAttr(provider.name || key)})</span>
                        ${compactBadge}
                    </div>
                    <button type="button" class="btn btn-secondary" onclick="event.stopPropagation(); removeProvider('${safeKey}')" style="background: #dc3545; padding: 5px 15px;">${window.i18n.t('providers.remove')}</button>
                </div>
                <div id="provider-details-${escHtmlAttr(key)}" style="display: ${isExpanded ? 'block' : 'none'}; padding: 20px; border-top: 1px solid var(--color-border); background: var(--bg-panel);">
                </div>
            `;

            container.appendChild(providerItem);

            if (isExpanded) {
                renderProviderDetails(key);
            }
        });
    }

    const sentinelBot = document.createElement('div');
    sentinelBot.id = 'providers-list-page-next';
    sentinelBot.className = 'ds-sentinel' + (hasNext ? ' ds-visible' : '');
    sentinelBot.textContent = '⬇ Drop here to move to next page';
    container.appendChild(sentinelBot);

    const paginationEl = document.getElementById('providers-pagination');
    if (paginationEl) {
        if (totalPages <= 1) {
            paginationEl.innerHTML = '';
        } else {
            let btns = '';
            btns += `<button class="btn btn-secondary" onclick="goToProviderPage(${currentProviderPage - 1})" ${currentProviderPage === 0 ? 'disabled' : ''} style="padding:5px 12px;">← Prev</button>`;
            const start = Math.max(0, currentProviderPage - 2);
            const end = Math.min(totalPages - 1, currentProviderPage + 2);
            for (let p = start; p <= end; p++) {
                btns += `<button class="btn ${p === currentProviderPage ? '' : 'btn-secondary'}" onclick="goToProviderPage(${p})" style="padding:5px 10px;min-width:36px;">${p + 1}</button>`;
            }
            btns += `<button class="btn btn-secondary" onclick="goToProviderPage(${currentProviderPage + 1})" ${currentProviderPage >= totalPages - 1 ? 'disabled' : ''} style="padding:5px 12px;">Next →</button>`;
            paginationEl.innerHTML = `<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;">
                ${btns}
                <span style="color:var(--color-muted);font-size:13px;margin-left:8px;">Page ${currentProviderPage + 1} of ${totalPages}</span>
            </div>`;
        }
    }

    if (!_providerDS) {
        _providerDS = new DragSort({
            containerId: 'providers-list',
            masterOrder: { value: providerMasterOrder },
            onReorder: function(newOrder) {
                providerMasterOrder = newOrder;
                renderProvidersList();
                fetch(BASE_PATH + '/dashboard/api/provider/reorder', {
                    method: 'POST', headers: {'Content-Type':'application/json'},
                    body: JSON.stringify({order: newOrder})
                }).catch(function(){});
            },
            pagination: {
                getCurrentPage: function() { return currentProviderPage; },
                getTotalPages: function() { return Math.max(1, Math.ceil(_providerFilteredKeys().length / PROVIDERS_PAGE_SIZE)); },
                goToPage: function(p) { goToProviderPage(p); },
                pageSize: PROVIDERS_PAGE_SIZE,
                getFilteredKeys: _providerFilteredKeys
            }
        });
    } else {
        _providerDS._opts.masterOrder.value = providerMasterOrder;
        _providerDS.attach();
    }
}

function goToProviderPage(page) {
    currentProviderPage = page;
    expandedProviders.clear();
    renderProvidersList();
    window.scrollTo(0, 0);
}

function toggleProvider(key) {
    if (expandedProviders.has(key)) {
        expandedProviders.delete(key);
    } else {
        expandedProviders.add(key);
        const filteredKeys = _providerFilteredKeys();
        const idx = filteredKeys.indexOf(key);
        if (idx >= 0) currentProviderPage = Math.floor(idx / PROVIDERS_PAGE_SIZE);
    }
    renderProvidersList();
}

function renderProviderDetails(key) {
    const container = document.getElementById(`provider-details-${key}`);
    const provider = providersData[key];
    const safeKey = key.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
    const isKiroProvider = provider.type === 'kiro';
    const isClaudeProvider = provider.type === 'claude';
    const isKiloProvider = provider.type === 'kilocode';
    const isQwenProvider = provider.type === 'qwen';
    const isCodexProvider = provider.type === 'codex';
    
    // Initialize kiro_config if this is a kiro provider and doesn't have it
    if (isKiroProvider && !provider.kiro_config) {
        provider.kiro_config = {
            region: 'us-east-1',
            refresh_token: '',
            profile_arn: '',
            client_id: '',
            client_secret: ''
        };
    }
    
    // Initialize claude_config if this is a claude provider and doesn't have it
    if (isClaudeProvider && !provider.claude_config) {
        provider.claude_config = {};
    }
    
    // Initialize kilo_config if this is a kilocode provider and doesn't have it
    if (isKiloProvider && !provider.kilo_config) {
        provider.kilo_config = {
            api_base: 'https://api.kilo.ai/api/gateway'
        };
    }
    
    // Initialize qwen_config if this is a qwen provider and doesn't have it
    if (isQwenProvider && !provider.qwen_config) {
        provider.qwen_config = {
            api_key: '',
            region: 'china-beijing',
            workspace_id: 'Default Workspace'
        };
    }
    
    // Initialize codex_config if this is a codex provider and doesn't have it
    if (isCodexProvider && !provider.codex_config) {
        provider.codex_config = {
            issuer: 'https://auth.openai.com'
        };
    }
    
    const kiroConfig = provider.kiro_config || {};
    const claudeConfig = provider.claude_config || {};
    const kiloConfig = provider.kilo_config || {};
    const qwenConfig = provider.qwen_config || {};
    const codexConfig = provider.codex_config || {};
    
    // Build authentication fields based on provider type
    let authFieldsHtml = '';
    if (isKiroProvider) {
        // Kiro-specific authentication fields
        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">${window.i18n.t('providers.kiro_auth_section')}</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Choose one authentication method: Kiro IDE credentials (creds_file), kiro-cli database (sqlite_db), or direct credentials (refresh_token + client_id/secret).
                </small>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_aws_region')}</label>
                    <input type="text" value="${kiroConfig.region || 'us-east-1'}" onchange="updateKiroConfig('${key}', 'region', this.value)" placeholder="us-east-1">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">AWS region for Kiro API (default: us-east-1)</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">Direct Credentials</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_refresh_token')}</label>
                    <input type="password" value="${kiroConfig.refresh_token || ''}" onchange="updateKiroConfig('${key}', 'refresh_token', this.value)" placeholder="Enter refresh token">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Kiro refresh token for direct authentication</small>
                </div>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_profile_arn')}</label>
                    <input type="text" value="${kiroConfig.profile_arn || ''}" onchange="updateKiroConfig('${key}', 'profile_arn', this.value)" placeholder="arn:aws:codewhisperer:us-east-1:...">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">AWS CodeWhisperer profile ARN (optional)</small>
                </div>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_client_id')}</label>
                    <input type="text" value="${kiroConfig.client_id || ''}" onchange="updateKiroConfig('${key}', 'client_id', this.value)" placeholder="OAuth client ID">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">OAuth client ID for AWS SSO OIDC authentication</small>
                </div>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_client_secret')}</label>
                    <input type="password" value="${kiroConfig.client_secret || ''}" onchange="updateKiroConfig('${key}', 'client_secret', this.value)" placeholder="OAuth client secret">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">OAuth client secret for AWS SSO OIDC authentication</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.kiro_opt4')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_upload_creds')}</label>
                    <input type="file" id="kiro-creds-file-${key}" accept=".json" onchange="uploadKiroFile('${key}', 'creds_file', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Kiro IDE credentials JSON file</small>
                </div>
                <div class="form-group">
                    <label>${window.i18n.t('providers.kiro_upload_sqlite')}</label>
                    <input type="file" id="kiro-db-file-${key}" accept=".sqlite3,.db,.sqlite" onchange="uploadKiroFile('${key}', 'sqlite_db', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload kiro-cli SQLite database file</small>
                </div>
                <div id="kiro-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else if (isKiloProvider) {
        // Kilocode authentication fields - supports both API key and OAuth2
        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">${window.i18n.t('providers.kilo_auth_section')}</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Choose your authentication method: API Key (recommended for simplicity) or OAuth2 Device Authorization Grant.
                </small>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">Option 1: API Key / Auth Token</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_key')}</label>
                    <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="Enter your Kilocode API key">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">If provided, API key authentication will be used instead of OAuth2</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.kilo_opt2')}</h5>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    If no API key is provided, OAuth2 will be used automatically.
                </small>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_base')}</label>
                    <input type="text" value="${kiloConfig.api_base || 'https://api.kilo.ai/api/gateway'}" readonly style="background: var(--bg-panel); cursor: not-allowed;" placeholder="https://api.kilo.ai/api/gateway">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Kilocode API base URL (fixed)</small>
                </div>
                
                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateKilo('${key}')" style="background: #4a9eff;">
                        🔐 ${window.i18n.t('providers.authenticate_claude').replace('Claude', 'Kilocode OAuth2')}
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkKiloAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>
                
                <div id="kilo-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="kilo-creds-file-${key}" accept=".json" onchange="uploadKiloFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Kilocode OAuth2 credentials JSON file</small>
                </div>
                <div id="kilo-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else if (isClaudeProvider) {
        // Claude OAuth2 authentication fields
        const extensionInstalled = !!window.aisbfOAuth2Extension;
        const oauth2Available = IS_LOCAL_CLIENT || extensionInstalled;

        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">Claude OAuth2 Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Claude uses OAuth2 authentication. Click "Authenticate" to start the OAuth2 flow in your browser.
                </small>

                ${!IS_LOCAL_CLIENT && !extensionInstalled ? `
                <div style="background: #2a1a00; border: 2px solid #f59e0b; border-radius: 8px; padding: 15px; margin-bottom: 15px;">
                    <h5 style="margin: 0 0 8px 0; color: #f59e0b;">🧩 Chrome Extension Required</h5>
                    <p style="margin: 0 0 10px 0; color: #fcd34d; font-size: 0.9em;">
                        Claude's OAuth2 flow redirects your browser to <code style="background:#1a1000;padding:1px 4px;border-radius:3px;">http://localhost:54545/callback</code>
                        to deliver the auth token. When you access AISBF remotely, that localhost URL goes to
                        <em>your machine</em>, not the server — so the token never reaches AISBF.
                        <br><br>
                        The AISBF Chrome Extension intercepts that localhost callback in your browser and
                        forwards it to this server, completing the OAuth2 flow transparently.
                        Once installed, it self-configures automatically — no setup needed.
                        Reload this page after installing and the Authenticate button will appear.
                    </p>
                    <a href="${BASE_PATH}/dashboard/extension/download" download
                       style="display: inline-block; padding: 8px 16px; background: #2d6a4f; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 600;">
                        ⬇️ Download Chrome Extension (.zip)
                    </a>
                    <small style="color: var(--color-muted); display: block; margin-top: 8px;">
                        In Chrome: go to <strong>chrome://extensions</strong>, enable Developer Mode, click "Load unpacked", and select the extracted folder.
                    </small>
                </div>
                ` : ''}

                ${oauth2Available ? `
                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateClaude('${key}')" style="background: #4a9eff;">
                        🔐 ${window.i18n.t('providers.authenticate_claude')}
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkClaudeAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>

                <div id="claude-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="claude-creds-file-${key}" accept=".json" onchange="uploadClaudeFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">${window.i18n.t('providers.upload_credentials_desc')}</small>
                </div>
                <div id="claude-upload-status-${key}" style="margin-top: 10px;"></div>
                ` : `
                <div id="claude-auth-status-${key}" style="display:none;"></div>
                <div id="claude-upload-status-${key}" style="display:none;"></div>
                `}

                ${CLAUDE_CLI_MODE ? `
                <div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--color-border);">
                    <h5 style="margin: 0 0 8px 0; color: #4ade80;">${window.i18n.t('providers.cli_mode_active')}</h5>
                    <small style="color: #4ade80; display: block; margin-bottom: 14px;">
                        The claude CLI was detected at startup. When enabled, requests are piped
                        through the local claude binary instead of the HTTP API.
                    </small>

                    <div class="form-group" style="margin-bottom: 16px;">
                        <label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
                            <input type="checkbox"
                                   id="claude-use-cli-mode-${key}"
                                   ${claudeConfig.use_cli_mode ? 'checked' : ''}
                                   onchange="updateClaudeConfig('${key}', 'use_cli_mode', this.checked)"
                                   style="width: 18px; height: 18px; cursor: pointer; accent-color: #4ade80;">
                            <span style="color: var(--color-text); font-weight: 500;">${window.i18n.t('providers.use_cli_mode')}</span>
                        </label>
                        <small style="color: var(--color-muted); display: block; margin-top: 6px; padding-left: 28px;">
                            When checked and you have already authenticated via OAuth2 above,
                            your existing tokens will be used automatically — no separate file upload needed.
                            You can also upload an explicit <code style="background:var(--bg-panel);padding:1px 4px;border-radius:3px;">.credentials.json</code>
                            below to override.
                        </small>
                    </div>

                    <div class="form-group">
                        <label>${window.i18n.t('providers.upload_cli_creds')}</label>
                        <input type="file" id="claude-cli-creds-file-${key}" accept=".json" onchange="uploadClaudeCliFile('${key}', this.files[0])">
                        <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                            Optional — upload a <code style="background:var(--bg-panel);padding:1px 4px;border-radius:3px;">~/.claude/.credentials.json</code>
                            to use a specific account instead of your OAuth2 tokens.
                        </small>
                    </div>
                    <div id="claude-cli-upload-status-${key}" style="margin-top: 10px;"></div>
                </div>
                ` : ''}
            </div>

            <div style="background: var(--alert-info-bg); border: 2px solid var(--alert-info-border); border-radius: 8px; padding: 20px; margin-bottom: 15px;">
                <h4 style="margin: 0 0 10px 0; color: var(--alert-info-fg);">⚠️ Important Notice</h4>
                <p style="margin: 0; color: var(--color-text);">
                    Claude.ai policies state that unofficial clients are not allowed. By using AISBF in <strong>HTTP API / OAuth2 mode</strong>, you acknowledge that:
                    <br><br>• While we do our best to mimic the claude-code CLI, using an unofficial client can lead to account suspension or cancellation
                    <br>• Claude.ai detects prompt patterns to identify unofficial client usage, so it may not work with complex agents and prompts
                    <br>• You use this software at your own risk and responsibility
                    <br>• We are not affiliated with Anthropic and cannot guarantee compatibility or continued functionality
                    <br><br>✅ <strong>CLI mode is different:</strong> when "Use Claude CLI mode" is enabled, AISBF proxies requests through the official <code style="background:#e8e0b0;padding:1px 4px;border-radius:3px;">claude</code> binary using <code style="background:#e8e0b0;padding:1px 4px;border-radius:3px;">claude -p</code>, which is the intended programmatic use of the official Anthropic CLI and is permitted by Claude's terms of service.
                </p>
            </div>
        `;
    } else if (isQwenProvider) {
        // Qwen authentication fields - supports both API key and OAuth2
        authFieldsHtml = `
            <div style="background: #ff6b35; border: 2px solid #d63031; border-radius: 8px; padding: 20px; margin-bottom: 15px;">
                <h4 style="margin: 0 0 10px 0; color: #fff;">⚠️ Qwen OAuth2 Service Discontinued</h4>
                <p style="margin: 0; color: #fff;">
                    As of April 2026, Qwen has completely disabled OAuth2 subscriptions for Qwen Code.
                    OAuth2 tokens are no longer accepted by the DashScope API.
                    <br><br><strong>Please use API Key authentication instead.</strong>
                    <br><br>OAuth2 support is maintained in the code for potential future re-enablement, but it is currently non-functional.
                </p>
            </div>
            
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">Qwen Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Choose your authentication method: API Key (recommended for simplicity) or OAuth2 Device Authorization Grant.
                </small>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.kilo_opt1')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_key')}</label>
                    <input type="password" value="${qwenConfig.api_key || ''}" onchange="updateQwenConfig('${key}', 'api_key', this.value)" placeholder="Enter your Qwen API key">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">If provided, API key authentication will be used instead of OAuth2</small>
                </div>

                <div id="qwen-region-config-${key}" style="display: ${qwenConfig.api_key ? 'block' : 'none'}; margin-top: 15px;">
                    <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">Region Configuration (API Key Mode)</h5>
                    <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                        Select your preferred region for API key authentication. Different regions have different endpoints.
                    </small>

                    <div class="form-group">
                        <label>Region</label>
                        <select onchange="updateQwenConfig('${key}', 'region', this.value)">
                            <option value="china-beijing" ${qwenConfig.region === 'china-beijing' || !qwenConfig.region ? 'selected' : ''}>China (Beijing) - dashscope.aliyuncs.com</option>
                            <option value="singapore" ${qwenConfig.region === 'singapore' ? 'selected' : ''}>Singapore - dashscope-intl.aliyuncs.com</option>
                            <option value="us-virginia" ${qwenConfig.region === 'us-virginia' ? 'selected' : ''}>US (Virginia) - dashscope-us.aliyuncs.com</option>
                            <option value="china-hongkong" ${qwenConfig.region === 'china-hongkong' ? 'selected' : ''}>China (Hong Kong) - cn-hongkong.dashscope.aliyuncs.com</option>
                            <option value="germany-frankfurt" ${qwenConfig.region === 'germany-frankfurt' ? 'selected' : ''}>Germany (Frankfurt) - Requires Workspace ID</option>
                        </select>
                        <small style="color: var(--color-muted); display: block; margin-top: 5px;">Select the region for your Qwen API endpoint</small>
                    </div>

                    <div id="qwen-workspace-config-${key}" style="display: ${qwenConfig.region === 'germany-frankfurt' ? 'block' : 'none'};">
                        <div class="form-group">
                            <label>${window.i18n.t('providers.workspace_id')}</label>
                            <input type="text" value="${qwenConfig.workspace_id || 'Default Workspace'}" onchange="updateQwenConfig('${key}', 'workspace_id', this.value)" placeholder="Default Workspace">
                            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Workspace ID for Germany region (default: "Default Workspace")</small>
                        </div>
                    </div>
                </div>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.qwen_opt2_discontinued')}</h5>
                <small style="color: #ff6b35; display: block; margin-bottom: 15px;">
                    ⚠️ OAuth2 authentication is no longer functional. Qwen has discontinued this service. Use API Key instead.
                </small>

                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateQwen('${key}')" style="background: #4a9eff;">
                        🔐 Authenticate with Qwen OAuth2
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkQwenAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>

                <div id="qwen-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="qwen-creds-file-${key}" accept=".json" onchange="uploadQwenFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Qwen OAuth2 credentials JSON file</small>
                </div>
                <div id="qwen-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else if (isCodexProvider) {
        // Codex OAuth2 authentication fields
        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: var(--color-link);">Codex OAuth2 Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Codex uses OAuth2 Device Authorization Grant (same protocol as OpenAI). Click "Authenticate" to start the OAuth2 flow.
                </small>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_key')} (${window.i18n.t('providers.optional')} - if provided, used instead of OAuth2)</label>
                    <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="Enter OpenAI API key (optional)">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">If provided, API key authentication will be used instead of OAuth2</small>
                </div>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.oauth2_issuer_url')}</label>
                    <input type="text" value="${codexConfig.issuer || 'https://auth.openai.com'}" onchange="updateCodexConfig('${key}', 'issuer', this.value)" placeholder="https://auth.openai.com">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">OAuth2 issuer URL (default: https://auth.openai.com)</small>
                </div>
                
                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateCodex('${key}')" style="background: #4a9eff;">
                        🔐 Authenticate with Codex (Device Code)
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkCodexAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>
                
                <div id="codex-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">Usage &amp; Quota</h5>
                <div id="usage-full-${key}" style="margin-bottom: 10px;">
                    <p style="color:var(--color-muted);font-size:13px;">Loading usage data…</p>
                </div>
                <button type="button" class="btn btn-secondary" onclick="(async()=>{document.getElementById('usage-full-${key}').innerHTML='<p style=\\'color:var(--color-muted);font-size:13px;\\'>Refreshing…</p>';_usageCache['${key}']={ts:0};const d=await fetchProviderUsage('${key}');document.getElementById('usage-full-${key}').innerHTML=_renderUsageFull(d);})()" style="font-size:12px;padding:4px 10px;">↺ Refresh usage</button>

                <h5 style="margin: 20px 0 10px 0; color: var(--color-link);">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="codex-creds-file-${key}" accept=".json" onchange="uploadCodexFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Codex OAuth2 credentials JSON file</small>
                </div>
                <div id="codex-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else {
        // Standard API key authentication fields
        authFieldsHtml = `
            <div class="form-group">
                <label>
                    <input type="checkbox" ${provider.api_key_required ? 'checked' : ''} onchange="updateProvider('${key}', 'api_key_required', this.checked)">
                    API Key Required
                </label>
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.api_key')}</label>
                <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="Enter API key">
            </div>
        `;
    }
    
    container.innerHTML = `
        <div class="form-group">
            <label>${window.i18n.t('providers.provider_name')}</label>
            <input type="text" value="${provider.id}" onchange="updateProvider('${key}', 'id', this.value)" required>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.provider_name')}</label>
            <input type="text" value="${provider.name}" onchange="updateProvider('${key}', 'name', this.value)" required>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.api_base')}</label>
            <input type="text" value="${provider.endpoint}" onchange="updateProvider('${key}', 'endpoint', this.value)" ${(provider.type === 'kilocode' || provider.type === 'qwen' || provider.type === 'claude' || provider.type === 'codex') ? 'readonly style="background: var(--bg-panel); cursor: not-allowed;"' : ''} required>
            ${isKiroProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Typically: https://q.us-east-1.amazonaws.com</small>' : ''}
            ${provider.type === 'kilocode' ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Kilocode OAuth2 provider</small>' : ''}
            ${isQwenProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Qwen OAuth2 provider (https://dashscope.aliyuncs.com/compatible-mode/v1)</small>' : ''}
            ${isClaudeProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Claude OAuth2 provider (https://api.anthropic.com/v1)</small>' : ''}
            ${isCodexProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Codex OAuth2 provider (https://api.openai.com/v1)</small>' : ''}
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.provider_type')}</label>
            <select onchange="updateProviderType('${key}', this.value)" required>
                <option value="google" ${provider.type === 'google' ? 'selected' : ''}>Google</option>
                <option value="openai" ${provider.type === 'openai' ? 'selected' : ''}>OpenAI</option>
                <option value="anthropic" ${provider.type === 'anthropic' ? 'selected' : ''}>Anthropic</option>
                <option value="ollama" ${provider.type === 'ollama' ? 'selected' : ''}>Ollama</option>
                <option value="kiro" ${provider.type === 'kiro' ? 'selected' : ''}>Kiro (Amazon Q Developer)</option>
                <option value="claude" ${provider.type === 'claude' ? 'selected' : ''}>Claude (OAuth2)</option>
                <option value="kilocode" ${provider.type === 'kilocode' ? 'selected' : ''}>Kilocode (OAuth2)</option>
                <option value="qwen" ${provider.type === 'qwen' ? 'selected' : ''}>Qwen (OAuth2)</option>
                <option value="codex" ${provider.type === 'codex' ? 'selected' : ''}>Codex (OpenAI OAuth2)</option>
            </select>
        </div>
        
        ${authFieldsHtml}
        
        <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin: 15px 0; border-left: 3px solid #4a9eff;">
            <h4 style="margin: 0 0 15px 0; color: var(--color-link);">${window.i18n.t('providers.pricing_section')}</h4>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${provider.is_subscription ? 'checked' : ''} onchange="updateProvider('${key}', 'is_subscription', this.checked); togglePricingFields('${key}')">
                    Subscription-Based Provider (Free)
                </label>
                <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                    If checked, this provider is subscription-based and costs will be calculated as $0. Usage is still tracked for analytics.
                </small>
            </div>
            
            <div id="pricing-fields-${key}" style="display: ${provider.is_subscription ? 'none' : 'block'};">
                <div class="form-group">
                    <label>${window.i18n.t('providers.price_prompt')}</label>
                    <input type="number" step="0.01" min="0" value="${provider.price_per_million_prompt || ''}" onchange="updateProvider('${key}', 'price_per_million_prompt', parseFloat(this.value) || null)" placeholder="e.g., 15.00">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                        Leave empty to use default pricing. Examples: OpenAI GPT-4: $10, Anthropic Claude: $15, Google Gemini: $1.25
                    </small>
                </div>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.price_completion')}</label>
                    <input type="number" step="0.01" min="0" value="${provider.price_per_million_completion || ''}" onchange="updateProvider('${key}', 'price_per_million_completion', parseFloat(this.value) || null)" placeholder="e.g., 75.00">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                        Leave empty to use default pricing. Examples: OpenAI GPT-4: $30, Anthropic Claude: $75, Google Gemini: $5.00
                    </small>
                </div>
            </div>
        </div>

        <div class="form-group">
            <label>${window.i18n.t('providers.rate_limit')}</label>
            <input type="number" value="${provider.rate_limit || 0}" onchange="updateProvider('${key}', 'rate_limit', parseFloat(this.value))" step="0.1">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Time delay between requests to this provider</small>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.default_rate_limit_tpm')}</label>
            <input type="number" value="${provider.default_rate_limit_TPM || ''}" onchange="updateProvider('${key}', 'default_rate_limit_TPM', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Default token limit per minute for models in this provider</small>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.default_rate_limit_tph')}</label>
            <input type="number" value="${provider.default_rate_limit_TPH || ''}" onchange="updateProvider('${key}', 'default_rate_limit_TPH', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Default token limit per hour for models in this provider</small>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.default_rate_limit_tpd')}</label>
            <input type="number" value="${provider.default_rate_limit_TPD || ''}" onchange="updateProvider('${key}', 'default_rate_limit_TPD', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Default token limit per day for models in this provider</small>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.max_tokens')}</label>
            <input type="number" value="${provider.default_max_request_tokens || ''}" onchange="updateProvider('${key}', 'default_max_request_tokens', this.value ? parseInt(this.value) : null)" placeholder="Optional">
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.context_size')}</label>
            <input type="number" value="${provider.default_context_size || ''}" onchange="updateProvider('${key}', 'default_context_size', this.value ? parseInt(this.value) : null)" placeholder="Optional">
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.default_condense_context')}</label>
            <input type="number" value="${provider.default_condense_context || ''}" onchange="updateProvider('${key}', 'default_condense_context', this.value ? parseInt(this.value) : null)" placeholder="Optional (e.g., 80)">
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.default_condense_method')}</label>
            <input type="text" value="${Array.isArray(provider.default_condense_method) ? provider.default_condense_method.join(', ') : (provider.default_condense_method || '')}" onchange="updateProviderCondenseMethod('${key}', this.value)" placeholder="e.g., semantic, conversational">
        </div>
        
        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.nsfw ? 'checked' : ''} onchange="updateProvider('${key}', 'nsfw', this.checked)">
                NSFW
            </label>
        </div>
        
        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.cache_enabled ? 'checked' : ''} onchange="setCacheSetting('${key}', null, this.checked)">
                Enable Prompt Caching
            </label>
            <small style="color: var(--color-subtle); font-size: 12px;">
                <strong>Provider-level cache override:</strong> Disabling this prevents the provider's native prompt 
                caching feature from being used, even if enabled in the provider configuration. 
                This takes priority over the global cache setting.
            </small>
        </div>
        
        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.privacy ? 'checked' : ''} onchange="updateProvider('${key}', 'privacy', this.checked)">
                Privacy
            </label>
        </div>

        <h4 style="margin-top: 20px; margin-bottom: 10px;">${window.i18n.t('providers.native_caching_section')}</h4>
        <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
            Provider-native caching features (Anthropic cache_control, Google Context Caching, OpenAI and Kilo-compatible APIs) for cost reduction.
        </small>

        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.enable_native_caching ? 'checked' : ''} onchange="updateProvider('${key}', 'enable_native_caching', this.checked)">
                Enable Native Caching
            </label>
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Enable provider-native caching for cost reduction (50-70% savings for supported providers)</small>
        </div>

        <div class="form-group">
            <label>${window.i18n.t('providers.cache_ttl')}</label>
            <input type="number" value="${provider.cache_ttl || ''}" onchange="updateProvider('${key}', 'cache_ttl', this.value ? parseInt(this.value) : null)" placeholder="Optional (e.g., 3600)">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Cache time-to-live in seconds (Google Context Caching only)</small>
        </div>

        <div class="form-group">
            <label>${window.i18n.t('providers.min_cacheable_tokens')}</label>
            <input type="number" value="${provider.min_cacheable_tokens || 1000}" onchange="updateProvider('${key}', 'min_cacheable_tokens', this.value ? parseInt(this.value) : 1000)" placeholder="1000">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Minimum token count for content to be cacheable (default: 1000)</small>
        </div>

        <div class="form-group">
            <label>${window.i18n.t('providers.prompt_cache_key')}</label>
            <input type="text" value="${provider.prompt_cache_key || ''}" onchange="updateProvider('${key}', 'prompt_cache_key', this.value)" placeholder="Optional cache key for load balancer optimization">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Optional cache key for OpenAI/Kilo load balancer routing optimization</small>
        </div>

        <h4 style="margin-top: 20px; margin-bottom: 10px;">${window.i18n.t('providers.models_section')}</h4>
        <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
            Configure specific models for this provider, or leave empty to automatically fetch all available models from the provider's API.
        </small>

        <div class="form-group" style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
            <label>${window.i18n.t('providers.model_filter')}</label>
            <input type="text" value="${provider.model_filter || ''}" onchange="updateProvider('${key}', 'model_filter', this.value)" placeholder="e.g., free, pro, flash">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                When no models are manually configured, only expose models whose ID contains this filter word (case-insensitive wildcard matching).
                <br>Example: "free" will match models like "model-name-free", "free-model", "gpt-4-free-tier", etc.
            </small>
        </div>

        <div id="models-${key}"></div>
        <div style="display: flex; gap: 10px; margin-top: 10px;">
            <button type="button" class="btn" onclick="getModelsFromProvider('${key}')" style="background: #4a9eff;">
                🔄 ${window.i18n.t('providers.fetch_models')}
            </button>
            <button type="button" class="btn btn-secondary" onclick="addModel('${key}')">${window.i18n.t('providers.add_model')}</button>
        </div>
        <div id="get-models-status-${key}" style="margin-top: 10px;"></div>

        <div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--color-border); display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
            <button type="button" class="btn" onclick="saveProvider('${safeKey}')" style="background: #22c55e;">${window.i18n.t('providers.save_this')}</button>
            <span id="save-status-${key}" style="font-size: 13px; color: var(--color-muted);"></span>
        </div>
    `;

    renderModels(key);

    if (isCodexProvider) {
        const cached = _usageCache[key];
        if (cached && cached.data) {
            const fullEl = document.getElementById(`usage-full-${key}`);
            if (fullEl) fullEl.innerHTML = _renderUsageFull(cached.data);
        } else {
            fetchProviderUsage(key).then(data => {
                const fullEl = document.getElementById(`usage-full-${key}`);
                if (fullEl) fullEl.innerHTML = _renderUsageFull(data);
                updateUsageBadge(key);
            });
        }
    }
}

function renderModels(providerKey) {
    const container = document.getElementById(`models-${providerKey}`);
    const provider = providersData[providerKey];
    
    if (!provider.models || provider.models.length === 0) {
        container.innerHTML = `<p style="color: var(--color-muted);">${window.i18n.t('providers.no_models')}</p>`;
        return;
    }
    
    container.innerHTML = '';
    provider.models.forEach((model, index) => {
        const modelDiv = document.createElement('div');
        modelDiv.style.cssText = 'border: 1px solid var(--color-border); padding: 15px; margin-bottom: 10px; border-radius: 3px; background: var(--bg-page);';
        
        modelDiv.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
                <strong>${window.i18n.t('providers.model_label')} ${index + 1}</strong>
                <button type="button" class="btn btn-secondary" onclick="removeModel('${providerKey}', ${index})" style="background: #dc3545; padding: 5px 10px; font-size: 12px;">${window.i18n.t('providers.remove')}</button>
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_name')}</label>
                <input type="text" value="${model.name}" onchange="updateModel('${providerKey}', ${index}, 'name', this.value)" required>
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.rate_limit')}</label>
                <input type="number" value="${model.rate_limit || 0}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit', parseFloat(this.value))" step="0.1">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.max_tokens')}</label>
                <input type="number" value="${model.max_request_tokens || ''}" onchange="updateModel('${providerKey}', ${index}, 'max_request_tokens', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.context_size')}</label>
                <input type="number" value="${model.context_size || ''}" onchange="updateModel('${providerKey}', ${index}, 'context_size', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_rate_limit_tpm')}</label>
                <input type="number" value="${model.rate_limit_TPM || ''}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit_TPM', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_rate_limit_tph')}</label>
                <input type="number" value="${model.rate_limit_TPH || ''}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit_TPH', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_rate_limit_tpd')}</label>
                <input type="number" value="${model.rate_limit_TPD || ''}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit_TPD', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_condense_context')}</label>
                <input type="number" value="${model.condense_context || ''}" onchange="updateModel('${providerKey}', ${index}, 'condense_context', this.value ? parseInt(this.value) : null)" placeholder="Optional (default: 80)">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_condense_method')}</label>
                <input type="text" value="${Array.isArray(model.condense_method) ? model.condense_method.join(', ') : (model.condense_method || '')}" onchange="updateModelCondenseMethod('${providerKey}', ${index}, this.value)" placeholder="e.g., semantic, conversational">
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${model.nsfw ? 'checked' : ''} onchange="updateModel('${providerKey}', ${index}, 'nsfw', this.checked)">
                    NSFW
                </label>
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${model.privacy ? 'checked' : ''} onchange="updateModel('${providerKey}', ${index}, 'privacy', this.checked)">
                    Privacy
                </label>
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${model.cache_enabled ? 'checked' : ''} onchange="setCacheSetting('${providerKey}', '${model.name || model.id}', this.checked)">
                    Enable Prompt Caching
                </label>
                <small style="color: var(--color-subtle); font-size: 12px;">
                    <strong>Model-level cache override:</strong> Disabling this prevents prompt caching for this specific model.
                    Overrides both provider-level cache setting and global cache setting.
                </small>
            </div>
        `;
        
        container.appendChild(modelDiv);
    });
}



function showAddProviderForm() {
    document.getElementById('new-provider-form').style.display = 'block';
    document.getElementById('add-provider-btn').style.display = 'none';
    document.getElementById('new-provider-key').value = '';
    document.getElementById('new-provider-type').value = 'openai';
    updateNewProviderDefaults();
    document.getElementById('new-provider-key').focus();
}

function cancelAddProvider() {
    document.getElementById('new-provider-form').style.display = 'none';
    document.getElementById('add-provider-btn').style.display = 'inline-block';
}

function updateNewProviderDefaults() {
    const providerType = document.getElementById('new-provider-type').value;
    const descriptionEl = document.getElementById('new-provider-type-description');

    const descriptions = {
        'openai': 'OpenAI-compatible provider. Uses standard API key authentication. Endpoint: https://api.openai.com/v1',
        'google': 'Google AI provider (Gemini). Uses API key authentication. Endpoint: https://generativelanguage.googleapis.com/v1beta',
        'anthropic': 'Anthropic provider (Claude). Uses API key authentication. Endpoint: https://api.anthropic.com/v1',
        'ollama': 'Ollama local provider. No API key required by default. Endpoint: http://localhost:11434/api',
        'kiro': 'Kiro (Amazon Q Developer) provider. Uses Kiro credentials (IDE, CLI, or direct tokens). Endpoint: https://q.us-east-1.amazonaws.com',
        'claude': 'Claude Code provider. Uses OAuth2 authentication (browser-based login). Endpoint: https://api.anthropic.com/v1',
        'kilocode': 'Kilocode provider. Uses OAuth2 Device Authorization Grant. Endpoint: https://api.kilo.ai/api/gateway',
        'qwen': 'Qwen provider. Uses OAuth2 Device Authorization Grant or API key. Endpoint: https://dashscope.aliyuncs.com/compatible-mode/v1',
        'codex': 'Codex provider. Uses OAuth2 Device Authorization Grant (same protocol as OpenAI). Endpoint: https://api.openai.com/v1'
    };

    descriptionEl.textContent = descriptions[providerType] || window.i18n.t('providers.standard_config');

    // Show/hide Claude warning
    const claudeWarningEl = document.getElementById('new-provider-claude-warning');
    if (providerType === 'claude') {
        claudeWarningEl.style.display = 'block';
    } else {
        claudeWarningEl.style.display = 'none';
    }

    // Show extension download prompt when Claude is selected on a non-local client without the extension
    const extensionPromptEl = document.getElementById('new-provider-extension-prompt');
    if (extensionPromptEl) {
        if (providerType === 'claude' && !IS_LOCAL_CLIENT && !window.aisbfOAuth2Extension) {
            const dlLink = document.getElementById('new-provider-extension-download');
            if (dlLink) dlLink.href = BASE_PATH + '/dashboard/extension/download';
            extensionPromptEl.style.display = 'block';
        } else {
            extensionPromptEl.style.display = 'none';
        }
    }
}

function togglePricingFields(key) {
    const provider = providersData[key];
    const pricingFields = document.getElementById(`pricing-fields-${key}`);
    if (pricingFields) {
        pricingFields.style.display = provider.is_subscription ? 'none' : 'block';
    }
}

// Helper function to format expiration time
function formatExpirationTime(expiresAt) {
    if (!expiresAt) return 'Unknown';
    
    const now = Math.floor(Date.now() / 1000);
    const secondsRemaining = expiresAt - now;
    
    if (secondsRemaining <= 0) {
        return 'Expired';
    }
    
    const days = Math.floor(secondsRemaining / 86400);
    const hours = Math.floor((secondsRemaining % 86400) / 3600);
    const minutes = Math.floor((secondsRemaining % 3600) / 60);
    const seconds = secondsRemaining % 60;
    
    if (days > 0) {
        return `${days} day${days > 1 ? 's' : ''} ${hours} hour${hours !== 1 ? 's' : ''}`;
    } else if (hours > 0) {
        return `${hours} hour${hours > 1 ? 's' : ''} ${minutes} minute${minutes !== 1 ? 's' : ''}`;
    } else if (minutes > 0) {
        return `${minutes} minute${minutes > 1 ? 's' : ''} ${seconds} second${seconds !== 1 ? 's' : ''}`;
    } else {
        return `${seconds} second${seconds > 1 ? 's' : ''}`;
    }
}

// OAuth authentication check functions
async function checkClaudeAuth(providerKey) {
    const statusDiv = document.getElementById(`claude-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.checking_auth'), {provider: 'Claude'});

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_valid'), {provider: 'Claude', expiry: expirationText});
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_failed'), {provider: 'Claude', error: result.error || window.i18n.t('providers.not_authenticated')});
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_error'), {provider: 'Claude', error: error.message});
    }
}

async function checkQwenAuth(providerKey) {
    const statusDiv = document.getElementById(`qwen-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.checking_auth'), {provider: 'Qwen'});

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_valid'), {provider: 'Qwen', expiry: expirationText});
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_failed'), {provider: 'Qwen', error: result.error || window.i18n.t('providers.not_authenticated')});
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_error'), {provider: 'Qwen', error: error.message});
    }
}

async function checkCodexAuth(providerKey) {
    const statusDiv = document.getElementById(`codex-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.checking_auth'), {provider: 'Codex'});

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_valid'), {provider: 'Codex', expiry: expirationText});
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_failed'), {provider: 'Codex', error: result.error || window.i18n.t('providers.not_authenticated')});
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_error'), {provider: 'Codex', error: error.message});
    }
}

async function checkKiloAuth(providerKey) {
    const statusDiv = document.getElementById(`kilo-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.checking_auth'), {provider: 'Kilocode'});

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_valid'), {provider: 'Kilocode', expiry: expirationText});
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_failed'), {provider: 'Kilocode', error: result.error || window.i18n.t('providers.not_authenticated')});
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.auth_error'), {provider: 'Kilocode', error: error.message});
    }
}

// OAuth authentication functions
async function authenticateClaude(key) {
    const statusEl = document.getElementById(`claude-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Starting OAuth2 authentication flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/claude/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key
            })
        });
        
        const data = await response.json();
        
        if (!data.success || !data.auth_url) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_start_failed'), {error: data.error || ''})}</p>`;
            return;
        }
        
        statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Opening authentication window...</p>';
        
        // Open OAuth2 URL in popup window
        const authWindow = window.open(data.auth_url, 'claude-auth', 'width=600,height=700');
        
        statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Please complete authentication in the popup window...</p>';
        
        // Poll for completion
        let pollCount = 0;
        const maxPolls = 60;
        let isCompleting = false;
        
        await new Promise(resolve => setTimeout(resolve, 8000));
        
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const statusResponse = await fetch(BASE_PATH + '/dashboard/claude/auth/callback-status');
                const statusData = await statusResponse.json();
                
                if (statusData.received) {
                    clearInterval(pollInterval);
                    
                    if (authWindow && !authWindow.closed) {
                        authWindow.close();
                    }
                    
                    if (statusData.error) {
                        statusEl.style.background = '#402010';
                        statusEl.style.border = '1px solid #ff4a4a';
                        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ OAuth2 error: ${statusData.error}</p>`;
                        return;
                    }
                    
                    if (isCompleting) return;
                    isCompleting = true;
                    
                    await new Promise(resolve => setTimeout(resolve, 1000));
                    
                    try {
                        statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Completing authentication...</p>';
                        
                        const completeResponse = await fetch(BASE_PATH + '/dashboard/claude/auth/complete', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json',
                            }
                        });
                        
                        const completeData = await completeResponse.json();
                        
                        if (completeData.success) {
                            statusEl.style.background = '#0f4020';
                            statusEl.style.border = '1px solid #4eff9e';
                            statusEl.innerHTML = '<p style="margin: 0; color: #4eff9e;">✓ Authentication successful! Credentials saved.</p>';
                        } else {
                            statusEl.style.background = '#402010';
                            statusEl.style.border = '1px solid #ff4a4a';
                            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Authentication incomplete. ${completeData.error || 'Please try again.'}</p>`;
                        }
                    } catch (error) {
                        statusEl.style.background = '#402010';
                        statusEl.style.border = '1px solid #ff4a4a';
                        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_error_completing'), {error: error.message})}</p>`;
                    }
                }
            } catch (error) {
                console.error('Error checking callback status:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                if (authWindow && !authWindow.closed) {
                    authWindow.close();
                }
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_timeout')}</p>`;
            }
        }, 2000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_generic_error'), {error: error.message})}</p>`;
    }
}

async function authenticateQwen(key) {
    const statusEl = document.getElementById(`qwen-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Starting Qwen OAuth2 Device Authorization flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/qwen/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key
            })
        });
        
        const data = await response.json();
        
        if (!data.success) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_start_failed'), {error: data.error || ''})}</p>`;
            return;
        }
        
        statusEl.style.background = 'var(--bg-panel)';
        statusEl.style.border = '1px solid #4a9eff';
        statusEl.innerHTML = `
            <div style="margin: 0;">
                <p style="margin: 0 0 10px 0; color: var(--color-link); font-weight: bold;">🔐 Qwen Device Authorization</p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Please visit: <a href="${data.verification_uri}" target="_blank" style="color: #4eff9e; text-decoration: underline;">${data.verification_uri}</a>
                </p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Enter code: <strong style="color: #4eff9e; font-size: 18px; letter-spacing: 2px;">${data.user_code}</strong>
                </p>
                <p style="margin: 0; color: var(--color-muted); font-size: 13px;">
                    Waiting for authorization... (expires in ${Math.floor(data.expires_in / 60)} minutes)
                </p>
            </div>
        `;
        
        try {
            window.open(data.verification_uri, 'qwen-auth', 'width=600,height=700');
        } catch (e) {
            console.error('Could not open auth window:', e);
        }
        
        let pollCount = 0;
        const maxPolls = Math.floor(data.expires_in / data.interval);
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const pollResponse = await fetch(BASE_PATH + '/dashboard/qwen/auth/poll', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });
                
                const pollData = await pollResponse.json();
                
                if (pollData.status === 'completed') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#0f4020';
                    statusEl.style.border = '1px solid #4eff9e';
                    statusEl.innerHTML = `<p style="margin: 0; color: #4eff9e;">${window.i18n.interpolate(window.i18n.t('providers.auth_success'), {provider: 'Qwen'})}</p>`;
                } else if (pollData.status === 'denied') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_denied')}</p>`;
                } else if (pollData.status === 'expired') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_expired')}</p>`;
                }
                
            } catch (error) {
                console.error('Error polling Qwen auth:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_timeout')}</p>`;
            }
        }, data.interval * 1000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_generic_error'), {error: error.message})}</p>`;
    }
}

async function pollQwenAuth(providerKey, deviceCode) {
    // This function is no longer needed as polling is handled in authenticateQwen
}

async function authenticateCodex(key) {
    const statusEl = document.getElementById(`codex-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Starting Codex OAuth2 Device Authorization flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/codex/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key,
                issuer: providersData[key].codex_config?.issuer || 'https://auth.openai.com'
            })
        });
        
        const data = await response.json();
        
        if (!data.success) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_start_failed'), {error: data.error || ''})}</p>`;
            return;
        }
        
        statusEl.style.background = 'var(--bg-panel)';
        statusEl.style.border = '1px solid #4a9eff';
        statusEl.innerHTML = `
            <div style="margin: 0;">
                <p style="margin: 0 0 10px 0; color: var(--color-link); font-weight: bold;">🔐 Codex Device Authorization</p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Please visit: <a href="${data.verification_uri}" target="_blank" style="color: #4eff9e; text-decoration: underline;">${data.verification_uri}</a>
                </p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Enter code: <strong style="color: #4eff9e; font-size: 18px; letter-spacing: 2px;">${data.user_code}</strong>
                </p>
                <p style="margin: 0; color: var(--color-muted); font-size: 13px;">
                    Waiting for authorization... (expires in ${Math.floor(data.expires_in / 60)} minutes)
                </p>
            </div>
        `;
        
        try {
            window.open(data.verification_uri, 'codex-auth', 'width=600,height=700');
        } catch (e) {
            console.error('Could not open auth window:', e);
        }
        
        let pollCount = 0;
        const maxPolls = Math.floor(data.expires_in / data.interval);
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const pollResponse = await fetch(BASE_PATH + '/dashboard/codex/auth/poll', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });
                
                const pollData = await pollResponse.json();
                
                if (pollData.status === 'approved') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#0f4020';
                    statusEl.style.border = '1px solid #4eff9e';
                    statusEl.innerHTML = `<p style="margin: 0; color: #4eff9e;">${window.i18n.interpolate(window.i18n.t('providers.auth_success'), {provider: 'Codex'})}</p>`;
                } else if (pollData.status === 'denied') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_denied')}</p>`;
                } else if (pollData.status === 'expired') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_expired')}</p>`;
                }
                
            } catch (error) {
                console.error('Error polling Codex auth:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_timeout')}</p>`;
            }
        }, data.interval * 1000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_generic_error'), {error: error.message})}</p>`;
    }
}

async function pollCodexAuth(providerKey, deviceCode) {
    // This function is no longer needed as polling is handled in authenticateCodex
}

async function authenticateKilo(key) {
    const statusEl = document.getElementById(`kilo-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: var(--color-link);">🔄 Starting Kilocode OAuth2 Device Authorization flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/kilo/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key
            })
        });
        
        const data = await response.json();
        
        if (!data.success) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_start_failed'), {error: data.error || ''})}</p>`;
            return;
        }
        
        statusEl.style.background = 'var(--bg-panel)';
        statusEl.style.border = '1px solid #4a9eff';
        statusEl.innerHTML = `
            <div style="margin: 0;">
                <p style="margin: 0 0 10px 0; color: var(--color-link); font-weight: bold;">🔐 Kilocode Device Authorization</p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Please visit: <a href="${data.verification_uri}" target="_blank" style="color: #4eff9e; text-decoration: underline;">${data.verification_uri}</a>
                </p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Enter code: <strong style="color: #4eff9e; font-size: 18px; letter-spacing: 2px;">${data.user_code}</strong>
                </p>
                <p style="margin: 0; color: var(--color-muted); font-size: 13px;">
                    Waiting for authorization... (expires in ${Math.floor(data.expires_in / 60)} minutes)
                </p>
            </div>
        `;
        
        try {
            window.open(data.verification_uri, 'kilo-auth', 'width=600,height=700');
        } catch (e) {
            console.error('Could not open auth window:', e);
        }
        
        let pollCount = 0;
        const maxPolls = Math.floor(data.expires_in / data.interval);
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const pollResponse = await fetch(BASE_PATH + '/dashboard/kilo/auth/poll', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });
                
                const pollData = await pollResponse.json();
                
                if (pollData.status === 'completed') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#0f4020';
                    statusEl.style.border = '1px solid #4eff9e';
                    statusEl.innerHTML = `<p style="margin: 0; color: #4eff9e;">${window.i18n.interpolate(window.i18n.t('providers.auth_success'), {provider: 'Kilocode'})}</p>`;
                } else if (pollData.status === 'denied') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_denied')}</p>`;
                } else if (pollData.status === 'expired') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_expired')}</p>`;
                }
                
            } catch (error) {
                console.error('Error polling Kilo auth:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.t('providers.auth_timeout')}</p>`;
            }
        }, data.interval * 1000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">${window.i18n.interpolate(window.i18n.t('providers.auth_generic_error'), {error: error.message})}</p>`;
    }
}

async function pollKiloAuth(providerKey, deviceCode) {
    // This function is no longer needed as polling is handled in authenticateKilo
}

async function saveProvider(key) {
    const statusEl = document.getElementById(`save-status-${key}`);
    if (statusEl) statusEl.textContent = window.i18n.t('providers.saving');
    try {
        const result = await apiCall('POST', '/dashboard/api/provider', {
            provider_id: key,
            config: providersData[key]
        });
        if (result.success) {
            if (statusEl) { statusEl.textContent = window.i18n.t('providers.saved'); statusEl.style.color = '#4ade80'; setTimeout(() => { statusEl.textContent = ''; }, 3000); }
        } else {
            if (statusEl) { statusEl.textContent = 'Error: ' + (result.error || 'Unknown'); statusEl.style.color = '#f87171'; }
        }
    } catch (e) {
        if (statusEl) { statusEl.textContent = 'Error: ' + e.message; statusEl.style.color = '#f87171'; }
    }
}

async function removeProvider(key) {
    if (await showDangerConfirm(window.i18n.interpolate(window.i18n.t('providers.remove_provider_confirm'), {key}), window.i18n.t('providers.remove_provider_title'))) {
        try {
            const result = await apiCall('DELETE', '/dashboard/api/provider/' + encodeURIComponent(key));
            if (!result.success) {
                showAlert('Error removing provider: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
                return;
            }
        } catch (e) {
            showAlert('Error removing provider: ' + e.message, 'Error', '❌', 'danger');
            return;
        }
        delete providersData[key];
        providerMasterOrder = providerMasterOrder.filter(k => k !== key);
        expandedProviders.delete(key);
        renderProvidersList();
    }
}

function updateProvider(key, field, value) {
    providersData[key][field] = value;
}

function updateKiroConfig(key, field, value) {
    if (!providersData[key].kiro_config) {
        providersData[key].kiro_config = {};
    }
    providersData[key].kiro_config[field] = value;
}

function updateClaudeConfig(key, field, value) {
    if (!providersData[key].claude_config) {
        providersData[key].claude_config = {};
    }
    providersData[key].claude_config[field] = value;
}

function updateKiloConfig(key, field, value) {
    if (!providersData[key].kilo_config) {
        providersData[key].kilo_config = {};
    }
    providersData[key].kilo_config[field] = value;
}

function updateQwenConfig(key, field, value) {
    if (!providersData[key].qwen_config) {
        providersData[key].qwen_config = {};
    }
    providersData[key].qwen_config[field] = value;
}

function updateCodexConfig(key, field, value) {
    if (!providersData[key].codex_config) {
        providersData[key].codex_config = {};
    }
    providersData[key].codex_config[field] = value;
}

function updateProviderType(key, value) {
    providersData[key].type = value;
    const defaultEndpoint = getDefaultEndpoint(value);
    if (defaultEndpoint) {
        providersData[key].endpoint = defaultEndpoint;
    }
    // Re-render to update the configuration fields
    renderProvidersList();
}

function updateProviderCondenseMethod(key, value) {
    const trimmed = value.trim();
    if (!trimmed) {
        providersData[key].default_condense_method = null;
        return;
    }

    // Check if it's a comma-separated list
    if (trimmed.includes(',')) {
        providersData[key].default_condense_method = trimmed.split(',').map(s => s.trim()).filter(s => s);
    } else {
        providersData[key].default_condense_method = trimmed;
    }
}

function addModel(key) {
    if (!providersData[key].models) {
        providersData[key].models = [];
    }

    providersData[key].models.push({
        name: '',
        rate_limit: 0
    });

    renderModels(key);
}

async function removeModel(providerKey, index) {
    if (await showDangerConfirm(window.i18n.t('providers.remove_model_confirm'), window.i18n.t('providers.remove_model_title'))) {
        providersData[providerKey].models.splice(index, 1);
        renderModels(providerKey);
    }
}

async function confirmAddProvider() {
    const key = document.getElementById('new-provider-key').value.trim();
    const type = document.getElementById('new-provider-type').value;

    if (!key) {
        showAlert(window.i18n.t('providers.missing_key'), window.i18n.t('providers.missing_key_title'), '⚠️', 'warn');
        return;
    }

    if (providersData[key]) {
        showAlert(window.i18n.t('providers.duplicate_key'), window.i18n.t('providers.duplicate_key_title'), '⚠️', 'warn');
        return;
    }

    // Create new provider with defaults
    providersData[key] = {
        id: key,
        name: key.charAt(0).toUpperCase() + key.slice(1),
        endpoint: getDefaultEndpoint(type),
        type: type,
        api_key_required: type !== 'ollama' && type !== 'kiro' && type !== 'claude' && type !== 'kilocode' && type !== 'qwen' && type !== 'codex',
        rate_limit: 0,
        models: []
    };

    // Add type-specific config
    if (type === 'kiro') {
        providersData[key].kiro_config = {
            region: 'us-east-1',
            refresh_token: '',
            profile_arn: '',
            client_id: '',
            client_secret: ''
        };
    } else if (type === 'claude') {
        providersData[key].claude_config = {};
    } else if (type === 'kilocode') {
        providersData[key].kilo_config = {
            api_base: 'https://api.kilo.ai/api/gateway'
        };
    } else if (type === 'qwen') {
        providersData[key].qwen_config = {
            api_key: '',
            region: 'china-beijing',
            workspace_id: 'Default Workspace'
        };
    } else if (type === 'codex') {
        providersData[key].codex_config = {
            issuer: 'https://auth.openai.com'
        };
    }

    try {
        const result = await apiCall('POST', '/dashboard/api/provider', {
            provider_id: key,
            config: providersData[key]
        });
        if (!result.success) {
            showAlert('Error creating provider: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
            delete providersData[key];
            return;
        }
    } catch (e) {
        showAlert('Error creating provider: ' + e.message, 'Error', '❌', 'danger');
        delete providersData[key];
        return;
    }

    if (!providerMasterOrder.includes(key)) providerMasterOrder.push(key);
    expandedProviders.add(key);
    cancelAddProvider();
    renderProvidersList();
}

function getDefaultEndpoint(type) {
    const defaults = {
        'openai': 'https://api.openai.com/v1',
        'google': 'https://generativelanguage.googleapis.com/v1beta',
        'anthropic': 'https://api.anthropic.com/v1',
        'ollama': 'http://localhost:11434/api',
        'kiro': 'https://q.us-east-1.amazonaws.com',
        'claude': 'https://api.anthropic.com/v1',
        'kilocode': 'https://api.kilo.ai/api/gateway',
        'qwen': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
        'codex': 'https://api.openai.com/v1'
    };
    return defaults[type] || '';
}

async function getModelsFromProvider(key) {
    const statusDiv = document.getElementById(`get-models-status-${key}`);
    if (!statusDiv) return;

    statusDiv.innerHTML = window.i18n.t('providers.fetching_models');

    try {
        const response = await fetch(BASE_PATH + '/dashboard/providers/get-models', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key,
                provider: providersData[key]
            })
        });

        const result = await response.json();

        if (response.ok && result.models) {
            providersData[key].models = result.models;
            renderModels(key);
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.models_found'), {n: result.models.length});
        } else {
            statusDiv.innerHTML = window.i18n.interpolate(window.i18n.t('providers.models_fetch_error'), {error: result.error || ''});
        }
    } catch (error) {
        statusDiv.innerHTML = `❌ Error: ${error.message}`;
    }
}

function updateModel(providerKey, index, field, value) {
    providersData[providerKey].models[index][field] = value;
}

function updateModelCondenseMethod(providerKey, index, value) {
    const trimmed = value.trim();
    if (!trimmed) {
        providersData[providerKey].models[index].condense_method = null;
        return;
    }

    // Check if it's a comma-separated list
    if (trimmed.includes(',')) {
        providersData[providerKey].models[index].condense_method = trimmed.split(',').map(s => s.trim()).filter(s => s);
    } else {
        providersData[providerKey].models[index].condense_method = trimmed;
    }
}

async function saveProviders() {
    try {
        const response = await fetch(BASE_PATH + '/dashboard/providers', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: (function() {
                const orderedData = {};
                providerMasterOrder.forEach(k => { if (providersData[k]) orderedData[k] = providersData[k]; });
                return 'config=' + encodeURIComponent(JSON.stringify(orderedData, null, 2));
            })()
        });

        if (response.ok) {
            window.location.href = BASE_PATH + '/dashboard/providers?success=1';
        } else {
            showAlert(window.i18n.t('providers.error_saving'), 'Error', '❌', 'danger');
        }
    } catch (error) {
        showAlert('Error: ' + error.message, 'Error', '❌', 'danger');
    }
}

async function initProviders() {
    await loadCacheSettings();
    renderProvidersList();
    refreshAllUsage().then(() => renderProvidersList());
}

// Defer until i18n is ready so translated strings are available
function _doInitialRender() { initProviders(); }
if (window.i18n) { _doInitialRender(); } else { document.addEventListener('i18n:ready', _doInitialRender, {once: true}); }

</script>
{% endblock %}
