<script lang="ts" setup>
/**
 * 排班面板（可复用核心组件）
 * 三个入口共用：诊所管理「排班管理」弹窗 / 诊所设置页「排班管理」tab /
 * 医生管理「排班日历」（lockedDoctorId 锁定医生，隐藏医生切换与矩阵视图）。
 *
 * 双视图：
 * - 月历视图：左右布局——左侧单医生整月日历，右侧双页签
 *   「当日值班」：单击日期联动展示当天排班方式、各时段值班人数与医生列表
 *     （只有日排/规则算出诊；未配置排班的默认兜底降级到「未排」折叠组，不冒充值班）
 *   「批量排班」：医生标签墙（头像+姓名），单击多选批量医生、双击切换日历展示医生
 * - 总览视图（矩阵）：全店医生 × 一周日期，格子级选择批量操作
 * 展示口径：默认兜底（register_num 全天可约）在月历/矩阵渲染为弱化的「默认可约」文字，
 * 只有真排班（按日排班绿 chip / 规则排班灰 chip）才以 chip 呈现，避免噪音淹没真实排班
 *
 * 选择模型（标准桌面，同文件管理器）：
 * 普通点击=只选该格（替换选区，并联动右栏值班视图）/ Ctrl+点击=追加或取消 /
 * Shift+点击=锚点到当前范围并入（多选不打断批量工作流，不切页签）；
 * 两视图格子均支持右键快捷菜单（套模板/自定义/休诊/恢复/复制粘贴/添加医生排班）。
 */
import type {
  MatrixCell,
  MatrixDoctor,
  ScheduleCalendarDay,
  ScheduleSlot,
  ShiftTemplate,
} from '../../api/schedule';

import { computed, ref, watch } from 'vue';

import {
  Avatar,
  Button,
  Empty,
  message,
  Popconfirm,
  Segmented,
  Select,
  Spin,
  Tooltip,
} from 'ant-design-vue';
import dayjs from 'dayjs';

import { Icon } from '#/components/icon';
import { getDoctorOptionApi } from '#/views/system/store/api';

import {
  getScheduleMonthCalendarApi,
  getShiftTemplateListApi,
  getStoreMatrixApi,
  getWorkingDoctorsApi,
  saveScheduleDaysApi,
} from '../../api/schedule';
import AddDoctorScheduleModal from './AddDoctorScheduleModal.vue';
import ScheduleContextMenu from './ScheduleContextMenu.vue';
import ScheduleMatrixView from './ScheduleMatrixView.vue';
import ShiftTemplateModal from './ShiftTemplateModal.vue';
import SlotRowsEditor from './SlotRowsEditor.vue';

const props = withDefaults(
  defineProps<{
    /** 锁定医生（医生管理入口传入）：隐藏医生下拉与矩阵视图，只排该医生 */
    lockedDoctorId?: number;
    lockedDoctorName?: string;
    storeId: number;
    storeName?: string;
  }>(),
  { lockedDoctorId: 0, lockedDoctorName: '', storeName: '' },
);

/** 周表头（周一开头，与后端 week 1-7 对齐） */
const WEEK_HEADS = [
  { label: '周一', value: 1 },
  { label: '周二', value: 2 },
  { label: '周三', value: 3 },
  { label: '周四', value: 4 },
  { label: '周五', value: 5 },
  { label: '周六', value: 6 },
  { label: '周日', value: 7 },
];

/** 视图记忆 key：用户偏好月历还是总览 */
const VIEW_STORAGE_KEY = 'xk-schedule-panel-view';

/** 当前视图：locked 场景强制月历（总览是全店视角，对单医生入口无意义） */
const view = ref<'calendar' | 'matrix'>(
  localStorage.getItem(VIEW_STORAGE_KEY) === 'matrix' ? 'matrix' : 'calendar',
);
if (props.lockedDoctorId) {
  view.value = 'calendar';
}

/** 是否锁定医生模式 */
const isLocked = computed(() => Number(props.lockedDoctorId) > 0);

/** 医生选项与当前医生（月历视图）；avatar 供右栏标签墙展示 */
const doctorOptions = ref<Array<{ avatar?: string; id: number; name: string }>>([]);
const doctorId = ref<number | undefined>();
/** 当前月份（YYYY-MM） */
const month = ref<string>(dayjs().format('YYYY-MM'));
/** 月历数据 */
const days = ref<ScheduleCalendarDay[]>([]);
/** 该医生是否还没配置过规则排班（提示用） */
const isDefaultRule = ref(false);
/** 患者端可预约窗口天数（提示用） */
const bookDays = ref(7);
/** 选中日期集合（月历视图，date 字符串） */
const selected = ref<string[]>([]);
/** Shift 范围选择锚点（date 字符串） */
const anchorDate = ref<string>('');
/** 班次模板列表 */
const templates = ref<ShiftTemplate[]>([]);
/** 操作条：套用模板选中值 */
const templateId = ref<number | undefined>();
/** 操作条：批量应用的医生集合（月历视图多医生批量，默认当前医生） */
const batchDoctorIds = ref<number[]>([]);
/** 自定义时段展开面板 */
const customOpen = ref(false);
const customSlots = ref<ScheduleSlot[]>([]);
/** 加载与提交状态 */
const loading = ref(false);
const saving = ref(false);
/** 矩阵视图已选格数（操作条计数） */
const matrixCount = ref(0);
/** 排班剪贴板（右键复制/粘贴，内存级，两视图共享） */
const clipboard = ref<null | { slots: ScheduleSlot[]; summary: string }>(null);

const templateModalRef = ref<InstanceType<typeof ShiftTemplateModal>>();
const addModalRef = ref<InstanceType<typeof AddDoctorScheduleModal>>();
const matrixRef = ref<InstanceType<typeof ScheduleMatrixView>>();
const ctxMenuRef = ref<InstanceType<typeof ScheduleContextMenu>>();

/** 右键目标上下文：菜单动作执行时读取（区分月历/矩阵与目标格） */
const ctxTarget = ref<null | {
  cellSlots: ScheduleSlot[];
  view: 'calendar' | 'matrix';
}>(null);

/** 月历表头需要的前置空位数（本月 1 号是周几 - 1） */
const leadingBlanks = computed(() =>
  days.value.length > 0 ? (days.value[0]!.week + 6) % 7 : 0,
);

/** 启用中的模板（添加医生排班弹窗数据源：含医生专属，弹窗内自行分流） */
const enabledTemplates = computed(() =>
  templates.value.filter((t) => Number(t.status) === 1),
);

/**
 * 启用中的店级通用模板（套用下拉与右键子列表数据源）
 * 医生专属模板不进通用套用入口：把某位医生的专属时段套给任意医生集合是明显误操作
 */
const generalTemplates = computed(() =>
  enabledTemplates.value.filter((t) => Number(t.doctor_id || 0) === 0),
);

/** 是否可以往前翻（限制最早回看 12 个月） */
const canPrev = computed(() =>
  dayjs(month.value).isAfter(dayjs().subtract(12, 'month'), 'month'),
);
/** 最多往后排 12 个月 */
const canNext = computed(() =>
  dayjs(month.value).isBefore(dayjs().add(12, 'month'), 'month'),
);

/** 视图切换：记忆偏好并清理跨视图状态 */
function onViewChange(val: any) {
  view.value = val as 'calendar' | 'matrix';
  localStorage.setItem(VIEW_STORAGE_KEY, view.value);
  customOpen.value = false;
}

/** 拉取门店医生下拉（锁定医生时跳过） */
async function fetchDoctors() {
  if (isLocked.value) {
    doctorId.value = Number(props.lockedDoctorId);
    batchDoctorIds.value = [doctorId.value];
    return;
  }
  const list = await getDoctorOptionApi(props.storeId);
  doctorOptions.value = Array.isArray(list) ? list : [];
  // 默认选第一个医生，减少一步点击
  if (!doctorId.value && doctorOptions.value.length > 0) {
    doctorId.value = doctorOptions.value[0]!.id;
  }
  batchDoctorIds.value = doctorId.value ? [doctorId.value] : [];
}

/** 拉取班次模板 */
async function fetchTemplates() {
  const data = await getShiftTemplateListApi(props.storeId);
  templates.value = Array.isArray(data?.list) ? data.list : [];
  // 套用下拉只放通用模板，已选模板变专属/被禁用后要清掉避免误套
  if (templateId.value && !generalTemplates.value.some((t) => t.id === templateId.value)) {
    templateId.value = undefined;
  }
}

/** 拉取月历（医生或月份变化时都走这里） */
async function fetchCalendar() {
  if (!doctorId.value) {
    days.value = [];
    return;
  }
  loading.value = true;
  try {
    const data = await getScheduleMonthCalendarApi(doctorId.value, month.value, props.storeId);
    days.value = Array.isArray(data?.days) ? data.days : [];
    isDefaultRule.value = Boolean(data?.is_default_rule);
    bookDays.value = Number(data?.book_days || 7);
  } finally {
    loading.value = false;
  }
}

/**
 * 切换日历展示医生：只重拉月历，保留日期选择与批量医生集合
 * 右栏标签墙已可视化批量集合，切人浏览不清状态才能支撑
 * 「选日期 → 多选医生 → 逐个切人核对 → 批量应用」的完整工作流
 */
function onDoctorChange() {
  if (batchDoctorIds.value.length === 0 && doctorId.value) {
    batchDoctorIds.value = [doctorId.value];
  }
  fetchCalendar();
}

// ---------------- 右栏医生标签墙 ----------------

/** 批量集合为空时实际生效的医生数（fallback 当前日历医生） */
const effectiveBatchCount = computed(() =>
  batchDoctorIds.value.length > 0 ? batchDoctorIds.value.length : (doctorId.value ? 1 : 0),
);

/** 单击标签：切换该医生是否加入批量集合 */
function toggleBatchDoctor(id: number) {
  const idx = batchDoctorIds.value.indexOf(id);
  if (idx === -1) {
    batchDoctorIds.value.push(id);
  } else {
    batchDoctorIds.value.splice(idx, 1);
  }
}

/** 只切换日历展示医生（值班列表点击用，不动批量集合，避免误加批量对象） */
function focusDoctorCalendar(id: number) {
  if (doctorId.value !== id) {
    doctorId.value = id;
    fetchCalendar();
  }
}

/** 双击标签：把日历切到该医生（核对排班），同时确保其在批量集合内 */
function viewDoctorCalendar(id: number) {
  if (!batchDoctorIds.value.includes(id)) {
    batchDoctorIds.value.push(id);
  }
  focusDoctorCalendar(id);
}

/** 全选门店医生到批量集合 */
function selectAllDoctors() {
  batchDoctorIds.value = doctorOptions.value.map((d) => d.id);
}

/** 清空批量集合（应用时回落到当前日历医生） */
function clearBatchDoctors() {
  batchDoctorIds.value = [];
}

// ---------------- 右栏当日值班视图 ----------------

/** 右栏页签记忆 key：用户偏好值班还是批量 */
const SIDE_TAB_KEY = 'xk-schedule-side-tab';
/** 右栏页签：duty=当日值班 batch=批量排班 */
const sideTab = ref<'batch' | 'duty'>(
  localStorage.getItem(SIDE_TAB_KEY) === 'batch' ? 'batch' : 'duty',
);
function onSideTabChange(val: any) {
  sideTab.value = val as 'batch' | 'duty';
  localStorage.setItem(SIDE_TAB_KEY, sideTab.value);
}

/** 当前展示值班的日期（YYYY-MM-DD，空=还没点过日期） */
const dutyDate = ref('');
const dutyLoading = ref(false);
/** 该日全店医生矩阵行（days=1，每人一个 cell） */
const dutyDoctors = ref<MatrixDoctor[]>([]);
/** 休诊/未排医生折叠展开态 */
const dutyOffExpanded = ref(false);
/** 同日数据内存缓存（保存排班成功后整体失效），非响应式即可 */
const dutyCache = new Map<string, MatrixDoctor[]>();
/** 值班请求序号：快速连点日期时丢弃过期响应 */
let dutySeq = 0;

/** 排班来源短文案（值班列表标签用；default 不会进出诊列表，仅折叠组展示） */
const DUTY_SOURCE_TEXT: Record<string, string> = {
  day: '日排',
  rule: '规则',
  default: '默认',
};

/**
 * 拉取某日全店值班（复用总览矩阵接口 days=1，后端零改动）
 * 缓存命中直接用；竞态用序号守卫，避免旧响应覆盖新日期的数据
 */
async function fetchDuty(date: string) {
  dutyDate.value = date;
  dutyOffExpanded.value = false;
  const cached = dutyCache.get(date);
  if (cached) {
    dutyDoctors.value = cached;
    return;
  }
  const seq = ++dutySeq;
  dutyLoading.value = true;
  try {
    const data = await getStoreMatrixApi(props.storeId, date, 1);
    if (seq !== dutySeq) return;
    const list = Array.isArray(data?.doctors) ? data.doctors : [];
    dutyCache.set(date, list);
    if (dutyDate.value === date) {
      dutyDoctors.value = list;
    }
  } finally {
    if (seq === dutySeq) {
      dutyLoading.value = false;
    }
  }
}

/** 排班保存成功后失效值班缓存并重拉当前日（保证右栏与库内一致） */
function invalidateDuty() {
  dutyCache.clear();
  if (dutyDate.value) {
    fetchDuty(dutyDate.value);
  }
}

/** 值班日期标题（如「8月13日 周四」） */
const dutyDateText = computed(() => {
  if (!dutyDate.value) return '';
  const d = dayjs(dutyDate.value);
  return `${d.format('M月D日')} 周${'日一二三四五六'[d.day()]}`;
});

/**
 * 值班有效集合：只有「日排/规则」来源才算真排班计入出诊。
 * source=default 是医生从未配置排班时的兜底放号（老字段 register_num 全天可约），
 * 不是诊所安排的值班——混进出诊列表会把真实排班淹没（尤其安排完当日值班后），
 * 所以统一降级到下方「未排」折叠组，仅作可约提示
 */
const dutyOnDuty = computed(() =>
  dutyDoctors.value.flatMap((d) => {
    const cell = d.cells?.[0];
    if (!cell || !cell.is_work || cell.is_rest || cell.source === 'default') return [];
    return [{ doc: d, cell }];
  }),
);

/** 出诊医生（含来源标签与时段容量摘要，模板直接渲染不再调函数） */
const dutyWorking = computed(() =>
  dutyOnDuty.value.map(({ cell, doc }) => ({
    id: doc.id,
    name: doc.name,
    avatar: doc.avatar,
    source: cell.source,
    sourceText: DUTY_SOURCE_TEXT[cell.source] || '规则',
    summary: (cell.slots || [])
      .map((s) => `${s.slot_name || '时段'}${s.capacity}`)
      .join(' / '),
  })),
);

/** 休诊/未排医生（折叠展示；isRest=主动休诊，isDefault=未配置排班仅默认可约） */
const dutyOff = computed(() =>
  dutyDoctors.value.flatMap((d) => {
    const cell = d.cells?.[0];
    const isDefault = Boolean(cell && cell.is_work && !cell.is_rest && cell.source === 'default');
    if (cell && cell.is_work && !cell.is_rest && !isDefault) return [];
    return [
      {
        id: d.id,
        name: d.name,
        avatar: d.avatar,
        isRest: Boolean(cell?.is_rest),
        isDefault,
      },
    ];
  }),
);

/** 未配置排班、按默认全天可约的医生数（值班空态提示用） */
const dutyDefaultCount = computed(() => dutyOff.value.filter((d) => d.isDefault).length);

/** 时段分布：按「时段名+起止」聚合值班人数（回答"每个时间段多少医生值班"；默认兜底不计入） */
const dutySlotGroups = computed(() => {
  const map = new Map<string, { count: number; key: string; name: string; time: string }>();
  for (const { cell } of dutyOnDuty.value) {
    for (const s of cell.slots || []) {
      const time = `${s.start_time}-${s.end_time}`;
      const key = `${s.slot_name}|${time}`;
      const hit = map.get(key);
      if (hit) {
        hit.count += 1;
      } else {
        map.set(key, { key, name: String(s.slot_name || '时段'), time, count: 1 });
      }
    }
  }
  // 按开始时间排序，展示顺序与一天的时间轴一致
  return [...map.values()].sort((a, b) => a.time.localeCompare(b.time));
});

/** 翻月：清空选择（跨月多选容易误操作，明确按月批量） */
function switchMonth(offset: number) {
  month.value = dayjs(month.value).add(offset, 'month').format('YYYY-MM');
  selected.value = [];
  anchorDate.value = '';
  fetchCalendar();
}

/** 某天是否可选（今天及以后才能改排班） */
function selectable(day: ScheduleCalendarDay) {
  return !day.is_past;
}

/**
 * 月历格子点击：标准桌面选择模型
 * 普通点击=只选该日（替换）；Ctrl/Cmd=追加/取消；Shift=锚点到当前日范围并入（跳过过去日期）
 */
function onDayClick(day: ScheduleCalendarDay, e: MouseEvent) {
  if (!selectable(day)) return;

  if (e.shiftKey && anchorDate.value) {
    const [from, to] = [anchorDate.value, day.date].sort();
    const range = days.value
      .filter((d) => selectable(d) && d.date >= from! && d.date <= to!)
      .map((d) => d.date);
    selected.value = [...new Set([...selected.value, ...range])];
  } else if (e.ctrlKey || e.metaKey) {
    const idx = selected.value.indexOf(day.date);
    if (idx === -1) {
      selected.value.push(day.date);
    } else {
      selected.value.splice(idx, 1);
    }
    anchorDate.value = day.date;
  } else {
    selected.value = [day.date];
    anchorDate.value = day.date;
    // 普通单击联动右栏「当日值班」：自动切页签并拉取该日全店值班
    // （Ctrl/Shift 多选不联动，避免打断「选日期→选医生→批量应用」的工作流）
    if (!isLocked.value) {
      if (sideTab.value !== 'duty') {
        sideTab.value = 'duty';
        localStorage.setItem(SIDE_TAB_KEY, 'duty');
      }
      fetchDuty(day.date);
    }
  }
}

/** 点击周表头：全选/反选该列的可选日期（周排场景一键选整列，保持追加语义） */
function toggleWeekColumn(week: number) {
  const colDates = days.value.filter((d) => d.week === week && selectable(d)).map((d) => d.date);
  if (colDates.length === 0) return;
  const allIn = colDates.every((d) => selected.value.includes(d));
  selected.value = allIn
    ? selected.value.filter((d) => !colDates.includes(d))
    : [...new Set([...selected.value, ...colDates])];
}

/** 快捷选择：scope=month 全月 / workday 周一~五 / weekend 周末（追加语义） */
function quickSelect(scope: 'month' | 'weekend' | 'workday') {
  const dates = days.value
    .filter((d) => {
      if (!selectable(d)) return false;
      if (scope === 'workday') return d.week <= 5;
      if (scope === 'weekend') return d.week >= 6;
      return true;
    })
    .map((d) => d.date);
  selected.value = [...new Set([...selected.value, ...dates])];
}

/** 清空月历选择 */
function clearSelected() {
  selected.value = [];
  anchorDate.value = '';
}

/** 把 ScheduleDaySlot（含 slot_id/used）萃取成可提交的四元组时段 */
function pickSlots(list: Array<Record<string, any>>): ScheduleSlot[] {
  return (list || []).map((s) => ({
    slot_name: String(s.slot_name || ''),
    start_time: String(s.start_time || ''),
    end_time: String(s.end_time || ''),
    capacity: Number(s.capacity || 0),
  }));
}

/** 时段摘要（剪贴板提示用，如「上午20 / 下午15」） */
function slotSummary(slots: ScheduleSlot[]): string {
  return slots.map((s) => `${s.slot_name}${s.capacity}`).join(' / ');
}

// ---------------- 批量提交 ----------------

/**
 * 月历视图批量提交：对已选日期 × 已选医生统一下发动作
 * 成功后清空选择并刷新月历，保证格子显示与库内一致
 */
async function applyAction(
  action: 'apply_template' | 'clear' | 'custom' | 'rest',
  extra: Record<string, any> = {},
) {
  if (view.value === 'matrix') {
    await applyMatrixAction(action, extra);
    return;
  }
  const doctorIds = batchDoctorIds.value.length > 0
    ? batchDoctorIds.value
    : (doctorId.value ? [doctorId.value] : []);
  if (doctorIds.length === 0) {
    message.warning('请先选择医生');
    return;
  }
  if (selected.value.length === 0) {
    message.warning('请先在日历上选择日期');
    return;
  }
  saving.value = true;
  try {
    await saveScheduleDaysApi({
      doctor_ids: doctorIds,
      store_id: props.storeId,
      dates: selected.value,
      action,
      ...extra,
    });
    message.success(
      doctorIds.length > 1
        ? `已更新 ${doctorIds.length} 位医生 × ${selected.value.length} 天排班`
        : `已更新 ${selected.value.length} 天排班`,
    );
    clearSelected();
    customOpen.value = false;
    invalidateDuty();
    await fetchCalendar();
  } catch {
    // 错误提示由 requestClient 统一弹出（如已约保护拦截，报错点名医生+日期）
  } finally {
    saving.value = false;
  }
}

/**
 * 矩阵视图批量提交：把（医生,日期）选择按「日期集合相同的医生」分组依次提交
 * 矩形选择时只有 1 组（原子）；散选多组时任一组失败即停止，已成功组保持生效
 */
async function applyMatrixAction(
  action: 'apply_template' | 'clear' | 'custom' | 'rest',
  extra: Record<string, any> = {},
) {
  const groups = matrixRef.value?.getSelectionGroups() ?? [];
  if (groups.length === 0) {
    message.warning('请先在总览表格上选择格子');
    return;
  }
  saving.value = true;
  try {
    for (const group of groups) {
      await saveScheduleDaysApi({
        doctor_ids: group.doctorIds,
        store_id: props.storeId,
        dates: group.dates,
        action,
        ...extra,
      });
    }
    message.success(`已更新 ${matrixCount.value} 个排班格`);
    matrixRef.value?.clearSelection();
    customOpen.value = false;
    invalidateDuty();
    await matrixRef.value?.fetchMatrix();
  } catch {
    // 组间部分成功时后端已约保护会点名冲突医生+日期，用户剔除后重试即可
    invalidateDuty();
    await matrixRef.value?.fetchMatrix();
  } finally {
    saving.value = false;
  }
}

/** 套用班次模板（操作条按钮） */
function applyTemplate() {
  if (!templateId.value) {
    message.warning('请先选择班次模板');
    return;
  }
  applyAction('apply_template', { template_id: templateId.value });
}

/** 打开自定义时段面板（默认给一个时段起步） */
function openCustom() {
  if (view.value === 'calendar' && selected.value.length === 0) {
    message.warning('请先在日历上选择日期');
    return;
  }
  if (view.value === 'matrix' && matrixCount.value === 0) {
    message.warning('请先在总览表格上选择格子');
    return;
  }
  if (customSlots.value.length === 0) {
    customSlots.value = [
      { slot_name: '', start_time: '08:00', end_time: '12:00', capacity: 10 },
    ];
  }
  customOpen.value = true;
}

/** 应用自定义时段（轻校验后提交，强校验在服务端） */
function applyCustom() {
  for (const slot of customSlots.value) {
    if (!slot.start_time || !slot.end_time) {
      message.warning('请补全各时段的开始/结束时间');
      return;
    }
    if (!slot.capacity || slot.capacity < 1) {
      message.warning('各时段号源数量至少为 1');
      return;
    }
  }
  applyAction('custom', { slots: customSlots.value });
}

/** 打开班次模板管理（带上店内医生列表，供「医生专属模板」绑定选择） */
function openTemplateModal() {
  templateModalRef.value?.ModalApi?.setData({
    storeId: props.storeId,
    storeName: props.storeName,
    doctors: doctorOptions.value,
  });
  templateModalRef.value?.ModalApi?.open();
}

// ---------------- 选出诊医生 ----------------

/**
 * 一键勾选「已选日期中出诊的医生」到批量医生集合
 * 典型场景：周三临时停诊 → 选中周三 → 一键选中当天出诊医生 → 批量设休诊
 */
async function pickWorkingDoctors() {
  if (selected.value.length === 0) {
    message.warning('请先在日历上选择日期');
    return;
  }
  const data = await getWorkingDoctorsApi(props.storeId, selected.value);
  const hits = (Array.isArray(data?.list) ? data.list : []).filter(
    (d: any) => Number(d.working_days) > 0,
  );
  if (hits.length === 0) {
    message.info('所选日期没有出诊的医生');
    return;
  }
  batchDoctorIds.value = hits.map((d: any) => Number(d.id));
  message.success(`已选中 ${hits.length} 位出诊医生：${hits.map((d: any) => d.name).join('、')}`);
}

// ---------------- 右键菜单 ----------------

/** 月历格子右键：不在选区内则替换选区为该日，再弹菜单 */
function onDayCtx(day: ScheduleCalendarDay, e: MouseEvent) {
  if (!selectable(day)) return;
  if (!selected.value.includes(day.date)) {
    selected.value = [day.date];
    anchorDate.value = day.date;
  }
  ctxTarget.value = {
    view: 'calendar',
    cellSlots: day.is_work && !day.is_rest ? pickSlots(day.slots) : [],
  };
  ctxMenuRef.value?.open(e, {
    title: `${dayjs(day.date).format('M月D日')} · 已选 ${selected.value.length} 天`,
    copyEnabled: ctxTarget.value.cellSlots.length > 0,
    // 添加医生排班只在非锁定的月历入口开放（锁定医生入口没有选人场景）
    addDoctorsEnabled: !isLocked.value,
  });
}

/** 矩阵格子右键（子组件已完成选区归一，这里组装菜单上下文） */
function onMatrixCellCtx(e: MouseEvent, doctor: MatrixDoctor, cell: MatrixCell) {
  ctxTarget.value = {
    view: 'matrix',
    cellSlots: cell.is_rest ? [] : pickSlots(cell.slots),
  };
  ctxMenuRef.value?.open(e, {
    title: `${doctor.name} · ${dayjs(cell.date).format('M月D日')} · 已选 ${matrixCount.value} 格`,
    copyEnabled: ctxTarget.value.cellSlots.length > 0,
  });
}

/** 打开「添加医生排班」弹窗（右键选区日期 + 全店医生 + 启用模板作为上下文） */
function openAddDoctorsModal() {
  if (selected.value.length === 0) {
    message.warning('请先在日历上选择日期');
    return;
  }
  addModalRef.value?.ModalApi?.setData({
    storeId: props.storeId,
    dates: [...selected.value],
    doctors: doctorOptions.value,
    templates: enabledTemplates.value,
  });
  addModalRef.value?.ModalApi?.open();
}

/** 添加医生排班保存成功：刷新月历 + 失效值班缓存（保留日期选择便于继续操作） */
function onAddDoctorsSaved() {
  invalidateDuty();
  fetchCalendar();
}

/** 右键菜单动作分发（复制进剪贴板 / 粘贴走 custom / 其余直接批量下发） */
function onCtxAction(
  type: 'add_doctors' | 'apply_template' | 'clear' | 'copy' | 'custom' | 'paste' | 'rest',
  payload?: { templateId?: number },
) {
  switch (type) {
    case 'add_doctors': {
      openAddDoctorsModal();
      break;
    }
    case 'apply_template': {
      applyAction('apply_template', { template_id: payload?.templateId });
      break;
    }
    case 'copy': {
      const slots = ctxTarget.value?.cellSlots ?? [];
      if (slots.length === 0) return;
      clipboard.value = { slots, summary: slotSummary(slots) };
      message.success(`已复制排班：${clipboard.value.summary}`);
      break;
    }
    case 'custom': {
      openCustom();
      break;
    }
    case 'paste': {
      if (!clipboard.value) return;
      applyAction('custom', { slots: clipboard.value.slots });
      break;
    }
    default: {
      // rest / clear：右键快捷语义直接执行；已约冲突由后端保护拦截并点名
      applyAction(type);
    }
  }
}

/** 门店变化（设置页 tab 异步拿到本店 id / 弹窗复用切店）时整体初始化 */
watch(
  () => props.storeId,
  (val) => {
    if (!val) return;
    doctorId.value = isLocked.value ? Number(props.lockedDoctorId) : undefined;
    month.value = dayjs().format('YYYY-MM');
    clearSelected();
    customOpen.value = false;
    templateId.value = undefined;
    days.value = [];
    // 切店后值班数据全部作废（缓存与当前展示都清）
    dutyCache.clear();
    dutyDate.value = '';
    dutyDoctors.value = [];
    fetchDoctors().then(() => fetchCalendar());
    fetchTemplates();
  },
  { immediate: true },
);
</script>

<template>
  <div class="sched">
    <!-- 顶部工具栏：视图切换 + 医生 + 月份 + 模板管理 -->
    <div class="sched__toolbar">
      <div class="sched__toolbar-left">
        <Segmented
          v-if="!isLocked"
          :options="[
            { label: '月历', value: 'calendar' },
            { label: '总览', value: 'matrix' },
          ]"
          :value="view"
          @change="onViewChange"
        />
        <template v-if="view === 'calendar'">
          <span v-if="isLocked" class="sched__locked-doctor">
            <Icon icon="lucide:user" />
            {{ lockedDoctorName || `医生 #${lockedDoctorId}` }}
          </span>
          <Select
            v-else
            v-model:value="doctorId"
            :field-names="{ label: 'name', value: 'id' }"
            :filter-option="
              (input: string, option: any) =>
                String(option.name || '').toLowerCase().includes(input.toLowerCase())
            "
            :options="doctorOptions"
            placeholder="选择医生"
            show-search
            style="width: 160px"
            @change="onDoctorChange"
          />
          <div class="sched__month-nav">
            <Button :disabled="!canPrev" size="small" type="text" @click="switchMonth(-1)">
              <Icon icon="lucide:chevron-left" />
            </Button>
            <span class="sched__month-text">{{ dayjs(month).format('YYYY年M月') }}</span>
            <Button :disabled="!canNext" size="small" type="text" @click="switchMonth(1)">
              <Icon icon="lucide:chevron-right" />
            </Button>
          </div>
          <span v-if="isDefaultRule && doctorId" class="sched__default-tip">
            该医生未配置默认排班，未单独排班的日期按「每天全天」放号
          </span>
          <Tooltip
            :title="`患者端当前可预约未来 ${bookDays} 天（含今天）内的号源；更远的排班会在日期进入窗口后自动开放预约`"
          >
            <span v-if="doctorId" class="sched__window-tip">
              <Icon icon="lucide:info" />
              可约窗口 {{ bookDays }} 天
            </span>
          </Tooltip>
        </template>
      </div>
      <div class="sched__toolbar-right">
        <Button size="small" @click="openTemplateModal">
          <Icon icon="lucide:layers" />
          班次模板
        </Button>
      </div>
    </div>

    <!-- 月历视图 -->
    <template v-if="view === 'calendar'">
      <!-- 快捷选择 -->
      <div class="sched__quick">
        <span class="sched__quick-label">快捷选择：</span>
        <Button size="small" type="link" @click="quickSelect('month')">整月</Button>
        <Button size="small" type="link" @click="quickSelect('workday')">工作日</Button>
        <Button size="small" type="link" @click="quickSelect('weekend')">周末</Button>
        <span class="sched__quick-count">
          已选 <b>{{ selected.length }}</b> 天
          <Button v-if="selected.length > 0" size="small" type="link" @click="clearSelected">
            清空
          </Button>
        </span>
        <span class="sched__select-tip">点击单选 · Ctrl 追加 · Shift 连选 · 右键快捷操作</span>
        <span class="sched__legend">
          <span class="sched__legend-item sched__legend-item--day">按日排班</span>
          <span class="sched__legend-item sched__legend-item--rule">规则排班</span>
          <span class="sched__legend-item sched__legend-item--rest">休诊</span>
        </span>
      </div>

      <!-- 左右布局：左侧月历主体，右侧医生标签墙（锁定医生时只有左侧） -->
      <div class="sched__body">
        <div class="sched__main">
          <div
            v-if="doctorId"
            class="sched__calendar"
            :class="{ 'sched__calendar--loading': loading }"
          >
            <div class="sched__week-head">
              <Tooltip v-for="head in WEEK_HEADS" :key="head.value" title="点击全选该列">
                <button class="sched__week-cell" type="button" @click="toggleWeekColumn(head.value)">
                  {{ head.label }}
                </button>
              </Tooltip>
            </div>
            <div class="sched__grid">
              <div v-for="i in leadingBlanks" :key="`blank-${i}`" class="sched__cell sched__cell--blank"></div>
              <div
                v-for="day in days"
                :key="day.date"
                class="sched__cell"
                :class="{
                  'sched__cell--past': day.is_past,
                  'sched__cell--today': day.is_today,
                  'sched__cell--selected': selected.includes(day.date),
                  'sched__cell--day-source': day.source === 'day' && !day.is_rest,
                  'sched__cell--rest': day.is_rest,
                }"
                @click="onDayClick(day, $event)"
                @contextmenu.prevent="onDayCtx(day, $event)"
              >
                <div class="sched__cell-head">
                  <span class="sched__cell-date">{{ dayjs(day.date).date() }}</span>
                  <span v-if="day.is_today" class="sched__cell-today-badge">今天</span>
                  <span v-if="day.used_total > 0" class="sched__cell-used">已约{{ day.used_total }}</span>
                </div>
                <div class="sched__cell-body">
                  <template v-if="day.is_rest">
                    <span class="sched__chip sched__chip--rest">休诊</span>
                  </template>
                  <template v-else-if="day.is_work">
                    <!-- 未配置排班医生的兜底日：不伪装成排班 chip，弱化成文字，让真排班（绿色日排）一眼可辨 -->
                    <span
                      v-if="isDefaultRule && day.source === 'rule'"
                      class="sched__cell-default"
                    >
                      默认可约
                    </span>
                    <template v-else>
                      <span
                        v-for="slot in day.slots.slice(0, 3)"
                        :key="slot.slot_id"
                        class="sched__chip"
                        :class="day.source === 'day' ? 'sched__chip--day' : 'sched__chip--rule'"
                      >
                        {{ slot.slot_name }} {{ slot.capacity }}号
                      </span>
                      <span v-if="day.slots.length > 3" class="sched__chip sched__chip--more">
                        +{{ day.slots.length - 3 }}
                      </span>
                    </template>
                  </template>
                  <template v-else>
                    <span class="sched__cell-off">不出诊</span>
                  </template>
                </div>
                <!-- 全店视角：该日按日排班安排的值班医生数（格子只画当前医生，
                     没有这个徽标会误以为「这天没配置值班」；点击格子右栏看明细） -->
                <span
                  v-if="(day.store_duty_count || 0) > 0"
                  class="sched__cell-duty"
                  title="当日全店已安排的值班医生数，点击查看明细"
                >
                  <Icon icon="lucide:users" />
                  值班 {{ day.store_duty_count }} 人
                </span>
              </div>
            </div>
          </div>
          <Empty
            v-else
            :description="doctorOptions.length === 0 ? '该门店暂无医生' : '请先选择医生'"
          />
        </div>

        <!-- 右栏：双页签（当日值班 / 批量排班；锁定医生入口不显示） -->
        <aside v-if="!isLocked" class="sched__side">
          <Segmented
            :options="[
              { label: '当日值班', value: 'duty' },
              { label: '批量排班', value: 'batch' },
            ]"
            :value="sideTab"
            block
            size="small"
            @change="onSideTabChange"
          />
          <!-- 当日值班：单击日历日期联动展示（排班方式 + 各时段人数 + 医生列表） -->
          <template v-if="sideTab === 'duty'">
            <Empty
              v-if="!dutyDate"
              :image-style="{ height: '48px' }"
              description="点击日历上的日期，查看当天全店值班"
            />
            <template v-else>
              <div class="sched__side-head">
                <span class="sched__side-title">{{ dutyDateText }}</span>
                <span class="sched__duty-summary">
                  出诊 {{ dutyWorking.length }} · 未排/休 {{ dutyOff.length }}
                </span>
              </div>
              <div v-if="dutyLoading" class="sched__duty-loading">
                <Spin size="small" />
              </div>
              <template v-else>
                <!-- 时段分布：每个时间段多少医生值班 -->
                <div v-if="dutySlotGroups.length > 0" class="sched__duty-slots">
                  <div v-for="g in dutySlotGroups" :key="g.key" class="sched__duty-slot">
                    <span class="sched__duty-slot-name">{{ g.name }}</span>
                    <span class="sched__duty-slot-time">{{ g.time }}</span>
                    <span class="sched__duty-slot-count">{{ g.count }} 人</span>
                  </div>
                </div>
                <div v-else class="sched__side-tip">
                  当天暂无排班{{
                    dutyDefaultCount > 0 ? `，${dutyDefaultCount} 位医生按默认全天可约` : ''
                  }}
                </div>
                <!-- 值班医生列表：点击切换左侧日历到该医生 -->
                <div class="sched__tags">
                  <button
                    v-for="doc in dutyWorking"
                    :key="doc.id"
                    class="sched__tag"
                    :class="{ 'sched__tag--viewing': doctorId === doc.id }"
                    type="button"
                    @click="focusDoctorCalendar(doc.id)"
                  >
                    <Avatar :size="22" :src="doc.avatar || undefined" class="sched__tag-avatar">
                      {{ doc.name.slice(0, 1) }}
                    </Avatar>
                    <span class="sched__duty-doc-main">
                      <span class="sched__tag-name">{{ doc.name }}</span>
                      <span class="sched__duty-doc-slots">{{ doc.summary }}</span>
                    </span>
                    <span class="sched__duty-source" :class="`sched__duty-source--${doc.source}`">
                      {{ doc.sourceText }}
                    </span>
                  </button>
                  <!-- 休诊/未排医生折叠一行，点击展开 -->
                  <button
                    v-if="dutyOff.length > 0"
                    class="sched__duty-off-toggle"
                    type="button"
                    @click="dutyOffExpanded = !dutyOffExpanded"
                  >
                    <Icon :icon="dutyOffExpanded ? 'lucide:chevron-down' : 'lucide:chevron-right'" />
                    休诊/未排 {{ dutyOff.length }} 人
                  </button>
                  <template v-if="dutyOffExpanded">
                    <button
                      v-for="doc in dutyOff"
                      :key="doc.id"
                      class="sched__tag sched__tag--off"
                      type="button"
                      @click="focusDoctorCalendar(doc.id)"
                    >
                      <Avatar :size="22" :src="doc.avatar || undefined" class="sched__tag-avatar">
                        {{ doc.name.slice(0, 1) }}
                      </Avatar>
                      <span class="sched__tag-name">{{ doc.name }}</span>
                      <span
                        class="sched__duty-source"
                        :class="
                          doc.isRest
                            ? 'sched__duty-source--rest'
                            : doc.isDefault
                              ? 'sched__duty-source--default'
                              : 'sched__duty-source--none'
                        "
                      >
                        {{ doc.isRest ? '休诊' : doc.isDefault ? '默认可约' : '未排' }}
                      </span>
                    </button>
                  </template>
                </div>
              </template>
            </template>
          </template>
          <!-- 批量排班：医生头像标签墙（多选批量排班对象） -->
          <template v-else>
            <div class="sched__side-head">
              <span class="sched__side-title">批量排班医生</span>
              <span class="sched__side-count">已选 {{ batchDoctorIds.length }} 人</span>
            </div>
            <div class="sched__side-tools">
              <Tooltip title="按已选日期查询出诊医生并一键勾选（日排班覆盖优先，未配置规则的按每天出诊算）">
                <Button size="small" @click="pickWorkingDoctors">
                  <Icon icon="lucide:user-check" />
                  选出诊
                </Button>
              </Tooltip>
              <Button size="small" @click="selectAllDoctors">全选</Button>
              <Button :disabled="batchDoctorIds.length === 0" size="small" @click="clearBatchDoctors">
                清空
              </Button>
            </div>
            <div class="sched__side-tip">单击选择 · 双击查看该医生月历</div>
            <div class="sched__tags">
              <button
                v-for="doc in doctorOptions"
                :key="doc.id"
                class="sched__tag"
                :class="{
                  'sched__tag--active': batchDoctorIds.includes(doc.id),
                  'sched__tag--viewing': doctorId === doc.id,
                }"
                type="button"
                @click="toggleBatchDoctor(doc.id)"
                @dblclick="viewDoctorCalendar(doc.id)"
              >
                <Avatar :size="22" :src="doc.avatar || undefined" class="sched__tag-avatar">
                  {{ doc.name.slice(0, 1) }}
                </Avatar>
                <span class="sched__tag-name">{{ doc.name }}</span>
                <Icon
                  v-if="doctorId === doc.id"
                  class="sched__tag-eye"
                  icon="lucide:calendar-check"
                />
              </button>
              <Empty
                v-if="doctorOptions.length === 0"
                :image-style="{ height: '48px' }"
                description="该门店暂无医生"
              />
            </div>
          </template>
        </aside>
      </div>
    </template>

    <!-- 总览（矩阵）视图 -->
    <ScheduleMatrixView
      v-else
      ref="matrixRef"
      :store-id="storeId"
      @cell-ctx="onMatrixCellCtx"
      @selection-change="(n: number) => (matrixCount = n)"
    />

    <!-- 底部批量操作条（两视图共用动作，选择来源不同；月历医生多选在右栏标签墙） -->
    <div class="sched__actions">
      <div class="sched__actions-row">
        <span class="sched__actions-label">
          {{
            view === 'calendar'
              ? `对 ${effectiveBatchCount} 位医生 × ${selected.length} 天：`
              : `对已选 ${matrixCount} 格：`
          }}
        </span>
        <Select
          v-model:value="templateId"
          :field-names="{ label: 'name', value: 'id' }"
          :options="generalTemplates"
          placeholder="选择班次模板"
          style="width: 170px"
        />
        <Button :loading="saving" type="primary" @click="applyTemplate">套用模板</Button>
        <Button :loading="saving" @click="openCustom">自定义时段</Button>
        <Popconfirm
          title="设为休诊后患者不可预约（已有预约的日期会被拦截），确定吗？"
          @confirm="applyAction('rest')"
        >
          <Button :loading="saving" danger>设休诊</Button>
        </Popconfirm>
        <Popconfirm
          title="恢复默认后这些日期回到医生默认排班规则，确定吗？"
          @confirm="applyAction('clear')"
        >
          <Button :loading="saving">恢复默认规则</Button>
        </Popconfirm>
      </div>
      <!-- 自定义时段展开面板 -->
      <div v-if="customOpen" class="sched__custom">
        <SlotRowsEditor v-model="customSlots" />
        <div class="sched__custom-actions">
          <Button size="small" @click="customOpen = false">收起</Button>
          <Button :loading="saving" size="small" type="primary" @click="applyCustom">
            {{ view === 'calendar' ? `应用到已选 ${selected.length} 天` : `应用到已选 ${matrixCount} 格` }}
          </Button>
        </div>
      </div>
    </div>

    <ShiftTemplateModal ref="templateModalRef" @changed="fetchTemplates" />
    <AddDoctorScheduleModal ref="addModalRef" @saved="onAddDoctorsSaved" />
    <ScheduleContextMenu
      ref="ctxMenuRef"
      :can-paste="!!clipboard"
      :paste-summary="clipboard?.summary || ''"
      :templates="generalTemplates"
      @action="onCtxAction"
    />
  </div>
</template>

<style scoped>
.sched {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.sched__toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
}

.sched__toolbar-left {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

.sched__locked-doctor {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  padding: 3px 10px;
  font-size: 13px;
  font-weight: 600;
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 0.08);
  border: 1px solid hsl(var(--primary) / 30%);
  border-radius: 8px;
  box-shadow: 0 0 6px hsl(var(--primary) / 18%);
}

.sched__month-nav {
  display: flex;
  gap: 2px;
  align-items: center;
}

.sched__month-text {
  min-width: 92px;
  font-size: 14px;
  font-weight: 600;
  color: hsl(var(--foreground));
  text-align: center;
}

.sched__default-tip {
  font-size: 12px;
  color: hsl(var(--warning, 35 92% 50%));
}

.sched__window-tip {
  display: inline-flex;
  gap: 3px;
  align-items: center;
  font-size: 12px;
  color: hsl(var(--muted-foreground));
  cursor: help;
}

.sched__quick {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  align-items: center;
}

.sched__quick-label,
.sched__quick-count {
  font-size: 12px;
  color: hsl(var(--muted-foreground));
}

.sched__quick-count {
  margin-left: 8px;
}

.sched__quick-count b {
  color: hsl(var(--primary));
}

.sched__select-tip {
  margin-left: 10px;
  font-size: 12px;
  color: hsl(var(--muted-foreground) / 0.75);
}

.sched__legend {
  display: flex;
  gap: 10px;
  margin-left: auto;
}

.sched__legend-item {
  position: relative;
  padding-left: 14px;
  font-size: 12px;
  color: hsl(var(--muted-foreground));
}

.sched__legend-item::before {
  position: absolute;
  top: 50%;
  left: 0;
  width: 8px;
  height: 8px;
  content: '';
  border-radius: 3px;
  transform: translateY(-50%);
}

.sched__legend-item--day::before {
  background: hsl(var(--primary) / 0.8);
}

.sched__legend-item--rule::before {
  background: hsl(var(--muted-foreground) / 0.5);
}

.sched__legend-item--rest::before {
  background: hsl(var(--destructive) / 0.7);
}

/* 左右布局：左侧日历自适应，右侧医生标签墙固定宽 */
.sched__body {
  display: flex;
  gap: 12px;
  align-items: stretch;
}

.sched__main {
  flex: 1;
  min-width: 0;
}

/* 右栏：Apple 风格卡片（1px 边框 + 微光），窄屏自动换行到下方 */
.sched__side {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 216px;
  min-width: 216px;
  max-height: 560px;
  padding: 10px;
  border: 1px solid hsl(var(--border));
  border-radius: 12px;
  box-shadow: 0 0 6px hsl(var(--border) / 40%);
}

@media (max-width: 860px) {
  .sched__body {
    flex-direction: column;
  }

  .sched__side {
    width: 100%;
    min-width: 0;
    max-height: 300px;
  }
}

.sched__side-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}

.sched__side-title {
  font-size: 13px;
  font-weight: 600;
  color: hsl(var(--foreground));
}

.sched__side-count {
  font-size: 12px;
  color: hsl(var(--primary));
}

.sched__side-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.sched__side-tip {
  font-size: 11px;
  color: hsl(var(--muted-foreground) / 0.75);
}

.sched__tags {
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow-y: auto;
}

/* 医生标签：头像 + 姓名 pill，选中态主色边框微光 */
.sched__tag {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 5px 8px;
  text-align: left;
  cursor: pointer;
  user-select: none;
  background: hsl(var(--muted) / 0.2);
  border: 1px solid hsl(var(--border) / 0.7);
  border-radius: 10px;
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}

.sched__tag:hover {
  background: hsl(var(--primary) / 0.06);
  border-color: hsl(var(--primary) / 0.35);
}

.sched__tag--active {
  background: hsl(var(--primary) / 0.1);
  border-color: hsl(var(--primary) / 0.45);
  box-shadow: 0 0 6px hsl(var(--primary) / 18%);
}

.sched__tag-avatar {
  flex-shrink: 0;
  font-size: 12px;
  background: hsl(var(--primary) / 0.2);
}

.sched__tag-name {
  flex: 1;
  overflow: hidden;
  font-size: 13px;
  color: hsl(var(--foreground));
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sched__tag--active .sched__tag-name {
  font-weight: 600;
  color: hsl(var(--primary));
}

/* 正在查看月历的医生：日历角标点亮 */
.sched__tag-eye {
  flex-shrink: 0;
  font-size: 14px;
  color: hsl(var(--primary));
}

/* ---------- 当日值班视图 ---------- */
.sched__duty-summary {
  font-size: 12px;
  color: hsl(var(--muted-foreground));
}

.sched__duty-loading {
  display: flex;
  justify-content: center;
  padding: 18px 0;
}

/* 时段分布卡：1px 边框 + 微光（不用左侧色条） */
.sched__duty-slots {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px;
  border: 1px solid hsl(var(--primary) / 30%);
  border-radius: 10px;
  box-shadow: 0 0 6px hsl(var(--primary) / 18%);
}

.sched__duty-slot {
  display: flex;
  gap: 6px;
  align-items: center;
  font-size: 12px;
}

.sched__duty-slot-name {
  max-width: 64px;
  overflow: hidden;
  font-weight: 600;
  color: hsl(var(--foreground));
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sched__duty-slot-time {
  flex: 1;
  color: hsl(var(--muted-foreground));
}

.sched__duty-slot-count {
  font-weight: 600;
  color: hsl(var(--primary));
}

/* 值班医生行：姓名 + 时段容量摘要两行 */
.sched__duty-doc-main {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.sched__duty-doc-slots {
  overflow: hidden;
  font-size: 11px;
  color: hsl(var(--muted-foreground));
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 排班来源角标：日排=主色 / 规则=中性 / 默认=弱化 / 休诊=警示 */
.sched__duty-source {
  flex-shrink: 0;
  padding: 0 6px;
  font-size: 11px;
  line-height: 18px;
  border-radius: 5px;
}

.sched__duty-source--day {
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 0.12);
}

.sched__duty-source--rule {
  color: hsl(var(--muted-foreground));
  background: hsl(var(--muted) / 0.4);
}

.sched__duty-source--default {
  color: hsl(var(--muted-foreground) / 0.8);
  background: hsl(var(--muted) / 0.25);
}

.sched__duty-source--rest {
  color: hsl(var(--destructive));
  background: hsl(var(--destructive) / 0.12);
}

.sched__duty-source--none {
  color: hsl(var(--muted-foreground) / 0.7);
  background: hsl(var(--muted) / 0.25);
}

/* 休诊/未排折叠行 */
.sched__duty-off-toggle {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  padding: 4px 2px;
  font-size: 12px;
  color: hsl(var(--muted-foreground));
  cursor: pointer;
  background: none;
  border: none;
}

.sched__duty-off-toggle:hover {
  color: hsl(var(--primary));
}

/* 休诊/未排医生行弱化展示 */
.sched__tag--off {
  opacity: 0.72;
}

/* 日历主体：Apple 风格圆角卡片网格 */
.sched__calendar {
  overflow: hidden;
  border: 1px solid hsl(var(--border));
  border-radius: 12px;
  transition: opacity 0.2s;
}

.sched__calendar--loading {
  pointer-events: none;
  opacity: 0.55;
}

.sched__week-head {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  background: hsl(var(--muted) / 0.35);
  border-bottom: 1px solid hsl(var(--border));
}

.sched__week-cell {
  padding: 8px 0;
  font-size: 12px;
  font-weight: 600;
  color: hsl(var(--muted-foreground));
  cursor: pointer;
  background: none;
  border: none;
  transition: color 0.15s, background 0.15s;
}

.sched__week-cell:hover {
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 0.08);
}

.sched__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}

.sched__cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 86px;
  padding: 6px;
  cursor: pointer;
  user-select: none;
  border-right: 1px solid hsl(var(--border) / 0.6);
  border-bottom: 1px solid hsl(var(--border) / 0.6);
  transition: background 0.15s, box-shadow 0.15s;
}

.sched__cell:nth-child(7n) {
  border-right: none;
}

.sched__cell--blank {
  cursor: default;
  background: hsl(var(--muted) / 0.15);
}

.sched__cell--past {
  cursor: not-allowed;
  opacity: 0.45;
}

.sched__cell:not(.sched__cell--past, .sched__cell--blank):hover {
  background: hsl(var(--primary) / 0.06);
}

/* 选中态：主色内描边 + 微光（不用左侧色条） */
.sched__cell--selected {
  background: hsl(var(--primary) / 0.1);
  box-shadow: inset 0 0 0 2px hsl(var(--primary) / 0.55), 0 0 6px hsl(var(--primary) / 18%);
}

/* 日排班来源：同色系细描边微光，与默认规则区分 */
.sched__cell--day-source:not(.sched__cell--selected) {
  box-shadow: inset 0 0 0 1px hsl(var(--primary) / 0.3);
}

.sched__cell--rest:not(.sched__cell--selected) {
  background: hsl(var(--destructive) / 0.05);
}

.sched__cell-head {
  display: flex;
  gap: 4px;
  align-items: center;
}

.sched__cell-date {
  font-size: 13px;
  font-weight: 600;
  color: hsl(var(--foreground));
}

.sched__cell--today .sched__cell-date {
  color: hsl(var(--primary));
}

.sched__cell-today-badge {
  padding: 0 4px;
  font-size: 10px;
  color: hsl(var(--primary-foreground, 0 0% 100%));
  background: hsl(var(--primary));
  border-radius: 4px;
}

.sched__cell-used {
  margin-left: auto;
  font-size: 10px;
  color: hsl(var(--warning, 35 92% 50%));
}

.sched__cell-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow: hidden;
}

.sched__chip {
  padding: 0 6px;
  overflow: hidden;
  font-size: 11px;
  line-height: 18px;
  text-overflow: ellipsis;
  white-space: nowrap;
  border-radius: 5px;
}

.sched__chip--day {
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 0.12);
}

.sched__chip--rule {
  color: hsl(var(--muted-foreground));
  background: hsl(var(--muted) / 0.4);
}

.sched__chip--rest {
  color: hsl(var(--destructive));
  background: hsl(var(--destructive) / 0.12);
}

.sched__chip--more {
  color: hsl(var(--muted-foreground));
}

.sched__cell-off {
  font-size: 11px;
  color: hsl(var(--muted-foreground) / 0.7);
}

/* 未配置排班的默认兜底日：比「不出诊」更弱化，避免被误读为已排班 */
.sched__cell-default {
  font-size: 11px;
  color: hsl(var(--muted-foreground) / 0.5);
}

/* 全店当日值班徽标：沉底展示，主色弱化与当前医生的排班 chip 区分层级 */
.sched__cell-duty {
  display: inline-flex;
  gap: 3px;
  align-items: center;
  margin-top: auto;
  font-size: 11px;
  color: hsl(var(--primary) / 0.85);
}

/* 底部操作条 */
.sched__actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
  background: hsl(var(--muted) / 0.2);
  border: 1px solid hsl(var(--border));
  border-radius: 10px;
}

.sched__actions-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.sched__actions-label {
  font-size: 12px;
  color: hsl(var(--muted-foreground));
}

.sched__custom {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 8px;
  border-top: 1px dashed hsl(var(--border));
}

.sched__custom-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
</style>
