// Rocks, Metrics Map, Notifications, My Scorecard views
// Rocks redesigned per DESIGN.md ("The Climbing Wall") — single column on mobile,
// two columns on desktop, amber transition at ≥75% progress, inline progress slider.

const ROCK_STATUS = {
  'done':     { label: 'Done',       cls: 'good',  color: 'var(--summit)'  },
  'on-track': { label: 'On Track',   cls: 'good',  color: 'var(--summit)'  },
  'at-risk':  { label: 'At Risk',    cls: 'warn',  color: 'var(--caution)' },
  'behind':   { label: 'Off Track',  cls: 'bad',   color: 'var(--slip)'    },
};

function inferRockStatus(rock) {
  if (rock.pct >= 100) return 'done';
  if (rock.pct >= 75)  return 'on-track';
  if (rock.pct >= 40)  return 'at-risk';
  return rock.status || 'behind';
}

function fmtRockDue(iso) {
  if (!iso) return '';
  const d = new Date(iso + 'T00:00:00');
  if (isNaN(d.getTime())) return iso;
  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}

function Rocks({ persona, mode = 'working' }) {
  const isMeeting = mode === 'meeting';
  const q = window.NORWILL_DATA.currentQuarter();
  const [items, setItems] = React.useState(() => ROCKS.map(r => ({ ...r })));
  const [expanded, setExpanded] = React.useState(null);

  const onTrackCount = items.filter(r => r.pct >= 75).length;
  const doneCount = items.filter(r => r.pct >= 100).length;
  const dueLabel = fmtRockDue(items[0]?.due) || fmtRockDue(q.endDate?.toISOString?.().slice(0,10));

  const setPct = (id, pct) => {
    setItems(curr => curr.map(r => r.id === id ? { ...r, pct: Math.max(0, Math.min(100, pct)) } : r));
  };

  return (
    <div style={{ padding: 'clamp(20px, 3vw, 32px)' }}>
      <div className="view-header" style={{ flexWrap: 'wrap' }}>
        <div>
          <div className="eyebrow">{q.label} · Quarterly priorities</div>
          <h2 className="view-title">Rocks</h2>
          <div className="view-sub">
            <span className="numeric" style={{ fontWeight: 600, color: 'var(--ink-800)' }}>{items.length}</span> rocks ·{' '}
            <span className="numeric" style={{ fontWeight: 600, color: 'var(--summit)' }}>{onTrackCount}</span> on pace ·{' '}
            <span className="numeric" style={{ fontWeight: 600, color: 'var(--norwill-forest)' }}>{doneCount}</span> done · due {dueLabel}
          </div>
        </div>
        <span style={{ flex: 1 }}/>
        {!isMeeting && (
          <button className="btn btn-primary btn-sm">
            <Icon.plus/>New Rock
          </button>
        )}
      </div>

      {items.length === 0 ? (
        <div className="card" style={{ padding: '32px 24px', textAlign: 'center' }}>
          <div style={{ fontFamily: 'var(--font-display)', fontWeight: 500, fontSize: 22 }}>No Rocks for this quarter yet.</div>
          <div style={{ color: 'var(--ink-600)', marginTop: 6, fontSize: 14 }}>Pick the 3 to 7 things that have to be done by quarter end. The team works them every L10.</div>
        </div>
      ) : (
        <div className="rocks-grid">
          {items.map(rock => (
            <RockCard
              key={rock.id}
              rock={rock}
              expanded={expanded === rock.id}
              isMeeting={isMeeting}
              onToggle={() => setExpanded(expanded === rock.id ? null : rock.id)}
              onSetPct={(v) => setPct(rock.id, v)}
            />
          ))}
        </div>
      )}
    </div>
  );
}

function RockCard({ rock, expanded, isMeeting, onToggle, onSetPct }) {
  const owner = PEOPLE.find(p => p.id === rock.ownerId);
  const status = inferRockStatus(rock);
  const meta = ROCK_STATUS[status] || ROCK_STATUS['behind'];
  const linkedMetrics = (rock.linked || []).map(id => METRICS.find(m => m.id === id)).filter(Boolean);
  const nearSummit = rock.pct >= 75;

  return (
    <div className="rock-card">
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
        <Avatar person={owner} size={32}/>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            fontFamily: 'var(--font-display)', fontWeight: 500,
            fontSize: isMeeting ? 19 : 17, lineHeight: 1.25,
            color: 'var(--ink-900)', letterSpacing: '-0.005em',
          }}>
            {rock.title}
          </div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', marginTop: 4 }}>
            <span style={{ fontSize: 12, color: 'var(--ink-600)' }}>{owner?.name?.split(' ')[0] || 'Open seat'}</span>
            <span style={{ color: 'var(--ink-300)' }}>·</span>
            <span style={{ fontSize: 12, color: 'var(--ink-500)' }}>Due {fmtRockDue(rock.due)}</span>
          </div>
        </div>
        <span className={'chip ' + meta.cls}>{meta.label}</span>
      </div>

      <div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 6 }}>
          <span className="eyebrow" style={{ fontSize: 10 }}>Progress</span>
          <span className="numeric" style={{ marginLeft: 'auto', fontFamily: 'var(--font-mono)', fontWeight: 600, color: nearSummit ? 'var(--amber-deep)' : 'var(--ink-800)' }}>
            {Math.round(rock.pct)}%
          </span>
        </div>
        <div className="rock-progress-track" role="progressbar" aria-valuenow={Math.round(rock.pct)} aria-valuemin={0} aria-valuemax={100} aria-label={'Progress on ' + rock.title}>
          <div className={'rock-progress-fill' + (nearSummit ? ' near-summit' : '')}
               style={{ width: rock.pct + '%' }}/>
        </div>
        {!isMeeting && (
          <input
            type="range"
            min={0} max={100} step={5}
            value={Math.round(rock.pct)}
            onChange={(e) => onSetPct(parseInt(e.target.value, 10))}
            className="rock-slider"
            aria-label={'Set progress for ' + rock.title}
          />
        )}
      </div>

      {linkedMetrics.length > 0 && (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
          {linkedMetrics.slice(0, expanded ? linkedMetrics.length : 3).map(m => (
            <span key={m.id} className="chip muted" title={m.label} style={{ maxWidth: 200, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'inline-block', height: 'auto' }}>
              {m.label}
            </span>
          ))}
          {!expanded && linkedMetrics.length > 3 && (
            <span style={{ fontSize: 11, color: 'var(--ink-500)', alignSelf: 'center' }}>+{linkedMetrics.length - 3} more</span>
          )}
        </div>
      )}

      <button
        type="button"
        onClick={onToggle}
        className="btn btn-quiet btn-sm"
        style={{ alignSelf: 'flex-start', padding: '4px 0', justifyContent: 'flex-start', gap: 6 }}
      >
        {expanded ? 'Hide details' : 'Details'}
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" style={{ transform: expanded ? 'rotate(180deg)' : 'none', transition: 'transform 160ms var(--ease-out)' }}><polyline points="6 9 12 15 18 9"/></svg>
      </button>

      {expanded && (
        <div style={{
          paddingTop: 12, borderTop: '1px solid var(--hairline)',
          fontSize: 13, color: 'var(--ink-700)', lineHeight: 1.55,
        }}>
          <div className="eyebrow" style={{ marginBottom: 6 }}>Linked metrics</div>
          {linkedMetrics.length === 0 ? (
            <div style={{ color: 'var(--ink-500)', fontStyle: 'italic' }}>No linked metrics.</div>
          ) : (
            <ul style={{ margin: 0, paddingLeft: 18, color: 'var(--ink-700)' }}>
              {linkedMetrics.map(m => (
                <li key={m.id} style={{ marginBottom: 2 }}>
                  {m.label} <span style={{ color: 'var(--ink-500)', fontSize: 11.5 }}>({m.fn})</span>
                </li>
              ))}
            </ul>
          )}
        </div>
      )}
    </div>
  );
}

function MetricsMap({ authUser }) {
  const [search, setSearch] = React.useState('');
  const [savingId, setSavingId] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [editor, setEditor] = React.useState(null);
  const [revisionPrompt, setRevisionPrompt] = React.useState(null);
  const isAdmin = !!authUser && authUser.role === 'admin';
  const isOwner = !!authUser && authUser.role === 'owner';
  const canEdit = isAdmin || isOwner;
  const ownerPersonaId = authUser?.personaId || null;
  const shown = METRICS.filter(m => m.label.toLowerCase().includes(search.toLowerCase()) || m.fn.toLowerCase().includes(search.toLowerCase()));

  function tableHead(extra) {
    return Object.assign({
      padding: '10px 12px',
      textTransform: 'uppercase',
      letterSpacing: 0.06,
      fontSize: 10.5,
      color: 'var(--ink-500)',
      fontWeight: 700,
      textAlign: 'center',
    }, extra || {});
  }

  function tableCell(extra) {
    return Object.assign({
      padding: '10px 12px',
      textAlign: 'center',
      verticalAlign: 'middle',
    }, extra || {});
  }

  function blankMetric() {
    return {
      id: '',
      name: '',
      owner: 'olie',
      fn: 'EOS System',
      dir: '>=',
      frequency: 'weekly',
      aggregationMethod: 'sum',
      dailyTarget: '',
      weeklyTarget: '',
      monthlyTarget: '',
      effectiveFrom: window.NORWILL_DATA.currentQuarter().startDate.toISOString().slice(0, 10),
      existing: false,
    };
  }

  function openEditor(metric) {
    if (!isAdmin) return;
    if (!metric) {
      setEditor(blankMetric());
      return;
    }
    setEditor({
      id: metric.id,
      name: metric.name || metric.label,
      owner: metric.owner || metric.ownerId,
      fn: metric.fn,
      dir: metric.dir,
      frequency: window.metricCadence(metric),
      aggregationMethod: metric.aggregationMethod || 'sum',
      dailyTarget: metric.dailyTarget != null ? String(metric.dailyTarget) : '',
      weeklyTarget: metric.weeklyTarget != null ? String(metric.weeklyTarget) : '',
      monthlyTarget: metric.monthlyTarget != null ? String(metric.monthlyTarget) : '',
      effectiveFrom: metric.effectiveFrom || window.NORWILL_DATA.currentQuarter().startDate.toISOString().slice(0, 10),
      createdAt: metric.createdAt || null,
      existing: true,
      original: metric,
    });
  }

  function parseMaybeNumber(raw) {
    if (raw === '' || raw == null) return null;
    const num = Number(raw);
    return Number.isFinite(num) ? num : null;
  }

  function definitionPayload(form) {
    return {
      id: form.id.trim(),
      name: form.name.trim(),
      owner: form.owner,
      fn: form.fn,
      dir: form.dir,
      frequency: form.frequency,
      aggregationMethod: form.aggregationMethod,
      dailyTarget: parseMaybeNumber(form.dailyTarget),
      weeklyTarget: parseMaybeNumber(form.weeklyTarget),
      monthlyTarget: parseMaybeNumber(form.monthlyTarget),
      effectiveFrom: form.effectiveFrom,
    };
  }

  function semanticChange(original, payload) {
    if (!original) return false;
    return (
      original.frequency !== payload.frequency ||
      (original.aggregationMethod || 'sum') !== payload.aggregationMethod ||
      (original.dailyTarget ?? null) !== payload.dailyTarget ||
      (original.weeklyTarget ?? null) !== payload.weeklyTarget ||
      (original.monthlyTarget ?? null) !== payload.monthlyTarget
    );
  }

  async function reassign(metric, newOwnerId) {
    if (!canEdit || newOwnerId === metric.ownerId) return;
    if (isOwner && newOwnerId !== ownerPersonaId) return;
    setSavingId(metric.id); setError(null);
    try {
      await firebase.firestore().collection('metricOverrides').doc(metric.id).set({
        ownerId:   newOwnerId,
        updatedBy: authUser.uid || authUser.username || 'unknown',
        updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
      }, { merge: true });
      // optimistic: snapshot listener in App will reconcile
      metric.ownerId = newOwnerId;
      metric.owner = newOwnerId;
    } catch (err) {
      setError(err?.message || 'Could not save change.');
    } finally {
      setSavingId(null);
    }
  }

  async function persistMetric(form, applyMode) {
    const payload = definitionPayload(form);
    if (!payload.id || !payload.name) {
      setError('Metric id and name are required.');
      return;
    }
    setSavingId(payload.id);
    setError(null);
    try {
      const ref = firebase.firestore().collection('metrics').doc(payload.id);
      await ref.set({
        id: payload.id,
        name: payload.name,
        owner: payload.owner,
        fn: payload.fn,
        dir: payload.dir,
        frequency: payload.frequency,
        aggregationMethod: payload.aggregationMethod,
        dailyTarget: payload.dailyTarget,
        weeklyTarget: payload.weeklyTarget,
        monthlyTarget: payload.monthlyTarget,
        effectiveFrom: payload.effectiveFrom,
        createdAt: form.createdAt || firebase.firestore.FieldValue.serverTimestamp(),
        updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
        deletedAt: null,
      }, { merge: true });
      if (form.existing && semanticChange(form.original, payload)) {
        await firebase.firestore().collection('metricRevisions').add({
          metricId: payload.id,
          changedBy: authUser.uid,
          changedAt: firebase.firestore.FieldValue.serverTimestamp(),
          applyMode,
          previous: {
            dailyTarget: form.original.dailyTarget ?? null,
            weeklyTarget: form.original.weeklyTarget ?? null,
            monthlyTarget: form.original.monthlyTarget ?? null,
            frequency: form.original.frequency || window.metricCadence(form.original),
            aggregationMethod: form.original.aggregationMethod || 'sum',
            effectiveFrom: form.original.effectiveFrom || null,
          },
          next: {
            dailyTarget: payload.dailyTarget,
            weeklyTarget: payload.weeklyTarget,
            monthlyTarget: payload.monthlyTarget,
            frequency: payload.frequency,
            aggregationMethod: payload.aggregationMethod,
            effectiveFrom: payload.effectiveFrom,
          },
          note: null,
        });
      }
      setEditor(null);
      setRevisionPrompt(null);
    } catch (err) {
      setError(err?.message || 'Could not save metric.');
    } finally {
      setSavingId(null);
    }
  }

  function saveMetric(form) {
    if (form.existing && semanticChange(form.original, definitionPayload(form))) {
      setRevisionPrompt(form);
      return;
    }
    persistMetric(form, 'history');
  }

  async function softDeleteMetric(metric) {
    if (!isAdmin) return;
    setSavingId(metric.id);
    setError(null);
    try {
      await firebase.firestore().collection('metrics').doc(metric.id).set({
        deletedAt: firebase.firestore.FieldValue.serverTimestamp(),
        updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
      }, { merge: true });
      setEditor(null);
    } catch (err) {
      setError(err?.message || 'Could not delete metric.');
    } finally {
      setSavingId(null);
    }
  }

  return (
    <div style={{ padding: 28 }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', gap: 14, marginBottom: 18 }}>
        <div>
          <div style={{ fontSize: 11, color: 'var(--ink-500)', letterSpacing: 0.06, textTransform: 'uppercase', fontWeight: 600 }}>Reference</div>
          <h2 style={{ margin: '4px 0 0', fontSize: 22, fontWeight: 700, letterSpacing: -0.4 }}>Metrics Map</h2>
          <div style={{ color: 'var(--ink-600)', fontSize: 13, marginTop: 4 }}>
            Canonical definitions · cadence · targets · aggregation
            {isAdmin && <span style={{ marginLeft: 8, color: 'var(--brand-700)', fontWeight: 600 }}>· Admin edits definitions</span>}
            {isOwner && <span style={{ marginLeft: 8, color: 'var(--brand-700)', fontWeight: 600 }}>· Owners can claim metrics only</span>}
          </div>
        </div>
        <span style={{ flex: 1 }}/>
        {isAdmin && (
          <button className="btn btn-primary btn-sm" onClick={() => openEditor(null)}>
            <Icon.plus/>New metric
          </button>
        )}
        <input value={search} onChange={e => setSearch(e.target.value)}
          placeholder="Search metrics…" style={{
          padding: '8px 12px', borderRadius: 8, border: '1px solid var(--line-strong)',
          fontSize: 13, width: 260, background: 'var(--card)' }}/>
      </div>
      {error && (
        <div style={{ marginBottom: 12, padding: '9px 12px', borderRadius: 9,
          background: 'var(--bad-bg)', color: 'var(--bad)', fontSize: 12.5 }}>{error}</div>
      )}
      <div className="card" style={{ overflow: 'hidden' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
          <thead>
            <tr style={{ background: 'var(--ink-50)', borderBottom: '1px solid var(--line)' }}>
              <th style={tableHead({ textAlign: 'left', width: 240 })}>Metric</th>
              <th style={tableHead({ textAlign: 'left', width: 120 })}>Function</th>
              <th style={tableHead({ width: 110 })}>Owner</th>
              <th style={tableHead({ width: 80 })}>Cadence</th>
              <th style={tableHead({ width: 60 })}>Dir</th>
              <th style={tableHead({ width: 70 })}>Daily</th>
              <th style={tableHead({ width: 70 })}>Weekly</th>
              <th style={tableHead({ width: 80 })}>Monthly</th>
              <th style={tableHead({ width: 70 })}>Agg</th>
              <th style={tableHead({ textAlign: 'left' })}>Rock</th>
              {canEdit && <th style={tableHead({ width: 120 })}>Actions</th>}
            </tr>
          </thead>
          <tbody>
            {shown.map((m, i) => {
              const owner = PEOPLE.find(p => p.id === m.ownerId);
              const rock = ROCKS.find(r => r.linked.includes(m.id));
              const canClaim = isOwner && ownerPersonaId && m.ownerId !== ownerPersonaId;
              return (
                <tr key={m.id} style={{ borderBottom: '1px solid var(--line)',
                  background: i % 2 ? 'var(--card-alt)' : 'var(--card)' }}>
                  <td style={tableCell({ textAlign: 'left', fontWeight: 600, color: 'var(--ink-800)' })}>{m.label}</td>
                  <td style={tableCell({ textAlign: 'left', color: 'var(--ink-600)' })}>{m.fn}</td>
                  <td style={tableCell()}>
                    {isAdmin ? (
                      <select
                        value={m.ownerId}
                        disabled={savingId === m.id}
                        onChange={(e) => reassign(m, e.target.value)}
                        title="Override owner"
                        style={{
                          padding: '4px 6px', borderRadius: 6,
                          border: '1px solid var(--line-strong)', background: 'var(--card)',
                          fontSize: 11.5, color: 'var(--ink-800)',
                          maxWidth: 120, opacity: savingId === m.id ? 0.5 : 1,
                        }}>
                        {PEOPLE.map(p => (
                          <option key={p.id} value={p.id}>{p.name.split(' ')[0]}{p.status === 'open' ? ' (open)' : ''}</option>
                        ))}
                      </select>
                    ) : (
                      <div style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
                        <Avatar person={owner} size={18}/>
                        <span style={{ fontSize: 11 }}>{owner?.name.split(' ')[0]}</span>
                      </div>
                    )}
                  </td>
                  <td style={tableCell()}><span className="chip" style={{ height: 18, fontSize: 10 }}>{window.metricCadence(m)}</span></td>
                  <td style={tableCell()}><span className="mono" style={{ fontSize: 11 }}>{m.dir}</span></td>
                  <td style={tableCell()}><span className="mono tnum">{m.dailyTarget != null ? fmtNum(m.dailyTarget, m) : '—'}</span></td>
                  <td style={tableCell()}><span className="mono tnum" style={{ fontWeight: 600 }}>{m.weeklyTarget != null ? fmtNum(m.weeklyTarget, m) : '—'}</span></td>
                  <td style={tableCell()}><span className="mono tnum">{m.monthlyTarget != null ? fmtNum(m.monthlyTarget, m) : '—'}</span></td>
                  <td style={tableCell()}><span className="chip" style={{ height: 18, fontSize: 10 }}>{window.metricAgg(m)}</span></td>
                  <td style={tableCell({ textAlign: 'left', fontSize: 11.5, color: rock ? 'var(--brand-700)' : 'var(--ink-400)' })}>
                    {rock ? rock.title : '—'}
                  </td>
                  {canEdit && (
                    <td style={tableCell()}>
                      <div style={{ display: 'inline-flex', gap: 6 }}>
                        {isAdmin && (
                          <button className="btn btn-quiet btn-sm" onClick={() => openEditor(m)} style={{ padding: '5px 9px' }}>
                            Edit
                          </button>
                        )}
                        {canClaim && (
                          <button className="btn btn-quiet btn-sm" onClick={() => reassign(m, ownerPersonaId)} style={{ padding: '5px 9px' }}>
                            Assign to me
                          </button>
                        )}
                      </div>
                    </td>
                  )}
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
      {editor && (
        <MetricEditorModal
          editor={editor}
          savingId={savingId}
          onChange={setEditor}
          onClose={() => setEditor(null)}
          onSave={() => saveMetric(editor)}
          onDelete={() => editor.original && softDeleteMetric(editor.original)}
        />
      )}
      {revisionPrompt && (
        <MetricRevisionPrompt
          metric={revisionPrompt}
          onCancel={() => setRevisionPrompt(null)}
          onChoose={(mode) => persistMetric(revisionPrompt, mode)}
        />
      )}
    </div>
  );
}

function MetricEditorModal({ editor, savingId, onChange, onClose, onSave, onDelete }) {
  const [deleteConfirm, setDeleteConfirm] = React.useState(false);
  const busy = savingId === editor.id;
  function setField(key, value) {
    onChange(Object.assign({}, editor, { [key]: value }));
  }
  const isNew = !editor.existing;
  return (
    <div style={{
      position: 'fixed', inset: 0, background: 'rgba(12, 20, 28, 0.42)',
      zIndex: 300, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }}>
      <div className="card" style={{ width: '100%', maxWidth: 640, padding: 20, maxHeight: '90vh', overflow: 'auto' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16 }}>
          <div>
            <div className="eyebrow">{isNew ? 'Create metric' : 'Edit definition'}</div>
            <div style={{ fontSize: 20, fontWeight: 700, color: 'var(--ink-900)' }}>{isNew ? 'New metric' : editor.name}</div>
          </div>
          <span style={{ flex: 1 }}/>
          <button className="btn btn-quiet btn-sm" onClick={onClose}>Close</button>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 12 }}>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Metric id</span>
            <input value={editor.id} disabled={editor.existing} onChange={(e) => setField('id', e.target.value.trim().toLowerCase())} style={metricInputStyle}/>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Name</span>
            <input value={editor.name} onChange={(e) => setField('name', e.target.value)} style={metricInputStyle}/>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Function</span>
            <input value={editor.fn} onChange={(e) => setField('fn', e.target.value)} style={metricInputStyle}/>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Direction</span>
            <select value={editor.dir} onChange={(e) => setField('dir', e.target.value)} style={metricInputStyle}>
              <option value=">=">{'>='}</option>
              <option value="<=">{'<='}</option>
            </select>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Owner</span>
            <select value={editor.owner} onChange={(e) => setField('owner', e.target.value)} style={metricInputStyle}>
              {PEOPLE.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
            </select>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Frequency</span>
            <select value={editor.frequency} onChange={(e) => setField('frequency', e.target.value)} style={metricInputStyle}>
              <option value="daily">daily</option>
              <option value="weekly">weekly</option>
              <option value="monthly">monthly</option>
            </select>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Aggregation</span>
            <select value={editor.aggregationMethod} onChange={(e) => setField('aggregationMethod', e.target.value)} style={metricInputStyle}>
              <option value="sum">sum</option>
              <option value="average">average</option>
              <option value="latest">latest</option>
              <option value="max">max</option>
            </select>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Effective from</span>
            <input type="date" value={editor.effectiveFrom} onChange={(e) => setField('effectiveFrom', e.target.value)} style={metricInputStyle}/>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Daily target</span>
            <input value={editor.dailyTarget} onChange={(e) => setField('dailyTarget', e.target.value)} style={metricInputStyle}/>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Weekly target</span>
            <input value={editor.weeklyTarget} onChange={(e) => setField('weeklyTarget', e.target.value)} style={metricInputStyle}/>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span className="eyebrow" style={{ fontSize: 10 }}>Monthly target</span>
            <input value={editor.monthlyTarget} onChange={(e) => setField('monthlyTarget', e.target.value)} style={metricInputStyle}/>
          </label>
        </div>
        <div style={{ display: 'flex', gap: 10, marginTop: 18, flexWrap: 'wrap' }}>
          {!deleteConfirm && (
            <button className="btn btn-primary btn-sm" onClick={onSave} disabled={busy}>
              {busy ? 'Saving…' : 'Save metric'}
            </button>
          )}
          {editor.original && !deleteConfirm && (
            <button className="btn btn-ghost btn-sm" onClick={() => setDeleteConfirm(true)} disabled={busy}>
              Delete
            </button>
          )}
          {deleteConfirm && (
            <div style={{
              width: '100%', padding: '12px 14px', borderRadius: 8,
              background: 'var(--slip-bg)', border: '1px solid rgba(181,60,60,0.2)',
            }}>
              <div style={{ fontWeight: 600, fontSize: 13, color: 'var(--ink-900)', marginBottom: 4 }}>Soft delete only</div>
              <div style={{ fontSize: 12.5, color: 'var(--ink-600)', marginBottom: 12, lineHeight: 1.5 }}>
                Historical entries for <strong>{editor.name}</strong> are preserved and will continue to appear in scorecard history. The metric will be hidden from active lists.
              </div>
              <div style={{ display: 'flex', gap: 8 }}>
                <button className="btn btn-sm" onClick={onDelete} disabled={busy} style={{
                  background: 'var(--slip)', color: '#fff', border: 'none',
                }}>
                  {busy ? 'Deleting…' : 'Confirm delete'}
                </button>
                <button className="btn btn-quiet btn-sm" onClick={() => setDeleteConfirm(false)} disabled={busy}>
                  Cancel
                </button>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function MetricRevisionPrompt({ metric, onCancel, onChoose }) {
  return (
    <div style={{
      position: 'fixed', inset: 0, background: 'rgba(12, 20, 28, 0.42)',
      zIndex: 320, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }}>
      <div className="card" style={{ width: '100%', maxWidth: 440, padding: 22 }}>
        <div className="eyebrow">History prompt</div>
        <div style={{ fontSize: 19, fontWeight: 700, color: 'var(--ink-900)', marginTop: 4 }}>Apply definition change to history?</div>
        <div style={{ fontSize: 13, color: 'var(--ink-600)', lineHeight: 1.5, marginTop: 8 }}>
          Target, frequency, or aggregation changed for <strong>{metric.name}</strong>. Choose whether to recalculate past periods or apply the new definition only from the current effective date forward.
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginTop: 18 }}>
          <button className="btn btn-primary btn-sm" onClick={() => onChoose('history')}>Apply to history</button>
          <button className="btn btn-ghost btn-sm" onClick={() => onChoose('forward')}>Current + future only</button>
          <button className="btn btn-quiet btn-sm" onClick={onCancel}>Cancel</button>
        </div>
      </div>
    </div>
  );
}

const metricInputStyle = {
  padding: '9px 10px',
  borderRadius: 8,
  border: '1px solid var(--line-strong)',
  background: 'var(--card)',
  color: 'var(--ink-900)',
  fontSize: 13,
};

function Notifications({ persona }) {
  return (
    <div style={{ padding: 28, maxWidth: 900 }}>
      <div style={{ marginBottom: 18 }}>
        <div style={{ fontSize: 11, color: 'var(--ink-500)', letterSpacing: 0.06, textTransform: 'uppercase', fontWeight: 600 }}>Email & in-app</div>
        <h2 style={{ margin: '4px 0 0', fontSize: 22, fontWeight: 700, letterSpacing: -0.4 }}>Notifications</h2>
        <div style={{ color: 'var(--ink-600)', fontSize: 13, marginTop: 4 }}>Reminders, nudges, and AI briefings delivered to your inbox and the Scorecard app.</div>
      </div>

      <div className="card" style={{ overflow: 'hidden' }}>
        <div style={{
          padding: '12px 16px', background: 'var(--card-alt)', borderBottom: '1px solid var(--line)',
          display: 'flex', alignItems: 'center', gap: 10, color: 'var(--ink-800)',
        }}>
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
          <div style={{ fontSize: 13, fontWeight: 600 }}>Inbox preview</div>
          <span style={{ flex: 1 }}/>
          <span title="Sample messages — not real sends" style={{
            fontSize: 9.5, fontWeight: 700, letterSpacing: 0.08,
            padding: '2px 6px', borderRadius: 4,
            background: 'var(--warn-bg)', color: 'var(--warn)',
            border: '1px solid var(--warn)',
          }}>DEMO</span>
        </div>
        <div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 14, background: 'var(--card-alt)' }}>
          {TEAMS_MESSAGES.map((msg, i) => (
            <div key={i} style={{
              display: 'flex', gap: 12, padding: 14, background: 'var(--card)',
              borderRadius: 10, border: '1px solid var(--line)',
            }}>
              <div style={{
                width: 36, height: 36, borderRadius: 8,
                background: msg.from === 'Scorecard Bot' ? 'var(--brand-700)' :
                  msg.from === 'AI Assistant' ? 'linear-gradient(135deg, var(--brand-700), var(--brand-500))' :
                  PEOPLE.find(p => p.avatar === msg.avatar)?.color || 'var(--ink-500)',
                color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 14, fontWeight: 600, flexShrink: 0,
              }}>{msg.avatar}</div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 4 }}>
                  <span style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--ink-900)' }}>{msg.from}</span>
                  <span style={{ fontSize: 11, color: 'var(--ink-500)' }}>{msg.ts}</span>
                  <span style={{ flex: 1 }}/>
                  <span style={{ fontSize: 10.5, color: 'var(--ink-500)', letterSpacing: 0.04, textTransform: 'uppercase', fontWeight: 600 }}>Email · In-app</span>
                </div>
                <div style={{ fontSize: 13, color: 'var(--ink-800)', lineHeight: 1.55 }}
                  dangerouslySetInnerHTML={{ __html: msg.body.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>') }}/>
                {msg.cta && <div style={{ display: 'flex', gap: 8, marginTop: 10 }}>
                  {msg.cta.map((c, ci) => (
                    <button key={c} style={{
                      background: ci === 0 ? 'var(--brand-700)' : 'var(--card)',
                      color: ci === 0 ? '#fff' : 'var(--brand-700)',
                      border: '1px solid var(--brand-700)', padding: '5px 12px',
                      fontSize: 12, borderRadius: 7, fontWeight: 600, cursor: 'pointer',
                    }}>{c}</button>
                  ))}
                </div>}
              </div>
            </div>
          ))}
        </div>
      </div>

      <div className="card" style={{ padding: 20, marginTop: 20 }}>
        <CardHead title="Notification rules" sub="When and how the system nudges your team"/>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginTop: 14 }}>
          {[
            { title: 'Morning standup prompt', when: 'Weekdays · 8:00 AM', channel: 'Email + in-app', on: true },
            { title: 'Same-day entry reminder', when: 'Weekdays · 4:30 PM if any blanks', channel: 'Email to owner', on: true },
            { title: 'Red metric alert', when: 'When metric flips red', channel: 'Email to owner + Olie', on: true },
            { title: 'L10 pre-brief', when: 'L10 morning · 9:00 AM', channel: 'Email with agenda PDF', on: true },
            { title: 'Weekly recap', when: 'Friday · 4:00 PM', channel: 'Email digest + PDF', on: false },
          ].map(rule => (
            <div key={rule.title} style={{
              display: 'flex', alignItems: 'center', gap: 12,
              padding: 12, borderRadius: 9, border: '1px solid var(--line)',
              background: 'var(--card-alt)',
            }}>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 600 }}>{rule.title}</div>
                <div style={{ fontSize: 11, color: 'var(--ink-500)', marginTop: 2 }}>{rule.when} · {rule.channel}</div>
              </div>
              <div style={{
                width: 34, height: 20, borderRadius: 99, padding: 2,
                background: rule.on ? 'var(--brand-500)' : 'var(--ink-200)',
                display: 'flex', alignItems: 'center',
              }}>
                <div style={{ width: 16, height: 16, borderRadius: '50%', background: '#fff',
                  transform: rule.on ? 'translateX(14px)' : 'translateX(0)', transition: 'transform 0.2s',
                  boxShadow: '0 1px 2px rgba(0,0,0,0.2)' }}/>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function MyScorecard({ persona }) {
  const mine = METRICS.filter(m => m.ownerId === persona.id);
  const myRocks = ROCKS.filter(r => r.ownerId === persona.id);
  const week = window.NORWILL_DATA.CURRENT_WEEK_IDX;

  const goToEntry = () => window.dispatchEvent(new CustomEvent('norwill:navigate', { detail: { view: 'huddle' } }));

  return (
    <div style={{ padding: 28, display: 'flex', flexDirection: 'column', gap: 20 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <Avatar person={persona} size={56}/>
        <div>
          <div style={{ fontSize: 11, color: 'var(--ink-500)', letterSpacing: 0.06, textTransform: 'uppercase', fontWeight: 600 }}>{persona.role}</div>
          <h2 style={{ margin: '4px 0 0', fontSize: 24, fontWeight: 700, letterSpacing: -0.5 }}>{persona.name}</h2>
          <div style={{ color: 'var(--ink-600)', fontSize: 13, marginTop: 2 }}>
            {mine.length} metrics · {myRocks.length} Rocks · Week {window.NORWILL_DATA.weekOfQuarter()} of {window.NORWILL_DATA.currentQuarter().label}
          </div>
        </div>
        <span style={{ flex: 1 }}/>
        <button
          onClick={goToEntry}
          style={{ background: 'var(--brand-700)', color: '#fff', border: 'none',
            padding: '9px 16px', borderRadius: 9, fontSize: 13, fontWeight: 600,
            display: 'inline-flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
          <Icon.chat/> Enter today's numbers
        </button>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 14 }}>
        {mine.map(m => {
          const val = HISTORY[m.id]?.[week] ?? null;
          const prev = HISTORY[m.id]?.[week - 1] ?? null;
          const status = metricStatus(m, val);
          const delta = (val != null && prev) ? ((val - prev) / prev * 100) : 0;
          return (
            <div key={m.id} className="card" style={{ padding: 16 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 }}>
                <StatusDot status={status}/>
                <span style={{ fontSize: 10.5, color: 'var(--ink-500)', fontWeight: 600, letterSpacing: 0.06, textTransform: 'uppercase' }}>{m.fn}</span>
              </div>
              <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink-800)', lineHeight: 1.25 }}>{m.label}</div>
              <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginTop: 12 }}>
                <div style={{ fontSize: 26, fontWeight: 700, letterSpacing: -0.4,
                  color: status === 'bad' ? 'var(--bad)' : status === 'warn' ? 'var(--warn)' : 'var(--ink-900)' }}>
                  {fmtNum(val, m)}
                </div>
                <Delta value={delta} good={m.dir === '>=' ? 'up' : 'down'}/>
              </div>
              <div style={{ fontSize: 11, color: 'var(--ink-500)', marginTop: 2 }}>
                {m.dir === '>=' ? 'Target' : 'Cap'}: {fmtNum(window.scorecardTarget(m), m)}/{window.metricCadence(m) === 'monthly' ? 'mo' : 'wk'}
              </div>
              <div style={{ marginTop: 10 }}>
                <Spark data={HISTORY[m.id] || []} width={230} height={28} target={window.scorecardTarget(m)} dir={m.dir}/>
              </div>
            </div>
          );
        })}
      </div>

      {myRocks.length > 0 && (
        <div className="card" style={{ padding: 20 }}>
          <CardHead title={`My ${window.NORWILL_DATA.currentQuarter().label} Rocks`} sub="What you own this quarter"/>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 12, marginTop: 14 }}>
            {myRocks.map(rock => (
              <div key={rock.id} style={{ padding: 14, borderRadius: 10, border: '1px solid var(--line)', background: 'var(--card-alt)' }}>
                <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 10 }}>{rock.title}</div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
                  <div style={{ flex: 1, height: 5, background: 'var(--ink-100)', borderRadius: 99, overflow: 'hidden' }}>
                    <div style={{ width: rock.pct + '%', height: '100%', background: 'var(--brand-500)' }}/>
                  </div>
                  <span className="mono tnum" style={{ fontSize: 11.5, fontWeight: 600 }}>{rock.pct}%</span>
                </div>
                <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>Due Jun 30 · {rock.linked.length} linked metrics</div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

// ─── User Access (admin + owner only) ──────────────────────────────────
const ROLE_OPTIONS = [
  { value: 'admin',  label: 'Admin · full access, no metrics'   },
  { value: 'owner',  label: 'Owner · CEO-level, all tabs'       },
  { value: 'member', label: 'Member · own scorecard + team'     },
];

function UserAccess({ authUser }) {
  const [users, setUsers] = React.useState(() => loadUserRegistry());
  const [openRow, setOpenRow] = React.useState(null); // username | '__NEW__' | null
  const [banner, setBanner] = React.useState(null);   // { kind: 'error'|'notice', text }

  const people = (window.NORWILL_DATA && window.NORWILL_DATA.PEOPLE) || [];
  const refresh = () => setUsers(loadUserRegistry());
  const notice  = (text) => { setBanner({ kind: 'notice', text }); setTimeout(() => setBanner(null), 3500); };
  const fail    = (text) => setBanner({ kind: 'error', text });

  return (
    <div style={{ padding: 24, display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 920 }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 16 }}>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 22, fontWeight: 700, color: 'var(--ink-900)', letterSpacing: -0.01 }}>User Access</div>
          <div style={{ fontSize: 13, color: 'var(--ink-500)', marginTop: 4 }}>Manage who can sign in to the scorecard and at what level.</div>
        </div>
        <button onClick={() => setOpenRow(openRow === '__NEW__' ? null : '__NEW__')} style={{
          display: 'flex', alignItems: 'center', gap: 6,
          padding: '9px 14px', borderRadius: 10,
          background: 'linear-gradient(135deg, var(--brand-700), var(--brand-500))',
          color: '#fff', fontSize: 13, fontWeight: 600, border: 'none',
          boxShadow: '0 3px 10px rgba(11,106,116,0.22)', cursor: 'pointer',
        }}>
          <Icon.plus/> Add user
        </button>
      </div>

      <div style={{
        padding: '10px 14px', fontSize: 12, lineHeight: 1.55,
        background: 'var(--ink-50)', color: 'var(--ink-600)',
        border: '1px solid var(--line)', borderRadius: 10,
      }}>
        Access changes save to <strong>this device only</strong> until Firebase Auth goes live. Passwords are SHA-256 hashed locally before storage.
      </div>

      {banner && (
        <div style={{
          padding: '9px 12px', borderRadius: 9, fontSize: 12.5, fontWeight: 500,
          background: banner.kind === 'error' ? 'var(--bad-bg)' : 'var(--good-bg, #E6F4EA)',
          color:      banner.kind === 'error' ? 'var(--bad)'    : 'var(--good, #1B9A4B)',
        }}>{banner.text}</div>
      )}

      {openRow === '__NEW__' && (
        <AddUserCard
          people={people}
          onCancel={() => setOpenRow(null)}
          onCreated={(u) => { refresh(); setOpenRow(null); notice(`Added ${u.name}.`); }}
          onError={fail}
        />
      )}

      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {users.map(u => (
          <UserRow
            key={u.username}
            user={u}
            people={people}
            isSelf={u.username === authUser.username}
            isOpen={openRow === u.username}
            onToggle={() => setOpenRow(openRow === u.username ? null : u.username)}
            onChanged={(updated) => { refresh(); setOpenRow(null); notice(`Updated ${updated.name}.`); }}
            onDeleted={(name) => { refresh(); setOpenRow(null); notice(`Removed ${name}.`); }}
            onError={fail}
          />
        ))}
      </div>
    </div>
  );
}

function AddUserCard({ people, onCancel, onCreated, onError }) {
  const [form, setForm] = React.useState({
    username: '', password: '', name: '', role: 'member', personaId: '', title: '',
  });
  const [busy, setBusy] = React.useState(false);

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const submit = async (e) => {
    e.preventDefault();
    setBusy(true);
    try {
      const created = await addUser({
        username: form.username,
        password: form.password,
        name: form.name || form.username,
        role: form.role,
        personaId: form.personaId || null,
        title: form.title,
      });
      onCreated(created);
    } catch (err) {
      onError(err.message || 'Could not add user.');
    } finally {
      setBusy(false);
    }
  };

  return (
    <form onSubmit={submit} className="card" style={{ padding: 18, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
      <div style={{ gridColumn: '1 / -1', fontSize: 13, fontWeight: 600, color: 'var(--ink-800)' }}>Add user</div>
      <AccessField label="Username" required><input value={form.username} onChange={e => set('username', e.target.value)} autoFocus placeholder="e.g. priya" style={accessInputStyle}/></AccessField>
      <AccessField label="Password" required><input type="password" value={form.password} onChange={e => set('password', e.target.value)} placeholder="at least 8 characters" style={accessInputStyle}/></AccessField>
      <AccessField label="Display name"><input value={form.name} onChange={e => set('name', e.target.value)} placeholder="Priya P." style={accessInputStyle}/></AccessField>
      <AccessField label="Title"><input value={form.title} onChange={e => set('title', e.target.value)} placeholder="Training Coordinator" style={accessInputStyle}/></AccessField>
      <AccessField label="Role">
        <select value={form.role} onChange={e => set('role', e.target.value)} style={accessInputStyle}>
          {ROLE_OPTIONS.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
        </select>
      </AccessField>
      <AccessField label="Persona">
        <select value={form.personaId} onChange={e => set('personaId', e.target.value)} style={accessInputStyle}>
          <option value="">— none (admin) —</option>
          {people.map(p => <option key={p.id} value={p.id}>{p.name} ({p.id})</option>)}
        </select>
      </AccessField>
      <div style={{ gridColumn: '1 / -1', display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
        <button type="button" onClick={onCancel} style={accessGhostStyle}>Cancel</button>
        <button type="submit" disabled={busy} style={accessPrimaryStyle}>{busy ? 'Adding…' : 'Add user'}</button>
      </div>
    </form>
  );
}

function UserRow({ user, people, isSelf, isOpen, onToggle, onChanged, onDeleted, onError }) {
  return (
    <div className="card" style={{ padding: isOpen ? 18 : 14 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <div style={{
          width: 40, height: 40, borderRadius: 10,
          background: 'var(--brand-100)', color: 'var(--brand-700)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontWeight: 700, fontSize: 14, letterSpacing: 0.02,
        }}>{(user.name || user.username).slice(0, 2).toUpperCase()}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink-900)' }}>
            {user.name}
            {isSelf && <span style={{ marginLeft: 8, fontSize: 10.5, padding: '1px 7px', borderRadius: 99, background: 'var(--brand-100)', color: 'var(--brand-700)', fontWeight: 600 }}>You</span>}
          </div>
          <div style={{ fontSize: 12, color: 'var(--ink-500)', marginTop: 2 }}>
            <span className="mono" style={{ color: 'var(--ink-700)' }}>{user.username}</span>
            {' · '}<span style={{ textTransform: 'capitalize' }}>{user.role}</span>
            {user.title && <> · {user.title}</>}
            {user.active === false && <> · <span style={{ color: 'var(--bad)' }}>inactive</span></>}
          </div>
        </div>
        <button onClick={onToggle} style={accessGhostStyle}>{isOpen ? 'Close' : 'Edit'}</button>
      </div>

      {isOpen && (
        <UserEditor
          user={user}
          people={people}
          isSelf={isSelf}
          onChanged={onChanged}
          onDeleted={onDeleted}
          onError={onError}
        />
      )}
    </div>
  );
}

function UserEditor({ user, people, isSelf, onChanged, onDeleted, onError }) {
  const [form, setForm] = React.useState({
    username: user.username,
    name:     user.name || '',
    role:     user.role,
    personaId: user.personaId || '',
    title:    user.title || '',
    active:   user.active !== false,
    password: '',
  });
  const [busy, setBusy] = React.useState(false);
  const [confirmDelete, setConfirmDelete] = React.useState(false);

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const save = async () => {
    setBusy(true);
    try {
      const patch = {
        name: form.name,
        personaId: form.personaId || null,
        title: form.title,
        active: form.active,
      };
      // Don't let admins change their own role (prevents self-lockout).
      if (!isSelf) patch.role = form.role;
      // Username edits disabled for self to avoid breaking the active session.
      if (!isSelf && form.username.trim().toLowerCase() !== user.username) patch.username = form.username;
      if (form.password) patch.password = form.password;
      const updated = await updateUser(user.username, patch);
      onChanged(updated);
    } catch (err) {
      onError(err.message || 'Could not save changes.');
    } finally {
      setBusy(false);
    }
  };

  const doDelete = () => {
    try {
      removeUser(user.username);
      onDeleted(user.name);
    } catch (err) {
      onError(err.message || 'Could not delete user.');
    }
  };

  return (
    <div style={{ marginTop: 14, paddingTop: 14, borderTop: '1px solid var(--line)', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
      <AccessField label="Username" disabled={isSelf} help={isSelf ? "You can't rename yourself — create a new account first." : undefined}>
        <input value={form.username} onChange={e => set('username', e.target.value)} disabled={isSelf} style={accessInputStyle}/>
      </AccessField>
      <AccessField label="Display name"><input value={form.name} onChange={e => set('name', e.target.value)} style={accessInputStyle}/></AccessField>
      <AccessField label="Title"><input value={form.title} onChange={e => set('title', e.target.value)} style={accessInputStyle}/></AccessField>
      <AccessField label="Role" disabled={isSelf} help={isSelf ? 'Role changes for yourself are disabled to prevent accidental lockout.' : undefined}>
        <select value={form.role} onChange={e => set('role', e.target.value)} disabled={isSelf} style={accessInputStyle}>
          {ROLE_OPTIONS.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
        </select>
      </AccessField>
      <AccessField label="Persona">
        <select value={form.personaId} onChange={e => set('personaId', e.target.value)} style={accessInputStyle}>
          <option value="">— none (admin) —</option>
          {people.map(p => <option key={p.id} value={p.id}>{p.name} ({p.id})</option>)}
        </select>
      </AccessField>
      <AccessField label="Active">
        <select value={form.active ? '1' : '0'} onChange={e => set('active', e.target.value === '1')} style={accessInputStyle}>
          <option value="1">Active — can sign in</option>
          <option value="0">Inactive — sign-in blocked</option>
        </select>
      </AccessField>
      <AccessField label="Change password" help="Leave blank to keep the current password.">
        <input type="password" value={form.password} onChange={e => set('password', e.target.value)} placeholder="New password (optional)" autoComplete="new-password" style={accessInputStyle}/>
      </AccessField>
      <div style={{ gridColumn: '1 / -1', display: 'flex', gap: 8, justifyContent: 'space-between', alignItems: 'center' }}>
        {isSelf ? (
          <span style={{ fontSize: 11, color: 'var(--ink-500)' }}>You can't delete your own account.</span>
        ) : confirmDelete ? (
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{ fontSize: 12, color: 'var(--bad)' }}>Delete {user.name}?</span>
            <button type="button" onClick={doDelete} style={{ ...accessGhostStyle, color: 'var(--bad)', borderColor: 'var(--bad)' }}>Yes, delete</button>
            <button type="button" onClick={() => setConfirmDelete(false)} style={accessGhostStyle}>Cancel</button>
          </div>
        ) : (
          <button type="button" onClick={() => setConfirmDelete(true)} style={{ ...accessGhostStyle, color: 'var(--bad)' }}>
            <Icon.trash/> Delete
          </button>
        )}
        <button type="button" onClick={save} disabled={busy} style={accessPrimaryStyle}>{busy ? 'Saving…' : 'Save changes'}</button>
      </div>
    </div>
  );
}

function AccessField({ label, required, disabled, help, children }) {
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 6, opacity: disabled ? 0.7 : 1 }}>
      <span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.08, color: 'var(--ink-600)' }}>
        {label}{required && <span style={{ color: 'var(--bad)' }}> *</span>}
      </span>
      {children}
      {help && <span style={{ fontSize: 10.5, color: 'var(--ink-500)', lineHeight: 1.4 }}>{help}</span>}
    </label>
  );
}

const accessInputStyle = {
  padding: '10px 12px',
  border: '1px solid var(--line-strong)',
  borderRadius: 9,
  fontSize: 13,
  background: 'var(--card-alt)',
  color: 'var(--ink-900)',
  width: '100%',
  boxSizing: 'border-box',
};

const accessPrimaryStyle = {
  padding: '9px 14px',
  border: 'none',
  borderRadius: 9,
  background: 'linear-gradient(135deg, var(--brand-700), var(--brand-500))',
  color: '#fff',
  fontSize: 13,
  fontWeight: 600,
  cursor: 'pointer',
};

const accessGhostStyle = {
  padding: '8px 12px',
  border: '1px solid var(--line-strong)',
  borderRadius: 9,
  background: 'var(--card)',
  color: 'var(--ink-700)',
  fontSize: 12.5,
  fontWeight: 500,
  cursor: 'pointer',
  display: 'inline-flex',
  alignItems: 'center',
  gap: 6,
};

Object.assign(window, { Rocks, MetricsMap, Notifications, MyScorecard, UserAccess });
