// schedule.jsx — Day / Week / Month views wired to /api/appointments.

const { useState: useStateS, useMemo: useMemoS, useEffect: useEffectS } = React;

function startOfDay(d) {
  const x = new Date(d);
  x.setHours(0, 0, 0, 0);
  return x;
}
function startOfWeek(d) {
  // ISO-style: week starts on Monday.
  const x = startOfDay(d);
  const day = x.getDay(); // 0..6, Sun..Sat
  const diff = (day === 0 ? -6 : 1 - day);
  x.setDate(x.getDate() + diff);
  return x;
}
function startOfMonth(d) {
  const x = startOfDay(d);
  x.setDate(1);
  return x;
}
function addDays(d, n) {
  const x = new Date(d);
  x.setDate(x.getDate() + n);
  return x;
}
function sameDay(a, b) {
  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
}
function dateInputValue(d) {
  const x = d ? new Date(d) : new Date();
  if (isNaN(x)) return "";
  const yyyy = x.getFullYear();
  const mm = String(x.getMonth() + 1).padStart(2, "0");
  const dd = String(x.getDate()).padStart(2, "0");
  return `${yyyy}-${mm}-${dd}`;
}
function timeInputValue(d) {
  const x = d ? new Date(d) : new Date();
  if (isNaN(x)) return "09:00";
  return `${String(x.getHours()).padStart(2, "0")}:${String(x.getMinutes()).padStart(2, "0")}`;
}
function slotDateFromPointer(baseDay, event, hours) {
  const rect = event.currentTarget.getBoundingClientRect();
  const y = Math.max(0, Math.min(rect.height, event.clientY - rect.top));
  const minutesFromStart = Math.round((y / (64 / 60)) / 15) * 15;
  const startHour = hours && hours.length ? hours[0] : 0;
  const maxMinutes = Math.max(0, ((hours && hours.length ? hours.length : 24) * 60) - 15);
  const clamped = Math.min(maxMinutes, minutesFromStart);
  const next = new Date(baseDay);
  next.setHours(startHour, 0, 0, 0);
  next.setMinutes(next.getMinutes() + clamped);
  return next;
}

function Schedule({ onOpenEncounter, initialPatient }) {
  const store = window.MSStore.useStore();
  const t = window.MSStore.useT();
  const [view, setView] = useStateS("day");
  const [providerFilter, setProviderFilter] = useStateS("all");
  const [cursor, setCursor] = useStateS(() => startOfDay(new Date()));
  const [bookingOpen, setBookingOpen] = useStateS(false);
  const [bookingDraft, setBookingDraft] = useStateS(null);

  const account = store.activeAccount || {};
  const bizStart = account.business_start_hour != null ? account.business_start_hour : 9;
  const bizEnd = account.business_end_hour != null ? account.business_end_hour : 18;
  const HOURS = Array.from({length: 24}, (_, i) => i); // 24-hour calendar
  const providers = (store.providers || []);
  const apps = (store.appointments || []).filter(a => providerFilter === "all" || a.providerId === providerFilter);

  const visibleProviders = providers.filter(p => providerFilter === "all" || p.id === providerFilter);

  function shiftCursor(delta) {
    const next = new Date(cursor);
    if (view === "day") next.setDate(next.getDate() + delta);
    else if (view === "week") next.setDate(next.getDate() + delta * 7);
    else next.setMonth(next.getMonth() + delta);
    if (view === "week") setCursor(startOfWeek(next));
    else if (view === "month") setCursor(startOfMonth(next));
    else setCursor(startOfDay(next));
  }
  function goToday() {
    const today = new Date();
    if (view === "week") setCursor(startOfWeek(today));
    else if (view === "month") setCursor(startOfMonth(today));
    else setCursor(startOfDay(today));
  }

  function setViewKeepAnchor(next) {
    setView(next);
    if (next === "week") setCursor(c => startOfWeek(c));
    else if (next === "month") setCursor(c => startOfMonth(c));
    else setCursor(c => startOfDay(c));
  }
  function openBooking(draft) {
    setBookingDraft(draft || null);
    setBookingOpen(true);
  }

  useEffectS(() => {
    if (!initialPatient) return;
    openBooking({ patient: initialPatient });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [initialPatient && initialPatient.id]);

  // Current header label / sub
  let titleDate, headerSub;
  if (view === "day") {
    titleDate = cursor.toLocaleDateString(window.MSi18n && window.MSi18n.getLang() === "es" ? "es-ES" : "en-US", { month: "long", day: "numeric", year: "numeric" });
    headerSub = cursor.toLocaleDateString(window.MSi18n && window.MSi18n.getLang() === "es" ? "es-ES" : "en-US", { weekday: "long", month: "long", day: "numeric" });
  } else if (view === "week") {
    const end = addDays(cursor, 6);
    titleDate = `${cursor.toLocaleDateString(window.MSi18n && window.MSi18n.getLang() === "es" ? "es-ES" : "en-US", { month: "short", day: "numeric" })} – ${end.toLocaleDateString(window.MSi18n && window.MSi18n.getLang() === "es" ? "es-ES" : "en-US", { month: "short", day: "numeric", year: "numeric" })}`;
    headerSub = t("sched.weekOf", { date: cursor.toLocaleDateString(window.MSi18n && window.MSi18n.getLang() === "es" ? "es-ES" : "en-US", { weekday: "long", month: "long", day: "numeric" }) });
  } else {
    titleDate = cursor.toLocaleDateString(window.MSi18n && window.MSi18n.getLang() === "es" ? "es-ES" : "en-US", { month: "long", year: "numeric" });
    headerSub = titleDate;
  }

  // Day-bucket appointments (used by week + day for counts)
  const dayApps = apps.filter(a => {
    const t = new Date(a.scheduledAt);
    if (isNaN(t)) return false;
    if (view === "day") return sameDay(t, cursor);
    if (view === "week") return t >= cursor && t < addDays(cursor, 7);
    return t >= cursor && t < new Date(cursor.getFullYear(), cursor.getMonth() + 1, 1);
  });

  return (
    <div className="ms-page fade-in">
      <div className="ms-page-head">
        <div>
          <h1 className="h1">{t("sched.title")}</h1>
          <div style={{color: "var(--ink-3)", marginTop: 4, fontSize: 14}}>
            {headerSub} · {t("sched.sub", { n: dayApps.length })}
          </div>
        </div>
        <div className="row" style={{gap: 8}}>
          <div className="ms-segmented">
            <button className={view === "day" ? "is-active" : ""} onClick={() => setViewKeepAnchor("day")}>{t("common.day")}</button>
            <button className={view === "week" ? "is-active" : ""} onClick={() => setViewKeepAnchor("week")}>{t("common.week")}</button>
            <button className={view === "month" ? "is-active" : ""} onClick={() => setViewKeepAnchor("month")}>{t("common.month")}</button>
          </div>
          <button className="btn btn-primary" onClick={() => openBooking(null)}><Icon.Plus size={16}/>{t("sched.book")}</button>
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <div className="row" style={{gap: 8}}>
            <button className="btn btn-icon btn-sm" onClick={() => shiftCursor(-1)}><Icon.ChevronLeft size={14}/></button>
            <button className="btn btn-sm" onClick={goToday}>{t("common.today")}</button>
            <button className="btn btn-icon btn-sm" onClick={() => shiftCursor(1)}><Icon.ChevronRight size={14}/></button>
            <div className="h3" style={{marginLeft: 8}}>{titleDate}</div>
          </div>
          <div className="row" style={{gap: 6}}>
            <div className="ms-segmented">
              <button className={providerFilter === "all" ? "is-active" : ""} onClick={() => setProviderFilter("all")}>{t("common.all")}</button>
              {providers.map(p => (
                <button key={p.id} className={providerFilter === p.id ? "is-active" : ""} onClick={() => setProviderFilter(p.id)}>
                  <span className="avatar avatar-sm" style={{width: 18, height: 18, fontSize: 9, background: p.color + "20", color: p.color}}>{p.initials}</span>
                  {p.name.replace(/^Dr\.?\s+/i, "").split(" ")[0]}
                </button>
              ))}
            </div>
          </div>
        </div>
        <div className="card-body" style={{paddingTop: 0}}>
          {visibleProviders.length === 0 ? (
            <div style={{padding: 48, textAlign: "center", color: "var(--ink-4)"}}>
              <Icon.Patients size={28}/>
              <div style={{marginTop: 10, fontSize: 13}}>{t("sched.noProviders")}</div>
            </div>
          ) : view === "day" ? (
            <DayGrid cursor={cursor} apps={dayApps} providers={visibleProviders} hours={HOURS} bizStart={bizStart} bizEnd={bizEnd} onOpenEncounter={onOpenEncounter} onSlotSelect={openBooking}/>
          ) : view === "week" ? (
            <WeekGrid cursor={cursor} apps={dayApps} providers={visibleProviders} hours={HOURS} bizStart={bizStart} bizEnd={bizEnd} onOpenEncounter={onOpenEncounter} onSlotSelect={openBooking}/>
          ) : (
            <MonthGrid cursor={cursor} apps={dayApps} providers={visibleProviders} onOpenEncounter={onOpenEncounter} onJumpToDay={(d) => { setView("day"); setCursor(startOfDay(d)); }}/>
          )}
        </div>
      </div>
      {bookingOpen && (
        <BookingDrawer
          cursor={cursor}
          providers={providers}
          initialDraft={bookingDraft}
          onClose={() => { setBookingOpen(false); setBookingDraft(null); }}
        />
      )}
    </div>
  );
}

function BookingDrawer({ cursor, providers, initialDraft, onClose }) {
  const store = window.MSStore.useStore();
  const t = window.MSStore.useT();
  const draftDate = initialDraft && initialDraft.scheduledAt ? new Date(initialDraft.scheduledAt) : null;
  const initialDate = dateInputValue(draftDate || cursor || new Date());
  const defaultProvider = (providers && providers[0] && providers[0].id) || "";
  const initialPatient = initialDraft && initialDraft.patient;
  const initialPatientId = initialPatient && initialPatient.id || "";
  const [patientQuery, setPatientQuery] = useStateS(initialPatient ? `${initialPatient.first || ""} ${initialPatient.last || ""}`.trim() : "");
  const [patientOptions, setPatientOptions] = useStateS([]);
  const [patientLoading, setPatientLoading] = useStateS(false);
  const [form, setForm] = useStateS({
    patientMode: initialPatient ? "existing" : "existing",
    selectedPatientId: initialPatientId,
    patientName: initialPatient ? `${initialPatient.first || ""} ${initialPatient.last || ""}`.trim() : "",
    patientPhone: initialPatient && initialPatient.phone || "",
    patientEmail: initialPatient && initialPatient.email || "",
    date: initialDate,
    time: draftDate ? timeInputValue(draftDate) : "09:00",
    duration: "30",
    providerId: (initialDraft && initialDraft.providerId) || defaultProvider,
    reason: "",
    notes: "",
  });
  const [pending, setPending] = useStateS(false);
  const [error, setError] = useStateS("");
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  function applySelectedPatient(patientId) {
    const p = patientOptions.find(p => (p.id || p.name) === patientId);
    const name = p ? (p.name || `${p.first || ""} ${p.last || ""}`.trim()) : "";
    setForm(f => ({
      ...f,
      selectedPatientId: patientId,
      patientName: name,
      patientPhone: p ? (p.phone || "") : "",
      patientEmail: p ? (p.email || "") : "",
    }));
    if (name) setPatientQuery(name);
  }

  useEffectS(() => {
    let cancelled = false;
    setPatientLoading(true);
    store.searchCalendarPatients(patientQuery).then(rows => {
      if (cancelled) return;
      setPatientOptions(Array.isArray(rows) ? rows : []);
    }).catch(() => {
      if (!cancelled) setPatientOptions([]);
    }).finally(() => {
      if (!cancelled) setPatientLoading(false);
    });
    return () => { cancelled = true; };
  }, [patientQuery, store.version]);

  useEffectS(() => {
    if (!form.providerId && providers && providers[0]) {
      set("providerId", providers[0].id);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [providers && providers.length]);

  async function submit() {
    if (pending) return;
    const patientName = form.patientName.trim();
    if (!patientName) {
      setError(t("sched.errors.patient"));
      return;
    }
    if (!form.date || !form.time) {
      setError(t("sched.errors.time"));
      return;
    }
    setPending(true);
    setError("");
    try {
      const scheduledAt = new Date(`${form.date}T${form.time}:00`).toISOString();
      const assignedProvider = form.providerId || (providers && providers[0] && providers[0].id) || "";
      await store.createAppointment({
        patient_name: patientName,
        patient_phone: form.patientPhone.trim(),
        patient_email: form.patientEmail.trim(),
        doctor_id: assignedProvider,
        scheduled_at: scheduledAt,
        duration_minutes: Number(form.duration) || 30,
        reason: form.reason.trim(),
        notes: form.notes.trim(),
        status: "confirmed",
      });
      onClose();
    } catch (e) {
      setError(e && e.message || t("sched.errors.create"));
    } finally {
      setPending(false);
    }
  }

  return (
    <div className="ms-edit-overlay" onClick={e => e.target === e.currentTarget && onClose()}>
      <div className="ms-book-modal slide-up">
        <div className="ms-edit-head">
          <span style={{fontWeight: 600, fontSize: 15}}>{t("sched.bookTitle")}</span>
          <button className="btn btn-icon btn-sm" onClick={onClose}><Icon.X size={14}/></button>
        </div>
        <div className="ms-edit-body scroll" style={{padding: 20}}>
          <div className="col" style={{gap: 12}}>
            <div>
              <label className="field-label">{t("sched.patientName")}</label>
              <div className="ms-segmented" style={{marginBottom: 10, width: "fit-content"}}>
                <button type="button" className={form.patientMode === "existing" ? "is-active" : ""} onClick={() => set("patientMode", "existing")}>Existing</button>
                <button type="button" className={form.patientMode === "new" ? "is-active" : ""} onClick={() => setForm(f => ({ ...f, patientMode: "new", selectedPatientId: "", patientName: "", patientPhone: "", patientEmail: "" }))}>New patient</button>
              </div>
              {form.patientMode === "existing" ? (
                <div className="col" style={{gap: 8}}>
                  <input className="input" value={patientQuery} onChange={e => { setPatientQuery(e.target.value); setForm(f => ({...f, selectedPatientId: "", patientName: ""})); }} placeholder="Search patients from database"/>
                  <select className="input" value={form.selectedPatientId} onChange={e => applySelectedPatient(e.target.value)}>
                    <option value="">{patientLoading ? "Loading patients..." : "Select a patient"}</option>
                    {patientOptions.map(p => (
                      <option key={p.id || p.name} value={p.id || p.name}>{p.name || `${p.first || ""} ${p.last || ""}`.trim() || p.id}</option>
                    ))}
                  </select>
                  {!patientLoading && patientQuery.trim() && patientOptions.length === 0 && (
                    <button type="button" className="btn btn-sm" onClick={() => setForm(f => ({...f, patientMode: "new", selectedPatientId: "", patientName: patientQuery.trim()}))}>
                      <Icon.Plus size={13}/>Add "{patientQuery.trim()}" as new patient
                    </button>
                  )}
                </div>
              ) : (
                <input className="input" value={form.patientName} onChange={e => set("patientName", e.target.value)} placeholder={t("sched.patientNamePh")}/>
              )}
            </div>
            <div className="ms-form-grid-2">
              <div>
                <label className="field-label">{t("tx.phone")}</label>
                <input className="input" value={form.patientPhone} onChange={e => set("patientPhone", e.target.value)}/>
              </div>
              <div>
                <label className="field-label">{t("auth.email")}</label>
                <input className="input" type="email" value={form.patientEmail} onChange={e => set("patientEmail", e.target.value)}/>
              </div>
            </div>
            <div className="ms-form-grid-2">
              <div>
                <label className="field-label">{t("sched.date")}</label>
                <input className="input" type="date" value={form.date} onChange={e => set("date", e.target.value)}/>
              </div>
              <div>
                <label className="field-label">{t("sched.time")}</label>
                <input className="input" type="time" value={form.time} onChange={e => set("time", e.target.value)}/>
              </div>
            </div>
            <div className="ms-form-grid-2">
              <div>
                <label className="field-label">{t("sched.duration")}</label>
                <select className="input" value={form.duration} onChange={e => set("duration", e.target.value)}>
                  <option value="15">15</option>
                  <option value="30">30</option>
                  <option value="45">45</option>
                  <option value="60">60</option>
                </select>
              </div>
              <div>
                <label className="field-label">{t("sched.provider")}</label>
                <select className="input" value={form.providerId} onChange={e => set("providerId", e.target.value)}>
                  {providers.length === 0 && <option value="">{t("sched.providerDefault")}</option>}
                  {providers.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
                </select>
              </div>
            </div>
            <div>
              <label className="field-label">{t("sched.reason")}</label>
              <input className="input" value={form.reason} onChange={e => set("reason", e.target.value)} placeholder={t("sched.reasonPh")}/>
            </div>
            <div>
              <label className="field-label">{t("sched.notes")}</label>
              <textarea className="input" style={{minHeight: 92, resize: "vertical"}} value={form.notes} onChange={e => set("notes", e.target.value)}/>
            </div>
            {error && <div style={{padding: "10px 12px", borderRadius: 10, color: "var(--accent-danger)", fontSize: 12.5}}>{error}</div>}
          </div>
        </div>
        <div className="ms-edit-foot">
          <button className="btn" onClick={onClose}>{t("common.cancel")}</button>
          <button className="btn btn-primary" disabled={pending} onClick={submit} style={{opacity: pending ? 0.7 : 1}}>
            <Icon.Check size={14}/>{pending ? t("common.saving") : t("sched.book")}
          </button>
        </div>
      </div>
    </div>
  );
}

// ── Day view ────────────────────────────────────────────────────────────────

function timeToY(date) {
  const d = new Date(date);
  const minsFromMidnight = d.getHours() * 60 + d.getMinutes();
  return Math.max(0, minsFromMidnight) * (64 / 60);
}

function DayGrid({ cursor, apps, providers, hours, bizStart, bizEnd, onOpenEncounter, onSlotSelect }) {
  const t = window.MSStore.useT();
  const isToday = sameDay(startOfDay(new Date()), cursor);
  const bodyRef = React.useRef(null);
  React.useEffect(() => {
    if (bodyRef.current) {
      // Scroll to 1 hour before business start
      const scrollTo = Math.max(0, (bizStart - 1)) * 64;
      bodyRef.current.scrollTop = scrollTo;
    }
  }, [bizStart]);
  return (
    <div className="ms-cal-grid">
      <div className="ms-cal-cols">
        {providers.map(p => (
          <div key={p.id} className="ms-cal-colhead">
            <span className="avatar avatar-sm" style={{background: p.color + "20", color: p.color, borderColor: p.color + "40"}}>{p.initials}</span>
            <div>
              <div style={{fontWeight: 600, fontSize: 13}}>{p.name}</div>
              <div style={{fontSize: 11.5, color: "var(--ink-4)"}}>{p.role}</div>
            </div>
          </div>
        ))}
      </div>
      <div className="ms-cal-body scroll" ref={bodyRef}>
        <div className="ms-cal-times">
          {hours.map(h => (
            <div key={h} className={"ms-cal-hour" + (h < bizStart || h >= bizEnd ? " is-off-hours" : "")}>
              <span>{h === 0 ? "12 AM" : h < 12 ? h + " AM" : h === 12 ? "12 PM" : (h - 12) + " PM"}</span>
            </div>
          ))}
        </div>
        <div className="ms-cal-tracks">
          {providers.map(p => (
            <div
              key={p.id}
              className="ms-cal-track is-bookable"
              onClick={(e) => onSlotSelect && onSlotSelect({ scheduledAt: slotDateFromPointer(cursor, e, hours), providerId: p.id })}
            >
              {hours.map(h => <div key={h} className={"ms-cal-slot" + (h < bizStart || h >= bizEnd ? " is-off-hours" : "")}/>)}
              {isToday && <div className="ms-cal-now" style={{top: timeToY(new Date())}}><span/></div>}
              {apps.filter(a => a.providerId === p.id || (!a.providerId && p.id === providers[0]?.id)).map(a => {
                const top = timeToY(a.scheduledAt);
                const height = (a.duration || 30) * (64 / 60) - 2;
                const isCurrent = a.status === "in-room";
                return (
                  <button key={a.id} className={"ms-cal-evt" + (isCurrent ? " is-current" : "")} style={{top, height, borderColor: p.color + "55", background: p.color + "12"}} onClick={(e) => { e.stopPropagation(); onOpenEncounter(a.id); }}>
                    <div style={{fontWeight: 600, fontSize: 12.5, color: "var(--ink)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}}>{a.patientName || t("sched.unknown")}</div>
                    <div style={{fontSize: 11.5, color: "var(--ink-3)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}}>{a.type} · {a.duration}m</div>
                    {height > 60 && a.reason && a.reason !== a.type && <div style={{fontSize: 11, color: "var(--ink-4)", marginTop: 2}}>{a.reason}</div>}
                  </button>
                );
              })}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ── Week view ────────────────────────────────────────────────────────────────

function WeekGrid({ cursor, apps, providers, hours, bizStart, bizEnd, onOpenEncounter, onSlotSelect }) {
  const t = window.MSStore.useT();
  const days = Array.from({length: 7}, (_, i) => addDays(cursor, i));
  const today = startOfDay(new Date());
  const lang = (window.MSi18n && window.MSi18n.getLang()) || "en";
  const localeTag = lang === "es" ? "es-ES" : "en-US";
  const provById = {};
  for (const p of providers) provById[p.id] = p;
  const weekBodyRef = React.useRef(null);
  React.useEffect(() => {
    if (weekBodyRef.current) {
      weekBodyRef.current.scrollTop = Math.max(0, (bizStart - 1)) * 64;
    }
  }, [bizStart]);

  return (
    <div className="ms-week-grid">
      {/* Weekday header */}
      <div className="ms-week-head">
        <div className="ms-week-head-spacer"/>
        {days.map(d => {
          const isToday = sameDay(d, today);
          return (
            <div key={d.toISOString()} className={"ms-week-day-head" + (isToday ? " is-today" : "")}>
              <div style={{fontSize: 11, color: "var(--ink-4)", textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 600}}>
                {d.toLocaleDateString(localeTag, { weekday: "short" })}
              </div>
              <div style={{fontSize: 18, fontWeight: 600, marginTop: 2, color: isToday ? "var(--primary)" : "var(--ink)"}}>
                {d.getDate()}
              </div>
            </div>
          );
        })}
      </div>

      {/* Body: time gutter + 7 day columns */}
      <div className="ms-week-body scroll" ref={weekBodyRef}>
        <div className="ms-cal-times">
          {hours.map(h => (
            <div key={h} className={"ms-cal-hour" + (h < bizStart || h >= bizEnd ? " is-off-hours" : "")}>
              <span>{h === 0 ? "12 AM" : h < 12 ? h + " AM" : h === 12 ? "12 PM" : (h - 12) + " PM"}</span>
            </div>
          ))}
        </div>
        <div className="ms-week-cols">
          {days.map(d => {
            const dayStart = startOfDay(d);
            const dayEnd = addDays(dayStart, 1);
            const dayApps = apps.filter(a => {
              const t = new Date(a.scheduledAt);
              return !isNaN(t) && t >= dayStart && t < dayEnd;
            });
            const isToday = sameDay(d, today);
            return (
              <div
                key={d.toISOString()}
                className="ms-week-col is-bookable"
                onClick={(e) => onSlotSelect && onSlotSelect({ scheduledAt: slotDateFromPointer(dayStart, e, hours) })}
              >
                {hours.map(h => <div key={h} className={"ms-cal-slot" + (h < bizStart || h >= bizEnd ? " is-off-hours" : "")}/>)}
                {isToday && <div className="ms-cal-now" style={{top: timeToY(new Date())}}><span/></div>}
                {dayApps.map(a => {
                  const top = timeToY(a.scheduledAt);
                  const height = Math.max(22, (a.duration || 30) * (64 / 60) - 2);
                  const provider = provById[a.providerId];
                  const color = (provider && provider.color) || "var(--primary)";
                  return (
                    <button key={a.id} className="ms-week-evt" style={{top, height, borderColor: color + "55", background: color + "16"}} onClick={(e) => { e.stopPropagation(); onOpenEncounter(a.id); }}>
                      <div style={{fontWeight: 600, fontSize: 11.5, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}}>{a.patientName || t("sched.unknown")}</div>
                      <div style={{fontSize: 10.5, color: "var(--ink-3)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}}>{a.time} · {a.type}</div>
                    </button>
                  );
                })}
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// ── Month view ──────────────────────────────────────────────────────────────

function MonthGrid({ cursor, apps, providers, onOpenEncounter, onJumpToDay }) {
  const t = window.MSStore.useT();
  const lang = (window.MSi18n && window.MSi18n.getLang()) || "en";
  const localeTag = lang === "es" ? "es-ES" : "en-US";
  // 6 rows * 7 columns = 42 cells starting from the Monday of the week that
  // contains the 1st of the month.
  const monthStart = startOfMonth(cursor);
  const gridStart = startOfWeek(monthStart);
  const today = startOfDay(new Date());
  const cells = Array.from({length: 42}, (_, i) => addDays(gridStart, i));

  // Bucket appointments by ISO date.
  const buckets = {};
  for (const a of apps) {
    const t = new Date(a.scheduledAt);
    if (isNaN(t)) continue;
    const key = startOfDay(t).toISOString().slice(0, 10);
    (buckets[key] ||= []).push(a);
  }

  const provById = {};
  for (const p of providers) provById[p.id] = p;

  // Build localized weekday short labels (Mon-first), via the browser locale.
  const headerLabels = (() => {
    const out = [];
    const ref = startOfWeek(new Date());
    for (let i = 0; i < 7; i++) {
      out.push(addDays(ref, i).toLocaleDateString(localeTag, { weekday: "short" }));
    }
    return out;
  })();

  return (
    <div className="ms-month-grid">
      <div className="ms-month-head">
        {headerLabels.map(l => <div key={l} className="ms-month-day-head">{l}</div>)}
      </div>
      <div className="ms-month-body">
        {cells.map(d => {
          const inMonth = d.getMonth() === monthStart.getMonth();
          const isToday = sameDay(d, today);
          const key = d.toISOString().slice(0, 10);
          const dayApps = (buckets[key] || []).slice().sort((a, b) => new Date(a.scheduledAt) - new Date(b.scheduledAt));
          return (
            <button
              key={key}
              className={"ms-month-cell" + (inMonth ? "" : " is-out") + (isToday ? " is-today" : "")}
              onClick={() => onJumpToDay && onJumpToDay(d)}
              title={t("sched.sub", { n: dayApps.length })}
            >
              <div className="ms-month-cell-head">
                <span className={"ms-month-cell-num" + (isToday ? " is-today" : "")}>{d.getDate()}</span>
                {dayApps.length > 0 && <span className="ms-month-cell-count">{dayApps.length}</span>}
              </div>
              <div className="ms-month-cell-list">
                {dayApps.slice(0, 3).map(a => {
                  const provider = provById[a.providerId];
                  const color = (provider && provider.color) || "var(--primary)";
                  return (
                    <div
                      key={a.id}
                      className="ms-month-evt"
                      style={{borderLeftColor: color, background: color + "0d"}}
                      onClick={(e) => { e.stopPropagation(); onOpenEncounter(a.id); }}
                    >
                      <span className="ms-month-evt-time mono">{a.time}</span>
                      <span className="ms-month-evt-name">{a.patientName || t("sched.unknown")}</span>
                    </div>
                  );
                })}
                {dayApps.length > 3 && (
                  <div className="ms-month-more">{t("sched.more", { n: dayApps.length - 3 })}</div>
                )}
              </div>
            </button>
          );
        })}
      </div>
    </div>
  );
}

window.Schedule = Schedule;
