/* screens-rubric.jsx — Rubric builder · Gradebook · Grading interface · Learner grade view */
const { useState: rbState, useEffect: rbEffect, useRef: rbRef } = React;

/* ── Factories ───────────────────────────────────────────────── */
const mkLevel = (overrides = {}) => ({
  _id: crypto.randomUUID(), label: '', description: '', points: 0, ...overrides,
});
const mkCriterion = (pos = 0) => ({
  _id: crypto.randomUUID(), title: '', description: '', position: pos,
  levels: [
    mkLevel({ label: 'Excellent',  points: 4 }),
    mkLevel({ label: 'Good',       points: 3 }),
    mkLevel({ label: 'Developing', points: 2 }),
    mkLevel({ label: 'Beginning',  points: 1 }),
  ],
});

/* ── Math helpers ────────────────────────────────────────────── */
const cMax   = (c) => Math.max(0, ...c.levels.map(l => Number(l.points) || 0));
const rMax   = (criteria) => criteria.reduce((s, c) => s + cMax(c), 0);
const pColor = (p) => p >= 80 ? 'var(--lime-strong)' : p >= 60 ? '#D97706' : '#DC2626';
const pBg    = (p) => p >= 80 ? 'var(--lime-soft)'   : p >= 60 ? 'rgba(217,119,6,.1)' : 'rgba(220,38,38,.08)';
const pBdr   = (p) => p >= 80 ? 'var(--lime)'         : p >= 60 ? '#D97706' : '#DC2626';
const pLabel = (p) => p >= 80 ? 'Pass' : p >= 60 ? 'Borderline' : 'Below passing';

/* ═══════════════════════════════════════════════════════════════
   RUBRIC BUILDER — create / edit a rubric
═══════════════════════════════════════════════════════════════ */
function LevelCell({ level, onChange, onRemove, canRemove }) {
  return (
    <div style={{ width: 165, flex: 'none', border: '1.5px solid var(--border)', borderRadius: 12, padding: '12px 13px', display: 'flex', flexDirection: 'column', gap: 7, background: 'var(--bg)', position: 'relative' }}>
      {canRemove && (
        <button onClick={onRemove} style={{ position: 'absolute', top: 7, right: 7, background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', padding: 2, display: 'flex', borderRadius: 4, transition: 'color .12s' }}
          onMouseEnter={e => e.currentTarget.style.color = '#DC2626'}
          onMouseLeave={e => e.currentTarget.style.color = 'var(--text-muted)'}>
          <Icon name="close" size={13} />
        </button>
      )}
      <input value={level.label} onChange={e => onChange({ label: e.target.value })}
        placeholder="Level name"
        style={{ fontWeight: 800, fontSize: 13.5, background: 'none', border: 'none', borderBottom: '1.5px solid var(--border)', paddingBottom: 6, color: 'var(--text)', outline: 'none', width: '100%', paddingRight: 18 }} />
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 4 }}>
        <input type="number" min="0" step="0.5" value={level.points}
          onChange={e => onChange({ points: parseFloat(e.target.value) || 0 })}
          style={{ width: 44, fontWeight: 900, fontSize: 22, color: 'var(--lime-strong)', background: 'none', border: 'none', outline: 'none', padding: 0, lineHeight: 1 }} />
        <span style={{ fontSize: 12, color: 'var(--text-muted)', fontWeight: 700 }}>pts</span>
      </div>
      <textarea value={level.description} onChange={e => onChange({ description: e.target.value })}
        placeholder="Describe this performance level..."
        style={{ fontSize: 12, lineHeight: 1.5, background: 'none', border: 'none', outline: 'none', resize: 'none', color: 'var(--text-muted)', minHeight: 72, flex: 1 }} />
    </div>
  );
}

function CriterionCard({ criterion, index, onChange, onRemove, onAddLevel, onUpdateLevel, onRemoveLevel }) {
  const max = cMax(criterion);
  const sorted = [...criterion.levels].sort((a, b) => b.points - a.points);
  return (
    <div style={{ border: '1.5px solid var(--border)', borderRadius: 16, overflow: 'hidden' }}>
      {/* Header */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '13px 18px', background: 'var(--surface-1)', borderBottom: '1px solid var(--border)' }}>
        <span style={{ fontSize: 11, fontWeight: 800, color: 'var(--text-muted)', background: 'var(--bg)', padding: '3px 9px', borderRadius: 20, border: '1px solid var(--border)', flex: 'none' }}>
          #{index + 1}
        </span>
        <input value={criterion.title} onChange={e => onChange({ title: e.target.value })}
          placeholder="Criterion name (e.g. Content Quality, Methodology)"
          style={{ flex: 1, fontWeight: 700, fontSize: 15, background: 'none', border: 'none', outline: 'none', color: 'var(--text)' }} />
        <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--lime-strong)', flex: 'none' }}>{max} pts max</span>
        <button onClick={onRemove} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', padding: 4, display: 'flex', borderRadius: 6, transition: 'color .12s' }}
          onMouseEnter={e => e.currentTarget.style.color = '#DC2626'}
          onMouseLeave={e => e.currentTarget.style.color = 'var(--text-muted)'}>
          <Icon name="close" size={16} />
        </button>
      </div>
      {/* Criterion description */}
      <div style={{ padding: '8px 18px', borderBottom: '1px solid var(--border)', background: 'var(--surface-1)' }}>
        <input value={criterion.description} onChange={e => onChange({ description: e.target.value })}
          placeholder="Describe what this criterion measures (optional)"
          style={{ width: '100%', background: 'none', border: 'none', outline: 'none', fontSize: 13, color: 'var(--text-muted)' }} />
      </div>
      {/* Rating level cells */}
      <div style={{ padding: '16px 18px 18px', overflowX: 'auto' }}>
        <div style={{ display: 'flex', gap: 10, alignItems: 'stretch', minWidth: 'max-content' }}>
          {sorted.map(l => (
            <LevelCell key={l._id} level={l}
              onChange={patch => onUpdateLevel(l._id, patch)}
              onRemove={() => onRemoveLevel(l._id)}
              canRemove={criterion.levels.length > 1} />
          ))}
          <button onClick={onAddLevel} style={{
            minWidth: 110, border: '2px dashed var(--border)', borderRadius: 12,
            background: 'none', cursor: 'pointer', color: 'var(--text-muted)',
            fontSize: 12, fontWeight: 700, display: 'flex', flexDirection: 'column',
            alignItems: 'center', justifyContent: 'center', gap: 6, padding: '14px 10px', transition: 'all .15s',
          }}
          onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--lime)'; e.currentTarget.style.color = 'var(--lime-strong)'; }}
          onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--text-muted)'; }}>
            <Icon name="plus" size={17} />Add Level
          </button>
        </div>
      </div>
    </div>
  );
}

function RubricBuilder({ rubricId, onSaved, onCancel }) {
  const [title, setTitle]       = rbState('');
  const [desc, setDesc]         = rbState('');
  const [criteria, setCriteria] = rbState([mkCriterion(0)]);
  const [loading, setLoading]   = rbState(!!rubricId);
  const [saving, setSaving]     = rbState(false);
  const [error, setError]       = rbState(null);

  rbEffect(() => {
    if (!rubricId || !window.supabaseClient) return;
    (async () => {
      const db = window.supabaseClient;
      const [{ data: r }, { data: crits }] = await Promise.all([
        db.from('rubrics').select('*').eq('id', rubricId).single(),
        db.from('rubric_criteria').select('*, rubric_rating_levels(*)').eq('rubric_id', rubricId).order('position'),
      ]);
      if (!r) { setError('Rubric not found.'); setLoading(false); return; }
      setTitle(r.title); setDesc(r.description || '');
      setCriteria((crits || []).map(c => ({
        _id: c.id, id: c.id, title: c.title, description: c.description || '', position: c.position,
        levels: (c.rubric_rating_levels || []).sort((a, b) => b.points - a.points)
          .map(l => ({ _id: l.id, id: l.id, label: l.label, description: l.description || '', points: l.points })),
      })));
      setLoading(false);
    })();
  }, [rubricId]);

  const updateCriterion = (cid, patch) => setCriteria(p => p.map(c => c._id === cid ? { ...c, ...patch } : c));
  const removeCriterion = (cid) => setCriteria(p => p.filter(c => c._id !== cid));
  const addCriterion    = () => setCriteria(p => [...p, mkCriterion(p.length)]);
  const updateLevel = (cid, lid, patch) => setCriteria(p => p.map(c =>
    c._id !== cid ? c : { ...c, levels: c.levels.map(l => l._id === lid ? { ...l, ...patch } : l) }));
  const removeLevel = (cid, lid) => setCriteria(p => p.map(c =>
    c._id !== cid ? c : { ...c, levels: c.levels.filter(l => l._id !== lid) }));
  const addLevel = (cid) => setCriteria(p => p.map(c =>
    c._id !== cid ? c : { ...c, levels: [...c.levels, mkLevel({ label: 'New Level', points: 0 })] }));

  const save = async () => {
    if (!title.trim()) { setError('Please give the rubric a title.'); return; }
    if (!window.supabaseClient) { setError('No database connection.'); return; }
    setSaving(true); setError(null);
    const db = window.supabaseClient;
    try {
      let rId = rubricId;
      const payload = { title: title.trim(), description: desc.trim() || null };
      if (rId) {
        await db.from('rubrics').update(payload).eq('id', rId);
        await db.from('rubric_criteria').delete().eq('rubric_id', rId); // cascade deletes levels
      } else {
        const { data: nr, error: e } = await db.from('rubrics').insert({ ...payload, created_by: window.ME_UUID }).select().single();
        if (e || !nr) throw new Error(e?.message || 'Failed to create rubric');
        rId = nr.id;
      }
      for (let ci = 0; ci < criteria.length; ci++) {
        const c = criteria[ci];
        const { data: nc } = await db.from('rubric_criteria').insert({
          rubric_id: rId, title: c.title || `Criterion ${ci + 1}`,
          description: c.description || null, position: ci, max_points: cMax(c),
        }).select().single();
        if (!nc) continue;
        const lvlRows = c.levels.map((l, li) => ({
          criterion_id: nc.id, label: l.label || 'Level',
          description: l.description || null, points: Number(l.points) || 0, position: li,
        }));
        if (lvlRows.length) await db.from('rubric_rating_levels').insert(lvlRows);
      }
      setSaving(false); onSaved?.(rId);
    } catch (e) { setError(e.message || 'Save failed.'); setSaving(false); }
  };

  if (loading) return <div style={{ padding: 48, textAlign: 'center', color: 'var(--text-muted)' }}>Loading rubric...</div>;

  const totalMax = rMax(criteria);

  return (
    <div className="stack" style={{ gap: 22, maxWidth: 1000 }}>
      <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: 12 }}>
        <div>
          <h2 style={{ margin: 0, fontSize: 22 }}>{rubricId ? 'Edit Rubric' : 'New Rubric'}</h2>
          <p className="muted" style={{ margin: '4px 0 0', fontSize: 13 }}>
            Define criteria and rating levels. Attach this rubric to any lesson with a file upload.
          </p>
        </div>
        <div className="row gap-10">
          <button className="btn btn-ghost" onClick={onCancel}>Cancel</button>
          <button className="btn btn-primary" onClick={save} disabled={saving}>
            {saving ? 'Saving...' : 'Save Rubric'}
          </button>
        </div>
      </div>

      {error && <div style={{ padding: '10px 16px', background: 'rgba(220,38,38,.08)', border: '1px solid #DC2626', borderRadius: 10, color: '#DC2626', fontSize: 14 }}>{error}</div>}

      <div className="card card-pad stack" style={{ gap: 14, background: 'var(--surface-2)', border: 'none' }}>
        <div>
          <label style={{ fontSize: 11, fontWeight: 800, color: 'var(--text-muted)', display: 'block', marginBottom: 6, letterSpacing: '.08em', textTransform: 'uppercase' }}>Rubric Title</label>
          <input className="input" value={title} onChange={e => setTitle(e.target.value)} placeholder="e.g. Module 2 Reflection Rubric" style={{ fontSize: 17, fontWeight: 700 }} />
        </div>
        <div>
          <label style={{ fontSize: 11, fontWeight: 800, color: 'var(--text-muted)', display: 'block', marginBottom: 6, letterSpacing: '.08em', textTransform: 'uppercase' }}>Description (optional)</label>
          <textarea className="input" value={desc} onChange={e => setDesc(e.target.value)} placeholder="What assignments will use this rubric?" rows={2} style={{ resize: 'vertical' }} />
        </div>
      </div>

      {criteria.map((c, ci) => (
        <CriterionCard key={c._id} criterion={c} index={ci}
          onChange={p => updateCriterion(c._id, p)}
          onRemove={() => removeCriterion(c._id)}
          onAddLevel={() => addLevel(c._id)}
          onUpdateLevel={(lid, p) => updateLevel(c._id, lid, p)}
          onRemoveLevel={lid => removeLevel(c._id, lid)} />
      ))}

      <div className="row" style={{ justifyContent: 'space-between', alignItems: 'center', padding: '4px 0' }}>
        <button className="btn btn-soft" onClick={addCriterion}><Icon name="plus" size={16} /> Add Criterion</button>
        <span style={{ fontWeight: 800, fontSize: 15, color: 'var(--lime-strong)' }}>Total: {totalMax} points max</span>
      </div>
    </div>
  );
}

/* ═══════════════════════════════════════════════════════════════
   ATTACH RUBRIC MODAL
═══════════════════════════════════════════════════════════════ */
function AttachRubricModal({ rubricId, rubricTitle, onClose }) {
  const [lessons, setLessons]   = rbState([]);
  const [attached, setAttached] = rbState({});
  const [loading, setLoading]   = rbState(true);
  const [saving, setSaving]     = rbState(false);

  rbEffect(() => {
    if (!window.supabaseClient) return;
    (async () => {
      const db = window.supabaseClient;
      const [{ data: ls }, { data: lr }] = await Promise.all([
        db.from('lessons').select('id, title').order('created_at'),
        db.from('lesson_rubrics').select('lesson_id, rubric_id'),
      ]);
      setLessons(ls || []);
      const map = {};
      (lr || []).forEach(x => { map[x.lesson_id] = x.rubric_id; });
      setAttached(map); setLoading(false);
    })();
  }, []);

  const toggle = async (lid) => {
    setSaving(true);
    const db = window.supabaseClient;
    if (attached[lid] === rubricId) {
      await db.from('lesson_rubrics').delete().eq('lesson_id', lid);
      setAttached(a => { const n = { ...a }; delete n[lid]; return n; });
    } else {
      await db.from('lesson_rubrics').upsert({ lesson_id: lid, rubric_id: rubricId }, { onConflict: 'lesson_id' });
      setAttached(a => ({ ...a, [lid]: rubricId }));
    }
    setSaving(false);
  };

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.55)', backdropFilter: 'blur(8px)', zIndex: 9000, display: 'grid', placeItems: 'center', padding: 20 }}>
      <div style={{ background: 'var(--bg)', borderRadius: 20, padding: '28px 32px', maxWidth: 520, width: '100%', maxHeight: '80vh', overflow: 'auto', boxShadow: '0 32px 80px rgba(0,0,0,.4)' }}>
        <div className="row" style={{ justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
          <h3 style={{ margin: 0 }}>Attach to Lesson</h3>
          <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', display: 'flex' }}><Icon name="close" size={20} /></button>
        </div>
        <p className="muted" style={{ margin: '0 0 20px', fontSize: 13 }}>
          Select which lessons should use <strong>{rubricTitle}</strong> for grading.
          Learners must have a file upload block to submit work.
        </p>
        {loading ? <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-muted)' }}>Loading lessons...</div> : (
          <div className="stack" style={{ gap: 8 }}>
            {lessons.length === 0 && <p className="muted" style={{ fontSize: 14, textAlign: 'center', padding: 20 }}>No lessons found.</p>}
            {lessons.map(l => {
              const isAttached = attached[l.id] === rubricId;
              const hasOther   = attached[l.id] && !isAttached;
              return (
                <label key={l.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px', borderRadius: 12, border: '1.5px solid ' + (isAttached ? 'var(--lime)' : 'var(--border)'), background: isAttached ? 'var(--lime-soft)' : 'var(--surface-1)', cursor: saving ? 'wait' : 'pointer', transition: 'all .15s' }}>
                  <input type="checkbox" checked={isAttached} onChange={() => !saving && toggle(l.id)} style={{ accentColor: 'var(--lime)', width: 18, height: 18, flex: 'none', cursor: 'pointer' }} />
                  <div style={{ flex: 1 }}>
                    <span style={{ fontWeight: 700, fontSize: 14 }}>{l.title}</span>
                    {hasOther && <span style={{ fontSize: 11, color: '#D97706', display: 'block', marginTop: 2 }}>⚠ Another rubric attached — checking will replace it</span>}
                  </div>
                  {isAttached && <Icon name="check" size={16} style={{ color: 'var(--lime-strong)', flex: 'none' }} />}
                </label>
              );
            })}
          </div>
        )}
        <div style={{ marginTop: 24, display: 'flex', justifyContent: 'flex-end' }}>
          <button className="btn btn-primary" onClick={onClose}>Done</button>
        </div>
      </div>
    </div>
  );
}

/* ═══════════════════════════════════════════════════════════════
   RUBRICS ADMIN — list + manage
═══════════════════════════════════════════════════════════════ */
function RubricsAdmin() {
  const [rubrics, setRubrics]   = rbState([]);
  const [loading, setLoading]   = rbState(true);
  const [view, setView]         = rbState('list'); // 'list' | 'new' | 'edit'
  const [editId, setEditId]     = rbState(null);
  const [attachModal, setAttachModal] = rbState(null);

  const load = async () => {
    if (!window.supabaseClient) return;
    setLoading(true);
    const { data } = await window.supabaseClient
      .from('rubrics').select('*, rubric_criteria(id, max_points)').order('created_at', { ascending: false });
    setRubrics(data || []); setLoading(false);
  };

  rbEffect(() => { load(); }, []);

  const del = async (id) => {
    if (!confirm('Delete this rubric? All attached grades will lose rubric data.')) return;
    await window.supabaseClient.from('rubrics').delete().eq('id', id);
    setRubrics(r => r.filter(x => x.id !== id));
  };

  if (view !== 'list') return (
    <div style={{ marginTop: 24 }}>
      <RubricBuilder rubricId={view === 'edit' ? editId : null}
        onSaved={() => { load(); setView('list'); }}
        onCancel={() => setView('list')} />
    </div>
  );

  return (
    <div className="stack" style={{ gap: 24, marginTop: 24 }}>
      <div className="row" style={{ justifyContent: 'space-between', alignItems: 'center' }}>
        <div>
          <h2 style={{ margin: 0, fontSize: 22 }}>Rubrics</h2>
          <p className="muted" style={{ margin: '4px 0 0', fontSize: 13 }}>Create reusable rubrics and attach them to assignment lessons for structured grading.</p>
        </div>
        <button className="btn btn-primary" onClick={() => { setEditId(null); setView('new'); }}>
          <Icon name="plus" size={16} /> New Rubric
        </button>
      </div>

      {loading ? <div style={{ padding: 48, textAlign: 'center', color: 'var(--text-muted)' }}>Loading...</div>
      : rubrics.length === 0 ? (
        <div style={{ padding: 64, textAlign: 'center', border: '2px dashed var(--border)', borderRadius: 16 }}>
          <div style={{ fontSize: 44, marginBottom: 12 }}>📋</div>
          <h3 style={{ margin: '0 0 8px', fontSize: 18 }}>No rubrics yet</h3>
          <p className="muted" style={{ margin: '0 0 20px', fontSize: 14 }}>Build your first rubric to enable structured grading for assignments.</p>
          <button className="btn btn-primary" onClick={() => setView('new')}>Create your first rubric</button>
        </div>
      ) : (
        <div className="stack" style={{ gap: 12 }}>
          {rubrics.map(r => {
            const cCount  = r.rubric_criteria?.length || 0;
            const maxPts  = (r.rubric_criteria || []).reduce((s, c) => s + (Number(c.max_points) || 0), 0);
            return (
              <div key={r.id} style={{ border: '1px solid var(--border)', borderRadius: 14, overflow: 'hidden', background: 'var(--bg)' }}>
                <div style={{ padding: '18px 22px', display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
                  <div style={{ flex: 1, minWidth: 200 }}>
                    <h3 style={{ margin: 0, fontSize: 16, fontWeight: 800 }}>{r.title}</h3>
                    {r.description && <p className="muted" style={{ margin: '4px 0 0', fontSize: 13 }}>{r.description}</p>}
                    <div className="row gap-14" style={{ marginTop: 8 }}>
                      <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-muted)' }}>
                        <Icon name="layers" size={13} /> {cCount} criteri{cCount === 1 ? 'on' : 'a'}
                      </span>
                      <span style={{ fontSize: 12, fontWeight: 800, color: 'var(--lime-strong)' }}>{maxPts} pts max</span>
                      <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>Created {new Date(r.created_at).toLocaleDateString()}</span>
                    </div>
                  </div>
                  <div className="row gap-10">
                    <button className="btn btn-soft btn-sm" onClick={() => setAttachModal({ rubricId: r.id, rubricTitle: r.title })}>
                      <Icon name="layers" size={14} /> Attach to Lesson
                    </button>
                    <button className="btn btn-soft btn-sm" onClick={() => { setEditId(r.id); setView('edit'); }}>
                      <Icon name="pen" size={14} /> Edit
                    </button>
                    <button className="btn btn-ghost btn-sm" onClick={() => del(r.id)} style={{ color: '#DC2626' }}>
                      <Icon name="close" size={14} />
                    </button>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      )}
      {attachModal && (
        <AttachRubricModal rubricId={attachModal.rubricId} rubricTitle={attachModal.rubricTitle}
          onClose={() => { setAttachModal(null); load(); }} />
      )}
    </div>
  );
}

/* ═══════════════════════════════════════════════════════════════
   GRADING MODAL — full-screen overlay for grading a submission
═══════════════════════════════════════════════════════════════ */
function GradingModal({ userId, lessonId, rubricId, learnerName, lessonTitle, onClose }) {
  const [rubric, setRubric]         = rbState(null);
  const [submission, setSubmission] = rbState(null);
  const [existing, setExisting]     = rbState(null);
  const [selLevels, setSelLevels]   = rbState({}); // criterionId → levelId
  const [earnedPts, setEarnedPts]   = rbState({}); // criterionId → points
  const [comments, setComments]     = rbState({}); // criterionId → string
  const [feedback, setFeedback]     = rbState('');
  const [loading, setLoading]       = rbState(true);
  const [saving, setSaving]         = rbState(false);

  rbEffect(() => {
    if (!window.supabaseClient) return;
    (async () => {
      const db = window.supabaseClient;
      const [{ data: r }, { data: crits }, { data: sub }, { data: g }] = await Promise.all([
        db.from('rubrics').select('*').eq('id', rubricId).single(),
        db.from('rubric_criteria').select('*, rubric_rating_levels(*)').eq('rubric_id', rubricId).order('position'),
        db.from('submissions').select('*').eq('user_id', userId).eq('lesson_id', lessonId).maybeSingle(),
        db.from('grades').select('*').eq('user_id', userId).eq('lesson_id', lessonId).maybeSingle(),
      ]);
      setRubric({ ...r, criteria: (crits || []).map(c => ({ ...c, levels: (c.rubric_rating_levels || []).sort((a, b) => b.points - a.points) })) });
      setSubmission(sub);
      setExisting(g);
      if (g) {
        setFeedback(g.feedback || '');
        const { data: scores } = await db.from('grade_criterion_scores').select('*').eq('grade_id', g.id);
        const sl = {}, ep = {}, cm = {};
        (scores || []).forEach(s => {
          if (s.rating_level_id) sl[s.criterion_id] = s.rating_level_id;
          ep[s.criterion_id] = s.points_earned;
          if (s.comment) cm[s.criterion_id] = s.comment;
        });
        setSelLevels(sl); setEarnedPts(ep); setComments(cm);
      }
      setLoading(false);
    })();
  }, []);

  const pickLevel = (cid, lid, pts) => {
    setSelLevels(s => ({ ...s, [cid]: lid }));
    setEarnedPts(s => ({ ...s, [cid]: pts }));
  };

  const totalEarned = (rubric?.criteria || []).reduce((s, c) => s + (Number(earnedPts[c.id]) || 0), 0);
  const totalMax    = rubric ? rMax(rubric.criteria) : 0;
  const percentage  = totalMax > 0 ? Math.round((totalEarned / totalMax) * 100) : 0;

  const saveGrade = async (publish) => {
    if (!window.supabaseClient || !window.ME_UUID) return;
    setSaving(true);
    const db = window.supabaseClient;
    try {
      const gPayload = {
        user_id: userId, lesson_id: lessonId, rubric_id: rubricId,
        total_score: totalEarned, max_score: totalMax, percentage,
        feedback: feedback.trim() || null,
        status: publish ? 'published' : 'draft',
        graded_by: window.ME_UUID, graded_at: new Date().toISOString(),
      };
      let gid = existing?.id;
      if (gid) {
        await db.from('grades').update(gPayload).eq('id', gid);
        await db.from('grade_criterion_scores').delete().eq('grade_id', gid);
      } else {
        const { data: ng, error: e } = await db.from('grades').insert(gPayload).select().single();
        if (e || !ng) throw new Error(e?.message || 'Failed to save grade');
        gid = ng.id;
      }
      const scoreRows = (rubric?.criteria || []).map(c => ({
        grade_id: gid, criterion_id: c.id,
        rating_level_id: selLevels[c.id] || null,
        points_earned: Number(earnedPts[c.id]) || 0,
        comment: comments[c.id] || null,
      }));
      if (scoreRows.length) await db.from('grade_criterion_scores').insert(scoreRows);
      onClose();
    } catch (e) { alert('Save failed: ' + e.message); setSaving(false); }
  };

  const submissionUrl = submission?.storage_path
    ? window.supabaseClient?.storage.from('media').getPublicUrl(submission.storage_path).data?.publicUrl
    : null;

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.65)', backdropFilter: 'blur(10px)', zIndex: 9000, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
      {/* Header bar */}
      <div style={{ background: 'var(--bg)', borderBottom: '1px solid var(--border)', padding: '14px 28px', display: 'flex', alignItems: 'center', gap: 16, flexShrink: 0 }}>
        <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', display: 'flex', padding: 6, borderRadius: 8, transition: 'background .12s' }}
          onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-1)'}
          onMouseLeave={e => e.currentTarget.style.background = 'none'}>
          <Icon name="close" size={20} />
        </button>
        <div style={{ flex: 1, minWidth: 0 }}>
          <h2 style={{ margin: 0, fontSize: 18, fontWeight: 800 }}>Grading: {lessonTitle}</h2>
          <p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)' }}>Learner: <strong>{learnerName}</strong></p>
        </div>
        {/* Running score */}
        {!loading && (
          <div style={{ textAlign: 'right', marginRight: 8 }}>
            <div style={{ fontSize: 30, fontWeight: 900, color: pColor(percentage), lineHeight: 1 }}>
              {totalEarned}<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-muted)' }}> / {totalMax} pts</span>
            </div>
            <div style={{ fontSize: 12, fontWeight: 700, color: pColor(percentage) }}>{percentage}% — {pLabel(percentage)}</div>
          </div>
        )}
        <div className="row gap-10">
          <button className="btn btn-ghost" onClick={() => saveGrade(false)} disabled={saving || loading}>Save Draft</button>
          <button className="btn btn-primary" onClick={() => saveGrade(true)} disabled={saving || loading}>
            {saving ? 'Saving...' : existing?.status === 'published' ? 'Update Grade' : 'Publish Grade'}
          </button>
        </div>
      </div>

      {loading ? (
        <div style={{ flex: 1, display: 'grid', placeItems: 'center', color: 'var(--text-muted)' }}>Loading...</div>
      ) : (
        <div style={{ flex: 1, overflow: 'hidden', display: 'grid', gridTemplateColumns: '320px 1fr' }}>
          {/* Left: submission + overall feedback */}
          <div style={{ borderRight: '1px solid var(--border)', padding: '24px 24px', display: 'flex', flexDirection: 'column', gap: 20, background: 'var(--surface-1)', overflowY: 'auto' }}>
            <div>
              <p style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 10 }}>Submission</p>
              {submission ? (
                <div style={{ padding: '16px 18px', background: 'var(--bg)', borderRadius: 12, border: '1px solid var(--border)' }}>
                  <div className="row gap-10" style={{ alignItems: 'center', marginBottom: 12 }}>
                    <Icon name="upload" size={22} style={{ color: 'var(--lime-strong)', flex: 'none' }} />
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontWeight: 700, fontSize: 14, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{submission.file_name}</div>
                      <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>
                        {new Date(submission.submitted_at).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
                        {submission.size_bytes && ` · ${(submission.size_bytes / 1048576).toFixed(1)} MB`}
                      </div>
                    </div>
                  </div>
                  {submissionUrl && (
                    <a href={submissionUrl} target="_blank" rel="noreferrer"
                      className="btn btn-soft btn-sm" style={{ width: '100%', justifyContent: 'center' }}>
                      <Icon name="eye" size={14} /> View File
                    </a>
                  )}
                </div>
              ) : (
                <div style={{ padding: '24px', background: 'var(--bg)', borderRadius: 12, border: '1.5px dashed var(--border)', textAlign: 'center' }}>
                  <p className="muted" style={{ fontSize: 13, margin: 0 }}>No file submitted yet.</p>
                </div>
              )}
            </div>

            <div>
              <label style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-muted)', display: 'block', marginBottom: 8 }}>
                Overall Feedback
              </label>
              <textarea className="input" value={feedback} onChange={e => setFeedback(e.target.value)}
                placeholder="Write overall feedback for the learner. This appears with their grade." rows={7} style={{ resize: 'vertical', fontSize: 14, lineHeight: 1.65 }} />
            </div>

            {existing && (
              <div style={{ padding: '10px 14px', borderRadius: 10, background: existing.status === 'published' ? 'var(--lime-soft)' : 'var(--surface-2)', border: '1px solid ' + (existing.status === 'published' ? 'var(--lime)' : 'var(--border)') }}>
                <span style={{ fontSize: 13, fontWeight: 700, color: existing.status === 'published' ? 'var(--lime-strong)' : 'var(--text-muted)' }}>
                  {existing.status === 'published' ? '✓ Grade published — learner can see this' : '⏳ Saved as draft — not yet visible to learner'}
                </span>
              </div>
            )}
          </div>

          {/* Right: rubric criteria */}
          <div style={{ overflowY: 'auto', padding: '24px 32px' }}>
            <p style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 22 }}>
              {rubric?.title}
            </p>
            <div className="stack" style={{ gap: 22 }}>
              {(rubric?.criteria || []).map(c => {
                const sel = selLevels[c.id];
                const earned = earnedPts[c.id];
                const sorted = [...c.levels].sort((a, b) => b.points - a.points);
                return (
                  <div key={c.id} style={{ border: '1.5px solid var(--border)', borderRadius: 14, overflow: 'hidden' }}>
                    {/* Criterion header */}
                    <div style={{ padding: '12px 18px', background: 'var(--surface-2)', borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                      <div>
                        <span style={{ fontWeight: 800, fontSize: 15 }}>{c.title}</span>
                        {c.description && <span style={{ fontSize: 12, color: 'var(--text-muted)', marginLeft: 10 }}>{c.description}</span>}
                      </div>
                      <span style={{ fontSize: 14, fontWeight: 800, color: sel ? pColor(Math.round((earned / cMax(c)) * 100)) : 'var(--text-muted)' }}>
                        {sel ? `${earned} / ${cMax(c)} pts` : `— / ${cMax(c)} pts`}
                      </span>
                    </div>
                    {/* Rating level buttons */}
                    <div style={{ padding: '16px 18px', display: 'flex', gap: 10, overflowX: 'auto' }}>
                      {sorted.map(l => {
                        const active = sel === l.id;
                        return (
                          <button key={l.id} onClick={() => pickLevel(c.id, l.id, l.points)}
                            style={{ minWidth: 155, flex: 'none', padding: '12px 14px', borderRadius: 12, cursor: 'pointer', textAlign: 'left', transition: 'all .15s',
                              border: '2px solid ' + (active ? 'var(--lime)' : 'var(--border)'),
                              background: active ? 'var(--lime-soft)' : 'var(--surface-1)',
                            }}>
                            <div style={{ fontWeight: 800, fontSize: 13.5, color: active ? 'var(--lime-strong)' : 'var(--text)', marginBottom: 5 }}>{l.label}</div>
                            <div style={{ fontSize: 22, fontWeight: 900, color: active ? 'var(--lime-strong)' : 'var(--text)', marginBottom: 8, lineHeight: 1 }}>
                              {l.points} <span style={{ fontSize: 12, fontWeight: 700 }}>pts</span>
                            </div>
                            {l.description && <div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.5 }}>{l.description}</div>}
                          </button>
                        );
                      })}
                    </div>
                    {/* Per-criterion comment */}
                    <div style={{ padding: '0 18px 14px' }}>
                      <input value={comments[c.id] || ''} onChange={e => setComments(s => ({ ...s, [c.id]: e.target.value }))}
                        placeholder="Add a specific comment for this criterion (optional)..."
                        style={{ width: '100%', padding: '8px 12px', borderRadius: 8, border: '1px solid var(--border)', background: 'var(--bg)', fontSize: 13, color: 'var(--text)', outline: 'none', boxSizing: 'border-box' }} />
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

/* ═══════════════════════════════════════════════════════════════
   GRADEBOOK ADMIN — grid: learners × assignments
═══════════════════════════════════════════════════════════════ */
function GradebookCell({ sub, grade, passingScore, onClick }) {
  if (!sub) return <span style={{ fontSize: 13, color: 'var(--border-strong)' }}>—</span>;
  if (!grade || grade.status !== 'published') {
    return (
      <button onClick={onClick} style={{ background: 'rgba(217,119,6,.1)', color: '#D97706', border: '1.5px solid #D97706', borderRadius: 8, padding: '5px 13px', fontWeight: 700, fontSize: 12, cursor: 'pointer', transition: 'all .15s' }}
        onMouseEnter={e => e.currentTarget.style.background = 'rgba(217,119,6,.2)'}
        onMouseLeave={e => e.currentTarget.style.background = 'rgba(217,119,6,.1)'}>
        Grade
      </button>
    );
  }
  const pct = Math.round(grade.percentage || 0);
  return (
    <button onClick={onClick} style={{ background: pBg(pct), color: pColor(pct), border: '1.5px solid ' + pBdr(pct), borderRadius: 8, padding: '5px 13px', fontWeight: 800, fontSize: 13, cursor: 'pointer', transition: 'all .15s' }}>
      {pct}%
    </button>
  );
}

function GradebookAdmin() {
  const [assignments, setAssignments] = rbState([]);
  const [learners, setLearners]       = rbState([]);
  const [submissions, setSubmissions] = rbState([]);
  const [grades, setGrades]           = rbState([]);
  const [loading, setLoading]         = rbState(true);
  const [target, setTarget]           = rbState(null);

  const load = async () => {
    if (!window.supabaseClient) return;
    setLoading(true);
    const db = window.supabaseClient;
    const [{ data: lr }, { data: profs }, { data: subs }, { data: gds }] = await Promise.all([
      db.from('lesson_rubrics').select('lesson_id, rubric_id, passing_score, rubrics(id,title), lessons(id,title)'),
      db.from('profiles').select('id, name, avatar').eq('role', 'learner').order('name'),
      db.from('submissions').select('*'),
      db.from('grades').select('*'),
    ]);
    setAssignments(lr || []); setLearners(profs || []);
    setSubmissions(subs || []); setGrades(gds || []);
    setLoading(false);
  };

  rbEffect(() => { load(); }, []);

  const getSub   = (uid, lid) => submissions.find(s => s.user_id === uid && s.lesson_id === lid);
  const getGrade = (uid, lid) => grades.find(g => g.user_id === uid && g.lesson_id === lid);

  if (loading) return <div style={{ padding: 48, textAlign: 'center', color: 'var(--text-muted)', marginTop: 24 }}>Loading gradebook...</div>;

  if (assignments.length === 0) return (
    <div style={{ padding: 64, textAlign: 'center', border: '2px dashed var(--border)', borderRadius: 16, marginTop: 24 }}>
      <div style={{ fontSize: 44, marginBottom: 12 }}>📊</div>
      <h3 style={{ margin: '0 0 8px' }}>No graded assignments yet</h3>
      <p className="muted" style={{ margin: 0, fontSize: 14 }}>Create a rubric in the Rubrics tab and attach it to a lesson to enable grading.</p>
    </div>
  );

  return (
    <div className="stack" style={{ gap: 24, marginTop: 24 }}>
      <div>
        <h2 style={{ margin: 0, fontSize: 22 }}>Gradebook</h2>
        <p className="muted" style={{ margin: '4px 0 0', fontSize: 13 }}>
          {learners.length} learner{learners.length !== 1 ? 's' : ''} · {assignments.length} graded assignment{assignments.length !== 1 ? 's' : ''}. Click any cell to grade or review.
        </p>
      </div>

      <div style={{ overflowX: 'auto', borderRadius: 14, border: '1px solid var(--border)' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14, whiteSpace: 'nowrap' }}>
          <thead>
            <tr style={{ background: 'var(--surface-2)', borderBottom: '1px solid var(--border)' }}>
              <th style={{ padding: '13px 20px', textAlign: 'left', fontWeight: 700, fontSize: 12, color: 'var(--text-muted)', position: 'sticky', left: 0, background: 'var(--surface-2)', textTransform: 'uppercase', letterSpacing: '.06em', zIndex: 2 }}>Learner</th>
              {assignments.map(a => (
                <th key={a.lesson_id} style={{ padding: '13px 20px', textAlign: 'center', fontWeight: 700, fontSize: 12, color: 'var(--text-muted)', minWidth: 170, textTransform: 'uppercase', letterSpacing: '.06em' }}>
                  <div style={{ color: 'var(--text)' }}>{a.lessons?.title || 'Assignment'}</div>
                  <div style={{ fontSize: 11, fontWeight: 500, marginTop: 2, textTransform: 'none', letterSpacing: 0 }}>{a.rubrics?.title || ''}</div>
                </th>
              ))}
              <th style={{ padding: '13px 20px', textAlign: 'center', fontWeight: 700, fontSize: 12, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '.06em' }}>Avg</th>
            </tr>
          </thead>
          <tbody>
            {learners.map(l => {
              const pubGrades = grades.filter(g => g.user_id === l.id && g.status === 'published');
              const avgPct    = pubGrades.length ? Math.round(pubGrades.reduce((s, g) => s + (g.percentage || 0), 0) / pubGrades.length) : null;
              return (
                <tr key={l.id} style={{ borderBottom: '1px solid var(--border)' }}
                  onMouseOver={e => e.currentTarget.style.background = 'var(--surface-1)'}
                  onMouseOut={e => e.currentTarget.style.background = 'transparent'}>
                  <td style={{ padding: '13px 20px', position: 'sticky', left: 0, background: 'inherit', zIndex: 1 }}>
                    <div className="row gap-10" style={{ alignItems: 'center' }}>
                      <div className="avatar" style={{ width: 34, height: 34, fontSize: 13, background: 'var(--surface-2)', border: '1px solid var(--border)', color: 'var(--text)', flex: 'none' }}>
                        {l.avatar || l.name?.[0] || '?'}
                      </div>
                      <span style={{ fontWeight: 700, fontSize: 14 }}>{l.name}</span>
                    </div>
                  </td>
                  {assignments.map(a => (
                    <td key={a.lesson_id} style={{ padding: '10px 20px', textAlign: 'center' }}>
                      <GradebookCell sub={getSub(l.id, a.lesson_id)} grade={getGrade(l.id, a.lesson_id)} passingScore={a.passing_score || 70}
                        onClick={() => setTarget({ userId: l.id, lessonId: a.lesson_id, rubricId: a.rubric_id, learnerName: l.name, lessonTitle: a.lessons?.title })} />
                    </td>
                  ))}
                  <td style={{ padding: '10px 20px', textAlign: 'center' }}>
                    {avgPct !== null
                      ? <span style={{ fontWeight: 800, fontSize: 14, color: pColor(avgPct) }}>{avgPct}%</span>
                      : <span style={{ color: 'var(--text-muted)', fontSize: 13 }}>—</span>}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {target && (
        <GradingModal {...target} onClose={() => { setTarget(null); load(); }} />
      )}
    </div>
  );
}

/* ═══════════════════════════════════════════════════════════════
   LEARNER GRADE CARD — shown inside the lesson page
═══════════════════════════════════════════════════════════════ */
function MyGradeCard({ lessonId }) {
  const [grade, setGrade]     = rbState(null);
  const [scores, setScores]   = rbState([]);
  const [rubric, setRubric]   = rbState(null);
  const [loading, setLoading] = rbState(true);
  const [open, setOpen]       = rbState(false);

  rbEffect(() => {
    if (!window.supabaseClient || !window.ME_UUID) { setLoading(false); return; }
    (async () => {
      const db = window.supabaseClient;
      const { data: g } = await db.from('grades').select('*')
        .eq('user_id', window.ME_UUID).eq('lesson_id', lessonId).eq('status', 'published').maybeSingle();
      if (!g) { setLoading(false); return; }
      setGrade(g);
      if (g.rubric_id) {
        const [{ data: crits }, { data: sc }] = await Promise.all([
          db.from('rubric_criteria').select('*, rubric_rating_levels(*)').eq('rubric_id', g.rubric_id).order('position'),
          db.from('grade_criterion_scores').select('*').eq('grade_id', g.id),
        ]);
        setRubric({ criteria: (crits || []).map(c => ({ ...c, levels: c.rubric_rating_levels || [] })) });
        setScores(sc || []);
      }
      setLoading(false);
    })();
  }, [lessonId]);

  if (loading || !grade) return null;

  const pct = Math.round(grade.percentage || 0);

  return (
    <section style={{ marginTop: 22, border: '1.5px solid ' + pBdr(pct), borderRadius: 16, overflow: 'hidden', background: 'var(--bg)' }}>
      {/* Summary toggle */}
      <button onClick={() => setOpen(o => !o)} style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 16, padding: '18px 22px', background: pBg(pct), border: 'none', cursor: 'pointer', borderBottom: open ? '1px solid var(--border)' : 'none' }}>
        <div style={{ width: 54, height: 54, borderRadius: '50%', background: pColor(pct), display: 'grid', placeItems: 'center', color: '#fff', fontWeight: 900, fontSize: 17, flex: 'none' }}>
          {pct}%
        </div>
        <div style={{ flex: 1, textAlign: 'left' }}>
          <div style={{ fontWeight: 800, fontSize: 16, color: pColor(pct) }}>
            {pct >= 80 ? 'Excellent work!' : pct >= 60 ? 'Good effort' : 'Keep working at it'}
          </div>
          <div style={{ fontSize: 13, color: 'var(--text-muted)', marginTop: 2 }}>
            {grade.total_score} / {grade.max_score} pts · {pLabel(pct)} · Grade released
          </div>
        </div>
        <div className="row gap-6" style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-muted)', flex: 'none' }}>
          {open ? 'Hide' : 'View'} feedback
          <Icon name="chevdown" size={16} style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .2s' }} />
        </div>
      </button>

      {open && (
        <div style={{ padding: '20px 24px' }}>
          {/* Overall feedback */}
          {grade.feedback && (
            <div style={{ marginBottom: 20, padding: '14px 18px', background: 'var(--surface-2)', borderRadius: 12, border: '1px solid var(--border)' }}>
              <p style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8 }}>Instructor Feedback</p>
              <p style={{ fontSize: 15, lineHeight: 1.65, margin: 0 }}>{grade.feedback}</p>
            </div>
          )}

          {/* Per-criterion breakdown */}
          {(rubric?.criteria || []).map(c => {
            const sc     = scores.find(s => s.criterion_id === c.id);
            if (!sc) return null;
            const lvl    = c.levels.find(l => l.id === sc.rating_level_id);
            const cmax   = cMax(c);
            const cpct   = cmax > 0 ? Math.round((sc.points_earned / cmax) * 100) : 0;
            return (
              <div key={c.id} style={{ marginBottom: 12, padding: '14px 18px', borderRadius: 12, border: '1.5px solid var(--border)', background: 'var(--bg)' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 6 }}>
                  <div>
                    <div style={{ fontWeight: 800, fontSize: 14 }}>{c.title}</div>
                    {lvl && <div style={{ fontSize: 13, fontWeight: 700, color: pColor(cpct), marginTop: 2 }}>
                      {lvl.label} · {sc.points_earned} / {cmax} pts
                    </div>}
                  </div>
                  <span style={{ fontWeight: 800, fontSize: 13, color: pColor(cpct), flex: 'none' }}>{cpct}%</span>
                </div>
                {lvl?.description && <p style={{ fontSize: 13, color: 'var(--text-muted)', margin: '6px 0 0', lineHeight: 1.55 }}>{lvl.description}</p>}
                {sc.comment && <p style={{ fontSize: 13, margin: '8px 0 0', paddingTop: 8, borderTop: '1px solid var(--border)', lineHeight: 1.55 }}>
                  💬 {sc.comment}
                </p>}
              </div>
            );
          })}
        </div>
      )}
    </section>
  );
}

window.RubricsAdmin  = RubricsAdmin;
window.GradebookAdmin = GradebookAdmin;
window.MyGradeCard   = MyGradeCard;
