// PRE — Settings Panel
(() => {
  const escapeHtml = PRE.escapeHtml;

  const SERVICE_ICONS = {
    brave_search: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L3 7v10l9 5 9-5V7L12 2zm0 2.18l6.66 3.7L12 11.56 5.34 7.88 12 4.18z"/></svg>',
    github: '<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>',
    google: '<svg width="18" height="18" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>',
    wolfram: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7v10l10 5 10-5V7L12 2z"/></svg>',
    telegram: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.479.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/></svg>',
    jira: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M11.571 11.513H0a5.218 5.218 0 0 0 5.232 5.215h2.13v2.057A5.215 5.215 0 0 0 12.575 24V12.518a1.005 1.005 0 0 0-1.005-1.005zm5.723-5.756H5.736a5.215 5.215 0 0 0 5.215 5.214h2.129v2.058a5.218 5.218 0 0 0 5.215 5.214V6.758a1.001 1.001 0 0 0-1.001-1.001zM23.013 0H11.455a5.215 5.215 0 0 0 5.215 5.215h2.129v2.057A5.215 5.215 0 0 0 24.013 12.5V1.005A1.005 1.005 0 0 0 23.013 0z"/></svg>',
    confluence: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M.87 18.257c-.248.382-.53.875-.763 1.245a.764.764 0 0 0 .255 1.04l4.965 3.054a.764.764 0 0 0 1.058-.26c.199-.332.494-.833.812-1.39 1.398-2.45 2.818-2.16 5.348-.875l4.947 2.51a.764.764 0 0 0 1.013-.382l2.227-5.093a.764.764 0 0 0-.357-1.006c-1.24-.63-3.45-1.76-4.986-2.537-5.14-2.602-9.01-2.269-14.519 3.694zm22.26-12.514c.249-.383.531-.876.764-1.246a.764.764 0 0 0-.256-1.04L18.674.403a.764.764 0 0 0-1.058.26c-.2.332-.494.833-.813 1.39-1.397 2.45-2.817 2.16-5.347.875L6.509.418A.764.764 0 0 0 5.496.8L3.27 5.893a.764.764 0 0 0 .357 1.006c1.24.63 3.45 1.76 4.985 2.537 5.14 2.602 9.01 2.27 14.52-3.693z"/></svg>',
    smartsheet: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M22.157 0H1.843A1.843 1.843 0 0 0 0 1.843v20.314A1.843 1.843 0 0 0 1.843 24h20.314A1.843 1.843 0 0 0 24 22.157V1.843A1.843 1.843 0 0 0 22.157 0zM5.5 7h13a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5zm0 4h13a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5zm0 4h13a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5z"/></svg>',
    slack: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.27 0a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.163 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.163 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.163 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zm0-1.27a2.527 2.527 0 0 1-2.52-2.523 2.527 2.527 0 0 1 2.52-2.52h6.315A2.528 2.528 0 0 1 24 15.163a2.528 2.528 0 0 1-2.522 2.523h-6.315z"/></svg>',
    microsoft: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M0 0h11.377v11.372H0zm12.623 0H24v11.372H12.623zM0 12.623h11.377V24H0zm12.623 0H24V24H12.623z"/></svg>',
    linear: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M2.652 11.422a.727.727 0 0 1 0-1.03L11.422 1.62a.727.727 0 0 1 .515-.213c.193 0 .378.077.515.213l8.926 8.926a.727.727 0 0 1 0 1.03l-8.926 8.926a.727.727 0 0 1-1.03 0l-8.77-8.08z"/></svg>',
    zoom: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M4.585 6.836A3.252 3.252 0 0 0 1.34 10.09v3.82a3.252 3.252 0 0 0 3.245 3.254h8.348v-3.82H6.53a1.95 1.95 0 0 1-1.945-1.954V8.79h11.608a1.95 1.95 0 0 1 1.945 1.6l3.358-2.26A1.3 1.3 0 0 0 22.66 7V5.75a.92.92 0 0 0-.917-.914H4.585v2zm17.07 3.234l-3.357 2.26v4.834a.92.92 0 0 1-.917.914h-4.453v2h5.37a3.25 3.25 0 0 0 3.245-3.254v-3.82a3.25 3.25 0 0 0-.888-2.934z"/></svg>',
    figma: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M8 24c2.208 0 4-1.792 4-4v-4H8c-2.208 0-4 1.792-4 4s1.792 4 4 4zm0-20c-2.208 0-4 1.792-4 4s1.792 4 4 4h4V4c0-2.208-1.792-4-4-4zm0 8c-2.208 0-4 1.792-4 4s1.792 4 4 4h4v-8H8zm8-8h-4v8h4c2.208 0 4-1.792 4-4s-1.792-4-4-4zm0 12a4 4 0 1 0 0 8 4 4 0 0 0 0-8z"/></svg>',
    asana: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="6" r="5.5"/><circle cx="5.5" cy="17.5" r="5.5"/><circle cx="18.5" cy="17.5" r="5.5"/></svg>',
    dynamics365: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M4 4l8-4v10.6L4 14V4zm0 12l8-3.4V24l-8-4V16zm10-5.4L22 8v8l-8 4V10.6z"/></svg>',
    servicenow: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10c2.05 0 3.97-.61 5.58-1.66.46-.3 1.04-.36 1.53-.13.49.22.84.69.93 1.22.02.12.04.24.06.36.04.32.31.55.63.55.35 0 .62-.29.61-.64-.05-1.61-.97-3.07-2.39-3.79l-.05-.02C20.32 16.27 22 14.32 22 12 22 6.48 17.52 2 12 2zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5z"/></svg>',
  };

  async function openSettingsPanel() {
    const content = PRE.openPanel('Settings');

    try {
      const [connRes, mcpRes, sysRes, provRes, routerRes] = await Promise.all([
        fetch('/api/connections'),
        fetch('/api/mcp'),
        fetch('/api/system/autostart'),
        fetch('/api/provider'),
        fetch('/api/router'),
      ]);
      const connections = await connRes.json();
      const mcpStatus = await mcpRes.json();
      const autostart = await sysRes.json();
      const provider = await provRes.json();
      const routerCfg = await routerRes.json();
      renderConnectionsPanel(content, connections, mcpStatus, autostart, provider, routerCfg);
    } catch {
      content.innerHTML = '<p style="color:var(--danger)">Failed to load settings</p>';
    }
  }

  function renderConnectionsPanel(container, connections, mcpStatus, autostart, provider, routerCfg) {
    let html = '';

    // Tutorial banner at top
    html += '<div style="margin-bottom:20px;background:linear-gradient(135deg,#1e293b,#0f172a);border-radius:10px;padding:16px 20px;display:flex;align-items:center;gap:14px;cursor:pointer;transition:transform 0.15s,box-shadow 0.15s" onclick="window.open(\'/tutorial.html\',\'_blank\')" onmouseenter="this.style.transform=\'translateY(-1px)\';this.style.boxShadow=\'0 4px 12px rgba(0,0,0,0.15)\'" onmouseleave="this.style.transform=\'\';this.style.boxShadow=\'\'">';
    html += '<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#2563eb,#7c3aed);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0">\uD83C\uDF93</div>';
    html += '<div style="flex:1;min-width:0">';
    html += '<div style="font-family:Georgia,serif;font-weight:700;font-size:0.95rem;color:#fff">PRE Academy</div>';
    html += '<div style="font-size:0.78rem;color:#94a3b8">Interactive tutorial — learn every feature step by step</div>';
    html += '</div>';
    html += '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg>';
    html += '</div>';

    // ── Model Provider section ──
    const provType = provider ? (provider.active || provider.type || 'ollama') : 'ollama';
    const provOpenai = (provider && provider.openai) || {};
    const provAzure = (provider && provider.azure) || {};
    const provAnthropic = (provider && provider.anthropic) || {};
    const provGemini = (provider && provider.gemini) || {};
    const provLlamaCpp = (provider && provider.llamacpp) || {};
    const provTinygrad = (provider && provider.tinygrad) || {};
    const isRemote = provType === 'openai' || provType === 'azure' || provType === 'anthropic' || provType === 'gemini';
    const isAzure = provType === 'azure';
    const isAnthropic = provType === 'anthropic';
    const isGemini = provType === 'gemini';
    const isLlamaCpp = provType === 'llamacpp';
    const isTinygrad = provType === 'tinygrad';
    const inputStyle = 'width:100%;padding:8px 10px;border-radius:6px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary);font-size:0.85rem;box-sizing:border-box';

    function radioLabel(value, label, checked) {
      return `<label style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:8px 14px;border-radius:8px;border:1px solid ${checked ? 'var(--accent)' : 'var(--border)'};background:${checked ? 'var(--accent-bg,rgba(37,99,235,0.1))' : 'transparent'}"><input type="radio" name="provider-type" value="${value}" ${checked ? 'checked' : ''} onchange="Settings.toggleProvider(this.value)" style="accent-color:var(--accent)"><span style="font-size:0.85rem">${label}</span></label>`;
    }

    html += '<div class="settings-section" style="margin-bottom:20px">';
    html += '<div class="settings-section-title">Model Provider</div>';
    html += '<div style="display:flex;gap:12px;margin-bottom:12px;flex-wrap:wrap">';
    html += radioLabel('ollama', 'Local (Ollama)', provType === 'ollama');
    html += radioLabel('llamacpp', 'llama.cpp Server', provType === 'llamacpp');
    html += radioLabel('tinygrad', 'tinygrad (eGPU)', provType === 'tinygrad');
    html += radioLabel('openai', 'Remote (OpenAI)', provType === 'openai');
    html += radioLabel('azure', 'Azure (OpenAI)', provType === 'azure');
    html += radioLabel('anthropic', 'Anthropic / Azure (Claude)', provType === 'anthropic');
    html += radioLabel('gemini', 'Gemini (Google AI)', provType === 'gemini');
    html += '</div>';

    // ── llama.cpp Server config ──
    html += `<div id="provider-llamacpp-config" style="display:${isLlamaCpp ? 'block' : 'none'}">`;
    html += '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">Direct llama.cpp server for grammar-constrained generation, speculative decoding, and KV cache control. Ollama stays running for embeddings.</div>';

    // Model path
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Model (GGUF path or Ollama tag)</label>';
    html += `<div style="display:flex;gap:8px"><input id="llamacpp-model" type="text" value="${escapeHtml(provLlamaCpp.model_path || '')}" placeholder="pre-gemma4 or /path/to/model.gguf" style="${inputStyle};flex:1">`;
    html += '<button class="btn btn-ghost btn-sm" onclick="Settings.browseLlamaCppModels()" style="white-space:nowrap">Browse</button></div>';
    html += '<div id="llamacpp-model-list" style="display:none;margin-top:6px"></div>';
    html += '</div>';

    // Draft model (optional, for speculative decoding)
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Draft Model (optional, for speculative decoding)</label>';
    html += `<input id="llamacpp-draft" type="text" value="${escapeHtml(provLlamaCpp.draft_model_path || '')}" placeholder="gemma:2b or /path/to/draft.gguf" style="${inputStyle}">`;
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">Small same-family model for 1.5-2x decode speedup. Leave empty to disable.</div>';
    html += '</div>';

    // Port
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Port</label>';
    html += `<input id="llamacpp-port" type="number" value="${provLlamaCpp.port || 8080}" min="1024" max="65535" style="${inputStyle}">`;
    html += '</div>';

    // GPU Layers
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">GPU Layers (-1 = all)</label>';
    html += `<input id="llamacpp-gpu-layers" type="number" value="${provLlamaCpp.n_gpu_layers ?? -1}" min="-1" max="200" style="${inputStyle}">`;
    html += '</div>';

    // Toggles row
    html += '<div style="display:flex;gap:16px;margin-bottom:14px;flex-wrap:wrap">';
    html += `<label style="display:flex;align-items:center;gap:6px;font-size:0.85rem;cursor:pointer"><input type="checkbox" id="llamacpp-flash" ${provLlamaCpp.flash_attn !== false ? 'checked' : ''}> Flash Attention</label>`;
    html += `<label style="display:flex;align-items:center;gap:6px;font-size:0.85rem;cursor:pointer"><input type="checkbox" id="llamacpp-grammar" ${provLlamaCpp.grammar_enabled !== false ? 'checked' : ''}> Grammar Constraints</label>`;
    html += '</div>';

    // Buttons
    html += '<div class="connection-card-actions" style="flex-wrap:wrap">';
    html += '<button class="btn btn-ghost btn-sm" onclick="Settings.checkLlamaCppStatus()">Check Status</button>';
    html += '<button class="btn btn-primary btn-sm" onclick="Settings.saveLlamaCpp()">Save & Activate</button>';
    if (provLlamaCpp.model_path) {
      html += '<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeProvider()">Revert to Ollama</button>';
    }
    html += '</div>';
    html += '<div id="llamacpp-status-result" style="margin-top:8px;font-size:0.8rem"></div>';
    html += '</div>'; // end provider-llamacpp-config

    // ── tinygrad (eGPU) config ──
    html += `<div id="provider-tinygrad-config" style="display:${isTinygrad ? 'block' : 'none'}">`;
    html += '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">tinygrad inference server on external GPU (TinyGPU). Supports Llama, Qwen, DeepSeek, and other GGUF models via OpenAI-compatible API. Primary inference (Gemma 4) stays on Ollama.</div>';

    // GPU status badge
    html += '<div id="tinygrad-gpu-status" style="margin-bottom:12px;padding:8px 12px;border-radius:6px;background:var(--bg-secondary);font-size:0.82rem"></div>';

    // Model path
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">GGUF Model Path</label>';
    html += `<input id="tinygrad-model" type="text" value="${escapeHtml(provTinygrad.model_path || '')}" placeholder="/path/to/model.gguf" style="${inputStyle}">`;
    html += '</div>';

    // Tokenizer
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Tokenizer (HuggingFace name or "auto")</label>';
    html += `<input id="tinygrad-tokenizer" type="text" value="${escapeHtml(provTinygrad.tokenizer_name || 'auto')}" placeholder="auto" style="${inputStyle}">`;
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">auto = detect from GGUF architecture. Examples: Qwen/Qwen3-32B, meta-llama/Llama-3.3-70B-Instruct</div>';
    html += '</div>';

    // Port
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Port</label>';
    html += `<input id="tinygrad-port" type="number" value="${provTinygrad.port || 8091}" min="1024" max="65535" style="${inputStyle}">`;
    html += '</div>';

    // Context size
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Context Size</label>';
    html += `<input id="tinygrad-ctx" type="number" value="${provTinygrad.context_size || 8192}" min="512" max="131072" step="512" style="${inputStyle}">`;
    html += '</div>';

    // Auto-start toggle
    html += '<div style="display:flex;gap:16px;margin-bottom:14px;flex-wrap:wrap">';
    html += `<label style="display:flex;align-items:center;gap:6px;font-size:0.85rem;cursor:pointer"><input type="checkbox" id="tinygrad-autostart" ${provTinygrad.auto_start !== false ? 'checked' : ''}> Auto-start when eGPU detected</label>`;
    html += '</div>';

    // Python interpreter (optional — overrides system python3)
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Python Path (optional)</label>';
    html += `<input id="tinygrad-python-path" type="text" value="${escapeHtml(provTinygrad.python_path || '')}" placeholder="/path/to/venv/bin/python" style="${inputStyle}">`;
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">Use a venv that has tinygrad\'s deps installed. Leave blank to use system python3.</div>';
    html += '</div>';

    // tinygrad source checkout (optional — overrides pip-installed tinygrad)
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">tinygrad Source Path (optional)</label>';
    html += `<input id="tinygrad-source-path" type="text" value="${escapeHtml(provTinygrad.tinygrad_path || '')}" placeholder="/path/to/tinygrad-checkout" style="${inputStyle}">`;
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">Sets PYTHONPATH so a source checkout is used instead of a pip install. Leave blank if tinygrad is installed in the chosen Python.</div>';
    html += '</div>';

    // Buttons
    html += '<div class="connection-card-actions" style="flex-wrap:wrap">';
    html += '<button class="btn btn-ghost btn-sm" onclick="Settings.checkTinygradStatus()">Check Status</button>';
    html += '<button class="btn btn-primary btn-sm" onclick="Settings.saveTinygrad()">Save & Activate</button>';
    html += `<button id="tinygrad-start-btn" class="btn btn-ghost btn-sm" onclick="Settings.startTinygrad()" ${provTinygrad.model_path ? '' : 'disabled'}>Start Server</button>`;
    html += '<button id="tinygrad-stop-btn" class="btn btn-ghost btn-sm" onclick="Settings.stopTinygrad()">Stop Server</button>';
    if (provTinygrad.model_path) {
      html += '<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeProvider()">Revert to Ollama</button>';
    }
    html += '</div>';
    html += '<div id="tinygrad-status-result" style="margin-top:8px;font-size:0.8rem"></div>';
    html += '</div>'; // end provider-tinygrad-config

    // ── OpenAI-compatible config ──
    html += `<div id="provider-remote-config" style="display:${provType === 'openai' ? 'block' : 'none'}">`;

    // Preset dropdown
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Preset</label>';
    html += `<select id="provider-preset" onchange="Settings.applyPreset(this.value)" style="${inputStyle}">`;
    html += '<option value="custom">Custom</option>';
    html += '<option value="openai">OpenAI</option>';
    html += '<option value="groq">Groq</option>';
    html += '<option value="together">Together AI</option>';
    html += '<option value="openrouter">OpenRouter</option>';
    html += '<option value="vllm">vLLM / LM Studio</option>';
    html += '</select></div>';

    // Base URL
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Base URL</label>';
    html += `<input id="provider-url" type="text" value="${escapeHtml(provOpenai.base_url || '')}" placeholder="https://api.openai.com/v1" style="${inputStyle}">`;
    html += '</div>';

    // API Key
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">API Key</label>';
    html += `<input id="provider-key" type="password" value="" placeholder="${provOpenai.api_key ? provOpenai.api_key : 'sk-...'}" style="${inputStyle}">`;
    html += '</div>';

    // Model
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Model</label>';
    html += `<input id="provider-model" type="text" value="${escapeHtml(provOpenai.model || '')}" placeholder="gpt-4o" style="${inputStyle}">`;
    html += '</div>';

    // Max Tokens
    html += '<div style="margin-bottom:14px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Max Tokens per Response</label>';
    html += `<input id="provider-max-tokens" type="number" value="${provOpenai.max_tokens || 4096}" min="256" max="128000" step="256" style="${inputStyle}">`;
    html += '</div>';

    // Buttons
    html += '<div class="connection-card-actions" style="flex-wrap:wrap">';
    html += '<button class="btn btn-ghost btn-sm" onclick="Settings.testProvider()">Test Connection</button>';
    html += '<button class="btn btn-primary btn-sm" onclick="Settings.saveProvider()">Save</button>';
    if (provOpenai.base_url) {
      html += '<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeProvider()">Revert to Local</button>';
    }
    html += '</div>';
    html += '<div id="provider-test-result" style="margin-top:8px;font-size:0.8rem"></div>';
    html += '</div>'; // end provider-remote-config

    // ── Azure AI Foundry config ──
    html += `<div id="provider-azure-config" style="display:${isAzure ? 'block' : 'none'}">`;

    // Endpoint URL (full deployment URL)
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Deployment Endpoint</label>';
    html += `<input id="azure-url" type="text" value="${escapeHtml(provAzure.base_url || '')}" placeholder="https://myresource.openai.azure.com/openai/deployments/gpt-4o/chat/completions" style="${inputStyle}">`;
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">For OpenAI models on Azure (GPT-4o, GPT-5.5, o-series). Paste the Target URI — auto-converts to chat/completions. For Claude on Azure, use the Anthropic tab instead.</div>';
    html += '</div>';

    // API Key
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">API Key</label>';
    html += `<input id="azure-key" type="password" value="" placeholder="${provAzure.api_key ? provAzure.api_key : 'Azure API key...'}" style="${inputStyle}">`;
    html += '</div>';

    // API Version
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">API Version</label>';
    html += `<input id="azure-version" type="text" value="${escapeHtml(provAzure.api_version || '2024-10-21')}" placeholder="2024-10-21" style="${inputStyle}">`;
    html += '</div>';

    // Model (display name, optional for Azure)
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Deployment Name</label>';
    html += `<input id="azure-model" type="text" value="${escapeHtml(provAzure.model || '')}" placeholder="gpt-4o" style="${inputStyle}">`;
    html += '</div>';

    // Max Tokens
    html += '<div style="margin-bottom:14px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Max Tokens per Response</label>';
    html += `<input id="azure-max-tokens" type="number" value="${provAzure.max_tokens || 4096}" min="256" max="128000" step="256" style="${inputStyle}">`;
    html += '</div>';

    // Buttons
    html += '<div class="connection-card-actions" style="flex-wrap:wrap">';
    html += '<button class="btn btn-ghost btn-sm" onclick="Settings.testAzure()">Test Connection</button>';
    html += '<button class="btn btn-primary btn-sm" onclick="Settings.saveAzure()">Save</button>';
    if (provAzure.base_url) {
      html += '<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeProvider()">Revert to Local</button>';
    }
    html += '</div>';
    html += '<div id="azure-test-result" style="margin-top:8px;font-size:0.8rem"></div>';
    html += '</div>'; // end provider-azure-config

    // ── Anthropic config ──
    html += `<div id="provider-anthropic-config" style="display:${isAnthropic ? 'block' : 'none'}">`;

    // Endpoint URL
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Messages Endpoint</label>';
    html += `<input id="anthropic-url" type="text" value="${escapeHtml(provAnthropic.base_url || '')}" placeholder="https://api.anthropic.com/v1/messages" style="${inputStyle}">`;
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">Direct: api.anthropic.com/v1/messages. Azure Claude: use the full endpoint from Azure AI Foundry.</div>';
    html += '</div>';

    // API Key
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">API Key</label>';
    html += `<input id="anthropic-key" type="password" value="" placeholder="${provAnthropic.api_key ? provAnthropic.api_key : 'x-api-key...'}" style="${inputStyle}">`;
    html += '</div>';

    // Anthropic Version
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">API Version</label>';
    html += `<input id="anthropic-version" type="text" value="${escapeHtml(provAnthropic.api_version || '2023-06-01')}" placeholder="2023-06-01" style="${inputStyle}">`;
    html += '</div>';

    // Model / Deployment Name
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Model / Deployment Name</label>';
    html += `<input id="anthropic-model" type="text" value="${escapeHtml(provAnthropic.model || '')}" placeholder="claude-sonnet-4-20250514" style="${inputStyle}">`;
    html += '</div>';

    // Max Tokens
    html += '<div style="margin-bottom:14px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Max Tokens per Response</label>';
    html += `<input id="anthropic-max-tokens" type="number" value="${provAnthropic.max_tokens || 4096}" min="256" max="128000" step="256" style="${inputStyle}">`;
    html += '</div>';

    // Buttons
    html += '<div class="connection-card-actions" style="flex-wrap:wrap">';
    html += '<button class="btn btn-ghost btn-sm" onclick="Settings.testAnthropic()">Test Connection</button>';
    html += '<button class="btn btn-primary btn-sm" onclick="Settings.saveAnthropic()">Save</button>';
    if (provAnthropic.base_url) {
      html += '<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeProvider()">Revert to Local</button>';
    }
    html += '</div>';
    html += '<div id="anthropic-test-result" style="margin-top:8px;font-size:0.8rem"></div>';
    html += '</div>'; // end provider-anthropic-config

    // ── Gemini config ──
    html += `<div id="provider-gemini-config" style="display:${isGemini ? 'block' : 'none'}">`;

    // API Key
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">API Key</label>';
    html += `<input id="gemini-key" type="password" value="" placeholder="${provGemini.api_key ? provGemini.api_key : 'AIza...'}" style="${inputStyle}">`;
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">Get a key from <a href="https://aistudio.google.com/apikey" target="_blank" style="color:var(--accent)">aistudio.google.com/apikey</a>. Embeddings stay local on Ollama.</div>';
    html += '</div>';

    // Model
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Model</label>';
    html += `<input id="gemini-model" type="text" value="${escapeHtml(provGemini.model || 'gemini-3.5-flash')}" placeholder="gemini-3.5-flash" style="${inputStyle}">`;
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">Try <code>gemini-3.5-flash</code> for agentic work (76.2% Terminal-Bench, 83.6% MCP Atlas) or <code>gemini-3.5-pro</code> for the heaviest reasoning.</div>';
    html += '</div>';

    // Endpoint (advanced, hidden default)
    html += '<div style="margin-bottom:10px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Endpoint (advanced)</label>';
    html += `<input id="gemini-url" type="text" value="${escapeHtml(provGemini.base_url || '')}" placeholder="https://generativelanguage.googleapis.com/v1beta" style="${inputStyle}">`;
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">Leave blank to use Google\'s public endpoint. Override for Vertex AI proxies.</div>';
    html += '</div>';

    // Max Tokens
    html += '<div style="margin-bottom:14px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Max Tokens per Response</label>';
    html += `<input id="gemini-max-tokens" type="number" value="${provGemini.max_tokens || 8192}" min="256" max="65536" step="256" style="${inputStyle}">`;
    html += '</div>';

    // Buttons
    html += '<div class="connection-card-actions" style="flex-wrap:wrap">';
    html += '<button class="btn btn-ghost btn-sm" onclick="Settings.testGemini()">Test Connection</button>';
    html += '<button class="btn btn-primary btn-sm" onclick="Settings.saveGemini()">Save</button>';
    if (provGemini.api_key) {
      html += '<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeProvider()">Revert to Local</button>';
    }
    html += '</div>';
    html += '<div id="gemini-test-result" style="margin-top:8px;font-size:0.8rem"></div>';
    html += '</div>'; // end provider-gemini-config

    html += '</div>'; // end settings-section

    // ── Multi-Model Router section ──
    const rCfg = routerCfg || {};
    const routerEnabled = rCfg.enabled || false;
    const routerFastModel = rCfg.fast_model || '';
    const routerFrontier = rCfg.frontier_enabled || false;
    const routerFrontierProvider = rCfg.frontier_provider || 'inherit';
    const routerAutoEscalate = rCfg.auto_escalate !== false;
    // Detect which provider sub-configs are saved so the dropdown only offers
    // ones the user has actually set up.
    const savedProviderTypes = (provider && typeof provider === 'object')
      ? Object.keys(provider).filter(k => k !== 'active' && k !== 'type' && provider[k] && typeof provider[k] === 'object')
      : [];

    html += '<div class="settings-section" style="margin-bottom:20px">';
    html += '<div class="settings-section-title">Multi-Model Router</div>';
    html += '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">Route messages to different models based on complexity. Simple queries go to a fast local model, complex tasks stay on the full agent, and frontier tasks escalate to remote APIs.</div>';

    // Enable toggle
    html += '<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">';
    html += `<label class="toggle-switch" style="flex-shrink:0">`;
    html += `<input type="checkbox" id="router-enabled" ${routerEnabled ? 'checked' : ''} onchange="Settings.saveRouter()">`;
    html += `<span class="slider"></span>`;
    html += `</label>`;
    html += `<span style="font-size:0.85rem">${routerEnabled ? 'Routing enabled' : 'Routing disabled (all messages use standard model)'}</span>`;
    html += '</div>';

    // Router config (shown when enabled)
    html += `<div id="router-config" style="display:${routerEnabled ? 'block' : 'none'}">`;

    // Fast model selector
    html += '<div style="margin-bottom:12px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Fast Tier Model</label>';
    html += `<div style="display:flex;gap:8px;align-items:center">`;
    html += `<input id="router-fast-model" type="text" value="${escapeHtml(routerFastModel)}" placeholder="e.g. pre-gemma4-fast, qwen2.5:3b" style="${inputStyle};flex:1">`;
    html += `<button class="btn btn-ghost btn-sm" onclick="Settings.loadRouterModels()" style="white-space:nowrap">Browse</button>`;
    html += `</div>`;
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">Small model for quick Q&A (default: pre-gemma4-fast). Leave empty to disable fast tier.</div>';
    html += '</div>';

    // Fast model dropdown (populated on Browse click)
    html += '<div id="router-model-list" style="display:none;margin-bottom:12px"></div>';

    // Frontier toggle
    html += '<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">';
    html += `<label class="toggle-switch" style="flex-shrink:0">`;
    html += `<input type="checkbox" id="router-frontier" ${routerFrontier ? 'checked' : ''} onchange="Settings.saveRouter()">`;
    html += `<span class="slider"></span>`;
    html += `</label>`;
    html += `<span style="font-size:0.85rem">Frontier tier (escalate complex tasks to remote provider)</span>`;
    html += '</div>';
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-bottom:12px">Requires a remote provider configured above. Use <code>@frontier</code> prefix to force.</div>';

    // Frontier provider selector — lets the user route frontier requests to a
    // specific saved provider (e.g. Gemini Flash) without flipping the default
    // active provider. 'inherit' keeps the legacy "use whatever's active" behavior.
    html += '<div style="margin-bottom:14px">';
    html += '<label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:4px">Frontier Provider</label>';
    html += `<select id="router-frontier-provider" style="${inputStyle}" onchange="Settings.saveRouter()">`;
    html += `<option value="inherit" ${routerFrontierProvider === 'inherit' ? 'selected' : ''}>Inherit active provider</option>`;
    const frontierChoices = [
      { value: 'gemini', label: 'Gemini (Google AI)' },
      { value: 'anthropic', label: 'Anthropic / Azure (Claude)' },
      { value: 'openai', label: 'OpenAI' },
      { value: 'azure', label: 'Azure (OpenAI)' },
    ];
    for (const c of frontierChoices) {
      const saved = savedProviderTypes.includes(c.value);
      const disabled = !saved ? ' disabled' : '';
      const suffix = !saved ? ' — not configured' : '';
      html += `<option value="${c.value}" ${routerFrontierProvider === c.value ? 'selected' : ''}${disabled}>${escapeHtml(c.label)}${suffix}</option>`;
    }
    html += '</select>';
    html += '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:3px">Send frontier-tier messages to this provider regardless of the active provider above. Lets you keep Ollama as the default while frontier goes to Gemini Flash.</div>';
    html += '</div>';

    // Auto-escalate toggle
    html += '<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">';
    html += `<label class="toggle-switch" style="flex-shrink:0">`;
    html += `<input type="checkbox" id="router-auto-escalate" ${routerAutoEscalate ? 'checked' : ''} onchange="Settings.saveRouter()">`;
    html += `<span class="slider"></span>`;
    html += `</label>`;
    html += `<span style="font-size:0.85rem">Auto-escalate from fast to standard when tools are needed</span>`;
    html += '</div>';

    // Tier override hints
    html += '<div style="font-size:0.78rem;color:var(--text-muted);padding:10px;border-radius:6px;background:var(--bg-secondary)">';
    html += '<strong>Tip:</strong> Prefix messages with <code>@fast</code>, <code>@standard</code>, or <code>@frontier</code> to override routing.';
    html += '</div>';

    html += '</div>'; // end router-config
    html += '<div id="router-status" style="margin-top:8px;font-size:0.8rem"></div>';
    html += '</div>'; // end settings-section

    html += '<div class="settings-section">';
    html += '<div class="settings-section-title">Connections</div>';

    for (const conn of connections) {
      const icon = SERVICE_ICONS[conn.service] || '';
      const statusClass = conn.active ? 'connected' : '';
      const statusText = conn.active ? 'Connected' : 'Not configured';

      html += `<div class="connection-card ${conn.active ? 'active' : ''}" data-service="${conn.service}">`;
      html += `<div class="connection-card-header">`;
      html += `<div class="connection-card-icon">${icon}</div>`;
      html += `<div class="connection-card-info">`;
      html += `<div class="connection-card-name">${escapeHtml(conn.label)}</div>`;
      html += `<div class="connection-card-status ${statusClass}">`;
      if (conn.active && conn.masked) {
        html += `${statusText} &middot; ${escapeHtml(conn.masked)}`;
      } else if (conn.active && conn.type === 'oauth') {
        html += `${statusText}`;
      } else {
        html += statusText;
      }
      html += `</div></div></div>`;

      // Action area
      html += `<div class="connection-card-actions" id="conn-actions-${conn.service}">`;
      if (conn.type === 'api_key') {
        if (conn.active) {
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.editKey('${conn.service}')">Edit</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.addKey('${conn.service}')">Add API Key</button>`;
        }
      } else if (conn.type === 'oauth' && conn.service === 'google') {
        if (conn.active) {
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.reconnectGoogle()">Reconnect</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else if (conn.hasCredentials) {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.authorizeGoogle()">Authorize</button>`;
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.editGoogleCreds()">Edit Credentials</button>`;
        } else {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.setupGoogle()">Setup</button>`;
        }
      } else if (conn.type === 'oauth' && conn.service === 'microsoft') {
        if (conn.active) {
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.reconnectMicrosoft()">Reconnect</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else if (conn.hasCredentials) {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.authorizeMicrosoft()">Authorize</button>`;
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.editMicrosoftCreds()">Edit Credentials</button>`;
        } else {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.setupMicrosoft()">Setup</button>`;
        }
      } else if (conn.type === 'jira') {
        if (conn.active) {
          html += `<div style="font-size:0.8rem;margin-bottom:6px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escapeHtml(conn.url)}</div>`;
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.setupJira()">Edit</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.setupJira()">Setup</button>`;
        }
      } else if (conn.type === 'confluence') {
        if (conn.active) {
          html += `<div style="font-size:0.8rem;margin-bottom:6px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escapeHtml(conn.url)}</div>`;
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.setupConfluence()">Edit</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.setupConfluence()">Setup</button>`;
        }
      } else if (conn.type === 'servicenow') {
        if (conn.active) {
          const authLabel = conn.authType === 'oauth' ? 'OAuth 2.0' : 'Basic auth';
          html += `<div style="font-size:0.8rem;margin-bottom:6px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escapeHtml(conn.url)} &middot; ${authLabel}</div>`;
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.setupServiceNow()">Edit</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else if (conn.authType === 'oauth' && conn.hasCredentials) {
          html += `<div style="font-size:0.8rem;margin-bottom:6px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escapeHtml(conn.url)} &middot; OAuth (not authorized)</div>`;
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.authorizeServiceNow()">Authorize</button>`;
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.setupServiceNow()">Edit Credentials</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.setupServiceNow()">Setup</button>`;
        }
      } else if (conn.type === 'slack') {
        if (conn.active) {
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.setupSlack()">Edit</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.setupSlack()">Setup</button>`;
        }
      } else if (conn.type === 'zoom') {
        if (conn.active) {
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.setupZoom()">Edit</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.setupZoom()">Setup</button>`;
        }
      } else if (conn.type === 'dynamics365') {
        if (conn.active) {
          const mode = conn.authMode === 'delegated' ? 'Delegated (user)' : 'Client credentials (app)';
          html += `<div style="font-size:0.8rem;margin-bottom:6px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escapeHtml(conn.url)} &middot; ${mode}</div>`;
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.reconnectD365()">Reconnect</button>`;
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.setupD365()">Edit Credentials</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else if (conn.hasCredentials) {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.authorizeD365()">Authorize</button>`;
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.setupD365()">Edit Credentials</button>`;
        } else {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.setupD365()">Setup</button>`;
        }
      } else if (conn.type === 'telegram') {
        if (conn.active) {
          const chatStatus = conn.chatId
            ? `<span style="color:var(--success)">Chat ID: ${escapeHtml(conn.chatId)}</span>`
            : `<span style="color:var(--warning)">No chat ID — send a message to the bot or set it below</span>`;
          html += `<div style="font-size:0.8rem;margin-bottom:6px">${chatStatus}</div>`;
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.setupTelegram()">Configure</button>`;
          html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.removeConn('${conn.service}')">Remove</button>`;
        } else {
          html += `<button class="btn btn-primary btn-sm" onclick="Settings.setupTelegram()">Setup</button>`;
        }
      }
      html += `</div></div>`;
    }

    html += '</div>';

    // ── MCP Servers section ──
    html += '<div class="settings-section">';
    html += '<div class="settings-section-title">MCP Servers</div>';

    const mcpNames = Object.keys(mcpStatus || {});
    if (mcpNames.length === 0) {
      html += '<p style="font-size:0.8rem;color:var(--text-muted);margin:0 0 8px">No MCP servers configured. Add a server to extend PRE with external tools.</p>';
    }

    for (const name of mcpNames) {
      const srv = mcpStatus[name];
      const active = srv.connected && !srv.disabled;
      const statusClass = active ? 'connected' : '';
      let statusText = srv.disabled ? 'Disabled' : (srv.connected ? `Connected (${srv.tools} tool${srv.tools !== 1 ? 's' : ''})` : 'Disconnected');
      const typeLabel = srv.type === 'http' ? srv.url : srv.command;

      html += `<div class="connection-card ${active ? 'active' : ''}" data-mcp="${escapeHtml(name)}">`;
      html += `<div class="connection-card-header">`;
      html += `<div class="connection-card-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg></div>`;
      html += `<div class="connection-card-info">`;
      html += `<div class="connection-card-name">${escapeHtml(name)}</div>`;
      html += `<div class="connection-card-status ${statusClass}">`;
      html += statusText;
      if (typeLabel) html += ` &middot; <span style="opacity:0.7">${escapeHtml(typeLabel)}</span>`;
      html += `</div></div></div>`;

      html += `<div class="connection-card-actions" id="mcp-actions-${CSS.escape(name)}">`;
      if (srv.connected) {
        if (srv.tools > 0) {
          html += `<button class="btn btn-ghost btn-sm" onclick="Settings.mcpShowTools('${escapeHtml(name)}')">Tools</button>`;
        }
        html += `<button class="btn btn-ghost btn-sm" onclick="Settings.mcpDisconnect('${escapeHtml(name)}')">Disconnect</button>`;
      } else if (!srv.disabled) {
        html += `<button class="btn btn-primary btn-sm" onclick="Settings.mcpConnect('${escapeHtml(name)}')">Connect</button>`;
      }
      html += `<button class="btn btn-ghost btn-sm" style="color:var(--danger)" onclick="Settings.mcpRemove('${escapeHtml(name)}')">Remove</button>`;
      html += `</div></div>`;
    }

    // Add server button
    html += `<div id="mcp-add-area">`;
    html += `<button class="btn btn-primary btn-sm" onclick="Settings.mcpShowAdd()">Add MCP Server</button>`;
    html += `</div>`;
    html += '</div>';

    // ── System section ──
    if (autostart) {
      html += '<div class="settings-section">';
      html += '<div class="settings-section-title">System</div>';

      const autostartOn = autostart.installed;
      html += `<div class="setting-toggle ${autostartOn ? 'active' : ''}">`;
      html += `<div class="setting-toggle-info">`;
      html += `<div class="setting-toggle-name">Start at login</div>`;
      html += `<div class="setting-toggle-desc">`;
      html += autostartOn
        ? 'PRE server starts automatically when you log in'
        : 'Launch PRE manually with <code>pre-launch</code>';
      html += `</div></div>`;
      html += `<label class="toggle-switch">`;
      html += `<input type="checkbox" ${autostartOn ? 'checked' : ''} onchange="Settings.toggleAutostart(this.checked)">`;
      html += `<span class="slider"></span>`;
      html += `</label>`;
      html += `</div>`;

      html += '</div>';
    }

    container.innerHTML = html;
  }

  // ── Button binding ──
  const btn = document.getElementById('settings-btn');
  if (btn) btn.addEventListener('click', openSettingsPanel);

  // ── Global Settings object for onclick handlers ──
  window.Settings = {
    async toggleAutostart(enabled) {
      try {
        await fetch('/api/system/autostart', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ enabled }),
        });
      } catch { /* ignore */ }
      this.refresh();
    },

    async addKey(service) {
      const actionsEl = document.getElementById(`conn-actions-${service}`);
      if (!actionsEl) return;
      actionsEl.innerHTML = `
        <div class="connection-input-group">
          <div class="connection-input-row">
            <input type="password" id="key-input-${service}" placeholder="Paste API key..." autocomplete="off">
            <button class="btn btn-primary btn-sm" onclick="Settings.saveKey('${service}')">Save</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
        </div>
      `;
      document.getElementById(`key-input-${service}`).focus();
    },

    editKey(service) {
      this.addKey(service);
    },

    async saveKey(service) {
      const input = document.getElementById(`key-input-${service}`);
      const key = input?.value.trim();
      if (!key) return;
      try {
        await fetch(`/api/connections/${service}/key`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ key }),
        });
        this.refresh();
      } catch {}
    },

    async removeConn(service) {
      if (!confirm(`Remove ${service} connection?`)) return;
      await fetch(`/api/connections/${service}`, { method: 'DELETE' });
      this.refresh();
    },

    setupGoogle() {
      const actionsEl = document.getElementById('conn-actions-google');
      if (!actionsEl) return;
      actionsEl.innerHTML = `
        <div class="connection-input-group">
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            Create OAuth credentials at
            <a href="https://console.cloud.google.com/apis/credentials" target="_blank" style="color:var(--primary)">Google Cloud Console</a>.
            Enable Gmail, Drive, and Docs APIs. Set redirect URI to
            <code style="font-size:0.75rem">http://localhost:7749/oauth/callback</code>
          </p>
          <input type="text" id="google-client-id" placeholder="Client ID" autocomplete="off">
          <input type="password" id="google-client-secret" placeholder="Client Secret" autocomplete="off">
          <div class="connection-input-row">
            <button class="btn btn-primary btn-sm" onclick="Settings.saveGoogleCreds()">Save & Authorize</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
        </div>
      `;
      document.getElementById('google-client-id').focus();
    },

    editGoogleCreds() {
      this.setupGoogle();
    },

    async saveGoogleCreds() {
      const clientId = document.getElementById('google-client-id')?.value.trim();
      const clientSecret = document.getElementById('google-client-secret')?.value.trim();
      if (!clientId || !clientSecret) return;
      try {
        await fetch('/api/connections/google/credentials', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ clientId, clientSecret }),
        });
        this.authorizeGoogle();
      } catch {}
    },

    async authorizeGoogle() {
      try {
        const res = await fetch('/api/connections/google/auth-url');
        const data = await res.json();
        if (data.url) {
          window.open(data.url, '_blank');
          // Show a waiting message
          const actionsEl = document.getElementById('conn-actions-google');
          if (actionsEl) {
            actionsEl.innerHTML = `
              <div style="font-size:0.8rem;color:var(--text-muted);display:flex;align-items:center;gap:8px">
                <div class="spinner" style="width:14px;height:14px;border-width:2px"></div>
                Waiting for authorization... Complete sign-in in the opened tab.
              </div>
              <button class="btn btn-ghost btn-sm" style="margin-top:8px" onclick="Settings.refresh()">Done</button>
            `;
          }
        }
      } catch {}
    },

    reconnectGoogle() {
      this.authorizeGoogle();
    },

    setupMicrosoft() {
      const actionsEl = document.getElementById('conn-actions-microsoft');
      if (!actionsEl) return;
      actionsEl.innerHTML = `
        <div class="connection-input-group">
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            Register an app at
            <a href="https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade" target="_blank" style="color:var(--primary)">Azure App Registrations</a>.
            Add API permissions: <code style="font-size:0.75rem">Sites.Read.All</code>, <code style="font-size:0.75rem">Files.ReadWrite.All</code>, <code style="font-size:0.75rem">User.Read</code>.
            Set redirect URI to
            <code style="font-size:0.75rem">http://localhost:7749/oauth/microsoft/callback</code> (Web platform).
          </p>
          <input type="text" id="ms-tenant-id" placeholder="Tenant ID (Directory ID)" autocomplete="off">
          <input type="text" id="ms-client-id" placeholder="Application (Client) ID" autocomplete="off">
          <input type="password" id="ms-client-secret" placeholder="Client Secret (Value)" autocomplete="off">
          <div class="connection-input-row">
            <button class="btn btn-primary btn-sm" onclick="Settings.saveMicrosoftCreds()">Save & Authorize</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
        </div>
      `;
      document.getElementById('ms-tenant-id').focus();
    },

    editMicrosoftCreds() {
      this.setupMicrosoft();
    },

    async saveMicrosoftCreds() {
      const tenantId = document.getElementById('ms-tenant-id')?.value.trim();
      const clientId = document.getElementById('ms-client-id')?.value.trim();
      const clientSecret = document.getElementById('ms-client-secret')?.value.trim();
      if (!tenantId || !clientId || !clientSecret) return;
      try {
        await fetch('/api/connections/microsoft/credentials', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ tenantId, clientId, clientSecret }),
        });
        this.authorizeMicrosoft();
      } catch {}
    },

    async authorizeMicrosoft() {
      try {
        const res = await fetch('/api/connections/microsoft/auth-url');
        const data = await res.json();
        if (data.url) {
          window.open(data.url, '_blank');
          const actionsEl = document.getElementById('conn-actions-microsoft');
          if (actionsEl) {
            actionsEl.innerHTML = `
              <div style="font-size:0.8rem;color:var(--text-muted);display:flex;align-items:center;gap:8px">
                <div class="spinner" style="width:14px;height:14px;border-width:2px"></div>
                Waiting for Microsoft authorization... Complete sign-in in the opened tab.
              </div>
              <button class="btn btn-ghost btn-sm" style="margin-top:8px" onclick="Settings.refresh()">Done</button>
            `;
          }
        }
      } catch {}
    },

    reconnectMicrosoft() {
      this.authorizeMicrosoft();
    },

    setupJira() {
      const actionsEl = document.getElementById('conn-actions-jira');
      if (!actionsEl) return;
      actionsEl.innerHTML = `
        <div class="connection-input-group">
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            Enter your Jira Server URL and a
            <a href="https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html" target="_blank" style="color:var(--primary)">Personal Access Token</a>.
          </p>
          <input type="text" id="jira-url-input" placeholder="Jira URL (e.g. https://jira.company.com)" autocomplete="off">
          <input type="password" id="jira-token-input" placeholder="Personal Access Token" autocomplete="off">
          <div class="connection-input-row">
            <button class="btn btn-primary btn-sm" onclick="Settings.saveJira()">Save</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
        </div>
      `;
      document.getElementById('jira-url-input').focus();
    },

    async saveJira() {
      const url = document.getElementById('jira-url-input')?.value.trim();
      const token = document.getElementById('jira-token-input')?.value.trim();
      if (!url || !token) return;
      try {
        await fetch('/api/connections/jira/config', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ url, token }),
        });
        this.refresh();
      } catch {}
    },

    setupConfluence() {
      const actionsEl = document.getElementById('conn-actions-confluence');
      if (!actionsEl) return;
      actionsEl.innerHTML = `
        <div class="connection-input-group">
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            Enter your Confluence Server URL and a
            <a href="https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html" target="_blank" style="color:var(--primary)">Personal Access Token</a>.
          </p>
          <input type="text" id="confluence-url-input" placeholder="Confluence URL (e.g. https://confluence.company.com)" autocomplete="off">
          <input type="password" id="confluence-token-input" placeholder="Personal Access Token" autocomplete="off">
          <div class="connection-input-row">
            <button class="btn btn-primary btn-sm" onclick="Settings.saveConfluence()">Save</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
        </div>
      `;
      document.getElementById('confluence-url-input').focus();
    },

    async saveConfluence() {
      const url = document.getElementById('confluence-url-input')?.value.trim();
      const token = document.getElementById('confluence-token-input')?.value.trim();
      if (!url || !token) return;
      try {
        await fetch('/api/connections/confluence/config', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ url, token }),
        });
        this.refresh();
      } catch {}
    },

    setupServiceNow() {
      const actionsEl = document.getElementById('conn-actions-servicenow');
      if (!actionsEl) return;
      actionsEl.innerHTML = `
        <div class="connection-input-group">
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            Enter your ServiceNow instance URL (e.g. <code>https://dev12345.service-now.com</code>) and pick an auth method.
            OAuth 2.0 is the right choice for SSO-locked instances; basic auth needs a dedicated integration service account.
          </p>
          <input type="text" id="servicenow-url-input" placeholder="Instance URL" autocomplete="off">
          <div style="display:flex;gap:14px;margin:6px 0">
            <label style="display:flex;align-items:center;gap:6px;font-size:0.85rem;cursor:pointer">
              <input type="radio" name="servicenow-auth" value="basic" checked onchange="Settings.servicenowToggleAuth('basic')"> Basic auth
            </label>
            <label style="display:flex;align-items:center;gap:6px;font-size:0.85rem;cursor:pointer">
              <input type="radio" name="servicenow-auth" value="oauth" onchange="Settings.servicenowToggleAuth('oauth')"> OAuth 2.0
            </label>
          </div>
          <div id="servicenow-basic-fields">
            <input type="text" id="servicenow-username-input" placeholder="Username (e.g. integration.user)" autocomplete="off">
            <input type="password" id="servicenow-password-input" placeholder="Password" autocomplete="off">
          </div>
          <div id="servicenow-oauth-fields" style="display:none">
            <p style="font-size:0.75rem;color:var(--text-muted);margin:0 0 4px">
              In ServiceNow, navigate to <strong>System OAuth → Application Registry</strong> and create an "OAuth API endpoint for external clients". Set the redirect URL to
              <code>http://localhost:7749/oauth/servicenow/callback</code>.
            </p>
            <input type="text" id="servicenow-client-id-input" placeholder="Client ID" autocomplete="off">
            <input type="password" id="servicenow-client-secret-input" placeholder="Client Secret" autocomplete="off">
          </div>
          <div class="connection-input-row">
            <button class="btn btn-primary btn-sm" onclick="Settings.saveServiceNow()">Save</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
        </div>
      `;
      document.getElementById('servicenow-url-input').focus();
    },

    servicenowToggleAuth(mode) {
      const basic = document.getElementById('servicenow-basic-fields');
      const oauth = document.getElementById('servicenow-oauth-fields');
      if (!basic || !oauth) return;
      basic.style.display = mode === 'basic' ? 'block' : 'none';
      oauth.style.display = mode === 'oauth' ? 'block' : 'none';
    },

    async saveServiceNow() {
      const url = document.getElementById('servicenow-url-input')?.value.trim();
      if (!url) return;
      const mode = document.querySelector('input[name="servicenow-auth"]:checked')?.value || 'basic';
      try {
        if (mode === 'basic') {
          const username = document.getElementById('servicenow-username-input')?.value.trim();
          const password = document.getElementById('servicenow-password-input')?.value.trim();
          if (!username || !password) return;
          await fetch('/api/connections/servicenow/basic', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ url, username, password }),
          });
          this.refresh();
        } else {
          const clientId = document.getElementById('servicenow-client-id-input')?.value.trim();
          const clientSecret = document.getElementById('servicenow-client-secret-input')?.value.trim();
          if (!clientId || !clientSecret) return;
          await fetch('/api/connections/servicenow/oauth', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ url, clientId, clientSecret }),
          });
          // Kick off the authorize flow immediately so the user gets a refresh token.
          await this.authorizeServiceNow();
        }
      } catch {}
    },

    async authorizeServiceNow() {
      try {
        const res = await fetch('/api/connections/servicenow/auth-url');
        if (!res.ok) { this.refresh(); return; }
        const { url } = await res.json();
        if (url) window.open(url, '_blank');
        const actionsEl = document.getElementById('conn-actions-servicenow');
        if (actionsEl) {
          actionsEl.innerHTML = `
            <div style="font-size:0.8rem;color:var(--text-muted);display:flex;align-items:center;gap:8px">
              <div class="spinner" style="width:14px;height:14px;border-width:2px"></div>
              Waiting for ServiceNow authorization... Complete sign-in in the opened tab.
            </div>
            <button class="btn btn-ghost btn-sm" style="margin-top:8px" onclick="Settings.refresh()">Done</button>
          `;
        }
      } catch {}
    },

    setupSlack() {
      const actionsEl = document.getElementById('conn-actions-slack');
      if (!actionsEl) return;
      actionsEl.innerHTML = `
        <div class="connection-input-group">
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            1. Create a Slack App at <a href="https://api.slack.com/apps" target="_blank" style="color:var(--primary)">api.slack.com/apps</a>.<br>
            2. Under <strong>OAuth & Permissions</strong>, add bot scopes: <code style="font-size:0.75rem">channels:read</code>, <code style="font-size:0.75rem">channels:history</code>, <code style="font-size:0.75rem">chat:write</code>, <code style="font-size:0.75rem">reactions:write</code>, <code style="font-size:0.75rem">users:read</code>, <code style="font-size:0.75rem">search:read</code>.<br>
            3. Install to your workspace and copy the <strong>Bot User OAuth Token</strong>.
          </p>
          <div class="connection-input-row">
            <input type="password" id="slack-token-input" placeholder="Bot User OAuth Token (xoxb-...)" autocomplete="off">
          </div>
          <div class="connection-input-row">
            <button class="btn btn-primary btn-sm" onclick="Settings.saveSlack()">Save</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
        </div>
      `;
      document.getElementById('slack-token-input').focus();
    },

    async saveSlack() {
      const token = document.getElementById('slack-token-input')?.value.trim();
      if (!token) return;
      try {
        await fetch('/api/connections/slack/key', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ key: token }),
        });
        this.refresh();
      } catch {}
    },

    setupZoom() {
      const actionsEl = document.getElementById('conn-actions-zoom');
      if (!actionsEl) return;
      actionsEl.innerHTML = `
        <div class="connection-input-group">
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            1. Create a Server-to-Server OAuth app at <a href="https://marketplace.zoom.us/develop/create" target="_blank" style="color:var(--primary)">Zoom Marketplace</a>.<br>
            2. Add scopes: <code style="font-size:0.75rem">meeting:read</code>, <code style="font-size:0.75rem">meeting:write</code>, <code style="font-size:0.75rem">user:read</code>, <code style="font-size:0.75rem">recording:read</code>.<br>
            3. Copy the Account ID, Client ID, and Client Secret.
          </p>
          <input type="text" id="zoom-account-id" placeholder="Account ID" autocomplete="off">
          <input type="text" id="zoom-client-id" placeholder="Client ID" autocomplete="off">
          <input type="password" id="zoom-client-secret" placeholder="Client Secret" autocomplete="off">
          <div class="connection-input-row">
            <button class="btn btn-primary btn-sm" onclick="Settings.saveZoom()">Save</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
        </div>
      `;
      document.getElementById('zoom-account-id').focus();
    },

    async saveZoom() {
      const accountId = document.getElementById('zoom-account-id')?.value.trim();
      const clientId = document.getElementById('zoom-client-id')?.value.trim();
      const clientSecret = document.getElementById('zoom-client-secret')?.value.trim();
      if (!accountId || !clientId || !clientSecret) return;
      try {
        await fetch('/api/connections/zoom/config', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ accountId, clientId, clientSecret }),
        });
        this.refresh();
      } catch {}
    },

    setupD365() {
      const actionsEl = document.getElementById('conn-actions-dynamics365');
      if (!actionsEl) return;
      actionsEl.innerHTML = `
        <div class="connection-input-group">
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            1. Register an app in <a href="https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade" target="_blank" style="color:var(--primary)">Azure AD (Entra ID)</a>.<br>
            2. Add API permission: <strong>Dynamics CRM &gt; user_impersonation</strong>.<br>
            3. Create a client secret under Certificates & Secrets.<br>
            4. Set redirect URI to <code style="font-size:0.75rem">http://localhost:7749/oauth/dynamics365/callback</code> (Web platform).
          </p>
          <input type="text" id="d365-url-input" placeholder="Environment URL (e.g. https://org.crm.dynamics.com)" autocomplete="off">
          <input type="text" id="d365-tenant-input" placeholder="Azure Tenant ID" autocomplete="off">
          <input type="text" id="d365-client-id-input" placeholder="Client ID (Application ID)" autocomplete="off">
          <input type="password" id="d365-client-secret-input" placeholder="Client Secret" autocomplete="off">
          <div class="connection-input-row">
            <button class="btn btn-primary btn-sm" onclick="Settings.saveD365Creds()">Save & Authorize</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.saveD365Direct()">Save (Client Credentials Only)</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
        </div>
      `;
      document.getElementById('d365-url-input').focus();
    },

    async saveD365Creds() {
      const url = document.getElementById('d365-url-input')?.value.trim();
      const tenantId = document.getElementById('d365-tenant-input')?.value.trim();
      const clientId = document.getElementById('d365-client-id-input')?.value.trim();
      const clientSecret = document.getElementById('d365-client-secret-input')?.value.trim();
      if (!url || !tenantId || !clientId || !clientSecret) return;
      try {
        await fetch('/api/connections/dynamics365/credentials', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ url, tenantId, clientId, clientSecret }),
        });
        this.authorizeD365();
      } catch {}
    },

    async saveD365Direct() {
      const url = document.getElementById('d365-url-input')?.value.trim();
      const tenantId = document.getElementById('d365-tenant-input')?.value.trim();
      const clientId = document.getElementById('d365-client-id-input')?.value.trim();
      const clientSecret = document.getElementById('d365-client-secret-input')?.value.trim();
      if (!url || !tenantId || !clientId || !clientSecret) return;
      try {
        await fetch('/api/connections/dynamics365/config', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ url, tenantId, clientId, clientSecret }),
        });
        this.refresh();
      } catch {}
    },

    async authorizeD365() {
      try {
        const res = await fetch('/api/connections/dynamics365/auth-url');
        const data = await res.json();
        if (data.url) {
          window.open(data.url, '_blank');
          const actionsEl = document.getElementById('conn-actions-dynamics365');
          if (actionsEl) {
            actionsEl.innerHTML = `
              <div style="font-size:0.8rem;color:var(--text-muted);display:flex;align-items:center;gap:8px">
                <div class="spinner" style="width:14px;height:14px;border-width:2px"></div>
                Waiting for D365 authorization... Complete sign-in in the opened tab.
              </div>
              <button class="btn btn-ghost btn-sm" style="margin-top:8px" onclick="Settings.refresh()">Done</button>
            `;
          }
        }
      } catch {}
    },

    reconnectD365() {
      this.authorizeD365();
    },

    setupTelegram() {
      const actionsEl = document.getElementById('conn-actions-telegram');
      if (!actionsEl) return;
      actionsEl.innerHTML = `
        <div class="connection-input-group">
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            1. Create a bot with <a href="https://t.me/BotFather" target="_blank" style="color:var(--primary)">@BotFather</a> on Telegram.
            Copy the bot token below.
          </p>
          <div class="connection-input-row">
            <input type="password" id="tg-token-input" placeholder="Bot token (e.g. 123456:ABC-DEF...)" autocomplete="off">
            <button class="btn btn-ghost btn-sm" onclick="Settings.testTelegram()">Test</button>
          </div>
          <div id="tg-test-result" style="font-size:0.8rem;min-height:1.2em"></div>
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            2. Your Chat ID — send any message to the bot, then click "Detect", or enter it manually.
            Find your ID via <a href="https://t.me/userinfobot" target="_blank" style="color:var(--primary)">@userinfobot</a>.
          </p>
          <div class="connection-input-row">
            <input type="text" id="tg-chatid-input" placeholder="Chat ID (e.g. 6902857843)" autocomplete="off">
            <button class="btn btn-ghost btn-sm" onclick="Settings.detectTelegramChat()">Detect</button>
          </div>
          <div id="tg-chat-result" style="font-size:0.8rem;min-height:1.2em"></div>
          <div class="connection-input-row">
            <button class="btn btn-primary btn-sm" onclick="Settings.saveTelegram()">Save</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
        </div>
      `;
      document.getElementById('tg-token-input').focus();
    },

    async testTelegram() {
      const token = document.getElementById('tg-token-input')?.value.trim();
      const resultEl = document.getElementById('tg-test-result');
      if (!token) { resultEl.innerHTML = '<span style="color:var(--danger)">Enter a token first</span>'; return; }
      resultEl.innerHTML = '<span style="color:var(--text-muted)">Testing...</span>';
      try {
        const res = await fetch('/api/connections/telegram/test', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ token }),
        });
        const data = await res.json();
        if (data.success) {
          resultEl.innerHTML = `<span style="color:var(--success)">Bot: @${escapeHtml(data.bot.username)} (${escapeHtml(data.bot.first_name)})</span>`;
        } else {
          resultEl.innerHTML = `<span style="color:var(--danger)">${escapeHtml(data.error)}</span>`;
        }
      } catch {
        resultEl.innerHTML = '<span style="color:var(--danger)">Test failed</span>';
      }
    },

    async detectTelegramChat() {
      const token = document.getElementById('tg-token-input')?.value.trim();
      const resultEl = document.getElementById('tg-chat-result');
      const chatInput = document.getElementById('tg-chatid-input');
      if (!token) { resultEl.innerHTML = '<span style="color:var(--danger)">Enter bot token first</span>'; return; }
      resultEl.innerHTML = '<span style="color:var(--text-muted)">Checking for messages...</span>';
      try {
        // Use the bot token to call getUpdates directly
        const res = await fetch(`https://api.telegram.org/bot${token}/getUpdates?limit=5`);
        const data = await res.json();
        if (data.ok && data.result && data.result.length > 0) {
          const lastMsg = data.result.reverse().find(u => u.message);
          if (lastMsg) {
            const chatId = lastMsg.message.chat.id;
            const from = lastMsg.message.from?.first_name || 'Unknown';
            chatInput.value = chatId;
            resultEl.innerHTML = `<span style="color:var(--success)">Found: ${escapeHtml(from)} (${chatId})</span>`;
          } else {
            resultEl.innerHTML = '<span style="color:var(--warning)">No messages found. Send a message to the bot first.</span>';
          }
        } else {
          resultEl.innerHTML = '<span style="color:var(--warning)">No messages yet. Send a message to the bot first.</span>';
        }
      } catch {
        resultEl.innerHTML = '<span style="color:var(--danger)">Detection failed</span>';
      }
    },

    async saveTelegram() {
      const token = document.getElementById('tg-token-input')?.value.trim();
      const chatId = document.getElementById('tg-chatid-input')?.value.trim();
      if (!token) return;
      try {
        // Save the bot token
        await fetch('/api/connections/telegram/key', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ key: token }),
        });
        // Save chat_id if provided
        if (chatId) {
          await fetch('/api/connections/telegram/chat-id', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ chatId }),
          });
        }
        this.refresh();
      } catch {}
    },

    // ── MCP Server management ──

    mcpShowAdd() {
      const area = document.getElementById('mcp-add-area');
      if (!area) return;
      area.innerHTML = `
        <div class="connection-input-group">
          <p style="font-size:0.8rem;color:var(--text-muted);margin:0">
            Add a <strong>stdio</strong> server (local command) or an <strong>HTTP</strong> server (remote URL).
          </p>
          <div class="connection-input-row" style="margin-bottom:4px">
            <label style="font-size:0.8rem;display:flex;align-items:center;gap:4px;cursor:pointer">
              <input type="radio" name="mcp-type" value="stdio" checked onchange="Settings.mcpToggleType()"> Command (stdio)
            </label>
            <label style="font-size:0.8rem;display:flex;align-items:center;gap:4px;cursor:pointer">
              <input type="radio" name="mcp-type" value="http" onchange="Settings.mcpToggleType()"> HTTP URL
            </label>
          </div>
          <input type="text" id="mcp-add-name" placeholder="Server name (e.g. my-server)" autocomplete="off">
          <div id="mcp-type-fields">
            <input type="text" id="mcp-add-command" placeholder="Command (e.g. npx -y @modelcontextprotocol/server-filesystem)" autocomplete="off">
            <input type="text" id="mcp-add-args" placeholder="Arguments, comma-separated (optional)" autocomplete="off">
            <input type="text" id="mcp-add-env" placeholder="Environment: KEY=VALUE, KEY2=VALUE2 (optional)" autocomplete="off">
          </div>
          <div class="connection-input-row">
            <button class="btn btn-primary btn-sm" onclick="Settings.mcpSaveAdd()">Add & Connect</button>
            <button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Cancel</button>
          </div>
          <div id="mcp-add-status" style="font-size:0.8rem;min-height:1.2em"></div>
        </div>
      `;
      document.getElementById('mcp-add-name').focus();
    },

    mcpToggleType() {
      const isHttp = document.querySelector('input[name="mcp-type"]:checked')?.value === 'http';
      const fields = document.getElementById('mcp-type-fields');
      if (!fields) return;
      if (isHttp) {
        fields.innerHTML = `<input type="text" id="mcp-add-url" placeholder="Server URL (e.g. https://api.example.com/mcp)" autocomplete="off">`;
      } else {
        fields.innerHTML = `
          <input type="text" id="mcp-add-command" placeholder="Command (e.g. npx -y @modelcontextprotocol/server-filesystem)" autocomplete="off">
          <input type="text" id="mcp-add-args" placeholder="Arguments, comma-separated (optional)" autocomplete="off">
          <input type="text" id="mcp-add-env" placeholder="Environment: KEY=VALUE, KEY2=VALUE2 (optional)" autocomplete="off">
        `;
      }
    },

    async mcpSaveAdd() {
      const name = document.getElementById('mcp-add-name')?.value.trim();
      const statusEl = document.getElementById('mcp-add-status');
      if (!name) { statusEl.innerHTML = '<span style="color:var(--danger)">Name is required</span>'; return; }

      const isHttp = document.querySelector('input[name="mcp-type"]:checked')?.value === 'http';
      let body;

      if (isHttp) {
        const url = document.getElementById('mcp-add-url')?.value.trim();
        if (!url) { statusEl.innerHTML = '<span style="color:var(--danger)">URL is required</span>'; return; }
        body = { name, url };
      } else {
        const command = document.getElementById('mcp-add-command')?.value.trim();
        if (!command) { statusEl.innerHTML = '<span style="color:var(--danger)">Command is required</span>'; return; }
        const argsStr = document.getElementById('mcp-add-args')?.value.trim();
        const envStr = document.getElementById('mcp-add-env')?.value.trim();
        const args = argsStr ? argsStr.split(',').map(s => s.trim()).filter(Boolean) : [];
        const env = {};
        if (envStr) {
          for (const pair of envStr.split(',')) {
            const [k, ...v] = pair.split('=');
            if (k?.trim()) env[k.trim()] = v.join('=').trim();
          }
        }
        body = { name, command, args, env };
      }

      statusEl.innerHTML = '<span style="color:var(--text-muted)">Connecting...</span>';

      try {
        const res = await fetch('/api/mcp/add', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(body),
        });
        const data = await res.json();
        if (data.error && !data.added) {
          statusEl.innerHTML = `<span style="color:var(--danger)">${escapeHtml(data.error)}</span>`;
        } else if (data.connected) {
          statusEl.innerHTML = `<span style="color:var(--success)">Connected (${data.tools} tools)</span>`;
          setTimeout(() => this.refresh(), 1000);
        } else {
          statusEl.innerHTML = `<span style="color:var(--warning)">Added but failed to connect: ${escapeHtml(data.error || 'unknown error')}</span>`;
          setTimeout(() => this.refresh(), 2000);
        }
      } catch (err) {
        statusEl.innerHTML = `<span style="color:var(--danger)">Failed: ${escapeHtml(err.message)}</span>`;
      }
    },

    async mcpConnect(name) {
      const actionsEl = document.getElementById(`mcp-actions-${CSS.escape(name)}`);
      if (actionsEl) actionsEl.innerHTML = '<span style="font-size:0.8rem;color:var(--text-muted)">Connecting...</span>';
      try {
        await fetch('/api/mcp/connect', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ name }),
        });
      } catch {}
      this.refresh();
    },

    async mcpDisconnect(name) {
      try {
        await fetch('/api/mcp/disconnect', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ name }),
        });
      } catch {}
      this.refresh();
    },

    async mcpRemove(name) {
      if (!confirm(`Remove MCP server "${name}"?`)) return;
      try {
        await fetch(`/api/mcp/${encodeURIComponent(name)}`, { method: 'DELETE' });
      } catch {}
      this.refresh();
    },

    mcpShowTools(name) {
      const actionsEl = document.getElementById(`mcp-actions-${CSS.escape(name)}`);
      if (!actionsEl) return;
      // Fetch current status to get tool names
      fetch('/api/mcp').then(r => r.json()).then(status => {
        const srv = status[name];
        if (!srv || !srv.toolNames?.length) {
          actionsEl.innerHTML = '<span style="font-size:0.8rem;color:var(--text-muted)">No tools available</span>';
          return;
        }
        let html = '<div style="font-size:0.75rem;color:var(--text-muted);max-height:150px;overflow-y:auto;margin-bottom:6px">';
        for (const tool of srv.toolNames) {
          html += `<div style="padding:2px 0;font-family:monospace">${escapeHtml(tool)}</div>`;
        }
        html += '</div>';
        html += `<button class="btn btn-ghost btn-sm" onclick="Settings.refresh()">Close</button>`;
        actionsEl.innerHTML = html;
      }).catch(() => {});
    },

    // ── Provider methods ──

    // ── Router methods ──

    async saveRouter() {
      const enabled = document.getElementById('router-enabled')?.checked || false;
      const fastModel = document.getElementById('router-fast-model')?.value?.trim() || '';
      const frontier = document.getElementById('router-frontier')?.checked || false;
      const frontierProvider = document.getElementById('router-frontier-provider')?.value || 'inherit';
      const autoEscalate = document.getElementById('router-auto-escalate')?.checked ?? true;

      // Show/hide config panel
      const configEl = document.getElementById('router-config');
      if (configEl) configEl.style.display = enabled ? 'block' : 'none';

      const statusEl = document.getElementById('router-status');
      try {
        const res = await fetch('/api/router', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            enabled,
            fast_model: fastModel,
            frontier_enabled: frontier,
            frontier_provider: frontierProvider,
            auto_escalate: autoEscalate,
          }),
        });
        const data = await res.json();
        if (statusEl) {
          statusEl.innerHTML = data.success
            ? '<span style="color:var(--success)">Router settings saved</span>'
            : '<span style="color:var(--danger)">Failed to save</span>';
          setTimeout(() => { if (statusEl) statusEl.innerHTML = ''; }, 3000);
        }
      } catch (err) {
        if (statusEl) statusEl.innerHTML = `<span style="color:var(--danger)">${escapeHtml(err.message)}</span>`;
      }
    },

    async loadRouterModels() {
      const listEl = document.getElementById('router-model-list');
      if (!listEl) return;
      listEl.style.display = 'block';
      listEl.innerHTML = '<span style="color:var(--text-muted);font-size:0.8rem">Loading models...</span>';
      try {
        const res = await fetch('/api/router/models');
        const data = await res.json();
        if (data.error) {
          listEl.innerHTML = `<span style="color:var(--danger);font-size:0.8rem">Error: ${escapeHtml(data.error)}</span>`;
          return;
        }
        const models = data.recommended.length > 0 ? data.recommended : data.all;
        if (models.length === 0) {
          listEl.innerHTML = '<span style="color:var(--text-muted);font-size:0.8rem">No models found. Pull a small model: <code>ollama pull qwen2.5:3b</code></span>';
          return;
        }
        let html = '<div style="font-size:0.78rem;color:var(--text-muted);margin-bottom:6px">Click to select:</div>';
        html += '<div style="display:flex;flex-wrap:wrap;gap:6px">';
        for (const m of models) {
          html += `<button class="btn btn-ghost btn-sm" style="font-size:0.78rem" onclick="document.getElementById('router-fast-model').value='${escapeHtml(m.name)}';document.getElementById('router-model-list').style.display='none';Settings.saveRouter()">${escapeHtml(m.name)} (${m.sizeHuman})</button>`;
        }
        html += '</div>';
        if (data.recommended.length > 0 && data.all.length > data.recommended.length) {
          html += `<div style="margin-top:6px"><button class="btn btn-ghost btn-sm" style="font-size:0.72rem;color:var(--text-muted)" onclick="Settings._showAllRouterModels()">Show all models</button></div>`;
        }
        listEl.innerHTML = html;
        listEl._allModels = data.all;
      } catch (err) {
        listEl.innerHTML = `<span style="color:var(--danger);font-size:0.8rem">${escapeHtml(err.message)}</span>`;
      }
    },

    _showAllRouterModels() {
      const listEl = document.getElementById('router-model-list');
      if (!listEl || !listEl._allModels) return;
      let html = '<div style="font-size:0.78rem;color:var(--text-muted);margin-bottom:6px">All available models:</div>';
      html += '<div style="display:flex;flex-wrap:wrap;gap:6px">';
      for (const m of listEl._allModels) {
        html += `<button class="btn btn-ghost btn-sm" style="font-size:0.78rem" onclick="document.getElementById('router-fast-model').value='${escapeHtml(m.name)}';document.getElementById('router-model-list').style.display='none';Settings.saveRouter()">${escapeHtml(m.name)} (${m.sizeHuman})</button>`;
      }
      html += '</div>';
      listEl.innerHTML = html;
    },

    toggleProvider(value) {
      const openaiEl = document.getElementById('provider-remote-config');
      const azureEl = document.getElementById('provider-azure-config');
      const anthropicEl = document.getElementById('provider-anthropic-config');
      const geminiEl = document.getElementById('provider-gemini-config');
      const llamacppEl = document.getElementById('provider-llamacpp-config');
      const tinygradEl = document.getElementById('provider-tinygrad-config');
      if (openaiEl) openaiEl.style.display = value === 'openai' ? 'block' : 'none';
      if (azureEl) azureEl.style.display = value === 'azure' ? 'block' : 'none';
      if (anthropicEl) anthropicEl.style.display = value === 'anthropic' ? 'block' : 'none';
      if (geminiEl) geminiEl.style.display = value === 'gemini' ? 'block' : 'none';
      if (llamacppEl) llamacppEl.style.display = value === 'llamacpp' ? 'block' : 'none';
      if (tinygradEl) tinygradEl.style.display = value === 'tinygrad' ? 'block' : 'none';
      // Load GPU status when tinygrad panel is shown
      if (value === 'tinygrad') Settings.refreshTinygradGpuStatus();
      // Switching to Ollama reverts to local without deleting saved configs
      if (value === 'ollama') {
        (async () => {
          await fetch('/api/provider', { method: 'DELETE' });
        })();
      }
    },

    // ── llama.cpp Settings ──

    async browseLlamaCppModels() {
      const listEl = document.getElementById('llamacpp-model-list');
      if (!listEl) return;
      listEl.style.display = 'block';
      listEl.innerHTML = '<span style="color:var(--text-muted)">Scanning...</span>';
      try {
        const res = await fetch('/api/llamacpp/models');
        const data = await res.json();
        if (!data.models || data.models.length === 0) {
          listEl.innerHTML = '<span style="color:var(--text-muted)">No GGUF models found in Ollama store.</span>';
          return;
        }
        let html = '<div style="display:flex;flex-wrap:wrap;gap:6px">';
        for (const m of data.models) {
          html += `<button class="btn btn-ghost btn-sm" style="font-size:0.78rem" onclick="document.getElementById('llamacpp-model').value='${m.fullTag}';document.getElementById('llamacpp-model-list').style.display='none'">${m.fullTag} (${m.sizeGB}GB)</button>`;
        }
        html += '</div>';
        if (!data.binaryInstalled) {
          html += '<div style="margin-top:8px;font-size:0.75rem;color:var(--warning,#f59e0b)">llama-server binary not found. Install: brew install llama.cpp (macOS) or download from github.com/ggml-org/llama.cpp/releases</div>';
        }
        listEl.innerHTML = html;
      } catch (err) {
        listEl.innerHTML = `<span style="color:var(--danger)">${err.message}</span>`;
      }
    },

    async checkLlamaCppStatus() {
      const resultEl = document.getElementById('llamacpp-status-result');
      if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Checking...</span>';
      try {
        const res = await fetch('/api/llamacpp/status');
        const data = await res.json();
        if (data.running) {
          resultEl.innerHTML = `<span style="color:var(--success,#10b981)">Running (pid ${data.pid}, port ${data.port}, uptime ${data.uptime}s)</span>`;
        } else {
          resultEl.innerHTML = `<span style="color:var(--text-muted)">Not running. ${data.binaryPath ? 'Binary found at ' + data.binaryPath : 'Binary not found.'}</span>`;
        }
      } catch (err) {
        resultEl.innerHTML = `<span style="color:var(--danger)">${err.message}</span>`;
      }
    },

    async saveLlamaCpp() {
      const resultEl = document.getElementById('llamacpp-status-result');
      if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Saving...</span>';
      try {
        const config = {
          type: 'llamacpp',
          model_path: (document.getElementById('llamacpp-model') || {}).value || '',
          draft_model_path: (document.getElementById('llamacpp-draft') || {}).value || '',
          port: parseInt((document.getElementById('llamacpp-port') || {}).value, 10) || 8080,
          n_gpu_layers: parseInt((document.getElementById('llamacpp-gpu-layers') || {}).value, 10),
          flash_attn: (document.getElementById('llamacpp-flash') || {}).checked !== false,
          grammar_enabled: (document.getElementById('llamacpp-grammar') || {}).checked !== false,
        };
        if (!config.model_path) {
          if (resultEl) resultEl.innerHTML = '<span style="color:var(--danger)">Model path is required</span>';
          return;
        }
        const res = await fetch('/api/provider', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        const data = await res.json();
        if (data.success !== false) {
          if (resultEl) resultEl.innerHTML = '<span style="color:var(--success,#10b981)">Saved. llama.cpp is now the active inference backend. Start the server manually or it will connect on the configured port.</span>';
        } else {
          if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${data.error || 'Save failed'}</span>`;
        }
      } catch (err) {
        if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${err.message}</span>`;
      }
    },

    // ── tinygrad Settings ──

    async refreshTinygradGpuStatus() {
      const el = document.getElementById('tinygrad-gpu-status');
      if (!el) return;
      try {
        const res = await fetch('/api/gpu/status');
        const gpu = await res.json();
        if (gpu.available) {
          el.innerHTML = `<span style="color:var(--success,#10b981)">&#9679;</span> GPU: ${gpu.name || 'Detected'} (${gpu.vram_gb || '?'}GB VRAM, ${gpu.backend || 'cuda'})`;
        } else {
          el.innerHTML = '<span style="color:var(--text-muted)">&#9675;</span> No eGPU detected. Connect an external GPU via Thunderbolt/USB4.';
        }
      } catch {
        el.innerHTML = '<span style="color:var(--text-muted)">&#9675;</span> GPU detection unavailable';
      }
    },

    async checkTinygradStatus() {
      const resultEl = document.getElementById('tinygrad-status-result');
      if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Checking...</span>';
      try {
        const res = await fetch('/api/tinygrad/status');
        const data = await res.json();
        if (data.running) {
          resultEl.innerHTML = `<span style="color:var(--success,#10b981)">Server running (pid ${data.pid}, port ${data.port}, uptime ${data.uptime}s)</span>`;
        } else {
          let msg = 'Server not running.';
          if (!data.pythonPath) msg += ' Python 3.11+ not found.';
          else if (!data.tinygradInstalled) msg += ' tinygrad not installed (pip install tinygrad).';
          resultEl.innerHTML = `<span style="color:var(--text-muted)">${msg}</span>`;
        }
      } catch (err) {
        if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${err.message}</span>`;
      }
    },

    async startTinygrad() {
      const resultEl = document.getElementById('tinygrad-status-result');
      const startBtn = document.getElementById('tinygrad-start-btn');
      const stopBtn = document.getElementById('tinygrad-stop-btn');
      const config = {
        model_path: (document.getElementById('tinygrad-model') || {}).value || '',
        tokenizer_name: (document.getElementById('tinygrad-tokenizer') || {}).value || 'auto',
        port: parseInt((document.getElementById('tinygrad-port') || {}).value, 10) || 8091,
        context_size: parseInt((document.getElementById('tinygrad-ctx') || {}).value, 10) || 8192,
        python_path: (document.getElementById('tinygrad-python-path') || {}).value || '',
        tinygrad_path: (document.getElementById('tinygrad-source-path') || {}).value || '',
      };
      if (!config.model_path) {
        if (resultEl) resultEl.innerHTML = '<span style="color:var(--danger)">Model path is required</span>';
        return;
      }
      if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Starting tinygrad server (model load can take 30s–3min on first start)…</span>';
      if (startBtn) startBtn.disabled = true;
      if (stopBtn) stopBtn.disabled = true;
      try {
        const res = await fetch('/api/tinygrad/start', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        const data = await res.json();
        if (data.success !== false && data.pid) {
          if (resultEl) resultEl.innerHTML = `<span style="color:var(--success,#10b981)">Server started (pid ${data.pid}, port ${data.port})</span>`;
        } else {
          if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${data.error || 'Start failed'}</span>`;
        }
      } catch (err) {
        if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${err.message}</span>`;
      } finally {
        if (startBtn) startBtn.disabled = false;
        if (stopBtn) stopBtn.disabled = false;
      }
    },

    async stopTinygrad() {
      const resultEl = document.getElementById('tinygrad-status-result');
      if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Stopping server…</span>';
      try {
        const res = await fetch('/api/tinygrad/stop', { method: 'POST' });
        const data = await res.json();
        if (data.success !== false) {
          if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Server stopped. Note: on macOS, NVIDIA eGPU has a one-shot-per-boot constraint — a reboot is required before tinygrad can be started again.</span>';
        } else {
          if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${data.error || 'Stop failed'}</span>`;
        }
      } catch (err) {
        if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${err.message}</span>`;
      }
    },

    async saveTinygrad() {
      const resultEl = document.getElementById('tinygrad-status-result');
      if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Saving...</span>';
      try {
        const config = {
          type: 'tinygrad',
          model_path: (document.getElementById('tinygrad-model') || {}).value || '',
          tokenizer_name: (document.getElementById('tinygrad-tokenizer') || {}).value || 'auto',
          port: parseInt((document.getElementById('tinygrad-port') || {}).value, 10) || 8091,
          context_size: parseInt((document.getElementById('tinygrad-ctx') || {}).value, 10) || 8192,
          auto_start: (document.getElementById('tinygrad-autostart') || {}).checked !== false,
          python_path: (document.getElementById('tinygrad-python-path') || {}).value || '',
          tinygrad_path: (document.getElementById('tinygrad-source-path') || {}).value || '',
        };
        if (!config.model_path) {
          if (resultEl) resultEl.innerHTML = '<span style="color:var(--danger)">Model path is required</span>';
          return;
        }
        const res = await fetch('/api/provider', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        const data = await res.json();
        if (data.success !== false) {
          if (resultEl) resultEl.innerHTML = '<span style="color:var(--success,#10b981)">Saved. tinygrad is now the active inference backend. Server will auto-start when eGPU is detected (if enabled).</span>';
        } else {
          if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${data.error || 'Save failed'}</span>`;
        }
      } catch (err) {
        if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${err.message}</span>`;
      }
    },

    applyPreset(preset) {
      const presets = {
        openai:     { url: 'https://api.openai.com/v1', model: 'gpt-4o', tokens: 4096 },
        groq:       { url: 'https://api.groq.com/openai/v1', model: 'llama-3.3-70b-versatile', tokens: 8192 },
        together:   { url: 'https://api.together.xyz/v1', model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo', tokens: 4096 },
        openrouter: { url: 'https://openrouter.ai/api/v1', model: 'anthropic/claude-sonnet-4', tokens: 8192 },
        vllm:       { url: 'http://localhost:8000/v1', model: '', tokens: 4096 },
      };
      const p = presets[preset];
      if (!p) return;
      const urlEl = document.getElementById('provider-url');
      const modelEl = document.getElementById('provider-model');
      const tokensEl = document.getElementById('provider-max-tokens');
      if (urlEl) urlEl.value = p.url;
      if (modelEl) modelEl.value = p.model;
      if (tokensEl) tokensEl.value = p.tokens;
    },

    async testProvider() {
      const resultEl = document.getElementById('provider-test-result');
      if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Testing...</span>';
      try {
        const config = {
          base_url: (document.getElementById('provider-url') || {}).value || '',
          api_key: (document.getElementById('provider-key') || {}).value || '',
          model: (document.getElementById('provider-model') || {}).value || '',
        };
        const res = await fetch('/api/provider/test', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        const data = await res.json();
        if (resultEl) {
          resultEl.innerHTML = data.success
            ? `<span style="color:var(--success)">Connected to ${escapeHtml(data.model)}</span>`
            : `<span style="color:var(--danger)">${escapeHtml(data.message || 'Connection failed')}</span>`;
        }
      } catch (err) {
        if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${escapeHtml(err.message)}</span>`;
      }
    },

    async saveProvider() {
      const config = {
        type: 'openai',
        base_url: (document.getElementById('provider-url') || {}).value || '',
        api_key: (document.getElementById('provider-key') || {}).value || '',
        model: (document.getElementById('provider-model') || {}).value || '',
        max_tokens: parseInt((document.getElementById('provider-max-tokens') || {}).value, 10) || 4096,
      };
      if (!config.base_url || !config.model) {
        const resultEl = document.getElementById('provider-test-result');
        if (resultEl) resultEl.innerHTML = '<span style="color:var(--danger)">Base URL and Model are required</span>';
        return;
      }
      try {
        await fetch('/api/provider', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        this.refresh();
      } catch {}
    },

    async removeProvider() {
      try {
        await fetch('/api/provider', { method: 'DELETE' });
        this.refresh();
      } catch {}
    },

    // ── Azure methods ──

    async testAzure() {
      const resultEl = document.getElementById('azure-test-result');
      if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Testing...</span>';
      try {
        const config = {
          type: 'azure',
          base_url: (document.getElementById('azure-url') || {}).value || '',
          api_key: (document.getElementById('azure-key') || {}).value || '',
          model: (document.getElementById('azure-model') || {}).value || '',
          api_version: (document.getElementById('azure-version') || {}).value || '2024-10-21',
        };
        const res = await fetch('/api/provider/test', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        const data = await res.json();
        if (resultEl) {
          resultEl.innerHTML = data.success
            ? `<span style="color:var(--success)">Connected to ${escapeHtml(data.model || 'Azure deployment')}</span>`
            : `<span style="color:var(--danger)">${escapeHtml(data.message || 'Connection failed')}</span>`;
        }
      } catch (err) {
        if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${escapeHtml(err.message)}</span>`;
      }
    },

    async saveAzure() {
      const config = {
        type: 'azure',
        base_url: (document.getElementById('azure-url') || {}).value || '',
        api_key: (document.getElementById('azure-key') || {}).value || '',
        model: (document.getElementById('azure-model') || {}).value || '',
        max_tokens: parseInt((document.getElementById('azure-max-tokens') || {}).value, 10) || 4096,
        api_version: (document.getElementById('azure-version') || {}).value || '2024-10-21',
      };
      if (!config.base_url || !config.model) {
        const resultEl = document.getElementById('azure-test-result');
        if (resultEl) resultEl.innerHTML = '<span style="color:var(--danger)">Deployment Endpoint and Deployment Name are required</span>';
        return;
      }
      try {
        await fetch('/api/provider', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        this.refresh();
      } catch {}
    },

    // ── Anthropic methods ──

    async testAnthropic() {
      const resultEl = document.getElementById('anthropic-test-result');
      if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Testing...</span>';
      try {
        const config = {
          type: 'anthropic',
          base_url: (document.getElementById('anthropic-url') || {}).value || '',
          api_key: (document.getElementById('anthropic-key') || {}).value || '',
          model: (document.getElementById('anthropic-model') || {}).value || '',
          api_version: (document.getElementById('anthropic-version') || {}).value || '2023-06-01',
        };
        const res = await fetch('/api/provider/test', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        const data = await res.json();
        if (resultEl) {
          resultEl.innerHTML = data.success
            ? `<span style="color:var(--success)">Connected to ${escapeHtml(data.model || 'Anthropic deployment')}</span>`
            : `<span style="color:var(--danger)">${escapeHtml(data.message || 'Connection failed')}</span>`;
        }
      } catch (err) {
        if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${escapeHtml(err.message)}</span>`;
      }
    },

    async saveAnthropic() {
      const config = {
        type: 'anthropic',
        base_url: (document.getElementById('anthropic-url') || {}).value || '',
        api_key: (document.getElementById('anthropic-key') || {}).value || '',
        model: (document.getElementById('anthropic-model') || {}).value || '',
        max_tokens: parseInt((document.getElementById('anthropic-max-tokens') || {}).value, 10) || 4096,
        api_version: (document.getElementById('anthropic-version') || {}).value || '2023-06-01',
      };
      if (!config.base_url) {
        const resultEl = document.getElementById('anthropic-test-result');
        if (resultEl) resultEl.innerHTML = '<span style="color:var(--danger)">Messages Endpoint is required</span>';
        return;
      }
      try {
        await fetch('/api/provider', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        this.refresh();
      } catch {}
    },

    // ── Gemini methods ──

    async testGemini() {
      const resultEl = document.getElementById('gemini-test-result');
      if (resultEl) resultEl.innerHTML = '<span style="color:var(--text-muted)">Testing...</span>';
      try {
        const config = {
          type: 'gemini',
          base_url: (document.getElementById('gemini-url') || {}).value || '',
          api_key: (document.getElementById('gemini-key') || {}).value || '',
          model: (document.getElementById('gemini-model') || {}).value || '',
        };
        const res = await fetch('/api/provider/test', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        const data = await res.json();
        if (resultEl) {
          resultEl.innerHTML = data.success
            ? `<span style="color:var(--success)">Connected to ${escapeHtml(data.model || 'Gemini')}</span>`
            : `<span style="color:var(--danger)">${escapeHtml(data.message || 'Connection failed')}</span>`;
        }
      } catch (err) {
        if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${escapeHtml(err.message)}</span>`;
      }
    },

    async saveGemini() {
      const config = {
        type: 'gemini',
        base_url: (document.getElementById('gemini-url') || {}).value || '',
        api_key: (document.getElementById('gemini-key') || {}).value || '',
        model: (document.getElementById('gemini-model') || {}).value || '',
        max_tokens: parseInt((document.getElementById('gemini-max-tokens') || {}).value, 10) || 8192,
      };
      try {
        const res = await fetch('/api/provider', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(config),
        });
        if (!res.ok) {
          const data = await res.json().catch(() => ({}));
          const resultEl = document.getElementById('gemini-test-result');
          if (resultEl) resultEl.innerHTML = `<span style="color:var(--danger)">${escapeHtml(data.error || 'Save failed')}</span>`;
          return;
        }
        this.refresh();
      } catch {}
    },

    async refresh() {
      openSettingsPanel();
    },
  };
})();
