// My Appointments — fresh state backed by the current patient booking
const APPT_SLOTS = {
  Morning: ['9:00a','9:30a','10:00a','10:30a','11:00a','11:30a'],
  Afternoon: ['12:00p','12:30p','1:00p','1:30p','2:00p','2:30p','3:00p','3:30p'],
  Evening: ['4:00p','4:30p','5:00p','5:30p','6:00p'],
};
const APPT_MONTHS = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 };

function apptSlotToMinutes(slot) {
  const m = /^(\d{1,2}):(\d{2})\s*(a|p|AM|PM)$/i.exec(String(slot || '').trim());
  if (!m) return 0;
  let h = Number(m[1]);
  const min = Number(m[2]);
  const mer = m[3].toLowerCase()[0];
  if (mer === 'p' && h !== 12) h += 12;
  if (mer === 'a' && h === 12) h = 0;
  return h * 60 + min;
}

function apptFormatSlot(slot) {
  return window.formatSlot ? window.formatSlot(slot) : String(slot || '').replace(/p$/i, ' PM').replace(/a$/i, ' AM');
}

function apptFormatDay(offset) {
  const d = new Date();
  d.setDate(d.getDate() + offset);
  const month = d.toLocaleDateString('en-IN', { month: 'short' });
  const day = d.getDate();
  if (offset === 0) return `Today · ${month} ${day}`;
  if (offset === 1) return `Tomorrow · ${month} ${day}`;
  return `${d.toLocaleDateString('en-IN', { weekday: 'short' })} · ${month} ${day}`;
}

function apptSlotIsPast(dayOffset, slot) {
  const now = new Date();
  return Number(dayOffset) === 0 && apptSlotToMinutes(slot) <= now.getHours() * 60 + now.getMinutes();
}

function apptFirstSlotForDay(dayOffset) {
  return Object.values(APPT_SLOTS).flat().find((slot) => !apptSlotIsPast(dayOffset, slot)) || '';
}

function apptDayHasAvailableSlots(dayOffset) {
  return !!apptFirstSlotForDay(dayOffset);
}

function apptSlotTaken(consults, docId, dayLabel, slotLabel, excludeId = '') {
  if (window.consultSlotIsBooked) return window.consultSlotIsBooked(consults, docId, dayLabel, slotLabel, excludeId);
  return false;
}

function appointmentStartMs(c) {
  if (window.consultStartMs) return window.consultStartMs(c);
  if (!c || c.time === 'Immediate' || c.slot === 'Immediate') return new Date(c?.paidAt || c?.createdAt || 0).getTime() || 0;
  const rawDate = String(c.date || '');
  const rawTime = String(c.slot || c.time || '');
  const now = new Date();
  let base = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  if (/^Tomorrow\b/i.test(rawDate)) {
    base.setDate(base.getDate() + 1);
  } else if (!/^Today\b/i.test(rawDate)) {
    const m = /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2})\b/i.exec(rawDate);
    if (m) {
      base = new Date(now.getFullYear(), APPT_MONTHS[m[1].slice(0, 3)] ?? now.getMonth(), Number(m[2]));
      if (base.getTime() < now.getTime() - 180 * 24 * 60 * 60 * 1000) base.setFullYear(base.getFullYear() + 1);
    }
  }
  const minutes = apptSlotToMinutes(rawTime);
  base.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0);
  return base.getTime();
}

function appointmentWasRescheduled(c) {
  return !!(c?.rescheduledAt || Number(c?.rescheduleCount || 0) > 0);
}

function appointmentOriginalStartMs(c) {
  return appointmentStartMs({
    ...c,
    date: c?.originalDate || c?.date,
    time: c?.originalTime || c?.time,
    slot: c?.originalSlot || c?.slot,
  });
}

function appointmentCanReschedule(c) {
  if (!c || appointmentIsCompleted(c) || appointmentWasRescheduled(c)) return false;
  const originalStart = appointmentOriginalStartMs(c);
  return !!(originalStart && Date.now() <= originalStart - 60 * 60 * 1000);
}

function MyAppointments() {
  const consults = useConsults();
  const appointments = consults.map(consultToAppointment);
  const appointmentGroups = React.useMemo(() => completedAppointmentDoctorGroups(appointments), [consults]);

  return (
    <PortalShell active="appointments" title="My Appointments" subtitle="Your booked dental consultations"
      action={<button onClick={() => window.navigate && window.navigate('/find')} className="ml-btn ml-btn--primary"><I.plus size={14}/> Book new</button>}>
      <div style={{ padding: '24px 32px', maxWidth: 1100 }}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {appointmentGroups.length ? (
            appointmentGroups.map((g) => <CompletedDoctorFlowGroup key={g.doc.id} group={g}/>)
          ) : (
            <FreshEmptyState
              title="No consultations booked yet."
              body="Once you book a dental consult, it will appear here with any follow-ups connected below it."
              actionLabel="Find a dentist"
              actionRoute="/find"
            />
          )}
        </div>
      </div>
    </PortalShell>
  );
}

function consultToAppointment(c) {
  return {
    id: c.id,
    consult: c,
    doc: DOC_BY_ID(c.docId),
    date: c.date,
    time: c.time,
    mode: c.mode,
    reason: c.reason,
    severity: c.severity,
    note: c.note,
    status: appointmentIsCompleted(c) ? 'Completed' : 'Confirmed',
  };
}

function appointmentSortMs(a) {
  const startMs = appointmentStartMs(a.consult);
  const createdMs = new Date(a.consult?.createdAt || 0).getTime();
  return startMs || createdMs || 0;
}

function appointmentCreatedMs(a) {
  return new Date(a.consult?.createdAt || a.consult?.paidAt || 0).getTime() || appointmentSortMs(a);
}

function treatmentFlowCompare(rootId) {
  return (a, b) => {
    const aRoot = String(a.id) === String(rootId) || !a.consult?.followUp ? 0 : 1;
    const bRoot = String(b.id) === String(rootId) || !b.consult?.followUp ? 0 : 1;
    if (aRoot !== bRoot) return aRoot - bRoot;
    return appointmentCreatedMs(a) - appointmentCreatedMs(b) || appointmentSortMs(a) - appointmentSortMs(b);
  };
}

function appointmentRootId(a, byId) {
  let rootId = a.consult?.followUpFor || a.id;
  const seen = new Set();
  for (let i = 0; i < 8 && rootId && !seen.has(String(rootId)); i += 1) {
    seen.add(String(rootId));
    const parent = byId.get(String(rootId));
    if (!parent?.consult?.followUpFor) break;
    rootId = parent.consult.followUpFor;
  }
  return String(rootId || a.id);
}

function completedAppointmentDoctorGroups(appointments) {
  const byId = new Map((appointments || []).map((a) => [String(a.id), a]));
  const chains = new Map();
  (appointments || []).forEach((a) => {
    const rootId = appointmentRootId(a, byId);
    const docId = a.consult?.docId || a.doc?.id;
    const key = `${docId}:${rootId}`;
    if (!chains.has(key)) chains.set(key, { rootId, doc: a.doc, all: [] });
    chains.get(key).all.push(a);
  });

  const doctorMap = new Map();
  chains.forEach((chain) => {
    chain.all.sort(treatmentFlowCompare(chain.rootId));
    chain.completed = chain.all.filter((a) => appointmentIsCompleted(a.consult));
    chain.pending = chain.all.filter((a) => !appointmentIsCompleted(a.consult));
    chain.latestCompleted = chain.completed[chain.completed.length - 1];
    const latestCompletedOrder = chain.latestCompleted ? chain.all.indexOf(chain.latestCompleted) : -1;
    chain.pendingAfterCompleted = chain.latestCompleted ? chain.pending.filter((a) => chain.all.indexOf(a) > latestCompletedOrder) : chain.pending;
    chain.canBookFollowup = !!chain.latestCompleted && chain.pendingAfterCompleted.length === 0;
    chain.latestMs = Math.max(...chain.all.map(appointmentCreatedMs));
    const doc = chain.doc;
    if (!doctorMap.has(doc.id)) doctorMap.set(doc.id, { doc, chains: [] });
    doctorMap.get(doc.id).chains.push(chain);
  });

  return Array.from(doctorMap.values())
    .map((g) => ({ ...g, chains: g.chains.sort((a, b) => b.latestMs - a.latestMs) }))
    .sort((a, b) => b.chains[0].latestMs - a.chains[0].latestMs);
}

function CompletedDoctorFlowGroup({ group }) {
  const completedCount = group.chains.reduce((sum, chain) => sum + chain.completed.length, 0);
  return (
    <div className="ml-card" style={{ padding: 22, display: 'grid', gap: 16 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <div className="ml-avatar" style={{ width: 54, height: 54, borderRadius: 14, fontSize: 17,
          background: TONE_BG[group.doc.tone], color: TONE_FG[group.doc.tone] }}>{group.doc.avatar}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 18, fontWeight: 500 }}>{group.doc.n}</div>
          <div style={{ fontSize: 12.5, color: 'var(--ink-3)', marginTop: 2 }}>
            {group.doc.spec} · {completedCount} completed consult{completedCount === 1 ? '' : 's'} · {group.chains.length} treatment flow{group.chains.length === 1 ? '' : 's'}
          </div>
        </div>
      </div>

      {group.chains.map((chain) => <CompletedTreatmentFlow key={chain.rootId} chain={chain}/>)}
    </div>
  );
}

function CompletedTreatmentFlow({ chain }) {
  const visible = chain.all;
  const first = chain.all[0];
  const care = first?.consult?.careType || first?.reason || 'Dental consultation';
  const upcomingCount = visible.filter((a) => !appointmentIsCompleted(a.consult)).length;
  return (
    <div style={{
      border: '1.5px solid oklch(72% 0.055 165)',
      borderRadius: 12,
      padding: 16,
      background: 'oklch(99% 0.01 165)',
      boxShadow: '0 3px 10px rgba(15, 45, 35, 0.04)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12, flexWrap: 'wrap' }}>
        <div className="ml-label" style={{ margin: 0, color: 'oklch(31% 0.08 165)' }}>Treatment flow</div>
        <span style={{ fontSize: 13.5, color: 'var(--ink)', fontWeight: 600 }}>{care}</span>
        <span className="ml-pill ml-pill--ghost" style={{ fontSize: 10.5 }}>{visible.length} consult{visible.length === 1 ? '' : 's'}</span>
        {upcomingCount > 0 && (
          <span className="ml-pill" style={{ background: 'oklch(88% 0.08 165)', color: 'oklch(26% 0.08 165)', fontSize: 10.5, fontWeight: 600 }}>
            {upcomingCount} upcoming
          </span>
        )}
      </div>

      <div style={{ display: 'grid', gap: 8 }}>
        {visible.map((a, index) => {
          const isCompleted = appointmentIsCompleted(a.consult);
          const isLatestCompleted = chain.latestCompleted?.id === a.id;
          const canBook = isCompleted && isLatestCompleted && chain.canBookFollowup;
          const stepLabel = a.consult.followUp ? `Follow-up ${chain.all.slice(0, chain.all.indexOf(a) + 1).filter((x) => x.consult.followUp).length}` : 'Original consult';
          return (
            <React.Fragment key={a.id}>
              <CompletedFlowNode
                a={a}
                stepLabel={stepLabel}
                canBookFollowup={canBook}
                hasPendingNext={isCompleted && isLatestCompleted && !chain.canBookFollowup}
              />
              {index < visible.length - 1 && <PatientFlowArrow/>}
            </React.Fragment>
          );
        })}
      </div>
    </div>
  );
}

function appointmentPaidFee(a) {
  const c = a?.consult || {};
  if (c.paymentStatus !== 'paid') return null;
  const freeFollowup = c.paidMethod === 'free-followup' || (c.followUp && c.paidAmount != null && Number(c.paidAmount) === 0);
  if (freeFollowup) return 0;
  return c.paidAmount ?? c.fee ?? a?.doc?.fee ?? 0;
}

function appointmentJoinStatusText(j) {
  if (j?.reason === 'unpaid') return 'Payment due';
  return 'WhatsApp link sent by email';
}

function appointmentVisitActionText(a) {
  return 'View booking';
}

function appointmentHasPrescription(c) {
  return !!(c?.prescription || c?.prescribed);
}

function appointmentBookingRoute(a) {
  const id = encodeURIComponent(a?.id || a?.consult?.id || '');
  if (!id) return '/appointments';
  return appointmentHasPrescription(a?.consult) ? `/visit/summary?consult=${id}` : `/confirmed?consult=${id}`;
}

function appointmentModeLabel(mode) {
  return window.bookingModeLabel ? window.bookingModeLabel(mode) : 'WhatsApp';
}

function appointmentModeIcon(mode) {
  const key = window.bookingModeKey ? window.bookingModeKey(mode) : String(mode || '').toLowerCase();
  if (key === 'video') return I.video;
  if (key === 'audio') return I.mic;
  return I.chat;
}

function PatientFlowArrow() {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10, width: 'fit-content',
      color: 'oklch(22% 0.09 165)', background: 'oklch(90% 0.075 165)',
      border: '1.5px solid oklch(70% 0.08 165)', borderRadius: 999,
      padding: '8px 14px', marginLeft: 22,
      boxShadow: '0 2px 8px rgba(20, 80, 55, 0.08)',
    }}>
      <I.arrowR size={15} style={{ transform: 'rotate(90deg)' }}/>
      <span style={{ fontSize: 11.5, fontFamily: 'var(--mono)', fontWeight: 800, letterSpacing: 0.06 + 'em' }}>FOLLOW-UP</span>
    </div>
  );
}

function CompletedFlowNode({ a, stepLabel, canBookFollowup, hasPendingNext }) {
  const completed = appointmentIsCompleted(a.consult);
  const j = a.consult && window.consultIsJoinable ? window.consultIsJoinable(a.consult) : { ok: true };
  const needsPay = !completed && !j.ok && j.reason === 'unpaid';
  const paidFee = appointmentPaidFee(a);
  const rxReady = !!(a.consult?.prescription || a.consult?.prescribed);
  const onJoin = () => {
    if (needsPay) {
      window.navigate && window.navigate('/pay?consult=' + encodeURIComponent(a.id));
    }
  };
  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: '38px 1fr auto',
      gap: 14,
      alignItems: 'center',
      padding: 14,
      borderRadius: 10,
      background: '#fff',
      border: '1px solid oklch(82% 0.035 165)',
    }}>
      <div style={{
        width: 34, height: 34, borderRadius: 17,
        background: completed ? 'var(--blue-soft)' : 'oklch(90% 0.08 165)',
        color: completed ? 'var(--blue-deep)' : 'oklch(25% 0.09 165)',
        display: 'grid', placeItems: 'center',
      }}>
        {completed ? <I.check size={14}/> : <I.calendar size={14}/>}
      </div>
      <div style={{ minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginBottom: 4 }}>
          <span style={{ fontSize: 14.5, fontWeight: 700 }}>{stepLabel}</span>
          <span className={completed ? 'ml-pill' : 'ml-pill ml-pill--ghost'} style={{ fontSize: 10.5 }}>
            {completed ? 'Completed' : 'Upcoming'}
          </span>
          {a.consult.followUp && <span className="ml-pill" style={{ background: 'oklch(88% 0.08 165)', color: 'oklch(26% 0.08 165)', fontSize: 10.5, fontWeight: 600 }}>Follow-up</span>}
          {rxReady && <span className="ml-pill" style={{ background: 'oklch(92% 0.06 165)', color: 'oklch(28% 0.09 165)', fontSize: 10.5, fontWeight: 700 }}>Prescription uploaded</span>}
          {paidFee !== null && <span className="ml-pill" style={{ background: 'oklch(90% 0.08 165)', color: 'oklch(25% 0.09 165)', fontSize: 10.5, fontWeight: 700 }}>Paid ₹{paidFee}</span>}
          {appointmentWasRescheduled(a.consult) && <span className="ml-pill" style={{ background: 'oklch(94% 0.05 80)', color: 'oklch(40% 0.10 80)', fontSize: 10.5 }}>Rescheduled</span>}
        </div>
        <div style={{ fontSize: 12.5, color: 'var(--ink-2)', marginBottom: 3 }}>{a.reason}</div>
        {completed && (
          <div style={{ display: 'grid', gridTemplateColumns: '150px 1fr', gap: 8, fontSize: 12, color: 'var(--ink-2)', marginBottom: 5 }}>
            <span className="ml-muted">{a.consult.followUp ? 'Follow-up date' : 'Consultation date'}</span>
            <span>{a.date}</span>
            <span className="ml-muted">{a.consult.followUp ? 'Follow-up time' : 'Consultation time'}</span>
            <span>{a.time}</span>
          </div>
        )}
        <div style={{ display: 'flex', alignItems: 'center', gap: 9, flexWrap: 'wrap', fontSize: 11.5, color: 'var(--ink-3)', fontFamily: 'var(--mono)' }}>
          <span>{a.id}</span>
          <span>·</span>
          <span>{a.date}</span>
          <span>·</span>
          <span>{a.time}</span>
          <span>·</span>
          <span>{appointmentModeLabel(a.mode).toUpperCase()}</span>
        </div>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6, alignItems: 'flex-end' }}>
        {completed ? (
          <>
            {rxReady && (
              <button onClick={() => window.navigate && window.navigate('/prescriptions')} className="ml-btn ml-btn--primary" style={{ padding: '7px 12px', fontSize: 12 }}>
                <I.doc size={12}/> Prescription
              </button>
            )}
            <button onClick={() => window.navigate && window.navigate(appointmentBookingRoute(a))} className="ml-btn ml-btn--ghost" style={{ padding: '7px 12px', fontSize: 12 }}>
              View booking
            </button>
          </>
        ) : (
          needsPay ? (
            <button onClick={onJoin} className="ml-btn ml-btn--primary" style={{ padding: '7px 12px', fontSize: 12 }}>
              <I.lock size={12}/> Pay now
            </button>
          ) : (
            <button onClick={() => window.navigate && window.navigate(appointmentBookingRoute(a))} className="ml-btn ml-btn--ghost" style={{ padding: '7px 12px', fontSize: 12 }}>
              View booking
            </button>
          )
        )}
        {canBookFollowup && (
          <button onClick={() => window.navigate && window.navigate(`/book?doctor=${encodeURIComponent(a.consult.docId)}&followup=1&from=${encodeURIComponent(a.id)}`)} className="ml-btn ml-btn--primary" style={{ padding: '7px 12px', fontSize: 12 }}>
            <I.chat size={12}/> Book follow-up
          </button>
        )}
        {hasPendingNext && (
          <span style={{ fontSize: 11.5, color: 'var(--ink-4)', textAlign: 'right' }}>Follow-up already booked</span>
        )}
      </div>
    </div>
  );
}

function AppointmentRow({ a }) {
  const [rescheduleOpen, setRescheduleOpen] = React.useState(false);
  const ModeIcon = appointmentModeIcon(a.mode);
  const joinText = appointmentVisitActionText(a);
  const completed = appointmentIsCompleted(a.consult);
  const rescheduled = appointmentWasRescheduled(a.consult);
  const canReschedule = appointmentCanReschedule(a.consult);
  const j = a.consult && window.consultIsJoinable ? window.consultIsJoinable(a.consult) : { ok: true };
  const needsPay = !completed && !j.ok && j.reason === 'unpaid';

  const onJoin = () => {
    if (needsPay) {
      window.navigate && window.navigate('/pay?consult=' + encodeURIComponent(a.id));
    } else {
      window.navigate && window.navigate(appointmentBookingRoute(a));
    }
  };

  return (
    <div className="ml-card" style={{
      padding: 20, display: 'grid',
      gridTemplateColumns: '64px 1.4fr 1fr auto', alignItems: 'center', gap: 18,
    }}>
      <div className="ml-avatar" style={{ width: 56, height: 56, borderRadius: '50%', fontSize: 18,
        background: TONE_BG[a.doc.tone], color: TONE_FG[a.doc.tone] }}>{a.doc.avatar}</div>

      <div>
        <div style={{ fontSize: 15, fontWeight: 500 }}>{a.doc.n}</div>
        <div style={{ fontSize: 12.5, color: 'var(--ink-3)', marginTop: 2 }}>{a.doc.spec} · {a.doc.creds}</div>
        <div style={{ display: 'flex', gap: 6, marginTop: 8, fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--ink-4)' }}>
          {a.id} · {a.reason}
        </div>
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <I.calendar size={14} color="var(--ink-3)"/>
          <span style={{ fontSize: 13.5 }}>{a.date} · {a.time}</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <ModeIcon size={14} color="var(--ink-3)"/>
          <span style={{ fontSize: 13, color: 'var(--ink-2)' }}>{appointmentModeLabel(a.mode)}</span>
        </div>
        <div style={{ display: 'flex', gap: 6, marginTop: 4 }}>
          <span className={completed ? 'ml-pill' : 'ml-pill ml-pill--mint'} style={{ alignSelf: 'flex-start', ...(completed ? { background: 'var(--paper-2)', color: 'var(--ink-2)' } : {}) }}>{a.status}</span>
          {rescheduled && (
            <span className="ml-pill" style={{ alignSelf: 'flex-start', background: 'oklch(94% 0.05 80)', color: 'oklch(40% 0.10 80)' }}>Rescheduled</span>
          )}
          {!completed && (needsPay
            ? <span className="ml-pill" style={{ alignSelf: 'flex-start', background: 'oklch(95% 0.05 25)', color: 'oklch(40% 0.14 25)' }}>Payment due</span>
            : (j.viaFollowup
                ? <span className="ml-pill" style={{ alignSelf: 'flex-start', background: 'oklch(95% 0.06 165)', color: 'oklch(38% 0.10 165)' }}>Free follow-up</span>
                : <span className="ml-pill" style={{ alignSelf: 'flex-start', background: 'oklch(95% 0.06 165)', color: 'oklch(38% 0.10 165)' }}>Paid</span>))}
        </div>
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 6, alignItems: 'flex-end' }}>
        {completed ? (
          <>
            <button onClick={() => window.navigate && window.navigate(appointmentBookingRoute(a))} className="ml-btn ml-btn--ghost" style={{ padding: '8px 14px', fontSize: 12.5 }}>
              View booking
            </button>
            <button onClick={() => window.navigate && window.navigate(`/book?doctor=${encodeURIComponent(a.consult.docId)}&followup=1&from=${encodeURIComponent(a.id)}`)} className="ml-btn ml-btn--primary" style={{ padding: '8px 14px', fontSize: 12.5 }}>
              <I.chat size={13}/> Book follow-up
            </button>
          </>
        ) : (
          <>
            {needsPay ? (
              <button onClick={onJoin} className="ml-btn ml-btn--primary" style={{ padding: '8px 14px', fontSize: 12.5 }}>
                <I.lock size={13}/> Pay now
              </button>
            ) : (
              <button onClick={onJoin} className="ml-btn ml-btn--primary" style={{ padding: '8px 14px', fontSize: 12.5 }}>
                <ModeIcon size={13}/> {joinText}
              </button>
            )}
            {canReschedule ? (
              <button onClick={() => setRescheduleOpen(true)} className="ml-btn ml-btn--link" style={{ fontSize: 12 }}>Reschedule</button>
            ) : rescheduled ? (
              <span style={{ fontSize: 12, color: 'var(--ink-4)' }}>Rescheduled once</span>
            ) : (
              <span style={{ fontSize: 12, color: 'var(--ink-4)' }}>Reschedule closed</span>
            )}
          </>
        )}
      </div>
      {rescheduleOpen && <RescheduleModal consult={a.consult} onClose={() => setRescheduleOpen(false)}/>}
    </div>
  );
}

function RescheduleModal({ consult, onClose }) {
  const consults = useConsults();
  const initialDay = apptDayHasAvailableSlots(0) ? 0 : 1;
  const [day, setDay] = React.useState(initialDay);
  const [slot, setSlot] = React.useState(() => apptFirstSlotForDay(initialDay) || APPT_SLOTS.Morning[0]);
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState('');
  const days = React.useMemo(() => [0, 1, 2, 3].map(apptFormatDay), []);
  const isSlotTaken = React.useCallback((dayOffset, rawSlot) => {
    return apptSlotTaken(consults, consult?.docId, days[dayOffset] || apptFormatDay(dayOffset), apptFormatSlot(rawSlot), consult?.id);
  }, [consults, consult?.docId, consult?.id, days]);
  const slotUnavailable = React.useCallback((dayOffset, rawSlot) => {
    return apptSlotIsPast(dayOffset, rawSlot) || isSlotTaken(dayOffset, rawSlot);
  }, [isSlotTaken]);
  const firstOpenSlotForDay = React.useCallback((dayOffset) => {
    return Object.values(APPT_SLOTS).flat().find((rawSlot) => !slotUnavailable(dayOffset, rawSlot)) || '';
  }, [slotUnavailable]);
  const dayHasOpenSlots = React.useCallback((dayOffset) => !!firstOpenSlotForDay(dayOffset), [firstOpenSlotForDay]);
  React.useEffect(() => {
    if (!slotUnavailable(day, slot)) return;
    const next = firstOpenSlotForDay(day);
    if (next) {
      setSlot(next);
      return;
    }
    const nextDay = [0, 1, 2, 3].find((d) => d !== day && dayHasOpenSlots(d));
    if (nextDay != null) {
      setDay(nextDay);
      setSlot(firstOpenSlotForDay(nextDay) || APPT_SLOTS.Morning[0]);
    }
  }, [day, slot, slotUnavailable, firstOpenSlotForDay, dayHasOpenSlots]);
  const save = async () => {
    if (!appointmentCanReschedule(consult) || slotUnavailable(day, slot)) return;
    setSaving(true);
    setErr('');
    const saved = await (window.updateConsult && window.updateConsult(consult.id, {
      originalDate: consult.originalDate || consult.date || '',
      originalTime: consult.originalTime || consult.time || '',
      originalSlot: consult.originalSlot || consult.slot || '',
      date: days[day] || apptFormatDay(day),
      time: apptFormatSlot(slot),
      slot,
      rescheduledAt: new Date().toISOString(),
      rescheduleCount: Number(consult.rescheduleCount || 0) + 1,
      status: 'confirmed',
    }));
    setSaving(false);
    if (saved) {
      onClose();
    } else {
      setErr('Could not reschedule. It may be within 1 hour of the original consultation time, already rescheduled once, or the slot was just booked.');
    }
  };
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(5,10,15,0.38)', display: 'grid', placeItems: 'center', zIndex: 80, padding: 18 }}>
      <div onClick={(e) => e.stopPropagation()} className="ml-card" style={{ width: 'min(640px, 100%)', padding: 24 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 18 }}>
          <div style={{ width: 42, height: 42, borderRadius: 12, background: 'var(--blue-soft)', color: 'var(--blue-deep)', display: 'grid', placeItems: 'center' }}>
            <I.calendar size={18}/>
          </div>
          <div>
            <h2 style={{ fontSize: 22, margin: 0 }}>Reschedule appointment</h2>
            <div className="ml-muted" style={{ fontSize: 12.5, marginTop: 2 }}>Available once, until 1 hour before the original consultation time.</div>
          </div>
        </div>

        <div className="ml-label" style={{ marginBottom: 10 }}>New day</div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 18, flexWrap: 'wrap' }}>
          {days.map((d, i) => {
            const noSlotsLeft = !dayHasOpenSlots(i);
            return (
              <button key={d} disabled={noSlotsLeft} onClick={() => { if (!noSlotsLeft) { setDay(i); setSlot(firstOpenSlotForDay(i) || APPT_SLOTS.Morning[0]); } }}
                style={{ padding: '10px 13px', borderRadius: 10, border: '1px solid ' + (day === i ? 'var(--ink)' : 'var(--line)'),
                  background: noSlotsLeft ? 'var(--paper-2)' : day === i ? 'var(--ink)' : '#fff',
                  color: noSlotsLeft ? 'var(--ink-4)' : day === i ? '#fff' : 'var(--ink)',
                  cursor: noSlotsLeft ? 'not-allowed' : 'pointer', fontSize: 12.5, fontWeight: 500 }}>
                {d}
              </button>
            );
          })}
        </div>

        {Object.entries(APPT_SLOTS).map(([period, times]) => (
          <div key={period} style={{ marginBottom: 18 }}>
            <div className="ml-label" style={{ marginBottom: 8 }}>{period}</div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 8 }}>
              {times.map((t) => {
                const past = apptSlotIsPast(day, t);
                const booked = isSlotTaken(day, t);
                const unavailable = past || booked;
                return (
                  <button key={t} disabled={unavailable} onClick={() => !unavailable && setSlot(t)}
                    style={{ padding: '11px 0', borderRadius: 8, border: '1px solid ' + (slot === t ? 'var(--blue)' : 'var(--line)'),
                      background: unavailable ? 'var(--paper-2)' : slot === t ? 'var(--blue)' : '#fff',
                      color: unavailable ? 'var(--ink-4)' : slot === t ? '#fff' : 'var(--ink)',
                      fontFamily: 'var(--mono)', fontSize: 12.5, cursor: unavailable ? 'not-allowed' : 'pointer', opacity: unavailable ? 0.55 : 1 }}>
                    <span>{t}</span>
                    {booked && <span style={{ display: 'block', fontSize: 9, marginTop: 2, fontFamily: 'var(--sans)' }}>Booked</span>}
                  </button>
                );
              })}
            </div>
          </div>
        ))}

        {err && <div style={{ marginBottom: 12, padding: 10, borderRadius: 8, background: 'oklch(96% 0.05 25)', color: 'var(--danger)', fontSize: 12.5 }}>{err}</div>}

        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', borderTop: '1px solid var(--line)', paddingTop: 18, marginTop: 2 }}>
          <button onClick={onClose} className="ml-btn ml-btn--ghost">Cancel</button>
          <button onClick={save} disabled={saving || slotUnavailable(day, slot)} className="ml-btn ml-btn--primary" style={{ opacity: saving ? 0.65 : 1 }}>
            {saving ? 'Saving...' : 'Save reschedule'}
          </button>
        </div>
      </div>
    </div>
  );
}

function appointmentIsCompleted(c) {
  if (!c) return false;
  if (c.status === 'completed' || c.status === 'awaiting-prescription' || c.prescription || c.prescribed || c.closedAt || c.completedAt || c.endedAt) return true;
  const startMs = appointmentStartMs(c);
  return !!(startMs && Date.now() >= startMs + 15 * 60 * 1000);
}

function FreshEmptyState({ title, body, actionLabel, actionRoute }) {
  return (
    <div className="ml-card" style={{ padding: 48, textAlign: 'center', color: 'var(--ink-3)' }}>
      <div style={{ width: 52, height: 52, borderRadius: 14, background: 'var(--blue-soft)', color: 'var(--blue-deep)', display: 'grid', placeItems: 'center', margin: '0 auto 16px' }}>
        <I.calendar size={22}/>
      </div>
      <h2 style={{ fontSize: 24, marginBottom: 8, color: 'var(--ink)' }}>{title}</h2>
      <p style={{ margin: '0 auto 20px', maxWidth: 420, fontSize: 14, lineHeight: 1.55 }}>{body}</p>
      <button onClick={() => window.navigate && window.navigate(actionRoute)} className="ml-btn ml-btn--primary" style={{ justifyContent: 'center', margin: '0 auto' }}>
        {actionLabel} <I.arrowR size={13}/>
      </button>
    </div>
  );
}

window.MyAppointments = MyAppointments;
