<!doctype html>
<html lang="de">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Wecker Remote Management</title>
  <style>
    :root {
      color-scheme: dark;
      --bg: #111827;
      --surface: #1f2937;
      --surface-2: #374151;
      --text: #f9fafb;
      --ok: #22c55e;
      --warn: #f59e0b;
      --error: #ef4444;
      --accent: #3b82f6;
      --muted: #9ca3af;
    }
    * { box-sizing: border-box; }
    body {
      margin: 0;
      font-family: Arial, sans-serif;
      background: var(--bg);
      color: var(--text);
      padding: 16px;
    }
    .wrap {
      max-width: 1200px;
      margin: 0 auto;
      display: grid;
      gap: 16px;
    }
    .card {
      background: var(--surface);
      border: 1px solid #4b5563;
      border-radius: 10px;
      padding: 14px;
    }
    h1, h2 {
      margin: 0 0 10px;
      font-weight: 700;
    }
    h1 { font-size: 1.4rem; }
    h2 { font-size: 1.1rem; }
    .row {
      display: flex;
      gap: 10px;
      flex-wrap: wrap;
      align-items: center;
    }
    label {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      font-size: 0.95rem;
    }
    input, button {
      border-radius: 8px;
      border: 1px solid #6b7280;
      background: var(--surface-2);
      color: var(--text);
      padding: 8px 10px;
      font-size: 0.95rem;
    }
    select {
      border-radius: 8px;
      border: 1px solid #6b7280;
      background: var(--surface-2);
      color: var(--text);
      padding: 8px 10px;
      font-size: 0.95rem;
    }
    input[type="number"] {
      width: 90px;
    }
    input[type="time"] {
      width: 140px;
    }
    input[type="text"] {
      min-width: 260px;
    }
    button {
      cursor: pointer;
      border-color: #4b5563;
    }
    button:hover {
      border-color: var(--accent);
    }
    .primary {
      background: var(--accent);
      border-color: transparent;
    }
    .danger {
      background: var(--error);
      border-color: transparent;
    }
    .status {
      font-size: 0.9rem;
      padding: 4px 10px;
      border-radius: 999px;
      border: 1px solid #4b5563;
      display: inline-flex;
      align-items: center;
      gap: 6px;
    }
    .status-dot {
      width: 8px;
      height: 8px;
      border-radius: 999px;
      background: #9ca3af;
    }
    .status.connected .status-dot { background: var(--ok); }
    .status.connecting .status-dot { background: var(--warn); }
    .status.disconnected .status-dot { background: var(--error); }
    table {
      width: 100%;
      border-collapse: collapse;
      font-size: 0.95rem;
    }
    th, td {
      text-align: left;
      border-bottom: 1px solid #4b5563;
      padding: 8px 6px;
    }
    .mono {
      font-family: Consolas, monospace;
    }
    .muted {
      color: var(--muted);
      font-size: 0.9rem;
    }
    .devices-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
      gap: 10px;
    }
    .device-tile {
      border: 1px solid #4b5563;
      border-radius: 10px;
      padding: 12px;
      background: #111827;
      cursor: pointer;
      transition: border-color 0.2s ease, transform 0.2s ease;
    }
    .device-tile:hover {
      border-color: var(--accent);
      transform: translateY(-1px);
    }
    .device-tile.active {
      border-color: var(--accent);
      box-shadow: 0 0 0 1px rgba(59,130,246,0.3) inset;
    }
    .device-title {
      margin: 0 0 6px;
      font-size: 1rem;
      display: flex;
      justify-content: space-between;
      gap: 8px;
      align-items: center;
    }
    .chip {
      font-size: 0.75rem;
      border-radius: 999px;
      padding: 2px 8px;
      border: 1px solid #4b5563;
      color: var(--muted);
    }
    .chip.online {
      border-color: rgba(34,197,94,0.45);
      color: #86efac;
    }
    .chip.offline {
      border-color: rgba(239,68,68,0.45);
      color: #fca5a5;
    }
    .detail-grid {
      display: grid;
      grid-template-columns: 1.1fr 1fr;
      gap: 12px;
    }
    iframe {
      width: 100%;
      height: 520px;
      border: 1px solid #4b5563;
      border-radius: 8px;
      background: #0b1220;
    }
    @media (max-width: 920px) {
      .detail-grid {
        grid-template-columns: 1fr;
      }
    }
    #log {
      margin: 0;
      max-height: 200px;
      overflow: auto;
      background: #0b1220;
      border: 1px solid #4b5563;
      border-radius: 8px;
      padding: 10px;
      font-family: Consolas, monospace;
      font-size: 0.85rem;
      line-height: 1.4;
      white-space: pre-wrap;
    }
  </style>
</head>
<body>
  <main class="wrap">
    <section class="card">
      <h1>Wecker Remote Management</h1>
      <div class="row" style="margin-bottom: 10px;">
        <span id="connState" class="status disconnected">
          <span class="status-dot"></span>
          <span id="connText">Getrennt</span>
        </span>
        <span class="muted">Mehrere Wecker melden sich beim NJS-Server und erscheinen als Kachel.</span>
      </div>
      <div class="row">
        <label>WebSocket URL:
          <input id="wsUrl" type="text" style="min-width: 360px;">
        </label>
        <button id="btnConnect" class="primary">Verbinden</button>
        <button id="btnRefresh">Aktualisieren</button>
      </div>
    </section>

    <section class="card">
      <h2>Geräte</h2>
      <div id="devicesGrid" class="devices-grid">
        <div class="muted">Noch keine Wecker gemeldet.</div>
      </div>
    </section>

    <section class="card">
      <h2>Geräte-Details</h2>
      <div id="deviceHint" class="muted">Wähle eine Kachel aus, um Wecker und Musik zu steuern.</div>
      <div id="deviceDetail" style="display:none;">
        <div class="detail-grid">
          <div>
            <h3 id="selectedDeviceName" style="margin-top:0;"></h3>
            <div class="row" style="margin-bottom:10px;">
              <button id="btnStop" class="danger">Alarm stoppen</button>
              <label>Snooze (Minuten):
                <input id="snoozeMinutes" type="number" min="1" max="60" value="5">
              </label>
              <button id="btnSnooze">Snooze senden</button>
            </div>
            <div id="alarmInfo" class="muted" style="margin-bottom:10px;">Noch keine Daten.</div>
            <table style="margin-bottom:12px;">
              <thead>
                <tr>
                  <th>ID</th>
                  <th>Zeit</th>
                  <th>Wiederholung</th>
                  <th>Status</th>
                  <th>Aktion</th>
                </tr>
              </thead>
              <tbody id="alarmTable">
                <tr><td colspan="5" class="muted">Keine Daten geladen.</td></tr>
              </tbody>
            </table>
            <div class="card" style="padding:10px; margin:0;">
              <h3 style="margin-top:0;">Wecker einstellen (Quick)</h3>
              <div class="row">
                <label>ID (leer = neu):
                  <input id="formAlarmId" type="number" min="0" value="">
                </label>
                <label>Uhrzeit:
                  <input id="formAlarmZeit" type="time" value="07:00">
                </label>
                <label>Wiederholung:
                  <select id="formAlarmRepeat">
                    <option value="taeglich">Täglich</option>
                    <option value="einmal">Einmal</option>
                    <option value="woechentlich">Wöchentlich</option>
                  </select>
                </label>
              </div>
              <div class="row" style="margin-top:8px;">
                <label>Sprachtext:
                  <input id="formAlarmText" type="text" value="Guten Morgen!">
                </label>
                <button id="btnSaveAlarm" class="primary">Speichern</button>
              </div>
            </div>
          </div>
          <div>
            <h3 style="margin-top:0;">Musik</h3>
            <div class="row" style="margin-bottom:8px;">
              <button id="btnLoadMusic">Musik laden</button>
              <a id="openUiLink" href="#" target="_blank" rel="noopener noreferrer" class="muted">Volle Wecker-UI öffnen</a>
            </div>
            <div id="musicList" class="muted">Noch keine Musikdaten geladen.</div>
            <div style="margin-top:10px;">
              <iframe id="deviceFrame" src="about:blank" title="Geräte UI"></iframe>
            </div>
          </div>
        </div>
      </div>
    </section>

    <section class="card">
      <h2>Log</h2>
      <pre id="log"></pre>
    </section>
  </main>

  <script>
    const els = {
      wsUrl: document.getElementById('wsUrl'),
      connState: document.getElementById('connState'),
      connText: document.getElementById('connText'),
      btnConnect: document.getElementById('btnConnect'),
      btnRefresh: document.getElementById('btnRefresh'),
      devicesGrid: document.getElementById('devicesGrid'),
      deviceHint: document.getElementById('deviceHint'),
      deviceDetail: document.getElementById('deviceDetail'),
      selectedDeviceName: document.getElementById('selectedDeviceName'),
      btnStop: document.getElementById('btnStop'),
      btnSnooze: document.getElementById('btnSnooze'),
      btnSaveAlarm: document.getElementById('btnSaveAlarm'),
      btnLoadMusic: document.getElementById('btnLoadMusic'),
      snoozeMinutes: document.getElementById('snoozeMinutes'),
      formAlarmId: document.getElementById('formAlarmId'),
      formAlarmZeit: document.getElementById('formAlarmZeit'),
      formAlarmRepeat: document.getElementById('formAlarmRepeat'),
      formAlarmText: document.getElementById('formAlarmText'),
      alarmInfo: document.getElementById('alarmInfo'),
      alarmTable: document.getElementById('alarmTable'),
      musicList: document.getElementById('musicList'),
      openUiLink: document.getElementById('openUiLink'),
      deviceFrame: document.getElementById('deviceFrame'),
      log: document.getElementById('log')
    };

    const state = {
      socket: null,
      reconnectTimer: null,
      manualDisconnect: false,
      devices: [],
      selectedDeviceId: null,
      deviceStateById: {},
      musicByDeviceId: {}
    };

    function defaultWsUrl() {
      const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
      const host = 'njs.piclock.cylone-network.com';
      return `${proto}//${host}/ws`;
    }

    function appendLog(msg) {
      const ts = new Date().toLocaleTimeString('de-DE');
      els.log.textContent = `[${ts}] ${msg}\n` + els.log.textContent;
    }

    function setConnectionStatus(mode, text) {
      els.connState.className = `status ${mode}`;
      els.connText.textContent = text;
    }

    function send(type, payload = {}) {
      if (!state.socket || state.socket.readyState !== WebSocket.OPEN) {
        appendLog('Senden fehlgeschlagen: keine Verbindung');
        return;
      }
      state.socket.send(JSON.stringify({ type, ...payload }));
    }

    function selectedDevice() {
      return state.devices.find((d) => d.id === state.selectedDeviceId) || null;
    }

    function requestSelectedDeviceState() {
      if (!state.selectedDeviceId) return;
      send('get_device_state', { deviceId: state.selectedDeviceId });
    }

    function renderDevices() {
      if (!Array.isArray(state.devices) || state.devices.length === 0) {
        els.devicesGrid.innerHTML = '<div class="muted">Noch keine Wecker gemeldet.</div>';
        state.selectedDeviceId = null;
        els.deviceHint.style.display = 'block';
        els.deviceDetail.style.display = 'none';
        return;
      }

      if (!state.selectedDeviceId || !state.devices.some((d) => d.id === state.selectedDeviceId)) {
        state.selectedDeviceId = state.devices[0].id;
      }

      const html = state.devices.map((device) => {
        const active = device.id === state.selectedDeviceId ? ' active' : '';
        const onlineClass = device.online ? 'online' : 'offline';
        const onlineText = device.online ? 'online' : 'offline';
        return `
          <article class="device-tile${active}" data-device-id="${device.id}">
            <h3 class="device-title">
              <span>${escapeHtml(device.name || device.id)}</span>
              <span class="chip ${onlineClass}">${onlineText}</span>
            </h3>
            <div class="muted mono">${escapeHtml(device.id)}</div>
            <div class="muted">${escapeHtml(device.uiUrl || '-')}</div>
          </article>
        `;
      }).join('');

      els.devicesGrid.innerHTML = html;
      els.devicesGrid.querySelectorAll('[data-device-id]').forEach((tile) => {
        tile.addEventListener('click', () => {
          state.selectedDeviceId = tile.getAttribute('data-device-id');
          renderDevices();
          renderSelectedDevice();
          requestSelectedDeviceState();
        });
      });
      renderSelectedDevice();
    }

    function renderAlarmsForSelected() {
      const selected = selectedDevice();
      const payload = selected ? state.deviceStateById[selected.id] : null;
      const alarms = Array.isArray(payload?.list?.wecker) ? payload.list.wecker : [];
      if (alarms.length === 0) {
        els.alarmTable.innerHTML = '<tr><td colspan="5" class="muted">Keine Wecker gefunden.</td></tr>';
        return;
      }

      const rows = alarms.map((alarm) => {
        const id = Number(alarm.id || 0);
        const active = Number(alarm.aktiv || 0) === 1;
        const zeit = String(alarm.weckzeit || '').slice(0, 5);
        const wiederholung = String(alarm.wiederholung || '-');
        const statusText = active ? 'Aktiv' : 'Inaktiv';
        const btnText = active ? 'Deaktivieren' : 'Aktivieren';
        const nextActive = active ? 0 : 1;
        return `
          <tr>
            <td class="mono">${id}</td>
            <td class="mono">${zeit || '--:--'}</td>
            <td>${wiederholung}</td>
            <td>${statusText}</td>
            <td>
              <button data-toggle-id="${id}" data-toggle-active="${nextActive}">${btnText}</button>
            </td>
          </tr>
        `;
      }).join('');

      els.alarmTable.innerHTML = rows;
      els.alarmTable.querySelectorAll('button[data-toggle-id]').forEach((btn) => {
        btn.addEventListener('click', () => {
          if (!state.selectedDeviceId) return;
          const id = Number(btn.getAttribute('data-toggle-id'));
          const aktiv = Number(btn.getAttribute('data-toggle-active')) === 1;
          send('toggle', { deviceId: state.selectedDeviceId, id, aktiv });
        });
      });
    }

    function renderAlarmStateForSelected() {
      const selected = selectedDevice();
      const alarm = selected ? state.deviceStateById[selected.id]?.alarmState : null;
      if (!alarm) {
        els.alarmInfo.textContent = 'Noch keine Daten.';
        return;
      }

      const ringing = alarm.ringing ? 'Ja' : 'Nein';
      const id = alarm.alarm_id ?? '-';
      const zeit = alarm.alarm_time ?? '-';
      const text = alarm.tts_text || '-';
      els.alarmInfo.innerHTML = `
        <div><strong>Klingelt:</strong> ${ringing}</div>
        <div><strong>Alarm-ID:</strong> <span class="mono">${id}</span></div>
        <div><strong>Zeit:</strong> <span class="mono">${zeit}</span></div>
        <div><strong>TTS:</strong> ${text}</div>
      `;
    }

    function renderMusicForSelected() {
      const selected = selectedDevice();
      if (!selected) {
        els.musicList.textContent = 'Kein Gerät ausgewählt.';
        return;
      }
      const musicPayload = state.musicByDeviceId[selected.id];
      const files = Array.isArray(musicPayload?.music?.files) ? musicPayload.music.files : [];
      if (files.length === 0) {
        els.musicList.innerHTML = '<span class="muted">Keine Musikdateien gefunden oder noch nicht geladen.</span>';
        return;
      }
      const lines = files.slice(0, 40).map((f) => `- ${escapeHtml(f.titel || f.name || 'Unbenannt')}`);
      els.musicList.innerHTML = `<div class="muted">Gefundene Titel: ${files.length}</div><pre>${lines.join('\n')}</pre>`;
    }

    function renderSelectedDevice() {
      const device = selectedDevice();
      if (!device) {
        els.deviceHint.style.display = 'block';
        els.deviceDetail.style.display = 'none';
        return;
      }
      els.deviceHint.style.display = 'none';
      els.deviceDetail.style.display = 'block';
      els.selectedDeviceName.textContent = `${device.name || device.id} (${device.id})`;
      els.openUiLink.href = device.uiUrl || '#';
      els.deviceFrame.src = device.uiUrl || 'about:blank';
      renderAlarmStateForSelected();
      renderAlarmsForSelected();
      renderMusicForSelected();
    }

    function escapeHtml(input) {
      return String(input ?? '')
        .replaceAll('&', '&amp;')
        .replaceAll('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;')
        .replaceAll("'", '&#039;');
    }

    function connect() {
      const url = (els.wsUrl.value || '').trim();
      if (!url) {
        appendLog('Bitte WebSocket-URL angeben.');
        return;
      }

      if (state.socket && state.socket.readyState === WebSocket.OPEN) {
        state.manualDisconnect = true;
        state.socket.close();
      }

      setConnectionStatus('connecting', 'Verbinde...');
      appendLog(`Verbinde zu ${url}`);

      const ws = new WebSocket(url);
      state.socket = ws;
      state.manualDisconnect = false;

      ws.addEventListener('open', () => {
        setConnectionStatus('connected', 'Verbunden');
        appendLog('Verbindung hergestellt.');
        send('get_devices');
      });

      ws.addEventListener('message', (event) => {
        let data;
        try {
          data = JSON.parse(event.data);
        } catch {
          appendLog(`Ungueltige Nachricht: ${event.data}`);
          return;
        }

        if (data.type === 'devices') {
          state.devices = Array.isArray(data.payload?.devices) ? data.payload.devices : [];
          renderDevices();
          return;
        }

        if (data.type === 'device_state') {
          const devId = data.payload?.device?.id || state.selectedDeviceId;
          if (devId) {
            state.deviceStateById[devId] = data.payload;
            renderSelectedDevice();
          }
          return;
        }

        if (data.type === 'device_music') {
          const devId = data.payload?.deviceId;
          if (devId) {
            state.musicByDeviceId[devId] = data.payload;
            renderMusicForSelected();
          }
          return;
        }

        if (data.type === 'action_result') {
          const deviceInfo = data.deviceId ? ` auf ${data.deviceId}` : '';
          appendLog(`Aktion "${data.action}" erfolgreich${deviceInfo}.`);
          return;
        }

        if (data.type === 'error') {
          appendLog(`Fehler: ${data.message || 'unbekannt'}`);
          return;
        }

        if (data.type === 'connected') {
          appendLog(`Server: ${data.payload?.message || 'verbunden'}`);
          return;
        }

        appendLog(`Info: ${JSON.stringify(data)}`);
      });

      ws.addEventListener('close', () => {
        setConnectionStatus('disconnected', 'Getrennt');
        appendLog('Verbindung geschlossen.');
        if (!state.manualDisconnect) {
          clearTimeout(state.reconnectTimer);
          state.reconnectTimer = setTimeout(connect, 2000);
        }
      });

      ws.addEventListener('error', () => {
        appendLog('WebSocket Fehler.');
      });
    }

    els.wsUrl.value = defaultWsUrl();

    els.btnConnect.addEventListener('click', connect);
    els.btnRefresh.addEventListener('click', () => {
      send('get_devices');
      requestSelectedDeviceState();
    });
    els.btnStop.addEventListener('click', () => {
      if (!state.selectedDeviceId) return;
      send('stop_alarm', { deviceId: state.selectedDeviceId });
    });
    els.btnSnooze.addEventListener('click', () => {
      if (!state.selectedDeviceId) return;
      const minutes = Number(els.snoozeMinutes.value || 5);
      send('snooze', { deviceId: state.selectedDeviceId, minutes });
    });
    els.btnLoadMusic.addEventListener('click', () => {
      if (!state.selectedDeviceId) return;
      send('music_list', { deviceId: state.selectedDeviceId });
    });
    els.btnSaveAlarm.addEventListener('click', () => {
      if (!state.selectedDeviceId) return;
      const alarmId = Number(els.formAlarmId.value || 0);
      const weckzeit = String(els.formAlarmZeit.value || '').trim();
      const wiederholung = String(els.formAlarmRepeat.value || 'taeglich');
      const sprachtext = String(els.formAlarmText.value || '').trim();
      if (!weckzeit || !sprachtext) {
        appendLog('Bitte Uhrzeit und Sprachtext für den Wecker angeben.');
        return;
      }
      send('save_alarm', {
        deviceId: state.selectedDeviceId,
        id: alarmId > 0 ? alarmId : 0,
        weckzeit,
        wiederholung,
        sprachtext,
        aktiv: true
      });
    });

    connect();
  </script>
</body>
</html>
