import { IncomingMessage, ServerResponse, createServer } from 'http'
import { networkInterfaces } from 'os'
import { existsSync, readFileSync } from 'fs'
import { join } from 'path'
import { app } from 'electron'
import { classifyInput, saveEntry, getDashboard } from '../services/classifier'
import { readSettings } from '../services/settings-store'
import { transcribeAudio } from '../services/llm-service'

export const MOBILE_PORT = 3847

// ─── NETZWERK-IP ──────────────────────────────────────────────────────────────

export function getLocalIp(): string {
  const nets = networkInterfaces()
  for (const iface of Object.values(nets)) {
    if (!iface) continue
    for (const config of iface) {
      if (config.family === 'IPv4' && !config.internal) return config.address
    }
  }
  return '127.0.0.1'
}

export function getMobileUrl(): string {
  return `http://${getLocalIp()}:${MOBILE_PORT}`
}

// ─── AVATAR ───────────────────────────────────────────────────────────────────

let _avatarCache: Buffer | null | undefined = undefined

function getAvatarBuffer(): Buffer | null {
  if (_avatarCache !== undefined) return _avatarCache
  const base = app.isPackaged
    ? (process as NodeJS.Process & { resourcesPath: string }).resourcesPath
    : join(app.getAppPath(), 'resources')
  const candidates = [
    join(base, 'donna.png'),
    join(base, 'donna.jpg'),
    join(process.cwd(), 'resources', 'donna.png'),
    join(process.cwd(), 'resources', 'donna.jpg'),
  ]
  for (const p of candidates) {
    if (existsSync(p)) { _avatarCache = readFileSync(p); return _avatarCache }
  }
  _avatarCache = null
  return null
}

// ─── HELPERS ──────────────────────────────────────────────────────────────────

function readBody(req: IncomingMessage): Promise<unknown> {
  return new Promise((resolve, reject) => {
    let raw = ''
    req.on('data', chunk => { raw += chunk })
    req.on('end', () => {
      try { resolve(JSON.parse(raw)) } catch { resolve({}) }
    })
    req.on('error', reject)
  })
}

function sendJson(res: ServerResponse, status: number, data: unknown) {
  const body = JSON.stringify(data)
  res.writeHead(status, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'Content-Type',
  })
  res.end(body)
}

// ─── PWA-ASSETS ───────────────────────────────────────────────────────────────

const ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <rect width="512" height="512" rx="90" fill="#1B1F3B"/>
  <text x="256" y="360" text-anchor="middle" font-family="Georgia,serif" font-size="300" fill="#C9A96E" font-weight="700">D</text>
</svg>`

const MANIFEST_JSON = JSON.stringify({
  name: 'The Donna',
  short_name: 'Donna',
  description: 'Deine strategische Assistentin',
  start_url: '/',
  display: 'standalone',
  orientation: 'portrait',
  background_color: '#1B1F3B',
  theme_color: '#1B1F3B',
  lang: 'de',
  icons: [{ src: '/icon.svg', sizes: 'any', type: 'image/svg+xml', purpose: 'any maskable' }],
})

// ─── MOBILE HTML ──────────────────────────────────────────────────────────────

const MOBILE_HTML = `<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  <meta name="theme-color" content="#1B1F3B">
  <link rel="manifest" href="/manifest.json">
  <link rel="apple-touch-icon" href="/icon.svg">
  <title>Donna</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
    :root {
      --navy: #1B1F3B;
      --gold: #C9A96E;
      --cream: #F5F0E8;
      --muted: rgba(245,240,232,0.45);
      --sidebar: #161929;
      --success: #4CAF7D;
      --danger: #c0392b;
    }
    html, body {
      background: var(--navy);
      color: var(--cream);
      font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif;
      min-height: 100vh;
      height: 100%;
    }
    .phase { display: none; min-height: 100vh; flex-direction: column; }
    .phase.active { display: flex; }

    header {
      padding: 16px 20px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 1px solid rgba(201,169,110,0.15);
      background: var(--sidebar);
      position: sticky;
      top: 0;
      z-index: 10;
    }
    .logo-wrap { display: flex; align-items: center; gap: 10px; }
    .donna-avatar { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; border: 1.5px solid rgba(201,169,110,0.4); flex-shrink: 0; }
    .logo { font-size: 18px; font-weight: 700; letter-spacing: 4px; color: var(--gold); line-height: 1; }

    .content { flex: 1; padding: 16px; display: flex; flex-direction: column; gap: 14px; }
    .spacer { flex: 1; }

    textarea {
      width: 100%;
      background: rgba(255,255,255,0.04);
      border: 1px solid rgba(255,255,255,0.1);
      border-radius: 12px;
      padding: 14px;
      color: var(--cream);
      font-size: 16px;
      font-family: inherit;
      resize: none;
      line-height: 1.6;
      min-height: 130px;
    }
    textarea:focus { outline: none; border-color: rgba(201,169,110,0.4); }
    textarea::placeholder { color: var(--muted); }

    .btn {
      padding: 14px 20px;
      border-radius: 10px;
      font-size: 15px;
      font-weight: 600;
      cursor: pointer;
      border: none;
      transition: opacity 0.15s, transform 0.1s;
      width: 100%;
    }
    .btn:active { opacity: 0.75; transform: scale(0.98); }
    .btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
    .btn-gold { background: var(--gold); color: #1B1F3B; }
    .btn-ghost {
      background: rgba(255,255,255,0.05);
      color: var(--cream);
      border: 1px solid rgba(255,255,255,0.1);
    }

    .card {
      background: var(--sidebar);
      border-radius: 12px;
      padding: 14px;
      border: 1px solid rgba(201,169,110,0.12);
    }

    .section-label {
      font-size: 10px;
      letter-spacing: 1px;
      color: var(--gold);
      font-weight: 600;
      text-transform: uppercase;
      margin-bottom: 10px;
    }

    .confidence { display: flex; align-items: center; gap: 6px; }
    .dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex-shrink: 0; }

    .category-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px; }
    .cat-btn {
      padding: 10px 4px;
      border-radius: 8px;
      background: rgba(255,255,255,0.04);
      border: 1px solid rgba(255,255,255,0.08);
      color: var(--muted);
      font-size: 11px;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 4px;
      cursor: pointer;
      transition: all 0.15s;
    }
    .cat-btn.active { background: rgba(201,169,110,0.15); border-color: var(--gold); color: var(--gold); }
    .cat-icon { font-size: 15px; }

    .pills { display: flex; flex-wrap: wrap; gap: 6px; }
    .pill {
      display: flex;
      align-items: center;
      gap: 5px;
      background: rgba(255,255,255,0.06);
      border: 1px solid rgba(255,255,255,0.1);
      border-radius: 16px;
      padding: 4px 10px;
    }
    .pill-label { font-size: 9px; color: var(--gold); font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; }
    .pill-value { font-size: 12px; color: var(--cream); }

    .hint-bar {
      background: rgba(201,169,110,0.06);
      border: 1px solid rgba(201,169,110,0.2);
      border-radius: 8px;
      padding: 10px 12px;
      font-size: 12px;
      color: var(--muted);
      line-height: 1.5;
    }
    .hint-bar strong { color: var(--gold); }

    .title-input {
      width: 100%;
      background: transparent;
      border: none;
      border-bottom: 1px solid rgba(201,169,110,0.3);
      padding: 6px 2px;
      color: var(--cream);
      font-size: 16px;
      font-family: inherit;
      font-weight: 600;
    }
    .title-input:focus { outline: none; border-bottom-color: var(--gold); }

    .error-bar {
      background: rgba(192,57,43,0.1);
      border: 1px solid rgba(192,57,43,0.3);
      border-radius: 8px;
      padding: 10px 12px;
      font-size: 13px;
      color: #e57373;
    }

    .thinking {
      flex: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 12px;
    }
    .thinking-dots { display: flex; gap: 8px; }
    .thinking-dot {
      width: 10px; height: 10px;
      border-radius: 50%;
      background: var(--gold);
      opacity: 0.3;
      animation: pulse 1.2s ease-in-out infinite;
    }
    .thinking-dot:nth-child(2) { animation-delay: 0.2s; }
    .thinking-dot:nth-child(3) { animation-delay: 0.4s; }
    @keyframes pulse { 0%,100%{opacity:0.2;transform:scale(0.8)} 50%{opacity:1;transform:scale(1)} }
    .thinking-text { color: var(--muted); font-size: 14px; font-style: italic; }

    .success-wrap {
      flex: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 8px;
      padding: 40px 20px;
      text-align: center;
    }
    .success-icon { font-size: 52px; color: var(--success); line-height: 1; margin-bottom: 8px; }
    .success-title { font-size: 22px; font-weight: 700; color: var(--cream); }
    .success-cat { font-size: 14px; color: var(--muted); }
    .success-btn-wrap { padding: 16px; width: 100%; }
    .sync-banner {
      position: fixed;
      top: 0; left: 0; right: 0;
      background: rgba(76,175,125,0.95);
      color: #fff;
      padding: 10px 16px;
      font-size: 13px;
      font-weight: 600;
      text-align: center;
      z-index: 1000;
      display: none;
    }
    .mic-wrap {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 8px;
    }
    .btn-mic {
      width: 68px;
      height: 68px;
      border-radius: 50%;
      background: var(--gold);
      border: none;
      color: #1B1F3B;
      font-size: 30px;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      box-shadow: 0 4px 20px rgba(201,169,110,0.35);
      transition: transform 0.12s, box-shadow 0.12s;
      flex-shrink: 0;
      line-height: 1;
    }
    .btn-mic:active { transform: scale(0.91); }
    .btn-mic.recording {
      background: var(--danger);
      animation: micPulse 1s ease-in-out infinite;
    }
    .mic-status {
      font-size: 12px;
      color: var(--gold);
      font-style: italic;
      letter-spacing: 0.3px;
      min-height: 16px;
    }
    @keyframes micPulse {
      0%, 100% { box-shadow: 0 0 0 0 rgba(192,57,43,0.5); }
      50%       { box-shadow: 0 0 0 14px rgba(192,57,43,0); }
    }
  </style>
</head>
<body>
<div id="sync-banner" class="sync-banner"></div>

<!-- PHASE: INPUT -->
<div id="phase-input" class="phase active">
  <header>
    <div class="logo-wrap">
      <img src="/avatar.png" class="donna-avatar" alt="Donna" onerror="this.style.display='none'">
      <div class="logo">DONNA</div>
    </div>
  </header>
  <div class="content">
    <textarea id="input-text" placeholder="Was beschäftigt dich, Chef? Schreib einfach drauf los\u2026" rows="5"></textarea>
    <div id="input-error" class="error-bar" style="display:none"></div>
    <div class="spacer"></div>
    <div class="mic-wrap" id="mic-wrap">
      <button class="btn-mic" id="btn-mic" title="Spracheingabe">
        <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
          <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
          <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
          <line x1="12" y1="19" x2="12" y2="23"/>
          <line x1="8" y1="23" x2="16" y2="23"/>
        </svg>
      </button>
      <div class="mic-status" id="mic-status"></div>
    </div>
    <button class="btn btn-gold" id="btn-analyze">Analysieren</button>
  </div>
</div>

<!-- PHASE: THINKING -->
<div id="phase-thinking" class="phase">
  <header>
    <div class="logo-wrap">
      <img src="/avatar.png" class="donna-avatar" alt="Donna" onerror="this.style.display='none'">
      <div class="logo">DONNA</div>
    </div>
  </header>
  <div class="content">
    <div class="thinking">
      <div class="thinking-dots">
        <div class="thinking-dot"></div>
        <div class="thinking-dot"></div>
        <div class="thinking-dot"></div>
      </div>
      <div class="thinking-text">Donna denkt kurz nach\u2026</div>
    </div>
  </div>
</div>

<!-- PHASE: RESULT -->
<div id="phase-result" class="phase">
  <header>
    <div class="logo-wrap">
      <img src="/avatar.png" class="donna-avatar" alt="Donna" onerror="this.style.display='none'">
      <div class="logo">DONNA</div>
    </div>
    <div id="result-confidence" class="confidence"></div>
  </header>
  <div class="content">
    <div class="card">
      <div class="section-label">Titel</div>
      <input type="text" class="title-input" id="result-title" autocomplete="off" />
    </div>
    <div class="card">
      <div class="section-label">Kategorie</div>
      <div class="category-grid" id="category-grid"></div>
    </div>
    <div id="fields-card" class="card" style="display:none">
      <div class="section-label">Erkannt</div>
      <div class="pills" id="result-pills"></div>
    </div>
    <div id="hints-bar" class="hint-bar" style="display:none"></div>
    <div id="result-error" class="error-bar" style="display:none"></div>
    <div class="spacer"></div>
    <button class="btn btn-gold" id="btn-save" style="margin-bottom:10px">Speichern</button>
    <button class="btn btn-ghost" id="btn-discard">Verwerfen</button>
  </div>
</div>

<!-- PHASE: SUCCESS -->
<div id="phase-success" class="phase">
  <header>
    <div class="logo-wrap">
      <img src="/avatar.png" class="donna-avatar" alt="Donna" onerror="this.style.display='none'">
      <div class="logo">DONNA</div>
    </div>
  </header>
  <div class="success-wrap">
    <div id="success-icon" class="success-icon">\u2713</div>
    <div id="success-title" class="success-title">Gespeichert</div>
    <div class="success-cat" id="success-cat"></div>
  </div>
  <div class="success-btn-wrap">
    <button class="btn btn-gold" id="btn-new">Neuer Eintrag</button>
  </div>
</div>

<script>
  // ── OFFLINE QUEUE ────────────────────────────────────────────────────────
  const QUEUE_KEY = 'donna_queue_v1'

  function getQueue() {
    try { return JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]') } catch { return [] }
  }
  function saveQueueStore(q) { localStorage.setItem(QUEUE_KEY, JSON.stringify(q)) }

  function enqueue(payload) {
    const q = getQueue()
    q.push(Object.assign({}, payload, { _ts: Date.now() }))
    saveQueueStore(q)
  }

  async function flushQueue() {
    const q = getQueue()
    if (!q.length) return 0
    const remaining = []
    let synced = 0
    for (const item of q) {
      try {
        const body = Object.assign({}, item)
        delete body._ts
        const res = await fetch('/api/save', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(body),
        })
        const json = await res.json()
        if (json.ok) { synced++; continue }
      } catch {}
      remaining.push(item)
    }
    saveQueueStore(remaining)
    return synced
  }

  function showSyncBanner(n) {
    const banner = document.getElementById('sync-banner')
    banner.textContent = '\u2713 ' + n + (n === 1 ? ' Eintrag' : ' Eintr\u00e4ge') + ' synchronisiert'
    banner.style.display = 'block'
    setTimeout(() => { banner.style.display = 'none' }, 4000)
  }

  // Beim Laden: offline Queue synchronisieren
  window.addEventListener('load', async () => {
    const n = await flushQueue()
    if (n > 0) showSyncBanner(n)
  })

  // Alle 60 Sekunden versuchen zu synchronisieren (falls PC inzwischen online)
  setInterval(async () => {
    const n = await flushQueue()
    if (n > 0) showSyncBanner(n)
  }, 60000)

  // ── MIKROFON (MediaRecorder + Whisper) ───────────────────────────────────
  ;(function () {
    const micWrap   = document.getElementById('mic-wrap')
    const micBtn    = document.getElementById('btn-mic')
    const micStatus = document.getElementById('mic-status')

    // MediaRecorder muss verf\u00fcgbar sein (iOS Safari 14+, Chrome, Firefox)
    if (typeof window.MediaRecorder === 'undefined') {
      micWrap.style.display = 'none'
      return
    }

    const MIC_SVG  = '<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>'
    const STOP_SVG = '<svg width="26" height="26" viewBox="0 0 24 24" fill="currentColor"><rect x="5" y="5" width="14" height="14" rx="3"/></svg>'

    var mediaRecorder = null
    var chunks        = []
    var recording     = false

    function setStatus(text, clear) {
      micStatus.textContent = text
      if (clear) setTimeout(function () { micStatus.textContent = '' }, 5000)
    }

    function resetBtn() {
      recording = false
      micBtn.classList.remove('recording')
      micBtn.innerHTML = MIC_SVG
    }

    micBtn.addEventListener('click', async function () {
      if (recording) {
        mediaRecorder && mediaRecorder.stop()
        return
      }

      // Pr\u00fcfen ob Mikrofon-Zugriff m\u00f6glich (ben\u00f6tigt HTTPS au\u00dferhalb von localhost)
      if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
        setStatus('HTTPS n\u00f6tig \u2013 Cloudflare Tunnel aktivieren', true)
        return
      }

      try {
        var stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false })

        chunks = []
        var mime = window.MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
          ? 'audio/webm;codecs=opus'
          : window.MediaRecorder.isTypeSupported('audio/mp4') ? 'audio/mp4' : ''

        mediaRecorder = mime ? new window.MediaRecorder(stream, { mimeType: mime }) : new window.MediaRecorder(stream)

        mediaRecorder.ondataavailable = function (e) {
          if (e.data && e.data.size > 0) chunks.push(e.data)
        }

        mediaRecorder.onstop = async function () {
          resetBtn()
          stream.getTracks().forEach(function (t) { t.stop() })
          setStatus('Transkribiere\u2026', false)

          try {
            var blob   = new Blob(chunks, { type: mediaRecorder.mimeType || 'audio/webm' })
            var base64 = await new Promise(function (resolve, reject) {
              var reader = new FileReader()
              reader.onloadend = function () { resolve(reader.result.split(',')[1]) }
              reader.onerror   = reject
              reader.readAsDataURL(blob)
            })

            var res  = await fetch('/api/transcribe', {
              method:  'POST',
              headers: { 'Content-Type': 'application/json' },
              body:    JSON.stringify({ audio: base64, mimeType: blob.type }),
            })
            var json = await res.json()

            if (json.ok && json.text) {
              document.getElementById('input-text').value = json.text
              micStatus.textContent = ''
            } else {
              setStatus(json.error || 'Transkription fehlgeschlagen', true)
            }
          } catch (e) {
            setStatus('Verbindung zum PC verloren', true)
          }
        }

        mediaRecorder.start()
        recording = true
        micBtn.classList.add('recording')
        micBtn.innerHTML = STOP_SVG
        setStatus('Aufnahme l\u00e4uft\u2026', false)

      } catch (err) {
        if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
          setStatus('Mikrofon-Zugriff verweigert', true)
        } else if (err.name === 'SecurityError' || err.name === 'NotSupportedError') {
          setStatus('HTTPS n\u00f6tig \u2013 Cloudflare Tunnel aktivieren', true)
        } else {
          setStatus('Mikrofon nicht verf\u00fcgbar: ' + err.name, true)
        }
      }
    })
  })()

  // ── KATEGORIEN ───────────────────────────────────────────────────────────
  const CATEGORIES = [
    { value: 'idea',    label: 'Idee',     icon: '\u25ce' },
    { value: 'project', label: 'Projekt',  icon: '\u25a3' },
    { value: 'task',    label: 'Aufgabe',  icon: '\u2713' },
    { value: 'event',   label: 'Termin',   icon: '\u25f7' },
    { value: 'note',    label: 'Notiz',    icon: '\u2261' },
    { value: 'contact', label: 'Kontakt',  icon: '\u25c9' },
    { value: 'finance', label: 'Finanzen', icon: '\u25c8' },
  ]

  let currentResult = null
  let selectedCategory = ''

  function showPhase(id) {
    document.querySelectorAll('.phase').forEach(p => p.classList.remove('active'))
    document.getElementById(id).classList.add('active')
    window.scrollTo(0, 0)
  }

  // ── INPUT ────────────────────────────────────────────────────────────────
  document.getElementById('btn-analyze').addEventListener('click', analyze)
  document.getElementById('input-text').addEventListener('keydown', e => {
    if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) analyze()
  })

  async function analyze() {
    const text = document.getElementById('input-text').value.trim()
    if (!text) return
    document.getElementById('input-error').style.display = 'none'
    showPhase('phase-thinking')
    try {
      const res = await fetch('/api/classify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ text }),
      })
      const json = await res.json()
      if (!json.ok) throw new Error(json.error || 'Analysefehler')
      showResult(json.data)
    } catch (err) {
      showPhase('phase-input')
      const el = document.getElementById('input-error')
      el.textContent = err.message
      el.style.display = 'block'
    }
  }

  // ── RESULT ────────────────────────────────────────────────────────────────
  function showResult(data) {
    currentResult = data
    selectedCategory = data.category

    document.getElementById('result-title').value = data.title || ''

    const conf = Math.round(data.confidence * 100)
    const color = conf >= 75 ? '#4CAF7D' : conf >= 50 ? '#C9A96E' : '#888'
    document.getElementById('result-confidence').innerHTML =
      '<span class="dot" style="background:' + color + '"></span>' +
      '<span style="font-size:12px;color:' + color + '">' + conf + '%</span>'

    const grid = document.getElementById('category-grid')
    grid.innerHTML = ''
    CATEGORIES.forEach(cat => {
      const btn = document.createElement('button')
      btn.className = 'cat-btn' + (cat.value === selectedCategory ? ' active' : '')
      btn.innerHTML = '<span class="cat-icon">' + cat.icon + '</span><span>' + cat.label + '</span>'
      btn.addEventListener('click', () => {
        selectedCategory = cat.value
        grid.querySelectorAll('.cat-btn').forEach(b => b.classList.remove('active'))
        btn.classList.add('active')
      })
      grid.appendChild(btn)
    })

    const f = data.fields || {}
    const pills = []
    if (f.date)        pills.push({ l: 'Datum',  v: f.date })
    if (f.time)        pills.push({ l: 'Zeit',   v: f.time })
    if (f.person)      pills.push({ l: 'Person', v: f.person })
    if (f.location)    pills.push({ l: 'Ort',    v: f.location })
    if (f.goal)        pills.push({ l: 'Ziel',   v: f.goal })
    if (f.amount != null) pills.push({ l: 'Betrag', v: f.amount + ' ' + (f.currency || 'EUR') })
    if (data.tags && data.tags.length) pills.push({ l: 'Tags', v: data.tags.join(', ') })

    const pillsEl = document.getElementById('result-pills')
    const fieldsCard = document.getElementById('fields-card')
    if (pills.length) {
      pillsEl.innerHTML = pills.map(p =>
        '<div class="pill"><span class="pill-label">' + p.l + '</span><span class="pill-value">' + p.v + '</span></div>'
      ).join('')
      fieldsCard.style.display = 'block'
    } else {
      fieldsCard.style.display = 'none'
    }

    const hintsBar = document.getElementById('hints-bar')
    if (data.hints && data.hints.length) {
      hintsBar.innerHTML = '<strong>\u25c8 Donna bemerkt:</strong> ' + data.hints.join(' \u00b7 ')
      hintsBar.style.display = 'block'
    } else {
      hintsBar.style.display = 'none'
    }

    document.getElementById('result-error').style.display = 'none'
    showPhase('phase-result')
  }

  document.getElementById('btn-discard').addEventListener('click', () => {
    document.getElementById('input-text').value = ''
    showPhase('phase-input')
  })

  document.getElementById('btn-save').addEventListener('click', async () => {
    const title = document.getElementById('result-title').value.trim()
    if (!title) return
    const btn    = document.getElementById('btn-save')
    const errEl  = document.getElementById('result-error')
    errEl.style.display = 'none'
    btn.disabled = true
    btn.textContent = '\u2026'
    try {
      const payload = {
        category: selectedCategory,
        title,
        raw_input: document.getElementById('input-text').value.trim(),
        status: 'active',
        fields: currentResult ? currentResult.fields || {} : {},
        tags:   currentResult ? currentResult.tags   || [] : [],
      }

      let savedOffline = false
      try {
        const res  = await fetch('/api/save', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(payload),
        })
        const json = await res.json()
        if (!json.ok) throw new Error(json.error || 'Speicherfehler')
      } catch (fetchErr) {
        if (fetchErr instanceof TypeError) {
          // Netzwerk nicht erreichbar – PC wahrscheinlich aus
          enqueue(payload)
          savedOffline = true
        } else {
          throw fetchErr
        }
      }

      const catInfo = CATEGORIES.find(c => c.value === selectedCategory)
      const iconEl  = document.getElementById('success-icon')
      const titleEl = document.getElementById('success-title')
      const catEl   = document.getElementById('success-cat')

      if (savedOffline) {
        iconEl.textContent  = '\u23f3'
        iconEl.style.color  = 'var(--gold)'
        titleEl.textContent = 'Gespeichert (offline)'
        catEl.textContent   = '\u25cc Wird \u00fcbertragen sobald PC erreichbar ist'
      } else {
        iconEl.textContent  = '\u2713'
        iconEl.style.color  = 'var(--success)'
        titleEl.textContent = 'Gespeichert'
        catEl.textContent   = (catInfo ? catInfo.icon + ' ' : '') + 'Als ' + (catInfo ? catInfo.label : selectedCategory) + ' gespeichert'
      }
      showPhase('phase-success')
    } catch (err) {
      errEl.textContent = err.message
      errEl.style.display = 'block'
    } finally {
      btn.disabled = false
      btn.textContent = 'Speichern'
    }
  })

  // ── SUCCESS ───────────────────────────────────────────────────────────────
  document.getElementById('btn-new').addEventListener('click', async () => {
    document.getElementById('input-text').value = ''
    currentResult = null
    selectedCategory = ''
    showPhase('phase-input')
    // Im Hintergrund: offline Queue synchronisieren falls PC jetzt erreichbar
    const n = await flushQueue()
    if (n > 0) showSyncBanner(n)
  })
</script>
</body>
</html>`

// ─── SERVER ───────────────────────────────────────────────────────────────────

export function startMobileServer(): void {
  const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
    const url    = req.url    ?? '/'
    const method = req.method ?? 'GET'

    // CORS preflight
    if (method === 'OPTIONS') {
      res.writeHead(204, {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
      })
      res.end()
      return
    }

    try {
      // ── Mobile UI
      if (url === '/' && method === 'GET') {
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
        res.end(MOBILE_HTML)
        return
      }

      // ── Avatar
      if (url === '/avatar.png' && method === 'GET') {
        const buf = getAvatarBuffer()
        if (buf) {
          const ext = buf[0] === 0xff ? 'jpeg' : 'png'
          res.writeHead(200, { 'Content-Type': `image/${ext}`, 'Cache-Control': 'public, max-age=3600' })
          res.end(buf)
        } else {
          res.writeHead(404)
          res.end('Not found')
        }
        return
      }

      // ── PWA-Assets
      if (url === '/icon.svg' && method === 'GET') {
        res.writeHead(200, { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=86400' })
        res.end(ICON_SVG)
        return
      }
      if (url === '/manifest.json' && method === 'GET') {
        res.writeHead(200, { 'Content-Type': 'application/manifest+json', 'Cache-Control': 'public, max-age=86400' })
        res.end(MANIFEST_JSON)
        return
      }

      // ── Klassifikation
      if (url === '/api/classify' && method === 'POST') {
        const body = await readBody(req) as { text?: string }
        if (!body.text?.trim()) {
          sendJson(res, 400, { ok: false, error: 'Kein Text angegeben' })
          return
        }
        const settings = readSettings()
        const result = await classifyInput(body.text.trim(), settings.preferredLlm)
        sendJson(res, 200, { ok: true, data: result })
        return
      }

      // ── Speichern
      if (url === '/api/save' && method === 'POST') {
        const body = await readBody(req)
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const id = saveEntry(body as any)
        sendJson(res, 200, { ok: true, id })
        return
      }

      // ── Whisper-Transkription
      if (url === '/api/transcribe' && method === 'POST') {
        const body = await readBody(req) as { audio?: string; mimeType?: string }
        if (!body.audio) {
          sendJson(res, 400, { ok: false, error: 'Keine Audiodaten' })
          return
        }
        const text = await transcribeAudio(body.audio, body.mimeType || 'audio/webm')
        sendJson(res, 200, { ok: true, text })
        return
      }

      // ── Dashboard
      if (url === '/api/dashboard' && method === 'GET') {
        const data = getDashboard()
        sendJson(res, 200, { ok: true, data })
        return
      }

      res.writeHead(404)
      res.end('Not found')

    } catch (err) {
      sendJson(res, 500, {
        ok: false,
        error: err instanceof Error ? err.message : String(err),
      })
    }
  })

  server.on('error', (err: NodeJS.ErrnoException) => {
    if (err.code === 'EADDRINUSE') {
      console.warn(`[Donna Mobile] Port ${MOBILE_PORT} bereits belegt – Mobile UI nicht verfügbar`)
    } else {
      console.error('[Donna Mobile] Server-Fehler:', err)
    }
  })

  server.listen(MOBILE_PORT, '0.0.0.0', () => {
    console.log(`[Donna Mobile] Erreichbar unter: ${getMobileUrl()}`)
  })
}
