// Calendar — grid & list pieces (split from calendar.jsx).
// Small UI: ConfirmDialog, toasts, FilterChip/Legend, DayColumn, ApptCard,
// time helpers, MobilePendingList, UpNext, MonthDatePicker, Quick/TypePicker.
// Loaded after calendar.jsx, before app mounts — all refs resolve at render.

// Generic confirm-before-save dialog. Owns no app state — caller passes the
// open flag and the two outcome handlers.
function ConfirmDialog({ open, onConfirm, onCancel, title = 'ยืนยันการบันทึก', message, confirmLabel = 'ยืนยันบันทึก', cancelLabel = 'ยกเลิก', tone = 'brand' }) {
  if (!open) return null;
  return (
    <Modal open={open} onClose={onCancel} size="sm" title={title} footer={
      <div className="flex items-center justify-end gap-2">
        <Btn variant="ghost" size="sm" onClick={onCancel}>{cancelLabel}</Btn>
        <Btn variant={tone} size="sm" icon={<Icon name="check" className="w-3.5 h-3.5" />} onClick={onConfirm}>{confirmLabel}</Btn>
      </div>
    }>
      <div className="text-sm text-ink-700 leading-relaxed">{message}</div>
    </Modal>
  );
}

function DragDropToast({ toast }) {
  if (!toast) return null;
  const kind = toast.kind || 'success';
  const color = kind === 'error' ? '#EF4444' : kind === 'info' ? '#2F5DE5' : '#10B981';
  const iconName = kind === 'error' ? 'info' : 'check';
  return (
    <div className="fixed bottom-6 right-6 z-[60] pointer-events-none">
      <div
        key={toast.ts}
        className="bg-white text-ink-900 text-xs font-medium pl-3 pr-4 py-2.5 rounded-xl shadow-lift flex items-center gap-2 border border-ink-100 pointer-events-auto"
        style={{ animation: 'emphasis-toast-in 180ms ease-out' }}
      >
        <span className="w-6 h-6 rounded-md flex items-center justify-center text-white" style={{ background: color }}>
          <Icon name={iconName} className="w-3.5 h-3.5" strokeWidth={3} />
        </span>
        <span className="leading-snug">{toast.message}</span>
      </div>
      <style>{`
        @keyframes emphasis-toast-in {
          from { opacity: 0; transform: translateY(8px); }
          to   { opacity: 1; transform: translateY(0); }
        }
      `}</style>
    </div>
  );
}

const FilterChip = ({ active, color, tint, onClick, children }) => (
  <button
    onClick={onClick}
    className={`text-[11px] font-medium px-2 py-1 rounded-md border transition-all ${
      active
        ? 'border-transparent text-white'
        : 'bg-white border-ink-200 text-ink-500 hover:border-ink-300'
    }`}
    style={active ? { background: color || '#0F1E38' } : {}}
  >{children}</button>
);

const Legend = () => {
  const { apptTypes } = useConfig();
  return (
  <div className="flex items-center gap-3">
    {apptTypes.map(t => (
      <div key={t.id} className="flex items-center gap-1.5">
        <div className="w-2.5 h-2.5 rounded-sm" style={{ background: t.color }} />
        <span className="text-[11px] text-ink-500">{t.full}</span>
      </div>
    ))}
  </div>
  );
};

// Assign side-by-side lanes for overlapping appointments in one day column.
// Returns Map<apptId, { lane, lanes }> where `lane` is the 0-based slot and
// `lanes` is the total number of lanes the cluster needs.
function _assignApptLanes(appts) {
  const result = new Map();
  if (!appts || !appts.length) return result;
  const sorted = [...appts].sort((a, b) => a.start - b.start || a.dur - b.dur);
  const laneEnds = [];          // laneEnds[i] = end time of last appt placed in lane i
  for (const a of sorted) {
    let laneIdx = laneEnds.findIndex(end => end <= a.start + 0.001);
    if (laneIdx === -1) { laneIdx = laneEnds.length; laneEnds.push(0); }
    laneEnds[laneIdx] = a.start + a.dur;
    result.set(a.id, { lane: laneIdx, lanes: 1 }); // lanes is computed below
  }
  // For each appt, the cluster's lanes = max lane among overlapping appts + 1
  for (const a of sorted) {
    let maxLane = result.get(a.id).lane;
    for (const b of sorted) {
      if (b.id === a.id) continue;
      if (b.start < a.start + a.dur && a.start < b.start + b.dur) {
        maxLane = Math.max(maxLane, result.get(b.id).lane);
      }
    }
    result.set(a.id, { ...result.get(a.id), lanes: maxLane + 1 });
  }
  return result;
}

function DayColumn({ dayIdx, hours, appts, onDragOver, onDrop, onDragLeave, onCellClick, onApptDragStart, onApptDragEnd, onApptClick, hoverCell }) {
  const HOURS = hours || _calendarHours();
  const { apptTypes } = useConfig();
  const hoverType = hoverCell?.typeId ? apptTypes.find(t => t.id === hoverCell.typeId) : null;
  const todayIdx = window.TODAY_DAY_IDX ?? -1;
  const isToday = dayIdx === todayIdx;
  // "Other day" only counts when there IS a today in the current week — for
  // past / future weeks we leave every column at full strength.
  const isOtherDay = todayIdx >= 0 && !isToday;
  const isWeekend = dayIdx === 5 || dayIdx === 6;

  // Background: today gets a stronger brand tint, other days look slightly
  // muted, weekends keep a subtle ink wash.
  const bgClass = isToday
    ? 'bg-brand-50/60'
    : isOtherDay
      ? 'bg-ink-50/40'
      : isWeekend
        ? 'bg-ink-50/30'
        : 'bg-white';

  return (
    <div
      className={`relative border-l ${isToday ? 'border-brand-300' : 'border-ink-100'} cursor-pointer ${bgClass}`}
      style={{ height: HOURS.length * HOUR_PX }}
      onDragOver={onDragOver}
      onDrop={onDrop}
      onDragLeave={onDragLeave}
      onClick={onCellClick}
    >
      {/* Subtle gray wash over non-today columns so today's appts visually
          pop. Opacity stays high enough to keep other days' cards readable. */}
      {isOtherDay && (
        <div className="absolute inset-0 bg-white/40 pointer-events-none z-0" />
      )}
      {/* hour lines */}
      {HOURS.map((_, i) => (
        <div key={i} className="absolute left-0 right-0 border-b border-ink-100/70" style={{ top: (i + 1) * HOUR_PX }} />
      ))}
      {/* half-hour lines (lighter) */}
      {HOURS.map((_, i) => (
        <div key={'h'+i} className="absolute left-0 right-0 border-b border-dashed border-ink-100/50" style={{ top: i * HOUR_PX + SLOT_PX }} />
      ))}

      {/* hover indicator — full default duration of the dragged type */}
      {hoverCell && (
        <div
          className="absolute left-1 right-1 rounded-md drop-target flex flex-col items-start justify-between p-1.5 overflow-hidden"
          style={{
            top: (hoverCell.start - (HOURS[0] ?? 8)) * HOUR_PX,
            height: (hoverCell.dur || 0.5) * HOUR_PX - 4,
            background: hoverType ? hoverType.tint : 'rgba(47,93,229,0.06)',
            borderColor: hoverType ? hoverType.color : '#2F5DE5',
          }}
        >
          {hoverType && (
            <>
              <div className="flex items-center gap-1.5">
                <span className="text-[10px] font-bold px-1.5 rounded-sm text-white" style={{ background: hoverType.color }}>{hoverType.label}</span>
                <span className="text-[10px] font-medium num" style={{ color: hoverType.color }}>{hoverType.dur} นาที</span>
              </div>
              <span className="text-[10px] num text-ink-500">{formatTime(hoverCell.start)} – {formatTime(hoverCell.start + (hoverCell.dur || 0.5))}</span>
            </>
          )}
        </div>
      )}

      {/* appointments — overlapping ones are laid out side-by-side.
          Wrap the whole layer in a slightly muted div when this isn't
          today's column so today's appts visually pop. Cards stay fully
          clickable / draggable. */}
      <div className={`absolute inset-0 z-[1] ${isOtherDay ? 'opacity-65 saturate-75' : ''}`} style={{ pointerEvents: 'none' }}>
        <div style={{ pointerEvents: 'auto', position: 'relative', height: '100%' }}>
          {(() => {
            const lanes = _assignApptLanes(appts);
            return appts.map(a => {
              const info = lanes.get(a.id) || { lane: 0, lanes: 1 };
              return (
                <ApptCard key={a.id} appt={a}
                  lane={info.lane} lanes={info.lanes}
                  onDragStart={onApptDragStart} onDragEnd={onApptDragEnd}
                  onClick={() => onApptClick(a)} />
              );
            });
          })()}
        </div>
      </div>
    </div>
  );
}

function ApptCard({ appt, onDragStart, onDragEnd, onClick, lane = 0, lanes = 1 }) {
  const { apptTypes, leadSources } = useConfig();
  const type = apptTypes.find(t => t.id === appt.type) || { label: '?', color: '#94A3B8', tint: '#F1F5F9' };
  const effStatus = _effectiveStatus(appt);
  const status = STATUS.find(s => s.id === effStatus);
  const src = leadSources.find(s => s.id === appt.source) || { label: 'Unknown', color: '#CBD5E1' };
  const baseHour = (window.WORKING_HOURS?.startHour ?? 8);
  const top = (appt.start - baseHour) * HOUR_PX;
  const height = appt.dur * HOUR_PX - 4;
  const cancelled = effStatus === 'cancelled';
  const completed = effStatus === 'completed';

  // Is this a DL's case (not the logged-in user's own / partner's own)?
  //   dlProspectId set         → DL via ABO Tracking prospect
  //   dlId set & != owner      → legacy DL via users row
  const me = window.__currentUser?.id;
  const isDlCase = !!appt.dlProspectId || (!!appt.dlId && !!me && appt.dlId !== me);

  // FU sub-type: a FU appt with dlProspectId is "ติดตาม DL" (downline check-
  // in), without it is "ติดตาม 6W" (customer in the 6W program). Surface this
  // distinction on the card so the user can read the calendar at a glance.
  const isFU = appt.type === 'FU';
  const fuKind = isFU ? (appt.dlProspectId ? 'dl' : '6w') : null;

  const startStr = formatTime(appt.start);
  const endStr = formatTime(appt.start + appt.dur);

  const isClass = !!appt._isClass;
  return (
    <div
      draggable={!isClass}
      onDragStart={isClass ? undefined : (e) => onDragStart(e, appt.id)}
      onDragEnd={isClass ? undefined : onDragEnd}
      onClick={(e) => { e.stopPropagation(); onClick && onClick(e); }}
      className={`absolute rounded-lg p-2 ${isClass ? 'cursor-pointer' : 'cursor-grab active:cursor-grabbing'} hover:shadow-lift transition-all overflow-hidden text-left border ${cancelled ? 'stripe-bg' : ''}`}
      style={{
        top, height,
        // Lane layout: divide the day column into `lanes` columns, with 2px gap on each side.
        left:  `calc(${(lane * 100) / lanes}% + 2px)`,
        width: `calc(${100 / lanes}% - 4px)`,
        background: cancelled ? '#F1F5F9' : type.tint,
        borderColor: cancelled ? '#CBD5E1' : type.color + '40',
        borderLeft: `3px solid ${cancelled ? '#94A3B8' : type.color}`,
        opacity: cancelled ? 0.6 : 1,
        zIndex: 1 + lane,
      }}
    >
      <div className="flex items-center gap-1.5 mb-0.5">
        {isClass ? (
          <EmphasisLogo className="w-4 h-4" />
        ) : (
          <span className="relative inline-flex text-[10px] font-bold px-1.5 rounded-sm text-white" style={{ background: type.color }}>
            {type.label}
            {isDlCase && (
              <span title="เคสของ Downline"
                className="absolute -top-1 -left-1 w-2.5 h-2.5 rounded-full border border-white"
                style={{ background: '#FFB400' }} />
            )}
          </span>
        )}
        {fuKind && (
          <span title={fuKind === 'dl' ? 'ติดตาม DL' : 'ติดตาม 6W'}
            className="inline-flex items-center gap-0.5 text-[9px] font-bold px-1 py-px rounded leading-none"
            style={fuKind === 'dl'
              ? { background: '#EEF4FF', color: '#2F5DE5', border: '1px solid #2F5DE540' }
              : { background: '#E7F8F1', color: '#0F8B5E', border: '1px solid #10B98140' }}>
            <span className="text-[10px] leading-none">{fuKind === 'dl' ? '💎' : '🥤'}</span>
            <span>{fuKind === 'dl' ? 'DL' : '6W'}</span>
          </span>
        )}
        {!fuKind && (() => {
          const c = window.COUNTRIES.find(c => c.id === (appt.country || 'th'));
          return c && <span className="text-[11px] leading-none" title={c.label}>{c.flag}</span>;
        })()}
        {completed && <span className="text-[10px] font-medium text-emerald-600 flex items-center gap-0.5"><Icon name="check" className="w-3 h-3" />ปิด</span>}
        {appt.status === 'followup' && <span className="text-[10px] font-medium text-brand-600">↻ FU</span>}
        <span className="text-[10px] num text-ink-500 ml-auto">{startStr}</span>
      </div>
      <div className="text-xs font-semibold text-ink-900 truncate leading-tight flex items-center gap-1" style={{ textDecoration: cancelled ? 'line-through' : 'none' }}>
        {appt.igAvatar && !isFU && <img src={appt.igAvatar} alt="" className="w-3.5 h-3.5 rounded-full flex-shrink-0" />}
        {/* Monthly running index of this appt's type — so the user sees
            "1 ครีม, 2 พี่กัน, 3 ..." at a glance and can tell which BM
            (or 6W, BI, etc.) session this is. Resets each month, tracks
            Shelldon's count separately from the partner's. */}
        {!isDlCase && !isFU && (() => {
          const idx = window.__emphasisCaseIndex?.get(appt.id);
          if (!idx) return null;
          return (
            <span className="text-[10px] font-bold leading-none px-1.5 py-0.5 rounded flex-shrink-0 num"
                  style={{ background: type.color, color: 'white' }}
                  title={`${type.label} ครั้งที่ ${idx} ของเดือนนี้`}>
              {idx}
            </span>
          );
        })()}
        <span className="truncate">{appt.prospect}</span>
        {/* For non-FU DL cases we append "(<DL>)" so the user sees whose
            case this is. For FU appts the prospect text is now a free
            "Topic" (not the DL/customer name) — surface the DL name here
            too so the user knows who the topic is about. */}
        {isDlCase && appt.dlName && (
          <span className="text-[10px] font-medium text-ink-500 truncate">({appt.dlName})</span>
        )}
      </div>
      {/* FU appts keep their kind label so users can distinguish ติดตาม DL
          vs ติดตาม 6W on the card. Non-FU appts no longer show the lead
          source on the card — the name + DL parens is enough at a glance. */}
      {height > 40 && isFU && (
        <div className="flex items-center gap-1 mt-1">
          <span className="text-[10px] text-ink-500 truncate">
            {fuKind === 'dl' ? 'ติดตาม DL' : 'ติดตาม 6W'}
          </span>
        </div>
      )}
    </div>
  );
}

function formatTime(h) {
  const hour = Math.floor(h);
  const min = Math.round((h - hour) * 60);
  return `${String(hour).padStart(2,'0')}:${String(min).padStart(2,'0')}`;
}

// Format an offset-from-WEEK_START day index into a "Mon 27 ม.ค." label.
// DAYS_FULL only has the 7 current-week entries, so for offsets ≥ 7 (now
// possible thanks to the month-grid picker) we compute the date ourselves.
function formatDayOffset(dayIdx) {
  const ws = window.WEEK_START ? new Date(window.WEEK_START) : new Date();
  if (dayIdx >= 0 && dayIdx < 7) {
    // Stay in lock-step with DAYS_FULL when it's a current-week index so
    // the label matches whatever else the calendar is showing.
    const inWeek = window.DAYS_FULL?.[dayIdx];
    if (inWeek) {
      const TH_MO = ['ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.','ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.'];
      const d = new Date(ws.getTime() + dayIdx * 86400000);
      return `${inWeek} ${TH_MO[d.getMonth()]}`;
    }
  }
  const d = new Date(ws.getTime() + dayIdx * 86400000);
  const EN_DOW = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
  const TH_MO  = ['ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.','ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.'];
  return `${EN_DOW[d.getDay()]} ${d.getDate()} ${TH_MO[d.getMonth()]}`;
}

// Mobile-only pending list, mirroring the "ต้องตามต่อ" card in the mockup.
// Two tabs: Pending (status=pending) and Follow-up (status=followup). Each
// row is a tappable card that opens EditApptModal.
function MobilePendingList({ appts, apptTypes, onOpen }) {
  const now = new Date();
  // appts spans the whole month, so day-index isn't reliable — build
  // Bangkok day ranges off scheduled_at instead.
  const ranges = useMemo(() => {
    const bkk = new Date(now.getTime() + 7 * 3600 * 1000);
    const y = bkk.getUTCFullYear(), m = bkk.getUTCMonth(), d = bkk.getUTCDate();
    const dow = bkk.getUTCDay(); // 0=Sun..6=Sat; Bangkok-aligned
    const ONE_DAY = 24 * 3600 * 1000;
    const todayStart = new Date(Date.UTC(y, m, d) - 7 * 3600 * 1000).getTime();
    const tomorrowStart = todayStart + ONE_DAY;
    // "สัปดาห์นี้" = today's Bangkok week (Mon..Sun). Shift today to Monday
    // by subtracting (dow=0 → 6, else dow-1) days.
    const offsetToMon = dow === 0 ? 6 : dow - 1;
    const weekStart = todayStart - offsetToMon * ONE_DAY;
    const weekEnd   = weekStart + 7 * ONE_DAY;
    return {
      todayStart, todayEnd: todayStart + ONE_DAY,
      tomorrowStart, tomorrowEnd: tomorrowStart + ONE_DAY,
      weekStart, weekEnd,
    };
  }, []);
  const tMs = (a) => a.scheduled_at ? new Date(a.scheduled_at).getTime() : 0;
  const inRange = (a, s, e) => { const t = tMs(a); return t >= s && t < e; };
  // Default tab → "วันนี้" for immediate workload.
  const [tab, setTab] = useState('today'); // 'today' | 'tomorrow' | 'week' | 'all'

  const sortByTime = (a, b) => {
    if (a.scheduled_at && b.scheduled_at) return a.scheduled_at.localeCompare(b.scheduled_at);
    return (a.day - b.day) || (a.start - b.start);
  };
  // All active (pending/followup) appts, sorted once.
  const all = useMemo(() => appts
    .filter(a => a.status === 'pending' || a.status === 'followup')
    .sort(sortByTime), [appts]);
  const today    = useMemo(() => all.filter(a => inRange(a, ranges.todayStart,    ranges.todayEnd)),    [all, ranges]);
  const tomorrow = useMemo(() => all.filter(a => inRange(a, ranges.tomorrowStart, ranges.tomorrowEnd)), [all, ranges]);
  // "สัปดาห์นี้" = whole week including today/tomorrow but excluding past
  // days (overdue rows show in "ทั้งหมด" instead so they don't clutter
  // the upcoming-week glance).
  const week = useMemo(() => all.filter(a => {
    const t = tMs(a);
    return t >= ranges.todayStart && t < ranges.weekEnd;
  }), [all, ranges]);
  const shown = tab === 'today'    ? today
              : tab === 'tomorrow' ? tomorrow
              : tab === 'week'     ? week
              :                       all;

  return (
    <div className="rounded-2xl border-2 border-gold-200 bg-gradient-to-br from-gold-50/80 to-white p-3 space-y-3">
      <div className="flex items-center gap-2">
        <div className="w-9 h-9 rounded-full bg-gold-100 flex items-center justify-center text-gold-600">
          <Icon name="bell" className="w-4 h-4" />
        </div>
        <div className="flex-1 min-w-0">
          <div className="text-sm font-bold text-ink-900">ต้องตามต่อ</div>
          <div className="text-[10px] text-ink-500">รายชื่อที่ต้องติดตาม</div>
        </div>
        <Pill color="#B87C00" tint="#FFECB8" size="sm">{all.length} เคส</Pill>
      </div>

      {/* 4 date-range tabs — lets the user peek at tomorrow / this week
          without losing the "ต้องตามต่อ" mental model. Tabs scroll
          horizontally on tiny screens. */}
      <div className="flex items-center gap-1 bg-white rounded-lg p-1 border border-ink-100 overflow-x-auto scroll-thin">
        {[
          { id: 'today',    label: 'วันนี้',     n: today.length,    color: '#DC2626' },
          { id: 'tomorrow', label: 'พรุ่งนี้',   n: tomorrow.length, color: '#F59E0B' },
          { id: 'week',     label: 'สัปดาห์นี้', n: week.length,     color: '#06B6D4' },
          { id: 'all',      label: 'ทั้งหมด',    n: all.length,      color: '#7C3AED' },
        ].map(t => (
          <button key={t.id} onClick={() => setTab(t.id)}
            className="flex-1 flex-shrink-0 flex items-center justify-center gap-1 py-1.5 px-2 rounded-md text-[11px] font-semibold transition-all whitespace-nowrap"
            style={tab === t.id ? { background: t.color + '15', color: t.color } : { color: '#64748B' }}>
            <span className="w-1.5 h-1.5 rounded-full" style={{ background: t.color }} />
            {t.label} <span className="num">{t.n}</span>
          </button>
        ))}
      </div>

      <div className="space-y-1.5">
        {shown.length === 0 ? (
          <div className="text-center py-6 text-[11px] text-ink-400">
            {tab === 'today'    ? '— ไม่มีนัดวันนี้ 🎉 —'
            : tab === 'tomorrow' ? '— ไม่มีนัดพรุ่งนี้ —'
            : tab === 'week'     ? '— ไม่มีนัดในสัปดาห์นี้ —'
            :                       '— ไม่มีรายการ —'}
          </div>
        ) : shown.slice(0, 100).map(a => {
          const t = apptTypes.find(x => x.id === a.type) || { color: '#94A3B8', label: '?', full: '—' };
          // Overdue = appt's end time already passed. Works for any week,
          // whereas the old day-index check broke for previous-week appts
          // that surface now that the list spans the whole month.
          const apptEndMs = a.scheduled_at
            ? new Date(a.scheduled_at).getTime() + Math.round((a.dur || 1) * 3600 * 1000)
            : Infinity;
          const overdue = apptEndMs < now.getTime();
          // Build a date label from scheduled_at so appts outside the
          // currently-viewed week (a.day < 0 or > 6) still render.
          const TH_DAYS = ['อา.','จ.','อ.','พ.','พฤ.','ศ.','ส.'];
          const TH_MO   = ['ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.','ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.'];
          let dayLabel = '', dateNum = '', monthAbbrv = '';
          if (a.scheduled_at) {
            const bkk = new Date(new Date(a.scheduled_at).getTime() + 7 * 3600 * 1000);
            dayLabel = TH_DAYS[bkk.getUTCDay()] || '';
            dateNum = String(bkk.getUTCDate());
            monthAbbrv = TH_MO[bkk.getUTCMonth()] || '';
          } else {
            dayLabel = (window.DAYS_FULL?.[a.day] || '').replace(/\d.*$/, '').trim();
            dateNum = (window.DAYS_FULL?.[a.day] || '').match(/\d+/)?.[0] || '';
            monthAbbrv = (window.DAYS_FULL?.[a.day] || '').match(/[ก-ฮ]+\.?$/)?.[0] || '';
          }
          return (
            <button key={a.id} onClick={() => onOpen(a)}
              className="w-full flex items-center gap-2 p-2 rounded-xl bg-white border border-ink-100 hover:shadow-card transition-all text-left">
              <div className="flex-shrink-0 w-12 h-12 rounded-lg flex flex-col items-center justify-center text-white"
                   style={{ background: t.color }}>
                <div className="text-[10px] font-bold">{t.label}</div>
                <div className="text-[10px] font-bold num leading-none mt-0.5">{formatTime(a.start)}</div>
              </div>
              <div className="flex-1 min-w-0">
                <div className="text-sm font-semibold text-ink-900 truncate">
                  {a.prospect || '—'}
                  {a.dlProspectId && a.dlName && a.type !== 'FU' && (
                    <span className="ml-1 text-[11px] font-medium text-ink-500">({a.dlName})</span>
                  )}
                </div>
                <div className="text-[10px] text-ink-500 flex items-center gap-1">
                  <Icon name="calendar" className="w-3 h-3" />
                  <span>{dayLabel || '—'} {dateNum} {monthAbbrv}</span>
                </div>
              </div>
              {overdue && a.status === 'pending' && (
                <Pill color="#DC2626" tint="#FEE2E2" size="xs">ผ่านไปแล้ว</Pill>
              )}
              <Icon name="chevR" className="w-3.5 h-3.5 text-ink-300 flex-shrink-0" />
            </button>
          );
        })}
      </div>
    </div>
  );
}

function UpNext({ appts, onOpen }) {
  const { apptTypes, leadSources } = useConfig();
  const upcoming = appts
    .filter(a => a.status === 'pending' || a.status === 'followup')
    .slice(0, 3);

  return (
    <Card title="Up Next">
      <div className="space-y-2.5">
        {upcoming.map(a => {
          const t = apptTypes.find(x => x.id === a.type);
          return (
            <button key={a.id} onClick={() => onOpen(a)} className="w-full text-left flex items-start gap-2.5 p-2 rounded-lg hover:bg-ink-50 transition-colors">
              <div className="flex-shrink-0 w-9 h-9 rounded-lg flex flex-col items-center justify-center text-white text-[10px] font-bold" style={{ background: t.color }}>
                <div className="leading-none">{t.label}</div>
                <div className="text-[8px] opacity-80 mt-0.5">{formatTime(a.start)}</div>
              </div>
              <div className="flex-1 min-w-0">
                <div className="text-xs font-semibold text-ink-900 truncate">{a.prospect}</div>
                <div className="text-[10px] text-ink-400">{DAYS_FULL[a.day]} · {leadSources.find(s=>s.id===a.source)?.label || '—'}</div>
              </div>
            </button>
          );
        })}
      </div>
    </Card>
  );
}

// ─── MonthDatePicker ────────────────────────────────────────────────────────
// Google-Calendar-style month grid picker. The user navigates between months
// with the chevron buttons, taps "↻" to reset to today's month, and clicks
// any cell to select that date. Past dates (< minDate) are disabled.
//
// Caller controls the picker via `value` (Date) + `onChange(date)`. The
// displayed month tracks `value` by default; the user can roam without
// changing the selection until they actually tap a day.
function MonthDatePicker({ value, onChange, minDate, color = '#2F5DE5' }) {
  const today = useMemo(() => {
    const d = new Date();
    d.setHours(0, 0, 0, 0);
    return d;
  }, []);
  const floor = minDate || today;

  // The month being shown in the grid. Auto-syncs to `value`'s month on
  // first render + when the user picks a date in a different month, but
  // the user can still navigate without committing a selection.
  const [viewMonth, setViewMonth] = useState(() => {
    const d = value || new Date();
    return new Date(d.getFullYear(), d.getMonth(), 1);
  });

  const TH_MO_FULL = ['มกราคม','กุมภาพันธ์','มีนาคม','เมษายน','พฤษภาคม','มิถุนายน','กรกฎาคม','สิงหาคม','กันยายน','ตุลาคม','พฤศจิกายน','ธันวาคม'];
  const DOW = ['จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.', 'อา.'];

  // 6×7 grid starting from the Monday of the week containing the 1st.
  const grid = useMemo(() => {
    const y = viewMonth.getFullYear();
    const m = viewMonth.getMonth();
    const first = new Date(y, m, 1);
    // Mon=1..Sun=7 (treat Sun as 7 so the week starts on Monday)
    const dow = first.getDay() === 0 ? 7 : first.getDay();
    const start = new Date(y, m, 1 - (dow - 1));
    return Array.from({ length: 42 }, (_, i) => {
      const d = new Date(start.getFullYear(), start.getMonth(), start.getDate() + i);
      return d;
    });
  }, [viewMonth]);

  const sameDay = (a, b) =>
    a && b && a.getFullYear() === b.getFullYear()
        && a.getMonth() === b.getMonth()
        && a.getDate() === b.getDate();

  const goPrev = () => setViewMonth(d => new Date(d.getFullYear(), d.getMonth() - 1, 1));
  const goNext = () => setViewMonth(d => new Date(d.getFullYear(), d.getMonth() + 1, 1));
  const goToday = () => {
    setViewMonth(new Date(today.getFullYear(), today.getMonth(), 1));
    onChange?.(today);
  };

  const monthLabel = `${TH_MO_FULL[viewMonth.getMonth()]} ${viewMonth.getFullYear() + 543}`;

  return (
    <div className="rounded-xl border border-ink-100 bg-white p-3 sm:p-4">
      {/* Header — month label + nav buttons */}
      <div className="flex items-center justify-between mb-3">
        <div className="text-sm font-semibold" style={{ color }}>{monthLabel}</div>
        <div className="flex items-center gap-1">
          <button onClick={goToday} title="กลับไปวันนี้"
            className="w-7 h-7 rounded-md flex items-center justify-center text-ink-500 hover:bg-ink-50 hover:text-ink-700 transition-colors">
            <Icon name="refresh" className="w-3.5 h-3.5" />
          </button>
          <button onClick={goPrev} title="เดือนก่อนหน้า"
            className="w-7 h-7 rounded-md flex items-center justify-center text-ink-500 hover:bg-ink-50 hover:text-ink-700 transition-colors">
            <Icon name="chevL" className="w-3.5 h-3.5" />
          </button>
          <button onClick={goNext} title="เดือนถัดไป"
            className="w-7 h-7 rounded-md flex items-center justify-center text-ink-500 hover:bg-ink-50 hover:text-ink-700 transition-colors">
            <Icon name="chevR" className="w-3.5 h-3.5" />
          </button>
        </div>
      </div>

      {/* DOW row */}
      <div className="grid grid-cols-7 gap-1 mb-1">
        {DOW.map((d, i) => (
          <div key={d}
            className={`text-center text-[10px] font-semibold py-1 ${i === 6 ? 'text-rose-500' : 'text-ink-400'}`}>
            {d}
          </div>
        ))}
      </div>

      {/* Day grid */}
      <div className="grid grid-cols-7 gap-1">
        {grid.map((d, i) => {
          const inMonth = d.getMonth() === viewMonth.getMonth();
          const isPast = d < floor;
          const isToday = sameDay(d, today);
          const isSelected = sameDay(d, value);
          const isSunday = d.getDay() === 0;
          const disabled = isPast;

          let cls = 'w-9 h-9 sm:w-10 sm:h-10 mx-auto rounded-full flex items-center justify-center text-sm transition-all num';
          const style = {};
          if (disabled) {
            cls += ' text-ink-300 cursor-not-allowed line-through';
          } else if (isSelected) {
            cls += ' text-white font-bold';
            style.background = color;
          } else if (isToday) {
            cls += ' font-bold border-2';
            style.borderColor = color;
            style.color = color;
          } else if (!inMonth) {
            cls += isSunday ? ' text-rose-300' : ' text-ink-300';
            cls += ' hover:bg-ink-50';
          } else {
            cls += isSunday ? ' text-rose-500' : ' text-ink-700';
            cls += ' hover:bg-ink-50';
          }

          return (
            <button key={i}
              onClick={() => !disabled && onChange?.(d)}
              disabled={disabled}
              className={cls}
              style={style}
              title={d.toLocaleDateString('th-TH', { year: 'numeric', month: 'long', day: 'numeric' })}>
              {d.getDate()}
            </button>
          );
        })}
      </div>
    </div>
  );
}

// Unified "+ นัดใหม่" flow — three-step picker (day → time → type) on a
// single screen. Used by the header button and as the mobile entry point
// for creating an appointment (no drag-and-drop on touch devices).
function QuickNewModal({ open, onClose, daysFull = [], todayIdx = 0, hours = [], onPick, defaultDate = null }) {
  const { apptTypes } = useConfig();
  // Currently selected date (full Date object — the picker emits Date, and
  // we compute weekStart + dayInWeek on confirm). Default to today.
  const today = useMemo(() => {
    const d = new Date(); d.setHours(0, 0, 0, 0); return d;
  }, []);
  // When opened from a month-grid day, start on that date (not today).
  const initialDate = useMemo(() => {
    if (defaultDate) { const d = new Date(defaultDate); d.setHours(0, 0, 0, 0); if (d >= today) return d; }
    return today;
  }, [defaultDate, today]);
  const [selectedDate, setSelectedDate] = useState(initialDate);
  const [start, setStart] = useState(10);
  const [typeId, setTypeId] = useState(apptTypes[0]?.id || '');

  useEffect(() => {
    if (open) {
      setSelectedDate(initialDate);
      // Default the start to the next half-hour slot ≥ now
      const baseHour = hours[0] ?? 8;
      const lastHour = hours[hours.length - 1] ?? 23;
      const now = new Date();
      const nowH = now.getHours() + now.getMinutes() / 60;
      let s = Math.ceil(nowH * 2) / 2;
      s = Math.max(baseHour, Math.min(lastHour + 0.5, s));
      setStart(s);
      setTypeId(apptTypes[0]?.id || '');
    }
  }, [open, initialDate]); // eslint-disable-line

  const chosenType = apptTypes.find(t => t.id === typeId);
  // Helpers to display the selected date in the footer.
  const TH_DOW_SHORT = ['อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.'];
  const TH_MO_SHORT  = ['ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.','ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.'];
  const selectedDateLabel = selectedDate
    ? `${TH_DOW_SHORT[selectedDate.getDay()]} ${selectedDate.getDate()} ${TH_MO_SHORT[selectedDate.getMonth()]}`
    : '—';

  // Generate half-hour time slots from working hours.
  const baseHour = hours[0] ?? 8;
  const lastHour = hours[hours.length - 1] ?? 23;
  const timeSlots = [];
  for (let h = baseHour; h <= lastHour + 0.5; h += 0.5) timeSlots.push(h);

  // Lock to today onward — no back-dating. If the currently selected day
  // becomes "today", disable any time slot that's already in the past.
  const now = new Date();
  const nowSlot = Math.ceil((now.getHours() + now.getMinutes() / 60) * 2) / 2;
  const isToday = selectedDate && selectedDate.getTime() === today.getTime();
  const startIsPast = isToday && start < nowSlot;
  const dayIsPast = selectedDate && selectedDate.getTime() < today.getTime();

  const canConfirm = !!typeId && !!selectedDate && start >= baseHour && !dayIsPast && !startIsPast;

  return (
    <Modal open={open} onClose={onClose} size="lg"
      title={
        <div className="flex items-center gap-2">
          <div className="w-7 h-7 rounded-md bg-brand-500 text-white flex items-center justify-center">
            <Icon name="plus" className="w-4 h-4" strokeWidth={2.5} />
          </div>
          <div>
            <div className="text-sm font-semibold text-ink-900">นัดหมายใหม่</div>
            <div className="text-[11px] text-ink-400">เลือกวัน · เวลา · ประเภท</div>
          </div>
        </div>
      }
      footer={
        <div className="flex items-center justify-between gap-2">
          <span className="text-[11px] text-ink-500 num">
            {selectedDateLabel} · {formatTime(start)} · <b style={{ color: chosenType?.color }}>{chosenType?.label || '—'}</b>
          </span>
          <div className="flex items-center gap-2">
            <Btn variant="ghost" size="sm" onClick={onClose}>ยกเลิก</Btn>
            <Btn variant="brand" size="sm" disabled={!canConfirm}
              icon={<Icon name="arrowR" className="w-3.5 h-3.5" />}
              onClick={() => {
                if (!selectedDate) return;
                const ws = window.weekStartFor(selectedDate);
                const dayInWeek = Math.round((selectedDate - ws) / 86400000);
                onPick({
                  weekStart: ws,
                  day: dayInWeek,
                  start,
                  typeId,
                  dur: (chosenType?.dur || 60) / 60,
                });
              }}>
              ต่อไป
            </Btn>
          </div>
        </div>
      }
    >
      <div className="space-y-4">
        {/* Day — Google-Calendar-style month grid. The user can navigate
            freely with the month-arrow buttons; past dates are disabled
            so saveDraft never has to backstop a past pick. */}
        <div>
          <div className="text-[10px] uppercase tracking-wider font-semibold text-ink-500 mb-1.5">วัน</div>
          <MonthDatePicker value={selectedDate} onChange={setSelectedDate} minDate={today} />
        </div>

        {/* Time */}
        <div>
          <div className="text-[10px] uppercase tracking-wider font-semibold text-ink-500 mb-1.5">
            เวลา {isToday && <span className="text-ink-400 normal-case font-normal">· หลัง {formatTime(nowSlot)}</span>}
          </div>
          <div className="flex flex-wrap gap-1.5 max-h-40 overflow-y-auto pr-1">
            {timeSlots.map(t => {
              const past = isToday && t < nowSlot;
              const selected = Math.abs(start - t) < 0.01;
              return (
                <button key={t} onClick={() => !past && setStart(t)}
                  disabled={past}
                  className="px-2.5 py-1.5 rounded-md text-[11px] font-medium num border-2 transition-all"
                  style={past
                    ? { borderColor: '#E6EAF3', background: '#F4F6FB', color: '#C9D2E3', cursor: 'not-allowed' }
                    : selected
                      ? { borderColor: '#2F5DE5', background: '#2F5DE5', color: 'white' }
                      : { borderColor: '#E6EAF3', background: 'white', color: '#445576' }}>
                  {formatTime(t)}
                </button>
              );
            })}
          </div>
        </div>

        {/* Type */}
        <div>
          <div className="text-[10px] uppercase tracking-wider font-semibold text-ink-500 mb-1.5">ประเภทนัด</div>
          <div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
            {apptTypes.map(t => (
              <button key={t.id} onClick={() => setTypeId(t.id)}
                className="text-left p-2.5 rounded-xl border-2 transition-all"
                style={typeId === t.id
                  ? { borderColor: t.color, background: t.tint || (t.color + '12') }
                  : { borderColor: '#E6EAF3', background: 'white' }}>
                <div className="flex items-center gap-1.5">
                  <span className="w-7 h-7 rounded-md text-white text-[10px] font-bold flex items-center justify-center flex-shrink-0"
                        style={{ background: t.color }}>{t.label}</span>
                  <div className="min-w-0 flex-1">
                    <div className="text-[11px] font-bold text-ink-900 truncate">{t.full || t.label}</div>
                    <div className="text-[10px] text-ink-500 num">{t.dur} นาที</div>
                  </div>
                </div>
              </button>
            ))}
          </div>
        </div>
      </div>
    </Modal>
  );
}

// Compact picker that opens when the user clicks an empty calendar cell.
// Each tile uses the type's color/tint and shows label + Thai name + default
// duration. Picking a type seeds draftDrop and the existing ProspectModal
// pipeline takes over.
function TypePickerModal({ open, cell, onClose, onPick }) {
  const { apptTypes } = useConfig();
  if (!cell) return null;
  return (
    <Modal open={open} onClose={onClose} size="md"
      title={
        <div className="flex items-center gap-2">
          <Icon name="plus" className="w-4 h-4 text-brand-600" strokeWidth={2.5} />
          <div>
            <div className="text-sm font-semibold text-ink-900">เลือกประเภทนัด</div>
            <div className="text-[11px] text-ink-400 num">
              {DAYS_FULL[cell.day]} · {formatTime(cell.start)}
            </div>
          </div>
        </div>
      }
    >
      <div className="grid grid-cols-2 gap-2">
        {apptTypes.map(t => (
          <button key={t.id}
            onClick={() => onPick(t.id, (t.dur || 60) / 60)}
            className="text-left p-3 rounded-xl border-2 transition-all hover:shadow-card"
            style={{ borderColor: t.color + '40', background: t.tint || (t.color + '12') }}>
            <div className="flex items-center gap-2">
              <span className="w-8 h-8 rounded-md flex items-center justify-center text-white font-bold text-xs flex-shrink-0"
                    style={{ background: t.color }}>{t.label}</span>
              <div className="min-w-0 flex-1">
                <div className="text-xs font-bold text-ink-900 truncate">{t.full || t.label}</div>
                <div className="text-[10px] text-ink-500 num">{t.dur} นาที</div>
              </div>
            </div>
            {t.desc && (
              <div className="text-[10px] text-ink-500 mt-1.5 leading-snug line-clamp-2">{t.desc}</div>
            )}
          </button>
        ))}
      </div>
    </Modal>
  );
}
