<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cryochamber</title>
<style>
  :root {
    --bg: #0a0e17;
    --surface: #111827;
    --surface2: #1a2236;
    --border: #1e2d4a;
    --text: #c9d1d9;
    --text-dim: #6b7b8d;
    --accent: #58a6ff;
    --accent-dim: #1a3a5c;
    --green: #3fb950;
    --red: #f85149;
    --orange: #d29922;
    --inbox-bg: #0d2137;
    --outbox-bg: #1a1a2e;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
    background: var(--bg);
    color: var(--text);
    height: 100vh;
    display: flex;
    flex-direction: column;
  }

  /* Header */
  header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 20px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
  }
  header h1 {
    font-size: 16px;
    font-weight: 600;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--accent);
  }
  header h1 span { color: var(--text-dim); }
  .status-bar {
    display: flex;
    align-items: center;
    gap: 16px;
    font-size: 12px;
  }
  .status-dot {
    width: 8px; height: 8px;
    border-radius: 50%;
    display: inline-block;
    margin-right: 6px;
  }
  .status-dot.on { background: var(--green); box-shadow: 0 0 6px var(--green); }
  .status-dot.off { background: var(--red); }
  .status-item { color: var(--text-dim); }
  .status-item strong { color: var(--text); font-weight: 500; }

  /* Main content area */
  main {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  /* Messages area */
  #messages {
    flex: 1;
    overflow-y: auto;
    padding: 16px 20px;
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  #messages::-webkit-scrollbar { width: 6px; }
  #messages::-webkit-scrollbar-track { background: transparent; }
  #messages::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }

  .msg {
    max-width: 72ch;
    padding: 10px 14px;
    border-radius: 8px;
    font-size: 13px;
    line-height: 1.5;
    border: 1px solid var(--border);
    position: relative;
  }
  .msg.inbox {
    align-self: flex-end;
    background: var(--inbox-bg);
    border-color: #1a3a5c;
  }
  .msg.outbox {
    align-self: flex-start;
    background: var(--outbox-bg);
    border-color: #2a2a4e;
  }
  .msg-meta {
    font-size: 11px;
    color: var(--text-dim);
    margin-bottom: 4px;
    display: flex;
    gap: 10px;
    align-items: center;
  }
  .msg-from {
    font-weight: 600;
    color: var(--accent);
  }
  .msg.outbox .msg-from { color: var(--orange); }
  .msg-subject {
    font-weight: 500;
    color: var(--text);
  }
  .msg-body {
    white-space: pre-wrap;
    word-break: break-word;
  }
  .msg-dir {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 1px;
    padding: 1px 5px;
    border-radius: 3px;
    font-weight: 600;
  }
  .msg.inbox .msg-dir { background: #0d3a5c; color: var(--accent); }
  .msg.outbox .msg-dir { background: #2a2a4e; color: #a78bfa; }

  /* Log area */
  #log-panel {
    border-top: 1px solid var(--border);
    background: var(--surface);
    max-height: 160px;
    overflow-y: auto;
    padding: 8px 20px;
    font-size: 11px;
    color: var(--text-dim);
    flex-shrink: 0;
    display: none;
  }
  #log-panel.visible { display: block; }
  #log-panel::-webkit-scrollbar { width: 6px; }
  #log-panel::-webkit-scrollbar-track { background: transparent; }
  #log-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
  .log-line { line-height: 1.6; font-family: inherit; }
  .hdr-toggle {
    cursor: pointer;
    font-size: 11px;
    color: var(--text-dim);
    background: none;
    border: 1px solid var(--border);
    padding: 3px 8px;
    border-radius: 4px;
  }
  .hdr-toggle:hover { color: var(--text); border-color: var(--text-dim); }

  /* Info panel */
  #info-panel {
    border-bottom: 1px solid var(--border);
    background: var(--surface);
    padding: 10px 20px;
    font-size: 12px;
    flex-shrink: 0;
    display: none;
  }
  #info-panel.visible { display: block; }
  .info-row { display: flex; gap: 8px; line-height: 1.8; }
  .info-label { color: var(--text-dim); min-width: 80px; flex-shrink: 0; }
  .info-value { color: var(--text); }
  .info-notes { list-style: none; padding: 0; margin: 0; }
  .info-notes li { color: var(--text); line-height: 1.6; }
  .info-notes li::before { content: "\2022 "; color: var(--text-dim); }

  /* Input area */
  footer {
    padding: 12px 20px;
    background: var(--surface);
    border-top: 1px solid var(--border);
    flex-shrink: 0;
  }
  .input-row {
    display: flex;
    gap: 8px;
    align-items: flex-end;
  }
  #msg-input {
    flex: 1;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 14px;
    color: var(--text);
    font-family: inherit;
    font-size: 13px;
    resize: none;
    min-height: 42px;
    max-height: 200px;
    line-height: 1.5;
    outline: none;
  }
  #msg-input:focus { border-color: var(--accent); }
  #msg-input::placeholder { color: var(--text-dim); }
  .btn {
    padding: 10px 16px;
    border: none;
    border-radius: 8px;
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    transition: opacity 0.15s;
    white-space: nowrap;
  }
  .btn:hover { opacity: 0.85; }
  .btn:active { opacity: 0.7; }
  .btn-send {
    background: var(--accent);
    color: #000;
  }
  .btn-wake {
    background: var(--orange);
    color: #000;
  }
  .btn:disabled { opacity: 0.4; cursor: not-allowed; }

  /* Empty state */
  .empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    flex: 1;
    color: var(--text-dim);
    gap: 12px;
    padding: 40px;
  }
  .empty-state .logo {
    font-size: 48px;
    opacity: 0.3;
  }
  .empty-state p { font-size: 13px; text-align: center; line-height: 1.6; }

  /* Toast */
  .toast {
    position: fixed;
    bottom: 80px;
    left: 50%;
    transform: translateX(-50%);
    background: var(--surface2);
    border: 1px solid var(--border);
    padding: 8px 16px;
    border-radius: 6px;
    font-size: 12px;
    z-index: 100;
    opacity: 0;
    transition: opacity 0.3s;
    pointer-events: none;
  }
  .toast.show { opacity: 1; }
</style>
</head>
<body>

<header>
  <h1>cryo<span>chamber</span></h1>
  <div class="status-bar">
    <div class="status-item">
      <span class="status-dot" id="status-dot"></span>
      <span id="status-text">connecting...</span>
    </div>
    <div class="status-item">session <strong id="status-session">-</strong></div>
    <div class="status-item">agent: <strong id="status-agent">-</strong></div>
    <button class="hdr-toggle" id="info-toggle">info</button>
    <button class="hdr-toggle" id="log-toggle">log</button>
  </div>
</header>

<main>
  <div id="info-panel">
    <div class="info-row"><span class="info-label">Next wake</span><span class="info-value" id="info-wake">&mdash;</span></div>
    <div class="info-row"><span class="info-label">Task</span><span class="info-value" id="info-task">&mdash;</span></div>
    <div class="info-row"><span class="info-label">Notes</span><span class="info-value" id="info-notes">&mdash;</span></div>
  </div>
  <div id="messages"></div>
  <div id="log-panel"></div>
</main>

<footer>
  <div class="input-row">
    <textarea id="msg-input" rows="1" placeholder="Send a message to the agent..."></textarea>
    <button class="btn btn-send" id="btn-send">Send</button>
    <button class="btn btn-wake" id="btn-wake">Wake</button>
  </div>
</footer>

<div class="toast" id="toast"></div>

<script>
(function() {
  const messagesEl = document.getElementById('messages');
  const logPanel = document.getElementById('log-panel');
  const logToggle = document.getElementById('log-toggle');
  const infoPanel = document.getElementById('info-panel');
  const infoToggle = document.getElementById('info-toggle');
  const infoWake = document.getElementById('info-wake');
  const infoTask = document.getElementById('info-task');
  const infoNotes = document.getElementById('info-notes');
  const input = document.getElementById('msg-input');
  const btnSend = document.getElementById('btn-send');
  const btnWake = document.getElementById('btn-wake');
  const statusDot = document.getElementById('status-dot');
  const statusText = document.getElementById('status-text');
  const statusSession = document.getElementById('status-session');
  const statusAgent = document.getElementById('status-agent');
  const toastEl = document.getElementById('toast');

  let infoLoaded = false;
  let infoHidden = false;
  let knownKeys = new Set();
  let logTailLoaded = false;
  let knownLogLines = new Set();

  function toast(msg) {
    toastEl.textContent = msg;
    toastEl.classList.add('show');
    setTimeout(() => toastEl.classList.remove('show'), 2500);
  }

  function escapeHtml(s) {
    const d = document.createElement('div');
    d.textContent = s;
    return d.innerHTML;
  }

  function msgKey(msg) {
    return msg.timestamp + '|' + msg.direction + '|' + msg.from + '|' + msg.body;
  }

  function renderMsg(msg) {
    const div = document.createElement('div');
    div.className = 'msg ' + msg.direction;
    const time = msg.timestamp ? msg.timestamp.replace('T', ' ') : '';
    div.innerHTML =
      '<div class="msg-meta">' +
        '<span class="msg-dir">' + escapeHtml(msg.direction) + '</span>' +
        '<span class="msg-from">' + escapeHtml(msg.from) + '</span>' +
        (msg.subject ? '<span class="msg-subject">' + escapeHtml(msg.subject) + '</span>' : '') +
        '<span>' + escapeHtml(time) + '</span>' +
      '</div>' +
      '<div class="msg-body">' + escapeHtml(msg.body) + '</div>';
    return div;
  }

  function scrollToBottom() {
    messagesEl.scrollTop = messagesEl.scrollHeight;
  }

  function addMessage(msg) {
    const key = msgKey(msg);
    if (knownKeys.has(key)) return;
    knownKeys.add(key);
    clearEmpty();
    messagesEl.appendChild(renderMsg(msg));
    scrollToBottom();
  }

  function showEmpty() {
    if (messagesEl.children.length === 0) {
      messagesEl.innerHTML =
        '<div class="empty-state">' +
          '<div class="logo">&#x2744;</div>' +
          '<p>No messages yet.<br>Send a message to the agent, or wake it up.</p>' +
        '</div>';
    }
  }

  function clearEmpty() {
    const empty = messagesEl.querySelector('.empty-state');
    if (empty) empty.remove();
  }

  // Load status
  async function loadStatus() {
    try {
      const res = await fetch('/api/status');
      const data = await res.json();
      const running = data.running;
      statusDot.className = 'status-dot ' + (running ? 'on' : 'off');
      statusText.textContent = running ? 'running' : 'stopped';
      statusSession.textContent = data.session || '0';
      statusAgent.textContent = data.agent || '-';

      // Update info panel
      if (!infoLoaded && !infoHidden) {
        infoLoaded = true;
        infoPanel.classList.add('visible');
        infoToggle.textContent = 'hide info';
      }
      infoWake.textContent = data.next_wake || '\u2014';
      infoTask.textContent = data.task || '\u2014';
      if (data.notes && data.notes.length > 0) {
        var ul = document.createElement('ul');
        ul.className = 'info-notes';
        data.notes.forEach(function(n) {
          var li = document.createElement('li');
          li.textContent = n;
          ul.appendChild(li);
        });
        infoNotes.innerHTML = '';
        infoNotes.appendChild(ul);
      } else {
        infoNotes.textContent = '\u2014';
      }

      // Show log tail only on first load
      if (!logTailLoaded && data.log_tail) {
        logTailLoaded = true;
        data.log_tail.split('\n').forEach(function(line) {
          if (line.trim()) addLogLine(line);
        });
      }
    } catch(e) {
      statusDot.className = 'status-dot off';
      statusText.textContent = 'error';
    }
  }

  // Load messages
  async function loadMessages() {
    try {
      const res = await fetch('/api/messages');
      const msgs = await res.json();
      msgs.forEach(addMessage);
      if (msgs.length === 0) showEmpty();
    } catch(e) {
      // ignore
    }
  }

  // Send message
  async function sendMessage() {
    const body = input.value.trim();
    if (!body) return;
    btnSend.disabled = true;
    input.value = '';
    autoResize();
    try {
      const res = await fetch('/api/send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ body: body })
      });
      const data = await res.json();
      if (data.ok) {
        toast('Message sent');
        loadMessages();
      } else {
        toast('Error: ' + data.message);
      }
    } catch(e) {
      toast('Network error');
    }
    btnSend.disabled = false;
    input.focus();
  }

  // Wake
  async function wake() {
    btnWake.disabled = true;
    try {
      const res = await fetch('/api/wake', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({})
      });
      const data = await res.json();
      toast(data.message || 'Wake sent');
    } catch(e) {
      toast('Network error');
    }
    btnWake.disabled = false;
  }

  // Log
  function addLogLine(line) {
    if (knownLogLines.has(line)) return;
    knownLogLines.add(line);
    const div = document.createElement('div');
    div.className = 'log-line';
    div.textContent = line;
    logPanel.appendChild(div);
    logPanel.scrollTop = logPanel.scrollHeight;
  }

  infoToggle.addEventListener('click', function() {
    infoPanel.classList.toggle('visible');
    var vis = infoPanel.classList.contains('visible');
    infoHidden = !vis;
    infoToggle.textContent = vis ? 'hide info' : 'info';
  });

  logToggle.addEventListener('click', function() {
    logPanel.classList.toggle('visible');
    logToggle.textContent = logPanel.classList.contains('visible') ? 'hide log' : 'log';
  });

  // SSE
  function connectSSE() {
    const es = new EventSource('/api/events');
    es.addEventListener('message', function(e) {
      try {
        const msg = JSON.parse(e.data);
        clearEmpty();
        addMessage(msg);
      } catch(err) {}
    });
    es.addEventListener('status', function() {
      loadStatus();
    });
    es.addEventListener('log', function(e) {
      try {
        const data = JSON.parse(e.data);
        addLogLine(data.line);
      } catch(err) {}
    });
    es.onerror = function() {
      // Auto-reconnect is built into EventSource
    };
  }

  // Auto-resize textarea
  function autoResize() {
    input.style.height = 'auto';
    input.style.height = Math.min(input.scrollHeight, 200) + 'px';
  }
  input.addEventListener('input', autoResize);
  input.addEventListener('keydown', function(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      sendMessage();
    }
  });

  btnSend.addEventListener('click', sendMessage);
  btnWake.addEventListener('click', wake);

  // Poll for new messages and status as a fallback (SSE may miss
  // messages written by the daemon via atomic rename).
  setInterval(loadMessages, 3000);
  setInterval(loadStatus, 5000);

  // Init
  loadStatus();
  loadMessages();
  connectSSE();
})();
</script>
</body>
</html>
