// src/features/meetings/ui/meeting-note-banner.jsx
//
// In-meeting admin-notes banner overlay. Mounts inside lesson-room.jsx
// (the in-house Daily callObject lesson page) when the viewer is the
// booking's assigned instructor.
//
// Why it lives in its own file:
//   - Keeps lesson-room.jsx under the 400-LOC cap (it was already at 478).
//   - The realtime subscription + dedup logic is self-contained — easier
//     to read here than buried inside the lesson room's lifecycle code.
//   - Future "send the same note over SMS too" / "show in admin panel as
//     'delivered'" features can hook the same shape from here.
//
// Public API:
//   window.MeetingNotes.LessonNoteBanner
//
// Props:
//   bookingId  — string UUID, required
//   active     — boolean; when false the banner mounts as null and does
//                not subscribe (e.g. viewer is not the instructor)
//
// Behaviour:
//   - Subscribes to public.meeting_notes realtime FIRST, then runs the
//     initial fetch — ordering avoids a race where a note inserted between
//     the snapshot and the channel becoming live would be lost forever.
//   - Initial fetch retries 3× with 250ms / 750ms backoff so a transient
//     blip doesn't silently leave the teacher with no banner.
//   - Dismiss is optimistic; realtime UPDATE reconciles on success / refresh
//     recovers on failure.
//   - Sticky until dismissed — no auto-disappear timer. Urgent messages
//     shouldn't vanish.
//   - Mobile-friendly: orange card, 44px+ tap target on Got it, stacked.

(function () {
  'use strict';

  window.MeetingNotes = window.MeetingNotes || {};

  const LessonNoteBanner = ({ bookingId, active }) => {
    const [notes, setNotes] = React.useState([]); // [{ id, message, created_at }]

    React.useEffect(() => {
      if (!active || !bookingId) return;

      let chan = null;
      let cancelled = false;

      function mergeNoteRow(prev, row) {
        if (prev.some((n) => n.id === row.id)) return prev;
        const next = [...prev, row];
        next.sort((a, b) => (a.created_at || '').localeCompare(b.created_at || ''));
        return next;
      }

      async function fetchInitialNotes() {
        let lastError = null;
        for (let attempt = 0; attempt < 3 && !cancelled; attempt++) {
          if (attempt > 0) {
            await new Promise((r) => setTimeout(r, attempt === 1 ? 250 : 750));
            if (cancelled) return;
          }
          const { data, error } = await window.supa
            .from('meeting_notes')
            .select('id, message, created_at')
            .eq('booking_id', bookingId)
            .is('dismissed_at', null)
            .order('created_at', { ascending: true });
          if (cancelled) return;
          if (!error && Array.isArray(data)) {
            setNotes((prev) => data.reduce(mergeNoteRow, prev));
            return;
          }
          lastError = error || new Error('initial notes load returned no data');
        }
        if (!cancelled && lastError) {
          // Log the failure shape only — never the note content (PII).
          // eslint-disable-next-line no-console
          console.warn(
            '[meeting-note-banner] initial admin-notes load failed:',
            lastError.message || lastError.code || 'unknown'
          );
        }
      }

      // Defensive (unreachable in current layout): if a future edit inserts
      // an await above, the cleanup may have already run by here.
      if (cancelled) return;

      chan = window.supa
        .channel('meeting_notes_' + bookingId)
        .on('postgres_changes',
            { event: 'INSERT', schema: 'public', table: 'meeting_notes',
              filter: 'booking_id=eq.' + bookingId },
            (payload) => {
              const row = payload.new;
              if (!row || row.dismissed_at) return;
              setNotes((prev) => mergeNoteRow(prev, row));
            })
        .on('postgres_changes',
            { event: 'UPDATE', schema: 'public', table: 'meeting_notes',
              filter: 'booking_id=eq.' + bookingId },
            (payload) => {
              const row = payload.new;
              if (!row) return;
              if (row.dismissed_at) setNotes((prev) => prev.filter((n) => n.id !== row.id));
            })
        .subscribe((status) => {
          // Wait for the channel to be live before fetching initial state —
          // any INSERTs after SUBSCRIBED arrive via the handler above and
          // are deduped against the fetch result.
          if (status === 'SUBSCRIBED' && !cancelled) fetchInitialNotes();
        });

      return () => {
        cancelled = true;
        if (chan) { try { window.supa.removeChannel(chan); } catch (_) {} }
      };
    }, [active, bookingId]);

    async function dismiss(noteId) {
      // Optimistic local removal; realtime UPDATE reconciles.
      setNotes((prev) => prev.filter((n) => n.id !== noteId));
      try {
        await window.supa.rpc('instructor_dismiss_meeting_note', { p_note_id: noteId });
      } catch (_) {
        // Swallow — banner already optimistically gone. Refresh would
        // re-show the row if the RPC actually failed.
      }
    }

    if (!active || notes.length === 0) return null;

    // top: 56 (not 14) so we never overlap the recErrorChip in
    // lesson-room.jsx (top: 14, left: 16, ~30px tall). The chip surfaces
    // recording failures — the teacher needs to see it the moment the
    // banner arrives, not after she dismisses. On viewports where both
    // appear, the chip sits underneath the banner, fully visible.
    return (
      <div style={{
        position: 'absolute', top: 56, left: 0, right: 0,
        display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8,
        padding: '0 12px',
        pointerEvents: 'none',
      }}>
        {notes.map((n) => (
          <div key={n.id} style={{
            background: 'oklch(72% 0.15 70)',
            color: 'oklch(20% 0.04 70)',
            borderRadius: 12,
            padding: '12px 16px',
            display: 'flex', alignItems: 'flex-start', gap: 12,
            maxWidth: 520, width: '100%',
            boxShadow: '0 6px 16px rgba(0,0,0,0.18)',
            fontFamily: "'Plus Jakarta Sans', sans-serif",
            pointerEvents: 'auto',
          }}>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{
                fontSize: 11, fontWeight: 700, letterSpacing: 0.4,
                textTransform: 'uppercase', opacity: 0.7, marginBottom: 4,
              }}>
                Note from admin
              </div>
              <div style={{
                fontSize: 14, lineHeight: 1.4,
                wordBreak: 'break-word', whiteSpace: 'pre-wrap',
              }}>
                {n.message}
              </div>
            </div>
            <button
              type="button"
              onClick={() => dismiss(n.id)}
              style={{
                background: 'oklch(20% 0.04 70)', color: '#fff',
                border: 'none', borderRadius: 8,
                padding: '0 14px', minHeight: 44, minWidth: 64,
                fontFamily: "'Plus Jakarta Sans', sans-serif",
                fontSize: 13, fontWeight: 700, cursor: 'pointer',
                flexShrink: 0,
              }}>
              Got it
            </button>
          </div>
        ))}
      </div>
    );
  };

  window.MeetingNotes.LessonNoteBanner = LessonNoteBanner;
})();
