// LearningScreen.jsx — "Learning": short notices the whole crew has to read
// and sign (v18.00).
//
// Not training. A training item is permanent curriculum with trainers and
// practice stages; a learning post is a bad review, or something that
// happened in the store on Tuesday. It's urgent, it's read-once, and the
// only thing it asks for is a signature from each person it names.
//
// Three pieces live here:
//   useLearningData()  — one fetch + realtime subscription, called ONCE at
//                        App level so the channel survives navigation (same
//                        reason broadcasts live up there).
//   LearningBelt       — the red band at the top of every screen while
//                        anything is outstanding. Count on the left, and
//                        once the stragglers are down to six or fewer,
//                        their faces on the right so the crew can chase
//                        each other instead of waiting on a manager.
//   LearningScreen     — list → reader → sign.
//
// A post retires itself: once every assigned person has signed, it stops
// being "active" and the belt drops it. Nothing to tidy up afterwards,
// which matters for something posted at 11pm about a review.
//
// Idents are lrn/Lrn/LRN_-prefixed — Babel-standalone shares one global
// scope across every .jsx in the app (HANDBOOK §9).

const { useState: useLrn, useEffect: useLrnEffect, useMemo: useLrnMemo, useCallback: useLrnCb } = React;

const LRN_RED = '#DC2626';
const LRN_RED_DARK = '#B91C1C';
const LRN_FACES_AT = 6;   // show faces once this many signers (or fewer) remain

// Admins write these posts; they don't sign them. Filtered here rather than
// left to whoever ticks the boxes, so an admin can never end up as the one
// name holding a post open on the belt — including on a post whose stored
// list predates this rule.
const lrnSigner = (s) => s.active !== false && s.role !== 'Admin';

// Who a post is addressed to. Mirrors isAssignedTraining's contract exactly,
// including the trap: an explicit empty array means NOBODY (you deselected
// everyone), and only a genuinely unset list falls through to "everyone".
const lrnAssignees = (post, staff) => {
  const ids = post.assignedStaffIds;
  const signers = (staff || []).filter(lrnSigner);
  if (Array.isArray(ids)) return signers.filter(s => ids.indexOf(s.id) >= 0);
  return signers;
};

const lrnOutstanding = (post, staff, acksByPost) => {
  const signed = acksByPost[post.id] || {};
  return lrnAssignees(post, staff).filter(s => !signed[s.id]);
};

const lrnPostFromRow = (r) => ({
  id: r.id,
  title: r.title || '',
  bodyHtml: r.body_html || '',
  assignedStaffIds: Array.isArray(r.assigned_staff_ids) ? r.assigned_staff_ids : null,
  status: r.status || 'draft',
  archived: !!r.archived,
  publishedAt: r.published_at || null,
  createdAt: r.created_at || null,
});

// ------------------------------------------------------------------
// Data — one subscription for the whole app
// ------------------------------------------------------------------
const useLearningData = () => {
  const [posts, setPosts] = useLrn([]);
  const [acks, setAcks] = useLrn([]);

  useLrnEffect(() => {
    if (!window.supa) return;
    let cancelled = false;
    const load = () => {
      window.supa.from('learning_posts').select('*')
        .eq('restaurant_id', window.RESTAURANT_ID)
        .eq('status', 'live')
        .eq('archived', false)
        .then(({ data, error }) => {
          if (error) { console.error('learning_posts select failed', error); return; }
          if (cancelled || !data) return;
          setPosts(data.map(lrnPostFromRow));
        });
      window.supa.from('learning_acks').select('*')
        .eq('restaurant_id', window.RESTAURANT_ID)
        .then(({ data, error }) => {
          if (error) { console.error('learning_acks select failed', error); return; }
          if (cancelled || !data) return;
          setAcks(data);
        });
    };
    load();
    // Refetch wholesale rather than patching: the belt's correctness depends
    // on posts AND acks agreeing, the data is a handful of rows, and a
    // publish flips `status` (an UPDATE the filtered select would otherwise
    // have to re-derive).
    const ch = window.supa
      .channel('ipad-learning:' + window.RESTAURANT_ID)
      .on('postgres_changes', {
        event: '*', schema: 'public', table: 'learning_posts',
        filter: 'restaurant_id=eq.' + window.RESTAURANT_ID,
      }, () => { if (!cancelled) load(); })
      .on('postgres_changes', {
        event: '*', schema: 'public', table: 'learning_acks',
        filter: 'restaurant_id=eq.' + window.RESTAURANT_ID,
      }, () => { if (!cancelled) load(); })
      .subscribe();
    return () => { cancelled = true; window.supa.removeChannel(ch); };
  }, []);

  const acksByPost = useLrnMemo(() => {
    const m = {};
    acks.forEach(a => { (m[a.post_id] = m[a.post_id] || {})[a.staff_id] = a; });
    return m;
  }, [acks]);

  const staff = window.SAMPLE_STAFF || [];

  // Active = live, not archived, and still waiting on at least one person.
  const active = useLrnMemo(
    () => posts.filter(p => lrnOutstanding(p, staff, acksByPost).length > 0),
    [posts, acksByPost, staff]);

  // The union of everyone still owing a signature anywhere. This is what the
  // belt counts down — one person outstanding on three posts is one face.
  const waitingOn = useLrnMemo(() => {
    const seen = {};
    const out = [];
    active.forEach(p => lrnOutstanding(p, staff, acksByPost).forEach(s => {
      if (seen[s.id]) return;
      seen[s.id] = true;
      out.push(s);
    }));
    return out.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
  }, [active, acksByPost, staff]);

  const sign = useLrnCb(async (postId, staffId) => {
    // Optimistic: the belt should drop the moment the last person signs,
    // not a round-trip later.
    const optimistic = { id: crypto.randomUUID(), post_id: postId, staff_id: staffId, acked_at: new Date().toISOString() };
    setAcks(prev => prev.concat([optimistic]));
    const { error } = await window.supa.from('learning_acks').insert({
      restaurant_id: window.RESTAURANT_ID, post_id: postId, staff_id: staffId,
    });
    // 23505 = already signed. Someone double-tapped, or two iPads raced —
    // either way the signature exists, which is all we wanted.
    if (error && error.code !== '23505') {
      console.error('learning ack insert failed', error);
      setAcks(prev => prev.filter(a => a.id !== optimistic.id));
      return false;
    }
    return true;
  }, []);

  return { posts, active, acksByPost, waitingOn, sign };
};

// ------------------------------------------------------------------
// The red belt
// ------------------------------------------------------------------
const LearningBelt = ({ active, waitingOn, onOpen }) => {
  if (!active || active.length === 0) return null;
  const n = active.length;
  const showFaces = waitingOn.length > 0 && waitingOn.length <= LRN_FACES_AT;

  return (
    <button
      onClick={onOpen}
      style={{
        flexShrink: 0, width: '100%',
        display: 'flex', alignItems: 'center', gap: 12,
        background: LRN_RED, color: '#fff',
        border: 'none', borderBottom: '1px solid ' + LRN_RED_DARK,
        padding: '9px 16px',
        paddingLeft: 'calc(16px + env(safe-area-inset-left))',
        paddingRight: 'calc(16px + env(safe-area-inset-right))',
        textAlign: 'left', fontSize: 13,
      }}
    >
      <span style={{ fontSize: 17, fontWeight: 700, fontFamily: 'var(--font-num)', lineHeight: 1 }}>{n}</span>
      <span style={{ opacity: 0.95 }}>{n === 1 ? 'thing to read' : 'things to read'}</span>

      <span style={{ flex: 1 }} />

      {showFaces && (
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
          <span style={{ opacity: 0.9, whiteSpace: 'nowrap' }}>Waiting on</span>
          {/* Laid out side by side rather than overlapped (v19.04). A stack
              reads as "a group" and hides most of each face behind the next
              one — but this list exists to be read as individuals, since the
              whole question it answers is WHO still owes a signature. At the
              LRN_FACES_AT cap of 6 it still fits comfortably. The white ring
              each Avatar carries (v18.09) now only has to separate the face
              from the red, not from its neighbour. */}
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
            {waitingOn.map(s => (
              <Avatar key={s.id} staff={s} size={24} />
            ))}
          </span>
        </span>
      )}

      <i className="ri-arrow-right-s-line" style={{ fontSize: 22, lineHeight: 1, opacity: 0.95 }} />
    </button>
  );
};

// ------------------------------------------------------------------
// Screen: list → reader → sign
// ------------------------------------------------------------------
const LearningScreen = ({ active, acksByPost, sign, showToast }) => {
  const [openId, setOpenId] = useLrn(null);
  const staff = window.SAMPLE_STAFF || [];
  const post = active.find(p => p.id === openId) || null;

  if (post) {
    return (
      <LrnReader
        post={post}
        outstanding={lrnOutstanding(post, staff, acksByPost)}
        onBack={() => setOpenId(null)}
        onSign={async (s) => {
          const ok = await sign(post.id, s.id);
          if (ok) {
            showToast && showToast({ message: 'Signed — thanks ' + (s.name || '').split(' ')[0], staff: s });
            setOpenId(null);
          }
          return ok;
        }}
      />
    );
  }

  return (
    <div style={{ height: '100%', overflowY: 'auto', padding: '20px 20px 32px' }}>
      <div style={{ marginBottom: 18 }}>
        <div style={{ fontSize: 11, color: 'var(--fg-3)', fontWeight: 500, letterSpacing: '0.06em', textTransform: 'uppercase' }}>
          Read and sign
        </div>
        <div style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em', color: 'var(--fg-1)', marginTop: 2 }}>
          Learning
        </div>
      </div>

      {active.length === 0 && (
        <div style={{ padding: '48px 20px', textAlign: 'center', color: 'var(--fg-3)', fontSize: 14 }}>
          Nothing to read right now.
        </div>
      )}

      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        {active.map(p => {
          const out = lrnOutstanding(p, staff, acksByPost);
          const total = lrnAssignees(p, staff).length;
          const signed = total - out.length;
          return (
            <button key={p.id} onClick={() => setOpenId(p.id)} style={{
              display: 'flex', alignItems: 'center', gap: 14,
              width: '100%', textAlign: 'left',
              background: 'var(--bg-surface, #fff)',
              border: '1px solid var(--border-1)', borderLeft: '4px solid ' + LRN_RED,
              borderRadius: 14, padding: '16px 18px',
            }}>
              {/* Title + meta in their own column so the chevron centres on the
                  whole card rather than riding the second line. */}
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 17, fontWeight: 600, color: 'var(--fg-1)', letterSpacing: '-0.01em' }}>
                  {p.title || 'Untitled'}
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 10 }}>
                  <span style={{ fontSize: 12.5, color: 'var(--fg-2)', fontFamily: 'var(--font-num)' }}>
                    {signed}/{total} signed
                  </span>
                  <span style={{ flex: 1 }} />
                  {out.length <= LRN_FACES_AT && out.length > 0 && (
                    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
                      <span style={{ fontSize: 12.5, color: 'var(--fg-2)', whiteSpace: 'nowrap' }}>Waiting on</span>
                      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, flexShrink: 0 }}>
                        {out.map(s => (
                          <Avatar key={s.id} staff={s} size={22} />
                        ))}
                      </span>
                    </span>
                  )}
                </div>
              </div>
              <i className="ri-arrow-right-s-line" style={{ fontSize: 20, color: 'var(--fg-3)', flexShrink: 0 }} />
            </button>
          );
        })}
      </div>
    </div>
  );
};

// ------------------------------------------------------------------
// Reader + signing
// ------------------------------------------------------------------
const LrnReader = ({ post, outstanding, onBack, onSign }) => {
  const [phase, setPhase] = useLrn('view');   // view | pick | pin
  const [who, setWho] = useLrn(null);

  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 50, background: 'var(--bg-page)', display: 'flex', flexDirection: 'column' }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 12, padding: '14px 18px',
        borderBottom: '1px solid var(--border-1)', background: 'var(--bg-surface, #fff)', flexShrink: 0,
      }}>
        <button onClick={onBack} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, background: 'transparent', border: 'none', color: 'var(--fg-2)', fontSize: 15 }}>
          <i className="ri-arrow-left-line" style={{ fontSize: 20 }} /> Back
        </button>
        <span style={{ flex: 1 }} />
        <span style={{ fontSize: 12.5, color: 'var(--fg-3)', fontFamily: 'var(--font-num)' }}>
          {outstanding.length} still to sign
        </span>
      </div>

      <div style={{ flex: 1, overflowY: 'auto', padding: '22px 22px 140px' }}>
        <h1 style={{ fontSize: 26, fontWeight: 700, letterSpacing: '-0.02em', color: 'var(--fg-1)', margin: '0 0 16px' }}>
          {post.title || 'Untitled'}
        </h1>
        {/* Fail closed: no DOMPurify, no body. Same rule as TrainingScreen. */}
        <div
          className="lrn-body"
          dangerouslySetInnerHTML={{
            __html: window.DOMPurify
              ? window.DOMPurify.sanitize(post.bodyHtml || '', { USE_PROFILES: { html: true } })
              : '',
          }}
        />
        <style>{`
.lrn-body { font-size: 16px; line-height: 1.65; color: var(--fg-1); }
.lrn-body img, .lrn-body video, .lrn-body iframe { max-width: 100%; height: auto; border-radius: 12px; display: block; margin: 14px 0; }
.lrn-body p { margin: 0 0 12px; }
.lrn-body h1, .lrn-body h2, .lrn-body h3 { line-height: 1.3; margin: 20px 0 8px; }
.lrn-body ul, .lrn-body ol { padding-left: 22px; margin: 0 0 12px; }
.lrn-body a { color: #1D4ED8; }
`}</style>
      </div>

      <div style={{
        position: 'absolute', left: 0, right: 0, bottom: 0,
        padding: '14px 18px calc(14px + env(safe-area-inset-bottom))',
        background: 'rgba(255,255,255,0.96)', borderTop: '1px solid var(--border-1)',
        backdropFilter: 'saturate(180%) blur(20px)', WebkitBackdropFilter: 'saturate(180%) blur(20px)',
      }}>
        <button
          onClick={() => setPhase('pick')}
          disabled={outstanding.length === 0}
          style={{
            width: '100%', height: 54, borderRadius: 14, border: 'none',
            background: outstanding.length === 0 ? 'var(--bg-sunken)' : LRN_RED,
            color: outstanding.length === 0 ? 'var(--fg-3)' : '#fff',
            fontSize: 17, fontWeight: 600, letterSpacing: '-0.01em',
          }}
        >
          {outstanding.length === 0 ? 'Everyone has signed' : 'I’ve read this — sign'}
        </button>
      </div>

      {/* Only people who still owe a signature. Someone who already signed
          shouldn't be able to pick themselves and be told "already done" —
          and it makes the remaining list a to-do the crew can work through. */}
      {phase === 'pick' && (
        <LrnSignerPicker
          people={outstanding}
          onCancel={() => setPhase('view')}
          onPick={(s) => { setWho(s); setPhase('pin'); }}
        />
      )}
      {phase === 'pin' && who && (
        <LrnPinSheet
          who={who}
          expectedPin={who.pin}
          onCancel={() => { setWho(null); setPhase('view'); }}
          onSuccess={async () => { setPhase('view'); await onSign(who); setWho(null); }}
        />
      )}
    </div>
  );
};

// A local picker rather than the shared StaffPicker: this one lists only the
// people who still owe a signature, and says how many are left.
const LrnSignerPicker = ({ people, onCancel, onPick }) => (
  <div style={{ position: 'absolute', inset: 0, zIndex: 70, display: 'flex', alignItems: 'flex-end' }}>
    <div onClick={onCancel} style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.40)' }} />
    <div style={{
      position: 'relative', width: '100%', maxHeight: '80%', overflowY: 'auto',
      background: 'var(--bg-surface, #fff)', borderRadius: '24px 24px 0 0',
      padding: '22px 22px calc(22px + env(safe-area-inset-bottom))',
    }}>
      <div style={{ fontSize: 20, fontWeight: 600, color: 'var(--fg-1)', letterSpacing: '-0.02em' }}>Who are you?</div>
      <div style={{ fontSize: 13.5, color: 'var(--fg-2)', marginTop: 4, marginBottom: 18 }}>
        {people.length} {people.length === 1 ? 'person has' : 'people have'} still to sign this.
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12 }}>
        {people.map(s => (
          <button key={s.id} onClick={() => onPick(s)} style={{
            display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8,
            padding: '16px 8px', borderRadius: 14,
            border: '1px solid var(--border-1)', background: 'var(--bg-surface, #fff)',
          }}>
            <Avatar staff={s} size={52} />
            <span style={{ fontSize: 13.5, fontWeight: 500, color: 'var(--fg-1)', textAlign: 'center', lineHeight: 1.25 }}>{s.name}</span>
          </button>
        ))}
      </div>
      <button onClick={onCancel} style={{
        marginTop: 18, width: '100%', height: 48, borderRadius: 12,
        border: '1px solid var(--border-1)', background: 'transparent',
        color: 'var(--fg-2)', fontSize: 15.5, fontWeight: 500,
      }}>Cancel</button>
    </div>
  </div>
);

// File-local copy of TrainingScreen's PinConfirmSheet — that one isn't
// exported to window, and CrewMealScreen set the precedent (CmPinSheet).
const LrnPinSheet = ({ who, expectedPin, onCancel, onSuccess }) => {
  const [pin, setPin] = useLrn('');
  const [shake, setShake] = useLrn(false);

  const tryPin = (p) => {
    if (p === expectedPin) { onSuccess(); return; }
    setShake(true);
    setPin('');
    setTimeout(() => setShake(false), 420);
  };

  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 80, display: 'flex', alignItems: 'flex-end' }}>
      <div onClick={onCancel} style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.40)' }} />
      <div style={{
        position: 'relative', width: '100%',
        background: 'var(--bg-surface, #fff)', borderRadius: '24px 24px 0 0',
        padding: '24px 22px calc(22px + env(safe-area-inset-bottom))',
        transform: shake ? 'translateX(0)' : 'none',
        animation: shake ? 'lrnShake 0.4s' : 'none',
      }}>
        <style>{`@keyframes lrnShake { 10%,90%{transform:translateX(-2px)} 20%,80%{transform:translateX(4px)} 30%,50%,70%{transform:translateX(-8px)} 40%,60%{transform:translateX(8px)} }`}</style>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 18 }}>
          <Avatar staff={who} size={56} />
          <div>
            <div style={{ fontSize: 19, fontWeight: 600, color: 'var(--fg-1)', letterSpacing: '-0.02em' }}>{who.name}</div>
            <div style={{ fontSize: 13.5, color: 'var(--fg-2)', marginTop: 2 }}>Enter your PIN to sign</div>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 12, justifyContent: 'center', marginBottom: 20 }}>
          {[0, 1, 2, 3].map(i => (
            <div key={i} style={{
              width: 14, height: 14, borderRadius: 999,
              background: i < pin.length ? LRN_RED : 'var(--bg-sunken)',
            }} />
          ))}
        </div>
        <NumPad value={pin} onChange={setPin} onEnter={tryPin} maxLen={4} />
        <button onClick={onCancel} style={{
          marginTop: 16, width: '100%', height: 48, borderRadius: 12,
          border: '1px solid var(--border-1)', background: 'transparent',
          color: 'var(--fg-2)', fontSize: 15.5, fontWeight: 500,
        }}>Cancel</button>
      </div>
    </div>
  );
};

Object.assign(window, { LearningScreen, LearningBelt, useLearningData, lrnAssignees, lrnOutstanding, lrnSigner });
