{% extends "base.html" %}
{% block title %}Settings — CoderAI{% endblock %}

{% block content %}
<div class="page-header">
  <div>
    <h1>Settings</h1>
    <p>Server configuration — restart CoderAI to apply changes</p>
  </div>
  <div class="header-actions">
    <span id="save-status" class="muted small" style="margin-right:.5rem"></span>
    <button class="btn btn-primary" onclick="saveSettings()">Save changes</button>
  </div>
</div>

<div id="settings-alert" style="display:none"></div>

<!-- Server -->
<div class="card mb-0">
  <div class="card-title">Server</div>
  <div style="display:grid;grid-template-columns:1fr 160px;gap:1rem;align-items:start">
    <div class="form-row" style="margin:0">
      <label class="form-label">Listen host</label>
      <input type="text" id="s-host" class="form-input" placeholder="0.0.0.0">
      <span class="form-hint">IP address or hostname to bind to (0.0.0.0 = all interfaces)</span>
    </div>
    <div class="form-row" style="margin:0">
      <label class="form-label">Port</label>
      <input type="number" id="s-port" class="form-input" placeholder="8000" min="1" max="65535">
    </div>
  </div>
  <div class="form-row" style="margin-top:1rem;margin-bottom:.25rem">
    <label style="display:flex;align-items:center;gap:.5rem;cursor:pointer">
      <input type="checkbox" id="s-https" onchange="toggleHttps()">
      <span style="font-size:13px;font-weight:500">Enable HTTPS</span>
    </label>
  </div>
  <div id="https-fields" style="display:none;margin-top:.75rem">
    <div class="form-row">
      <label class="form-label">SSL key path <span class="muted">(leave blank to auto-generate)</span></label>
      <input type="text" id="s-key" class="form-input" placeholder="/path/to/key.pem">
    </div>
    <div class="form-row" style="margin:0">
      <label class="form-label">SSL certificate path</label>
      <input type="text" id="s-cert" class="form-input" placeholder="/path/to/cert.pem">
    </div>
  </div>
  <div class="form-row" style="margin-top:1rem;margin-bottom:0">
    <label class="form-label">Request queue max size</label>
    <input type="number" id="s-queue-max" class="form-input" placeholder="6" min="1" max="1000" style="max-width:160px">
    <span class="form-hint">Maximum number of concurrent queued requests. Authenticated requests arriving when the queue is full receive a 429 response.</span>
  </div>
</div>

<!-- Storage -->
<div class="card mb-0" style="margin-top:1rem">
  <div class="card-title">Storage</div>
  <div class="form-row">
    <label class="form-label">HuggingFace cache directory <span class="muted">(leave blank for default ~/.cache/huggingface)</span></label>
    <input type="text" id="s-hf-cache" class="form-input" placeholder="e.g. /data/models/huggingface">
  </div>
  <div class="form-row">
    <label class="form-label">GGUF cache directory <span class="muted">(leave blank for default ~/.cache/coderai/models)</span></label>
    <input type="text" id="s-gguf-cache" class="form-input" placeholder="e.g. /data/models/gguf">
  </div>
  <div class="form-row" style="margin:0">
    <label class="form-label">Default offload directory <span class="muted">(default: ./offload)</span></label>
    <input type="text" id="s-offload-dir" class="form-input" placeholder="./offload">
    <span class="form-hint">Models will inherit this as default when configured</span>
  </div>
</div>

<!-- Archive -->
<div class="card mb-0" style="margin-top:1rem">
  <div class="card-title">Generation Archive</div>
  <div class="form-row">
    <label style="display:flex;align-items:center;gap:.5rem;cursor:pointer">
      <input type="checkbox" id="s-arc-enabled">
      <span style="font-size:13px;font-weight:500">Enable generation archive</span>
    </label>
    <span class="form-hint">When enabled, every generation (image, video, audio, text) is logged with its prompt, model, and output files.</span>
  </div>
  <div class="form-row">
    <label class="form-label">Archive directory <span class="muted" id="s-arc-dir-hint"></span></label>
    <input type="text" id="s-arc-dir" class="form-input" placeholder="(default)">
    <span class="form-hint">Absolute path or relative to the config directory. Leave blank to use the default.</span>
  </div>
  <div class="form-row" style="margin:0">
    <label class="form-label">Retention period</label>
    <select id="s-arc-retention" class="form-input" style="max-width:200px">
      <option value="1h">1 hour</option>
      <option value="1d">1 day</option>
      <option value="2d">2 days</option>
      <option value="1w">1 week</option>
      <option value="1m">1 month</option>
      <option value="3m">3 months</option>
      <option value="6m">6 months</option>
      <option value="1y">1 year</option>
      <option value="never">Never (keep forever)</option>
    </select>
    <span class="form-hint">Archived entries older than this are automatically deleted. Takes effect immediately on save.</span>
  </div>
</div>

<!-- Thermal protection -->
<div class="card mb-0" style="margin-top:1rem">
  <div class="card-title">Thermal Protection</div>
  <span class="form-hint" style="display:block;margin-bottom:.75rem">
    Before serving a request against a loaded model, wait until temperatures are
    safe so a long sequence of heavy generations can't overheat the machine and
    trip its power-off protection. The wait is non-blocking (other requests keep
    being accepted) and takes effect immediately on save. Temperatures in °C.
  </span>

  <div class="form-row">
    <label style="display:flex;align-items:center;gap:.5rem;cursor:pointer">
      <input type="checkbox" id="s-therm-gpu-enabled" onchange="toggleThermalFields()">
      <span style="font-size:13px;font-weight:500">Enable GPU temperature protection</span>
    </label>
  </div>
  <div id="therm-gpu-fields" class="form-row" style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
    <div>
      <label class="form-label">Pause when GPU reaches (°C)</label>
      <input type="number" id="s-therm-gpu-high" class="form-input" min="40" max="120" step="1" placeholder="90">
    </div>
    <div>
      <label class="form-label">Resume when GPU drops to (°C)</label>
      <input type="number" id="s-therm-gpu-resume" class="form-input" min="30" max="120" step="1" placeholder="87">
    </div>
  </div>

  <div class="form-row" style="margin-top:.5rem">
    <label style="display:flex;align-items:center;gap:.5rem;cursor:pointer">
      <input type="checkbox" id="s-therm-cpu-enabled" onchange="toggleThermalFields()">
      <span style="font-size:13px;font-weight:500">Enable CPU temperature protection</span>
    </label>
  </div>
  <div id="therm-cpu-fields" class="form-row" style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
    <div>
      <label class="form-label">Pause when CPU reaches (°C)</label>
      <input type="number" id="s-therm-cpu-high" class="form-input" min="40" max="120" step="1" placeholder="90">
    </div>
    <div>
      <label class="form-label">Resume when CPU drops to (°C)</label>
      <input type="number" id="s-therm-cpu-resume" class="form-input" min="30" max="120" step="1" placeholder="87">
    </div>
  </div>

  <div class="form-row" style="margin:0">
    <label class="form-label">Re-check interval while cooling down (seconds)</label>
    <input type="number" id="s-therm-poll" class="form-input" style="max-width:200px" min="1" max="120" step="1" placeholder="5">
    <span class="form-hint">How often to re-read temperatures while waiting for cooldown.</span>
  </div>
</div>

<div class="card mb-0" style="margin-top:1rem">
  <div class="card-title">AISBF Broker</div>
  <div class="form-row">
    <label style="display:flex;align-items:center;gap:.5rem;cursor:pointer">
      <input type="checkbox" id="s-broker-enabled" onchange="toggleBrokerFields()">
      <span style="font-size:13px;font-weight:500">Enable broker client</span>
    </label>
    <span class="form-hint">Registers this CoderAI instance with an AISBF broker so it can receive brokered requests.</span>
  </div>
  <div id="broker-fields" style="display:none">
    <div class="form-row">
      <label class="form-label">Broker base URL</label>
      <input type="text" id="s-broker-base-url" class="form-input" placeholder="https://broker.example.com">
      <span class="form-hint">Supports `http`, `https`, `ws`, or `wss`. The websocket endpoint is derived automatically.</span>
    </div>
    <div style="display:grid;grid-template-columns:180px 1fr;gap:1rem;align-items:start">
      <div class="form-row" style="margin:0">
        <label class="form-label">Scope</label>
        <select id="s-broker-scope" class="form-input" onchange="toggleBrokerFields()">
          <option value="user">user</option>
          <option value="global">global</option>
        </select>
      </div>
      <div class="form-row" style="margin:0">
        <label class="form-label">Username</label>
        <input type="text" id="s-broker-username" class="form-input" placeholder="alice or global">
        <span class="form-hint">This is forced to `global` for global scope; user scope requires the AISBF username.</span>
      </div>
    </div>
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;align-items:start">
      <div class="form-row" style="margin:0">
        <label class="form-label">Provider ID</label>
        <input type="text" id="s-broker-provider-id" class="form-input" placeholder="coderai-local">
      </div>
      <div class="form-row" style="margin:0">
        <label class="form-label">Client ID</label>
        <input type="text" id="s-broker-client-id" class="form-input" placeholder="workstation-01">
      </div>
    </div>
    <div class="form-row">
      <label class="form-label">Registration token</label>
      <input type="password" id="s-broker-registration-token" class="form-input" placeholder="Broker registration token">
    </div>
    <div class="form-row">
      <label class="form-label">Advertised endpoint</label>
      <input type="text" id="s-broker-advertised-endpoint" class="form-input" placeholder="http://127.0.0.1:8776">
      <span class="form-hint">Optional external URL advertised to the broker for this instance.</span>
    </div>
    <div class="form-row">
      <label class="form-label">Websocket path override</label>
      <input type="text" id="s-broker-websocket-path" class="form-input" placeholder="/api/coderai/wss">
      <span class="form-hint">Optional manual websocket route override for proxied or custom broker deployments; leave empty to derive from scope.</span>
    </div>
    <div style="display:grid;grid-template-columns:repeat(3, minmax(0, 1fr));gap:1rem;align-items:start">
      <div class="form-row" style="margin:0">
        <label class="form-label">Heartbeat seconds</label>
        <input type="number" id="s-broker-heartbeat" class="form-input" min="1" placeholder="30">
      </div>
      <div class="form-row" style="margin:0">
        <label class="form-label">Connect timeout</label>
        <input type="number" id="s-broker-connect-timeout" class="form-input" min="1" placeholder="10">
      </div>
      <div class="form-row" style="margin:0">
        <label class="form-label">Request timeout</label>
        <input type="number" id="s-broker-request-timeout" class="form-input" min="1" placeholder="30">
      </div>
    </div>
    <div style="display:grid;grid-template-columns:repeat(3, minmax(0, 1fr));gap:1rem;align-items:start">
      <div class="form-row" style="margin:0">
        <label class="form-label">Reconnect initial delay</label>
        <input type="number" id="s-broker-reconnect-initial" class="form-input" min="1" placeholder="1">
      </div>
      <div class="form-row" style="margin:0">
        <label class="form-label">Reconnect max delay</label>
        <input type="number" id="s-broker-reconnect-max" class="form-input" min="1" placeholder="60">
      </div>
      <div class="form-row" style="margin:0">
        <label class="form-label">WS ping interval (s)</label>
        <input type="number" id="s-broker-ws-ping" class="form-input" min="5" placeholder="20">
        <span class="form-hint" style="font-size:11px">Keeps connection alive through nginx proxies. Lower if you get 504 timeouts.</span>
      </div>
    </div>
  </div>
</div>
{% endblock %}

{% block scripts %}
<script>
function toggleHttps(){
  document.getElementById('https-fields').style.display =
    document.getElementById('s-https').checked ? 'block' : 'none';
}

function toggleBrokerFields(){
  document.getElementById('broker-fields').style.display =
    document.getElementById('s-broker-enabled').checked ? 'block' : 'none';

  const scope = document.getElementById('s-broker-scope').value;
  const usernameInput = document.getElementById('s-broker-username');
  if(scope === 'global'){
    usernameInput.value = 'global';
    usernameInput.readOnly = true;
  } else {
    usernameInput.readOnly = false;
    if(usernameInput.value === 'global') usernameInput.value = '';
  }
}

function toggleThermalFields(){
  document.getElementById('therm-gpu-fields').style.display =
    document.getElementById('s-therm-gpu-enabled').checked ? 'grid' : 'none';
  document.getElementById('therm-cpu-fields').style.display =
    document.getElementById('s-therm-cpu-enabled').checked ? 'grid' : 'none';
}

function showAlert(type, msg){
  const el = document.getElementById('settings-alert');
  el.className = 'alert alert-' + (type === 'error' ? 'error' : 'info');
  el.textContent = msg;
  el.style.display = 'flex';
  if(type !== 'error') setTimeout(()=>{ el.style.display='none'; }, 4000);
}

async function loadSettings(){
  try{
    const d = await fetch(ROOT_PATH + '/admin/api/settings').then(r=>r.json());
    document.getElementById('s-host').value  = d.server?.host ?? '0.0.0.0';
    document.getElementById('s-port').value  = d.server?.port ?? 8000;
    document.getElementById('s-https').checked = !!d.server?.https;
    document.getElementById('s-key').value   = d.server?.https_key_path ?? '';
    document.getElementById('s-cert').value  = d.server?.https_cert_path ?? '';
    document.getElementById('s-queue-max').value = d.server?.queue_max_size ?? 6;
    document.getElementById('s-hf-cache').value   = d.models?.hf_cache_dir ?? '';
    document.getElementById('s-gguf-cache').value = d.models?.gguf_cache_dir ?? '';
    document.getElementById('s-offload-dir').value = d.offload?.directory ?? './offload';
    toggleHttps();
    // Archive
    const arc = d.archive || {};
    document.getElementById('s-arc-enabled').checked = !!arc.enabled;
    document.getElementById('s-arc-dir').value = arc.directory ?? '';
    document.getElementById('s-arc-retention').value = arc.retention ?? 'never';
    // Show effective default dir as placeholder hint
    try {
      const as = await fetch(ROOT_PATH + '/admin/api/archive-settings').then(r=>r.json());
      const hint = document.getElementById('s-arc-dir-hint');
      if (hint && as.default_directory) hint.textContent = `(default: ${as.default_directory})`;
      document.getElementById('s-arc-dir').placeholder = as.default_directory || '(default)';
    } catch(_){}
    const broker = d.broker || {};
    document.getElementById('s-broker-enabled').checked = !!broker.enabled;
    document.getElementById('s-broker-base-url').value = broker.base_url ?? '';
    document.getElementById('s-broker-scope').value = broker.scope ?? 'user';
    document.getElementById('s-broker-username').value = broker.username ?? '';
    document.getElementById('s-broker-provider-id').value = broker.provider_id ?? '';
    document.getElementById('s-broker-client-id').value = broker.client_id ?? '';
    document.getElementById('s-broker-registration-token').value = broker.registration_token ?? '';
    document.getElementById('s-broker-advertised-endpoint').value = broker.advertised_endpoint ?? '';
    document.getElementById('s-broker-websocket-path').value = broker.websocket_path ?? '';
    document.getElementById('s-broker-heartbeat').value = broker.heartbeat_interval_seconds ?? 30;
    document.getElementById('s-broker-connect-timeout').value = broker.connect_timeout_seconds ?? 10;
    document.getElementById('s-broker-request-timeout').value = broker.request_timeout_seconds ?? 30;
    document.getElementById('s-broker-reconnect-initial').value = broker.reconnect_initial_delay_seconds ?? 1;
    document.getElementById('s-broker-reconnect-max').value = broker.reconnect_max_delay_seconds ?? 60;
    document.getElementById('s-broker-ws-ping').value = broker.websocket_ping_interval ?? 20;
    toggleBrokerFields();
    // Thermal protection
    const therm = d.thermal || {};
    document.getElementById('s-therm-gpu-enabled').checked = therm.gpu_enabled !== false;
    document.getElementById('s-therm-cpu-enabled').checked = therm.cpu_enabled !== false;
    document.getElementById('s-therm-gpu-high').value = therm.gpu_high ?? 90;
    document.getElementById('s-therm-gpu-resume').value = therm.gpu_resume ?? 87;
    document.getElementById('s-therm-cpu-high').value = therm.cpu_high ?? 90;
    document.getElementById('s-therm-cpu-resume').value = therm.cpu_resume ?? 87;
    document.getElementById('s-therm-poll').value = therm.poll_seconds ?? 5;
    toggleThermalFields();
  }catch(e){ showAlert('error','Failed to load settings: '+e.message); }
}

async function saveSettings(){
  const strOrNull = id => document.getElementById(id).value.trim() || null;
  const data = {
    server:{
      host: document.getElementById('s-host').value.trim() || '0.0.0.0',
      port: parseInt(document.getElementById('s-port').value) || 8000,
      https: document.getElementById('s-https').checked,
      https_key_path:  strOrNull('s-key'),
      https_cert_path: strOrNull('s-cert'),
      queue_max_size: parseInt(document.getElementById('s-queue-max').value) || 6,
    },
    models:{
      hf_cache_dir:   strOrNull('s-hf-cache'),
      gguf_cache_dir: strOrNull('s-gguf-cache'),
    },
    offload:{
      directory: document.getElementById('s-offload-dir').value.trim() || './offload',
    },
    archive:{
      enabled: document.getElementById('s-arc-enabled').checked,
      directory: document.getElementById('s-arc-dir').value.trim(),
      retention: document.getElementById('s-arc-retention').value,
    },
    thermal:{
      gpu_enabled: document.getElementById('s-therm-gpu-enabled').checked,
      cpu_enabled: document.getElementById('s-therm-cpu-enabled').checked,
      gpu_high:   parseFloat(document.getElementById('s-therm-gpu-high').value)   || 90,
      gpu_resume: parseFloat(document.getElementById('s-therm-gpu-resume').value) || 87,
      cpu_high:   parseFloat(document.getElementById('s-therm-cpu-high').value)   || 90,
      cpu_resume: parseFloat(document.getElementById('s-therm-cpu-resume').value) || 87,
      poll_seconds: parseFloat(document.getElementById('s-therm-poll').value) || 5,
    },
    broker:{
      enabled: document.getElementById('s-broker-enabled').checked,
      base_url: document.getElementById('s-broker-base-url').value.trim(),
      scope: document.getElementById('s-broker-scope').value,
      username: document.getElementById('s-broker-username').value.trim(),
      provider_id: document.getElementById('s-broker-provider-id').value.trim(),
      client_id: document.getElementById('s-broker-client-id').value.trim(),
      registration_token: document.getElementById('s-broker-registration-token').value.trim(),
      advertised_endpoint: document.getElementById('s-broker-advertised-endpoint').value.trim(),
      websocket_path: document.getElementById('s-broker-websocket-path').value.trim(),
      heartbeat_interval_seconds: parseInt(document.getElementById('s-broker-heartbeat').value) || 30,
      connect_timeout_seconds: parseInt(document.getElementById('s-broker-connect-timeout').value) || 10,
      request_timeout_seconds: parseInt(document.getElementById('s-broker-request-timeout').value) || 30,
      reconnect_initial_delay_seconds: parseInt(document.getElementById('s-broker-reconnect-initial').value) || 1,
      reconnect_max_delay_seconds: parseInt(document.getElementById('s-broker-reconnect-max').value) || 60,
      websocket_ping_interval: parseInt(document.getElementById('s-broker-ws-ping').value) || 20,
      transport: 'websocket',
    },
  };
  try{
    const r = await fetch(ROOT_PATH + '/admin/api/settings',{
      method:'POST', headers:{'Content-Type':'application/json'},
      body: JSON.stringify(data)
    });
    if(r.ok) showAlert('info','Settings saved. Archive and thermal-protection changes take effect immediately; restart CoderAI for other changes.');
    else{ const e=await r.json(); showAlert('error', e.detail||'Save failed'); }
  }catch(e){ showAlert('error','Error: '+e.message); }
}

loadSettings();
</script>
{% endblock %}
