<script lang="ts">
  import { onMount, onDestroy } from 'svelte'
  import { statusStore } from '../stores/status.svelte.js'
  import { jobsStore } from '../stores/jobs.svelte.js'
  import { api, getToken } from '@core/api.js'
  import { esc, toast, confirmModal, promptModal } from '@core/ui.js'

  const status = $derived(statusStore.value)
  const jobs = $derived(jobsStore.value)

  // Console
  let ws: WebSocket | null = null
  let consoleLogs = $state('Verbinde…\n')
  let selectedService = $state('llama-swap')
  let consoleEl: HTMLDivElement | undefined

  // Active job tracking
  let activeJobId = $state<string | null>(null)
  let activeJobLabel = $state('')
  const notified = new Set<string>()

  const activeJob = $derived(activeJobId ? jobs.find((j: any) => j.id === activeJobId) : null)

  // Auto-scroll + job finish notification
  $effect(() => {
    if (!activeJob) return
    const j = activeJob as any
    const done = j.state === 'done', failed = j.state === 'failed', canceled = j.state === 'canceled'
    if ((done || failed || canceled) && !notified.has(j.id)) {
      notified.add(j.id)
      toast(done ? `✓ ${activeJobLabel} abgeschlossen.` : canceled ? `${activeJobLabel} abgebrochen.` : `✗ ${activeJobLabel} fehlgeschlagen.`, failed)
      if (done) setTimeout(() => document.dispatchEvent(new Event('mc:refresh')), 800)
    }
  })

  function watchJob(id: string, label: string) {
    activeJobId = id
    activeJobLabel = label
    jobsStore.track(id)
  }

  // Expose track for use from other panels (server panel tracks its own jobs)
  onMount(() => { connectConsole() })
  onDestroy(() => { ws?.close() })

  function connectConsole() {
    ws?.close()
    const svc = selectedService
    consoleLogs = `Verbinde mit ${svc}…\n`
    const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'
    ws = new WebSocket(`${proto}//${location.host}/api/logs/${svc}?token=${encodeURIComponent(getToken())}`)
    ws.onmessage = e => {
      consoleLogs += e.data
      if (consoleEl) consoleEl.scrollTop = consoleEl.scrollHeight
    }
    ws.onclose = () => { consoleLogs += '\n— Verbindung getrennt —' }
    ws.onerror = () => ws?.close()
  }

  async function restartService(name: string, title: string, body: string) {
    if (!await confirmModal({ title, body, confirmLabel: 'Neustarten' })) return
    try {
      await api(`/api/service/${name}/restart`, { method: 'POST' })
      toast('Neustart ausgelöst: ' + name)
      setTimeout(() => document.dispatchEvent(new Event('mc:refresh')), 2000)
    } catch (e: any) { toast(e.message, true) }
  }

  async function selfUpdate() {
    if (!await confirmModal({ title: 'Mission Control aktualisieren?', body: 'Holt die neueste Dashboard-Version aus Gitea und startet automatisch neu. Die Seite trennt kurz und verbindet von selbst wieder (~10 Sek).' })) return
    try { const r = await api('/api/self-update', { method: 'POST' }); toast('Update läuft — Dashboard startet gleich neu.'); watchJob(r.job_id, 'Mission Control Update') }
    catch (e: any) { toast(e.message, true) }
  }

  async function engineUpdate() {
    if (!await confirmModal({ title: 'LLM-Engine aktualisieren?', body: 'Lädt die neueste llama.cpp-ROCm-Version und installiert sie. Die Engine ist während des Updates kurz nicht erreichbar.' })) return
    try { const r = await api('/api/update', { method: 'POST' }); toast('LLM-Engine-Update läuft.'); watchJob(r.job_id, 'LLM-Engine-Update') }
    catch (e: any) { toast(e.message, true) }
  }

  async function unloadAll() {
    if (!await confirmModal({ title: 'Grafikspeicher leeren?', body: 'Alle geladenen Modelle werden entladen. Sie laden beim nächsten Aufruf automatisch neu — es geht nichts verloren.', confirmLabel: 'Leeren' })) return
    try { await api('/api/unload', { method: 'POST' }); toast('Grafikspeicher geleert.'); setTimeout(() => document.dispatchEvent(new Event('mc:refresh')), 600) }
    catch (e: any) { toast(e.message, true) }
  }

  async function osUpdate() {
    if (!await confirmModal({ title: 'OS-Updates installieren?', body: 'Führt <code>apt update & upgrade</code> aus. Das kann ein paar Minuten dauern; der Fortschritt erscheint in der Aktivität.' })) return
    const pwd = await promptModal({ title: 'sudo-Passwort', body: 'Für die System-Updates wird einmalig dein sudo-Passwort gebraucht.', placeholder: 'sudo-Passwort', password: true, confirmLabel: 'Installieren' })
    if (!pwd) return
    try { const r = await api('/api/os-update', { method: 'POST', body: JSON.stringify({ password: pwd }) }); toast('OS-Update gestartet.'); watchJob(r.job_id, 'OS-Update') }
    catch (e: any) { toast(e.message, true) }
  }

  async function rebootServer() {
    if (!await confirmModal({ title: 'Server wirklich neu starten?', body: 'Der ganze Bosgame startet physisch neu. Alles ist für ~1 Minute offline — auch dieses Dashboard.', confirmLabel: 'Reboot', danger: true })) return
    const pwd = await promptModal({ title: 'sudo-Passwort', body: 'Für den Reboot wird einmalig dein sudo-Passwort gebraucht.', placeholder: 'sudo-Passwort', password: true, confirmLabel: 'Jetzt neustarten', danger: true })
    if (!pwd) return
    try { await api('/api/reboot', { method: 'POST', body: JSON.stringify({ password: pwd }) }); toast('Reboot ausgelöst — bis gleich.') }
    catch (e: any) { toast(e.message, true) }
  }

  async function checkSwap() {
    swapCheckResult = 'prüfe…'
    try {
      const r = await api('/api/integration/test')
      swapCheckResult = r.ok ? `✓ Engine antwortet — ${r.models.length} Modell(e): ${r.models.join(', ')}` : `✗ keine Verbindung: ${r.error}`
      swapCheckOk = r.ok
    } catch (e: any) { swapCheckResult = '✗ ' + e.message; swapCheckOk = false }
  }

  let swapCheckResult = $state('')
  let swapCheckOk = $state(false)

  const swapModels = $derived((status?.models || []) as any[])
  const LOADED = ['running', 'ready', 'loading', 'starting']
  const loadedModel = $derived(swapModels.find((m: any) => LOADED.includes(m.state)))

  // Job progress helpers
  function jobPct(j: any) {
    if (!j || j.state !== 'running') return null
    if (typeof j.progress === 'number') return Math.min(100, j.progress)
    const last = (j.log || []).at(-1) || ''
    const m = last.match(/(\d+(?:\.\d+)?)\s*%/)
    return m ? Math.min(100, parseFloat(m[1])) : null
  }

  function qa(tone: string, title: string, desc: string, fn: () => void, danger = false) {
    return { tone, title, desc, fn, danger }
  }
</script>

<div class="pagehead">
  <div>
    <h1>Server &amp; Wartung</h1>
    <div class="sub">Dienste steuern, Updates einspielen und live mitlesen — ganz ohne SSH oder Terminal.</div>
  </div>
</div>

<!-- LLM-Engine Card -->
<div class="card">
  <div class="card-h">
    <h3>LLM-Engine (llama-swap)</h3>
    <span class="chip" style="margin-left:auto;{status?.swap_ok
      ? 'background:rgba(63,185,80,.12);border-color:rgba(63,185,80,.25);color:#7ee29a'
      : 'background:rgba(248,81,73,.12);border-color:rgba(248,81,73,.3);color:#ff9b95'}">
      {status?.swap_ok ? '● erreichbar' : '● offline'}
    </span>
  </div>
  <div class="card-sub">Das Herzstück: lädt bei jeder Anfrage automatisch das passende Modell.</div>
  <div class="kv" style="margin-bottom:6px">
    <div class="kv-row"><span class="kv-k">Adresse (für deine Tools)</span><span class="kv-v mono-sm">{(status?.swap_url || '—') + '/v1'}</span></div>
    <div class="kv-row"><span class="kv-k">Aktuell geladen</span>
      <span class="kv-v">{loadedModel ? loadedModel.name + ` (${loadedModel.state})` : 'nichts geladen — lädt bei der nächsten Anfrage'}</span>
    </div>
    <div class="kv-row"><span class="kv-k">Konfigurierte Modelle</span>
      <span class="kv-v">{swapModels.filter((m: any) => !m.incomplete).length || 'keine'}</span>
    </div>
  </div>
  <div class="btn-row" style="display:flex;align-items:center;gap:10px">
    <button class="ghost" onclick={checkSwap}>Verbindung prüfen</button>
    {#if swapCheckResult}
      <span class="mono-sm" style="color:{swapCheckOk ? '#7ee29a' : '#ff9b95'}">{swapCheckResult}</span>
    {/if}
  </div>
</div>

<!-- Service Actions -->
<div class="card">
  <div class="card-h"><h3>Dienste &amp; Applikation</h3></div>
  <div class="card-sub">Diese Aktionen sind harmlos und passwortlos — es geht nichts dabei verloren.</div>
  <button class="qa" onclick={() => restartService('llama-swap', 'LLM-Engine neustarten?', 'Die Engine startet neu. Geladene Modelle werden kurz entladen (~5 Sekunden), laden danach automatisch wieder.')}>
    <span class="qa-ic teal">↻</span>
    <span class="qa-main"><span class="qa-t">LLM-Engine neustarten</span><span class="qa-s">Startet llama-swap neu. Geladene Modelle laden danach automatisch wieder.</span></span>
    <span class="qa-arrow">›</span>
  </button>
  <button class="qa" onclick={() => restartService('mission-control', 'Dashboard neustarten?', 'Diese Oberfläche trennt sich kurz und verbindet automatisch wieder.')}>
    <span class="qa-ic blue">↻</span>
    <span class="qa-main"><span class="qa-t">Dashboard neustarten</span><span class="qa-s">Startet diese Oberfläche neu (~5 Sek).</span></span>
    <span class="qa-arrow">›</span>
  </button>
  <button class="qa" onclick={selfUpdate}>
    <span class="qa-ic teal">⬇</span>
    <span class="qa-main"><span class="qa-t">Mission Control aktualisieren</span><span class="qa-s">Holt die neueste Version aus Gitea und startet automatisch neu (~10 Sek).</span></span>
    <span class="qa-arrow">›</span>
  </button>
  <button class="qa" onclick={engineUpdate}>
    <span class="qa-ic blue">⚡</span>
    <span class="qa-main"><span class="qa-t">LLM-Engine aktualisieren</span><span class="qa-s">Lädt die neueste llama.cpp-Version (ROCm) für deine GPU.</span></span>
    <span class="qa-arrow">›</span>
  </button>
  <button class="qa" onclick={unloadAll}>
    <span class="qa-ic amber">🗄</span>
    <span class="qa-main"><span class="qa-t">Grafikspeicher leeren</span><span class="qa-s">Wirft alle aktuell geladenen Modelle aus dem VRAM. Fragt vorher nach.</span></span>
    <span class="qa-arrow">›</span>
  </button>
</div>

<!-- OS Actions -->
<div class="card">
  <div class="card-h"><h3>Betriebssystem (Bosgame)</h3></div>
  <div class="card-sub">Tiefe Eingriffe — das Dashboard fragt einmalig nach deinem sudo-Passwort.</div>
  <button class="qa" onclick={osUpdate}>
    <span class="qa-ic amber">↻</span>
    <span class="qa-main"><span class="qa-t">OS-Updates installieren</span><span class="qa-s">Führt apt update &amp; upgrade aus. Kann ein paar Minuten dauern.</span></span>
    <span class="qa-arrow">›</span>
  </button>
  <button class="qa qa-danger" onclick={rebootServer}>
    <span class="qa-ic red">⚡</span>
    <span class="qa-main"><span class="qa-t">Server neustarten (Reboot)</span><span class="qa-s">Bootet den ganzen Bosgame neu. Alles ist für ~1 Minute offline.</span></span>
    <span class="qa-arrow">›</span>
  </button>
</div>

<!-- Active Job Card -->
{#if activeJob}
  {@const j = activeJob as any}
  {@const done = j.state === 'done'}
  {@const failed = j.state === 'failed'}
  {@const canceled = j.state === 'canceled'}
  {@const terminal = done || failed || canceled}
  {@const p = jobPct(j)}
  <div class="card">
    <div class="card-h">
      <h3>Aktueller Vorgang</h3>
      {#if !terminal}
        <button class="ghost" style="margin-left:auto;padding:3px 12px"
          onclick={async () => {
            if (await confirmModal({ title: activeJobLabel + ' abbrechen?', body: 'Der laufende Vorgang wird gestoppt.', confirmLabel: 'Abbrechen', danger: true }))
              api(`/api/jobs/${j.id}/cancel`, { method: 'POST' })
          }}>Abbrechen</button>
      {/if}
      <span style="margin-left:10px">
        {#if done}<span class="badge b-run">✓ fertig</span>
        {:else if failed}<span class="badge b-err">✗ fehlgeschlagen</span>
        {:else if canceled}<span class="badge">abgebrochen</span>
        {:else}<span class="badge b-load">läuft…</span>{/if}
      </span>
    </div>
    <div class="card-sub">{activeJobLabel}{terminal ? (done ? ' — erfolgreich.' : canceled ? ' — abgebrochen.' : ' — fehlgeschlagen.') : ' läuft…'}</div>
    {#if p != null && !terminal}
      <div class="bar" style="margin-bottom:10px"><i style="width:{p}%"></i></div>
    {/if}
    <div class="log" style="max-height:280px">{(j.log || []).join('\n')}</div>
  </div>
{/if}

<!-- Live Console -->
<div class="card">
  <div class="card-h">
    <h3>Live-Konsole</h3>
    <select bind:value={selectedService} onchange={connectConsole} style="margin:0 0 0 auto;width:240px">
      <option value="llama-swap">LLM-Engine (llama-swap)</option>
      <option value="mission-control">Dashboard (mission-control)</option>
      <option value="system">System (ganzer Server)</option>
    </select>
  </div>
  <div class="card-sub">Live mitlesen, was der Dienst gerade tut.</div>
  <div class="console" bind:this={consoleEl}>{consoleLogs}</div>
</div>
