// ── Shared helpers ──────────────────────────────────────────────────────────
// `Req` (red asterisk for required fields) is defined in shell.jsx so every
// later-loaded JSX file can use it without depending on admin.jsx load order.

// `TypedConfirmModal` — gates a destructive action behind typing the entity's
// name verbatim. Used for purge (hard delete) so admins can't fat-finger a
// campaign wipe. Resolves via the onConfirm/onCancel callbacks; not in the
// dialog.* queue because it has a custom input.
const TypedConfirmModal = ({ title, body, expectedText, danger = true, confirmLabel = 'Delete forever', onConfirm, onCancel, busy }) => {
  const [typed, setTyped] = React.useState('');
  const ok = typed.trim() === expectedText;
  return (
    <div style={dialogStyles.scrim} {...scrimDismiss(onCancel)}>
      <div style={{...dialogStyles.box, maxWidth:520}} onClick={e => e.stopPropagation()}>
        <div style={{...dialogStyles.title, color: danger ? '#f87171' : '#e8e6e3'}}>{title}</div>
        <div style={{...dialogStyles.msg, marginBottom:14}}>{body}</div>
        <div style={{ fontSize:12, color:'#9a9793', marginBottom:6 }}>
          Type <strong style={{color:'#e8e6e3'}}>{expectedText}</strong> to confirm:
        </div>
        <input autoFocus value={typed} onChange={e => setTyped(e.target.value)}
          style={{ width:'100%', background:'#16161b', border:'1px solid #2a2a32', borderRadius:6, color:'#e8e6e3', padding:'8px 10px', fontSize:13, marginBottom:12, fontFamily:"'Nunito',sans-serif", boxSizing:'border-box' }} />
        <div style={dialogStyles.row}>
          <button style={dialogStyles.btnGhost} onClick={onCancel} disabled={busy}>Cancel</button>
          <button style={{...dialogStyles.btn, ...(danger ? dialogStyles.btnDanger : {}), opacity: ok && !busy ? 1 : 0.5, cursor: ok && !busy ? 'pointer' : 'not-allowed'}}
            onClick={onConfirm} disabled={!ok || busy}>
            {busy ? 'Working…' : confirmLabel}
          </button>
        </div>
      </div>
    </div>
  );
};
window.TypedConfirmModal = TypedConfirmModal;

// Admin Panel — user management, campaign assignment, world events.
// All three tabs are now API-backed. Each tab loads its slice independently and
// uses inline edit forms (avoid the inner-component focus-loss anti-pattern).
// `onCampaignsChanged` lets the App-level sidebar refresh after we create,
// edit, or archive a campaign — otherwise newly added campaigns don't show
// up in the nav until the next login.
const AdminView = ({ user, onCampaignsChanged, onImpersonate }) => {
  const [tab, setTab] = React.useState('users');

  // ── Users ────────────────────────────────────────────────────────────────
  const [users, setUsers] = React.useState([]);
  const [usersLoading, setUsersLoading] = React.useState(true);
  const [showAddUser, setShowAddUser] = React.useState(false);
  const [editUserId, setEditUserId] = React.useState(null);
  const blankUser = { name:'', username:'', email:'', password:'', isAdmin: false };
  const [newUser, setNewUser] = React.useState(blankUser);
  const [editForm, setEditForm] = React.useState(blankUser);

  const refreshUsers = React.useCallback(async () => {
    setUsersLoading(true);
    try { setUsers((await api.admin.users.list()) || []); }
    catch (err) { console.error('[Admin] users:', err); }
    finally { setUsersLoading(false); }
  }, []);

  // ── Campaigns ────────────────────────────────────────────────────────────
  const [campaigns, setCampaigns] = React.useState([]);
  const [campaignsLoading, setCampaignsLoading] = React.useState(true);
  const [showAddCampaign, setShowAddCampaign] = React.useState(false);
  const [editCampaignId, setEditCampaignId] = React.useState(null);
  const [addingPlayerFor, setAddingPlayerFor] = React.useState(null);
  const blankCampaign = { name:'', setting:'', description:'', bannerColor:'#6b1a1a', dmUserIds:[], status:'active', realmId:'', features: {} };
  const [newCampaign, setNewCampaign] = React.useState(blankCampaign);
  const [editCampaign, setEditCampaign] = React.useState(blankCampaign);

  const refreshCampaigns = React.useCallback(async () => {
    setCampaignsLoading(true);
    try { setCampaigns((await api.campaigns.list()) || []); }
    catch (err) { console.error('[Admin] campaigns:', err); }
    finally { setCampaignsLoading(false); }
  }, []);

  // ── Hard-delete (purge) state ────────────────────────────────────────────
  // `purgeTarget` = { kind: 'campaign'|'user'|'realm', id, name, body? }. When
  // set, a typed-confirm modal blocks the UI until admin types the entity's name.
  const [purgeTarget, setPurgeTarget] = React.useState(null);
  const [purgeBusy, setPurgeBusy] = React.useState(false);

  const startPurgeCampaign = (c) => setPurgeTarget({
    kind:'campaign', id:c.id, name:c.name,
    body:`Permanently delete "${c.name}" and EVERYTHING in it: sessions, quests, NPCs, characters, guild/tribe, shops, materials, homebrew, inventory. Global-board quests originally from this campaign will survive but lose their campaign link.`,
  });
  const startPurgeUser = (u) => setPurgeTarget({
    kind:'user', id:u.id, name:u.name,
    body:`Permanently remove ${u.name} from the system. The server will refuse if they DM any campaigns or authored any homebrew — purge or reassign those first.`,
  });
  const startPurgeRealm = (r) => setPurgeTarget({
    kind:'realm', id:r.id, name:r.name,
    body:`Permanently delete realm "${r.name}". Campaigns referencing it will become realm-less (the DM will need to repick a realm before features render).`,
  });

  const runPurge = async () => {
    if (!purgeTarget) return;
    setPurgeBusy(true);
    try {
      if (purgeTarget.kind === 'campaign') {
        await api.admin.campaigns.purge(purgeTarget.id);
        await refreshCampaigns();
        onCampaignsChanged?.();
      } else if (purgeTarget.kind === 'user') {
        await api.admin.users.purge(purgeTarget.id);
        await refreshUsers();
      } else if (purgeTarget.kind === 'realm') {
        await api.admin.realms.purge(purgeTarget.id);
        await refreshRealms();
        await refreshCampaigns(); // realms can become null on campaigns
        onCampaignsChanged?.();
      }
      setPurgeTarget(null);
    } catch (err) {
      const body = err.body || {};
      // 409 = rule violation (last DM, etc.) — that's an expected outcome, not a bug to log loudly.
      if (err.status !== 409) console.error('[Admin] purge failed:', err);
      let msg = err.message || 'unknown error';
      if (body.lastDmCampaigns?.length) msg += `\nLast-DM of: ${body.lastDmCampaigns.map(c=>c.name).join(', ')}.`;
      if (body.homebrewCount) msg += `\nBlocking homebrew items: ${body.homebrewCount}.`;
      window.dialog.alert(msg, { title: err.status === 409 ? 'Cannot purge yet' : 'Purge failed' });
    } finally {
      setPurgeBusy(false);
    }
  };

  // ── Realms ───────────────────────────────────────────────────────────────
  const [realms, setRealms] = React.useState([]);
  const [realmsLoading, setRealmsLoading] = React.useState(true);
  const [showAddRealm, setShowAddRealm] = React.useState(false);
  const [editRealmId, setEditRealmId] = React.useState(null);
  const blankRealm = { name:'', slug:'', description:'', mapImageUrl:'', orgKind:'guild', defaults: { org:true, association:true, worldMap:true, shops:true } };
  const [newRealm, setNewRealm] = React.useState(blankRealm);
  const [editRealm, setEditRealm] = React.useState(blankRealm);
  const refreshRealms = React.useCallback(async () => {
    setRealmsLoading(true);
    try { setRealms((await api.realms.list()) || []); }
    catch (err) { console.error('[Admin] realms:', err); }
    finally { setRealmsLoading(false); }
  }, []);

  // ── World Events ─────────────────────────────────────────────────────────
  const [events, setEvents] = React.useState([]);
  const [eventsLoading, setEventsLoading] = React.useState(true);
  const [showAddEvent, setShowAddEvent] = React.useState(false);
  const [editEventId, setEditEventId] = React.useState(null);
  const blankEvent = { title:'', description:'', sourceCampaignId:'', date: new Date().toISOString().slice(0,10), severity:'moderate', visibility:'all', affectedCampaignIds:[], tags:'' };
  const [newEvent, setNewEvent] = React.useState(blankEvent);
  const [editEvent, setEditEvent] = React.useState(blankEvent);

  const refreshEvents = React.useCallback(async () => {
    setEventsLoading(true);
    try { setEvents((await api.worldEvents.list()) || []); }
    catch (err) { console.error('[Admin] world events:', err); }
    finally { setEventsLoading(false); }
  }, []);

  // Load each slice when its tab becomes active. Users tab is the default so it loads on mount.
  // Campaigns tab also loads realms — the campaign edit/create form needs the realm picker.
  React.useEffect(() => {
    if (tab === 'users')        refreshUsers();
    if (tab === 'campaigns')    { refreshCampaigns(); if (users.length === 0) refreshUsers(); if (realms.length === 0) refreshRealms(); }
    if (tab === 'world-events') { refreshEvents();    if (campaigns.length === 0) refreshCampaigns(); }
    if (tab === 'realms')       refreshRealms();
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tab]);

  // ── User actions ─────────────────────────────────────────────────────────
  const addUser = async () => {
    if (!newUser.name.trim() || !newUser.username.trim() || !newUser.password.trim()) return;
    try {
      await api.admin.users.create({
        name: newUser.name, username: newUser.username, email: newUser.email,
        password: newUser.password, isAdmin: !!newUser.isAdmin,
      });
      setNewUser(blankUser);
      setShowAddUser(false);
      await refreshUsers();
    } catch (err) {
      console.error('[Admin] addUser failed:', err);
      window.dialog.alert('Failed to create user: ' + (err.message || 'unknown error'));
    }
  };

  const startEditUser = (u) => {
    setEditUserId(u.id);
    setEditForm({ name:u.name, username:u.username || '', email:u.email, password:'', isAdmin: !!u.isAdmin });
  };

  const saveEditUser = async () => {
    try {
      const patch = {
        name: editForm.name, username: editForm.username, email: editForm.email,
        isAdmin: !!editForm.isAdmin,
      };
      if (editForm.password.trim()) patch.password = editForm.password;
      await api.admin.users.update(editUserId, patch);
      setEditUserId(null);
      await refreshUsers();
    } catch (err) {
      console.error('[Admin] saveEditUser failed:', err);
      window.dialog.alert('Failed to save user: ' + (err.message || 'unknown error'));
    }
  };

  const removeUser = async (u) => {
    if (!(await window.dialog.confirm({ title:'Deactivate user?', message:`Mark ${u.name} as inactive? They won't be able to log in, but their data is kept. Use Purge for a hard delete.`, danger:false, confirmLabel:'Deactivate' }))) return;
    try {
      await api.admin.users.delete(u.id);
      await refreshUsers();
    } catch (err) {
      console.error('[Admin] removeUser failed:', err);
      window.dialog.alert('Failed to remove user: ' + (err.message || 'unknown error'));
    }
  };

  // Reactivate: flips IsActive back to true. Same PATCH endpoint as a normal
  // user update — we just send the one field. No confirm dialog needed since
  // this is a reversal of a reversible action.
  const reactivateUser = async (u) => {
    try {
      await api.admin.users.update(u.id, { isActive: true });
      await refreshUsers();
    } catch (err) {
      console.error('[Admin] reactivateUser failed:', err);
      window.dialog.alert('Failed to reactivate: ' + (err.message || 'unknown error'));
    }
  };

  // ── Campaign actions ─────────────────────────────────────────────────────
  // Feature override map → JSON. Each toggleable key may be true/false, or omitted
  // (which means "inherit realm default"). UI exposes a tri-state per key: inherit / on / off.
  const featuresToJson = (features) => {
    const out = {};
    for (const k of api.FEATURE_KEYS) {
      if (features[k] === true || features[k] === false) out[k] = features[k];
    }
    return JSON.stringify(out);
  };
  const featuresFromJson = (json) => {
    try { return JSON.parse(json || '{}'); } catch { return {}; }
  };

  const addCampaign = async () => {
    if (!newCampaign.name.trim() || !newCampaign.setting.trim() || newCampaign.dmUserIds.length === 0) return;
    try {
      await api.campaigns.create({
        name: newCampaign.name, setting: newCampaign.setting, description: newCampaign.description,
        bannerColor: newCampaign.bannerColor,
        dmUserIds: newCampaign.dmUserIds,
        realmId: newCampaign.realmId || null,
        featuresJson: featuresToJson(newCampaign.features || {}),
      });
      setNewCampaign(blankCampaign);
      setShowAddCampaign(false);
      await refreshCampaigns();
      onCampaignsChanged?.();
    } catch (err) {
      console.error('[Admin] addCampaign failed:', err);
      window.dialog.alert('Failed to create campaign: ' + (err.message || 'unknown error'));
    }
  };

  const startEditCampaign = (c) => {
    setEditCampaignId(c.id);
    setEditCampaign({
      name: c.name, setting: c.setting, description: c.description || '',
      bannerColor: c.bannerColor || '#6b1a1a',
      dmUserIds: dmsOf(c).map(d => d.userId),
      status: c.status || 'active',
      realmId: c.realmId || '',
      features: featuresFromJson(c.featuresJson),
    });
  };

  const saveEditCampaign = async () => {
    try {
      await api.campaigns.update(editCampaignId, {
        name: editCampaign.name, setting: editCampaign.setting, description: editCampaign.description,
        bannerColor: editCampaign.bannerColor, status: editCampaign.status,
        realmId: editCampaign.realmId || null,
        featuresJson: featuresToJson(editCampaign.features || {}),
      });
      setEditCampaignId(null);
      await refreshCampaigns();
      onCampaignsChanged?.();
    } catch (err) {
      console.error('[Admin] saveEditCampaign failed:', err);
      window.dialog.alert('Failed to save campaign: ' + (err.message || 'unknown error'));
    }
  };

  // ── Realm actions ────────────────────────────────────────────────────────
  const addRealm = async () => {
    if (!newRealm.name.trim() || !newRealm.slug.trim()) return;
    try {
      await api.realms.create({
        name: newRealm.name, slug: newRealm.slug, description: newRealm.description,
        mapImageUrl: newRealm.mapImageUrl, orgKind: newRealm.orgKind,
        defaultFeaturesJson: JSON.stringify(newRealm.defaults),
      });
      setNewRealm(blankRealm);
      setShowAddRealm(false);
      await refreshRealms();
    } catch (err) {
      console.error('[Admin] addRealm failed:', err);
      window.dialog.alert('Failed to create realm: ' + (err.message || 'unknown error'), { title:'Error' });
    }
  };

  const startEditRealm = (r) => {
    let defaults = { org:true, association:true, worldMap:true, shops:true };
    try { defaults = { ...defaults, ...JSON.parse(r.defaultFeaturesJson || '{}') }; } catch {}
    setEditRealmId(r.id);
    setEditRealm({
      name: r.name, slug: r.slug, description: r.description || '',
      mapImageUrl: r.mapImageUrl || '', orgKind: r.orgKind || 'guild', defaults,
    });
  };

  const saveEditRealm = async () => {
    try {
      await api.realms.update(editRealmId, {
        name: editRealm.name, slug: editRealm.slug, description: editRealm.description,
        mapImageUrl: editRealm.mapImageUrl, orgKind: editRealm.orgKind,
        defaultFeaturesJson: JSON.stringify(editRealm.defaults),
      });
      setEditRealmId(null);
      await refreshRealms();
    } catch (err) {
      console.error('[Admin] saveEditRealm failed:', err);
      window.dialog.alert('Failed to save realm: ' + (err.message || 'unknown error'), { title:'Error' });
    }
  };

  const unarchiveRealm = async (r) => {
    try {
      await api.realms.update(r.id, { isArchived: false });
      await refreshRealms();
    } catch (err) {
      console.error('[Admin] unarchiveRealm failed:', err);
      window.dialog.alert('Failed to unarchive: ' + (err.message || 'unknown error'));
    }
  };

  const removeRealm = async (r) => {
    const ok = await window.dialog.confirm({
      title: 'Delete realm?',
      message: `Delete "${r.name}"? Campaigns referencing this realm will be archived along with it.`,
      danger: true, confirmLabel: 'Delete',
    });
    if (!ok) return;
    try {
      await api.realms.delete(r.id);
      await refreshRealms();
    } catch (err) {
      console.error('[Admin] removeRealm failed:', err);
      window.dialog.alert('Failed: ' + (err.message || 'unknown error'), { title:'Error' });
    }
  };

  const archiveCampaign = async (c) => {
    if (!(await window.dialog.confirm({ title:'Deactivate campaign?', message:`Mark "${c.name}" as deactivated? It stays in the database and can be reactivated later. Use Purge for a hard delete.`, danger:false, confirmLabel:'Deactivate' }))) return;
    try {
      await api.campaigns.update(c.id, { status: 'archived' });
      await refreshCampaigns();
      onCampaignsChanged?.();
    } catch (err) {
      console.error('[Admin] archiveCampaign failed:', err);
      window.dialog.alert('Failed to archive: ' + (err.message || 'unknown error'));
    }
  };

  // Unarchive: flip status back to active. Reversal of Archive; no confirm.
  const unarchiveCampaign = async (c) => {
    try {
      await api.campaigns.update(c.id, { status: 'active' });
      await refreshCampaigns();
      onCampaignsChanged?.();
    } catch (err) {
      console.error('[Admin] unarchiveCampaign failed:', err);
      window.dialog.alert('Failed to unarchive: ' + (err.message || 'unknown error'));
    }
  };

  const addMember = async (campaignId, userId, role) => {
    try {
      await api.admin.campaigns.addPlayer(campaignId, { userId, role });
      setAddingPlayerFor(null);
      await refreshCampaigns();
      onCampaignsChanged?.();
    } catch (err) {
      console.error('[Admin] addMember failed:', err);
      window.dialog.alert('Failed to add member: ' + (err.message || (err.body && err.body.message) || 'unknown error'));
    }
  };

  const changeMemberRole = async (campaignId, userId, newRole) => {
    try {
      await api.admin.campaigns.updateMember(campaignId, userId, { role: newRole });
      await refreshCampaigns();
      onCampaignsChanged?.();
    } catch (err) {
      console.error('[Admin] changeMemberRole failed:', err);
      window.dialog.alert((err.body && err.body.message) || err.message || 'Failed to change role', { title:'Cannot change role' });
    }
  };

  const removeMember = async (campaignId, userId, name) => {
    if (!(await window.dialog.confirm({ title:'Remove member?', message:`Remove ${name} from this campaign?`, danger:true, confirmLabel:'Remove' }))) return;
    try {
      await api.admin.campaigns.removePlayer(campaignId, userId);
      await refreshCampaigns();
      onCampaignsChanged?.();
    } catch (err) {
      console.error('[Admin] removeMember failed:', err);
      window.dialog.alert((err.body && err.body.message) || err.message || 'Failed to remove member', { title:'Cannot remove member' });
    }
  };

  // ── World event actions ──────────────────────────────────────────────────
  const parseTags = (s) => s.split(',').map(t => t.trim()).filter(Boolean);

  const addEvent = async () => {
    if (!newEvent.title.trim()) return;
    try {
      await api.worldEvents.create({
        title: newEvent.title, description: newEvent.description,
        sourceCampaignId: newEvent.sourceCampaignId || null,
        date: newEvent.date, severity: newEvent.severity, visibility: newEvent.visibility,
        affectedCampaignIds: newEvent.affectedCampaignIds, tags: parseTags(newEvent.tags),
      });
      setNewEvent(blankEvent);
      setShowAddEvent(false);
      await refreshEvents();
    } catch (err) {
      console.error('[Admin] addEvent failed:', err);
      window.dialog.alert('Failed to create event: ' + (err.message || 'unknown error'));
    }
  };

  const startEditEvent = (ev) => {
    setEditEventId(ev.id);
    setEditEvent({
      title: ev.title, description: ev.description,
      sourceCampaignId: ev.sourceCampaignId || '',
      date: ev.date, severity: ev.severity, visibility: ev.visibility || 'all',
      affectedCampaignIds: (ev.affectedCampaigns || []).map(c => c.id),
      tags: (ev.tags || []).join(', '),
    });
  };

  const saveEditEvent = async () => {
    try {
      await api.worldEvents.update(editEventId, {
        title: editEvent.title, description: editEvent.description,
        severity: editEvent.severity, visibility: editEvent.visibility,
        affectedCampaignIds: editEvent.affectedCampaignIds,
        tags: parseTags(editEvent.tags),
      });
      setEditEventId(null);
      await refreshEvents();
    } catch (err) {
      console.error('[Admin] saveEditEvent failed:', err);
      window.dialog.alert('Failed to save event: ' + (err.message || 'unknown error'));
    }
  };

  const deleteEvent = async (ev) => {
    if (!(await window.dialog.confirm({ title:'Delete event?', message:`Delete event "${ev.title}"?`, danger:true, confirmLabel:'Delete' }))) return;
    try {
      await api.worldEvents.delete(ev.id);
      await refreshEvents();
    } catch (err) {
      console.error('[Admin] deleteEvent failed:', err);
      window.dialog.alert('Failed to delete: ' + (err.message || 'unknown error'));
    }
  };

  const toggleAffected = (formObj, setter, cid) => {
    const next = formObj.affectedCampaignIds.includes(cid)
      ? formObj.affectedCampaignIds.filter(x => x !== cid)
      : [...formObj.affectedCampaignIds, cid];
    setter({ ...formObj, affectedCampaignIds: next });
  };

  return (
    <div style={admStyles.page}>
      <div style={admStyles.header}>
        <h1 style={admStyles.title}>Admin Panel</h1>
        <div style={{fontSize:13, color:'#6b6966'}}>Manage users, campaigns, and world events</div>
      </div>

      <div style={admStyles.tabs}>
        {['users','campaigns','realms','world-events'].map(t => (
          <button key={t} style={{...admStyles.tab, ...(tab===t ? admStyles.tabActive : {})}} onClick={() => setTab(t)}>
            {t === 'users' ? 'Users' : t === 'campaigns' ? 'Campaigns' : t === 'realms' ? 'Realms' : 'World Events'}
          </button>
        ))}
      </div>

      {/* ─────────── USERS TAB ─────────── */}
      {tab === 'users' && (
        <div style={admStyles.section}>
          <div style={admStyles.sectionHeader}>
            <span style={{fontSize:13, color:'#9a9793'}}>{users.length} users{usersLoading && ' (loading…)'}</span>
            <button style={admStyles.addBtn} onClick={() => setShowAddUser(!showAddUser)}>+ Add User</button>
          </div>

          {showAddUser && (
            <div style={admStyles.addForm}>
              <div style={admStyles.formTitle}>New User</div>
              <div style={admStyles.formGrid}>
                <div><label style={admStyles.label}>Full Name<Req/></label><input style={admStyles.input} value={newUser.name} onChange={e=>setNewUser(p=>({...p,name:e.target.value}))} /></div>
                <div><label style={admStyles.label}>Username<Req/></label><input style={admStyles.input} value={newUser.username} onChange={e=>setNewUser(p=>({...p,username:e.target.value}))} placeholder="login name, lowercase" /></div>
                <div><label style={admStyles.label}>Email</label><input type="email" style={admStyles.input} value={newUser.email} onChange={e=>setNewUser(p=>({...p,email:e.target.value}))} /></div>
                <div><label style={admStyles.label}>Password<Req/></label><input type="password" style={admStyles.input} value={newUser.password} onChange={e=>setNewUser(p=>({...p,password:e.target.value}))} /></div>
                <div style={{gridColumn:'1/-1', display:'flex', alignItems:'center', gap:8, marginTop:4}}>
                  <label style={{display:'flex', alignItems:'center', gap:6, fontSize:12, color:'#9a9793', cursor:'pointer'}}>
                    <input type="checkbox" checked={!!newUser.isAdmin} onChange={e=>setNewUser(p=>({...p,isAdmin:e.target.checked}))}/>
                    <strong style={{color:'#e8e6e3'}}>Administrator</strong> — full system access. Per-campaign roles (DM, Co-DM, Player) are assigned later when this user joins a campaign.
                  </label>
                </div>
              </div>
              <div style={{display:'flex', gap:10, marginTop:14}}>
                <button style={admStyles.addBtn} onClick={addUser} disabled={!newUser.name.trim() || !newUser.username.trim() || !newUser.password.trim()}>Create User</button>
                <button style={{...admStyles.addBtn, background:'none', borderColor:'#3a3a42', color:'#9a9793'}} onClick={()=>{setShowAddUser(false); setNewUser(blankUser);}}>Cancel</button>
              </div>
            </div>
          )}

          <table style={admStyles.table}>
            <thead>
              <tr>{['User','Email','Admin','Auth','Actions'].map(h => <th key={h} style={admStyles.th}>{h}</th>)}</tr>
            </thead>
            <tbody>
              {users.map(u => (
                editUserId === u.id ? (
                  <tr key={u.id} style={{...admStyles.tr, background:'#111116'}}>
                    <td colSpan={5} style={{padding:'14px 12px'}}>
                      <div style={admStyles.formTitle}>Edit {u.name}</div>
                      <div style={admStyles.formGrid}>
                        <div><label style={admStyles.label}>Name<Req/></label><input style={admStyles.input} value={editForm.name} onChange={e=>setEditForm(p=>({...p,name:e.target.value}))} /></div>
                        <div><label style={admStyles.label}>Username<Req/></label><input style={admStyles.input} value={editForm.username} onChange={e=>setEditForm(p=>({...p,username:e.target.value}))} /></div>
                        <div><label style={admStyles.label}>Email</label><input style={admStyles.input} value={editForm.email} onChange={e=>setEditForm(p=>({...p,email:e.target.value}))} /></div>
                        <div><label style={admStyles.label}>Password (blank = unchanged)</label><input type="password" style={admStyles.input} value={editForm.password} onChange={e=>setEditForm(p=>({...p,password:e.target.value}))} /></div>
                        <div style={{gridColumn:'1/-1', display:'flex', alignItems:'center'}}>
                          <label style={{display:'flex', alignItems:'center', gap:6, fontSize:12, color:'#9a9793', cursor:'pointer'}}>
                            <input type="checkbox" checked={!!editForm.isAdmin} onChange={e=>setEditForm(p=>({...p,isAdmin:e.target.checked}))}/>
                            <strong style={{color:'#e8e6e3'}}>Administrator</strong> — full system access.
                          </label>
                        </div>
                      </div>
                      <div style={{display:'flex', gap:10, marginTop:12}}>
                        <button style={admStyles.addBtn} onClick={saveEditUser}>Save</button>
                        <button style={{...admStyles.addBtn, background:'none', borderColor:'#3a3a42', color:'#9a9793'}} onClick={()=>setEditUserId(null)}>Cancel</button>
                      </div>
                    </td>
                  </tr>
                ) : (
                  <tr key={u.id} style={admStyles.tr}>
                    <td style={admStyles.td}>
                      <div style={{display:'flex', alignItems:'center', gap:10}}>
                        <div style={{...admStyles.avatar, background: roleColor(u)}}>{u.avatar}</div>
                        <span style={{fontSize:14, fontWeight:600, color:'#e8e6e3'}}>{u.name}</span>
                        {!u.isActive && <span style={{...admStyles.roleBadge, background:'rgba(180,83,9,0.15)', color:'#b45309'}}>inactive</span>}
                      </div>
                    </td>
                    <td style={admStyles.td}><span style={{fontSize:13, color:'#9a9793'}}>{u.email}</span></td>
                    <td style={admStyles.td}>{u.isAdmin ? <span style={{...admStyles.roleBadge, background:'rgba(124,58,237,0.15)', color:'#7c3aed'}}>Admin</span> : <span style={{fontSize:12, color:'#6b6966'}}>—</span>}</td>
                    <td style={admStyles.td}>
                      <div style={{display:'flex', gap:4, flexWrap:'wrap'}}>
                        {u.username && <span style={admStyles.authTag}>Password</span>}
                        {u.hasGoogle && <span style={{...admStyles.authTag, background:'rgba(66,133,244,0.1)', color:'#4285F4', borderColor:'rgba(66,133,244,0.2)'}}>Google</span>}
                      </div>
                    </td>
                    <td style={admStyles.td}>
                      <div style={{display:'flex', gap:6}}>
                        <button style={admStyles.actionBtn} onClick={()=>startEditUser(u)}>Edit</button>
                        {u.isActive && u.id !== user.id && onImpersonate && (
                          <button title="View the app as this user. A banner will let you switch back." style={{...admStyles.actionBtn, color:'#a78bfa', borderColor:'rgba(167,139,250,0.3)'}} onClick={()=>onImpersonate(u)}>Impersonate</button>
                        )}
                        {u.isActive
                          ? <button title="Soft action: marks the user inactive but keeps their data" style={{...admStyles.actionBtn, color:'#b45309', borderColor:'rgba(180,83,9,0.3)'}} onClick={()=>removeUser(u)}>Deactivate</button>
                          : <button title="Restore login access for this user" style={{...admStyles.actionBtn, color:'#22c55e', borderColor:'rgba(34,197,94,0.3)'}} onClick={()=>reactivateUser(u)}>Reactivate</button>}
                        <button title="Hard-delete: actually removes the user row" style={{...admStyles.actionBtn, color:'#f87171', borderColor:'rgba(248,113,113,0.2)'}} onClick={()=>startPurgeUser(u)}>Purge</button>
                      </div>
                    </td>
                  </tr>
                )
              ))}
            </tbody>
          </table>
        </div>
      )}

      {/* ─────────── CAMPAIGNS TAB ─────────── */}
      {tab === 'campaigns' && (
        <div style={admStyles.section}>
          <div style={admStyles.sectionHeader}>
            <span style={{fontSize:13, color:'#9a9793'}}>{campaigns.length} campaigns{campaignsLoading && ' (loading…)'}</span>
            <button style={admStyles.addBtn} onClick={()=>setShowAddCampaign(!showAddCampaign)}>+ New Campaign</button>
          </div>

          {showAddCampaign && (
            <div style={admStyles.addForm}>
              <div style={admStyles.formTitle}>New Campaign</div>
              <div style={admStyles.formGrid}>
                <div><label style={admStyles.label}>Name<Req/></label><input style={admStyles.input} value={newCampaign.name} onChange={e=>setNewCampaign(p=>({...p,name:e.target.value}))} placeholder="The Shattered Realm" /></div>
                <div>
                  <label style={admStyles.label}>Setting / region<Req/></label>
                  <input style={admStyles.input} value={newCampaign.setting} onChange={e=>setNewCampaign(p=>({...p,setting:e.target.value}))} placeholder="e.g. Aldenmoor Continent" />
                  <div style={{ fontSize:11, color:'#6b6966', marginTop:3 }}>Where the campaign takes place — used as a filter in the Homebrew Repository.</div>
                </div>
                <div style={{gridColumn:'1/-1'}}><label style={admStyles.label}>Description</label><textarea style={{...admStyles.input, minHeight:60}} value={newCampaign.description} onChange={e=>setNewCampaign(p=>({...p,description:e.target.value}))} placeholder="A few sentences about the campaign's premise (optional)." /></div>
                <div style={{gridColumn:'1/-1'}}>
                  <label style={admStyles.label}>Dungeon Master(s)<Req/></label>
                  <div style={{display:'flex', flexWrap:'wrap', gap:6, marginTop:4}}>
                    {users.filter(u => u.isActive).map(u => {
                      const on = newCampaign.dmUserIds.includes(u.id);
                      return (
                        <button key={u.id} type="button"
                          onClick={()=>setNewCampaign(p=>({...p, dmUserIds: on ? p.dmUserIds.filter(x=>x!==u.id) : [...p.dmUserIds, u.id]}))}
                          style={{background: on?'rgba(197,48,48,0.18)':'none', border:`1px solid ${on?'#c53030':'#3a3a42'}`, color: on?'#e8e6e3':'#9a9793', borderRadius:5, padding:'4px 10px', cursor:'pointer', fontSize:12, fontFamily:"'Nunito',sans-serif"}}>
                          {u.name}{u.isAdmin ? ' ★' : ''}
                        </button>
                      );
                    })}
                  </div>
                  <div style={{fontSize:11, color:'#6b6966', marginTop:4}}>Pick one or more users to act as DM. Add Co-DMs / Players from the campaign row after creation.</div>
                </div>
                <div>
                  <label style={admStyles.label}>Banner Color</label>
                  <input type="color" style={{...admStyles.input, padding:0, height:36}} value={newCampaign.bannerColor} onChange={e=>setNewCampaign(p=>({...p,bannerColor:e.target.value}))} />
                </div>
                <div>
                  <label style={admStyles.label}>Realm</label>
                  <select style={admStyles.select} value={newCampaign.realmId} onChange={e=>setNewCampaign(p=>({...p, realmId:e.target.value}))}>
                    <option value="">— Select realm —</option>
                    {realms.filter(r => !r.isArchived).map(r => <option key={r.id} value={r.id}>{r.name} ({r.orgKind})</option>)}
                  </select>
                </div>
                <div style={{gridColumn:'1/-1'}}>
                  <FeatureToggleEditor realm={realms.find(r => r.id === newCampaign.realmId)} value={newCampaign.features} onChange={v => setNewCampaign(p => ({...p, features:v}))} />
                </div>
              </div>
              <div style={{display:'flex', gap:10, marginTop:14}}>
                <button style={admStyles.addBtn} onClick={addCampaign} disabled={!newCampaign.name.trim() || !newCampaign.setting.trim() || newCampaign.dmUserIds.length === 0}>Create</button>
                <button style={{...admStyles.addBtn, background:'none', borderColor:'#3a3a42', color:'#9a9793'}} onClick={()=>{setShowAddCampaign(false); setNewCampaign(blankCampaign);}}>Cancel</button>
              </div>
            </div>
          )}

          {campaigns.map(c => (
            editCampaignId === c.id ? (
              <div key={c.id} style={{...admStyles.campaignRow, background:'#111116', flexDirection:'column', padding:'18px 24px'}}>
                <div style={admStyles.formTitle}>Edit {c.name}</div>
                <div style={admStyles.formGrid}>
                  <div><label style={admStyles.label}>Name<Req/></label><input style={admStyles.input} value={editCampaign.name} onChange={e=>setEditCampaign(p=>({...p,name:e.target.value}))} /></div>
                  <div><label style={admStyles.label}>Setting / region<Req/></label><input style={admStyles.input} value={editCampaign.setting} onChange={e=>setEditCampaign(p=>({...p,setting:e.target.value}))} placeholder="e.g. Aldenmoor Continent" /></div>
                  <div style={{gridColumn:'1/-1'}}><label style={admStyles.label}>Description</label><textarea style={{...admStyles.input, minHeight:60}} value={editCampaign.description} onChange={e=>setEditCampaign(p=>({...p,description:e.target.value}))} /></div>
                  <div>
                    <label style={admStyles.label}>Status</label>
                    <select style={admStyles.select} value={editCampaign.status} onChange={e=>setEditCampaign(p=>({...p,status:e.target.value}))}>
                      <option value="active">Active</option><option value="paused">Paused</option><option value="completed">Completed</option><option value="archived">Archived</option>
                    </select>
                  </div>
                  <div>
                    <label style={admStyles.label}>Banner Color</label>
                    <input type="color" style={{...admStyles.input, padding:0, height:36}} value={editCampaign.bannerColor} onChange={e=>setEditCampaign(p=>({...p,bannerColor:e.target.value}))} />
                  </div>
                  <div>
                    <label style={admStyles.label}>Realm</label>
                    <select style={admStyles.select} value={editCampaign.realmId} onChange={e=>setEditCampaign(p=>({...p, realmId:e.target.value}))}>
                      <option value="">— None —</option>
                      {realms.map(r => <option key={r.id} value={r.id}>{r.name}{r.isArchived ? ' (archived)' : ''} ({r.orgKind})</option>)}
                    </select>
                  </div>
                  <div style={{gridColumn:'1/-1'}}>
                    <FeatureToggleEditor realm={realms.find(r => r.id === editCampaign.realmId)} value={editCampaign.features} onChange={v => setEditCampaign(p => ({...p, features:v}))} />
                  </div>
                </div>
                <div style={{display:'flex', gap:10, marginTop:12}}>
                  <button style={admStyles.addBtn} onClick={saveEditCampaign}>Save</button>
                  <button style={{...admStyles.addBtn, background:'none', borderColor:'#3a3a42', color:'#9a9793'}} onClick={()=>setEditCampaignId(null)}>Cancel</button>
                </div>
              </div>
            ) : (
              <div key={c.id} style={admStyles.campaignRow}>
                <div style={{...admStyles.campaignStripe, background: c.bannerColor}}></div>
                <div style={{flex:1, padding:'16px 20px'}}>
                  <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom:10}}>
                    <div>
                      <div style={{fontSize:15, fontWeight:800, color:'#e8e6e3', fontFamily:"'Cinzel',serif"}}>{c.name} {c.status !== 'active' && <span style={{fontSize:10, color:'#6b6966', marginLeft:6, textTransform:'uppercase', letterSpacing:'0.1em'}}>· {c.status}</span>}</div>
                      <div style={{fontSize:12, color:'#9a9793', marginTop:2}}>{c.setting} · {c.sessionCount} sessions{c.lastSession ? ' · last ' + c.lastSession : ''}</div>
                    </div>
                    <div style={{display:'flex', gap:6}}>
                      <button style={admStyles.actionBtn} onClick={()=>startEditCampaign(c)}>Edit</button>
                      {c.status !== 'archived'
                        ? <button title="Soft action: marks the campaign deactivated but keeps everything" style={{...admStyles.actionBtn, color:'#b45309', borderColor:'rgba(180,83,9,0.3)'}} onClick={()=>archiveCampaign(c)}>Deactivate</button>
                        : <button title="Restore campaign to active status" style={{...admStyles.actionBtn, color:'#22c55e', borderColor:'rgba(34,197,94,0.3)'}} onClick={()=>unarchiveCampaign(c)}>Reactivate</button>}
                      <button title="Hard delete: wipes the campaign and ALL child data" style={{...admStyles.actionBtn, color:'#f87171', borderColor:'rgba(248,113,113,0.2)'}} onClick={()=>startPurgeCampaign(c)}>Purge</button>
                    </div>
                  </div>
                  <CampaignRoster
                    campaign={c}
                    users={users}
                    adding={addingPlayerFor === c.id}
                    onToggleAdd={() => setAddingPlayerFor(addingPlayerFor === c.id ? null : c.id)}
                    onAdd={(userId, role) => addMember(c.id, userId, role)}
                    onChangeRole={(userId, role) => changeMemberRole(c.id, userId, role)}
                    onRemove={(userId, name) => removeMember(c.id, userId, name)}
                  />
                </div>
              </div>
            )
          ))}
        </div>
      )}

      {/* ─────────── REALMS TAB ─────────── */}
      {tab === 'realms' && (
        <div style={admStyles.section}>
          <div style={admStyles.sectionHeader}>
            <span style={{fontSize:13, color:'#9a9793'}}>{realms.length} realms{realmsLoading && ' (loading…)'}</span>
            <button style={admStyles.addBtn} onClick={()=>setShowAddRealm(!showAddRealm)}>+ New Realm</button>
          </div>

          {showAddRealm && (
            <div style={admStyles.addForm}>
              <div style={admStyles.formTitle}>New Realm</div>
              <RealmFormFields value={newRealm} onChange={setNewRealm}/>
              <div style={{display:'flex', gap:10, marginTop:14}}>
                <button style={admStyles.addBtn} onClick={addRealm} disabled={!newRealm.name.trim() || !newRealm.slug.trim()}>Create</button>
                <button style={{...admStyles.addBtn, background:'none', borderColor:'#3a3a42', color:'#9a9793'}} onClick={()=>{setShowAddRealm(false); setNewRealm(blankRealm);}}>Cancel</button>
              </div>
            </div>
          )}

          {realms.map(r => (
            editRealmId === r.id ? (
              <div key={r.id} style={{...admStyles.campaignRow, background:'#111116', flexDirection:'column', padding:'18px 24px'}}>
                <div style={admStyles.formTitle}>Edit {r.name}</div>
                <RealmFormFields value={editRealm} onChange={setEditRealm}/>
                <div style={{display:'flex', gap:10, marginTop:12}}>
                  <button style={admStyles.addBtn} onClick={saveEditRealm}>Save</button>
                  <button style={{...admStyles.addBtn, background:'none', borderColor:'#3a3a42', color:'#9a9793'}} onClick={()=>setEditRealmId(null)}>Cancel</button>
                </div>
              </div>
            ) : (
              <div key={r.id} style={admStyles.campaignRow}>
                <div style={{...admStyles.campaignStripe, background: r.orgKind === 'tribe' ? '#3a7d3a' : '#c9a227'}}></div>
                <div style={{flex:1, padding:'16px 20px'}}>
                  <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start'}}>
                    <div>
                      <div style={{fontSize:15, fontWeight:800, color:'#e8e6e3', fontFamily:"'Cinzel',serif"}}>
                        {r.name}
                        {r.isArchived && <span style={{fontSize:10, color:'#6b6966', marginLeft:6, textTransform:'uppercase', letterSpacing:'0.1em'}}>· archived</span>}
                      </div>
                      <div style={{fontSize:12, color:'#9a9793', marginTop:2}}>
                        {r.slug} · org: <strong style={{color:'#c9a227'}}>{r.orgKind}</strong>
                        {r.mapImageUrl ? <> · map: <code style={{color:'#9a9793'}}>{r.mapImageUrl}</code></> : ''}
                      </div>
                      <div style={{fontSize:12, color:'#6b6966', marginTop:6, lineHeight:1.5, maxWidth:680}}>{r.description}</div>
                      <div style={{fontSize:11, color:'#6b6966', marginTop:8}}>
                        Defaults: {Object.entries((() => { try { return JSON.parse(r.defaultFeaturesJson || '{}'); } catch { return {}; } })())
                          .map(([k,v]) => <span key={k} style={{marginRight:8, color: v ? '#22c55e' : '#c53030'}}>{k}={v?'on':'off'}</span>)}
                      </div>
                    </div>
                    <div style={{display:'flex', gap:6}}>
                      <button style={admStyles.actionBtn} onClick={()=>startEditRealm(r)}>Edit</button>
                      {!r.isArchived
                        ? <button title="Soft action: deactivates if any campaigns reference this realm; otherwise removes it" style={{...admStyles.actionBtn, color:'#b45309', borderColor:'rgba(180,83,9,0.3)'}} onClick={()=>removeRealm(r)}>Deactivate</button>
                        : <button title="Restore realm to active status" style={{...admStyles.actionBtn, color:'#22c55e', borderColor:'rgba(34,197,94,0.3)'}} onClick={()=>unarchiveRealm(r)}>Reactivate</button>}
                      <button title="Hard delete: removes even if campaigns reference it (their RealmId becomes null)" style={{...admStyles.actionBtn, color:'#f87171', borderColor:'rgba(248,113,113,0.2)'}} onClick={()=>startPurgeRealm(r)}>Purge</button>
                    </div>
                  </div>
                </div>
              </div>
            )
          ))}
        </div>
      )}

      {/* ─────────── WORLD EVENTS TAB ─────────── */}
      {tab === 'world-events' && (
        <div style={admStyles.section}>
          <div style={admStyles.sectionHeader}>
            <span style={{fontSize:13, color:'#9a9793'}}>{events.length} world events{eventsLoading && ' (loading…)'}</span>
            <button style={admStyles.addBtn} onClick={()=>setShowAddEvent(!showAddEvent)}>+ Create Event</button>
          </div>

          {showAddEvent && (
            <div style={admStyles.addForm}>
              <div style={admStyles.formTitle}>New World Event</div>
              <div style={admStyles.formGrid}>
                <div style={{gridColumn:'1/-1'}}><label style={admStyles.label}>Title<Req/></label><input style={admStyles.input} value={newEvent.title} onChange={e=>setNewEvent(p=>({...p,title:e.target.value}))} /></div>
                <div style={{gridColumn:'1/-1'}}><label style={admStyles.label}>Description</label><textarea style={{...admStyles.input, minHeight:80}} value={newEvent.description} onChange={e=>setNewEvent(p=>({...p,description:e.target.value}))} /></div>
                <div>
                  <label style={admStyles.label}>Date</label>
                  <input type="date" style={admStyles.input} value={newEvent.date} onChange={e=>setNewEvent(p=>({...p,date:e.target.value}))} />
                </div>
                <div>
                  <label style={admStyles.label}>Severity</label>
                  <select style={admStyles.select} value={newEvent.severity} onChange={e=>setNewEvent(p=>({...p,severity:e.target.value}))}>
                    <option value="moderate">Moderate</option><option value="major">Major</option><option value="catastrophic">Catastrophic</option>
                  </select>
                </div>
                <div>
                  <label style={admStyles.label}>Source Campaign</label>
                  <select style={admStyles.select} value={newEvent.sourceCampaignId} onChange={e=>setNewEvent(p=>({...p,sourceCampaignId:e.target.value}))}>
                    <option value="">— None —</option>
                    {campaigns.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
                  </select>
                </div>
                <div>
                  <label style={admStyles.label}>Tags (comma separated)</label>
                  <input style={admStyles.input} value={newEvent.tags} onChange={e=>setNewEvent(p=>({...p,tags:e.target.value}))} placeholder="political, magic, …" />
                </div>
                <div style={{gridColumn:'1/-1'}}>
                  <label style={admStyles.label}>Affects Campaigns</label>
                  <div style={{display:'flex', flexWrap:'wrap', gap:6, marginTop:4}}>
                    {campaigns.map(c => {
                      const on = newEvent.affectedCampaignIds.includes(c.id);
                      return <button key={c.id} onClick={()=>toggleAffected(newEvent, setNewEvent, c.id)} style={{background: on?'rgba(201,162,39,0.15)':'none', border:`1px solid ${on?'#c9a227':'#2a2a32'}`, color: on?'#c9a227':'#9a9793', borderRadius:5, padding:'4px 10px', cursor:'pointer', fontSize:12, fontFamily:"'Nunito',sans-serif"}}>{c.name}</button>;
                    })}
                  </div>
                </div>
              </div>
              <div style={{display:'flex', gap:10, marginTop:14}}>
                <button style={admStyles.addBtn} onClick={addEvent} disabled={!newEvent.title.trim()}>Create</button>
                <button style={{...admStyles.addBtn, background:'none', borderColor:'#3a3a42', color:'#9a9793'}} onClick={()=>{setShowAddEvent(false); setNewEvent(blankEvent);}}>Cancel</button>
              </div>
            </div>
          )}

          {events.map(ev => (
            editEventId === ev.id ? (
              <div key={ev.id} style={{...admStyles.eventRow, flexDirection:'column', alignItems:'stretch', borderLeftColor:severityColor(ev.severity), background:'#111116'}}>
                <div style={admStyles.formTitle}>Edit {ev.title}</div>
                <div style={admStyles.formGrid}>
                  <div style={{gridColumn:'1/-1'}}><label style={admStyles.label}>Title<Req/></label><input style={admStyles.input} value={editEvent.title} onChange={e=>setEditEvent(p=>({...p,title:e.target.value}))} /></div>
                  <div style={{gridColumn:'1/-1'}}><label style={admStyles.label}>Description</label><textarea style={{...admStyles.input, minHeight:80}} value={editEvent.description} onChange={e=>setEditEvent(p=>({...p,description:e.target.value}))} /></div>
                  <div>
                    <label style={admStyles.label}>Severity</label>
                    <select style={admStyles.select} value={editEvent.severity} onChange={e=>setEditEvent(p=>({...p,severity:e.target.value}))}>
                      <option value="moderate">Moderate</option><option value="major">Major</option><option value="catastrophic">Catastrophic</option>
                    </select>
                  </div>
                  <div>
                    <label style={admStyles.label}>Visibility</label>
                    <select style={admStyles.select} value={editEvent.visibility} onChange={e=>setEditEvent(p=>({...p,visibility:e.target.value}))}>
                      <option value="all">All</option><option value="dm">DM only</option>
                    </select>
                  </div>
                  <div>
                    <label style={admStyles.label}>Tags</label>
                    <input style={admStyles.input} value={editEvent.tags} onChange={e=>setEditEvent(p=>({...p,tags:e.target.value}))} />
                  </div>
                  <div style={{gridColumn:'1/-1'}}>
                    <label style={admStyles.label}>Affects Campaigns</label>
                    <div style={{display:'flex', flexWrap:'wrap', gap:6, marginTop:4}}>
                      {campaigns.map(c => {
                        const on = editEvent.affectedCampaignIds.includes(c.id);
                        return <button key={c.id} onClick={()=>toggleAffected(editEvent, setEditEvent, c.id)} style={{background: on?'rgba(201,162,39,0.15)':'none', border:`1px solid ${on?'#c9a227':'#2a2a32'}`, color: on?'#c9a227':'#9a9793', borderRadius:5, padding:'4px 10px', cursor:'pointer', fontSize:12, fontFamily:"'Nunito',sans-serif"}}>{c.name}</button>;
                      })}
                    </div>
                  </div>
                </div>
                <div style={{display:'flex', gap:10, marginTop:12}}>
                  <button style={admStyles.addBtn} onClick={saveEditEvent}>Save</button>
                  <button style={{...admStyles.addBtn, background:'none', borderColor:'#3a3a42', color:'#9a9793'}} onClick={()=>setEditEventId(null)}>Cancel</button>
                </div>
              </div>
            ) : (
              <div key={ev.id} style={{...admStyles.eventRow, borderLeftColor: severityColor(ev.severity)}}>
                <div style={{flex:1}}>
                  <div style={{display:'flex', gap:8, marginBottom:5, alignItems:'center', flexWrap:'wrap'}}>
                    <span style={{...admStyles.severityBadge, background: severityBg(ev.severity), color: severityColor(ev.severity)}}>{ev.severity}</span>
                    <span style={{fontSize:11, color:'#6b6966'}}>{ev.date}</span>
                    {(ev.tags || []).map(t => <span key={t} style={admStyles.tag}>{t}</span>)}
                  </div>
                  <div style={{fontSize:14, fontWeight:700, color:'#e8e6e3', marginBottom:4}}>{ev.title}</div>
                  <div style={{fontSize:12, color:'#9a9793', lineHeight:1.5}}>{(ev.description || '').substring(0,140)}…</div>
                  <div style={{fontSize:11, color:'#6b6966', marginTop:6}}>
                    Visible to: <span style={{color:'#9a9793'}}>{ev.visibility || 'all'}</span> · Affects: {(ev.affectedCampaigns || []).map(c => <span key={c.id} style={{color:'#c9a227', marginLeft:4}}>{c.name}</span>)}
                  </div>
                </div>
                <div style={{display:'flex', flexDirection:'column', gap:6, marginLeft:16, flexShrink:0}}>
                  <button style={admStyles.actionBtn} onClick={()=>startEditEvent(ev)}>Edit</button>
                  <button style={{...admStyles.actionBtn, color:'#f87171', borderColor:'rgba(248,113,113,0.2)'}} onClick={()=>deleteEvent(ev)}>Delete</button>
                </div>
              </div>
            )
          ))}
        </div>
      )}

      {/* Hard-delete confirm — typed-name gate, single instance for all kinds. */}
      {purgeTarget && (
        <TypedConfirmModal
          title={`Purge ${purgeTarget.kind}`}
          body={purgeTarget.body}
          expectedText={purgeTarget.name}
          confirmLabel={`Delete ${purgeTarget.kind} forever`}
          busy={purgeBusy}
          onCancel={() => { if (!purgeBusy) setPurgeTarget(null); }}
          onConfirm={runPurge}
        />
      )}
    </div>
  );
};

const admStyles = {
  page: { padding:'32px 36px', fontFamily:"'Nunito',sans-serif", overflowY:'auto', height:'100%' },
  header: { marginBottom:24 },
  title: { fontFamily:"'Cinzel',serif", fontSize:24, fontWeight:700, color:'#e8e6e3', margin:'0 0 4px' },
  tabs: { display:'flex', borderBottom:'1px solid #2a2a32', marginBottom:24 },
  tab: { background:'none', borderTop:'none', borderLeft:'none', borderRight:'none', borderBottomWidth:2, borderBottomStyle:'solid', borderBottomColor:'transparent', color:'#6b6966', padding:'10px 18px', cursor:'pointer', fontSize:13, fontWeight:600, fontFamily:"'Nunito',sans-serif" },
  tabActive: { color:'#e8e6e3', borderBottomColor:'#c53030' },
  section: { background:'#1c1c22', border:'1px solid #2a2a32', borderRadius:10, padding:24 },
  sectionHeader: { display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:20 },
  addBtn: { background:'rgba(197,48,48,0.15)', border:'1px solid #c53030', color:'#e8e6e3', borderRadius:6, padding:'7px 16px', cursor:'pointer', fontSize:13, fontWeight:700, fontFamily:"'Nunito',sans-serif" },
  addForm: { background:'#111116', border:'1px solid #2a2a32', borderRadius:8, padding:20, marginBottom:20 },
  formTitle: { fontSize:13, fontWeight:700, color:'#9a9793', textTransform:'uppercase', letterSpacing:'0.08em', marginBottom:14 },
  formGrid: { display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 },
  label: { fontSize:11, color:'#6b6966', fontWeight:700, textTransform:'uppercase', letterSpacing:'0.08em', display:'block', marginBottom:4 },
  input: { width:'100%', background:'#1c1c22', border:'1px solid #3a3a42', borderRadius:6, color:'#e8e6e3', padding:'8px 12px', fontSize:13, outline:'none', fontFamily:"'Nunito',sans-serif", boxSizing:'border-box' },
  select: { width:'100%', background:'#1c1c22', border:'1px solid #3a3a42', borderRadius:6, color:'#e8e6e3', padding:'8px 12px', fontSize:13, cursor:'pointer', fontFamily:"'Nunito',sans-serif", boxSizing:'border-box' },
  table: { width:'100%', borderCollapse:'collapse' },
  th: { fontSize:11, fontWeight:800, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.1em', padding:'8px 12px', textAlign:'left', borderBottom:'1px solid #2a2a32' },
  tr: { borderBottom:'1px solid #1c1c22' },
  td: { padding:'12px 12px', verticalAlign:'middle' },
  avatar: { width:30, height:30, borderRadius:'50%', display:'flex', alignItems:'center', justifyContent:'center', fontSize:11, fontWeight:700, color:'#fff', flexShrink:0 },
  roleBadge: { fontSize:11, fontWeight:700, padding:'2px 8px', borderRadius:20, textTransform:'capitalize' },
  campaignTag: { fontSize:11, background:'#2a2a32', color:'#9a9793', padding:'2px 8px', borderRadius:20 },
  authTag: { fontSize:11, background:'rgba(30,107,60,0.1)', color:'#22c55e', border:'1px solid rgba(34,197,94,0.2)', padding:'2px 8px', borderRadius:20 },
  actionBtn: { background:'none', border:'1px solid #2a2a32', color:'#9a9793', borderRadius:5, padding:'4px 10px', cursor:'pointer', fontSize:12, fontFamily:"'Nunito',sans-serif" },
  campaignRow: { display:'flex', background:'#111116', border:'1px solid #2a2a32', borderRadius:8, overflow:'hidden', marginBottom:10 },
  campaignStripe: { width:6, flexShrink:0 },
  eventRow: { display:'flex', gap:12, alignItems:'flex-start', borderLeft:'3px solid', padding:'14px 16px', background:'#111116', borderRadius:6, marginBottom:10 },
  severityBadge: { fontSize:10, fontWeight:700, textTransform:'uppercase', letterSpacing:'0.1em', padding:'2px 8px', borderRadius:20 },
  tag: { fontSize:10, background:'#2a2a32', color:'#6b6966', borderRadius:20, padding:'2px 7px' },
};

// ─ CampaignRoster — DMs + Co-DMs + Players panel in admin campaign row ─────
// Each member shows their role with a color-coded badge. Click the badge to
// cycle DM → Co-DM → Player → DM (with last-DM guard enforced server-side).
// "+ Add member" opens a small inline select with role picker.
const CampaignRoster = ({ campaign, users, adding, onToggleAdd, onAdd, onChangeRole, onRemove }) => {
  const members = campaign.members || [];
  const memberIds = members.map(m => m.userId);
  const ROLE_NEXT = { 'DM':'Co-DM', 'Co-DM':'Player', 'Player':'DM' };
  const roleBg = (r) => r === 'DM' ? 'rgba(197,48,48,0.18)' : r === 'Co-DM' ? 'rgba(180,83,9,0.18)' : 'rgba(30,107,60,0.18)';
  const roleColorOf = (r) => r === 'DM' ? '#f87171' : r === 'Co-DM' ? '#fbbf24' : '#22c55e';

  const [addUserId, setAddUserId] = React.useState('');
  const [addRole,   setAddRole]   = React.useState('Player');
  React.useEffect(() => { if (!adding) { setAddUserId(''); setAddRole('Player'); } }, [adding]);

  return (
    <div>
      <div style={{fontSize:10, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.1em', marginBottom:6}}>Members</div>
      <div style={{display:'flex', gap:6, flexWrap:'wrap', alignItems:'center'}}>
        {members.map(m => (
          <div key={m.userId} style={{display:'inline-flex', alignItems:'center', gap:6, background:'#16161b', border:'1px solid #2a2a32', borderRadius:20, padding:'3px 4px 3px 10px'}}>
            <span style={{fontSize:13, color:'#e8e6e3'}}>{m.name}</span>
            <button
              title="Click to cycle role (DM → Co-DM → Player). Server enforces last-DM rule."
              onClick={() => onChangeRole(m.userId, ROLE_NEXT[m.role] || 'Player')}
              style={{
                background: roleBg(m.role), color: roleColorOf(m.role),
                border: '1px solid ' + roleColorOf(m.role) + '55',
                borderRadius: 12, padding: '1px 8px', fontSize:10, fontWeight:700,
                fontFamily:"'Nunito',sans-serif", cursor:'pointer', textTransform:'uppercase', letterSpacing:'0.06em',
              }}>{m.role}</button>
            <button title="Remove from campaign" onClick={() => onRemove(m.userId, m.name)}
              style={{background:'transparent', border:'none', color:'#6b6966', fontSize:14, cursor:'pointer', padding:'2px 4px'}}>×</button>
          </div>
        ))}
        <button style={{...admStyles.avatar, background:'#1c1c22', border:'1px dashed #3a3a42', color:'#6b6966', width:26, height:26, fontSize:14, cursor:'pointer'}} onClick={onToggleAdd}>+</button>
      </div>
      {adding && (
        <div style={{marginTop:8, display:'flex', gap:6, alignItems:'center', flexWrap:'wrap'}}>
          <select style={{...admStyles.select, width:'auto'}} value={addUserId} onChange={e=>setAddUserId(e.target.value)}>
            <option value="">— pick a user —</option>
            {users.filter(u => u.isActive && !memberIds.includes(u.id)).map(u => <option key={u.id} value={u.id}>{u.name}{u.isAdmin ? ' (admin)' : ''}</option>)}
          </select>
          <select style={{...admStyles.select, width:'auto'}} value={addRole} onChange={e=>setAddRole(e.target.value)}>
            <option>DM</option><option>Co-DM</option><option>Player</option>
          </select>
          <button style={admStyles.actionBtn} disabled={!addUserId} onClick={() => addUserId && onAdd(addUserId, addRole)}>Add</button>
          <button style={admStyles.actionBtn} onClick={onToggleAdd}>Cancel</button>
        </div>
      )}
    </div>
  );
};

// ─ Realm form fields (shared by add + edit) ────────────────────────────────
const RealmFormFields = ({ value, onChange }) => {
  const set = (k, v) => onChange({ ...value, [k]: v });
  const setDefault = (k, v) => onChange({ ...value, defaults: { ...value.defaults, [k]: v } });
  return (
    <div style={admStyles.formGrid}>
      <div><label style={admStyles.label}>Name<Req/></label><input style={admStyles.input} value={value.name} onChange={e=>set('name', e.target.value)} placeholder="Kamia" /></div>
      <div><label style={admStyles.label}>Slug<Req/></label><input style={admStyles.input} value={value.slug} onChange={e=>set('slug', e.target.value.toLowerCase().replace(/[^a-z0-9-]/g,''))} placeholder="kamia (kebab-case, used in URLs)" /></div>
      <div style={{gridColumn:'1/-1'}}><label style={admStyles.label}>Description</label><textarea style={{...admStyles.input, minHeight:60}} value={value.description} onChange={e=>set('description', e.target.value)} /></div>
      <div>
        <label style={admStyles.label}>Org Kind</label>
        <select style={admStyles.select} value={value.orgKind} onChange={e=>set('orgKind', e.target.value)}>
          <option value="guild">Guild</option>
          <option value="tribe">Tribe</option>
          <option value="none">None</option>
        </select>
      </div>
      <div>
        <label style={admStyles.label}>Map Image URL</label>
        <input style={admStyles.input} value={value.mapImageUrl} onChange={e=>set('mapImageUrl', e.target.value)} placeholder="/map.jpg" />
      </div>
      <div style={{gridColumn:'1/-1'}}>
        <label style={admStyles.label}>Default Features (campaign overrides win)</label>
        <div style={{display:'flex', gap:14, flexWrap:'wrap', marginTop:6}}>
          {[['org', 'Org (Guild/Tribe)'], ['association', 'Association / Quests'], ['worldMap', 'World Map + Events'], ['shops', 'Shops + Forge + Homebrew']].map(([key, label]) => (
            <label key={key} style={{display:'flex', alignItems:'center', gap:6, fontSize:12, color:'#9a9793', cursor:'pointer'}}>
              <input type="checkbox" checked={!!value.defaults[key]} onChange={e=>setDefault(key, e.target.checked)} />
              {label}
            </label>
          ))}
        </div>
      </div>
    </div>
  );
};

// ─ Feature toggle editor (per-campaign override) ───────────────────────────
// Tri-state per key: inherit (null/undefined) / on (true) / off (false). Click
// cycles through the three states. Disabled keys are visibly muted so DM can
// see what the realm gives them by default before deciding to override.
const FeatureToggleEditor = ({ realm, value, onChange }) => {
  const realmDefaults = (() => { try { return JSON.parse(realm?.defaultFeaturesJson || '{}'); } catch { return {}; } })();
  const labels = { org:'Org', association:'Association', worldMap:'World Map+Events', shops:'Shops+Forge+Homebrew' };
  const cycle = (k) => {
    const cur = value[k];
    let next;
    if (cur === undefined || cur === null) next = true;
    else if (cur === true) next = false;
    else next = undefined;
    const out = { ...value };
    if (next === undefined) delete out[k]; else out[k] = next;
    onChange(out);
  };
  const stateOf = (k) => {
    const v = value[k];
    if (v === true)  return { label:'ON',      color:'#22c55e', bg:'rgba(34,197,94,0.10)' };
    if (v === false) return { label:'OFF',     color:'#c53030', bg:'rgba(197,48,48,0.10)' };
    const realmOn = realmDefaults[k] !== false;
    return { label: 'INHERIT (' + (realmOn ? 'on' : 'off') + ')', color:'#9a9793', bg:'rgba(255,255,255,0.04)' };
  };
  return (
    <div>
      <label style={admStyles.label}>Features ({realm ? `realm "${realm.name}" defaults baseline` : 'no realm — all default ON'})</label>
      <div style={{display:'flex', flexWrap:'wrap', gap:8, marginTop:6}}>
        {api.FEATURE_KEYS.map(k => {
          const s = stateOf(k);
          return (
            <button key={k} type="button" onClick={()=>cycle(k)} style={{background: s.bg, border:`1px solid ${s.color}33`, color: s.color, borderRadius:6, padding:'6px 10px', cursor:'pointer', fontSize:12, fontFamily:"'Nunito',sans-serif"}}>
              {labels[k]}: <strong>{s.label}</strong>
            </button>
          );
        })}
      </div>
      <div style={{fontSize:11, color:'#6b6966', marginTop:6}}>Click to cycle inherit → on → off → inherit.</div>
    </div>
  );
};

Object.assign(window, { AdminView, admStyles, RealmFormFields, FeatureToggleEditor });
