// Learning.jsx — portal page for "Learning" posts (v18.00).
//
// Short notices the whole crew has to read and sign: a bad review, something
// that happened in the store. Same rich body as a training item, but the
// point is the signature rather than the curriculum — and the post retires
// itself the moment the last assigned person signs, so nothing here needs
// tidying up afterwards.
//
// Deliberately NOT part of Training: a training item is permanent curriculum
// with trainers, practice stages and categories. Merging them would have
// meant a mode flag that changed the meaning of half of training_items'
// columns. See the migration comment for the same note from the DB side.
//
// All top-level idents are lp/Lp/LP_-prefixed — Babel-standalone shares one
// global scope across every .jsx in the portal (HANDBOOK §9).

const lpPostFromRow = (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,
});
// NOTE: never send restaurant_id — useSupaList injects it on inserts.
const lpPostToRow = (o) => ({
  id: o.id,
  title: o.title || '',
  body_html: o.bodyHtml || '',
  assigned_staff_ids: Array.isArray(o.assignedStaffIds) ? o.assignedStaffIds : null,
  status: o.status || 'draft',
  archived: !!o.archived,
  published_at: o.publishedAt || null,
  updated_at: new Date().toISOString(),
});

const lpFmtWhen = (iso) => {
  if (!iso) return '—';
  const d = new Date(iso);
  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' ' +
    d.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
};

// Admins write these posts; they don't sign them. Must stay identical to the
// iPad's lrnSigner or the two apps will disagree about whether a post is
// finished — the portal would show it waiting while the belt had dropped it.
const lpSigner = (s) => s.active !== false && s.role !== 'Admin';

// Mirrors the iPad's lrnAssignees and isAssignedTraining: an explicit []
// means NOBODY, only an unset list means everyone. Getting this backwards
// is the v6.22 trap — it silently re-assigns the whole crew.
const lpAssignees = (post, staff) => {
  const signers = (staff || []).filter(lpSigner);
  if (Array.isArray(post.assignedStaffIds)) {
    return signers.filter(s => post.assignedStaffIds.indexOf(s.id) >= 0);
  }
  return signers;
};

const Learning = () => {
  const [posts, setPosts] = window.useSupaList('learning_posts', {
    fromRow: lpPostFromRow, toRow: lpPostToRow, initial: [],
  });
  const [acks, setAcks] = useState([]);
  const [editing, setEditing] = useState(null);   // post object or null
  const [showDone, setShowDone] = useState(false);
  const [toast, setToast] = useState('');

  const staff = (window.SAMPLE_STAFF || []);

  // Acks are read-mostly here (the iPad writes them), so a plain fetch +
  // subscription rather than useSupaList — nothing on this page inserts one.
  useEffect(() => {
    if (!window.supa) return;
    let cancelled = false;
    const load = () => {
      window.supa.from('learning_acks').select('*')
        .eq('restaurant_id', window.RESTAURANT_ID)
        .then(({ data, error }) => {
          if (error || cancelled || !data) return;
          setAcks(data);
        });
    };
    load();
    const ch = window.supa
      .channel('portal-learning-acks:' + window.RESTAURANT_ID)
      .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 = useMemo(() => {
    const m = {};
    acks.forEach(a => { (m[a.post_id] = m[a.post_id] || {})[a.staff_id] = a; });
    return m;
  }, [acks]);

  const rows = useMemo(() => posts.map(p => {
    const assigned = lpAssignees(p, staff);
    const signed = assigned.filter(s => (acksByPost[p.id] || {})[s.id]);
    const outstanding = assigned.filter(s => !(acksByPost[p.id] || {})[s.id]);
    return { post: p, assigned, signed, outstanding, done: p.status === 'live' && outstanding.length === 0 };
  }).sort((a, b) => {
    // Live-and-waiting first — that's what the crew is looking at.
    const rank = (r) => (r.post.status !== 'live' ? 1 : r.done ? 2 : 0);
    return rank(a) - rank(b) ||
      String(b.post.publishedAt || b.post.createdAt || '').localeCompare(String(a.post.publishedAt || a.post.createdAt || ''));
  }), [posts, acksByPost, staff]);

  const visible = rows.filter(r => showDone ? true : (!r.done && !r.post.archived));
  const doneCount = rows.filter(r => r.done || r.post.archived).length;
  const liveWaiting = rows.filter(r => r.post.status === 'live' && !r.done && !r.post.archived).length;

  const newPost = () => setEditing({
    id: 'new', title: '', bodyHtml: '<p></p>',
    // Default to the whole signing crew as an explicit list, so the roster is
    // visible and editable in the form rather than an invisible "everyone".
    assignedStaffIds: staff.filter(lpSigner).map(s => s.id),
    status: 'draft', archived: false, publishedAt: null,
  });

  const savePost = (draft, publish) => {
    const isNew = draft.id === 'new';
    const next = Object.assign({}, draft, {
      status: publish ? 'live' : draft.status,
      publishedAt: publish && !draft.publishedAt ? new Date().toISOString() : draft.publishedAt,
    });
    if (isNew) {
      next.id = crypto.randomUUID();
      setPosts(prev => prev.concat([next]));
    } else {
      setPosts(prev => prev.map(p => p.id === next.id ? next : p));
    }
    setEditing(null);
    setToast(publish ? 'Posted — the crew sees it now.' : 'Saved as a draft.');
  };

  const deletePost = (post) => {
    if (!window.confirm('Delete “' + (post.title || 'this post') + '”? Signatures collected for it go too.')) return;
    setPosts(prev => prev.filter(p => p.id !== post.id));
    setEditing(null);
  };

  const archiveToggle = (post) => {
    setPosts(prev => prev.map(p => p.id === post.id ? Object.assign({}, p, { archived: !p.archived }) : p));
    setToast(post.archived ? 'Reopened.' : 'Closed — it’s off the crew’s belt.');
  };

  return (
    <div className="portal-page-wide">
      <div className="portal-page-header">
        <div>
          <h1 className="portal-page-title">Learning</h1>
          <div className="portal-page-subtitle">
            Things the crew needs to read and sign — a review, an incident, a change.
            A post drops off their belt on its own once the last person has signed.
          </div>
        </div>
        <PBtn variant="primary" size="md" icon="ri-add-line" onClick={newPost}>New post</PBtn>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 14 }}>
        <span style={{ fontSize: 12.5, color: 'var(--fg-2)' }}>
          {liveWaiting === 0
            ? 'Nothing outstanding.'
            : liveWaiting + (liveWaiting === 1 ? ' post is' : ' posts are') + ' still waiting on signatures.'}
        </span>
        <div style={{ flex: 1 }} />
        {doneCount > 0 && (
          <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12.5, color: 'var(--fg-2)', cursor: 'pointer' }}>
            <input type="checkbox" checked={showDone} onChange={e => setShowDone(e.target.checked)} />
            Show finished ({doneCount})
          </label>
        )}
      </div>

      <div className="portal-card">
        <table className="portal-table">
          <thead>
            <tr>
              <th>Post</th>
              <th style={{ width: 110 }}>Status</th>
              <th style={{ width: 110, textAlign: 'right' }}>Signed</th>
              <th>Still to sign</th>
              <th style={{ width: 150 }}>Posted</th>
              <th style={{ width: 40 }}></th>
            </tr>
          </thead>
          <tbody>
            {visible.length === 0 && (
              <tr><td colSpan={6} style={{ padding: 30, textAlign: 'center', color: 'var(--fg-3)', fontSize: 12.5 }}>
                {posts.length === 0 ? 'Nothing posted yet.' : 'Nothing outstanding — tick “Show finished” to see past posts.'}
              </td></tr>
            )}
            {visible.map(r => (
              <tr key={r.post.id} onClick={() => setEditing(r.post)} style={{ cursor: 'pointer' }}>
                <td style={{ fontWeight: 500 }}>
                  {r.post.title || <span style={{ color: 'var(--fg-3)' }}>Untitled</span>}
                </td>
                <td><LpStatus post={r.post} done={r.done} /></td>
                <td style={{ textAlign: 'right', fontFamily: 'var(--font-num)', color: 'var(--fg-2)' }}>
                  {r.signed.length}/{r.assigned.length}
                </td>
                <td>
                  {r.outstanding.length === 0
                    ? <span style={{ color: 'var(--fg-3)', fontSize: 12.5 }}>—</span>
                    : r.outstanding.length <= 6
                      ? <PAvatarStack ids={r.outstanding.map(s => s.id)} size={22} max={6} />
                      : <span style={{ fontSize: 12.5, color: 'var(--fg-2)' }}>{r.outstanding.length} people</span>}
                </td>
                <td style={{ color: 'var(--fg-2)', fontSize: 12.5 }}>{lpFmtWhen(r.post.publishedAt)}</td>
                <td style={{ textAlign: 'right', color: 'var(--fg-3)' }}><PIcon name="ri-arrow-right-s-line" size={16} /></td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {editing && (
        <LpEditor
          post={editing}
          staff={staff}
          signed={(acksByPost[editing.id] || {})}
          onSave={savePost}
          onDelete={deletePost}
          onArchive={archiveToggle}
          onClose={() => setEditing(null)}
        />
      )}

      <PToast message={toast} open={!!toast} onClose={() => setToast('')} />
    </div>
  );
};

const LpStatus = ({ post, done }) => {
  let label = 'Draft', bg = 'var(--bg-sunken)', fg = 'var(--fg-2)';
  if (post.archived) { label = 'Closed'; }
  else if (done) { label = 'All signed'; bg = 'var(--success-bg)'; fg = 'var(--success)'; }
  else if (post.status === 'live') { label = 'Live'; bg = 'var(--danger-bg)'; fg = 'var(--danger)'; }
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', padding: '3px 9px', borderRadius: 999,
      background: bg, color: fg, fontSize: 11, fontWeight: 600, letterSpacing: '0.02em',
    }}>{label}</span>
  );
};

// ------------------------------------------------------------------
// Editor
// ------------------------------------------------------------------
const LpEditor = ({ post, staff, signed, onSave, onDelete, onArchive, onClose }) => {
  const [draft, setDraft] = useState(post);
  const [crewTab, setCrewTab] = useState('all');
  const isNew = post.id === 'new';
  const set = (patch) => setDraft(d => Object.assign({}, d, patch));

  const roster = staff
    .filter(lpSigner)
    .filter(s => crewTab === 'line' ? s.isLineCrew : true)
    .sort((a, b) => (a.name || '').localeCompare(b.name || ''));

  const picked = Array.isArray(draft.assignedStaffIds) ? draft.assignedStaffIds : staff.filter(lpSigner).map(s => s.id);
  const toggle = (id) => set({
    assignedStaffIds: picked.indexOf(id) >= 0 ? picked.filter(x => x !== id) : picked.concat([id]),
  });

  const signedCount = Object.keys(signed || {}).length;

  return (
    <PModal open onClose={onClose} width={760} title={isNew ? 'New learning post' : 'Learning post'} footer={
      <>
        {!isNew && <PBtn variant="danger" onClick={() => onDelete(draft)}>Delete</PBtn>}
        {!isNew && draft.status === 'live' && (
          <PBtn variant="ghost" onClick={() => { onArchive(draft); onClose(); }}>
            {draft.archived ? 'Reopen' : 'Close it out'}
          </PBtn>
        )}
        <div style={{ flex: 1 }} />
        <PBtn variant="ghost" onClick={onClose}>Cancel</PBtn>
        {draft.status !== 'live' && (
          <PBtn onClick={() => onSave(draft, false)} disabled={!draft.title.trim()}>Save draft</PBtn>
        )}
        <PBtn variant="primary" disabled={!draft.title.trim() || picked.length === 0}
          onClick={() => onSave(draft, true)}>
          {draft.status === 'live' ? 'Save' : 'Post to the crew'}
        </PBtn>
      </>
    }>
      <div style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 16 }}>
        {/* Summarize writes the title FROM the body, which is the order you
            actually write one of these in: something happened, you type what
            happened, and the headline is the afterthought. */}
        <PField label="Title">
          {window.AiTextField
            ? <window.AiTextField
                value={draft.title}
                onChange={(v) => set({ title: v })}
                placeholder="e.g. 1-star review about a cold bento — what happened"
                contextLabel={draft.title}
                summarizeFrom={draft.bodyHtml}
                summarizeFromFormat="html"
                summarizeAs="title"
              />
            : <input className="portal-input" autoFocus value={draft.title}
                onChange={e => set({ title: e.target.value })}
                placeholder="e.g. 1-star review about a cold bento — what happened" />}
        </PField>

        <PField label="What they need to know" hint="Paste or drop images straight in — they upload rather than bloating the post.">
          {window.QuillEditor
            ? <QuillEditor value={draft.bodyHtml} onChange={v => set({ bodyHtml: v })} minHeight={220} contextLabel="learning post" />
            : <textarea className="portal-input" rows={8} value={draft.bodyHtml} onChange={e => set({ bodyHtml: e.target.value })} style={{ height: 'auto', padding: 10 }} />}
        </PField>

        <PField
          label={'Who has to sign — ' + picked.length + ' picked'}
          hint={draft.status === 'live'
            ? 'Removing someone who hasn’t signed drops them from the belt. Signatures already collected are kept.'
            : 'Everyone here has to sign before the post clears itself off the crew’s belt.'}
        >
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
            <PSegment
              options={[{ value: 'all', label: 'All crew' }, { value: 'line', label: 'Line crew' }]}
              value={crewTab} onChange={setCrewTab}
            />
            <div style={{ flex: 1 }} />
            <PBtn size="xs" onClick={() => set({ assignedStaffIds: roster.map(s => s.id) })}>Select all</PBtn>
            <PBtn size="xs" onClick={() => set({ assignedStaffIds: [] })}>Clear</PBtn>
          </div>
          <div style={{
            display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6,
            maxHeight: 260, overflowY: 'auto', border: '1px solid var(--border-2)',
            borderRadius: 10, padding: 8,
          }}>
            {roster.map(s => {
              const on = picked.indexOf(s.id) >= 0;
              const hasSigned = !!(signed || {})[s.id];
              return (
                <label key={s.id} style={{
                  display: 'flex', alignItems: 'center', gap: 8, padding: '6px 8px',
                  borderRadius: 8, cursor: 'pointer',
                  background: on ? 'var(--bg-sunken)' : 'transparent',
                }}>
                  <input type="checkbox" checked={on} onChange={() => toggle(s.id)} />
                  <PAvatar staff={s} size={22} />
                  <span style={{ fontSize: 12.5, color: 'var(--fg-1)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {s.name}
                  </span>
                  {hasSigned && <PIcon name="ri-check-line" size={14} color="var(--success)" />}
                </label>
              );
            })}
          </div>
        </PField>

        {!isNew && draft.status === 'live' && (
          <div style={{ fontSize: 12, color: 'var(--fg-3)', background: 'var(--bg-sunken)', borderRadius: 8, padding: '9px 11px', lineHeight: 1.5 }}>
            {signedCount} {signedCount === 1 ? 'person has' : 'people have'} signed so far — the ticks above show who.
            The post takes itself off the crew’s belt once everyone picked here has signed; you don’t need to come back and close it.
          </div>
        )}
      </div>
    </PModal>
  );
};

window.Learning = Learning;
