// Admin: Live Map · Settings · Notifications · Bulk actions helper

// ── 1. LIVE ADMIN MAP ────────────────────────────────────────────────────
// Real positions, real names — admin-only. Plus a full pin editor so
// staff can add/move/delete map markers (traders, rigs, events, etc).
// Admin sees ALL pins including non-public ones (hidden trader).

function hashToFloat(s, salt = 0) {
  let h = (salt * 2654435761) >>> 0;
  for (let i = 0; i < s.length; i++) h = ((h * 31 + s.charCodeAt(i)) >>> 0);
  return ((h % 100000) / 100000);
}

function AdminMap() {
  const [tick, setTick] = React.useState(0);
  const [selected, setSelected] = React.useState(null);
  const [showLabels, setShowLabels] = React.useState(true);
  const [showPlayers, setShowPlayers] = React.useState(true);
  const [showPins, setShowPins] = React.useState(true);
  const [mode, setMode] = React.useState("view"); // view | add | move | del
  const [pins, savePins] = usePins();
  const [pinForm, setPinForm] = React.useState(null); // {x,y,clientX,clientY,editing?}
  const [coord, setCoord] = React.useState(null);
  const [dragging, setDragging] = React.useState(null);
  const [dropTarget, setDropTarget] = React.useState(false);
  const mapImage = useMapImage();
  const stageRef = React.useRef(null);

  // Drift player positions every 8s to simulate movement
  React.useEffect(() => {
    const id = setInterval(() => setTick(t => t + 1), 8000);
    return () => clearInterval(id);
  }, []);

  // Live player positions are not wired up yet (Mission 10 / CFTools / RPT
  // scraper). Empty until then so the admin map stays clean.
  const players = [];

  // Stage mouse handlers (coord readout + add-click + drag-move + drop-image)
  const eventToNorm = (e) => {
    const r = stageRef.current.getBoundingClientRect();
    return {
      x: Math.max(0, Math.min(1, (e.clientX - r.left) / r.width)),
      y: Math.max(0, Math.min(1, (e.clientY - r.top) / r.height)),
    };
  };
  const onStageMove = (e) => {
    const { x, y } = eventToNorm(e);
    const w = DAYZ.normToWorld(x, y);
    setCoord({ x, y, wx: w.x, wz: w.z, grid: DAYZ.normToGrid(x, y) });
    if (dragging) {
      savePins(pins.map(p => p.id === dragging ? { ...p, x, y } : p));
    }
  };
  const onStageClick = (e) => {
    if (mode === "add" && stageRef.current.contains(e.target)) {
      const { x, y } = eventToNorm(e);
      setPinForm({
        x, y, clientX: e.clientX - stageRef.current.getBoundingClientRect().left,
        clientY: e.clientY - stageRef.current.getBoundingClientRect().top,
        draft: { type: "event", label: "", desc: "", public: true, expiresInH: 0 },
      });
    }
  };
  const onPinDown = (e, p) => {
    e.stopPropagation();
    if (mode === "move") setDragging(p.id);
    else if (mode === "del") {
      if (confirm(`Delete pin "${p.label}"?`)) savePins(pins.filter(x => x.id !== p.id));
    } else if (mode === "view") {
      setSelected(p);
    }
  };
  const onStageUp = () => setDragging(null);

  // ── pin form save ──
  const savePin = () => {
    const f = pinForm.draft;
    const id = pinForm.editing || ("pin-" + Date.now().toString(36));
    const expiresAt = f.expiresInH > 0 ? Date.now() + f.expiresInH * 3600 * 1000 : null;
    const newPin = { id, x: pinForm.x, y: pinForm.y, type: f.type,
                     label: f.label || "Untitled pin", desc: f.desc,
                     public: f.public, expiresAt };
    const next = pinForm.editing ? pins.map(p => p.id === id ? newPin : p) : [...pins, newPin];
    savePins(next);
    setPinForm(null);
    setMode("view");
  };

  // ── image drop ──
  const onDragOver = (e) => { e.preventDefault(); setDropTarget(true); };
  const onDragLeave = () => setDropTarget(false);
  const onDrop = (e) => {
    e.preventDefault();
    setDropTarget(false);
    const f = e.dataTransfer.files?.[0];
    if (!f || !f.type.startsWith("image/")) return;
    const reader = new FileReader();
    reader.onload = ev => window.ShovelheadMapImage.set(ev.target.result);
    reader.readAsDataURL(f);
  };
  const onPickImage = () => {
    const input = document.createElement("input");
    input.type = "file"; input.accept = "image/*";
    input.onchange = e => {
      const f = e.target.files?.[0];
      if (!f) return;
      const reader = new FileReader();
      reader.onload = ev => window.ShovelheadMapImage.set(ev.target.result);
      reader.readAsDataURL(f);
    };
    input.click();
  };

  return (
    <>
      <div className="adm-mapedit-toolbar">
        {[
          ["view","View"], ["add","+ Add pin"], ["move","Move"], ["del","Delete"],
        ].map(([k, l]) => (
          <button key={k} className={"chip" + (mode === k ? " on" : "")} onClick={() => { setMode(k); setPinForm(null); setSelected(null); }}>{l}</button>
        ))}
        <span style={{width:1,height:20,background:"var(--line-2)",margin:"0 6px"}}></span>
        <label className="adm-check"><input type="checkbox" checked={showPins} onChange={e => setShowPins(e.target.checked)} /><span>Map pins ({pins.length})</span></label>
        <label className="adm-check"><input type="checkbox" checked={showLabels} onChange={e => setShowLabels(e.target.checked)} /><span>Labels</span></label>
        <span className="spacer"></span>
        {coord && (
          <span className="readout">
            GRID <strong>{coord.grid}</strong> · DAYZ <strong>{coord.wx}</strong>, <strong>{coord.wz}</strong>
          </span>
        )}
      </div>

      <div className="adm-map-wrap">
        <div
          ref={stageRef}
          className={`adm-map-stage mode-${mode}${dropTarget ? " drop-target" : ""}`}
          onMouseMove={onStageMove}
          onMouseUp={onStageUp}
          onMouseLeave={() => { setCoord(null); setDragging(null); }}
          onClick={onStageClick}
          onDragOver={onDragOver}
          onDragLeave={onDragLeave}
          onDrop={onDrop}
        >
          {mapImage ? (
            <img src={mapImage} className="map-bg" alt="Chernarus" draggable="false" />
          ) : (
            <svg className="map-svg" viewBox="0 0 100 100" preserveAspectRatio="none">
              <rect width="100" height="100" fill="var(--bg-3)"/>
              <path d="M3 8 Q12 4 28 6 Q45 3 60 5 Q78 4 92 10 Q97 22 95 38 Q97 55 92 68 Q88 80 78 86 Q60 91 42 88 Q22 91 10 82 Q3 70 5 50 Q2 28 3 8 Z"
                    fill="var(--bg-2)" stroke="var(--line-2)" strokeWidth="0.3"/>
              {Array.from({length: 14}).map((_, i) => (
                <React.Fragment key={i}>
                  <line x1={(i+1)*100/15} x2={(i+1)*100/15} y1="0" y2="100" stroke="var(--line)" strokeWidth="0.15" strokeDasharray="0.5 1"/>
                  <line x1="0" x2="100" y1={(i+1)*100/15} y2={(i+1)*100/15} stroke="var(--line)" strokeWidth="0.15" strokeDasharray="0.5 1"/>
                </React.Fragment>
              ))}
            </svg>
          )}

          {/* MAP PINS (admin sees all) */}
          {showPins && pins.map(p => {
            const expired = p.expiresAt && p.expiresAt < Date.now();
            if (expired) return null;
            const remainH = p.expiresAt ? Math.max(0, Math.round((p.expiresAt - Date.now()) / 3600000)) : null;
            return (
              <div
                key={p.id}
                className={`adm-pin type-${p.type}`}
                style={{ left: p.x*100 + "%", top: p.y*100 + "%" }}
                onMouseDown={(e) => onPinDown(e, p)}
                onClick={(e) => e.stopPropagation()}
              >
                <div className="adm-pin-dot"></div>
                <div className="adm-pin-ring"></div>
                {p.public === false && (
                  <div style={{position:"absolute",top:-2,left:-2,width:18,height:18,border:"1px dashed var(--accent)",borderRadius:"50%",pointerEvents:"none"}}></div>
                )}
                {remainH !== null && <div className="adm-pin-expiry">{remainH}h</div>}
                {showLabels && <div className="adm-pin-label">{p.label}</div>}
              </div>
            );
          })}

          {/* PLAYER PINS */}
          {showPlayers && players.map(p => {
            const t = TIERS.find(x => x.key === p.tier);
            const color = t?.color || "var(--ink)";
            const isSel = selected?.steamId === p.steamId;
            return (
              <div
                key={p.steamId}
                className={"adm-pin" + (isSel ? " on" : "")}
                style={{ left: p.x*100 + "%", top: p.y*100 + "%", "--c": color }}
                onClick={e => { e.stopPropagation(); setSelected(p); }}
              >
                <div className="adm-pin-dot"></div>
                <div className="adm-pin-ring"></div>
                {showLabels && <div className="adm-pin-label">{p.name}</div>}
              </div>
            );
          })}

          {/* pin form */}
          {pinForm && (
            <div className="adm-pin-form" style={{ left: pinForm.clientX + 10, top: pinForm.clientY + 10 }} onClick={e => e.stopPropagation()}>
              <h4>{pinForm.editing ? "Edit pin" : "New pin"}</h4>
              <div className="row">
                <label>Type</label>
                <select className="adm-input" value={pinForm.draft.type} onChange={e => setPinForm(f => ({...f, draft: {...f.draft, type: e.target.value}}))}>
                  <option value="event">Event</option>
                  <option value="rig">Oil rig</option>
                  <option value="trader_hidden">Hidden trader</option>
                  <option value="mil">Military</option>
                  <option value="teleport">Trader teleport</option>
                  <option value="bm">Black market</option>
                </select>
              </div>
              <div className="row">
                <label>Label</label>
                <input className="adm-input" value={pinForm.draft.label} onChange={e => setPinForm(f => ({...f, draft: {...f.draft, label: e.target.value}}))} placeholder="e.g. Convoy at F5" autoFocus />
              </div>
              <div className="row">
                <label>Desc</label>
                <input className="adm-input" value={pinForm.draft.desc} onChange={e => setPinForm(f => ({...f, draft: {...f.draft, desc: e.target.value}}))} placeholder="(optional)" />
              </div>
              <div className="row">
                <label>Public?</label>
                <Switch on={pinForm.draft.public} onChange={v => setPinForm(f => ({...f, draft: {...f.draft, public: v}}))} />
              </div>
              <div className="row">
                <label>Expires</label>
                <select className="adm-input" value={pinForm.draft.expiresInH} onChange={e => setPinForm(f => ({...f, draft: {...f.draft, expiresInH: Number(e.target.value)}}))}>
                  <option value="0">Never (permanent)</option>
                  <option value="1">In 1 hour</option>
                  <option value="6">In 6 hours</option>
                  <option value="24">In 24 hours</option>
                  <option value="168">In 7 days</option>
                </select>
              </div>
              <div className="row" style={{gridTemplateColumns:"80px 1fr"}}>
                <label>Coords</label>
                <div className="admin-mono" style={{fontSize:11,color:"var(--ink-3)"}}>
                  GRID {DAYZ.normToGrid(pinForm.x, pinForm.y)} · DAYZ {DAYZ.normToWorld(pinForm.x, pinForm.y).x}, {DAYZ.normToWorld(pinForm.x, pinForm.y).z}
                </div>
              </div>
              <div className="actions">
                <button className="admin-btn" onClick={() => { setPinForm(null); setMode("view"); }}>Cancel</button>
                <button className="admin-btn" style={{borderColor:"var(--accent)",color:"var(--accent)"}} onClick={savePin}>SAVE</button>
              </div>
            </div>
          )}

          <div className="adm-map-stamp">
            CHERNARUS · ADMIN LIVE · POLL 15s · TICK #{tick}
            {mode !== "view" && <span style={{color:"var(--accent)",marginLeft:12}}>· MODE: {mode.toUpperCase()}</span>}
          </div>
          <div className="adm-map-count">{pins.length} PINS</div>

        </div>

        <aside className="adm-map-side">
          {!selected && (
            <div className="adm-map-empty">
              <div className="admin-mono" style={{color:"var(--ink-3)",letterSpacing:"0.14em",textTransform:"uppercase",fontSize:11}}>— EDITOR MODES</div>
              <ul style={{listStyle:"none",padding:0,margin:"12px 0 24px",fontSize:13,color:"var(--ink-2)",lineHeight:1.6}}>
                <li><strong style={{color:"var(--ink)"}}>VIEW</strong> · click pins for detail</li>
                <li><strong style={{color:"var(--ink)"}}>+ ADD</strong> · click map to place new pin</li>
                <li><strong style={{color:"var(--ink)"}}>MOVE</strong> · drag any pin to reposition</li>
                <li><strong style={{color:"var(--ink)"}}>DELETE</strong> · click pin to remove</li>
              </ul>

              <div className="adm-legend">
                <div className="adm-legend-h">PIN LEGEND</div>
                <div className="adm-legend-row"><span className="adm-legend-dot" style={{background:"var(--accent)", boxShadow:"0 0 0 1px var(--accent)"}}></span><span>Hidden trader (admin-only)</span></div>
                <div className="adm-legend-row"><span className="adm-legend-dot" style={{background:"var(--accent-2)"}}></span><span>Oil rig / event</span></div>
                <div className="adm-legend-row"><span className="adm-legend-dot" style={{background:"var(--ink)"}}></span><span>Military</span></div>
                <div className="adm-legend-row"><span className="adm-legend-dot" style={{background:"var(--good)"}}></span><span>Trader teleport</span></div>
              </div>
              <div className="adm-legend" style={{marginTop:14}}>
                <div className="adm-legend-h">DATA SOURCES</div>
                <div className="admin-mono" style={{fontSize:11,color:"var(--ink-3)",lineHeight:1.7}}>
                  PINS · localStorage <span style={{color:"var(--ink-4)"}}>(DB in prod)</span><br/>
                  IMAGE · localStorage <span style={{color:"var(--ink-4)"}}>(S3 in prod)</span><br/>
                  PLAYERS · RPT poll <span style={{color:"var(--ink-4)"}}>(15s)</span><br/>
                  COORDS · DAYZ X/Z 0–15360
                </div>
              </div>
            </div>
          )}
          {selected && <AdminPlayerDetail player={selected} onClose={() => setSelected(null)} />}
        </aside>
      </div>
    </>
  );
}

function AdminPlayerDetail({ player, onClose }) {
  const t = TIERS.find(x => x.key === player.tier);
  return (
    <div className="adm-detail">
      <div className="adm-detail-head">
        <div>
          <div className="admin-mono" style={{color:"var(--ink-3)",fontSize:10,letterSpacing:"0.18em"}}>SELECTED PLAYER</div>
          <div style={{fontFamily:"Anton",fontSize:26,letterSpacing:"0.03em",marginTop:4}}>{player.name}</div>
        </div>
        <button className="acct-x" onClick={onClose}>✕</button>
      </div>
      <div className="adm-detail-grid">
        <div><div className="dk">STEAM ID</div><div className="dv admin-mono">{player.steamId}</div></div>
        <div><div className="dk">COUNTRY</div><div className="dv">{player.country}</div></div>
        <div><div className="dk">SESSION</div><div className="dv">{player.joinedAgo}</div></div>
        <div><div className="dk">K/D (SESSION)</div><div className="dv admin-mono">{player.kdSession}</div></div>
        <div><div className="dk">TIER</div><div className="dv">{fmtTier(player.tier)}</div></div>
        <div><div className="dk">WARNS</div><div className="dv">{player.warns === 0 ? "—" : "× " + player.warns}</div></div>
      </div>
      <div className="adm-detail-actions">
        <button className="admin-btn">DM PLAYER</button>
        <button className="admin-btn">VIEW HISTORY</button>
        <button className="admin-btn">TELEPORT TO</button>
        <button className="admin-btn">SPECTATE</button>
        <button className="admin-btn">KICK</button>
        <button className="admin-btn danger">BAN</button>
      </div>
    </div>
  );
}

// ── 2. SETTINGS TAB ──────────────────────────────────────────────────────
function AdminSettings() {
  const [server, setServer] = React.useState({
    name: SERVER.name, ip: SERVER.ip, port: SERVER.port,
    slots: SERVER.slots, version: SERVER.version,
    region: SERVER.region, wipe: SERVER.wipe,
  });
  const [rules, setRules] = React.useState(RULES);
  const [tiers, setTiers] = React.useState(TIERS);
  const [dirty, setDirty] = React.useState(false);

  const onServer = (k, v) => { setServer(s => ({...s, [k]: v})); setDirty(true); };
  const onRule = (i, k, v) => {
    setRules(rs => rs.map((r, idx) => idx === i ? {...r, [k]: v} : r));
    setDirty(true);
  };
  const delRule = (i) => { setRules(rs => rs.filter((_, idx) => idx !== i)); setDirty(true); };
  const addRule = () => {
    setRules(rs => [...rs, { n: String(rs.length + 1).padStart(2,"0"), t: "New rule", d: "Describe the rule here." }]);
    setDirty(true);
  };
  const onTier = (i, k, v) => {
    setTiers(ts => ts.map((t, idx) => idx === i ? {...t, [k]: v} : t));
    setDirty(true);
  };

  return (
    <>
      <div className="admin-toolbar">
        <div className="admin-mono" style={{color:"var(--ink-3)"}}>SERVER & PRICING CONFIG</div>
        <div className="admin-toolbar-actions">
          {dirty && <span className="admin-mono" style={{color:"var(--accent)"}}>● UNSAVED CHANGES</span>}
          <button className="admin-btn" disabled={!dirty} onClick={() => setDirty(false)}>SAVE ALL</button>
          <button className="admin-btn" onClick={() => { setDirty(false); }}>DISCARD</button>
        </div>
      </div>

      <div className="adm-settings">
        <section className="admin-panel">
          <header><h3>Server</h3></header>
          <div className="adm-form">
            <SettingRow label="Server name"><input className="adm-input" value={server.name} onChange={e => onServer("name", e.target.value)} /></SettingRow>
            <SettingRow label="IP / port">
              <div style={{display:"flex",gap:8}}>
                <input className="adm-input" value={server.ip} onChange={e => onServer("ip", e.target.value)} style={{flex:2}} />
                <input className="adm-input" value={server.port} onChange={e => onServer("port", e.target.value)} style={{flex:1}} />
              </div>
            </SettingRow>
            <SettingRow label="Max slots"><input className="adm-input" type="number" value={server.slots} onChange={e => onServer("slots", e.target.value)} /></SettingRow>
            <SettingRow label="Region"><input className="adm-input" value={server.region} onChange={e => onServer("region", e.target.value)} /></SettingRow>
            <SettingRow label="DayZ version"><input className="adm-input" value={server.version} onChange={e => onServer("version", e.target.value)} /></SettingRow>
            <SettingRow label="Next wipe"><input className="adm-input" type="date" value={server.wipe} onChange={e => onServer("wipe", e.target.value)} /></SettingRow>
          </div>
        </section>

        <section className="admin-panel">
          <header>
            <h3>Rules</h3>
            <button className="admin-btn" onClick={addRule}>+ ADD RULE</button>
          </header>
          <div className="adm-rules-edit">
            {rules.map((r, i) => (
              <div key={i} className="adm-rule-edit">
                <input className="adm-input adm-input-n" value={r.n} onChange={e => onRule(i, "n", e.target.value)} />
                <div style={{flex:1, display:"flex", flexDirection:"column", gap:6}}>
                  <input className="adm-input" value={r.t} onChange={e => onRule(i, "t", e.target.value)} placeholder="Title" />
                  <textarea className="adm-input" rows="2" value={r.d} onChange={e => onRule(i, "d", e.target.value)} placeholder="Description" />
                </div>
                <button className="admin-btn danger" onClick={() => delRule(i)}>DEL</button>
              </div>
            ))}
          </div>
        </section>

        <section className="admin-panel">
          <header><h3>Priority queue tiers & pricing</h3></header>
          <div className="adm-tiers-edit">
            {tiers.map((t, i) => (
              <div key={t.key} className="adm-tier-edit">
                <div className="adm-tier-edit-head" style={{borderColor: t.color}}>
                  <span style={{color: t.color, fontFamily:"Anton", fontSize:22, letterSpacing:"0.04em"}}>{t.label.toUpperCase()}</span>
                  <span className="admin-mono" style={{fontSize:10,color:"var(--ink-3)",letterSpacing:"0.14em"}}>{t.key}</span>
                </div>
                <SettingRow label="Display name"><input className="adm-input" value={t.label} onChange={e => onTier(i, "label", e.target.value)} /></SettingRow>
                <SettingRow label="Monthly price (USD)"><input className="adm-input" type="number" value={t.price} onChange={e => onTier(i, "price", Number(e.target.value))} /></SettingRow>
                <SettingRow label="Queue skip (spots)"><input className="adm-input" type="number" value={t.queueSkip} onChange={e => onTier(i, "queueSkip", Number(e.target.value))} /></SettingRow>
                <SettingRow label="Perks (one per line)">
                  <textarea className="adm-input" rows="5" value={t.perks.join("\n")}
                            onChange={e => onTier(i, "perks", e.target.value.split("\n"))} />
                </SettingRow>
              </div>
            ))}
          </div>
        </section>
      </div>

      <div className="admin-callout" style={{borderColor:"var(--line-2)", background:"var(--panel)"}}>
        <strong style={{color:"var(--accent)"}}>How saves work in production:</strong>{" "}
        SAVE ALL fires a single PATCH to <span className="admin-mono">/api/admin/config</span> which (a) writes the new server settings to your config table, (b) regenerates <span className="admin-mono">data.js</span> on the static site, (c) creates new PayPal Plans for any tier price changes and deactivates the old ones. Audit-logged.
      </div>
    </>
  );
}

function SettingRow({ label, children }) {
  return (
    <div className="adm-row">
      <div className="adm-row-lbl">{label}</div>
      <div className="adm-row-val">{children}</div>
    </div>
  );
}

// ── 3. NOTIFICATIONS TAB ─────────────────────────────────────────────────
const NOTIF_GROUPS = [
  {
    title: "Payments",
    items: [
      { k: "pay_success",  l: "New subscription created", d: "Any tier · any user",                  def: { dm: true,  channel: true } },
      { k: "pay_failed",   l: "Payment failed",          d: "Card declined, insufficient funds",      def: { dm: true,  channel: false } },
      { k: "pay_refund",   l: "Refund issued",           d: "By staff or chargeback dispute",         def: { dm: false, channel: true } },
      { k: "pay_cancel",   l: "Subscription canceled",   d: "User cancels mid-period",                def: { dm: false, channel: true } },
    ],
  },
  {
    title: "Moderation",
    items: [
      { k: "appeal_new",   l: "New ban appeal filed",    d: "Routes to #appeals + DM Head Admin",     def: { dm: true,  channel: true } },
      { k: "ban_new",      l: "Player banned",           d: "Any staff action",                       def: { dm: false, channel: true } },
      { k: "kick_new",     l: "Player kicked",           d: "Any staff action",                       def: { dm: false, channel: false } },
      { k: "warn_new",     l: "Player warned (×3)",      d: "Auto-escalation threshold reached",      def: { dm: true,  channel: true } },
    ],
  },
  {
    title: "Server health",
    items: [
      { k: "srv_down",     l: "Server went offline",     d: "BattleMetrics ping fails 3× in a row",   def: { dm: true,  channel: true } },
      { k: "srv_up",       l: "Server came back online", d: "After downtime > 5 min",                 def: { dm: true,  channel: true } },
      { k: "srv_full",     l: "Server hit max + queue",  d: "60/60 with queue > 0",                   def: { dm: false, channel: true } },
      { k: "mod_update",   l: "Mod requires update",     d: "Workshop pushed a new version",          def: { dm: false, channel: true } },
    ],
  },
  {
    title: "Events",
    items: [
      { k: "evt_spawn",    l: "Dynamic event spawned",   d: "Convoy / heli / etc",                    def: { dm: false, channel: false } },
      { k: "evt_loot",     l: "Helicrash looted",        d: "First player picks up Tier 4 item",      def: { dm: false, channel: false } },
      { k: "evt_wipe",     l: "Wipe day T-24h reminder", d: "Daily reminder ahead of wipe",           def: { dm: true,  channel: true } },
    ],
  },
];

function AdminNotifications() {
  const initial = {};
  NOTIF_GROUPS.forEach(g => g.items.forEach(it => { initial[it.k] = { ...it.def }; }));
  const [prefs, setPrefs] = React.useState(initial);
  const [channel, setChannel] = React.useState("#staff-alerts");
  const [dmTarget, setDmTarget] = React.useState("Halberd");
  const [dirty, setDirty] = React.useState(false);
  const set = (k, kind, v) => {
    setPrefs(p => ({...p, [k]: {...p[k], [kind]: v}}));
    setDirty(true);
  };

  return (
    <>
      <div className="admin-toolbar">
        <div className="admin-mono" style={{color:"var(--ink-3)"}}>NOTIFICATION PREFERENCES · YOUR ACCOUNT</div>
        <div className="admin-toolbar-actions">
          {dirty && <span className="admin-mono" style={{color:"var(--accent)"}}>● UNSAVED</span>}
          <button className="admin-btn" disabled={!dirty} onClick={() => setDirty(false)}>SAVE PREFS</button>
        </div>
      </div>

      <div className="adm-notif-targets">
        <SettingRow label="Discord channel (channel column)">
          <input className="adm-input" value={channel} onChange={e => { setChannel(e.target.value); setDirty(true); }} />
        </SettingRow>
        <SettingRow label="DM target (DM column)">
          <input className="adm-input" value={dmTarget} onChange={e => { setDmTarget(e.target.value); setDirty(true); }} />
        </SettingRow>
      </div>

      <div className="adm-notif">
        <div className="adm-notif-head">
          <div></div>
          <div>EVENT</div>
          <div style={{textAlign:"center"}}>DM YOU</div>
          <div style={{textAlign:"center"}}>POST TO <span style={{color:"var(--ink)"}}>{channel}</span></div>
        </div>
        {NOTIF_GROUPS.map(g => (
          <React.Fragment key={g.title}>
            <div className="adm-notif-sep">
              <span>— {g.title.toUpperCase()}</span>
            </div>
            {g.items.map(it => (
              <div key={it.k} className="adm-notif-row">
                <div className="admin-mono" style={{color:"var(--ink-4)",fontSize:10}}>{it.k}</div>
                <div>
                  <div style={{fontWeight:600}}>{it.l}</div>
                  <div className="muted" style={{fontSize:12}}>{it.d}</div>
                </div>
                <div style={{textAlign:"center"}}>
                  <Switch on={prefs[it.k]?.dm} onChange={v => set(it.k, "dm", v)} />
                </div>
                <div style={{textAlign:"center"}}>
                  <Switch on={prefs[it.k]?.channel} onChange={v => set(it.k, "channel", v)} />
                </div>
              </div>
            ))}
          </React.Fragment>
        ))}
      </div>

      <div className="admin-callout" style={{borderColor:"var(--line-2)", background:"var(--panel)"}}>
        <strong style={{color:"var(--accent)"}}>How this routes:</strong> Saves to <span className="admin-mono">staff_notifications</span> keyed by your Discord ID. The webhook handler reads it per-event and either DMs you or posts to the configured channel via the bot. Latency &lt; 2s.
      </div>
    </>
  );
}

function Switch({ on, onChange }) {
  return (
    <button className={"adm-switch" + (on ? " on" : "")} onClick={() => onChange(!on)} aria-pressed={on}>
      <span className="adm-switch-knob"></span>
    </button>
  );
}

window.AdminMap = AdminMap;
window.AdminSettings = AdminSettings;
window.AdminNotifications = AdminNotifications;
