  // onClose (optional) runs on every dismiss path — close button, backdrop
  // click, and Esc — so callers can attach a side effect (e.g. persisting a
  // "seen" flag for a first-visit popup) without re-wiring the listeners.
  function bindModalClose(modalId, closeBtnId, onClose) {
    var modal = document.getElementById(modalId);
    if (!modal) return;
    function close() { modal.classList.remove('open'); if (onClose) onClose(); }
    var closeBtn = document.getElementById(closeBtnId);
    if (closeBtn) closeBtn.addEventListener('click', close);
    modal.addEventListener('click', function(ev) { if (ev.target === modal) close(); });
    document.addEventListener('keydown', function(ev) {
      if (ev.key === 'Escape' && modal.classList.contains('open')) close();
    });
  }
  bindModalClose('slim-agent-modal', 'slim-agent-close');
  bindModalClose('slim-tools-modal', 'slim-tools-close');
  bindModalClose('slim-tile-modal', 'slim-tile-close');

  function tileEmpty(body, text) {
    var d = document.createElement('div');
    d.className = 'tile-empty';
    d.textContent = text;
    body.appendChild(d);
  }

  // One list row for the tile detail modal. opts: { title, meta, chip:{text,cls}, href }.
  // Built as an <a> when href is present so PR rows open the PR in a new tab.
  function tileCard(opts) {
    var card = document.createElement(opts.href ? 'a' : 'div');
    card.className = 'tile-item';
    if (opts.href) { card.href = opts.href; card.target = '_blank'; card.rel = 'noopener'; }
    var top = document.createElement('div');
    top.className = 'tile-item-top';
    var title = document.createElement('span');
    title.className = 'tile-item-title';
    title.textContent = opts.title;
    title.title = opts.title;
    top.appendChild(title);
    if (opts.chip && opts.chip.text) {
      var chip = document.createElement('span');
      chip.className = 'tile-chip ' + (opts.chip.cls || '');
      chip.textContent = opts.chip.text;
      top.appendChild(chip);
    }
    card.appendChild(top);
    if (opts.meta) {
      var meta = document.createElement('div');
      meta.className = 'tile-item-meta';
      meta.textContent = opts.meta;
      card.appendChild(meta);
    }
    return card;
  }

  // Per-tile body renderers for the detail modal. Each reads the latest status
  // snapshot and fills the modal body; openTileModal looks them up by key.
  function renderEngineTileBody(body, data) {
    var e = data.engine || {};
    appendAgentRow(body, 'State', e.state || '—', !e.state);
    appendAgentRow(body, 'Version', e.codeVersion || '—', !e.codeVersion);
    appendAgentRow(body, 'PID', e.pid != null ? String(e.pid) : '—', e.pid == null);
    appendAgentRow(body, 'Started', e.started_at ? (relTime(e.started_at) + ' · ' + new Date(e.started_at).toLocaleString()) : '—', !e.started_at);
    appendAgentRow(body, 'Last tick', e.lastTickAt ? relTime(e.lastTickAt) : '—', !e.lastTickAt);
    appendAgentRow(body, 'Tick interval', e.tickInterval ? Math.round(e.tickInterval / 1000) + 's' : '—', !e.tickInterval);
    appendAgentRow(body, 'Worktrees', e.worktreeCount != null ? String(e.worktreeCount) : '—', e.worktreeCount == null);
    appendAgentRow(body, 'Heartbeat', e.heartbeatStale ? 'stale' : 'ok', false);
  }

  function renderDispatchTileBody(body, data, isActive) {
    var dispatch = data.dispatch || {};
    var dlist = Array.isArray(dispatch[isActive ? 'active' : 'pending']) ? dispatch[isActive ? 'active' : 'pending'] : [];
    if (!dlist.length) { tileEmpty(body, isActive ? 'Nothing in flight.' : 'Queue is empty.'); return; }
    dlist.forEach(function(d) {
      var who = d.agentName || d.agent || d.dispatched_to || '—';
      var task = (d.meta && d.meta.item && d.meta.item.title) || d.task || d.title || d.workItemTitle || d.id || '(untitled)';
      var when = d.started_at || d.startedAt || d.created_at || d.createdAt;
      body.appendChild(tileCard({
        title: task,
        meta: who + (when ? ' · ' + (isActive ? 'started ' : 'queued ') + relTime(when) : ''),
        chip: { text: d.type || 'task', cls: isActive ? 'blue' : 'amber' },
      }));
    });
  }

  function renderPrTileBody(body, data) {
    var prs = Array.isArray(data.pullRequests) ? data.pullRequests : [];
    if (!prs.length) { tileEmpty(body, 'No tracked pull requests.'); return; }
    prs.forEach(function(p) {
      var num = p.prNumber || p.number || p.id;
      var build = p.buildStatus || '';
      var cls = p.status === 'merged' ? 'green'
              : (build === 'failing' ? 'red'
              : ((p.status === 'active' || p.status === 'linked') ? 'blue' : ''));
      body.appendChild(tileCard({
        title: (num ? '#' + num + ' ' : '') + (p.title || '(untitled PR)'),
        meta: [p.status || 'unknown', build ? 'build ' + build : 'no build', p.reviewStatus ? 'review ' + p.reviewStatus : null].filter(Boolean).join(' · '),
        chip: { text: p.status || '—', cls: cls },
        href: p.url || null,
      }));
    });
  }

  function renderWatchTileBody(body, data) {
    var watches = Array.isArray(data.watches) ? data.watches : [];
    if (!watches.length) { tileEmpty(body, 'No watches configured.'); return; }
    watches.forEach(function(w) {
      var st = w.status || '';
      var cls = st === 'active' ? 'blue' : (st === 'triggered' ? 'green' : (st === 'expired' ? 'red' : 'amber'));
      body.appendChild(tileCard({
        title: (w.targetType ? w.targetType + ': ' : '') + (w.target || w.id || '(target)'),
        meta: [w.condition ? 'when ' + w.condition : null, w.interval ? 'every ' + w.interval + ' ticks' : null].filter(Boolean).join(' · '),
        chip: { text: st || '—', cls: cls },
      }));
    });
  }

  // tile key -> { title, render }. Adding a tile is a one-line table edit.
  var TILE_VIEWS = {
    engine:     { title: 'Engine',            render: renderEngineTileBody },
    dispatches: { title: 'Active dispatches', render: function(body, data) { renderDispatchTileBody(body, data, true); } },
    queued:     { title: 'Queued work',       render: function(body, data) { renderDispatchTileBody(body, data, false); } },
    prs:        { title: 'Pull requests',     render: renderPrTileBody },
    watches:    { title: 'Watches',           render: renderWatchTileBody },
  };

  // Populate + open the cockpit-tile detail modal from the latest status
  // snapshot. Mirrors the corresponding old-dashboard tab for each tile.
  function openTileModal(key) {
    // The Pinned-context tile has its own list/editor modal (view + edit + unpin)
    // rather than the read-only tile detail view.
    if (key === 'pinned') { openSlimPinnedList(); return; }
    var view = TILE_VIEWS[key];
    if (!view) return;
    var modal = document.getElementById('slim-tile-modal');
    var titleEl = document.getElementById('slim-tile-title');
    var body = document.getElementById('slim-tile-body');
    if (!modal || !body) return;
    body.textContent = '';
    if (titleEl) titleEl.textContent = view.title;
    // The "+ Link PR" header chip only applies to the PR list.
    var headerChip = document.getElementById('slim-tile-modal-linkpr');
    if (headerChip) headerChip.style.display = (key === 'prs') ? '' : 'none';
    view.render(body, lastStatusData || {});
    modal.classList.add('open');
  }

  // Make the cockpit tiles clickable / keyboard-activatable.
  (function bindCockpitTiles() {
    var grid = document.getElementById('cockpit-grid');
    if (!grid) return;
    var tiles = grid.querySelectorAll('.cockpit-tile');
    for (var i = 0; i < tiles.length; i++) {
      (function(tile) {
        var key = tile.getAttribute('data-tile');
        if (!key) return;
        tile.setAttribute('role', 'button');
        tile.setAttribute('tabindex', '0');
        tile.title = 'View details';
        tile.addEventListener('click', function() { openTileModal(key); });
        tile.addEventListener('keydown', function(ev) {
          if (ev.target !== tile) return;   // ignore keys from inner controls (e.g. the Link-PR chip)
          if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); openTileModal(key); }
        });
      })(tiles[i]);
    }
  })();

