// src/features/instructor/ui/instructor-booking-page.jsx
//
// The Calendly-style booking page used in two contexts:
//   - Student initiates a trial / lesson with a specific instructor.
//   - Instructor reschedules an existing booking.
//
// Was lines 377-886 of instructor-dashboard.jsx (when that file was
// 1,781 LOC). Extracted intact to bring instructor-dashboard.jsx
// closer to the <400 LOC budget. The other 8 components in that file
// will follow in subsequent passes.
//
// Public: window.InstructorBookingPage.

const InstructorBookingPage = ({ instructor, assignment, asStudent, onBack, onBook, reschedule }) => {
  const data = window.useDashboardData();
  // Mobile breakpoint at 720 — below this the calendar + time-slot panes
  // need to stack vertically instead of sitting side-by-side. The min-width
  // of 280+220 cols was overflowing 375px iPhone viewports.
  const isMobile = window.useIsMobile(720);
  const [monthOffset, setMonthOffset] = React.useState(0); // 0 = this month, 1 = next…
  const [selectedDateKey, setSelectedDateKey] = React.useState(null); // 'YYYY-MM-DD' in viewer's TZ
  const [selected,   setSelected]   = React.useState(null); // { absIso, dayLabel, hourLabel }
  const [duration,   setDuration]   = React.useState(60);
  const [form,       setForm]       = React.useState({ name:'', phone:'', email:'', notes:'' });
  const [submitted,  setSubmitted]  = React.useState(false);
  const [busy,       setBusy]       = React.useState(false);
  const [error,      setError]      = React.useState('');
  // ── Calendly-style timezone state ───────────────────────────────────
  // viewerTz: which zone the student is THINKING in. Drives both the
  // calendar's date labels and each slot button's hour. Initialized to
  // the browser's detected zone; for signed-in students we override it
  // from profiles.timezone in an effect below and persist changes back.
  // teacherTz: the zone the teacher's availability rows are expressed in
  // (loaded from the teacher's profiles.timezone). When unknown (eg. an
  // old teacher who hasn't picked a TZ yet) we fall back to viewerTz
  // which preserves the legacy behaviour where everything was browser-
  // local — that way nothing regresses for existing data.
  const [viewerTz, setViewerTz] = React.useState(() => window.Calendar.time.detect());
  const [teacherTz, setTeacherTz] = React.useState(null);

  // Pull this instructor's availability + their existing bookings.
  // Public visitors won't have these in DASHBOARD_DATA, so fetch on-mount.
  //
  // CRITICAL: subscribe to Realtime AS WELL so that if the teacher updates
  // their availability while a student has this page open, the new slots
  // appear without a refresh. Same for new bookings — they should grey out
  // immediately. This was Bug 1 found by the test bot (cross-user updates
  // didn't propagate to the booking page, even though logic.js was wired
  // for Realtime — the page just had its own local state nobody nudged).
  const [availMap, setAvailMap] = React.useState({ 0:[],1:[],2:[],3:[],4:[],5:[],6:[] });
  const [existing, setExisting] = React.useState([]);
  // Availability exceptions for THIS instructor (v37). The booking slot
  // computation below excludes any moment that intersects an exception.
  const [instructorExceptions, setInstructorExceptions] = React.useState([]);
  // Exceptions the signed-in student has set for themselves — also exclude.
  const [studentExceptions, setStudentExceptions] = React.useState([]);
  React.useEffect(() => {
    let cancelled = false;
    const refetch = async () => {
      const [{ data: avRows }, { data: bkRows }, profR, exR] = await Promise.all([
        window.supa.from('availability_slots').select('*').eq('instructor_id', instructor.id),
        window.supa.from('bookings').select('scheduled_at, status').eq('instructor_id', instructor.id).neq('status', 'cancelled'),
        // The instructors row's id IS the auth user_id (createInstructor sets
        // id == user_id). profiles.timezone is the canonical place we store
        // each user's IANA zone — set by the avail modals shipped in earlier
        // pushes. Fall back to the viewer's zone for teachers who haven't
        // picked one yet so legacy data still books at the expected wall-clock.
        window.supa.from('profiles').select('timezone').eq('id', instructor.id).maybeSingle(),
        // Instructor's own time-off exceptions (anonymously readable per v37 RLS).
        window.supa.from('availability_exceptions').select('*')
          .eq('instructor_id', instructor.id)
          .gte('end_at', new Date().toISOString()),
      ]);
      if (cancelled) return;
      setAvailMap(availabilityToMockMap((avRows || []).map(r => ({ dayOfWeek: Number(r.day_of_week), startTime: r.start_time, endTime: r.end_time }))));
      setExisting(bkRows || []);
      setTeacherTz(profR?.data?.timezone || window.Calendar.time.detect());
      setInstructorExceptions(exR?.data || []);
    };
    refetch();

    // Realtime: bump this page whenever the relevant rows change.
    // We re-fetch (cheap, ~10 rows) rather than mutating local state in
    // place — simpler and matches what page-reload does.
    const ch = window.supa
      .channel(`book-${instructor.id}-${Date.now()}`)
      .on('postgres_changes',
          { event: '*', schema: 'public', table: 'availability_slots', filter: `instructor_id=eq.${instructor.id}` },
          () => refetch())
      .on('postgres_changes',
          { event: '*', schema: 'public', table: 'bookings', filter: `instructor_id=eq.${instructor.id}` },
          () => refetch())
      .on('postgres_changes',
          { event: '*', schema: 'public', table: 'availability_exceptions', filter: `instructor_id=eq.${instructor.id}` },
          () => refetch())
      .subscribe((status) => {
        // Force one refetch as soon as we're subscribed — closes the race
        // where a row was inserted during the channel-connecting window.
        if (status === 'SUBSCRIBED') refetch();
      });

    return () => {
      cancelled = true;
      try { window.supa.removeChannel(ch); } catch (e) {}
    };
  }, [instructor.id]);

  // Load the signed-in student's own exceptions (one fetch — RLS lets each
  // student read their own rows; the anon viewer just gets empty).
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const { data: u } = await window.supa.auth.getUser();
        const uid = u?.user?.id;
        if (!uid) { setStudentExceptions([]); return; }
        const { data } = await window.supa.from('availability_exceptions')
          .select('*').eq('student_id', uid)
          .gte('end_at', new Date().toISOString());
        if (!cancelled) setStudentExceptions(data || []);
      } catch (e) { /* leave empty */ }
    })();
    return () => { cancelled = true; };
  }, [asStudent]);

  // viewerTz = current browser zone, Calendly-style. We deliberately do NOT
  // load profiles.timezone here — that column is the ANCHOR for the user's
  // own availability (set by whoever saved their hours), not their
  // preferred viewing zone. Confusing the two destroys the projection: if
  // the student's profile.timezone == admin-set "Beirut" and we used that
  // as viewer, the student would see un-translated Beirut hours. The
  // dropdown still lets them override for this view; the override is
  // session-only, not persisted.
  const onChangeViewerTz = React.useCallback((newTz) => {
    setViewerTz(newTz);
    setSelected(null); // the previously-picked slot's labels are now stale
  }, []);

  // ── TZ-aware slot computation ────────────────────────────────────────
  // Project the teacher's wall-clock availability map (Mon=0..Sun=6 keyed,
  // values = arrays of hour ints in the TEACHER's TZ) onto an array of
  // absolute UTC moments for the next ~92 days. Doing this once and then
  // bucketing by viewerTz lets the dropdown re-render instantly without
  // hitting Supabase again.
  const absoluteSlots = React.useMemo(() => {
    if (!teacherTz || !availMap) return [];
    const T = window.Calendar.time;
    const out = [];
    const nowMs = Date.now();
    // Start from "today in teacher's TZ" so we don't accidentally drop or
    // duplicate a day across the UTC boundary.
    const today = T.partsIn(new Date(), teacherTz);
    let { year, month, day } = today;
    // Default duration for slot conflict check. Booking-page lets the user
    // pick 30/45/60/90 later, but for the "is this slot blocked at all" gate
    // we use the default hour cell width (60 min) — finer-grained durations
    // are filtered at the duration-picker level.
    const blockDur = 60;
    for (let dOff = 0; dOff < 92; dOff++) {
      // Day-of-week (Mon=0..Sun=6) for this calendar date. A calendar date's
      // DoW is the same no matter the zone, so we can use a UTC anchor.
      const utcDow = new Date(Date.UTC(year, month - 1, day)).getUTCDay();
      const md = (utcDow + 6) % 7;
      for (const h of (availMap[md] || [])) {
        const m = T.makeDate(year, month, day, h, 0, teacherTz);
        if (m.getTime() < nowMs) continue;
        const booked = existing.some(b => {
          const bd = new Date(b.scheduled_at);
          return Math.abs(bd.getTime() - m.getTime()) < 60_000; // 1-minute slop
        });
        if (booked) continue;
        // Drop slots that intersect any active availability exception —
        // either the instructor's own (e.g. "vacation Aug 1–7") or the
        // signed-in student's (e.g. "can't this Monday").
        if (window.Calendar.shape.isSlotBlocked && window.Calendar.shape.isSlotBlocked(instructorExceptions, m, blockDur)) continue;
        if (window.Calendar.shape.isSlotBlocked && window.Calendar.shape.isSlotBlocked(studentExceptions,    m, blockDur)) continue;
        out.push(m);
      }
      // Walk to next calendar day in teacher's TZ.
      day += 1;
      const dim = new Date(Date.UTC(year, month, 0)).getUTCDate();
      if (day > dim) { day = 1; month += 1; if (month > 12) { month = 1; year += 1; } }
    }
    return out;
  }, [availMap, teacherTz, existing, instructorExceptions, studentExceptions]);

  // Bucket the absolute moments by viewer-TZ YYYY-MM-DD so the calendar can
  // light up dates + the right column can list hours for the chosen day.
  // Re-runs whenever viewerTz flips (the dropdown switch).
  const slotsByViewerDate = React.useMemo(() => {
    const T = window.Calendar.time;
    const map = {};
    for (const m of absoluteSlots) {
      const k = T.dateKey(m, viewerTz);
      (map[k] = map[k] || []).push(m);
    }
    for (const k of Object.keys(map)) map[k].sort((a, b) => a - b);
    return map;
  }, [absoluteSlots, viewerTz]);

  // ── Calendar grid expressed in viewer-TZ wall-clock dates ────────────
  // Used to be JS Date cells (browser-local midnight); now {y,m,d,key}
  // so flipping the TZ dropdown actually shifts which dates appear.
  const monthCursor = (() => {
    const T = window.Calendar.time;
    const nowParts = T.partsIn(new Date(), viewerTz);
    let year = nowParts.year;
    let month = nowParts.month + monthOffset;
    while (month > 12) { month -= 12; year += 1; }
    while (month < 1)  { month += 12; year -= 1; }
    return { year, month };
  })();
  const monthLabel = `${INST_MONTHS[monthCursor.month - 1]} ${monthCursor.year}`;
  const monthGrid = (() => {
    const { year, month } = monthCursor;
    const firstDow = (new Date(Date.UTC(year, month - 1, 1)).getUTCDay() + 6) % 7;
    const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate();
    const cells = [];
    for (let i = 0; i < firstDow; i++) cells.push(null);
    for (let d = 1; d <= daysInMonth; d++) {
      cells.push({
        year, month, day: d,
        key: `${year}-${String(month).padStart(2,'0')}-${String(d).padStart(2,'0')}`,
      });
    }
    while (cells.length % 7 !== 0) cells.push(null);
    return cells;
  })();

  // Today in viewer's TZ — for past-date greying.
  const todayParts = window.Calendar.time.partsIn(new Date(), viewerTz);
  const todayKey = `${todayParts.year}-${String(todayParts.month).padStart(2,'0')}-${String(todayParts.day).padStart(2,'0')}`;

  const slotsForDate = selectedDateKey ? (slotsByViewerDate[selectedDateKey] || []) : [];
  const selectedDateLabel = selectedDateKey ? (() => {
    const [y, m, d] = selectedDateKey.split('-').map(Number);
    // Anchor at noon UTC and ask toLocaleDateString to format in viewer's TZ
    // — anchoring at midnight could cross-day under negative offsets.
    return new Date(Date.UTC(y, m - 1, d, 12)).toLocaleDateString(undefined, {
      timeZone: viewerTz, weekday: 'long', month: 'long', day: 'numeric',
    });
  })() : '';

  const handleBook = async (e) => {
    e.preventDefault();
    if (!selected) return;
    setBusy(true); setError('');
    try {
      // selected.absIso is already an absolute UTC ISO — no local-TZ math
      // needed. This is the whole point of the refactor: by the time the
      // user clicks a slot, we've already resolved its absolute moment.
      const scheduledAt = selected.absIso;
      if (reschedule) {
        await reschedule.onSubmit(scheduledAt);
      } else if (asStudent) {
        await window.MASTERY.bookLesson({
          instructorId: instructor.id,
          scheduledAt,
          durationMinutes: 60,
          message: null,
        });
      } else {
        // Public visitor — no auth. Admin follows up by phone/email.
      }
      setSubmitted(true);
      if (onBook) onBook();
    } catch (e2) {
      setError(e2.message || 'Could not book.');
    } finally {
      setBusy(false);
    }
  };

  if (submitted) return (
    <div style={{ minHeight:'100vh', display:'flex', alignItems:'center', justifyContent:'center', background:'oklch(98.5% 0.007 60)', fontFamily:"'Plus Jakarta Sans', sans-serif", textAlign:'center' }}>
      <div style={{ maxWidth:340 }}>
        <div style={{ fontSize:44, marginBottom:16 }}>✅</div>
        <div style={{ fontWeight:700, fontSize:18, color:'oklch(18% 0.03 265)', marginBottom:8 }}>
          {reschedule ? 'Reschedule requested!' : (asStudent ? 'Lesson booked!' : 'Request sent!')}
        </div>
        <div style={{ fontSize:14, color:'oklch(55% 0.03 265)', lineHeight:1.7 }}>
          {reschedule
            ? <>Your instructor will confirm the new time shortly.</>
            : asStudent
              ? <>Your lesson with <strong>{instructor.name}</strong> is confirmed.</>
              : <>We'll confirm your trial lesson with <strong>{instructor.name}</strong> shortly.</>}
        </div>
        <button onClick={onBack} style={{ marginTop:28, background:'oklch(22% 0.06 265)', color:'#fff', border:'none', borderRadius:9, padding:'11px 28px', fontWeight:600, fontSize:13, cursor:'pointer', fontFamily:"'Plus Jakarta Sans', sans-serif" }}>Done</button>
      </div>
    </div>
  );

  // What rate to show (if asStudent, look up from assignment)
  const studentRateForDuration = assignment?.rates?.student?.[duration];

  // Block past months & previous-month nav button when we're already on the
  // current month — there's nothing to book in the past. Both sides are
  // computed in viewer-TZ now so flipping the dropdown can't accidentally
  // unlock past-month navigation.
  const canGoPrev = (
    monthCursor.year > todayParts.year ||
    (monthCursor.year === todayParts.year && monthCursor.month > todayParts.month)
  );

  return (
    <div style={{ minHeight:'100vh', background:'oklch(98.5% 0.007 60)', fontFamily:"'Plus Jakarta Sans', sans-serif", paddingBottom:80 }}>
      <div style={{ background:'#fff', borderBottom:'1px solid oklch(92% 0.01 265)', padding: isMobile ? '0 16px' : '0 32px', height:54, display:'flex', alignItems:'center', justifyContent:'space-between' }}>
        <div style={{ fontFamily:"'Cormorant Garamond', serif", fontSize:18, fontWeight:700, letterSpacing:'0.12em', color:'oklch(22% 0.06 265)' }}>COACHING</div>
        <button onClick={onBack} style={{ background:'none', border:'none', fontSize:13, color:'oklch(58% 0.03 265)', cursor:'pointer', fontFamily:"'Plus Jakarta Sans', sans-serif" }}>← Back</button>
      </div>

      <div style={{ maxWidth:720, margin: isMobile ? '24px auto 0' : '48px auto 0', padding: isMobile ? '0 14px' : '0 24px' }}>
        <div style={{ fontSize:11, fontWeight:700, color:'oklch(72% 0.17 80)', textTransform:'uppercase', letterSpacing:'0.1em', marginBottom:6 }}>
          {reschedule ? 'Reschedule your lesson' : 'Book a lesson'}
        </div>
        <div style={{ fontFamily:"'Cormorant Garamond', serif", fontSize:30, fontWeight:600, color:'oklch(18% 0.03 265)', marginBottom:2 }}>{instructor.name}</div>
        <div style={{ fontSize:13, color:'oklch(55% 0.03 265)', marginBottom: reschedule ? 14 : 14 }}>
          {instructor.subject}
          {/* Hide rate from the student — admin-negotiated, not surfaced here.
              Public visitors (asStudent === false) still see the public rate. */}
          {!asStudent && !reschedule && studentRateForDuration != null && <> · ${studentRateForDuration}/{duration}min</>}
        </div>
        {/* Interactive TZ dropdown — Calendly-style. Auto-detected on mount,
            override sticks for the session (and persists to profiles.timezone
            for signed-in students via onChangeViewerTz). Changing it
            re-renders the calendar + slot list against the new zone. */}
        <div style={{ marginBottom: reschedule ? 14 : 24 }}>
          <window.TimezoneBadge value={viewerTz} onChange={onChangeViewerTz} />
        </div>
        {reschedule && reschedule.originalIso && (() => {
          const od = new Date(reschedule.originalIso);
          const odLabel = od.toLocaleDateString(undefined, {
            timeZone: viewerTz, weekday: 'long', month: 'long', day: 'numeric',
          });
          const odHour = window.Calendar.time.hourIn(od, viewerTz);
          return (
            <div style={{ background:'oklch(96.5% 0.07 80)', borderRadius:10, padding:'12px 16px', marginBottom:20, fontSize:13, color:'oklch(36% 0.13 75)', borderLeft:'3px solid oklch(72% 0.17 80)' }}>
              <strong>Current:</strong> {odLabel} at {window.Calendar.time.hourLabel(odHour)} · Pick a new time below.
            </div>
          );
        })()}

        {/* ── Calendar + time picker ──────────────────────────────────
            The whole month-grid + time-slot column lives in
            instructor-booking-calendar.jsx so this file stays under
            the 400 LOC budget. */}
        <window.BookingCalendarPicker
          isMobile={isMobile}
          canGoPrev={canGoPrev}
          setMonthOffset={setMonthOffset}
          monthLabel={monthLabel}
          monthGrid={monthGrid}
          todayKey={todayKey}
          slotsByViewerDate={slotsByViewerDate}
          selectedDateKey={selectedDateKey}
          setSelectedDateKey={setSelectedDateKey}
          setSelected={setSelected}
          selected={selected}
          slotsForDate={slotsForDate}
          viewerTz={viewerTz}
          selectedDateLabel={selectedDateLabel}
        />
        {!selectedDateKey && (
          <div style={{ background:'oklch(98% 0.008 60)', borderRadius:12, padding:'14px 18px', fontSize:13, color:'oklch(50% 0.04 265)', marginBottom:20 }}>
            👋 Pick any green date above to see open times.
          </div>
        )}

        {selected && (
          <window.BookingConfirmForm
            selected={selected}
            asStudent={asStudent}
            reschedule={reschedule}
            busy={busy}
            error={error}
            form={form}
            setForm={setForm}
            handleBook={handleBook}
          />
        )}
      </div>
    </div>
  );
};

window.InstructorBookingPage = InstructorBookingPage;
