// month-schedule.jsx — 月份排班月曆 + 單日 override

function MonthSchedulePage({ state, update, accent, density, doctorDisplay, viewMode }) {
  const [cur, setCur] = React.useState(() => ({ y: 2026, m: 4 })); // May 2026
  const [selectedDay, setSelectedDay] = React.useState(null);
  const [selectedTplWeekday, setSelectedTplWeekday] = React.useState(null);

  const grid = getMonthGrid(cur.y, cur.m);
  const { shifts, doctors, weekTemplate, dayOverrides } = state;
  const meta = getMonthMeta(state, cur.y, cur.m);
  const mKey = monthKey(cur.y, cur.m);

  // Determine if there are unpublished changes for this month
  const isDirty = (() => {
    if (meta.status === 'draft') return true;
    if (!meta.snapshot) return false;
    // Compare current month's overrides vs snapshot
    const cur_ovs = Object.fromEntries(Object.entries(state.dayOverrides).filter(([iso]) => iso.startsWith(mKey)));
    const snap_ovs = Object.fromEntries(Object.entries(meta.snapshot.dayOverrides || {}).filter(([iso]) => iso.startsWith(mKey)));
    return JSON.stringify(cur_ovs) !== JSON.stringify(snap_ovs);
  })();

  const publishMonth = () => {
    if (!confirm(`確定要發佈 ${cur.y}年${cur.m+1}月 的排班？\n發佈後預約日曆會更新使用這版排班。`)) return;
    const cur_ovs = Object.fromEntries(Object.entries(state.dayOverrides).filter(([iso]) => iso.startsWith(mKey)));
    const now = new Date();
    const ts = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')} ${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}`;
    update(s => ({
      ...s,
      monthStatus: { ...s.monthStatus, [mKey]: {
        status: 'published', publishedAt: ts,
        snapshot: {
          dayOverrides: cur_ovs,
          weekTemplate: s.weekTemplate,
          weeklyHolidays: s.weeklyHolidays,
          specialHolidays: s.specialHolidays,
          specialWorkdays: s.specialWorkdays,
        },
      }},
    }));
    flashToast(`✓ ${cur.y}年${cur.m+1}月 排班已發佈`);
  };

  const discardDraft = () => {
    if (!confirm(`捨棄本月草稿變更，回到已發佈版本？`)) return;
    if (!meta.snapshot) return;
    update(s => {
      const restored = { ...s.dayOverrides };
      // remove all current overrides for this month
      Object.keys(restored).forEach(iso => { if (iso.startsWith(mKey)) delete restored[iso]; });
      // restore from snapshot
      Object.entries(meta.snapshot.dayOverrides || {}).forEach(([iso, v]) => {
        if (iso.startsWith(mKey)) restored[iso] = v;
      });
      return { ...s, dayOverrides: restored };
    });
    flashToast('已回到已發佈版本');
  };

  // Mark month as draft when edits happen (auto)
  React.useEffect(() => {
    if (meta.status === 'published' && isDirty) {
      // don't actually mutate status — keep "published with unpublished changes" as a state
      // we'll reflect that in the banner
    }
  }, [isDirty]);

  const setMonth = (delta) => {
    let y = cur.y, m = cur.m + delta;
    if (m < 0) { m = 11; y--; } if (m > 11) { m = 0; y++; }
    setCur({ y, m });
  };

  const copyFromPrev = () => {
    // Copy previous month's overrides + apply same template (already auto). We copy overrides keyed by day-of-month.
    const prev = new Date(cur.y, cur.m - 1, 1);
    const prevY = prev.getFullYear(), prevM = prev.getMonth();
    const newOverrides = { ...state.dayOverrides };
    Object.entries(state.dayOverrides).forEach(([iso, val]) => {
      const [y,m,d] = iso.split('-').map(Number);
      if (y === prevY && m-1 === prevM) {
        const targetIso = fmtDate(cur.y, cur.m, d);
        // Only copy if the target month has that day
        const last = new Date(cur.y, cur.m + 1, 0).getDate();
        if (d <= last && !newOverrides[targetIso]) {
          newOverrides[targetIso] = val;
        }
      }
    });
    update(s => ({ ...s, dayOverrides: newOverrides }));
    flashToast('已將上月覆寫拷貝到本月，可繼續微調');
  };

  const resetDay = (iso) => {
    update(s => {
      const { [iso]: _drop, ...rest } = s.dayOverrides;
      return { ...s, dayOverrides: rest };
    });
  };

  const setDay = (iso, payload) => {
    update(s => ({ ...s, dayOverrides: { ...s.dayOverrides, [iso]: payload } }));
  };

  const isThisMonth = (cell) => cell.inMonth;
  const today = new Date();
  const todayIso = fmtDate(today.getFullYear(), today.getMonth(), today.getDate());

  return (
    <div>
      {/* status banner */}
      <StatusBanner meta={meta} isDirty={isDirty} accent={accent}
        onPublish={publishMonth} onDiscard={discardDraft} canDiscard={!!meta.snapshot} />

      {/* toolbar */}
      <div style={{
        display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 14,
      }}>
        <div style={{ display:'flex', alignItems:'center', gap: 12 }}>
          <button onClick={()=>setMonth(-1)} style={navBtn}>‹</button>
          <div style={{ fontSize: 20, fontWeight: 600, color:'#29261b', minWidth: 140 }}>
            {cur.y} 年 {MONTHS_ZH[cur.m]} 月
          </div>
          <button onClick={()=>setMonth(1)} style={navBtn}>›</button>
          <button onClick={()=>setCur({ y: today.getFullYear(), m: today.getMonth() })}
            style={{ ...navBtn, width: 'auto', padding: '0 12px', fontSize: 13 }}>今天</button>
        </div>
        <div style={{ display:'flex', gap: 8 }}>
          <button onClick={copyFromPrev} style={ghostBtn}>↺ 拷貝上月</button>
          <button onClick={()=>{
            if (confirm('將本月所有覆寫清除，回到週模板？')) {
              const newOv = { ...state.dayOverrides };
              grid.forEach(c => { if (c.inMonth) delete newOv[c.iso]; });
              update(s => ({ ...s, dayOverrides: newOv }));
            }
          }} style={ghostBtn}>套用週模板</button>
        </div>
      </div>

      {/* legend */}
      <div style={{ display:'flex', gap: 14, alignItems:'center', marginBottom: 10, fontSize: 11, color:'#86827a' }}>
        {shifts.map(sh => (
          <div key={sh.id} style={{ display:'flex', alignItems:'center', gap: 6 }}>
            <span style={{ width: 10, height: 10, borderRadius: 2, background: sh.accent }} />
            {sh.label} <span style={{ fontFamily:'ui-monospace, monospace', color:'#bcb3a3' }}>{sh.start}–{sh.end}</span>
            {sh.dayTimes && Object.keys(sh.dayTimes).length > 0 && (
              <span title="依星期不同" style={{
                fontSize: 9, color: accent, fontWeight: 600,
                border:`1px solid ${accent}55`, padding:'0 4px', borderRadius: 3,
              }}>依日</span>
            )}
          </div>
        ))}
        <div style={{ marginLeft: 'auto', display:'flex', gap: 14 }}>
          <span><span style={legendDot('#f7ede0')} /> 休</span>
          <span><span style={legendDot('#fff')} style2={{border:'1px dashed '+accent}} /> 已覆寫</span>
        </div>
      </div>

      {/* calendar grid (with template row at top) */}
      <div style={{
        display:'grid', gridTemplateColumns:'repeat(7, 1fr)',
        background:'#ebe4d8', gap: 1, border:'1px solid #ebe4d8', borderRadius: 8, overflow:'hidden',
        position:'relative',
      }}>
        {WEEKDAYS_ZH.map((w, i) => (
          <div key={i} style={{
            background:'#faf6ef', padding:'8px 10px', fontSize: 12,
            fontWeight: 600, color:'#86827a', textAlign:'left',
          }}>週{w}</div>
        ))}
        {grid.map((cell, idx) => {
          const isTemplateDay = cell.inMonth && cell.d <= 7;
          if (isTemplateDay) {
            const isFirstTpl = cell.weekday === 0 || cell.d === 1;
            const isLastTpl  = cell.weekday === 6 || cell.d === 7;
            return (
              <TemplateCell key={`tpl-${cell.iso}`} weekday={cell.weekday} cell={cell} state={state} accent={accent}
                density={density} doctorDisplay={doctorDisplay}
                isFirstCol={isFirstTpl} isLastCol={isLastTpl}
                onClick={()=>setSelectedTplWeekday(cell.weekday)}
                onDrop={(id, shiftId) => {
                  const wd = cell.weekday;
                  if (state.weeklyHolidays.includes(wd)) return;
                  const cur_ids = state.weekTemplate[wd]?.[shiftId] || [];
                  if (!cur_ids.includes(id)) {
                    update(s => ({ ...s, weekTemplate: {
                      ...s.weekTemplate, [wd]: { ...s.weekTemplate[wd], [shiftId]: [...cur_ids, id] },
                    }}));
                  }
                }} />
            );
          }
          return (
          <DayCell key={cell.iso} cell={cell} cur={cur} state={state} accent={accent}
            density={density} doctorDisplay={doctorDisplay} viewMode={viewMode}
            todayIso={todayIso}
            onClick={()=>{ if (cell.inMonth) setSelectedDay(cell.iso); }}
            onDrop={(id, shiftId) => {
              if (!cell.inMonth) return;
              const resolved = resolveShifts(cell.iso, cell.weekday, state);
              if (resolved.holiday) return;
              const ov = state.dayOverrides[cell.iso] || {
                early: resolved.early, mid: resolved.mid, late: resolved.late,
              };
              if (!ov[shiftId].includes(id)) {
                setDay(cell.iso, { ...ov, [shiftId]: [...ov[shiftId], id] });
              }
            }}
          />
          );
        })}
      </div>

      {selectedDay && (
        <DayEditor iso={selectedDay} state={state} update={update}
          onClose={()=>setSelectedDay(null)} accent={accent} onReset={()=>resetDay(selectedDay)} />
      )}

      {selectedTplWeekday !== null && (
        <TemplateDayEditor weekday={selectedTplWeekday} state={state} update={update}
          onClose={()=>setSelectedTplWeekday(null)} accent={accent} />
      )}
    </div>
  );
}

function TemplateCell({ weekday, cell, state, accent, density, doctorDisplay, isFirstCol, isLastCol, onClick, onDrop }) {
  const { shifts, doctors, weekTemplate, weeklyHolidays } = state;
  const isHoliday = weeklyHolidays.includes(weekday);
  const compact = density === 'compact';
  const minH = compact ? 110 : 140;
  const [dragOver, setDragOver] = React.useState(false);

  return (
    <div onClick={onClick}
      onDragOver={(e)=>{ if (!isHoliday) { e.preventDefault(); setDragOver(true); } }}
      onDragLeave={()=>setDragOver(false)}
      onDrop={(e)=>{
        setDragOver(false);
        const id = e.dataTransfer.getData('text/doctor-id');
        const shiftId = e.dataTransfer.getData('text/shift-id') || 'early';
        if (id) onDrop(id, shiftId);
      }}
      style={{
        background: isHoliday ? '#f7ede0' : `${accent}0a`,
        padding: compact ? 7 : 9,
        minHeight: minH,
        cursor:'pointer',
        position:'relative',
        borderTop: `2px solid ${accent}`,
        borderBottom: `2px solid ${accent}`,
        borderLeft: isFirstCol ? `2px solid ${accent}` : 'none',
        borderRight: isLastCol ? `2px solid ${accent}` : 'none',
        outline: dragOver ? `2px solid ${accent}` : 'none',
        outlineOffset: -2,
        boxSizing: 'border-box',
      }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 5 }}>
        {cell && cell.d === 1 ? (
          <span style={{
            fontSize: 10, fontWeight: 600, letterSpacing:'.04em',
            color:'#fff', padding:'2px 8px', borderRadius: 3,
            background: accent,
          }}>週模板</span>
        ) : (
          <span style={{
            fontSize: 11, color:'#bcb3a3', fontWeight: 500,
          }}>{cell ? `${cell.d}` : ''}</span>
        )}
        {isHoliday && (
          <span style={{
            fontSize: 10, padding:'1px 6px', borderRadius: 4,
            background:'#fff', color:'#b48a52', border:'1px solid #f1e2c8',
          }}>每週休</span>
        )}
      </div>
      {isHoliday ? (
        <div style={{ color:'#b48a52', fontSize: 12, opacity:.7,
          display:'grid', placeItems:'center', height: minH - 36 }}>
          每週固定休
        </div>
      ) : (
        <div style={{ display:'flex', flexDirection:'column', gap: compact ? 2 : 4 }}>
          {shifts.map(sh => {
            const ids = weekTemplate[weekday]?.[sh.id] || [];
            if (ids.length === 0) {
              return (
                <div key={sh.id} style={{
                  display:'flex', alignItems:'center', gap: 4,
                  fontSize: compact ? 10 : 11, color:'#bcb3a3',
                  borderLeft:`2px solid ${sh.accent}50`, paddingLeft: 5,
                }}>
                  <span style={{ color:'#86827a', fontSize: 10 }}>{sh.label}</span>
                  <span style={{ fontStyle:'italic' }}>未排</span>
                </div>
              );
            }
            return (
              <div key={sh.id} style={{
                display:'flex', flexWrap:'wrap', gap: 2,
                borderLeft:`2px solid ${sh.accent}`, paddingLeft: 5,
                alignItems:'center',
              }}>
                <span style={{ fontSize: 10, color:'#86827a', marginRight: 2, fontWeight: 500 }}>{sh.label}</span>
                {ids.map(id => {
                  const d = docById(doctors, id);
                  return d && <DocPill key={id} doctor={d} mode={doctorDisplay} size="xs" />;
                })}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function TemplateDayEditor({ weekday, state, update, onClose, accent }) {
  const isHoliday = state.weeklyHolidays.includes(weekday);

  const setShiftIds = (shiftId, ids) => {
    update(s => ({ ...s, weekTemplate: {
      ...s.weekTemplate, [weekday]: { ...s.weekTemplate[weekday], [shiftId]: ids },
    }}));
  };

  const toggleWeeklyHoliday = () => {
    update(s => ({ ...s,
      weeklyHolidays: isHoliday ? s.weeklyHolidays.filter(x=>x!==weekday) : [...s.weeklyHolidays, weekday],
    }));
  };

  return (
    <div onClick={onClose} style={{
      position:'fixed', inset: 0, background:'rgba(41,38,27,.4)',
      display:'grid', placeItems:'center', zIndex: 100,
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        background:'#fff', borderRadius: 12, padding: 24,
        width: 560, maxWidth:'90vw', maxHeight:'85vh', overflow:'auto',
        boxShadow:'0 20px 60px rgba(0,0,0,.2)',
      }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom: 18 }}>
          <div>
            <div style={{ fontSize: 10, color: accent, letterSpacing:'.06em', fontWeight: 600,
              padding:'2px 8px', borderRadius: 3, background:`${accent}14`, display:'inline-block',
            }}>編輯週模板</div>
            <div style={{ fontSize: 22, fontWeight: 600, color:'#29261b', marginTop: 6 }}>
              每週 {WEEKDAYS_ZH[weekday] === '六' || WEEKDAYS_ZH[weekday] === '日' ? '' : ''}週{WEEKDAYS_ZH[weekday]}
            </div>
            <div style={{ fontSize: 12, color:'#86827a', marginTop: 4 }}>
              影響範圍：未來所有未覆寫的週{WEEKDAYS_ZH[weekday]}
            </div>
          </div>
          <button onClick={onClose} style={{
            appearance:'none', border:0, background:'transparent', fontSize: 22, color:'#86827a',
            cursor:'pointer', padding: 0, lineHeight: 1,
          }}>×</button>
        </div>

        <div style={{
          display:'flex', alignItems:'center', justifyContent:'space-between', gap: 10,
          padding:'10px 12px', background:'#faf6ef', borderRadius: 8, marginBottom: 16,
        }}>
          <div style={{ fontSize: 12, color:'#3f3a36' }}>
            {isHoliday ? '此 weekday 為每週固定休息日' : '此 weekday 為工作日'}
          </div>
          <button onClick={toggleWeeklyHoliday} style={ghostBtn}>
            {isHoliday ? '設為工作日' : '設為每週休'}
          </button>
        </div>

        {!isHoliday && state.shifts.map(sh => {
          const ids = state.weekTemplate[weekday]?.[sh.id] || [];
          const t = shiftTimeFor(sh, weekday);
          return (
            <div key={sh.id} style={{ marginBottom: 14 }}>
              <div style={{ display:'flex', alignItems:'center', gap: 8, marginBottom: 8 }}>
                <span style={{ width:10, height:10, borderRadius:2, background: sh.accent }} />
                <span style={{ fontWeight: 600, color:'#29261b' }}>{sh.label}</span>
                <span style={{ color: t.custom ? accent : '#bcb3a3', fontSize: 12, fontFamily:'ui-monospace, monospace', fontWeight: t.custom ? 600 : 400 }}>
                  {t.start} – {t.end}
                </span>
                {t.custom && <span style={{ fontSize: 10, color: accent, border:`1px solid ${accent}55`, padding:'0 4px', borderRadius: 3 }}>週{WEEKDAYS_ZH[weekday]}專屬</span>}
                <span style={{ marginLeft:'auto', fontSize: 11, color:'#86827a' }}>
                  {ids.length} 位醫師
                </span>
              </div>
              <DoctorMultiSelect
                doctors={state.doctors}
                selectedIds={ids}
                onChange={(newIds) => setShiftIds(sh.id, newIds)}
              />
            </div>
          );
        })}

        <div style={{ display:'flex', justifyContent:'flex-end', gap: 8, marginTop: 8 }}>
          <button onClick={onClose} style={{
            appearance:'none', border:0, background: accent, color:'#fff',
            padding:'8px 18px', borderRadius: 6, fontSize: 13, cursor:'pointer', fontWeight: 500,
          }}>完成</button>
        </div>
      </div>
    </div>
  );
}

function DayCell({ cell, cur, state, accent, density, doctorDisplay, todayIso, onClick, onDrop, viewMode }) {
  const { shifts, doctors } = state;
  const resolved = resolveShifts(cell.iso, cell.weekday, state);
  const inMonth = cell.inMonth;
  const isToday = cell.iso === todayIso;
  const compact = density === 'compact';
  const minH = compact ? 100 : 130;

  const [dragOver, setDragOver] = React.useState(false);

  return (
    <div onClick={onClick}
      onDragOver={(e)=>{ if (inMonth && !resolved.holiday) { e.preventDefault(); setDragOver(true); } }}
      onDragLeave={()=>setDragOver(false)}
      onDrop={(e)=>{
        setDragOver(false);
        const id = e.dataTransfer.getData('text/doctor-id');
        const shiftId = e.dataTransfer.getData('text/shift-id') || 'early';
        if (id) onDrop(id, shiftId);
      }}
      style={{
        background: inMonth ? '#fff' : '#fafafa',
        opacity: inMonth ? 1 : .5,
        padding: compact ? 6 : 8,
        minHeight: minH,
        cursor: inMonth ? 'pointer' : 'default',
        position:'relative',
        outline: dragOver ? `2px solid ${accent}` : 'none',
        outlineOffset: -2,
      }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 4 }}>
        <div style={{
          fontSize: 12, fontWeight: 600,
          color: cell.weekday === 6 ? '#c75478' : cell.weekday === 5 ? '#4072a8' : '#29261b',
          display:'flex', alignItems:'center', gap: 4,
        }}>
          {isToday ? (
            <span style={{
              background: accent, color:'#fff', borderRadius:'50%',
              width: 22, height: 22, display:'grid', placeItems:'center', fontSize: 11,
            }}>{cell.d}</span>
          ) : cell.d}
          {resolved.overridden && (
            <span style={{
              fontSize: 9, color: accent, border:`1px dashed ${accent}`,
              padding:'0 4px', borderRadius: 8,
            }}>覆寫</span>
          )}
        </div>
        {resolved.holiday && (
          <span style={{
            fontSize: 10, padding:'1px 6px', borderRadius: 4,
            background:'#f7ede0', color:'#b48a52',
          }}>休</span>
        )}
      </div>
      {resolved.holiday && state.holidayNotes?.[cell.iso] && inMonth && (
        <div style={{
          fontSize: compact ? 10 : 11, color:'#b48a52',
          background:'#fbf3e6', border:'1px solid #f1e2c8',
          borderRadius: 4, padding:'3px 6px', marginTop: 2,
          lineHeight: 1.3, overflow:'hidden', textOverflow:'ellipsis',
        }}>{state.holidayNotes[cell.iso]}</div>
      )}
      {!resolved.holiday && inMonth && (
        <div style={{ display:'flex', flexDirection:'column', gap: compact ? 2 : 4 }}>
          {shifts.map(sh => {
            const tpl = patternShiftsFor(state, cell.iso, cell.weekday);
            const tplIds = tpl[sh.id] || [];
            return (
              <ShiftLine key={sh.id} shift={sh} ids={resolved[sh.id]} tplIds={tplIds}
                overridden={resolved.overridden}
                doctors={doctors} compact={compact} doctorDisplay={doctorDisplay} />
            );
          })}
        </div>
      )}
    </div>
  );
}

function ShiftLine({ shift, ids, tplIds = [], overridden, doctors, compact, doctorDisplay }) {
  if (!ids) return null;
  const tplSet = new Set(tplIds);
  const curSet = new Set(ids);
  const removed = tplIds.filter(id => !curSet.has(id));
  const hasAny = ids.length > 0 || removed.length > 0;
  if (!hasAny) {
    return (
      <div style={{
        display:'flex', alignItems:'center', gap: 4,
        fontSize: compact ? 10 : 11, color:'#bcb3a3',
        borderLeft: `2px solid ${shift.accent}50`, paddingLeft: 5,
      }}>
        <span style={{ color:'#86827a', fontSize: 10 }}>{shift.label}</span>
        <span style={{ fontStyle:'italic' }}>未排</span>
      </div>
    );
  }
  return (
    <div style={{
      display:'flex', flexWrap:'wrap', gap: 2,
      borderLeft: `2px solid ${shift.accent}`, paddingLeft: 5,
      alignItems:'center',
    }}>
      <span style={{ fontSize: 10, color:'#86827a', marginRight: 2, fontWeight: 500 }}>{shift.label}</span>
      {ids.map(id => {
        const d = docById(doctors, id);
        if (!d) return null;
        const variant = tplSet.has(id) ? 'template' : 'added';
        return <DocPill key={id} doctor={d} mode={doctorDisplay} size="xs" variant={variant} />;
      })}
      {removed.map(id => {
        const d = docById(doctors, id);
        if (!d) return null;
        return <DocPill key={`r-${id}`} doctor={d} mode={doctorDisplay} size="xs" variant="removed" />;
      })}
    </div>
  );
}

function DayEditor({ iso, state, update, onClose, accent, onReset }) {
  const [y,m,d] = iso.split('-').map(Number);
  const weekday = monIdx(new Date(y, m-1, d));
  const resolved = resolveShifts(iso, weekday, state);
  const ov = state.dayOverrides[iso];
  const inHoliday = resolved.holiday;
  const isSpecHoliday = state.specialHolidays.includes(iso);
  const isSpecWorkday = state.specialWorkdays.includes(iso);

  const setShiftIds = (shiftId, ids) => {
    const base = ov || { early: resolved.early, mid: resolved.mid, late: resolved.late };
    update(s => ({ ...s, dayOverrides: { ...s.dayOverrides, [iso]: { ...base, [shiftId]: ids } } }));
  };

  const toggleHoliday = () => {
    if (state.weeklyHolidays.includes(weekday)) {
      // weekly holiday — toggle specialWorkdays
      update(s => ({ ...s,
        specialWorkdays: isSpecWorkday ? s.specialWorkdays.filter(x=>x!==iso) : [...s.specialWorkdays, iso],
      }));
    } else {
      update(s => ({ ...s,
        specialHolidays: isSpecHoliday ? s.specialHolidays.filter(x=>x!==iso) : [...s.specialHolidays, iso],
      }));
    }
  };

  return (
    <div onClick={onClose} style={{
      position:'fixed', inset: 0, background:'rgba(41,38,27,.4)',
      display:'grid', placeItems:'center', zIndex: 100, fontFamily:'inherit',
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        background:'#fff', borderRadius: 12, padding: 24,
        width: 560, maxWidth:'90vw', maxHeight:'85vh', overflow:'auto',
        boxShadow:'0 20px 60px rgba(0,0,0,.2)',
      }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom: 18 }}>
          <div>
            <div style={{ fontSize: 11, color:'#a09887', letterSpacing:'.04em' }}>編輯單日排班</div>
            <div style={{ fontSize: 22, fontWeight: 600, color:'#29261b', marginTop: 2 }}>
              {y} / {String(m).padStart(2,'0')} / {String(d).padStart(2,'0')}
              <span style={{ fontSize: 14, color:'#86827a', fontWeight: 400, marginLeft: 10 }}>
                週{WEEKDAYS_ZH[weekday]}
              </span>
            </div>
          </div>
          <button onClick={onClose} style={{
            appearance:'none', border:0, background:'transparent', fontSize: 22, color:'#86827a',
            cursor:'pointer', padding: 0, lineHeight: 1,
          }}>×</button>
        </div>

        <div style={{
          display:'flex', alignItems:'center', justifyContent:'space-between', gap: 10,
          padding:'10px 12px', background:'#faf6ef', borderRadius: 8, marginBottom: inHoliday ? 12 : 16,
        }}>
          <div style={{ fontSize: 12, color:'#3f3a36' }}>
            {inHoliday ? '此日為休假日（不排班）' : '此日為工作日'}
            {ov && <span style={{ marginLeft: 8, color: accent, fontWeight:500 }}>· 已覆寫</span>}
          </div>
          <div style={{ display:'flex', gap: 8 }}>
            <button onClick={toggleHoliday} style={ghostBtn}>
              {inHoliday ? '設為工作日' : '設為休假日'}
            </button>
            {ov && <button onClick={()=>{ onReset(); }} style={ghostBtn}>回到週模板</button>}
          </div>
        </div>

        {inHoliday && (
          <div style={{ marginBottom: 16 }}>
            <label style={{ display:'block', fontSize: 12, color:'#86827a', marginBottom: 6 }}>
              休假註解 <span style={{ color:'#bcb3a3' }}>（會顯示在月曆與預約日曆）</span>
            </label>
            <input type="text" placeholder="例如：員工旅遊、國定假日、診所維修"
              value={state.holidayNotes?.[iso] || ''}
              onChange={(e) => {
                const v = e.target.value;
                update(s => {
                  const notes = { ...(s.holidayNotes || {}) };
                  if (v.trim()) notes[iso] = v;
                  else delete notes[iso];
                  return { ...s, holidayNotes: notes };
                });
              }}
              style={{
                width:'100%', boxSizing:'border-box', border:'1px solid #e5dccb',
                background:'#fff', borderRadius: 6, padding:'8px 12px',
                fontSize: 13, color:'#29261b', fontFamily:'inherit', outline:'none',
              }} />
          </div>
        )}

        {!inHoliday && state.shifts.map(sh => {
          const ids = (ov ? ov[sh.id] : resolved[sh.id]) || [];
          const t = shiftTimeFor(sh, weekday);
          return (
            <div key={sh.id} style={{ marginBottom: 14 }}>
              <div style={{ display:'flex', alignItems:'center', gap: 8, marginBottom: 8 }}>
                <span style={{ width:10, height:10, borderRadius:2, background: sh.accent }} />
                <span style={{ fontWeight: 600, color:'#29261b' }}>{sh.label}</span>
                <span style={{ color: t.custom ? accent : '#bcb3a3', fontSize: 12, fontFamily:'ui-monospace, monospace', fontWeight: t.custom ? 600 : 400 }}>
                  {t.start} – {t.end}
                </span>
                {t.custom && <span style={{ fontSize: 10, color: accent, border:`1px solid ${accent}55`, padding:'0 4px', borderRadius: 3 }}>週{WEEKDAYS_ZH[weekday]}專屬</span>}
                <span style={{ marginLeft:'auto', fontSize: 11, color:'#86827a' }}>
                  {ids.length} 位醫師
                </span>
              </div>
              <DoctorMultiSelect
                doctors={state.doctors}
                selectedIds={ids}
                onChange={(newIds) => setShiftIds(sh.id, newIds)}
              />
            </div>
          );
        })}

        <div style={{ display:'flex', justifyContent:'flex-end', gap: 8, marginTop: 8 }}>
          <button onClick={onClose} style={{
            appearance:'none', border:0, background: accent, color:'#fff',
            padding:'8px 18px', borderRadius: 6, fontSize: 13, cursor:'pointer', fontWeight: 500,
          }}>完成</button>
        </div>
      </div>
    </div>
  );
}

function StatusBanner({ meta, isDirty, accent, onPublish, onDiscard, canDiscard }) {
  let dot, label, hint, bg, fg;
  if (meta.status === 'published' && !isDirty) {
    dot = '#3f8a45'; bg = '#eef6ee'; fg = '#225b29';
    label = '已發佈';
    hint = `發佈時間 ${meta.publishedAt} · 預約日曆使用此版`;
  } else if (meta.status === 'published' && isDirty) {
    dot = '#d97a3c'; bg = '#fbeee0'; fg = '#7a4318';
    label = '已發佈 · 有未發佈變更';
    hint = `預約日曆目前仍顯示 ${meta.publishedAt} 發佈版本`;
  } else {
    dot = '#86827a'; bg = '#faf6ef'; fg = '#3f3a36';
    label = '草稿';
    hint = '尚未發佈 — 預約日曆不會顯示這份排班';
  }
  return (
    <div style={{
      display:'flex', alignItems:'center', gap: 14,
      background: bg, padding:'10px 16px', borderRadius: 8, marginBottom: 14,
    }}>
      <span style={{ width: 10, height: 10, borderRadius:'50%', background: dot, flexShrink: 0 }} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: fg }}>{label}</div>
        <div style={{ fontSize: 11, color: fg, opacity:.75, marginTop: 1 }}>{hint}</div>
      </div>
      <div style={{ display:'flex', gap: 8 }}>
        {canDiscard && isDirty && (
          <button onClick={onDiscard} style={ghostBtn}>捨棄草稿</button>
        )}
        <button onClick={onPublish} disabled={meta.status === 'published' && !isDirty} style={{
          appearance:'none', border:0, background: accent, color:'#fff',
          padding:'7px 16px', borderRadius: 6, fontSize: 13, fontWeight: 500,
          cursor: (meta.status === 'published' && !isDirty) ? 'default' : 'pointer',
          opacity: (meta.status === 'published' && !isDirty) ? .4 : 1,
        }}>
          {meta.status === 'published' && isDirty ? '↑ 重新發佈' : meta.status === 'published' ? '已發佈' : '↑ 發佈本月'}
        </button>
      </div>
    </div>
  );
}

const navBtn = {
  width: 32, height: 32, border:'1px solid #e5dccb', background:'#fff',
  borderRadius: 6, cursor:'pointer', fontSize: 16, color:'#29261b',
};
const ghostBtn = {
  appearance:'none', border:'1px solid #e5dccb', background:'#fff',
  padding:'6px 12px', borderRadius: 6, fontSize: 12, cursor:'pointer', color:'#29261b',
};
const legendDot = (bg) => ({
  display:'inline-block', width: 10, height: 10, borderRadius: 2,
  background: bg, border:'1px solid #ebe4d8', marginRight: 4, verticalAlign:'middle',
});

let _toastTimer;
function flashToast(msg) {
  let el = document.getElementById('__toast');
  if (!el) {
    el = document.createElement('div');
    el.id = '__toast';
    Object.assign(el.style, {
      position: 'fixed', bottom: '40px', left: '50%', transform: 'translateX(-50%)',
      background: '#29261b', color: '#fff', padding: '10px 18px', borderRadius: '8px',
      fontSize: '13px', zIndex: 9999, boxShadow: '0 8px 20px rgba(0,0,0,.2)',
      opacity: '0', transition: 'opacity .2s', fontFamily:'ui-sans-serif, system-ui',
    });
    document.body.appendChild(el);
  }
  el.textContent = msg;
  el.style.opacity = '1';
  clearTimeout(_toastTimer);
  _toastTimer = setTimeout(()=>{ el.style.opacity = '0'; }, 1800);
}

Object.assign(window, { MonthSchedulePage });
