// admin-live-meetings.jsx — admin Live Meetings tab.
//
// Left:  active bookings (scheduled_at within now-15min … now+90min).
//        Click to select.
// Right: live transcript of the selected booking, subscribed to
//        public.meeting_captions + meeting_alerts via Supabase realtime.
// Below: forbidden-topics textarea + master-switch toggle, both writing
//        through is_admin()-gated RPCs.
//
// Exposed as window.AdminLiveMeetings.

(function () {
  'use strict';

  const ALM_INK      = 'oklch(18% 0.03 265)';
  const ALM_MUTED    = 'oklch(50% 0.03 265)';
  const ALM_BORDER   = 'oklch(90% 0.01 265)';
  const ALM_BG       = '#fff';
  const ALM_PANEL    = 'oklch(98% 0.005 265)';
  const ALM_BLUE     = 'oklch(56% 0.13 250)';
  const ALM_GREEN    = 'oklch(55% 0.14 150)';
  const ALM_RED      = 'oklch(58% 0.20 25)';

  const fmtClock = (iso) => {
    if (!iso) return '';
    try {
      const d = new Date(iso);
      return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
    } catch (_) { return ''; }
  };

  const fmtRelative = (iso) => {
    if (!iso) return '';
    const diffMs = Date.now() - new Date(iso).getTime();
    const m = Math.round(diffMs / 60000);
    if (Math.abs(m) < 1) return 'now';
    if (m > 0)  return `${m} min ago`;
    return `in ${-m} min`;
  };

  function ActiveBookingsList({ selectedId, onSelect }) {
    const [rows, setRows]       = React.useState([]);
    const [loading, setLoading] = React.useState(true);
    const [error, setError]     = React.useState('');

    const load = React.useCallback(async () => {
      try {
        const minIso = new Date(Date.now() - 15 * 60 * 1000).toISOString();
        const maxIso = new Date(Date.now() + 90 * 60 * 1000).toISOString();
        const { data: bks, error: be } = await window.supa
          .from('bookings')
          .select('id,scheduled_at,duration_minutes,student_id,instructor_id,status')
          .gte('scheduled_at', minIso)
          .lte('scheduled_at', maxIso)
          .order('scheduled_at', { ascending: true });
        if (be) { setError(be.message); setRows([]); setLoading(false); return; }
        // Allowlist of statuses where the lesson is actually happening (or
        // about to). Excludes 'cancelled', 'completed', 'rescheduled', etc.
        // that may share the ±90-min time window but aren't live.
        const ACTIVE_STATUSES = ['pending', 'confirmed', 'in_progress'];
        const list = (bks || []).filter(b => ACTIVE_STATUSES.includes(b.status));
        if (!list.length) { setRows([]); setError(''); setLoading(false); return; }
        // Look up student names from profiles + instructor names via instructors→profiles.
        const studentIds    = Array.from(new Set(list.map(b => b.student_id).filter(Boolean)));
        const instructorIds = Array.from(new Set(list.map(b => b.instructor_id).filter(Boolean)));
        const [{ data: students }, { data: instructors }] = await Promise.all([
          studentIds.length
            ? window.supa.from('profiles').select('id,full_name').in('id', studentIds)
            : Promise.resolve({ data: [] }),
          instructorIds.length
            ? window.supa.from('instructors').select('id,user_id,profiles!instructors_user_id_fkey(full_name)').in('id', instructorIds)
            : Promise.resolve({ data: [] }),
        ]);
        const studentName = new Map((students || []).map(s => [s.id, s.full_name]));
        const instructorName = new Map((instructors || []).map(i => [i.id, i.profiles?.full_name || null]));
        setRows(list.map(b => ({
          ...b,
          _studentName:    studentName.get(b.student_id)    || 'Student',
          _instructorName: instructorName.get(b.instructor_id) || 'Teacher',
        })));
        setError('');
      } catch (err) {
        setError(err.message || 'load failed');
      } finally {
        setLoading(false);
      }
    }, []);

    React.useEffect(() => {
      load();
      const t = setInterval(load, 30_000);
      return () => clearInterval(t);
    }, [load]);

    if (loading) return <div style={{ padding: 16, color: ALM_MUTED }}>Loading…</div>;
    if (error)   return <div style={{ padding: 16, color: ALM_RED }}>{error}</div>;
    if (!rows.length) {
      return (
        <div style={{ padding: 16, color: ALM_MUTED, fontSize: 13 }}>
          No active lessons right now. The list shows lessons scheduled within
          ±90 minutes of now.
        </div>
      );
    }

    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6, padding: 8 }}>
        {rows.map((b) => {
          const active  = b.id === selectedId;
          const teacher = b._instructorName;
          const student = b._studentName;
          return (
            <button
              key={b.id}
              type="button"
              onClick={() => onSelect(b.id)}
              style={{
                textAlign: 'left',
                padding: '10px 12px',
                borderRadius: 8,
                border: '1px solid ' + (active ? ALM_BLUE : ALM_BORDER),
                background: active ? 'oklch(96% 0.04 250)' : ALM_BG,
                cursor: 'pointer',
                color: ALM_INK,
                fontFamily: "'Plus Jakarta Sans', sans-serif",
              }}
            >
              <div style={{ fontWeight: 600, fontSize: 13 }}>
                {teacher} <span style={{ color: ALM_MUTED, fontWeight: 400 }}>·</span> {student}
              </div>
              <div style={{ fontSize: 11, color: ALM_MUTED, marginTop: 2 }}>
                {fmtRelative(b.scheduled_at)} — {b.duration_minutes || 60} min
              </div>
            </button>
          );
        })}
      </div>
    );
  }

  function TranscriptPane({ bookingId }) {
    const [captions, setCaptions] = React.useState([]);
    const [alerts, setAlerts]     = React.useState([]);
    const [error, setError]       = React.useState('');
    const scrollRef = React.useRef(null);
    const stickyBottom = React.useRef(true);

    React.useEffect(() => {
      if (!bookingId) return undefined;
      setCaptions([]); setAlerts([]); setError('');

      let cancelled = false;
      (async () => {
        const sinceIso = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
        const [{ data: caps, error: ce }, { data: alts, error: ae }] = await Promise.all([
          window.supa.from('meeting_captions')
            .select('id,speaker_role,text,captured_at')
            .eq('booking_id', bookingId)
            .gte('captured_at', sinceIso)
            .order('captured_at', { ascending: true })
            .limit(500),
          window.supa.from('meeting_alerts')
            .select('id,flagged_topic,quote,created_at')
            .eq('booking_id', bookingId)
            .gte('created_at', sinceIso)
            .order('created_at', { ascending: true })
            .limit(50),
        ]);
        if (cancelled) return;
        if (ce) setError(ce.message);
        if (ae) setError(prev => prev || ae.message);
        // Realtime can deliver INSERTs between when the DB ran the
        // SELECT and when the response arrived on the client. Merge the
        // fetched rows with anything already in state, dedup by id, and
        // keep chronological order so neither side gets dropped.
        const fetchedCapIds = new Set((caps || []).map(c => c.id));
        setCaptions(prev => {
          const realtimeOnly = prev.filter(c => !fetchedCapIds.has(c.id));
          return [...(caps || []), ...realtimeOnly]
            .sort((a, b) => (a.captured_at < b.captured_at ? -1 : a.captured_at > b.captured_at ? 1 : 0));
        });
        const fetchedAltIds = new Set((alts || []).map(a => a.id));
        setAlerts(prev => {
          const realtimeOnly = prev.filter(a => !fetchedAltIds.has(a.id));
          return [...(alts || []), ...realtimeOnly]
            .sort((a, b) => (a.created_at < b.created_at ? -1 : a.created_at > b.created_at ? 1 : 0));
        });
      })();

      // Supabase realtime can re-deliver the same INSERT on WebSocket
      // reconnect, so dedup by id before appending — otherwise the same
      // caption would render twice.
      const capCh = window.supa
        .channel(`live-caps-${bookingId}`)
        .on('postgres_changes',
          { event: 'INSERT', schema: 'public', table: 'meeting_captions', filter: `booking_id=eq.${bookingId}` },
          (payload) => {
            setCaptions(prev => prev.some(c => c.id === payload.new.id) ? prev : prev.concat(payload.new));
          })
        .subscribe();

      const altCh = window.supa
        .channel(`live-alerts-${bookingId}`)
        .on('postgres_changes',
          { event: 'INSERT', schema: 'public', table: 'meeting_alerts', filter: `booking_id=eq.${bookingId}` },
          (payload) => {
            setAlerts(prev => prev.some(a => a.id === payload.new.id) ? prev : prev.concat(payload.new));
          })
        .subscribe();

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

    // Sticky-bottom: auto-scroll only when the user hasn't scrolled up.
    const onScroll = (e) => {
      const el = e.currentTarget;
      stickyBottom.current = (el.scrollHeight - el.scrollTop - el.clientHeight) < 40;
    };
    React.useEffect(() => {
      const el = scrollRef.current;
      if (el && stickyBottom.current) {
        el.scrollTop = el.scrollHeight;
      }
    }, [captions, alerts]);

    if (!bookingId) {
      return (
        <div style={{ padding: 32, color: ALM_MUTED, fontSize: 14 }}>
          Pick an active lesson on the left to see the live transcript.
        </div>
      );
    }

    // Merge captions + alerts in chronological order for rendering.
    const merged = []
      .concat(captions.map(c => ({ kind: 'cap',   ts: c.captured_at, item: c })))
      .concat(alerts.map(a   => ({ kind: 'alert', ts: a.created_at,  item: a })))
      .sort((x, y) => (x.ts < y.ts ? -1 : x.ts > y.ts ? 1 : 0));

    return (
      <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
        {error && (
          <div style={{ padding: 8, fontSize: 12, color: ALM_RED, background: 'oklch(96% 0.05 25)' }}>
            {error}
          </div>
        )}
        <div
          ref={scrollRef}
          onScroll={onScroll}
          style={{
            flex: 1,
            overflowY: 'auto',
            padding: 16,
            background: ALM_PANEL,
            fontFamily: "'Plus Jakarta Sans', sans-serif",
            fontSize: 14,
            lineHeight: 1.45,
            color: ALM_INK,
          }}
        >
          {!merged.length && (
            <div style={{ color: ALM_MUTED, fontSize: 13 }}>
              Waiting for captions… (the teacher and/or student need to be in
              the lesson with their Mastery tab open.)
            </div>
          )}
          {merged.map((m) => {
            if (m.kind === 'alert') {
              return (
                <div
                  key={'a' + m.item.id}
                  style={{
                    margin: '10px 0',
                    padding: '10px 12px',
                    borderRadius: 8,
                    background: 'oklch(96% 0.06 25)',
                    border: '1px solid ' + ALM_RED,
                    color: ALM_RED,
                    fontSize: 13,
                  }}
                >
                  <strong>⚠ Flagged: {m.item.flagged_topic}</strong>
                  <div style={{ marginTop: 4, color: ALM_INK, fontStyle: 'italic', fontSize: 12 }}>
                    {(m.item.quote || '').slice(0, 600)}
                  </div>
                  <div style={{ marginTop: 4, fontSize: 10.5, color: ALM_MUTED }}>{fmtClock(m.item.created_at)}</div>
                </div>
              );
            }
            const c = m.item;
            const isTeacher = c.speaker_role === 'instructor';
            return (
              <div key={'c' + c.id} style={{ marginBottom: 6 }}>
                <span style={{
                  fontWeight: 700,
                  color: isTeacher ? ALM_BLUE : ALM_GREEN,
                  marginRight: 8,
                }}>
                  {isTeacher ? 'Teacher' : 'Student'}
                </span>
                <span style={{ fontSize: 11, color: ALM_MUTED, marginRight: 8 }}>{fmtClock(c.captured_at)}</span>
                <span>{c.text}</span>
              </div>
            );
          })}
        </div>
      </div>
    );
  }

  function SettingsPanel() {
    const [enabled, setEnabled]   = React.useState(false);
    const [topics, setTopics]     = React.useState('');
    const [loading, setLoading]   = React.useState(true);
    const [saving, setSaving]     = React.useState(false);
    const [toast, setToast]       = React.useState('');

    const load = React.useCallback(async () => {
      setLoading(true);
      const { data, error } = await window.supa
        .from('admin_settings')
        .select('live_captions_enabled,forbidden_topics')
        .eq('id', 1)
        .single();
      if (!error && data) {
        setEnabled(!!data.live_captions_enabled);
        setTopics(Array.isArray(data.forbidden_topics) ? data.forbidden_topics.join('\n') : '');
      }
      setLoading(false);
    }, []);

    React.useEffect(() => { load(); }, [load]);

    async function toggle() {
      setSaving(true);
      const { error } = await window.supa.rpc('admin_set_live_captions_enabled', { p_enabled: !enabled });
      if (!error) {
        setEnabled(!enabled);
        setToast(!enabled ? 'Captions enabled' : 'Captions disabled');
        setTimeout(() => setToast(''), 2000);
      } else {
        setToast('Failed: ' + error.message);
      }
      setSaving(false);
    }

    async function saveTopics() {
      setSaving(true);
      const list = topics.split('\n').map(s => s.trim()).filter(Boolean);
      const { error } = await window.supa.rpc('admin_set_forbidden_topics', { p_list: list });
      if (!error) {
        setToast('Topics saved');
        setTimeout(() => setToast(''), 2000);
      } else {
        setToast('Failed: ' + error.message);
      }
      setSaving(false);
    }

    if (loading) return <div style={{ padding: 12, color: ALM_MUTED, fontSize: 13 }}>Loading…</div>;

    return (
      <div style={{
        padding: 12,
        borderTop: '1px solid ' + ALM_BORDER,
        background: ALM_BG,
        fontFamily: "'Plus Jakarta Sans', sans-serif",
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 10 }}>
          <button
            type="button"
            onClick={toggle}
            disabled={saving}
            style={{
              padding: '6px 14px',
              borderRadius: 999,
              border: 'none',
              background: enabled ? ALM_GREEN : ALM_MUTED,
              color: '#fff',
              fontSize: 12,
              fontWeight: 600,
              cursor: 'pointer',
            }}
          >
            Captions {enabled ? 'ON' : 'OFF'}
          </button>
          {toast && <span style={{ fontSize: 12, color: ALM_MUTED }}>{toast}</span>}
        </div>
        <label style={{ fontSize: 12, color: ALM_MUTED, display: 'block', marginBottom: 4 }}>
          Forbidden topics (one per line — the AI flags any of these)
        </label>
        <textarea
          value={topics}
          onChange={(e) => setTopics(e.target.value)}
          rows={4}
          style={{
            width: '100%',
            padding: 8,
            border: '1px solid ' + ALM_BORDER,
            borderRadius: 6,
            fontSize: 12,
            fontFamily: 'inherit',
            resize: 'vertical',
          }}
          placeholder="e.g. politics&#10;dating&#10;personal phone numbers"
        />
        <button
          type="button"
          onClick={saveTopics}
          disabled={saving}
          style={{
            marginTop: 8,
            padding: '6px 14px',
            borderRadius: 6,
            border: '1px solid ' + ALM_BORDER,
            background: ALM_BG,
            cursor: 'pointer',
            fontSize: 12,
            fontWeight: 600,
            color: ALM_INK,
          }}
        >
          Save topics
        </button>
      </div>
    );
  }

  const AdminLiveMeetings = () => {
    // Allow deep-linking via ?booking=<id> so the Telegram alert URL can
    // open straight to a specific transcript. The Telegram URL is shaped
    // like /#admin?tab=live-meetings&booking=<id>; since the `?` follows
    // the `#`, those params live in window.location.hash, not .search.
    const initial = (() => {
      try {
        const hash = window.location.hash || '';
        const qix  = hash.indexOf('?');
        if (qix === -1) return null;
        return new URLSearchParams(hash.slice(qix + 1)).get('booking') || null;
      } catch (_) { return null; }
    })();
    const [selectedId, setSelectedId] = React.useState(initial);

    return (
      <div style={{
        display: 'flex',
        height: 'calc(100vh - 110px)',
        background: ALM_BG,
        border: '1px solid ' + ALM_BORDER,
        borderRadius: 10,
        overflow: 'hidden',
      }}>
        <div style={{
          width: 280,
          borderRight: '1px solid ' + ALM_BORDER,
          display: 'flex',
          flexDirection: 'column',
          minWidth: 0,
        }}>
          <div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
            <ActiveBookingsList selectedId={selectedId} onSelect={setSelectedId} />
          </div>
          <SettingsPanel />
        </div>
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
          <TranscriptPane bookingId={selectedId} />
        </div>
      </div>
    );
  };

  window.AdminLiveMeetings = AdminLiveMeetings;
})();
