// ── Admin extras: New Slot modal, New User modal, Analytics access manager,
//    Docs view, Settings view. All attached to window.

// ── Modal shell ─────────────────────────────────────────────
function Modal({ onClose, title, subtitle, children, width = 560, footer }) {
  React.useEffect(() => {
    const esc = (e) => e.key === 'Escape' && onClose();
    document.addEventListener('keydown', esc);
    return () => document.removeEventListener('keydown', esc);
  }, [onClose]);
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal-sheet fade-up" onClick={(e) => e.stopPropagation()} style={{ width, maxWidth: '100%' }}>
        <button className="modal-close" onClick={onClose} title="Close"><Icon name="close" size={14}/></button>
        <div style={{ padding: '28px 32px 12px', borderBottom: '1px solid var(--rule)' }}>
          <h2 className="serif" style={{ fontSize: 22, fontWeight: 500, letterSpacing: '-0.01em', margin: 0 }}>{title}</h2>
          {subtitle && <div style={{ fontSize: 12, color: 'var(--ink-4)', marginTop: 4 }}>{subtitle}</div>}
        </div>
        <div style={{ padding: '20px 32px 24px', maxHeight: '72vh', overflowY: 'auto' }}>{children}</div>
        {footer && (
          <div style={{ padding: '14px 32px 22px', borderTop: '1px solid var(--rule)', display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
            {footer}
          </div>
        )}
      </div>
    </div>
  );
}

// ── Shared form controls ────────────────────────────────────
function Field({ label, hint, required, children, style }) {
  return (
    <label style={{ display: 'block', marginBottom: 14, ...style }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 5 }}>
        <span className="eyebrow" style={{ fontSize: 10 }}>{label}{required && <span style={{ color: 'var(--bad)', marginLeft: 4 }}>*</span>}</span>
        {hint && <span style={{ fontSize: 10, color: 'var(--ink-4)' }}>{hint}</span>}
      </div>
      {children}
    </label>
  );
}

const TextInput = React.forwardRef(function TextInput({ value, onChange, placeholder, mono = false, type = 'text', disabled, style: sx, ...rest }, ref) {
  const [focused, setFocused] = React.useState(false);
  return (
    <input ref={ref} type={type} value={value} onChange={(e) => onChange && onChange(e.target.value)}
      placeholder={placeholder} disabled={disabled} {...rest}
      style={{
        width: '100%', padding: '10px 12px',
        background: disabled ? 'var(--paper-3)' : 'var(--paper-2)',
        border: `1px solid ${focused ? 'var(--accent)' : 'var(--rule)'}`,
        boxShadow: focused ? '0 0 0 3px color-mix(in oklab, var(--accent) 18%, transparent)' : 'none',
        borderRadius: 6, fontSize: 13, color: 'var(--ink)',
        fontFamily: mono ? 'var(--font-mono)' : 'inherit',
        letterSpacing: mono ? '0.06em' : 'normal',
        outline: 'none', transition: 'border-color .15s, box-shadow .15s',
        opacity: disabled ? 0.6 : 1, cursor: disabled ? 'not-allowed' : 'text',
        ...sx,
      }}
      onFocus={() => setFocused(true)}
      onBlur={() => setFocused(false)}
    />
  );
});

function SelectInput({ value, onChange, options }) {
  return (
    <select value={value} onChange={(e) => onChange(e.target.value)}
      style={{
        width: '100%', padding: '10px 12px', background: 'var(--paper-2)',
        border: '1px solid var(--rule)', borderRadius: 6, fontSize: 13,
        color: 'var(--ink)', outline: 'none', cursor: 'pointer',
      }}>
      {options.map((o) => typeof o === 'string'
        ? <option key={o} value={o}>{o}</option>
        : <option key={o.value} value={o.value}>{o.label}</option>)}
    </select>
  );
}

function Toggle({ checked, onChange, label, desc }) {
  return (
    <label style={{
      display: 'flex', alignItems: 'flex-start', gap: 12, padding: '10px 12px',
      borderRadius: 6, cursor: 'pointer',
      background: checked ? 'color-mix(in oklab, var(--accent) 8%, transparent)' : 'var(--paper-2)',
      border: `1px solid ${checked ? 'color-mix(in oklab, var(--accent) 30%, var(--rule))' : 'var(--rule)'}`,
      transition: 'background .15s, border-color .15s',
    }}>
      <div style={{
        width: 34, height: 20, flexShrink: 0, borderRadius: 999,
        background: checked ? 'var(--accent)' : 'var(--paper-4)', transition: 'background .2s', marginTop: 2, position: 'relative',
      }}>
        <div style={{
          position: 'absolute', top: 2, left: checked ? 16 : 2,
          width: 16, height: 16, borderRadius: '50%', background: '#fff',
          transition: 'left .2s', boxShadow: '0 1px 3px rgba(0,0,0,0.2)',
        }}/>
      </div>
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--ink)' }}>{label}</div>
        {desc && <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 2 }}>{desc}</div>}
      </div>
      <input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked)} style={{ display: 'none' }}/>
    </label>
  );
}

// ── Multi-pill select (searchable, checkbox list, pill display) ──
function MultiPillSelect({ value, onChange, options, placeholder = 'Select…' }) {
  const [open, setOpen] = React.useState(false);
  const [q, setQ] = React.useState('');
  const ref = React.useRef(null);

  React.useEffect(() => {
    if (!open) return;
    const fn = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', fn);
    return () => document.removeEventListener('mousedown', fn);
  }, [open]);

  const filtered = options.filter((o) => !q || o.toLowerCase().includes(q.toLowerCase()));
  const toggle = (opt) => onChange(value.includes(opt) ? value.filter((v) => v !== opt) : [...value, opt]);

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <div onClick={() => setOpen((o) => !o)} style={{
        minHeight: 42, padding: '6px 10px 6px 10px', display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center',
        background: 'var(--paper-2)', border: '1px solid var(--rule)', borderRadius: 6, cursor: 'pointer',
      }}>
        {value.length === 0 && <span style={{ color: 'var(--ink-4)', fontSize: 12.5 }}>{placeholder}</span>}
        {value.map((v) => (
          <span key={v} style={{
            display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 8px', borderRadius: 999,
            fontSize: 11.5, fontWeight: 500,
            background: 'color-mix(in oklab, var(--accent) 12%, var(--card))',
            border: '1px solid color-mix(in oklab, var(--accent) 30%, var(--rule))',
            color: 'var(--ink)',
          }}>
            {v}
            <span onClick={(e) => { e.stopPropagation(); toggle(v); }}
              style={{ cursor: 'pointer', opacity: 0.5, marginLeft: 2, lineHeight: 1, fontSize: 13 }}>×</span>
          </span>
        ))}
        <span style={{ marginLeft: 'auto', color: 'var(--ink-4)', fontSize: 10 }}>▾</span>
      </div>
      {open && (
        <div className="fade-in" style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, zIndex: 60,
          background: 'var(--card)', border: '1px solid var(--rule)', borderRadius: 6,
          boxShadow: 'var(--shadow-2)', overflow: 'hidden',
        }}>
          <div style={{ padding: '6px 8px', borderBottom: '1px solid var(--rule)' }}>
            <input autoFocus value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search…"
              style={{ width: '100%', padding: '5px 8px', background: 'var(--paper-2)', border: '1px solid var(--rule)', borderRadius: 4, fontSize: 12, color: 'var(--ink)', outline: 'none' }}
            />
          </div>
          <div style={{ maxHeight: 180, overflowY: 'auto' }}>
            {filtered.map((opt) => (
              <div key={opt} onClick={() => toggle(opt)} style={{
                padding: '8px 12px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 10,
                background: value.includes(opt) ? 'color-mix(in oklab, var(--accent) 8%, transparent)' : 'transparent',
                fontSize: 12.5, transition: 'background .1s',
              }}
              onMouseEnter={(e) => { if (!value.includes(opt)) e.currentTarget.style.background = 'var(--paper-2)'; }}
              onMouseLeave={(e) => { if (!value.includes(opt)) e.currentTarget.style.background = 'transparent'; }}>
                <div style={{
                  width: 14, height: 14, borderRadius: 3, flexShrink: 0,
                  border: `1.5px solid ${value.includes(opt) ? 'var(--accent)' : 'var(--ink-4)'}`,
                  background: value.includes(opt) ? 'var(--accent)' : 'transparent',
                  display: 'grid', placeItems: 'center', color: '#fff',
                }}>
                  {value.includes(opt) && <Icon name="check" size={10}/>}
                </div>
                {opt}
              </div>
            ))}
            {filtered.length === 0 && (
              <div style={{ padding: '10px 12px', fontSize: 12, color: 'var(--ink-4)', fontStyle: 'italic' }}>No matches</div>
            )}
          </div>
          {value.length > 0 && (
            <div style={{ padding: '6px 10px', borderTop: '1px solid var(--rule)', display: 'flex', justifyContent: 'space-between' }}>
              <span style={{ fontSize: 10.5, color: 'var(--ink-4)' }}>{value.length} selected</span>
              <button onClick={() => onChange([])} style={{ fontSize: 11, color: 'var(--bad)', background: 'none', border: 'none', cursor: 'pointer' }}>Clear all</button>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ── New Slot Modal (rewritten) ──────────────────────────────
function NewSlotModal({ onClose, onCreate }) {
  const [title, setTitle] = React.useState('');
  const [depts, setDepts]   = React.useState([]);
  const [colleges, setColleges] = React.useState([]);
  const [faculties, setFaculties] = React.useState([]);
  const [attempts, setAttempts]   = React.useState(1);
  const [duration, setDuration]   = React.useState(180);
  const [expiryDate, setExpiryDate] = React.useState(() => {
    const d = new Date(); d.setDate(d.getDate() + 7);
    return d.toISOString().slice(0, 10);
  });
  const [expiryTime, setExpiryTime] = React.useState('23:59');
  const [usePasskey, setUsePasskey] = React.useState(true);
  const [passkey, setPasskey] = React.useState(() => genPasskey());
  const [analyticsAccess, setAnalyticsAccess] = React.useState(true);
  const [step, setStep] = React.useState(0);

  // Load faculty users from API
  const [facList, setFacList] = React.useState([]);
  React.useEffect(() => {
    apiFetch('/auth/admin/users')
      .then((users) => {
        const admins = (Array.isArray(users) ? users : [])
          .filter((u) => u.role === 'admin' && u.is_active)
          .map((u) => u.name);
        setFacList(admins);
      })
      .catch(() => setFacList([]));
  }, []);

  function genPasskey() {
    const chars = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
    return Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
  }

  const fallbackDeptList = (window.DEPTS && window.DEPTS.length ? window.DEPTS : ['CSE', 'ECE', 'IT', 'ADS', 'EEE', 'ME']);

  // Load colleges + departments from API
  const [apiColleges, setApiColleges] = React.useState([]);
  React.useEffect(() => {
    apiFetch('/colleges/')
      .then((d) => setApiColleges(Array.isArray(d) ? d : []))
      .catch(() => setApiColleges([]));
  }, []);
  const apiCollegeNames = apiColleges.map((c) => c.name);
  // Dept list: all departments across all API colleges, deduplicated
  const apiDeptList = React.useMemo(() => {
    const all = apiColleges.flatMap((c) => c.departments.map((d) => d.name));
    return [...new Set(all)];
  }, [apiColleges]);
  const deptList = apiDeptList.length > 0 ? apiDeptList : fallbackDeptList;
  const collegeList = apiCollegeNames.length > 0 ? apiCollegeNames : (window.COLLEGES && window.COLLEGES.length ? window.COLLEGES : []);

  const toggleCollege = (c) => setColleges((prev) =>
    prev.includes(c) ? (prev.length > 1 ? prev.filter((x) => x !== c) : prev) : [...prev, c]
  );

  const [submitting, setSubmitting] = React.useState(false);
  const [submitErr, setSubmitErr] = React.useState('');
  const [validationErr, setValidationErr] = React.useState('');
  const titleRef = React.useRef(null);

  const canCreate = title.trim().length > 3 && (!usePasskey || passkey.length >= 6);

  const handleReview = () => {
    if (title.trim().length === 0) {
      setValidationErr('Please enter a slot title.');
      titleRef.current && titleRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
      titleRef.current && titleRef.current.focus();
      return;
    }
    if (title.trim().length <= 3) {
      setValidationErr('Slot title must be more than 3 characters.');
      titleRef.current && titleRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
      titleRef.current && titleRef.current.focus();
      return;
    }
    if (usePasskey && passkey.length < 6) {
      setValidationErr('Passkey must be at least 6 characters.');
      return;
    }
    setValidationErr('');
    setStep(1);
  };

  const submit = async () => {
    if (!canCreate) return;
    setSubmitting(true); setSubmitErr('');
    try {
      const payload = {
        title: title.trim(),
        description: '',
        expires_at: `${expiryDate}T${expiryTime}:00`,
        max_attempts: attempts === 999 ? null : attempts,
        allowed_dept: depts.length ? depts[0] : null,
        allowed_college: colleges.length ? colleges.join(',') : null,
        generate_passkey: usePasskey,
        analytics_access: analyticsAccess,
      };
      if (usePasskey) payload.passkey = passkey;
      const data = await apiFetch('/slots/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
      onCreate({ title, depts, colleges, faculties, attempts, duration, expiryDate, expiryTime, analyticsAccess, passkey: usePasskey ? (data.passkey || passkey) : null });
    } catch (ex) {
      setSubmitErr(ex.message || 'Failed to create slot');
    } finally {
      setSubmitting(false);
    }
  };

  const reviewRows = [
    ['Departments', depts.length ? depts.join(', ') : 'All departments'],
    ['Colleges',    colleges.join(', ')],
    ['Faculty',     faculties.length ? faculties.join(', ') : '—'],
    ['Attempts',    attempts === 999 ? 'Unlimited' : attempts],
    ['Duration',    `${duration}s (${Math.round(duration / 60)} min)`],
    ['Expires',     `${expiryDate} at ${expiryTime}`],
  ];

  return (
    <Modal onClose={onClose} title="Create assessment slot"
      subtitle="Configure a new evaluation session for your institution."
      width={640}
      footer={
        <>
          <button className="btn btn-sm btn-ghost" onClick={onClose}>Cancel</button>
          {step === 0 ? (
            <>
              {validationErr && (
                <span style={{ fontSize: 11, color: 'var(--bad)', flex: 1, textAlign: 'right' }}>
                  <Icon name="close" size={10}/> {validationErr}
                </span>
              )}
              <button className="btn btn-sm btn-primary" onClick={handleReview}>
                Review <Icon name="arrowRight" size={11}/>
              </button>
            </>
          ) : (
            <>
              <button className="btn btn-sm" onClick={() => setStep(0)} disabled={submitting}><Icon name="arrowLeft" size={11}/> Back</button>
              {submitErr && <span style={{ fontSize: 11, color: 'var(--bad)', flex: 1 }}>{submitErr}</span>}
              <button className="btn btn-sm btn-primary" onClick={submit} disabled={submitting}>{submitting ? 'Creating…' : <><Icon name="check" size={11}/> Create slot</>}</button>
            </>
          )}
        </>
      }>

      {/* Step indicator */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 20 }}>
        {['Details', 'Review'].map((lbl, i) => (
          <React.Fragment key={lbl}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <div style={{
                width: 22, height: 22, borderRadius: '50%',
                background: step >= i ? 'var(--accent)' : 'var(--paper-3)',
                color: step >= i ? '#fff' : 'var(--ink-4)',
                display: 'grid', placeItems: 'center', fontSize: 11, fontWeight: 600, transition: 'all .2s',
              }}>{i + 1}</div>
              <span style={{ fontSize: 11.5, color: step >= i ? 'var(--ink)' : 'var(--ink-4)', fontWeight: step === i ? 600 : 400 }}>{lbl}</span>
            </div>
            {i === 0 && <div style={{ flex: 1, height: 1, background: step >= 1 ? 'var(--accent)' : 'var(--rule)', margin: '0 4px' }}/>}
          </React.Fragment>
        ))}
      </div>

      {step === 0 && (
        <div>
          <Field label="Slot title" required hint={`${title.length}/80`}>
            <TextInput ref={titleRef} value={title} onChange={(v) => { setTitle(v.slice(0, 80)); setValidationErr(''); }} placeholder="e.g. Unit 3 Viva — Technical Presentations"/>
          </Field>

          <Field label="Assign faculty" hint="Optional — assign faculty who will evaluate this slot">
              <MultiPillSelect value={faculties} onChange={setFaculties} options={facList} placeholder={facList.length === 0 ? 'No faculty yet — slot can still be created' : 'Choose one or more faculty (optional)…'}/>
              {facList.length === 0 && (
                <div style={{ marginTop: 5, fontSize: 10.5, color: 'var(--ink-4)' }}>
                  No faculty accounts yet. You can assign faculty later or create one in User Management.
                </div>
              )}
          </Field>

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
            <Field label="Departments" hint="Leave empty for all">
              <MultiPillSelect value={depts} onChange={setDepts} options={deptList} placeholder="All departments"/>
            </Field>
            <Field label="Colleges" hint="Leave blank for all colleges">
              <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 2 }}>
                {collegeList.length === 0
                  ? <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>No colleges configured yet — leave blank for all.</div>
                  : collegeList.map((c) => (
                    <button key={c} type="button" onClick={() => toggleCollege(c)}
                      style={{
                        padding: '5px 12px', borderRadius: 20, fontSize: 12, fontWeight: 500, cursor: 'pointer',
                        background: colleges.includes(c) ? 'var(--accent)' : 'var(--paper-2)',
                        border: `1px solid ${colleges.includes(c) ? 'var(--accent)' : 'var(--rule)'}`,
                        color: colleges.includes(c) ? '#fff' : 'var(--ink-3)',
                        transition: 'all .15s',
                      }}>
                      {colleges.includes(c) && <Icon name="check" size={10} style={{ marginRight: 4 }}/>}
                      {c}
                    </button>
                  ))
                }
              </div>
              {colleges.length > 0 && (
                <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 4 }}>
                  Selected: {colleges.join(', ')} · <span style={{ cursor: 'pointer', color: 'var(--accent)' }} onClick={() => setColleges([])}>Clear</span>
                </div>
              )}
            </Field>
          </div>

          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12 }}>
            <Field label="Attempts">
              <SelectInput value={String(attempts)} onChange={(v) => setAttempts(Number(v))}
                options={['1','2','3','999'].map((n) => ({ value: n, label: n === '999' ? 'Unlimited' : n }))}/>
            </Field>
            <Field label="Duration (sec)">
              <TextInput value={String(duration)} onChange={(v) => setDuration(Number(v) || 0)} mono/>
            </Field>
            <div/>
          </div>

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
            <Field label="Expiry date" required>
              <TextInput value={expiryDate} onChange={setExpiryDate} type="date"/>
            </Field>
            <Field label="Expiry time">
              <TextInput value={expiryTime} onChange={setExpiryTime} type="time" mono/>
            </Field>
          </div>

          <div style={{ marginBottom: 12 }}>
            <Toggle checked={usePasskey} onChange={setUsePasskey}
              label="Require passkey"
              desc="Students and faculty must enter a passkey to access this slot. Disable for open-access slots."/>
          </div>

          {usePasskey && (
            <Field label="Passkey" hint="Students + faculty enter this to access the slot">
              <div style={{ display: 'flex', gap: 8 }}>
                <TextInput value={passkey}
                  onChange={(v) => setPasskey(v.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 12))}
                  mono style={{ textTransform: 'uppercase' }}/>
                <button type="button" className="btn btn-sm" onClick={() => setPasskey(genPasskey())}>
                  <Icon name="refresh" size={12}/> Regenerate
                </button>
              </div>
            </Field>
          )}

          <div style={{ marginTop: 8 }}>
            <Toggle checked={analyticsAccess} onChange={setAnalyticsAccess}
              label="Grant analytics access to assigned faculty"
              desc="Faculty can see cohort distributions, trends, and dimension breakdowns. Can be overridden per-slot."/>
          </div>
        </div>
      )}

      {step === 1 && (
        <div>
          <div style={{
            padding: '16px 18px', borderRadius: 8, marginBottom: 16,
            background: 'linear-gradient(135deg, color-mix(in oklab, var(--accent) 10%, var(--card)), var(--card) 60%)',
            border: '1px solid color-mix(in oklab, var(--accent) 25%, var(--rule))',
          }}>
            <div className="eyebrow mb-2">Slot title</div>
            <div className="serif" style={{ fontSize: 19, lineHeight: 1.3 }}>{title}</div>
          </div>

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginBottom: 14 }}>
            {reviewRows.map(([k, v]) => (
              <div key={k} style={{ padding: '10px 12px', background: 'var(--paper-2)', borderRadius: 6 }}>
                <div className="eyebrow" style={{ fontSize: 9 }}>{k}</div>
                <div style={{ fontSize: 12.5, color: 'var(--ink)', marginTop: 3, wordBreak: 'break-word' }}>{v}</div>
              </div>
            ))}
          </div>

          {usePasskey ? (
            <div style={{
              padding: '14px 16px', borderRadius: 8,
              background: 'linear-gradient(135deg, oklch(0.62 0.18 25 / 0.08), oklch(0.62 0.18 25 / 0.02))',
              border: '1px solid oklch(0.62 0.18 25 / 0.25)',
              display: 'flex', alignItems: 'center', gap: 14,
            }}>
              <div style={{ color: 'oklch(0.55 0.19 25)' }}><Icon name="lock" size={18}/></div>
              <div style={{ flex: 1 }}>
                <div className="eyebrow" style={{ fontSize: 9 }}>Generated passkey</div>
                <div className="mono" style={{ fontSize: 20, fontWeight: 600, letterSpacing: '0.12em', color: 'var(--ink)', marginTop: 2 }}>{passkey}</div>
              </div>
              <button type="button" className="btn btn-sm" onClick={() => navigator.clipboard?.writeText(passkey)}>
                <Icon name="copy" size={11}/> Copy
              </button>
            </div>
          ) : (
            <div style={{ padding: '12px 14px', background: 'var(--paper-2)', borderRadius: 6, fontSize: 12, color: 'var(--ink-3)', display: 'flex', gap: 8, alignItems: 'center' }}>
              <Icon name="eye" size={13}/>
              <span>Open-access slot — no passkey required.</span>
            </div>
          )}

          <div style={{ marginTop: 12, padding: '10px 12px', background: analyticsAccess ? 'color-mix(in oklab, var(--ok) 10%, transparent)' : 'var(--paper-2)', borderRadius: 6, fontSize: 11.5, color: 'var(--ink-3)', display: 'flex', alignItems: 'center', gap: 8 }}>
            <Icon name={analyticsAccess ? 'check' : 'lock'} size={12} style={{ color: analyticsAccess ? 'var(--ok)' : 'var(--ink-4)' }}/>
            Analytics access: <b style={{ color: 'var(--ink)' }}>{analyticsAccess ? 'Granted' : 'Restricted'}</b>
          </div>
        </div>
      )}
    </Modal>
  );
}

// ── New User Modal (rewritten) ──────────────────────────────
function NewUserModal({ onClose, onCreate }) {
  const [role, setRole] = React.useState('admin');
  const [name, setName] = React.useState('');
  const [email, setEmail] = React.useState('');
  const [dept, setDept] = React.useState('CSE');
  const [college, setCollege] = React.useState('');
  const [regNo, setRegNo] = React.useState('');
  const [analyticsDefault, setAnalyticsDefault] = React.useState(true);

  // Load colleges from API for dropdown
  const [collegeData, setCollegeData] = React.useState([]);
  React.useEffect(() => {
    apiFetch('/colleges/')
      .then((d) => setCollegeData(Array.isArray(d) ? d : []))
      .catch(() => setCollegeData([]));
  }, []);
  const apiDeptList = React.useMemo(() => {
    if (!college) return [];
    const c = collegeData.find((c) => c.name === college);
    return c ? c.departments.map((d) => d.name) : [];
  }, [college, collegeData]);
  const fallbackDepts = window.DEPTS && window.DEPTS.length ? window.DEPTS : ['CSE', 'ECE', 'IT', 'ADS', 'EEE', 'ME', 'Administration'];
  const deptOptions = apiDeptList.length > 0 ? apiDeptList : fallbackDepts;

  // Live password preview
  const pwPattern = (window.DEFAULT_PASSWORD_PATTERNS || {})[role] || '{department}_{fullname}';
  const pwPreview = (window.applyPasswordPattern || ((p) => p))(pwPattern, {
    department: dept,
    fullname: name || 'name',
    regnumber: regNo || 'reg0001',
  });

  const [submitting, setSubmitting] = React.useState(false);
  const [submitErr, setSubmitErr] = React.useState('');

  const canCreate = name.trim().length > 2 && /.+@.+\..+/.test(email);

  const roleDefs = [
    { id: 'superadmin', label: 'Super admin', desc: 'Full access. Manages users, slots, analytics grants.', color: 'oklch(0.55 0.18 295)' },
    { id: 'admin',      label: 'Faculty',     desc: 'Evaluates slots. Analytics access granted per-slot.', color: 'oklch(0.55 0.17 215)' },
    { id: 'user',       label: 'Student',     desc: 'Attempts assessments with a passkey. Sees own reports.', color: 'oklch(0.55 0.15 145)' },
  ];

  return (
    <Modal onClose={onClose} title="Create user" subtitle="Add a new member to your institution." width={600}
      footer={
        <>
          <button className="btn btn-sm btn-ghost" onClick={onClose}>Cancel</button>
          {submitErr && <span style={{ fontSize: 11, color: 'var(--bad)' }}>{submitErr}</span>}
          <button className="btn btn-sm btn-primary" disabled={!canCreate || submitting}
            onClick={async () => {
              setSubmitting(true); setSubmitErr('');
              try {
                const pwPattern = (window.DEFAULT_PASSWORD_PATTERNS || {})[role] || '{department}_{fullname}';
                const password = (window.applyPasswordPattern || ((p) => p))(pwPattern, { department: dept, fullname: name, regnumber: regNo });
                await apiFetch('/auth/admin/create-user', {
                  method: 'POST',
                  headers: { 'Content-Type': 'application/json' },
                  body: JSON.stringify({ name: name.trim(), email: email.trim(), password, role, department: dept, college, reg_number: regNo }),
                });
                onCreate({ role, name, email, dept, regNo, analyticsDefault, sendInvite: false });
              } catch (ex) {
                setSubmitErr(ex.message || 'Failed to create user');
              } finally {
                setSubmitting(false);
              }
            }}>
            {submitting ? 'Creating…' : <><Icon name="check" size={11}/> Create user</>}
          </button>
        </>
      }>

      <Field label="Role" required>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8 }}>
          {roleDefs.map((r) => (
            <button key={r.id} type="button" onClick={() => setRole(r.id)}
              style={{
                padding: '12px 14px', borderRadius: 8, textAlign: 'left',
                background: role === r.id ? `color-mix(in oklab, ${r.color} 10%, var(--card))` : 'var(--paper-2)',
                border: `1px solid ${role === r.id ? r.color : 'var(--rule)'}`,
                cursor: 'pointer', transition: 'all .15s', position: 'relative',
              }}>
              <div style={{ width: 8, height: 8, borderRadius: '50%', background: r.color, marginBottom: 8 }}/>
              <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink)' }}>{r.label}</div>
              <div style={{ fontSize: 10.5, color: 'var(--ink-4)', marginTop: 3, lineHeight: 1.4 }}>{r.desc}</div>
              {role === r.id && <div style={{ position: 'absolute', top: 10, right: 10, color: r.color }}><Icon name="check" size={12}/></div>}
            </button>
          ))}
        </div>
      </Field>

      <Field label="Full name" required>
        <TextInput value={name} onChange={setName} placeholder={role === 'user' ? 'e.g. Student Name' : 'e.g. Faculty Name'}/>
      </Field>

      <div style={{ display: 'grid', gridTemplateColumns: role === 'user' ? '1.2fr 1fr' : '1fr', gap: 12 }}>
        <Field label="Email" required>
          <TextInput value={email} onChange={setEmail} placeholder="name@sjce.ac.in" type="email"/>
        </Field>
        {role === 'user' && (
          <Field label="Reg. number">
            <TextInput value={regNo} onChange={(v) => setRegNo(v.toUpperCase())} mono placeholder="Reg number"/>
          </Field>
        )}
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
        <Field label="College">
          {collegeData.length > 0
            ? <select className="input" value={college} onChange={(e) => { setCollege(e.target.value); setDept(''); }}>
                <option value="">— select college —</option>
                {collegeData.map((c) => <option key={c.id} value={c.name}>{c.name}</option>)}
              </select>
            : <TextInput value={college} onChange={setCollege} placeholder="College name (optional)"/>
          }
        </Field>
        <Field label="Department">
          {apiDeptList.length > 0
            ? <select className="input" value={dept} onChange={(e) => setDept(e.target.value)}>
                <option value="">— select dept —</option>
                {apiDeptList.map((d) => <option key={d} value={d}>{d}</option>)}
              </select>
            : <SelectInput value={dept} onChange={setDept} options={fallbackDepts}/>
          }
        </Field>
      </div>

      {role === 'admin' && (
        <div style={{ marginBottom: 14 }}>
          <Toggle checked={analyticsDefault} onChange={setAnalyticsDefault}
            label="Default analytics access on new slots"
            desc="When assigned to a new slot, analytics access will be granted by default. Can be overridden per-slot."/>
        </div>
      )}

      {/* Password pattern preview */}
      <div style={{
        padding: '12px 14px', borderRadius: 6,
        background: 'color-mix(in oklab, var(--accent) 6%, var(--paper-2))',
        border: '1px solid color-mix(in oklab, var(--accent) 20%, var(--rule))',
      }}>
        <div className="eyebrow mb-2" style={{ fontSize: 9 }}>Default password (pattern: <span className="mono" style={{ fontSize: 9 }}>{pwPattern}</span>)</div>
        <div className="mono" style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink)', letterSpacing: '0.04em' }}>{pwPreview}</div>
        <div style={{ fontSize: 10.5, color: 'var(--ink-4)', marginTop: 4 }}>
          User will receive this password on first login. Pattern editable in Settings → Password Pattern.
        </div>
      </div>
    </Modal>
  );
}

// ── Analytics Access Manager ──
function AnalyticsAccessView() {
  const [facultyUsers, setFacultyUsers] = React.useState([]);
  const [slots, setSlots] = React.useState([]);
  const [grants, setGrants] = React.useState({});
  const [loading, setLoading] = React.useState(true);
  const [q, setQ] = React.useState('');
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => {
    Promise.all([
      apiFetch('/auth/admin/users'),
      apiFetch('/slots/'),
    ]).then(([users, slotsData]) => {
      const admins = (Array.isArray(users) ? users : []).filter((u) => u.role === 'admin' && u.is_active);
      const slotList = Array.isArray(slotsData) ? slotsData : (slotsData.slots || []);
      setFacultyUsers(admins);
      setSlots(slotList);
      // init grants — all false by default
      const g = {};
      admins.forEach((f) => {
        g[f.id] = {};
        slotList.forEach((s) => { g[f.id][s.id] = false; });
      });
      setGrants(g);
    }).catch(() => {}).finally(() => setLoading(false));
  }, []);

  const filtered = facultyUsers.filter((f) =>
    !q || f.name.toLowerCase().includes(q.toLowerCase()) || (f.department || '').toLowerCase().includes(q.toLowerCase())
  );

  const toggleGrant = (facultyId, slotId) => {
    setGrants((g) => ({
      ...g,
      [facultyId]: { ...g[facultyId], [slotId]: !g[facultyId]?.[slotId] },
    }));
  };

  const grantAll = (facultyId, val) => {
    setGrants((g) => {
      const next = { ...g, [facultyId]: {} };
      slots.forEach((s) => { next[facultyId][s.id] = val; });
      return next;
    });
  };

  const totalGranted = Object.values(grants).reduce((a, fg) => a + Object.values(fg).filter(Boolean).length, 0);
  const totalPossible = facultyUsers.length * slots.length;

  if (loading) return (
    <div className="fade-up">
      <div className="page-head"><div><h1 className="page-title">Analytics <em>Access</em></h1></div></div>
      <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-4)' }}>Loading faculty and slots…</div>
    </div>
  );

  return (
    <div className="fade-up">
      <div className="page-head">
        <div>
          <div className="eyebrow mb-2">Superadmin controls</div>
          <h1 className="page-title">Analytics <em>Access</em></h1>
          <div className="page-sub">Grant cohort analytics visibility per slot, per faculty. Students never see analytics.</div>
        </div>
      </div>

      <div style={{
        padding: '16px 20px', borderRadius: 10, marginBottom: 16,
        background: 'linear-gradient(110deg, oklch(0.58 0.17 280) 0%, oklch(0.55 0.19 320) 100%)',
        color: '#fff', display: 'flex', alignItems: 'center', gap: 16,
      }}>
        <Icon name="chart" size={20}/>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 14.5, fontWeight: 700 }}>{totalGranted} of {totalPossible} faculty×slot pairs have analytics enabled</div>
          <div style={{ fontSize: 11.5, opacity: 0.88, marginTop: 2 }}>Toggle slots individually per faculty row.</div>
        </div>
      </div>

      <div style={{ marginBottom: 12 }}>
        <div style={{ position: 'relative', maxWidth: 300 }}>
          <Icon name="search" size={13} style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)', color: 'var(--ink-4)' }}/>
          <input className="input" placeholder="Search faculty…" value={q} onChange={(e) => setQ(e.target.value)}
            style={{ width: '100%', padding: '7px 10px 7px 30px', fontSize: 12.5 }}/>
        </div>
      </div>

      {filtered.length === 0 ? (
        <div className="card card-pad" style={{ textAlign: 'center', color: 'var(--ink-4)', padding: 40 }}>
          No faculty accounts found. Create faculty users first via User Management.
        </div>
      ) : (
        <div className="card" style={{ overflowX: 'auto' }}>
          <table>
            <thead>
              <tr>
                <th>Faculty</th>
                <th>Dept</th>
                {slots.map((s) => (
                  <th key={s.id} style={{ fontSize: 9, textAlign: 'center', maxWidth: 80 }}>
                    <div style={{ overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 80 }} title={s.title}>
                      #{s.id}
                    </div>
                    <div style={{ fontWeight: 400, opacity: 0.6, fontSize: 8, maxWidth: 80, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                      {(s.title || '').slice(0, 18)}
                    </div>
                  </th>
                ))}
                <th style={{ textAlign: 'center' }}>Actions</th>
              </tr>
            </thead>
            <tbody>
              {filtered.map((f, i) => {
                const totalForFaculty = Object.values(grants[f.id] || {}).filter(Boolean).length;
                return (
                  <tr key={f.id}>
                    <td>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                        <AdminAvatar name={f.name} size={26} seed={i + 10}/>
                        <div>
                          <div style={{ fontSize: 12.5, fontWeight: 500 }}>{f.name}</div>
                          <div className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>{f.email}</div>
                        </div>
                      </div>
                    </td>
                    <td style={{ fontSize: 12, color: 'var(--ink-3)' }}>{f.department || '—'}</td>
                    {slots.map((s) => {
                      const on = !!(grants[f.id] && grants[f.id][s.id]);
                      return (
                        <td key={s.id} style={{ textAlign: 'center' }}>
                          <button onClick={() => toggleGrant(f.id, s.id)}
                            title={on ? 'Revoke' : 'Grant'}
                            style={{
                              width: 28, height: 20, borderRadius: 10, cursor: 'pointer', border: 'none',
                              background: on ? 'var(--ok)' : 'var(--paper-3)',
                              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                              transition: 'background .15s',
                            }}>
                            <div style={{
                              width: 12, height: 12, borderRadius: '50%', background: '#fff',
                              boxShadow: '0 1px 2px rgba(0,0,0,0.15)',
                              transform: `translateX(${on ? 4 : -4}px)`,
                              transition: 'transform .2s',
                            }}/>
                          </button>
                        </td>
                      );
                    })}
                    <td style={{ textAlign: 'center' }}>
                      <div style={{ display: 'flex', gap: 4, justifyContent: 'center' }}>
                        <button className="btn btn-sm" style={{ fontSize: 10, padding: '2px 8px' }} onClick={() => grantAll(f.id, true)}>All</button>
                        <button className="btn btn-sm btn-ghost" style={{ fontSize: 10, padding: '2px 8px' }} onClick={() => grantAll(f.id, false)}>None</button>
                      </div>
                      <div className="mono tnum" style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 2 }}>{totalForFaculty}/{slots.length}</div>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

// ── Documentation view (role-scoped) ───────────────────────
function DocsView({ role = 'admin' }) {
  const allSections = [
    { id: 'getting-started', label: 'Getting started', icon: 'play',   roles: ['user','admin','superadmin'] },
    { id: 'roles',           label: 'Roles & permissions', icon: 'user', roles: ['admin','superadmin'] },
    { id: 'slots',           label: 'Assessment slots', icon: 'layers',  roles: ['admin','superadmin'] },
    { id: 'passkeys',        label: 'Passkeys & security', icon: 'lock', roles: ['admin','superadmin'] },
    { id: 'analytics',       label: 'Analytics & reports', icon: 'chart', roles: ['admin','superadmin'] },
    { id: 'evaluation',      label: 'Evaluation criteria', icon: 'check', roles: ['user','admin','superadmin'] },
    { id: 'users',           label: 'User management', icon: 'users',    roles: ['superadmin'] },
    { id: 'troubleshooting', label: 'Troubleshooting', icon: 'help',     roles: ['user','admin','superadmin'] },
  ];
  const sections = allSections.filter((s) => s.roles.includes(role));
  const [active, setActive] = React.useState(sections[0]?.id || 'getting-started');

  const content = {
    'getting-started': (
      <>
        <h2>Welcome to ChiselAssess</h2>
        <p>ChiselAssess is a voice-first assessment platform for higher-education institutions. Students record spoken responses; the system evaluates them across six rhetorical dimensions; faculty review results through structured reports and cohort analytics.</p>
        {role === 'user' && (
          <>
            <h3>As a student</h3>
            <ol>
              <li>Sign in with the credentials your institution admin provided.</li>
              <li>Go to <b>Take Assessment</b> and enter the passkey your faculty shared.</li>
              <li>Record your spoken response within the allotted time.</li>
              <li>View your full evaluation report from <b>My Reports</b>.</li>
            </ol>
          </>
        )}
        {role === 'admin' && (
          <>
            <h3>As faculty</h3>
            <ol>
              <li>Sign in with the credentials from your institution admin.</li>
              <li>Go to <b>My Slots</b> and enter the passkey for your assigned slot.</li>
              <li>Review submissions and — if analytics access was granted — view cohort data.</li>
            </ol>
          </>
        )}
        {role === 'superadmin' && (
          <>
            <h3>As superadmin</h3>
            <ol>
              <li>Go to <b>All Slots → New slot</b> to create your first evaluation session.</li>
              <li>Assign faculty, set an expiry date and optional passkey, confirm.</li>
              <li>Share the passkey with enrolled students and assigned faculty.</li>
              <li>Manage users from <b>User Management</b> and control analytics access from <b>Analytics Access</b>.</li>
            </ol>
          </>
        )}
      </>
    ),
    'roles': (
      <>
        <h2>Roles & permissions</h2>
        <p>ChiselAssess has three roles. Permissions cascade strictly — superadmins can do everything faculty can, and faculty can do everything students can within their scope.</p>
        <div style={{ display: 'grid', gap: 12, margin: '18px 0' }}>
          {[
            { r: 'Superadmin', c: 'oklch(0.55 0.18 295)', p: ['Create & archive slots', 'Create users (faculty + students)', 'Issue and reset passkeys', 'Grant analytics access per faculty per slot', 'View cohort data across all departments', 'Manage institution settings & password patterns'] },
            { r: 'Faculty',    c: 'oklch(0.55 0.17 215)', p: ['Enter assigned slots with passkey', 'Review individual submissions', 'Download student reports', 'View analytics (if granted)', 'Export & share coaching programs'] },
            { r: 'Student',    c: 'oklch(0.55 0.15 145)', p: ['Enter slots with passkey', 'Record spoken responses', 'View own reports and history', 'Download own report PDF'] },
          ].map((x) => (
            <div key={x.r} style={{ padding: '14px 16px', borderLeft: `3px solid ${x.c}`, background: 'var(--paper-2)', borderRadius: 4 }}>
              <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)', marginBottom: 6 }}>{x.r}</div>
              <ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.7 }}>
                {x.p.map((pp) => <li key={pp}>{pp}</li>)}
              </ul>
            </div>
          ))}
        </div>
      </>
    ),
    'slots': (
      <>
        <h2>Assessment slots</h2>
        <p>A <b>slot</b> is a time-boxed evaluation session. Each slot has:</p>
        <ul>
          <li><b>A passkey (optional)</b> — an 8-character code students and faculty must enter. Can be disabled for open-access slots.</li>
          <li><b>Scope</b> — departments and colleges (multi-select; leave blank for institution-wide).</li>
          <li><b>Multiple faculty</b> — one or more evaluators can be assigned to the same slot.</li>
          <li><b>Expiry date + time</b> — slots close automatically at the configured moment.</li>
          <li><b>Attempts & duration</b> — how many recordings each student can submit, and max recording length.</li>
          <li><b>Analytics access flag</b> — whether the assigned faculty can see cohort-level data. Managed per faculty from the Analytics Access page.</li>
        </ul>
        <h3>Creating a slot</h3>
        <p>Go to <b>Slots → New slot</b>. Fill in details, assign faculty, set the expiry date and time, and optionally enable a passkey. Share the passkey (if used) with enrolled students and faculty via your LMS or email.</p>
      </>
    ),
    'passkeys': (
      <>
        <h2>Passkeys & security</h2>
        <p>Passkeys are optional per-slot — superadmins can enable or disable the requirement during slot creation. When enabled, an 8-character alphanumeric code gates access.</p>
        <h3>Best practices</h3>
        <ul>
          <li>Share passkeys via your LMS, institutional email, or in-person at the start of a session.</li>
          <li>Do not post passkeys in public forums or shared documents.</li>
          <li>Regenerate a passkey if you suspect it has leaked — existing sessions are invalidated immediately.</li>
          <li>Superadmins see the raw key; faculty and students see only the redacted passkey-required badge.</li>
        </ul>
        <h3>Open-access slots</h3>
        <p>When passkey is disabled on a slot, any user with the slot link can enter. Use this for practice rounds or baseline assessments where identity verification isn't needed.</p>
      </>
    ),
    'analytics': (
      <>
        <h2>Analytics & reports</h2>
        <h3>Individual reports</h3>
        <p>Every submission produces a per-student report with six dimension scores (0–100), an overall grade, transcript annotations, pace and filler metrics, and an AI-generated feedback note.</p>
        <h3>Cohort analytics</h3>
        <p>When analytics access is granted (via <b>Analytics Access</b>), faculty see the <b>Analytics</b> tab: score distribution, rolling trend, criterion averages, pace × filler scatter, grade mix, department breakdown, and dimension correlations.</p>
        <h3>Exporting</h3>
        <ul>
          <li><b>Download .csv</b> — full tabular export of all submissions and criterion scores.</li>
          <li><b>Download PDF</b> — a rendered snapshot of the entire analytics view including the slot header, charts, and department breakdown.</li>
          <li><b>Coaching → Export PDF</b> — a structured coaching program based on selected training aspects.</li>
          <li><b>Coaching → Share with cohort</b> — opens a pre-filled email to all students in the slot.</li>
        </ul>
      </>
    ),
    'evaluation': (
      <>
        <h2>Evaluation criteria</h2>
        <p>Every response is scored across six dimensions, each on a 0–100 scale. Scores roll up into an overall grade (A+ through E).</p>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginTop: 14 }}>
          {CRITERIA.map((c) => (
            <div key={c.key} style={{ padding: '12px 14px', border: '1px solid var(--rule)', borderRadius: 6, background: 'var(--paper-2)' }}>
              <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink)', marginBottom: 4 }}>{c.label}</div>
              <div style={{ fontSize: 11, color: 'var(--ink-4)', lineHeight: 1.5 }}>{c.desc}</div>
            </div>
          ))}
        </div>
        <h3 style={{ marginTop: 20 }}>Grade scale</h3>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 8 }}>
          {[['A+','90+','ok'],['A','80–89','ok'],['B','70–79','info'],['C','60–69','warn'],['D','50–59','warn'],['E','<50','bad']].map(([g,r,t]) => (
            <span key={g} className={`pill pill-${t}`} style={{ fontSize: 11 }}>{g} · {r}</span>
          ))}
        </div>
      </>
    ),
    'users': (
      <>
        <h2>User management</h2>
        <p>Superadmins can create users of any role. Each user is assigned a default password derived from the institution's password pattern (see Settings → Password Pattern).</p>
        <h3>Roles</h3>
        <ul>
          <li><b>Superadmin</b> — full platform access, including slot creation and analytics grants.</li>
          <li><b>Faculty</b> — evaluates slots; can see analytics only where explicitly granted.</li>
          <li><b>Student</b> — attempts assessments; sees only their own reports.</li>
        </ul>
        <h3>Password pattern</h3>
        <p>Go to <b>Settings → Password Pattern</b> to configure the default password formula per role. Uses tokens like <span className="mono">{'{department}'}</span>, <span className="mono">{'{fullname}'}</span>, and <span className="mono">{'{regnumber}'}</span>.</p>
        <h3>Bulk operations</h3>
        <p>Use the <b>Analytics Access</b> page to grant or revoke analytics access for multiple faculty at once. Use the bulk-select checkboxes + "Grant selected" / "Revoke selected" buttons.</p>
      </>
    ),
    'troubleshooting': (
      <>
        <h2>Troubleshooting</h2>
        <h3>Passkey isn't working</h3>
        <p>Double-check for O/0 or I/1 confusion. Ask your superadmin to confirm or regenerate. Passkeys are case-insensitive.</p>
        <h3>Microphone access denied</h3>
        <p>In Chrome / Edge: padlock icon in URL bar → Site settings → Microphone → Allow, then reload.</p>
        <h3>Recording cut off early</h3>
        <p>The slot has a duration cap set by the admin (default 180 s). Check the timer at the top of the recording panel.</p>
        <h3>Can't see analytics</h3>
        <p>Analytics access is granted per-slot per-faculty by your superadmin from the <b>Analytics Access</b> page.</p>
        <h3>PDF export is missing content</h3>
        <p>Scroll back to the top of the analytics tab, then click Download PDF. The export captures the full view including the slot header and all charts.</p>
        <h3>Contact support</h3>
        <p>Email <span className="mono">support@chiselassess.ai</span> or reach your institutional admin.</p>
      </>
    ),
  };

  return (
    <div className="fade-up">
      <div className="page-head">
        <div>
          <div className="eyebrow mb-2">Help center</div>
          <h1 className="page-title"><em>Documentation</em></h1>
          <div className="page-sub">
            {role === 'user' ? 'Student guide — everything you need to record, submit, and review your assessments.'
              : role === 'admin' ? 'Faculty guide — running slots, reviewing submissions, and coaching your cohort.'
              : 'Admin guide — managing the full institution from slots to users to analytics.'}
          </div>
        </div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '220px 1fr', gap: 20 }}>
        <nav style={{ position: 'sticky', top: 20, alignSelf: 'start' }}>
          {sections.map((s) => (
            <button key={s.id} onClick={() => setActive(s.id)}
              style={{
                display: 'flex', alignItems: 'center', gap: 10, width: '100%', textAlign: 'left',
                padding: '9px 12px', borderRadius: 6, marginBottom: 2, cursor: 'pointer',
                background: active === s.id ? 'color-mix(in oklab, var(--accent) 10%, transparent)' : 'transparent',
                color: active === s.id ? 'var(--accent)' : 'var(--ink-3)',
                border: 'none', fontSize: 12.5, fontWeight: active === s.id ? 600 : 400,
                transition: 'background .15s, color .15s',
              }}>
              <Icon name={s.icon} size={12}/>{s.label}
            </button>
          ))}
        </nav>
        <article className="card card-pad docs-content" style={{ padding: '36px 48px', lineHeight: 1.7 }}>
          {content[active] || <p style={{ color: 'var(--ink-4)', fontStyle: 'italic' }}>Section not available for your role.</p>}
        </article>
      </div>
    </div>
  );
}

// ── Settings view (rewritten) ───────────────────────────────
// ── Login protection toggle (superadmin only) ─────────────────────────────
function LoginProtectionToggle() {
  const [enabled, setEnabled] = React.useState(true);
  const [loading, setLoading] = React.useState(true);
  const [msg, setMsg]         = React.useState(null);

  React.useEffect(() => {
    apiFetch('/auth/admin/rate-limit')
      .then(d => setEnabled(d.rate_limit_enabled !== false))
      .catch(() => {})
      .finally(() => setLoading(false));
  }, []);

  const toggle = async () => {
    const next = !enabled;
    try {
      await apiFetch('/auth/admin/rate-limit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ enabled: next }),
      });
      setEnabled(next);
      setMsg({ ok: true, text: next ? 'Login protection enabled.' : 'Protection disabled — remember to re-enable after testing.' });
    } catch(e) {
      setMsg({ ok: false, text: 'Failed to update: ' + e.message });
    }
    setTimeout(() => setMsg(null), 4000);
  };

  return (
    <div style={{ marginTop: 24, padding: '14px 16px', background: enabled ? 'var(--paper-2)' : 'color-mix(in oklab, var(--warn) 8%, var(--paper-2))',
      borderRadius: 6, border: `1px solid ${enabled ? 'var(--rule)' : 'color-mix(in oklab, var(--warn) 30%, var(--rule))'}` }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <div>
          <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink)', marginBottom: 2 }}>
            <Icon name="lock" size={11}/> Brute-force login protection
          </div>
          <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>
            {enabled ? 'Active — max 10 attempts per IP per 5 minutes.' : '⚠ Disabled — turn back on after stress testing.'}
          </div>
        </div>
        <button className={`btn btn-sm ${enabled ? 'btn-ghost' : 'btn-primary'}`}
          onClick={toggle} disabled={loading} style={{ minWidth: 90 }}>
          {loading ? '…' : enabled ? 'Disable' : 'Enable'}
        </button>
      </div>
      {msg && <div style={{ marginTop: 8, fontSize: 11, color: msg.ok ? 'var(--ok)' : 'var(--bad)' }}>{msg.text}</div>}
    </div>
  );
}

function SettingsView({ role = 'admin', tweaks, setTweak }) {
  const [instName, setInstName] = React.useState(() => localStorage.getItem('ca_inst_name') || '');
  const [tab, setTab] = React.useState('profile');

  // Read actual logged-in user from localStorage
  const storedUser = React.useMemo(() => {
    try { return JSON.parse(localStorage.getItem('ca_user') || '{}'); } catch { return {}; }
  }, []);
  const [displayName, setDisplayName] = React.useState(storedUser.name || '');
  const [nameSaving, setNameSaving] = React.useState(false);
  const [nameMsg, setNameMsg] = React.useState(null);

  const handleSaveName = async () => {
    if (!displayName.trim()) return;
    setNameSaving(true); setNameMsg(null);
    try {
      // Persist to server if endpoint exists
      try {
        await apiFetch('/auth/update-profile', {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ name: displayName.trim() }),
        });
      } catch { /* endpoint may not exist — local update is still applied */ }
      // Always update localStorage so sidebar + UI reflect change immediately
      const updated = { ...storedUser, name: displayName.trim() };
      localStorage.setItem('ca_user', JSON.stringify(updated));
      // Force sidebar refresh by dispatching a storage event
      window.dispatchEvent(new Event('ca_user_updated'));
      setNameMsg({ ok: true, text: 'Display name updated.' });
    } catch (ex) {
      setNameMsg({ ok: false, text: ex.message || 'Save failed.' });
    } finally { setNameSaving(false); }
  };

  const [timezone, setTimezone] = React.useState('Asia/Kolkata');
  const [language, setLanguage] = React.useState('English');

  // Password change state
  const [curPw, setCurPw] = React.useState('');
  const [newPw, setNewPw] = React.useState('');
  const [confPw, setConfPw] = React.useState('');
  const [pwSaving, setPwSaving] = React.useState(false);
  const [pwMsg, setPwMsg] = React.useState(null); // { ok, text }
  const pwMatch = newPw && newPw === confPw;
  const pwError = confPw && !pwMatch;

  const handleChangePassword = async () => {
    if (!pwMatch || !curPw) return;
    setPwSaving(true); setPwMsg(null);
    try {
      await apiFetch('/auth/change-password', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ old_password: curPw, new_password: newPw }),
      });
      setPwMsg({ ok: true, text: 'Password changed successfully.' });
      setCurPw(''); setNewPw(''); setConfPw('');
    } catch (ex) {
      setPwMsg({ ok: false, text: ex.message || 'Failed to change password.' });
    } finally {
      setPwSaving(false);
    }
  };

  // Password pattern (superadmin) ─────────────────────────────────────────
  // Load from localStorage first (overrides compiled defaults)
  const _loadPatterns = () => {
    const base = window.DEFAULT_PASSWORD_PATTERNS || {
      superadmin: '{department}_{fullname}',
      admin:      '{department}_{fullname}',
      user:       '{first4name}_{regnumber}',
    };
    try {
      const saved = localStorage.getItem('ca_pw_patterns');
      if (saved) return Object.assign({}, base, JSON.parse(saved));
    } catch {}
    return Object.assign({}, base);
  };
  const defaultPatterns = window.DEFAULT_PASSWORD_PATTERNS || {
    superadmin: '{department}_{fullname}',
    admin:      '{department}_{fullname}',
    user:       '{first4name}_{regnumber}',
  };
  const [patterns, setPatterns] = React.useState(_loadPatterns);
  const [patternSaving, setPatternSaving] = React.useState(false);
  const [patternMsg,    setPatternMsg]    = React.useState(null); // {ok, text}

  // Called when the superadmin clicks "Save patterns"
  const handleSavePatterns = () => {
    setPatternSaving(true);
    setPatternMsg(null);
    try {
      // 1. Persist to localStorage (survives page reloads)
      localStorage.setItem('ca_pw_patterns', JSON.stringify(patterns));

      // 2. Mutate window.DEFAULT_PASSWORD_PATTERNS IN-PLACE so every component
      //    that reads it (NewUserModal, BulkImportModal) picks up changes immediately
      const target = window.DEFAULT_PASSWORD_PATTERNS;
      if (target && typeof target === 'object') {
        Object.keys(patterns).forEach((k) => { target[k] = patterns[k]; });
      } else {
        window.DEFAULT_PASSWORD_PATTERNS = Object.assign({}, patterns);
      }

      // 3. Fire a global event so any open modal can re-render
      window.dispatchEvent(new CustomEvent('ca_pw_patterns_updated', { detail: patterns }));

      setPatternMsg({ ok: true, text: 'Patterns saved. New user creation will use the updated patterns.' });
    } catch (ex) {
      setPatternMsg({ ok: false, text: String(ex.message || 'Save failed.') });
    } finally {
      setPatternSaving(false);
    }
  };

  const patternPreview = (roleKey, previewData) =>
    (window.applyPasswordPattern || ((p) => p))(patterns[roleKey] || '', previewData || {
      department: 'dept', fullname: 'username', regnumber: 'regnumber',
    });

  const tabs = [
    { id: 'profile',          label: 'Profile',          icon: 'user'   },
    { id: 'appearance',       label: 'Appearance',       icon: 'type'   },
    { id: 'security',         label: 'Security',         icon: 'lock'   },
    ...(role === 'superadmin' ? [
      { id: 'pwd-pattern',    label: 'Password Pattern', icon: 'pen'    },
      { id: 'institution',    label: 'Institution',      icon: 'layers' },
    ] : []),
  ];

  return (
    <div className="fade-up">
      <div className="page-head">
        <div>
          <div className="eyebrow mb-2">Account</div>
          <h1 className="page-title"><em>Settings</em></h1>
          <div className="page-sub">Manage your profile, appearance, and security preferences.</div>
        </div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '220px 1fr', gap: 20 }}>
        <nav style={{ alignSelf: 'start' }}>
          {tabs.map((t) => (
            <button key={t.id} onClick={() => setTab(t.id)}
              style={{
                display: 'flex', alignItems: 'center', gap: 10, width: '100%', textAlign: 'left',
                padding: '9px 12px', borderRadius: 6, marginBottom: 2, cursor: 'pointer',
                background: tab === t.id ? 'color-mix(in oklab, var(--accent) 10%, transparent)' : 'transparent',
                color: tab === t.id ? 'var(--accent)' : 'var(--ink-3)',
                border: 'none', fontSize: 12.5, fontWeight: tab === t.id ? 600 : 400,
              }}>
              <Icon name={t.icon} size={12}/> {t.label}
            </button>
          ))}
        </nav>

        <div className="card card-pad" style={{ padding: '28px 32px' }}>
          {/* ── Profile ── */}
          {tab === 'profile' && (
            <div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 24 }}>
                <AdminAvatar name={displayName} size={64} seed={1}/>
                <div>
                  <div className="serif" style={{ fontSize: 22, fontWeight: 500, lineHeight: 1.2 }}>{displayName}</div>
                  <div style={{ fontSize: 12, color: 'var(--ink-4)', marginTop: 4 }}>
                    {(()=>{ try { return JSON.parse(localStorage.getItem('ca_user')||'{}').email || ''; } catch { return ''; } })()} · {role === 'user' ? 'Student' : role === 'superadmin' ? 'Superadmin' : 'Faculty'}
                  </div>
                </div>
                <button className="btn btn-sm" style={{ marginLeft: 'auto' }}><Icon name="refresh" size={11}/> Change avatar</button>
              </div>
              <Field label="Display name">
                <TextInput value={displayName} onChange={setDisplayName}/>
              </Field>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
                <Field label="Timezone">
                  <SelectInput value={timezone} onChange={setTimezone}
                    options={['Asia/Kolkata', 'Asia/Dubai', 'UTC', 'America/New_York', 'Europe/London']}/>
                </Field>
                <Field label="Language">
                  <SelectInput value={language} onChange={setLanguage} options={['English', 'हिंदी', 'தமிழ்', 'ಕನ್ನಡ']}/>
                </Field>
              </div>
              {nameMsg && (
                <div style={{ fontSize: 12, color: nameMsg.ok ? 'var(--ok)' : 'var(--bad)', padding: '6px 10px',
                  background: nameMsg.ok ? 'var(--ok-wash)' : 'var(--bad-wash)', borderRadius: 5, marginBottom: 6 }}>
                  {nameMsg.text}
                </div>
              )}
              <button className="btn btn-primary btn-sm" style={{ marginTop: 4 }}
                disabled={nameSaving || !displayName.trim()} onClick={handleSaveName}>
                {nameSaving ? 'Saving…' : 'Save changes'}
              </button>
            </div>
          )}

          {/* ── Appearance ── */}
          {tab === 'appearance' && tweaks && setTweak && (
            <div>
              <div className="eyebrow mb-2">Theme</div>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, marginBottom: 22 }}>
                {[
                  { id: 'light', label: 'Light', bg: '#f7f3ea', ink: '#2a2419' },
                  { id: 'dark',  label: 'Dark',  bg: '#1a1612', ink: '#e8e0d0' },
                ].map((th) => (
                  <button key={th.id} onClick={() => setTweak('theme', th.id)}
                    style={{
                      padding: '18px 14px', borderRadius: 8, cursor: 'pointer', textAlign: 'left',
                      background: th.bg, color: th.ink,
                      border: `2px solid ${tweaks.theme === th.id ? 'var(--accent)' : 'var(--rule)'}`,
                      transition: 'border-color .15s',
                    }}>
                    <div style={{ fontSize: 13, fontWeight: 600 }}>{th.label}</div>
                    <div style={{ fontSize: 10.5, opacity: 0.6, marginTop: 4 }}>Sample text preview</div>
                    {tweaks.theme === th.id && (
                      <div style={{ marginTop: 8, display: 'flex', alignItems: 'center', gap: 4, fontSize: 10, color: 'var(--ok)' }}>
                        <Icon name="check" size={10}/> Active
                      </div>
                    )}
                  </button>
                ))}
              </div>

              <div className="eyebrow mb-2">Accent color</div>
              <div style={{ display: 'flex', gap: 10, marginBottom: 22, flexWrap: 'wrap' }}>
                {[
                  { id: 'ink-red', c: 'oklch(0.55 0.18 30)',  label: 'Red ink' },
                  { id: 'ochre',   c: 'oklch(0.65 0.14 75)',  label: 'Ochre'   },
                  { id: 'forest',  c: 'oklch(0.5 0.14 150)',  label: 'Forest'  },
                  { id: 'indigo',  c: 'oklch(0.5 0.15 265)',  label: 'Indigo'  },
                  { id: 'plum',    c: 'oklch(0.5 0.16 320)',  label: 'Plum'    },
                  { id: 'teal',    c: 'oklch(0.55 0.13 200)', label: 'Teal'    },
                ].map((a) => (
                  <button key={a.id} onClick={() => setTweak('accent', a.id)} title={a.label}
                    style={{
                      width: 40, height: 40, borderRadius: '50%', background: a.c, cursor: 'pointer', padding: 0,
                      border: `3px solid ${tweaks.accent === a.id ? 'var(--ink)' : 'transparent'}`,
                      boxShadow: tweaks.accent === a.id ? '0 0 0 1px var(--ink)' : '0 0 0 1px var(--rule)',
                      transition: 'transform .15s, box-shadow .15s',
                    }}/>
                ))}
              </div>

              <div className="eyebrow mb-2">Typography</div>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 8 }}>
                {[
                  { id: 'editorial', label: 'Editorial',  desc: 'Instrument Serif + Inter'  },
                  { id: 'swiss',     label: 'Swiss',      desc: 'Fraunces + Space Grotesk'  },
                  { id: 'academic',  label: 'Academic',   desc: 'Cormorant + IBM Plex'      },
                  { id: 'geometric', label: 'Geometric',  desc: 'Fraunces + Inter'          },
                ].map((t) => (
                  <button key={t.id} onClick={() => setTweak('type', t.id)}
                    style={{
                      padding: '12px 14px', borderRadius: 6, textAlign: 'left', cursor: 'pointer',
                      background: tweaks.type === t.id ? 'color-mix(in oklab, var(--accent) 10%, var(--card))' : 'var(--paper-2)',
                      border: `1px solid ${tweaks.type === t.id ? 'var(--accent)' : 'var(--rule)'}`,
                      transition: 'all .15s',
                    }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>{t.label}</div>
                    <div style={{ fontSize: 10.5, color: 'var(--ink-4)', marginTop: 2 }}>{t.desc}</div>
                    {tweaks.type === t.id && <div style={{ fontSize: 10, color: 'var(--accent)', marginTop: 6, fontWeight: 600 }}>✓ Active</div>}
                  </button>
                ))}
              </div>
            </div>
          )}

          {/* ── Security ── */}
          {tab === 'security' && (
            <div>
              <div className="eyebrow mb-2" style={{ marginBottom: 14 }}>Change password</div>
              <Field label="Current password">
                <TextInput value={curPw} onChange={setCurPw} type="password" placeholder="••••••••"/>
              </Field>
              <Field label="New password" hint="At least 10 characters">
                <TextInput value={newPw} onChange={setNewPw} type="password" placeholder="••••••••"/>
              </Field>
              <Field label="Confirm new password">
                <TextInput value={confPw} onChange={setConfPw} type="password" placeholder="••••••••"
                  sx={{ borderColor: pwError ? 'var(--bad)' : undefined }}/>
                {pwError && <div style={{ fontSize: 11, color: 'var(--bad)', marginTop: 5 }}>Passwords don't match.</div>}
                {pwMatch && <div style={{ fontSize: 11, color: 'var(--ok)', marginTop: 5 }}>✓ Passwords match.</div>}
              </Field>
              <button className="btn btn-primary btn-sm" style={{ marginTop: 4 }}
                disabled={!pwMatch || !curPw || pwSaving}
                onClick={handleChangePassword}>
                {pwSaving ? 'Saving…' : 'Update password'}
              </button>
              {pwMsg && (
                <div style={{ marginTop: 8, fontSize: 12, color: pwMsg.ok ? 'var(--ok)' : 'var(--bad)' }}>
                  {pwMsg.ok ? '✓ ' : '⚠ '}{pwMsg.text}
                </div>
              )}

              <div style={{ marginTop: 28, padding: '14px 16px', background: 'var(--paper-2)', borderRadius: 6, border: '1px solid var(--rule)' }}>
                <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink)', marginBottom: 4 }}>Active sessions</div>
                <div style={{ fontSize: 11.5, color: 'var(--ink-4)', marginBottom: 10 }}>Sign out of all other devices where you're currently logged in.</div>
                <button className="btn btn-sm"><Icon name="logout" size={11}/> Sign out other sessions</button>
              </div>

              {role === 'superadmin' && <LoginProtectionToggle />}
            </div>
          )}

          {/* ── Password Pattern (superadmin only) ── */}
          {tab === 'pwd-pattern' && role === 'superadmin' && (
            <div>
              <div style={{ fontSize: 13, lineHeight: 1.65, color: 'var(--ink-3)', marginBottom: 20 }}>
                Default passwords are auto-generated when creating users. Available tokens:{' '}
                {['{department}', '{fullname}', '{first4name}', '{regnumber}'].map((t) => (
                  <span key={t} className="mono" style={{ background: 'var(--paper-3)', padding: '1px 6px', borderRadius: 3, marginRight: 5 }}>{t}</span>
                ))}
                <br/><span style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 4, display: 'block' }}>
                  All tokens → lowercase, no spaces. <code>{'{first4name}'}</code> = first 4 chars of full name.
                  Changes take effect immediately for all new user creation.
                </span>
              </div>

              {[
                { key: 'superadmin', label: 'Superadmin',  previewData: { department: 'dept', fullname: 'username', regnumber: '' } },
                { key: 'admin',      label: 'Faculty',      previewData: { department: 'dept', fullname: 'username', regnumber: '' } },
                { key: 'user',       label: 'Student',      previewData: { department: 'cse', fullname: 'priyakrishnan', regnumber: '22cse1042' } },
              ].map(({ key, label, previewData }) => (
                <div key={key} style={{ marginBottom: 20 }}>
                  <Field label={`${label} pattern`}>
                    <TextInput value={patterns[key]} onChange={(v) => setPatterns((p) => ({ ...p, [key]: v }))} mono/>
                  </Field>
                  <div style={{
                    marginTop: -8, marginBottom: 4, padding: '10px 12px', borderRadius: '0 0 6px 6px',
                    background: 'color-mix(in oklab, var(--accent) 6%, var(--paper-2))',
                    border: '1px solid color-mix(in oklab, var(--accent) 18%, var(--rule))',
                    borderTop: 'none',
                  }}>
                    <span style={{ fontSize: 10, color: 'var(--ink-4)' }}>Preview → </span>
                    <span className="mono" style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>
                      {patternPreview(key, previewData)}
                    </span>
                  </div>
                </div>
              ))}

              {patternMsg && (
                <div style={{
                  marginBottom: 12, padding: '8px 12px', borderRadius: 5, fontSize: 12,
                  fontWeight: 500,
                  color: patternMsg.ok ? 'var(--ok)' : 'var(--bad)',
                  background: patternMsg.ok ? 'var(--ok-wash)' : 'var(--bad-wash)',
                  border: '1px solid',
                  borderColor: patternMsg.ok ? 'var(--ok)' : 'var(--bad)',
                }}>
                  {patternMsg.ok ? '✓ ' : '⚠ '}{patternMsg.text}
                </div>
              )}
              <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                <button className="btn btn-primary btn-sm"
                  disabled={patternSaving}
                  onClick={handleSavePatterns}
                  style={{ marginTop: 4 }}>
                  {patternSaving ? 'Saving…' : 'Save patterns'}
                </button>
                <button className="btn btn-ghost btn-sm"
                  style={{ marginTop: 4 }}
                  onClick={() => { setPatterns(Object.assign({}, defaultPatterns)); setPatternMsg(null); }}>
                  Reset to defaults
                </button>
              </div>
            </div>
          )}

          {/* ── Institution (superadmin only) ── */}
          {tab === 'institution' && role === 'superadmin' && (
            <div>
              <Field label="Institution name">
                <TextInput value={instName} onChange={setInstName}/>
              </Field>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
                <Field label="Affiliated colleges">
                  <TextInput value="" onChange={() => {}} disabled placeholder="—"/>
                </Field>
                <Field label="Total users">
                  <TextInput value="" onChange={() => {}} disabled placeholder="—"/>
                </Field>
              </div>
              <Field label="Default analytics access for new slots" hint="Applies when faculty default isn't set">
                <SelectInput value="granted" onChange={() => {}}
                  options={[{ value: 'granted', label: 'Granted by default' }, { value: 'restricted', label: 'Restricted by default' }]}/>
              </Field>
              <Field label="Default attempts per slot">
                <SelectInput value="1" onChange={() => {}} options={['1', '2', '3']}/>
              </Field>
              <div style={{ marginTop: 18, padding: '14px 16px', border: '1px solid oklch(0.62 0.18 25 / 0.35)', background: 'oklch(0.62 0.18 25 / 0.06)', borderRadius: 6 }}>
                <div style={{ fontSize: 12.5, fontWeight: 600, color: 'oklch(0.45 0.19 25)', marginBottom: 4 }}>Danger zone</div>
                <div style={{ fontSize: 11.5, color: 'var(--ink-4)', marginBottom: 10 }}>Archive all closed slots older than 12 months. Cannot be undone.</div>
                <button className="btn btn-sm" style={{ color: 'oklch(0.45 0.19 25)', borderColor: 'oklch(0.62 0.18 25 / 0.4)' }}>Archive old slots</button>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// Small local avatar (renamed to avoid clash with charts.jsx Avatar)
function AdminAvatar({ name, size = 32, seed = 1 }) {
  const init = initials(name);
  const hue = (seed * 67) % 360;
  return (
    <div style={{
      width: size, height: size, borderRadius: '50%',
      background: `oklch(0.82 0.06 ${hue})`,
      color: `oklch(0.3 0.1 ${hue})`,
      display: 'grid', placeItems: 'center',
      fontSize: size * 0.38, fontWeight: 600,
      border: '1px solid var(--rule)', flexShrink: 0,
    }}>{init}</div>
  );
}

// ── Bulk CSV Import Modal ────────────────────────────────────
function BulkImportModal({ onClose, onCreate }) {
  const [step, setStep]           = React.useState(0); // 0=upload, 1=preview, 2=done
  const [rows, setRows]           = React.useState([]);
  const [error, setError]         = React.useState('');
  const [dragging, setDragging]   = React.useState(false);
  const fileRef                   = React.useRef(null);

  // Known colleges fetched from API
  const [knownColleges, setKnownColleges] = React.useState([]);
  React.useEffect(() => {
    apiFetch('/colleges/')
      .then((d) => setKnownColleges(Array.isArray(d) ? d : []))
      .catch(() => setKnownColleges([]));
  }, []);

  // Compute which (college, dept) pairs are missing from the tool
  const missingPairs = React.useMemo(() => {
    const seen = new Set();
    const missing = [];
    for (const row of rows) {
      const col  = (row.college || '').trim();
      const dept = (row.dept    || '').trim();
      if (!col || !dept) continue;
      const key = `${col.toLowerCase()}|||${dept.toLowerCase()}`;
      if (seen.has(key)) continue;
      seen.add(key);
      const foundCollege = knownColleges.find(
        (c) => c.name.toLowerCase() === col.toLowerCase()
      );
      const deptExists = foundCollege
        ? foundCollege.departments.some((d) => d.name.toLowerCase() === dept.toLowerCase())
        : false;
      if (!foundCollege || !deptExists) {
        missing.push({ college: col, dept, collegeNew: !foundCollege, deptNew: !deptExists });
      }
    }
    return missing;
  }, [rows, knownColleges]);

  const pwPattern = (window.DEFAULT_PASSWORD_PATTERNS || {}).user || '{department}_{regnumber}';
  const applyPw   = window.applyPasswordPattern || ((p, d) =>
    p.replace(/\{department\}/g, d.department)
     .replace(/\{fullname\}/g, d.fullname)
     .replace(/\{regnumber\}/g, d.regnumber));

  function parseCSV(text) {
    const lines = text.trim().split(/\r?\n/).filter(Boolean);
    if (lines.length < 2) return { err: 'CSV must have a header row and at least one data row.' };
    const header = lines[0].split(',').map((h) => h.trim().toLowerCase().replace(/\s+/g, ''));
    const nameIdx    = header.findIndex((h) => h.includes('name'));
    const emailIdx   = header.findIndex((h) => h.includes('email'));
    const regIdx     = header.findIndex((h) => h.includes('reg') || h.includes('roll'));
    const deptIdx    = header.findIndex((h) => h.includes('dept') || h.includes('department') || h.includes('branch'));
    const collegeIdx = header.findIndex((h) => h.includes('college') || h.includes('institution') || h.includes('university'));
    if (nameIdx < 0 || emailIdx < 0) return { err: 'CSV must include at least "Name" and "Email" columns.' };

    const parsed = [];
    for (let i = 1; i < lines.length; i++) {
      const cols    = lines[i].split(',').map((c) => c.trim());
      const name    = cols[nameIdx]    || '';
      const email   = cols[emailIdx]   || '';
      const regNo   = regIdx     >= 0 ? (cols[regIdx]     || '') : '';
      const dept    = deptIdx    >= 0 ? (cols[deptIdx]    || '') : '';
      const college = collegeIdx >= 0 ? (cols[collegeIdx] || '') : '';
      if (!name || !email) continue;
      const normDept = dept.toLowerCase().replace(/\s+/g, '');
      const normName = name.toLowerCase().replace(/\s+/g, '');
      const normReg  = regNo.toLowerCase().replace(/\s+/g, '');
      const password = applyPw(pwPattern, { department: normDept, fullname: normName, regnumber: normReg || normName });
      parsed.push({ name, email, regNo, dept, college, password });
    }
    if (parsed.length === 0) return { err: 'No valid rows found after parsing.' };
    return { rows: parsed };
  }

  function handleFile(file) {
    if (!file) return;
    if (!file.name.endsWith('.csv')) { setError('Please upload a .csv file.'); return; }
    const reader = new FileReader();
    reader.onload = (e) => {
      const result = parseCSV(e.target.result);
      if (result.err) { setError(result.err); return; }
      setError('');
      setRows(result.rows);
      setStep(1);
    };
    reader.readAsText(file);
  }

  async function downloadDocx() {
    const tableRows = rows.map((r) => `
      <tr>
        <td style="border:1px solid #ccc;padding:6px 10px;font-size:11pt">${r.name}</td>
        <td style="border:1px solid #ccc;padding:6px 10px;font-size:11pt">${r.email}</td>
        <td style="border:1px solid #ccc;padding:6px 10px;font-size:11pt;font-family:monospace">${r.regNo}</td>
        <td style="border:1px solid #ccc;padding:6px 10px;font-size:11pt">${r.college || '—'}</td>
        <td style="border:1px solid #ccc;padding:6px 10px;font-size:11pt">${r.dept}</td>
        <td style="border:1px solid #ccc;padding:6px 10px;font-size:11pt;font-family:monospace;font-weight:bold">${r.password}</td>
      </tr>`).join('');
    const html = `
<html xmlns:o="urn:schemas-microsoft-com:office:office"
      xmlns:w="urn:schemas-microsoft-com:office:word"
      xmlns="http://www.w3.org/TR/REC-html40">
<head><meta charset="utf-8"/><title>Student Login Credentials</title>
<style>body{font-family:Calibri,sans-serif;margin:40px;color:#1a1a1a}h1{font-size:18pt}p{font-size:10pt;color:#555}table{border-collapse:collapse;width:100%;margin-top:12pt}th{background:#2a2419;color:#fff;padding:8px 10px;font-size:10pt;text-align:left;border:1px solid #2a2419}</style>
</head><body>
  <h1>ChiselAssess — Student Login Credentials</h1>
  <p>Generated: ${new Date().toLocaleDateString('en-IN')} &nbsp;·&nbsp; Password pattern: <code>${pwPattern}</code> &nbsp;·&nbsp; ${rows.length} students</p>
  <table><thead><tr><th>Name</th><th>Email</th><th>Reg. No.</th><th>College</th><th>Department</th><th>Password</th></tr></thead>
  <tbody>${tableRows}</tbody></table>
  <p style="margin-top:24pt;font-size:9pt;color:#999">Distribute securely. Students should change their password on first login.</p>
</body></html>`;
    const blob = new Blob([html], { type: 'application/msword' });
    const url  = URL.createObjectURL(blob);
    const a    = document.createElement('a');
    a.href = url; a.download = 'student-credentials.doc';
    document.body.appendChild(a); a.click();
    setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 0);
  }

  const [importing, setImporting]     = React.useState(false);
  const [importResult, setImportResult] = React.useState(null);
  const [provisionLog, setProvisionLog] = React.useState([]); // tracking auto-created depts

  async function confirmImport() {
    setImporting(true);
    const log = [];

    // ── STEP A: Ensure all (college, dept) pairs exist in the tool ────────────
    const uniquePairs = new Map();
    for (const row of rows) {
      const col  = (row.college || '').trim();
      const dept = (row.dept    || '').trim();
      if (!col || !dept) continue;
      const key = `${col.toLowerCase()}|||${dept.toLowerCase()}`;
      if (!uniquePairs.has(key)) uniquePairs.set(key, { college: col, dept });
    }

    for (const { college, dept } of uniquePairs.values()) {
      try {
        const res = await apiFetch('/colleges/ensure-dept', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ college_name: college, dept_name: dept }),
        });
        // Log only newly created items
        if (res.college?.created || res.department?.created) {
          log.push({
            college,
            dept,
            collegeNew: res.college?.created,
            deptNew: res.department?.created,
            message: res.message,
          });
        }
      } catch (e) {
        // Non-fatal — user creation will still proceed; dept might already exist
        log.push({ college, dept, error: String(e.message || e), failed: true });
      }
    }
    setProvisionLog(log);

    // ── STEP B: Create user accounts ─────────────────────────────────────────
    let created = 0, failed = 0;
    for (const row of rows) {
      try {
        await apiFetch('/auth/admin/create-user', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            name:       row.name,
            email:      row.email,
            password:   row.password,
            role:       'user',
            department: row.dept,
            college:    row.college || '',
            reg_number: row.regNo   || '',
          }),
        });
        created++;
      } catch (e) {
        failed++;
      }
    }
    setImportResult({ created, failed });
    setImporting(false);
    onCreate && onCreate(rows);
    setStep(2);
  }

  // Helper: is a (college, dept) pair missing in the tool?
  const isPairMissing = (college, dept) => {
    if (!college || !dept) return false;
    const found = knownColleges.find((c) => c.name.toLowerCase() === college.toLowerCase());
    if (!found) return true;
    return !found.departments.some((d) => d.name.toLowerCase() === dept.toLowerCase());
  };

  return (
    <Modal onClose={onClose} title="Bulk Import Students"
      subtitle="Upload a CSV file to add multiple students at once. Missing colleges or departments are created automatically."
      width={740}
      footer={
        step === 0 ? (
          <>
            <button className="btn btn-sm btn-ghost" onClick={onClose}>Cancel</button>
            <button className="btn btn-sm btn-primary" onClick={() => fileRef.current?.click()}>
              <Icon name="upload" size={11}/> Choose CSV file
            </button>
          </>
        ) : step === 1 ? (
          <>
            <button className="btn btn-sm btn-ghost" onClick={onClose}>Cancel</button>
            <button className="btn btn-sm" onClick={() => setStep(0)}><Icon name="arrowLeft" size={11}/> Re-upload</button>
            <button className="btn btn-sm" onClick={downloadDocx}><Icon name="download" size={11}/> Download Credentials (.doc)</button>
            <button className="btn btn-sm btn-primary" onClick={confirmImport} disabled={importing}>
              {importing ? 'Provisioning & importing…' : <><Icon name="check" size={11}/> Import {rows.length} students</>}
            </button>
          </>
        ) : (
          <>
            <button className="btn btn-sm" onClick={downloadDocx}><Icon name="download" size={11}/> Download Credentials (.doc)</button>
            <button className="btn btn-sm btn-primary" onClick={onClose}>Done</button>
          </>
        )
      }>

      {/* ── Step 0: Upload ── */}
      {step === 0 && (
        <div>
          <div
            onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
            onDragLeave={() => setDragging(false)}
            onDrop={(e) => { e.preventDefault(); setDragging(false); handleFile(e.dataTransfer.files[0]); }}
            onClick={() => fileRef.current?.click()}
            style={{
              border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--rule)'}`,
              borderRadius: 10, padding: '48px 32px', textAlign: 'center', cursor: 'pointer',
              background: dragging ? 'color-mix(in oklab, var(--accent) 6%, var(--paper-2))' : 'var(--paper-2)',
              transition: 'all .15s',
            }}>
            <div style={{ marginBottom: 12, color: 'var(--ink-4)' }}><Icon name="upload" size={32}/></div>
            <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink)', marginBottom: 6 }}>Drop your CSV here, or click to browse</div>
            <div style={{ fontSize: 12, color: 'var(--ink-4)' }}>Accepts .csv files only</div>
            <input ref={fileRef} type="file" accept=".csv" style={{ display: 'none' }}
              onChange={(e) => handleFile(e.target.files[0])}/>
          </div>

          {error && (
            <div style={{ marginTop: 12, padding: '10px 14px', background: 'var(--bad-wash)', border: '1px solid color-mix(in oklab, var(--bad) 30%, var(--rule))', borderRadius: 6, fontSize: 12.5, color: 'var(--bad)' }}>
              <Icon name="alert" size={12}/> {error}
            </div>
          )}

          <div style={{ marginTop: 20 }}>
            <div className="eyebrow mb-2">Expected CSV format</div>
            <div style={{ background: 'var(--paper-3)', borderRadius: 6, padding: '14px 16px', fontFamily: 'var(--f-mono)', fontSize: 11.5, lineHeight: 1.7, color: 'var(--ink-3)', border: '1px solid var(--rule)' }}>
              <div style={{ color: 'var(--accent)', fontWeight: 600 }}>Name, Email, Reg No, College, Department</div>
              <div>Priya Krishnan,priya@college.ac.in,22CSE1042,SRMIST,CSE</div>
              <div>Arjun Mehta,arjun@college.ac.in,22ECE0871,VIT Chennai,ECE</div>
            </div>
            <div style={{ marginTop: 10, fontSize: 11.5, color: 'var(--ink-4)', lineHeight: 1.65, padding: '8px 12px', background: 'var(--info-wash)', borderRadius: 5, border: '1px solid color-mix(in oklab, var(--info) 25%, var(--rule))' }}>
              <strong style={{ color: 'var(--info)' }}>Auto-provisioning:</strong>{' '}
              If a college or department in the CSV doesn't exist in the tool, it will be <strong>created automatically</strong> before the accounts are imported.
            </div>
          </div>
        </div>
      )}

      {/* ── Step 1: Preview ── */}
      {step === 1 && (
        <div>
          {/* Summary bar */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14, padding: '12px 14px', background: 'color-mix(in oklab, var(--ok) 10%, transparent)', borderRadius: 6, border: '1px solid color-mix(in oklab, var(--ok) 30%, var(--rule))' }}>
            <Icon name="check" size={16} style={{ color: 'var(--ok)' }}/>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 13, fontWeight: 600 }}>{rows.length} students parsed successfully</div>
              <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 2 }}>
                Password pattern: <span className="mono">{pwPattern}</span>
              </div>
            </div>
          </div>

          {/* Auto-provision notice if there are missing pairs */}
          {missingPairs.length > 0 && (
            <div style={{
              marginBottom: 14, padding: '12px 14px', borderRadius: 6,
              background: 'color-mix(in oklab, var(--warn) 9%, var(--card))',
              border: '1px solid color-mix(in oklab, var(--warn) 30%, var(--rule))',
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
                <Icon name="alert" size={14} style={{ color: 'var(--warn)' }}/>
                <span style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--warn)' }}>
                  {missingPairs.length} new college/department pair{missingPairs.length > 1 ? 's' : ''} will be auto-created
                </span>
              </div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                {missingPairs.map((p, i) => (
                  <span key={i} style={{
                    display: 'inline-flex', alignItems: 'center', gap: 4,
                    padding: '3px 8px', borderRadius: 4, fontSize: 11,
                    background: 'color-mix(in oklab, var(--warn) 14%, var(--card))',
                    border: '1px solid color-mix(in oklab, var(--warn) 35%, var(--rule))',
                    color: 'var(--warn)',
                  }}>
                    {p.collegeNew && <span style={{ fontWeight: 700 }}>★ </span>}
                    {p.college} → {p.dept}
                    {p.collegeNew && <span style={{ fontSize: 9, opacity: 0.8 }}> (new college)</span>}
                    {!p.collegeNew && p.deptNew && <span style={{ fontSize: 9, opacity: 0.8 }}> (new dept)</span>}
                  </span>
                ))}
              </div>
              <div style={{ fontSize: 10.5, color: 'var(--ink-4)', marginTop: 8 }}>
                ★ = entirely new college &nbsp;|&nbsp; Items without ★ = new department added to existing college.
                These will be created automatically when you click "Import".
              </div>
            </div>
          )}

          {/* Student table */}
          <div style={{ maxHeight: 340, overflowY: 'auto', borderRadius: 6, border: '1px solid var(--rule)' }}>
            <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
              <thead>
                <tr style={{ background: 'var(--paper-3)', position: 'sticky', top: 0 }}>
                  {['#','Name','Email','Reg. No.','College','Dept','Password'].map((h) => (
                    <th key={h} style={{ padding: '8px 10px', textAlign: 'left', fontWeight: 600, borderBottom: '1px solid var(--rule)', whiteSpace: 'nowrap' }}>{h}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {rows.map((r, i) => {
                  const missing = isPairMissing(r.college, r.dept);
                  return (
                    <tr key={i} style={{ borderBottom: '1px solid var(--rule)', background: i % 2 === 0 ? 'transparent' : 'var(--paper-2)' }}>
                      <td style={{ padding: '7px 10px', color: 'var(--ink-4)', fontFamily: 'var(--f-mono)', fontSize: 11 }}>{i + 1}</td>
                      <td style={{ padding: '7px 10px', fontWeight: 500 }}>{r.name}</td>
                      <td style={{ padding: '7px 10px', fontFamily: 'var(--f-mono)', fontSize: 11, color: 'var(--ink-3)' }}>{r.email}</td>
                      <td style={{ padding: '7px 10px', fontFamily: 'var(--f-mono)', fontSize: 11 }}>{r.regNo || '—'}</td>
                      <td style={{ padding: '7px 10px', color: 'var(--ink-3)' }}>
                        {r.college || '—'}
                        {missing && r.college && (
                          <span title="Will be auto-created" style={{ marginLeft: 4, fontSize: 9, padding: '1px 4px', borderRadius: 3, background: 'var(--warn-wash)', color: 'var(--warn)', fontWeight: 700 }}>NEW</span>
                        )}
                      </td>
                      <td style={{ padding: '7px 10px', color: 'var(--ink-3)' }}>
                        {r.dept || '—'}
                        {missing && r.dept && (
                          <span title="Will be auto-created" style={{ marginLeft: 4, fontSize: 9, padding: '1px 4px', borderRadius: 3, background: 'var(--warn-wash)', color: 'var(--warn)', fontWeight: 700 }}>NEW</span>
                        )}
                      </td>
                      <td style={{ padding: '7px 10px', fontFamily: 'var(--f-mono)', fontSize: 10.5, color: 'var(--accent)' }}>{r.password}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {/* ── Step 2: Done ── */}
      {step === 2 && importResult && (
        <div style={{ textAlign: 'center', padding: '20px 0' }}>
          <div style={{ width: 60, height: 60, borderRadius: '50%', background: 'var(--ok-wash)', border: '2px solid var(--ok)', display: 'grid', placeItems: 'center', margin: '0 auto 16px' }}>
            <Icon name="check" size={26} style={{ color: 'var(--ok)' }}/>
          </div>
          <div className="serif" style={{ fontSize: 24, marginBottom: 6 }}>Import complete</div>
          <div style={{ fontSize: 13, color: 'var(--ink-3)', marginBottom: 20 }}>
            <strong style={{ color: 'var(--ok)' }}>{importResult.created}</strong> accounts created
            {importResult.failed > 0 && <> · <strong style={{ color: 'var(--bad)' }}>{importResult.failed}</strong> failed (already exist or invalid)</>}
          </div>

          {/* Auto-provisioning log */}
          {provisionLog.length > 0 && (
            <div style={{ textAlign: 'left', maxWidth: 520, margin: '0 auto 16px', padding: '14px 16px', borderRadius: 8, background: 'var(--paper-2)', border: '1px solid var(--rule)' }}>
              <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 10, color: 'var(--ink)' }}>
                Auto-provisioned colleges & departments:
              </div>
              {provisionLog.map((item, i) => (
                <div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: 8, marginBottom: 6, fontSize: 11.5, color: item.failed ? 'var(--bad)' : 'var(--ok)' }}>
                  <span style={{ marginTop: 1 }}>{item.failed ? '✗' : '✓'}</span>
                  <span style={{ color: 'var(--ink)' }}>
                    {item.failed
                      ? <><strong>{item.college}</strong> → <strong>{item.dept}</strong>: <span style={{ color: 'var(--bad)' }}>{item.error}</span></>
                      : <>{item.message}</>
                    }
                  </span>
                </div>
              ))}
            </div>
          )}

          <div style={{ fontSize: 11.5, color: 'var(--ink-4)', marginBottom: 16 }}>
            Credentials sheet ready to download and share with students.
          </div>
        </div>
      )}
    </Modal>
  );
}

function CollegesView() {
  const [colleges, setColleges]       = React.useState([]);
  const [loading, setLoading]         = React.useState(true);
  const [err, setErr]                 = React.useState('');
  const [expanded, setExpanded]       = React.useState({});   // {collegeId: bool}
  const [addingCollege, setAddingCollege] = React.useState(false);
  const [newCollegeName, setNewCollegeName] = React.useState('');
  const [addingDept, setAddingDept]   = React.useState(null); // college id or null
  const [newDeptName, setNewDeptName] = React.useState('');
  const [saving, setSaving]           = React.useState(false);

  const load = React.useCallback(() => {
    setLoading(true); setErr('');
    apiFetch('/colleges/')
      .then((data) => { setColleges(Array.isArray(data) ? data : []); })
      .catch((e)   => setErr(e.message || 'Failed to load colleges'))
      .finally(()  => setLoading(false));
  }, []);

  React.useEffect(load, [load]);

  const toggleExpand = (id) => setExpanded((p) => ({ ...p, [id]: !p[id] }));

  const createCollege = async () => {
    if (!newCollegeName.trim()) return;
    setSaving(true);
    try {
      await apiFetch('/colleges/', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: newCollegeName.trim() }),
      });
      setNewCollegeName(''); setAddingCollege(false);
      load();
    } catch (e) { alert(e.message || 'Failed to create college'); }
    finally { setSaving(false); }
  };

  const deleteCollege = async (id, name) => {
    if (!confirm(`Delete college "${name}" and all its departments? This cannot be undone.`)) return;
    try {
      await apiFetch(`/colleges/${id}`, { method: 'DELETE' });
      load();
    } catch (e) { alert(e.message || 'Failed to delete college'); }
  };

  const addDept = async (collegeId) => {
    if (!newDeptName.trim()) return;
    setSaving(true);
    try {
      await apiFetch(`/colleges/${collegeId}/departments`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: newDeptName.trim() }),
      });
      setNewDeptName(''); setAddingDept(null);
      load();
    } catch (e) { alert(e.message || 'Failed to add department'); }
    finally { setSaving(false); }
  };

  const deleteDept = async (collegeId, deptId, deptName) => {
    if (!confirm(`Remove department "${deptName}"?`)) return;
    try {
      await apiFetch(`/colleges/${collegeId}/departments/${deptId}`, { method: 'DELETE' });
      load();
    } catch (e) { alert(e.message || 'Failed to delete department'); }
  };

  return (
    <div className="fade-up">
      <div className="page-head">
        <div>
          <div className="eyebrow mb-2">Institution</div>
          <h1 className="page-title">Colleges &amp; <em>Departments</em></h1>
          <div className="page-sub">Manage colleges and their branch/department list. These are available as options during student registration and slot filtering.</div>
        </div>
        <button className="btn btn-primary" onClick={() => { setAddingCollege(true); setNewCollegeName(''); }}>
          <Icon name="plus" size={13}/> Add college
        </button>
      </div>

      {/* Add college inline form */}
      {addingCollege && (
        <div className="card fade-in" style={{ padding: '16px 20px', marginBottom: 16, display: 'flex', gap: 10, alignItems: 'center' }}>
          <input
            className="input"
            style={{ flex: 1 }}
            placeholder="College name…"
            value={newCollegeName}
            onChange={(e) => setNewCollegeName(e.target.value)}
            onKeyDown={(e) => { if (e.key === 'Enter') createCollege(); if (e.key === 'Escape') setAddingCollege(false); }}
            autoFocus
          />
          <button className="btn btn-primary btn-sm" onClick={createCollege} disabled={saving || !newCollegeName.trim()}>
            {saving ? 'Saving…' : 'Save'}
          </button>
          <button className="btn btn-ghost btn-sm" onClick={() => setAddingCollege(false)}>Cancel</button>
        </div>
      )}

      {loading && <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-4)' }}>Loading…</div>}
      {err && <div style={{ padding: 16, color: 'var(--bad)', background: 'color-mix(in oklab, var(--bad) 8%, transparent)', borderRadius: 6, marginBottom: 12 }}>{err}</div>}

      {!loading && colleges.length === 0 && !err && (
        <div style={{ padding: '60px 0', textAlign: 'center', color: 'var(--ink-4)' }}>
          <Icon name="layers" size={32} style={{ opacity: 0.3, display: 'block', margin: '0 auto 14px' }}/>
          <div style={{ fontSize: 14, fontWeight: 500, marginBottom: 6 }}>No colleges yet</div>
          <div style={{ fontSize: 12 }}>Click "Add college" to create your first institution.</div>
        </div>
      )}

      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {colleges.map((c) => (
          <div key={c.id} className="card fade-up" style={{ overflow: 'hidden' }}>
            {/* College header row */}
            <div
              style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px 18px', cursor: 'pointer', userSelect: 'none' }}
              onClick={() => toggleExpand(c.id)}
            >
              <div style={{
                width: 32, height: 32, borderRadius: '50%',
                background: 'linear-gradient(135deg, var(--accent), oklch(0.55 0.18 250))',
                display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
              }}>
                <span style={{ fontSize: 13, fontWeight: 700, color: '#fff' }}>
                  {c.name.charAt(0).toUpperCase()}
                </span>
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink)' }}>{c.name}</div>
                <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 1 }}>
                  {c.departments.length} department{c.departments.length !== 1 ? 's' : ''}
                </div>
              </div>
              <button
                className="btn btn-ghost btn-sm"
                style={{ color: 'var(--ink-4)', fontSize: 11 }}
                onClick={(e) => { e.stopPropagation(); setAddingDept(c.id); setNewDeptName(''); setExpanded((p) => ({ ...p, [c.id]: true })); }}
              >
                <Icon name="plus" size={11}/> Add dept
              </button>
              <button
                className="btn btn-ghost btn-sm"
                style={{ color: 'var(--bad)' }}
                onClick={(e) => { e.stopPropagation(); deleteCollege(c.id, c.name); }}
              >
                <Icon name="trash" size={12}/>
              </button>
              <Icon name={expanded[c.id] ? 'arrowUp' : 'arrowDown'} size={13} style={{ color: 'var(--ink-4)', flexShrink: 0 }}/>
            </div>

            {/* Department list (expanded) */}
            {expanded[c.id] && (
              <div style={{ borderTop: '1px solid var(--rule)', background: 'var(--paper-2)' }}>
                {/* Add dept inline form */}
                {addingDept === c.id && (
                  <div style={{ padding: '10px 18px', display: 'flex', gap: 8, alignItems: 'center', borderBottom: '1px solid var(--rule)', background: 'var(--paper)' }}>
                    <input
                      className="input"
                      style={{ flex: 1 }}
                      placeholder="Department / branch name…"
                      value={newDeptName}
                      onChange={(e) => setNewDeptName(e.target.value)}
                      onKeyDown={(e) => { if (e.key === 'Enter') addDept(c.id); if (e.key === 'Escape') setAddingDept(null); }}
                      autoFocus
                    />
                    <button className="btn btn-primary btn-sm" onClick={() => addDept(c.id)} disabled={saving || !newDeptName.trim()}>
                      {saving ? '…' : 'Add'}
                    </button>
                    <button className="btn btn-ghost btn-sm" onClick={() => setAddingDept(null)}>Cancel</button>
                  </div>
                )}

                {c.departments.length === 0 && addingDept !== c.id && (
                  <div style={{ padding: '14px 18px', fontSize: 12, color: 'var(--ink-4)' }}>
                    No departments yet. Click "Add dept" to add one.
                  </div>
                )}

                {c.departments.map((d, di) => (
                  <div key={d.id} style={{
                    display: 'flex', alignItems: 'center', gap: 10,
                    padding: '9px 18px 9px 54px',
                    borderBottom: di < c.departments.length - 1 ? '1px solid var(--rule)' : 'none',
                  }}>
                    <span style={{ fontSize: 11, color: 'var(--ink-4)', marginRight: 4 }}>└</span>
                    <span style={{ fontSize: 13, color: 'var(--ink)', flex: 1 }}>{d.name}</span>
                    <span style={{ fontSize: 10, color: 'var(--ink-5)', fontFamily: 'monospace' }}>
                      {d.created_at ? d.created_at.slice(0, 10) : ''}
                    </span>
                    <button
                      className="btn btn-ghost btn-sm"
                      style={{ color: 'var(--bad)', padding: '2px 6px' }}
                      onClick={() => deleteDept(c.id, d.id, d.name)}
                    >
                      <Icon name="trash" size={11}/>
                    </button>
                  </div>
                ))}
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}


// ── ExportFilterModal — delegates to the new DownloadModal ──────────────────
// DownloadModal is loaded by download.jsx which is included in index.html
function ExportFilterModal({ onClose, slotId = null, slotTitle = null, slots = null }) {
  const DM = window.DownloadModal;
  if (DM) {
    return <DM onClose={onClose} slotId={slotId} slotTitle={slotTitle} slots={slots} />;
  }
  return (
    <Modal onClose={onClose} title="Export" width={360}>
      <div style={{ padding: 20, textAlign: 'center', color: 'var(--ink-3)' }}>
        Download module loading… please refresh if this persists.
      </div>
    </Modal>
  );
}

Object.assign(window, {
  Modal, Field, TextInput, SelectInput, Toggle, MultiPillSelect,
  NewSlotModal, NewUserModal, BulkImportModal, AnalyticsAccessView, DocsView, SettingsView,
  CollegesView, ExportFilterModal, EditSlotModal,
});

// ── Edit Slot Modal (superadmin only) ─────────────────────────────────────────
function EditSlotModal({ slot, onClose, onSaved }) {
  const [title, setTitle]         = React.useState(slot.title || '');
  const [description, setDesc]    = React.useState(slot.description || '');
  // Frontend uses 'open'/'closed'; API expects 'active'/'closed'
  // Map frontend -> API for the dropdown value
  const [status, setStatus]       = React.useState(slot.status === 'open' ? 'active' : 'closed');
  // normaliseApiSlot stores max_attempts as slot.attempts
  const [maxAttempts, setMaxAtt]  = React.useState(String(slot.attempts != null ? slot.attempts : (slot.max_attempts != null ? slot.max_attempts : '')));
  const [expiresAt, setExpires]   = React.useState(() => {
    if (!slot.expires_at) return '';
    try {
      const d = new Date(slot.expires_at);
      // Format as datetime-local: YYYY-MM-DDTHH:MM
      const pad = (n) => String(n).padStart(2, '0');
      return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
    } catch { return ''; }
  });
  const [saving, setSaving]       = React.useState(false);
  const [err, setErr]             = React.useState('');

  // The original frontend status (normalised: 'open' or 'closed')
  const origFrontendStatus = slot.status === 'open' ? 'active' : 'closed';

  const save = async () => {
    if (!title.trim()) { setErr('Title is required.'); return; }
    setSaving(true); setErr('');
    try {
      const body = { title: title.trim() };
      if (description.trim() !== (slot.description || '').trim()) body.description = description.trim();
      // Send API-side status ('active' or 'closed')
      if (status !== origFrontendStatus) body.status = status;
      if (expiresAt) body.expires_at = expiresAt;
      else if (slot.expires_at) body.expires_at = '';  // clear
      const att = parseInt(maxAttempts, 10);
      if (!isNaN(att) && att > 0) body.max_attempts = att;
      else if (maxAttempts === '' && (slot.attempts || slot.max_attempts)) body.max_attempts = null;

      await apiFetch(`/slots/${slot.id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });

      // Map API status back to frontend normalised status before updating local state
      const updatedFrontend = { ...slot, ...body };
      if (body.status !== undefined) {
        updatedFrontend.status = body.status === 'active' ? 'open' : 'closed';
      }
      if (body.max_attempts !== undefined) {
        updatedFrontend.attempts = body.max_attempts;
      }
      onSaved && onSaved(updatedFrontend);
      onClose();
    } catch (ex) {
      setErr(ex.message || 'Save failed.');
    } finally { setSaving(false); }
  };

  return (
    <Modal
      title="Edit Slot"
      subtitle={`Slot ID: ${slot.id}`}
      onClose={onClose}
      width={520}
      footer={
        <>
          <button className="btn btn-ghost btn-sm" onClick={onClose}>Cancel</button>
          <button className="btn btn-accent btn-sm" onClick={save} disabled={saving}>
            {saving ? 'Saving…' : 'Save changes'}
          </button>
        </>
      }
    >
      {err && (
        <div style={{ marginBottom: 14, padding: '9px 12px', background: 'color-mix(in oklab, var(--bad) 10%, transparent)', border: '1px solid color-mix(in oklab, var(--bad) 30%, transparent)', borderRadius: 6, fontSize: 12.5, color: 'var(--bad)' }}>
          {err}
        </div>
      )}

      <Field label="Slot title" required>
        <TextInput value={title} onChange={setTitle} placeholder="e.g. Mid-semester Assessment — CSE Batch A" />
      </Field>

      <Field label="Description">
        <textarea
          value={description}
          onChange={(e) => setDesc(e.target.value)}
          placeholder="Optional description for faculty/students…"
          rows={3}
          style={{
            width: '100%', padding: '10px 12px', background: 'var(--paper-2)',
            border: '1px solid var(--rule)', borderRadius: 6, fontSize: 13,
            color: 'var(--ink)', resize: 'vertical', outline: 'none',
            fontFamily: 'inherit',
          }}
        />
      </Field>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
        <Field label="Status">
          <SelectInput
            value={status}
            onChange={setStatus}
            options={[
              { value: 'active', label: '🟢 Open (active)' },
              { value: 'closed', label: '🔴 Closed' },
            ]}
          />
        </Field>
        <Field label="Max attempts per student" hint="Leave blank for unlimited">
          <TextInput
            value={maxAttempts}
            onChange={(v) => setMaxAtt(v.replace(/\D/g, ''))}
            placeholder="e.g. 1"
            type="number"
            style={{ minWidth: 0 }}
          />
        </Field>
      </div>

      <Field label="Expires at" hint="Datetime-local (interpreted as IST)">
        <input
          type="datetime-local"
          value={expiresAt}
          onChange={(e) => setExpires(e.target.value)}
          style={{
            width: '100%', padding: '10px 12px', background: 'var(--paper-2)',
            border: '1px solid var(--rule)', borderRadius: 6, fontSize: 13,
            color: 'var(--ink)', outline: 'none',
          }}
        />
        {expiresAt && (
          <button
            type="button"
            onClick={() => setExpires('')}
            style={{ marginTop: 5, fontSize: 11, color: 'var(--ink-4)', background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
          >
            ✕ Clear expiry (slot won't auto-close)
          </button>
        )}
      </Field>

      <div style={{ padding: '10px 12px', background: 'var(--paper-3)', border: '1px solid var(--rule)', borderRadius: 6, fontSize: 11.5, color: 'var(--ink-4)', marginTop: 4 }}>
        <b>Note:</b> Changing status to <em>Closed</em> immediately prevents students from submitting. Setting to <em>Open</em> re-enables access (subject to expiry time).
      </div>
    </Modal>
  );
}

// Re-export so the above assignment at top sees EditSlotModal
Object.assign(window, { EditSlotModal });
