<script lang="ts" setup>
/**
 * 排班矩阵总览视图（行=医生 × 列=日期，周粒度）
 * 医院排班表惯例：一屏看全店一周的出诊安排；格子支持标准桌面选择模型
 * （点击单选 / Ctrl 追加 / Shift 矩形范围）后由父面板批量下发排班动作。
 * 选择项是（医生,日期）组合，与月历的纯日期选择不同，所以批量提交时
 * 由 getSelectionGroups 把「日期集合相同的医生」合并成组，减少请求次数。
 */
import type { MatrixCell, MatrixDate, MatrixDoctor } from '../../api/schedule';

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

import { Button, Empty, Tooltip } from 'ant-design-vue';
import dayjs from 'dayjs';

import { Icon } from '#/components/icon';

import { getStoreMatrixApi } from '../../api/schedule';

const props = defineProps<{
  storeId: number;
}>();

const emit = defineEmits<{
  /** 右键格子：上抛给父面板统一弹右键菜单（含选区归一逻辑） */
  cellCtx: [event: MouseEvent, doctor: MatrixDoctor, cell: MatrixCell];
  /** 选中格数变化：父面板操作条展示「已选 N 格」 */
  selectionChange: [count: number];
}>();

/** 求某天所在周的周一（不依赖 dayjs locale 的 startOf('week') 起点差异） */
function mondayOf(d: dayjs.Dayjs) {
  const dow = d.day(); // 0=周日 1=周一 ... 6=周六
  return d.subtract(dow === 0 ? 6 : dow - 1, 'day').startOf('day');
}

/** 周起点（周一），矩阵固定 7 列一周 */
const weekStart = ref(mondayOf(dayjs()));
/** 日期轴与医生行 */
const dates = ref<MatrixDate[]>([]);
const doctors = ref<MatrixDoctor[]>([]);
const loading = ref(false);

/** 选中格集合，key = `${doctorId}|${date}` */
const selectedKeys = ref<Set<string>>(new Set());
/** Shift 范围选择锚点（行/列下标） */
const anchor = ref<null | { col: number; row: number }>(null);

/** 最早回看 12 周 / 最远排 26 周，防无限翻页 */
const canPrev = computed(() => weekStart.value.isAfter(dayjs().subtract(12, 'week')));
const canNext = computed(() => weekStart.value.isBefore(dayjs().add(26, 'week')));

/** 周标题（如 8月17日 - 8月23日） */
const weekTitle = computed(() => {
  const end = weekStart.value.add(6, 'day');
  return `${weekStart.value.format('M月D日')} - ${end.format('M月D日')}`;
});

function cellKey(doctorId: number, date: string) {
  return `${doctorId}|${date}`;
}

/** 拉取矩阵数据（切周/门店变化/父面板要求刷新时） */
async function fetchMatrix() {
  if (!props.storeId) return;
  loading.value = true;
  try {
    const data = await getStoreMatrixApi(props.storeId, weekStart.value.format('YYYY-MM-DD'), 7);
    dates.value = Array.isArray(data?.dates) ? data.dates : [];
    doctors.value = Array.isArray(data?.doctors) ? data.doctors : [];
  } finally {
    loading.value = false;
  }
}

/** 切周：清空选择（跨周选择容易误操作） */
function switchWeek(offset: number) {
  weekStart.value = weekStart.value.add(offset, 'week');
  clearSelection();
  fetchMatrix();
}

/** 回到本周 */
function backToThisWeek() {
  weekStart.value = mondayOf(dayjs());
  clearSelection();
  fetchMatrix();
}

/** 某格是否可选（过去日期只读） */
function selectable(cell: MatrixCell, colIdx: number) {
  return !(dates.value[colIdx]?.is_past ?? false) && !!cell;
}

function notifySelection() {
  emit('selectionChange', selectedKeys.value.size);
}

/**
 * 格子点击：标准桌面选择模型
 * 普通点击=只选该格（替换）；Ctrl/Cmd=追加/取消；Shift=从锚点到当前格的矩形范围并入
 */
function onCellClick(e: MouseEvent, rowIdx: number, colIdx: number) {
  const doctor = doctors.value[rowIdx];
  const cell = doctor?.cells[colIdx];
  if (!doctor || !cell || !selectable(cell, colIdx)) return;
  const key = cellKey(doctor.id, cell.date);
  const next = new Set(selectedKeys.value);

  if (e.shiftKey && anchor.value) {
    // 矩形范围：锚点与当前格围成的（医生行 × 日期列）区域全部并入（跳过过去日期）
    const [r1, r2] = [Math.min(anchor.value.row, rowIdx), Math.max(anchor.value.row, rowIdx)];
    const [c1, c2] = [Math.min(anchor.value.col, colIdx), Math.max(anchor.value.col, colIdx)];
    for (let r = r1; r <= r2; r++) {
      for (let c = c1; c <= c2; c++) {
        const d = doctors.value[r];
        const cc = d?.cells[c];
        if (d && cc && selectable(cc, c)) {
          next.add(cellKey(d.id, cc.date));
        }
      }
    }
  } else if (e.ctrlKey || e.metaKey) {
    if (next.has(key)) {
      next.delete(key);
    } else {
      next.add(key);
    }
    anchor.value = { row: rowIdx, col: colIdx };
  } else {
    next.clear();
    next.add(key);
    anchor.value = { row: rowIdx, col: colIdx };
  }
  selectedKeys.value = next;
  notifySelection();
}

/** 右键格子：不在选区内则替换选区为该格，然后上抛父面板弹菜单 */
function onCellCtx(e: MouseEvent, rowIdx: number, colIdx: number) {
  const doctor = doctors.value[rowIdx];
  const cell = doctor?.cells[colIdx];
  if (!doctor || !cell || !selectable(cell, colIdx)) return;
  const key = cellKey(doctor.id, cell.date);
  if (!selectedKeys.value.has(key)) {
    selectedKeys.value = new Set([key]);
    anchor.value = { row: rowIdx, col: colIdx };
    notifySelection();
  }
  emit('cellCtx', e, doctor, cell);
}

/** 点击日期表头：全选/反选该列（某天全员操作，如临时停业） */
function toggleColumn(colIdx: number) {
  const date = dates.value[colIdx];
  if (!date || date.is_past) return;
  const keys = doctors.value.map((d) => cellKey(d.id, date.date));
  const allIn = keys.every((k) => selectedKeys.value.has(k));
  const next = new Set(selectedKeys.value);
  for (const k of keys) {
    if (allIn) {
      next.delete(k);
    } else {
      next.add(k);
    }
  }
  selectedKeys.value = next;
  notifySelection();
}

/** 点击医生名：全选/反选该行的可选日期（给某医生排整周） */
function toggleRow(rowIdx: number) {
  const doctor = doctors.value[rowIdx];
  if (!doctor) return;
  const keys: string[] = [];
  doctor.cells.forEach((cell, c) => {
    if (selectable(cell, c)) keys.push(cellKey(doctor.id, cell.date));
  });
  if (keys.length === 0) return;
  const allIn = keys.every((k) => selectedKeys.value.has(k));
  const next = new Set(selectedKeys.value);
  for (const k of keys) {
    if (allIn) {
      next.delete(k);
    } else {
      next.add(k);
    }
  }
  selectedKeys.value = next;
  notifySelection();
}

function clearSelection() {
  selectedKeys.value = new Set();
  anchor.value = null;
  notifySelection();
}

/**
 * 把选中格按「日期集合相同的医生」合并成组（父面板批量调 save-days 用）
 * 矩形选择时全部医生日期一致 → 只有 1 组 1 次请求；散选时才会拆多组
 */
function getSelectionGroups(): Array<{ dates: string[]; doctorIds: number[] }> {
  const byDoctor = new Map<number, string[]>();
  for (const key of selectedKeys.value) {
    const [didStr, date] = key.split('|');
    const did = Number(didStr);
    if (!byDoctor.has(did)) byDoctor.set(did, []);
    byDoctor.get(did)!.push(date!);
  }
  const byDates = new Map<string, number[]>();
  for (const [did, ds] of byDoctor) {
    const k = [...ds].sort().join(',');
    if (!byDates.has(k)) byDates.set(k, []);
    byDates.get(k)!.push(did);
  }
  return [...byDates.entries()].map(([k, doctorIds]) => ({
    dates: k.split(','),
    doctorIds,
  }));
}

/** 门店切换时重置到本周并重拉 */
watch(
  () => props.storeId,
  () => {
    clearSelection();
    fetchMatrix();
  },
  { immediate: true },
);

defineExpose({ clearSelection, fetchMatrix, getSelectionGroups });
</script>

<template>
  <div class="smx" :class="{ 'smx--loading': loading }">
    <!-- 周导航 -->
    <div class="smx__nav">
      <Button :disabled="!canPrev" size="small" type="text" @click="switchWeek(-1)">
        <Icon icon="lucide:chevron-left" />
      </Button>
      <span class="smx__week-text">{{ weekTitle }}</span>
      <Button :disabled="!canNext" size="small" type="text" @click="switchWeek(1)">
        <Icon icon="lucide:chevron-right" />
      </Button>
      <Button size="small" type="link" @click="backToThisWeek">本周</Button>
      <span class="smx__nav-tip">点医生名选整行 · 点日期选整列 · Ctrl 散选 · Shift 框选</span>
    </div>

    <!-- 矩阵表格 -->
    <div v-if="doctors.length > 0" class="smx__table-wrap">
      <table class="smx__table">
        <thead>
          <tr>
            <th class="smx__corner">医生</th>
            <th
              v-for="(d, colIdx) in dates"
              :key="d.date"
              class="smx__date-head"
              :class="{ 'smx__date-head--today': d.is_today, 'smx__date-head--past': d.is_past }"
              @click="toggleColumn(colIdx)"
            >
              <div class="smx__date-week">周{{ d.week_text }}</div>
              <div class="smx__date-day">
                {{ d.day }}
                <span v-if="d.is_today" class="smx__today-dot"></span>
              </div>
            </th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(doctor, rowIdx) in doctors" :key="doctor.id">
            <td class="smx__doctor" @click="toggleRow(rowIdx)">
              <Tooltip title="点击全选该医生整周">
                <span class="smx__doctor-name">{{ doctor.name }}</span>
              </Tooltip>
            </td>
            <td
              v-for="(cell, colIdx) in doctor.cells"
              :key="cell.date"
              class="smx__cell"
              :class="{
                'smx__cell--past': dates[colIdx]?.is_past,
                'smx__cell--selected': selectedKeys.has(`${doctor.id}|${cell.date}`),
                'smx__cell--rest': cell.is_rest,
                'smx__cell--day-source': cell.source === 'day' && !cell.is_rest,
              }"
              @click="onCellClick($event, rowIdx, colIdx)"
              @contextmenu.prevent="onCellCtx($event, rowIdx, colIdx)"
            >
              <template v-if="cell.is_rest">
                <span class="smx__chip smx__chip--rest">休诊</span>
              </template>
              <template v-else>
                <!-- 未配置排班的默认兜底：弱化成文字不伪装成排班 chip，与月历视图口径一致 -->
                <span v-if="cell.source === 'default'" class="smx__default">默认可约</span>
                <template v-else>
                  <span
                    v-for="(name, i) in cell.slot_names.slice(0, 2)"
                    :key="i"
                    class="smx__chip"
                    :class="cell.source === 'day' ? 'smx__chip--day' : 'smx__chip--rule'"
                  >
                    {{ name }}
                  </span>
                  <span v-if="cell.slot_names.length > 2" class="smx__chip smx__chip--more">
                    +{{ cell.slot_names.length - 2 }}
                  </span>
                </template>
                <span class="smx__cell-num">
                  {{ cell.total }}号<template v-if="cell.used > 0">·约{{ cell.used }}</template>
                </span>
              </template>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    <Empty v-else description="该门店暂无医生" />
  </div>
</template>

<style scoped>
.smx {
  display: flex;
  flex-direction: column;
  gap: 8px;
  transition: opacity 0.2s;
}

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

.smx__nav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
}

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

.smx__nav-tip {
  margin-left: auto;
  font-size: 12px;
  color: hsl(var(--muted-foreground));
}

/* 矩阵表格：Apple 风圆角卡片容器 */
.smx__table-wrap {
  overflow: auto hidden;
  border: 1px solid hsl(var(--border));
  border-radius: 12px;
}

.smx__table {
  width: 100%;
  min-width: 720px;
  border-collapse: collapse;
  table-layout: fixed;
}

.smx__corner {
  width: 92px;
  padding: 8px;
  font-size: 12px;
  font-weight: 600;
  color: hsl(var(--muted-foreground));
  background: hsl(var(--muted) / 0.35);
  border-bottom: 1px solid hsl(var(--border));
}

.smx__date-head {
  padding: 6px 4px;
  text-align: center;
  cursor: pointer;
  user-select: none;
  background: hsl(var(--muted) / 0.35);
  border-bottom: 1px solid hsl(var(--border));
  border-left: 1px solid hsl(var(--border) / 0.6);
  transition: background 0.15s;
}

.smx__date-head:hover {
  background: hsl(var(--primary) / 0.08);
}

.smx__date-head--past {
  cursor: not-allowed;
  opacity: 0.5;
}

.smx__date-week {
  font-size: 11px;
  color: hsl(var(--muted-foreground));
}

.smx__date-day {
  position: relative;
  font-size: 14px;
  font-weight: 600;
  color: hsl(var(--foreground));
}

.smx__date-head--today .smx__date-day {
  color: hsl(var(--primary));
}

.smx__today-dot {
  position: absolute;
  bottom: -3px;
  left: 50%;
  width: 4px;
  height: 4px;
  background: hsl(var(--primary));
  border-radius: 50%;
  transform: translateX(-50%);
}

.smx__doctor {
  padding: 6px 8px;
  overflow: hidden;
  cursor: pointer;
  user-select: none;
  border-top: 1px solid hsl(var(--border) / 0.6);
  transition: background 0.15s;
}

.smx__doctor:hover {
  background: hsl(var(--primary) / 0.06);
}

.smx__doctor-name {
  display: block;
  overflow: hidden;
  font-size: 13px;
  font-weight: 500;
  color: hsl(var(--foreground));
  text-overflow: ellipsis;
  white-space: nowrap;
}

.smx__cell {
  height: 56px;
  padding: 4px;
  vertical-align: top;
  cursor: pointer;
  user-select: none;
  border-top: 1px solid hsl(var(--border) / 0.6);
  border-left: 1px solid hsl(var(--border) / 0.6);
  transition: background 0.15s, box-shadow 0.15s;
}

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

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

/* 选中态：主色内描边 + 微光（同月历视图语言） */
.smx__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%);
}

.smx__cell--day-source:not(.smx__cell--selected) {
  box-shadow: inset 0 0 0 1px hsl(var(--primary) / 0.3);
}

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

.smx__chip {
  display: inline-block;
  max-width: 100%;
  padding: 0 5px;
  margin: 0 2px 2px 0;
  overflow: hidden;
  font-size: 11px;
  line-height: 17px;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: top;
  border-radius: 4px;
}

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

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

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

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

.smx__cell-num {
  display: block;
  font-size: 10px;
  color: hsl(var(--muted-foreground));
}

/* 未配置排班的默认兜底格：弱化文字，避免被误读为已排班 */
.smx__default {
  display: block;
  font-size: 11px;
  color: hsl(var(--muted-foreground) / 0.5);
}
</style>
