// ─────────────────────────────────────────────────────────────
// admin/admin.js — pulls invites + joins from the gated
// /api/fenjaops endpoints and renders them. Also wires up the
// "Invite a new user" form (POST /api/fenjaops/invites), which
// only creates non-admin invites. Promoting to admin stays on
// the CLI (bin/invite.js admin add) by design.
//
// Note: the public URL path is `/fenjaops` (not `/admin`) as a
// small obscurity measure. Internal names stay as "admin".
// ─────────────────────────────────────────────────────────────

function iso(ms) {
  return new Date(ms).toISOString().replace('T', ' ').slice(0, 19) + 'Z';
}

function shortSession(id) {
  if (!id) return '';
  return id.slice(0, 10) + '…';
}

function escapeHtml(s) {
  return String(s ?? '').replace(/[&<>"']/g, (c) => ({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;',
  }[c]));
}

// Email of the currently-authenticated admin, set by load() from
// /auth/me. Used to suppress the "Remove" button on the viewer's
// own row so they can't accidentally delete themselves from the UI
// (the server also rejects this, but hiding the control is clearer).
let currentAdminEmail = null;

function renderInvites(rows) {
  const tbody = document.querySelector('#t-invites tbody');
  const empty = document.getElementById('empty-invites');
  if (!rows.length) {
    tbody.innerHTML = '';
    empty.hidden = false;
    return;
  }
  empty.hidden = true;
  tbody.innerHTML = rows.map((r) => {
    const when = new Date(r.invited_at).toISOString().slice(0, 10);
    const admin = r.is_admin ? '<span class="badge">Admin</span>' : '';
    // Action cell: "Remove" button for non-admin rows that aren't
    // the viewer's own. Admin rows require a CLI demote first (see
    // the server's cannot_remove_admin guard) — the column shows
    // a dash instead. Self row: also a dash.
    let action = '<span class="dim">—</span>';
    if (!r.is_admin && r.email !== currentAdminEmail) {
      action = `<button type="button" class="row-action row-action--delete"
                  data-email="${escapeHtml(r.email)}">Remove</button>`;
    }
    return `<tr>
      <td>${escapeHtml(r.email)}</td>
      <td>${escapeHtml(r.first_name || '')}</td>
      <td class="when">${when}</td>
      <td>${escapeHtml(r.invited_by || '')}</td>
      <td class="num">${admin}</td>
      <td class="num">${action}</td>
    </tr>`;
  }).join('');
}

// Event delegation — one listener on the table, dispatched to the
// clicked row-action button. Avoids re-binding every re-render.
(function wireInviteActions() {
  const table = document.getElementById('t-invites');
  if (!table) return;
  table.addEventListener('click', async (ev) => {
    const btn = ev.target.closest('.row-action--delete');
    if (!btn) return;
    const email = btn.dataset.email;
    if (!email) return;
    if (!window.confirm(`Remove invite for ${email}?\n\nThis also kicks them out of any active session.`)) return;

    btn.disabled = true;
    const original = btn.textContent;
    btn.textContent = 'Removing…';
    try {
      const res = await fetch('/api/fenjaops/invites/' + encodeURIComponent(email), {
        method: 'DELETE',
        credentials: 'same-origin',
      });
      if (res.status === 200) {
        load(); // refresh the whole board — simplest + shows new counts
        return;
      }
      const payload = await res.json().catch(() => ({}));
      const msg = {
        invalid_email:      'That email is invalid.',
        cannot_remove_self: 'You cannot remove your own admin account.',
        cannot_remove_admin:'Demote this admin via the CLI first (bin/invite.js admin remove).',
        not_found:          'That invite is already gone.',
      }[payload.error] || `Remove failed (HTTP ${res.status}).`;
      alert(msg);
      btn.disabled = false;
      btn.textContent = original;
    } catch (err) {
      console.error('[admin] remove invite', err);
      alert('Network error — see server logs.');
      btn.disabled = false;
      btn.textContent = original;
    }
  });
})();

function renderSummary(rows) {
  const tbody = document.querySelector('#t-summary tbody');
  const empty = document.getElementById('empty-summary');
  if (!rows.length) {
    tbody.innerHTML = '';
    empty.hidden = false;
    return;
  }
  empty.hidden = true;
  tbody.innerHTML = rows.map((r) => `
    <tr>
      <td>${escapeHtml(r.email)}</td>
      <td class="num">${r.click_count}</td>
      <td class="when">${iso(r.first_clicked_at)}</td>
      <td class="when">${iso(r.last_clicked_at)}</td>
    </tr>
  `).join('');
}

function renderClicks(rows) {
  const tbody = document.querySelector('#t-clicks tbody');
  const empty = document.getElementById('empty-clicks');
  if (!rows.length) {
    tbody.innerHTML = '';
    empty.hidden = false;
    return;
  }
  empty.hidden = true;
  tbody.innerHTML = rows.map((r) => `
    <tr>
      <td class="when">${iso(r.clicked_at)}</td>
      <td>${escapeHtml(r.email)}</td>
      <td class="mono">${escapeHtml(shortSession(r.session_id))}</td>
    </tr>
  `).join('');
}

async function load() {
  try {
    const [invitesRes, joinsRes, meRes] = await Promise.all([
      fetch('/api/fenjaops/invites', { credentials: 'same-origin' }),
      fetch('/api/fenjaops/joins',   { credentials: 'same-origin' }),
      fetch('/auth/me',              { credentials: 'same-origin' }),
    ]);

    if (!invitesRes.ok || !joinsRes.ok) {
      // Session expired or admin flag revoked while the page was open —
      // bounce to the front page rather than leaving stale tables.
      window.location.href = '/';
      return;
    }

    // Capture the viewer's own email so renderInvites() can suppress
    // the delete button on their own row. Non-fatal if this fetch
    // fails — worst case the user sees a Remove button on their own
    // row and the server's cannot_remove_self guard catches the click.
    if (meRes.ok) {
      const me = await meRes.json().catch(() => ({}));
      currentAdminEmail = (me.email || '').toLowerCase() || null;
    }

    const invites = await invitesRes.json();
    const joins   = await joinsRes.json();

    document.getElementById('stat-clicks').textContent  = joins.total_clicks;
    document.getElementById('stat-unique').textContent  = joins.unique_users;
    document.getElementById('stat-invites').textContent = invites.length;
    document.getElementById('stat-admins').textContent  = invites.filter((r) => r.is_admin).length;

    renderInvites(invites);
    renderSummary(joins.summary);
    renderClicks(joins.clicks);
  } catch (err) {
    // Network failure — show a soft error rather than a blank page.
    const stats = document.getElementById('stats');
    stats.innerHTML = '<p style="color:var(--admin)">Failed to load admin data — check the server logs.</p>';
    console.error('[admin]', err);
  }
}

const ERROR_COPY = {
  invalid_email:       'That email address is not valid.',
  invalid_first_name:  'First name is invalid.',
  first_name_too_long: 'First name is too long (max 64 characters).',
  already_invited:     'That email is already on the invite list.',
};

function setupInviteForm() {
  const form = document.getElementById('invite-form');
  const msg  = document.getElementById('invite-msg');
  if (!form || !msg) return;

  function show(text, cls) {
    msg.textContent = text;
    msg.classList.remove('ok', 'err');
    msg.classList.add(cls);
    msg.hidden = false;
  }

  form.addEventListener('submit', async (ev) => {
    ev.preventDefault();
    msg.hidden = true;

    const data       = new FormData(form);
    const email      = String(data.get('email') || '').trim();
    const firstName  = String(data.get('first_name') || '').trim();
    const body       = { email };
    if (firstName) body.first_name = firstName;

    const btn = form.querySelector('button[type="submit"]');
    btn.disabled = true;
    try {
      const res = await fetch('/api/fenjaops/invites', {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(body),
      });
      const payload = await res.json().catch(() => ({}));

      if (res.status === 201) {
        show(`Invited ${payload.email}.`, 'ok');
        form.reset();
        load();
        return;
      }
      if (res.status === 401 || res.status === 404) {
        window.location.href = '/';
        return;
      }
      const code = payload.error || 'error';
      show(ERROR_COPY[code] || `Error: ${code}`, 'err');
    } catch (err) {
      console.error('[admin] invite submit', err);
      show('Network error — check server logs.', 'err');
    } finally {
      btn.disabled = false;
    }
  });
}

load();
setupInviteForm();
