/**
 * Admin panel — DC-048.
 *
 * Minimal admin UI for managing users + invites. Rendered as a modal overlay
 * triggered by an "Admin" button in the top bar that only appears when
 * /api/v1/auth/me returns isAdmin=true. The panel renders three sections:
 *
 *   1. Users — list of authorized users with role badges, role-edit,
 *      delete actions.
 *   2. Invite a user — form to issue a single-use invite (email, role,
 *      TTL). The accept-link is shown post-issue so the admin can copy it.
 *   3. Outstanding invites — list of issued-not-yet-accepted invites
 *      with a revoke button.
 *
 * The panel does NOT add a tab to the dashboard nav — it lives as a modal
 * to keep DC-048 surgical. Future DC-049 work can promote it to a tab.
 *
 * Behaviour:
 *   - On load: GET /me; if !isAdmin → show "admin only" placeholder
 *   - Then GET /admin/users + /admin/invites in parallel
 *   - Forms POST to the admin endpoints, refresh lists on success
 *   - "Copy link" button writes the acceptUrl to the clipboard
 *
 * Wires into the global error-handler (window.errorHandler) for failure
 * surfaces. Uses window.SITE for any UI constants (none currently).
 */

(function () {
  'use strict';

  const API = {
    me: '/api/v1/auth/me',
    users: '/api/v1/auth/admin/users',
    allowlist: '/api/v1/auth/admin/allowlist',
    invites: '/api/v1/auth/admin/invites',
  };

  function _el(tag, attrs, ...children) {
    const node = document.createElement(tag);
    if (attrs) {
      for (const k of Object.keys(attrs)) {
        const v = attrs[k];
        if (v === null || v === undefined || v === false) continue;
        if (k === 'class') node.className = v;
        else if (k === 'text') node.textContent = v;
        else if (k === 'html') node.innerHTML = v;
        else if (k.startsWith('on') && typeof v === 'function') node.addEventListener(k.slice(2).toLowerCase(), v);
        else node.setAttribute(k, v);
      }
    }
    for (const c of children) {
      if (c === null || c === undefined || c === false) continue;
      if (typeof c === 'string') node.appendChild(document.createTextNode(c));
      else node.appendChild(c);
    }
    return node;
  }

  async function _fetchJSON(url, opts) {
    const csrf = (window.SITE && window.SITE.csrfToken) || '';
    opts = opts || {};
    opts.headers = Object.assign(
      { 'Content-Type': 'application/json' },
      opts.headers || {},
      csrf ? { 'X-CSRF-Token': csrf } : {}
    );
    if (opts.body && typeof opts.body !== 'string') opts.body = JSON.stringify(opts.body);
    const r = await fetch(url, opts);
    const data = await r.json().catch(() => ({}));
    if (!r.ok) {
      const msg = (data && (data.message || data.error)) || ('HTTP ' + r.status);
      const err = new Error(msg);
      err.status = r.status;
      throw err;
    }
    return data;
  }

  function _renderBadge(role) {
    const colors = {
      admin: 'background:#7c3aed;color:#fff',
      operator: 'background:#2563eb;color:#fff',
      viewer: 'background:#6b7280;color:#fff',
    };
    return _el('span', {
      class: 'role-badge',
      style: 'display:inline-block;padding:2px 8px;border-radius:4px;font-size:0.75rem;font-weight:600;text-transform:uppercase;' +
        (colors[role] || colors.viewer),
      text: role,
    });
  }

  function _renderUsersList(container, users, onChange) {
    container.innerHTML = '';
    if (!users || users.length === 0) {
      container.appendChild(_el('p', { style: 'color:var(--muted)', text: 'No users yet.' }));
      return;
    }
    const table = _el('table', {
      style: 'width:100%;border-collapse:collapse;font-size:0.9rem',
    });
    table.appendChild(_el('thead', null,
      _el('tr', { style: 'border-bottom:1px solid var(--border)' },
        _el('th', { style: 'text-align:left;padding:8px', text: 'Email' }),
        _el('th', { style: 'text-align:left;padding:8px', text: 'Role' }),
        _el('th', { style: 'text-align:left;padding:8px', text: 'Created' }),
        _el('th', { style: 'text-align:left;padding:8px', text: 'Last login' }),
        _el('th', { style: 'text-align:right;padding:8px', text: 'Actions' }),
      ),
    ));
    const tbody = _el('tbody');
    for (const u of users) {
      const row = _el('tr', { style: 'border-bottom:1px solid var(--border)' });

      const emailCell = _el('td', { style: 'padding:8px' });
      emailCell.appendChild(_el('span', { text: u.email || '(no email)' }));
      if (u.displayName && u.displayName !== (u.email || '').split('@')[0]) {
        emailCell.appendChild(_el('br'));
        emailCell.appendChild(_el('small', {
          style: 'color:var(--muted)', text: u.displayName,
        }));
      }
      row.appendChild(emailCell);

      const roleCell = _el('td', { style: 'padding:8px' });
      roleCell.appendChild(_renderBadge(u.role));
      row.appendChild(roleCell);

      row.appendChild(_el('td', {
        style: 'padding:8px;color:var(--muted);font-size:0.85rem',
        text: u.createdAt ? new Date(u.createdAt).toLocaleDateString() : '—',
      }));
      row.appendChild(_el('td', {
        style: 'padding:8px;color:var(--muted);font-size:0.85rem',
        text: u.lastLoginAt ? new Date(u.lastLoginAt).toLocaleString() : '—',
      }));

      const actionsCell = _el('td', { style: 'padding:8px;text-align:right' });
      const roleSelect = _el('select', {
        style: 'padding:2px 6px;margin-right:6px',
        onchange: async (ev) => {
          try {
            await _fetchJSON(API.users + '/' + encodeURIComponent(u.id), {
              method: 'PATCH', body: { role: ev.target.value },
            });
            onChange && onChange();
          } catch (e) {
            window.errorHandler && window.errorHandler.show('Role update failed: ' + e.message);
            ev.target.value = u.role;
          }
        },
      });
      for (const r of ['admin', 'operator', 'viewer']) {
        const opt = _el('option', { value: r, text: r });
        if (r === u.role) opt.selected = true;
        roleSelect.appendChild(opt);
      }
      actionsCell.appendChild(roleSelect);

      const delBtn = _el('button', {
        class: 'btn-sm', style: 'padding:2px 8px',
        text: 'Delete',
        onclick: async () => {
          if (!confirm('Delete user ' + (u.email || u.id) + '? This cannot be undone.')) return;
          try {
            await _fetchJSON(API.users + '/' + encodeURIComponent(u.id), { method: 'DELETE' });
            onChange && onChange();
          } catch (e) {
            window.errorHandler && window.errorHandler.show('Delete failed: ' + e.message);
          }
        },
      });
      actionsCell.appendChild(delBtn);
      row.appendChild(actionsCell);

      tbody.appendChild(row);
    }
    table.appendChild(tbody);
    container.appendChild(table);
  }

  function _renderInviteForm(container, onIssued) {
    const form = _el('form', {
      style: 'display:flex;gap:8px;flex-wrap:wrap;align-items:end',
      onsubmit: async (ev) => {
        ev.preventDefault();
        const fd = new FormData(ev.target);
        const body = {
          email: fd.get('email'),
          role: fd.get('role'),
          ttlHours: parseInt(fd.get('ttlHours'), 10) || 24,
          sendEmail: fd.get('sendEmail') === 'on',
        };
        try {
          const r = await _fetchJSON(API.invites, { method: 'POST', body });
          ev.target.reset();
          onIssued && onIssued(r);
        } catch (e) {
          window.errorHandler && window.errorHandler.show('Invite failed: ' + e.message);
        }
      },
    });
    form.appendChild(_el('label', { style: 'display:flex;flex-direction:column;gap:2px;font-size:0.85rem' },
      _el('span', { text: 'Email' }),
      _el('input', { name: 'email', type: 'email', required: true, placeholder: 'user@example.com', style: 'padding:6px' }),
    ));
    const roleSel = _el('select', { name: 'role', style: 'padding:6px' });
    for (const r of ['operator', 'viewer', 'admin']) {
      roleSel.appendChild(_el('option', { value: r, text: r }));
    }
    form.appendChild(_el('label', { style: 'display:flex;flex-direction:column;gap:2px;font-size:0.85rem' },
      _el('span', { text: 'Role' }), roleSel,
    ));
    form.appendChild(_el('label', { style: 'display:flex;flex-direction:column;gap:2px;font-size:0.85rem' },
      _el('span', { text: 'TTL (hours)' }),
      _el('input', { name: 'ttlHours', type: 'number', min: '1', max: '168', value: '24', style: 'padding:6px;width:80px' }),
    ));
    form.appendChild(_el('label', { style: 'display:flex;gap:4px;align-items:center;font-size:0.85rem' },
      _el('input', { name: 'sendEmail', type: 'checkbox', checked: false }),
      _el('span', { text: 'Also send via email (optional)' }),
    ));
    form.appendChild(_el('button', { type: 'submit', class: 'btn-sm', style: 'padding:6px 12px', text: 'Issue invite' }));
    container.appendChild(form);
  }

  function _renderInvitesList(container, invites, onChange) {
    container.innerHTML = '';
    if (!invites || invites.length === 0) {
      container.appendChild(_el('p', { style: 'color:var(--muted)', text: 'No outstanding invites.' }));
      return;
    }
    const table = _el('table', {
      style: 'width:100%;border-collapse:collapse;font-size:0.9rem',
    });
    table.appendChild(_el('thead', null,
      _el('tr', { style: 'border-bottom:1px solid var(--border)' },
        _el('th', { style: 'text-align:left;padding:8px', text: 'Email' }),
        _el('th', { style: 'text-align:left;padding:8px', text: 'Role' }),
        _el('th', { style: 'text-align:left;padding:8px', text: 'Invited by' }),
        _el('th', { style: 'text-align:left;padding:8px', text: 'Expires' }),
        _el('th', { style: 'text-align:right;padding:8px', text: 'Actions' }),
      ),
    ));
    const tbody = _el('tbody');
    for (const inv of invites) {
      const row = _el('tr', { style: 'border-bottom:1px solid var(--border)' });
      row.appendChild(_el('td', { style: 'padding:8px', text: inv.email }));
      row.appendChild(_el('td', { style: 'padding:8px' }, _renderBadge(inv.role)));
      row.appendChild(_el('td', { style: 'padding:8px;color:var(--muted)', text: inv.invitedBy || '—' }));
      row.appendChild(_el('td', {
        style: 'padding:8px;color:var(--muted);font-size:0.85rem',
        text: inv.expiresAt ? new Date(inv.expiresAt).toLocaleString() : '—',
      }));
      const actionsCell = _el('td', { style: 'padding:8px;text-align:right' });
      actionsCell.appendChild(_el('button', {
        class: 'btn-sm', style: 'padding:2px 8px',
        text: 'Revoke',
        onclick: async () => {
          if (!confirm('Revoke invite for ' + inv.email + '?')) return;
          try {
            await _fetchJSON(API.invites + '/' + encodeURIComponent(inv.id), { method: 'DELETE' });
            onChange && onChange();
          } catch (e) {
            window.errorHandler && window.errorHandler.show('Revoke failed: ' + e.message);
          }
        },
      }));
      row.appendChild(actionsCell);
      tbody.appendChild(row);
    }
    table.appendChild(tbody);
    container.appendChild(table);
  }

  function _renderIssuedInviteBanner(invite, parent) {
    const banner = _el('div', {
      style: 'margin-top:12px;padding:12px;border:1px solid #16a34a;border-radius:6px;background:#052e1a;color:#bbf7d0;font-size:0.85rem',
    });
    banner.appendChild(_el('strong', { text: 'Invite issued — copy the link below. ' +
      'It will not be shown again.' }));
    banner.appendChild(_el('br'));
    banner.appendChild(_el('code', {
      style: 'display:block;margin-top:8px;padding:8px;background:#000;border-radius:4px;word-break:break-all;color:#d1fae5',
      text: invite.acceptUrl,
    }));
    const copyBtn = _el('button', {
      class: 'btn-sm', style: 'margin-top:8px;padding:4px 10px',
      text: 'Copy link',
      onclick: async () => {
        try {
          await navigator.clipboard.writeText(invite.acceptUrl);
          copyBtn.textContent = 'Copied!';
          setTimeout(() => { copyBtn.textContent = 'Copy link'; }, 2000);
        } catch (e) {
          window.errorHandler && window.errorHandler.show('Clipboard blocked: select the link manually.');
        }
      },
    });
    banner.appendChild(copyBtn);

    // DC-085: pre-formatted message for one-tap paste into iMessage / WhatsApp /
    // Telegram / SMS / Signal / Discord / paste-into-email. The operator can
    // copy this as a sentence instead of dealing with the raw URL.
    if (invite.shareText) {
      const shareBlock = _el('div', { style: 'margin-top:12px' });
      shareBlock.appendChild(_el('div', {
        style: 'font-size:0.8rem;color:#86efac;margin-bottom:4px',
        text: 'Share this message:',
      }));
      shareBlock.appendChild(_el('div', {
        style: 'padding:8px;background:#000;border-radius:4px;color:#d1fae5;white-space:pre-wrap',
        text: invite.shareText,
      }));
      const shareActions = _el('div', { style: 'margin-top:6px;display:flex;gap:6px;flex-wrap:wrap' });
      const copyTextBtn = _el('button', {
        class: 'btn-sm', style: 'padding:4px 10px',
        text: 'Copy message',
        onclick: async () => {
          try {
            await navigator.clipboard.writeText(invite.shareText);
            copyTextBtn.textContent = 'Copied!';
            setTimeout(() => { copyTextBtn.textContent = 'Copy message'; }, 2000);
          } catch (e) {
            window.errorHandler && window.errorHandler.show('Clipboard blocked: select the text manually.');
          }
        },
      });
      shareActions.appendChild(copyTextBtn);
      // Native share sheet on mobile / supported browsers. Falls back silently
      // (the copy buttons cover the same intent).
      if (typeof navigator !== 'undefined' && typeof navigator.share === 'function') {
        const nativeShareBtn = _el('button', {
          class: 'btn-sm', style: 'padding:4px 10px',
          text: 'Share via…',
          onclick: async () => {
            try {
              await navigator.share({
                title: 'DashCaddy invite',
                text: invite.shareText,
                url: invite.acceptUrl,
              });
            } catch (e) {
              // User-cancelled throws AbortError — that's fine, just stay quiet.
              if (e && e.name && e.name !== 'AbortError') {
                window.errorHandler && window.errorHandler.show('Share failed: ' + e.message);
              }
            }
          },
        });
        shareActions.appendChild(nativeShareBtn);
      }
      shareBlock.appendChild(shareActions);
      banner.appendChild(shareBlock);
    }

    if (invite.deliveredVia === 'failed') {
      banner.appendChild(_el('p', {
        style: 'margin-top:8px;color:#fbbf24;font-size:0.8rem',
        text: 'Email could not be sent (SMTP not configured). Share the link above instead — it works the same way.',
      }));
    } else if (invite.deliveredVia === 'email') {
      banner.appendChild(_el('p', {
        style: 'margin-top:8px;color:#86efac;font-size:0.8rem',
        text: 'Email sent to ' + invite.email + '.',
      }));
    } else if (invite.deliveredVia === 'manual') {
      banner.appendChild(_el('p', {
        style: 'margin-top:8px;color:#86efac;font-size:0.8rem',
        text: 'Share the link above via text, chat, or any messenger.',
      }));
    }
    parent.appendChild(banner);
  }

  /**
   * Mount the admin panel. Called by the open trigger; safe to call multiple
   * times (re-renders into the same container).
   */
  async function mount(container) {
    container.innerHTML = '';
    container.appendChild(_el('h2', { style: 'margin:0 0 16px', text: 'Admin · Users & Invites' }));

    const meData = await _fetchJSON(API.me).catch(() => ({}));
    if (!meData || !meData.user || meData.user.role !== 'admin') {
      container.appendChild(_el('p', {
        style: 'color:var(--muted)',
        text: 'Admin role required to view this panel. If multi-user mode is enabled and you should have access, check /api/v1/auth/me.',
      }));
      return;
    }

    // Refresh button
    const refreshBtn = _el('button', {
      class: 'btn-sm', style: 'float:right;padding:4px 10px',
      text: 'Refresh',
      onclick: () => mount(container),
    });
    container.appendChild(refreshBtn);

    // ── Users section ────────────────────────────────────────────────────
    const usersHeader = _el('h3', { style: 'margin:24px 0 8px;clear:both', text: 'Users' });
    container.appendChild(usersHeader);

    const usersList = _el('div', { id: 'admin-users-list' });
    container.appendChild(usersList);

    const usersData = await _fetchJSON(API.users).catch(() => ({ users: [] }));
    _renderUsersList(usersList, usersData.users, () => mount(container));

    // Add user form (pre-authorize an email without issuing an invite).
    container.appendChild(_el('h4', { style: 'margin:24px 0 8px;font-size:0.95rem', text: 'Pre-authorize email' }));
    const addUserForm = _el('form', {
      style: 'display:flex;gap:8px;align-items:end',
      onsubmit: async (ev) => {
        ev.preventDefault();
        const email = ev.target.email.value.trim();
        if (!email) return;
        try {
          await _fetchJSON(API.users, { method: 'POST', body: { email } });
          ev.target.reset();
          mount(container);
        } catch (e) {
          window.errorHandler && window.errorHandler.show('Add failed: ' + e.message);
        }
      },
    });
    addUserForm.appendChild(_el('input', { name: 'email', type: 'email', required: true, placeholder: 'user@example.com', style: 'padding:6px' }));
    addUserForm.appendChild(_el('button', { type: 'submit', class: 'btn-sm', style: 'padding:6px 12px', text: 'Add to allowlist' }));
    container.appendChild(addUserForm);

    // ── Invites section ──────────────────────────────────────────────────
    container.appendChild(_el('h3', { style: 'margin:24px 0 8px', text: 'Issue invite' }));
    const inviteFormContainer = _el('div');
    container.appendChild(inviteFormContainer);

    const invitesList = _el('div', { id: 'admin-invites-list', style: 'margin-top:16px' });
    container.appendChild(invitesList);

    const invitesData = await _fetchJSON(API.invites).catch(() => ({ invites: [] }));
    _renderInvitesList(invitesList, invitesData.invites, () => mount(container));

    _renderInviteForm(inviteFormContainer, (issued) => {
      _renderIssuedInviteBanner(issued, inviteFormContainer);
      mount(container);
    });
  }

  // ── Public API ────────────────────────────────────────────────────────

  /**
   * Open the admin panel as a modal overlay. Closes on backdrop click or
   * the close button. Renders into document.body so it floats above the
   * dashboard chrome.
   */
  async function open() {
    // If already open, just focus.
    const existing = document.getElementById('admin-panel-root');
    if (existing) return;

    const backdrop = _el('div', {
      id: 'admin-panel-root',
      style: 'position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:1000;display:flex;align-items:center;justify-content:center;',
      onclick: (ev) => {
        if (ev.target === backdrop) close();
      },
    });

    const card = _el('div', {
      style: 'background:var(--card-base,#1f2937);color:var(--text,#f3f4f6);border-radius:8px;padding:24px;max-width:900px;width:90%;max-height:85vh;overflow:auto;position:relative;box-shadow:0 10px 30px rgba(0,0,0,0.3)',
    });
    card.appendChild(_el('button', {
      class: 'btn-sm', style: 'position:absolute;top:12px;right:12px;padding:4px 10px',
      text: 'Close', onclick: close,
    }));

    const body = _el('div', { id: 'admin-panel-body' });
    card.appendChild(body);
    backdrop.appendChild(card);
    document.body.appendChild(backdrop);

    try {
      await mount(body);
    } catch (e) {
      body.innerHTML = '<p style="color:#f87171">Failed to load admin panel: ' + (e.message || e) + '</p>';
    }
  }

  function close() {
    const existing = document.getElementById('admin-panel-root');
    if (existing) existing.remove();
  }

  /**
   * Inject an "Admin" button into the top bar. Only renders when the
   * current /me response says isAdmin=true. Re-checks periodically
   * (every 60s) so a permission downgrade takes effect without a reload.
   */
  async function attachTrigger(barContainer) {
    async function _maybeShow() {
      const me = await _fetchJSON(API.me).catch(() => ({}));
      const existing = document.getElementById('admin-trigger-btn');
      if (me && me.user && me.user.role === 'admin') {
        if (existing) return;
        const btn = _el('button', {
          id: 'admin-trigger-btn',
          class: 'btn-sm',
          style: 'margin-left:8px;padding:6px 12px',
          text: 'Admin',
          onclick: open,
        });
        if (barContainer) barContainer.appendChild(btn);
        else if (document.body) document.body.appendChild(btn);
      } else if (existing) {
        existing.remove();
      }
    }
    await _maybeShow();
    setInterval(_maybeShow, 60_000);
  }

  window.AdminPanel = { open, close, attachTrigger };
})();