<template>
  <div class="ele-body factory-calendar">
    <el-card shadow="never" class="factory-calendar-card">
      <div class="page-header">
        <div class="header-actions">
          <el-button
            size="small"
            type="primary"
            icon="el-icon-plus"
            class="ele-btn-icon"
            @click="openCalendarDialog()"
          >
            新增日历
          </el-button>
        </div>
      </div>

      <el-tabs v-model="activeTab" @tab-click="handleTabChange">
        <el-tab-pane label="基础管理" name="base">
          <base-manage ref="baseManage" :vm="vm" />
        </el-tab-pane>

        <el-tab-pane label="多维日历视图" name="view">
          <multi-calendar-view :vm="vm" />
        </el-tab-pane>

        <el-tab-pane label="临时调整审批" name="adjust">
          <adjust-approval ref="adjustApproval" :vm="vm" />
        </el-tab-pane>

        <el-tab-pane label="数据统计" name="stat">
          <statistics-view :vm="vm" />
        </el-tab-pane>
      </el-tabs>
    </el-card>

    <ele-modal
      :visible.sync="calendarDialogVisible"
      :title="calendarDialogTitle"
      width="900px"
      custom-class="ele-dialog-form factory-calendar-dialog"
      :close-on-click-modal="false"
    >
      <el-form
        ref="calendarForm"
        :model="calendarForm"
        :rules="calendarRules"
        label-width="110px"
        class="el-form-box"
      >
        <el-form-item label="日历编码">
          <el-input v-model="calendarForm.calendarCode" disabled />
        </el-form-item>
        <el-form-item label="日历名称" prop="calendarName">
          <el-input v-model="calendarForm.calendarName" maxlength="128" />
        </el-form-item>
        <el-form-item label="日历类型" prop="calendarType">
          <el-select
            v-model="calendarForm.calendarType"
            :disabled="!!calendarForm.id"
            style="width: 100%"
          >
            <el-option
              v-for="item in calendarTypeOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="适用年份" prop="applyYear">
          <el-select v-model="calendarFormApplyYear" style="width: 100%">
            <el-option
              v-for="year in yearOptions"
              :key="year"
              :label="year"
              :value="year"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="适用月份" prop="applyMonth">
          <el-select
            v-model="calendarForm.applyMonth"
            multiple
            style="width: 100%"
            @change="handleApplyMonthChange"
          >
            <el-option
              v-for="month in monthOptions"
              :key="month.value"
              :label="month.label"
              :value="month.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="状态">
          <el-switch
            v-model="calendarForm.status"
            :active-value="1"
            :inactive-value="0"
            active-text="启用"
            inactive-text="禁用"
          />
        </el-form-item>
        <el-form-item label="周休设置">
          <div class="rest-rule-editor">
            <el-radio-group v-model="calendarForm.restMode">
              <el-radio-button
                v-for="item in restModeOptions"
                :key="item.value"
                :label="item.value"
              >
                {{ item.label }}
              </el-radio-button>
            </el-radio-group>
            <el-checkbox-group
              v-if="calendarForm.restMode === 'custom'"
              v-model="calendarForm.restWeekdays"
              class="custom-rest-weekdays"
            >
              <el-checkbox-button
                v-for="item in restWeekdayOptions"
                :key="item.value"
                :label="item.value"
              >
                {{ item.label }}
              </el-checkbox-button>
            </el-checkbox-group>
            <div class="rule-tip">
              双休为周六周日休，单休为周日休，大小周为周日固定休且隔周周六休，自定义可勾选任意休息星期。
            </div>
          </div>
        </el-form-item>
        <el-form-item v-if="showLegalHolidaySection" label="法定节假日">
          <div class="legal-holiday-section">
            <div class="legal-holiday-header">
              <div>
                <div class="legal-holiday-title">法定节假日</div>
                <div class="legal-holiday-desc">
                  默认休息，点选日期后按上班处理
                </div>
              </div>
              <span class="legal-holiday-count">
                {{
                  legalHolidayLoading
                    ? '同步节假日中...'
                    : `${legalHolidaySourceText}，已设上班 ${legalHolidayVisibleWorkCount} 天`
                }}
              </span>
            </div>
            <div v-if="legalHolidayGroups.length" class="legal-holiday-list">
              <div
                v-for="group in legalHolidayGroups"
                :key="group.name"
                class="legal-holiday-group"
              >
                <div class="legal-holiday-group-head">
                  <strong>{{ group.name }}</strong>
                  <span>{{ group.dates.length }}天</span>
                </div>
                <div class="legal-holiday-dates">
                  <button
                    v-for="item in group.dates"
                    :key="item.calendarDate"
                    type="button"
                    class="legal-holiday-date"
                    :class="{
                      'is-work': calendarForm.legalHolidayWorkDates.includes(
                        item.calendarDate
                      )
                    }"
                    @click="toggleLegalHolidayWorkStatus(item.calendarDate)"
                  >
                    <span>{{ formatHolidayDate(item.calendarDate) }}</span>
                    <em>
                      {{
                        calendarForm.legalHolidayWorkDates.includes(
                          item.calendarDate
                        )
                          ? '上班'
                          : '休息'
                      }}
                    </em>
                  </button>
                </div>
              </div>
            </div>
          </div>
        </el-form-item>
        <el-form-item label="特殊日期">
          <div class="holiday-rule-editor">
            <div
              v-for="(rule, index) in calendarForm.holidayRules"
              :key="rule.id"
              class="holiday-rule-row"
            >
              <el-date-picker
                v-model="rule.calendarDate"
                type="date"
                value-format="yyyy-MM-dd"
                placeholder="选择日期"
                style="width: 160px"
              />
              <el-select
                v-model="rule.dateType"
                placeholder="日期类型"
                style="width: 130px"
              >
                <el-option label="上班" :value="1" />
                <el-option label="休息" :value="2" />
                <el-option label="法定节假日" :value="3" />
              </el-select>
              <el-input
                v-model="rule.remark"
                placeholder="备注"
                maxlength="80"
                style="flex: 1"
              />
              <el-button
                type="text"
                icon="el-icon-delete"
                class="danger-link"
                @click="removeHolidayRule(index)"
              >
                删除
              </el-button>
            </div>
            <el-button size="small" icon="el-icon-plus" @click="addHolidayRule">
              新增特殊日期
            </el-button>
            <div class="rule-tip">
              特殊日期优先级高于周休设置，可把周末设为上班，也可把工作日设为休息或法定节假日。
            </div>
          </div>
        </el-form-item>
        <el-form-item label="备注">
          <el-input
            v-model="calendarForm.remark"
            type="textarea"
            :rows="3"
            maxlength="500"
            show-word-limit
          />
        </el-form-item>
      </el-form>
      <div slot="footer">
        <el-button type="primary" @click="saveCalendar">确定</el-button>
        <el-button @click="calendarDialogVisible = false">取消</el-button>
      </div>
    </ele-modal>

    <ele-modal
      :visible.sync="copyDialogVisible"
      title="复制日历配置"
      width="520px"
      custom-class="ele-dialog-form"
    >
      <el-form :model="copyForm" label-width="100px" class="el-form-box">
        <el-form-item label="源日历">
          <el-select v-model="copyForm.sourceId" style="width: 100%">
            <el-option
              v-for="item in calendars"
              :key="item.id"
              :label="item.calendarName"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="目标年份">
          <el-select v-model="copyForm.targetYear" style="width: 100%">
            <el-option
              v-for="year in yearOptions"
              :key="year"
              :label="year"
              :value="year"
            />
          </el-select>
        </el-form-item>
      </el-form>
      <div slot="footer">
        <el-button type="primary" @click="copyCalendar">确定</el-button>
        <el-button @click="copyDialogVisible = false">取消</el-button>
      </div>
    </ele-modal>

    <ele-modal
      :visible.sync="adjustDialogVisible"
      title="临时调整申请"
      width="720px"
      custom-class="ele-dialog-form"
      :close-on-click-modal="false"
    >
      <el-form
        ref="adjustForm"
        :model="adjustForm"
        :rules="adjustRules"
        label-width="120px"
        class="el-form-box"
      >
        <el-form-item label="关联日历" prop="calendarId">
          <el-select v-model="adjustForm.calendarId" style="width: 100%">
            <el-option
              v-for="item in enabledCalendars"
              :key="item.id"
              :label="item.calendarName"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="调整类型" prop="adjustType">
          <el-select v-model="adjustForm.adjustType" style="width: 100%">
            <el-option
              v-for="item in adjustTypeOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="调整日期" prop="adjustDate">
          <el-date-picker
            v-model="adjustForm.adjustDate"
            type="date"
            value-format="yyyy-MM-dd"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="调整时段" required>
          <div class="inline-time">
            <el-time-select
              v-model="adjustForm.startTime"
              placeholder="开始时间"
              :picker-options="timePickerOptions"
            />
            <span>至</span>
            <el-time-select
              v-model="adjustForm.endTime"
              placeholder="结束时间"
              :picker-options="getTimePickerOptions(adjustForm.startTime)"
            />
          </div>
        </el-form-item>
        <el-form-item label="生效时间" prop="effectiveTime">
          <el-date-picker
            v-model="adjustForm.effectiveTime"
            type="datetime"
            value-format="yyyy-MM-dd HH:mm:ss"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="失效时间" prop="expireTime">
          <el-date-picker
            v-model="adjustForm.expireTime"
            type="datetime"
            value-format="yyyy-MM-dd HH:mm:ss"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="调整原因" prop="applyReason">
          <el-input
            v-model="adjustForm.applyReason"
            type="textarea"
            :rows="3"
            maxlength="500"
            show-word-limit
          />
        </el-form-item>
      </el-form>
      <div slot="footer">
        <el-button type="primary" @click="submitAdjust">提交审批</el-button>
        <el-button @click="adjustDialogVisible = false">取消</el-button>
      </div>
    </ele-modal>

    <el-drawer
      :visible.sync="dayDrawerVisible"
      :title="dayDrawerTitle"
      size="460px"
      append-to-body
      custom-class="calendar-day-drawer"
    >
      <div class="drawer-body" v-if="currentDay.date">
        <div class="drawer-summary">
          <div class="summary-date">
            <span>{{ drawerDateMonth }}</span>
            <strong>{{ drawerDateDay }}</strong>
          </div>
          <div class="summary-info">
            <div class="summary-title">
              {{ getDateTypeText(currentDay) }}
              <el-tag
                size="mini"
                :type="currentDay.scheduleStatus ? 'success' : 'info'"
              >
                {{ currentDay.scheduleStatus ? '已排班' : '未排班' }}
              </el-tag>
            </div>
            <p>{{ getRangeText(currentDay.details) }}</p>
            <div class="summary-tags">
              <el-tag v-if="currentDay.isRest" size="mini" type="info">
                休息/节假日
              </el-tag>
              <el-tag v-if="currentDay.isTempAdjust" size="mini">
                临时调整
              </el-tag>
              <el-tag v-if="currentDay.isConflict" size="mini" type="danger">
                存在冲突
              </el-tag>
              <el-tag v-if="currentDay.planCount" size="mini" type="warning">
                {{ currentDay.planCount }} 个计划
              </el-tag>
            </div>
          </div>
        </div>

        <div class="drawer-section">
          <div class="drawer-title">
            <span>时段明细</span>
            <em>{{ currentDay.details.length }} 条</em>
          </div>
          <div class="detail-timeline">
            <div
              v-for="detail in currentDay.details"
              :key="detail.id"
              class="detail-item"
            >
              <div class="detail-time">
                <strong>{{ detail.startTime }} - {{ detail.endTime }}</strong>
              </div>
              <div class="detail-content">
                <div>
                  <strong>{{
                    detail.dutyName ||
                    getLabel(dateTypeOptions, detail.dateType)
                  }}</strong>
                  <span>{{ detail.startTime }}-{{ detail.endTime }}</span>
                </div>
                <el-tag
                  size="mini"
                  :type="detail.scheduleStatus ? 'success' : 'info'"
                >
                  {{ detail.scheduleStatus ? '已排班' : '未排班' }}
                </el-tag>
                <div
                  v-if="
                    detail.productionLineName ||
                    detail.workStationName ||
                    detail.productionShift ||
                    detail.workingHours ||
                    detail.restTime
                  "
                  class="detail-meta"
                >
                  <span v-if="detail.productionLineName"
                    >产线：{{ detail.productionLineName }}</span
                  >
                  <span v-if="detail.workStationName"
                    >工位：{{ detail.workStationName }}</span
                  >
                  <span v-if="detail.productionShift"
                    >班次：{{ detail.productionShift }}</span
                  >
                  <span v-if="detail.workingHours"
                    >工时：{{ detail.workingHours }}分钟</span
                  >
                  <span v-if="detail.restTime"
                    >休息：{{ detail.restTime }}分钟</span
                  >
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="drawer-section">
          <div class="drawer-title">
            <span>{{ relatedSectionTitle }}</span>
            <em>{{ relatedPlans.length }} 条</em>
          </div>
          <div v-if="relatedPlans.length" class="plan-list">
            <el-tooltip
              v-for="plan in relatedPlans"
              :key="plan.id"
              placement="top"
              effect="light"
            >
              <div slot="content" class="plan-tooltip">
                <template v-if="isTeamQueueDrawer">
                  <div>排班名称：{{ getRelatedQueueName(plan) }}</div>
                  <div>人员：{{ getRelatedQueueUserName(plan) || '-' }}</div>
                </template>
                <template v-else>
                  <div>
                    {{ isEquipmentDrawer ? '设备编码' : '计划编号' }}：{{
                      getRelatedPlanCode(plan)
                    }}
                  </div>
                  <div>
                    {{ isEquipmentDrawer ? '设备名称' : '计划名称' }}：{{
                      getRelatedPlanName(plan)
                    }}
                  </div>
                  <div v-if="!isEquipmentDrawer">
                    订单编号：{{ plan.orderNo || '-' }}
                  </div>
                  <div v-if="!isEquipmentDrawer">
                    计划数量：{{ plan.requiredFormingNum || '-' }}
                  </div>
                  <div>开始时间：{{ plan.displayStartTime || '-' }}</div>
                  <div>结束时间：{{ plan.displayEndTime || '-' }}</div>
                </template>
              </div>
              <div
                class="plan-item"
                :class="{ 'team-queue-item': isTeamQueueDrawer }"
              >
                <div v-if="isTeamQueueDrawer" class="plan-main team-queue-plan">
                  <div class="queue-avatar">
                    <i class="el-icon-user-solid"></i>
                  </div>
                  <div class="queue-info">
                    <div class="plan-name queue-name-only">
                      <span class="queue-name-label">排班名称：</span>
                      <span class="queue-name-text">
                        {{ getRelatedQueueName(plan) }}
                      </span>
                    </div>
                    <div class="queue-user-name">
                      <i class="el-icon-user"></i>
                      <span>人员：</span>
                      <strong>
                        {{ getRelatedQueueUserName(plan) || '-' }}
                      </strong>
                    </div>
                  </div>
                  <div class="queue-badge">排班</div>
                </div>
                <div v-else class="plan-main">
                  <div class="plan-topline">
                    <strong>{{ plan.planNo }}</strong>
                    <span v-if="plan.orderNo">{{ plan.orderNo }}</span>
                  </div>
                  <div class="plan-name">{{ plan.taskName }}</div>
                  <div class="plan-bottomline">
                    <span class="plan-metrics">
                      <em
                        v-if="
                          plan.requiredFormingNum !== undefined &&
                          plan.requiredFormingNum !== null
                        "
                      >
                        数量 {{ plan.requiredFormingNum }}
                      </em>
                      <em v-if="plan.displayStartTime || plan.displayEndTime">
                        {{ plan.displayStartTime || '-' }} -
                        {{ plan.displayEndTime || '-' }}
                      </em>
                    </span>
                  </div>
                </div>
              </div>
            </el-tooltip>
          </div>
          <el-empty
            v-else
            class="drawer-empty"
            description="暂无关联计划"
            :image-size="92"
          />
        </div>
      </div>

      <!-- <div class="drawer-actions" v-if="currentDay.date">
        <el-button
          size="small"
          icon="el-icon-check"
          @click="toggleScheduleStatus(1)"
        >
          完成排班
        </el-button>
        <el-button
          size="small"
          icon="el-icon-refresh-left"
          @click="toggleScheduleStatus(0)"
        >
          取消排班
        </el-button>
        <el-button
          size="small"
          type="primary"
          icon="el-icon-plus"
          @click="openAdjustDialog(currentDay)"
        >
          临时调整
        </el-button>
        <el-button
          size="small"
          icon="el-icon-warning-outline"
          @click="checkSingleDay(currentDay)"
        >
          实时检测
        </el-button>
      </div> -->
    </el-drawer>

    <ele-modal
      :visible.sync="approveDialogVisible"
      :title="approveForm.status === 1 ? '审批通过' : '审批驳回'"
      width="520px"
      custom-class="ele-dialog-form"
      :close-on-click-modal="false"
    >
      <el-form :model="approveForm" label-width="90px" class="el-form-box">
        <el-form-item label="审批意见">
          <el-input
            v-model="approveForm.opinion"
            type="textarea"
            :rows="4"
            maxlength="300"
            show-word-limit
            :placeholder="
              approveForm.status === 1 ? '请输入审批意见' : '请输入驳回原因'
            "
          />
        </el-form-item>
      </el-form>
      <div slot="footer">
        <el-button type="primary" @click="confirmApproveAdjust">确定</el-button>
        <el-button @click="approveDialogVisible = false">取消</el-button>
      </div>
    </ele-modal>

    <ele-modal
      :visible.sync="adjustDetailVisible"
      title="调整记录追溯"
      width="860px"
      custom-class="ele-dialog-form adjust-detail-dialog"
    >
      <div v-if="adjustDetail.id" class="adjust-detail-view">
        <div class="adjust-detail-grid">
          <div class="adjust-detail-field">
            <span>申请单号</span>
            <strong>{{ adjustDetail.applyNo || '-' }}</strong>
          </div>
          <div class="adjust-detail-field">
            <span>关联日历</span>
            <strong>{{ adjustDetail.calendarName || '-' }}</strong>
          </div>
          <div class="adjust-detail-field">
            <span>调整类型</span>
            <strong>{{
              getLabel(adjustTypeOptions, adjustDetail.adjustType)
            }}</strong>
          </div>
          <div class="adjust-detail-field">
            <span>审批状态</span>
            <el-tag
              size="mini"
              :type="approvalStatusTag(adjustDetail.applyStatus)"
            >
              {{ getLabel(approvalStatusOptions, adjustDetail.applyStatus) }}
            </el-tag>
          </div>
          <div class="adjust-detail-field">
            <span>调整日期</span>
            <strong>{{ adjustDetail.adjustDate || '-' }}</strong>
          </div>
          <div class="adjust-detail-field">
            <span>生效周期</span>
            <strong>
              {{ adjustDetail.effectiveTime || '-' }} 至
              {{ adjustDetail.expireTime || '-' }}
            </strong>
          </div>
        </div>

        <div class="adjust-compare">
          <div class="adjust-data-card before">
            <span>调整前数据</span>
            <strong>{{ adjustDetail.oldContent || '-' }}</strong>
          </div>
          <div class="adjust-data-card after">
            <span>调整后数据</span>
            <strong>{{
              formatSegmentContent(adjustDetail.newContent, adjustDetail) || '-'
            }}</strong>
          </div>
        </div>

        <div class="adjust-detail-section">
          <div class="section-title">调整原因</div>
          <p>{{ adjustDetail.applyReason || '-' }}</p>
        </div>

        <div class="adjust-detail-section">
          <div class="section-title">审批节点</div>
          <div
            v-if="formatApproveNodes(adjustDetail.approveNode).length"
            class="approve-timeline"
          >
            <div
              v-for="(node, index) in formatApproveNodes(
                adjustDetail.approveNode
              )"
              :key="index"
              class="approve-node"
            >
              <div class="node-dot"></div>
              <div class="node-content">
                <div>
                  <strong>{{ node.statusText }}</strong>
                  <span>{{ node.approveTime || '-' }}</span>
                </div>
                <p>{{ node.approveOpinion || '-' }}</p>
                <em v-if="node.approveUserId"
                  >审批人ID：{{ node.approveUserId }}</em
                >
              </div>
            </div>
          </div>
          <div v-else class="approve-empty">暂无审批记录</div>
        </div>
      </div>
      <div slot="footer">
        <!-- <el-button icon="el-icon-download" @click="exportAdjustRecord">
          导出追溯
        </el-button> -->
        <el-button @click="adjustDetailVisible = false">关闭</el-button>
      </div>
    </ele-modal>
  </div>
</template>

<script>
  import dayjs from 'dayjs';
  import { solarToLunar } from 'lunar-calendar';
  import BaseManage from './components/BaseManage.vue';
  import MultiCalendarView from './components/MultiCalendarView.vue';
  import AdjustApproval from './components/AdjustApproval.vue';
  import StatisticsView from './components/StatisticsView.vue';
  import {
    addCalendar,
    editCalendar,
    deleteCalendar,
    updateCalendarStatus,
    pageCalendar,
    copyCalendar,
    viewCalendar,
    saveCalendarDetail,
    checkCalendarConflict,
    checkAllCalendarConflict,
    applyCalendarAdjust,
    approveCalendarAdjust,
    pageCalendarAdjust,
    refreshCalendarPlan,
    getPublicLegalHolidays
  } from '@/api/productionScheduling/factoryCalendar';
  const currentYear = Number(dayjs().format('YYYY'));

  const calendarTypeOptions = [
    { label: '标准生产日历', value: 1 },
    { label: '设备维护日历', value: 2 },
    { label: '人员排班日历', value: 3 }
  ];
  const dateTypeOptions = [
    { label: '正常工作日', value: 1 },
    { label: '休息日', value: 2 },
    { label: '节假日', value: 3 },
    { label: '设备检修期', value: 4 },
    { label: '临时调整时段', value: 5 }
  ];
  const adjustTypeOptions = [
    { label: '临时加班', value: 1 },
    { label: '临时停工', value: 2 },
    { label: '临时设备检修', value: 3 },
    { label: '临时人员调班', value: 4 }
  ];
  const approvalStatusOptions = [
    { label: '待审批', value: 0 },
    { label: '审批通过', value: 1 },
    { label: '审批驳回', value: 2 },
    { label: '已失效', value: 3 }
  ];

  const legalHolidayKeywords = [
    { keyword: '元旦节', label: '元旦节' },
    { keyword: '春节', label: '春节' },
    { keyword: '清明', label: '清明节' },
    { keyword: '劳动节', label: '劳动节' },
    { keyword: '端午节', label: '端午节' },
    { keyword: '中秋节', label: '中秋节' },
    { keyword: '国庆节', label: '国庆节' }
  ];
  const officialHolidayRanges = {
    2026: [
      { name: '元旦节', start: '2026-01-01', end: '2026-01-03' },
      { name: '春节', start: '2026-02-15', end: '2026-02-23' },
      { name: '清明节', start: '2026-04-04', end: '2026-04-06' },
      { name: '劳动节', start: '2026-05-01', end: '2026-05-05' },
      { name: '端午节', start: '2026-06-19', end: '2026-06-21' },
      { name: '中秋节', start: '2026-09-25', end: '2026-09-27' },
      { name: '国庆节', start: '2026-10-01', end: '2026-10-07' }
    ]
  };
  const inferredHolidayRules = {
    元旦节: { before: 0, after: 2 },
    春节: { before: 2, after: 6 },
    清明节: { before: 1, after: 1 },
    劳动节: { before: 0, after: 4 },
    端午节: { before: 0, after: 2 },
    中秋节: { before: 0, after: 2 },
    国庆节: { before: 0, after: 6 }
  };
  const inferredHolidayRangeCache = {};

  function clone(data) {
    return JSON.parse(JSON.stringify(data));
  }

  function createCode(prefix) {
    return `${prefix}${dayjs().format('YYYYMMDD')}${String(
      Math.floor(Math.random() * 1000000)
    ).padStart(6, '0')}`;
  }

  function toMinute(time) {
    const [hour, minute] = time.split(':').map(Number);
    return hour * 60 + minute;
  }

  function isOverlap(aStart, aEnd, bStart, bEnd) {
    return (
      toMinute(aStart) < toMinute(bEnd) && toMinute(bStart) < toMinute(aEnd)
    );
  }

  function createHolidayRule(dateType = 3, calendarDate = '', remark = '') {
    return {
      id: Date.now() + Math.random(),
      calendarDate,
      dateType,
      remark
    };
  }

  function normalizePage(data) {
    const list =
      data?.records || data?.list || data?.rows || data?.data || data || [];
    const count =
      data?.total || data?.count || data?.totalCount || list.length || 0;
    return {
      list: Array.isArray(list) ? list : [],
      count
    };
  }

  function normalizeYearList(applyYear) {
    if (Array.isArray(applyYear)) {
      return applyYear.map(Number).filter(Boolean);
    }
    return String(applyYear || '')
      .split(',')
      .map((item) => Number(item.trim()))
      .filter(Boolean);
  }

  export default {
    name: 'FactoryCalendar',
    components: {
      BaseManage,
      MultiCalendarView,
      AdjustApproval,
      StatisticsView
    },
    data() {
      return {
        activeTab: 'base',
        query: {
          year: currentYear,
          month: '',
          calendarType: '',
          status: ''
        },
        viewQuery: {
          calendarType: 1,
          year: currentYear,
          month: Number(dayjs().format('M')),
          date: dayjs().format('YYYY-MM-DD'),
          viewType: 'month',
          statusFilter: ''
        },
        viewMonth: dayjs().format('YYYY-MM'),
        calendars: [],
        details: [],
        adjusts: [],
        plans: [],
        conflictList: [],
        filteredCalendars: [],
        legalHolidayLoading: false,
        legalHolidayCache: {},
        legalHolidayStatusMap: {},
        legalHolidayPromises: {},
        conflictCheckPromise: null,
        conflictDataKey: '',
        viewDataKey: '',
        viewDataPromise: null,
        planDataKey: '',
        planDataPromise: null,
        pageSize: this.$store.state.tablePageSize,
        tableResponse: {
          dataName: 'list',
          countName: 'count'
        },
        calendarDialogVisible: false,
        copyDialogVisible: false,
        adjustDialogVisible: false,
        dayDrawerVisible: false,
        approveDialogVisible: false,
        adjustDetailVisible: false,
        calendarForm: this.getDefaultCalendarForm(),
        copyForm: {
          sourceId: '',
          targetYear: currentYear + 1
        },
        adjustForm: this.getDefaultAdjustForm(),
        approveForm: {
          row: null,
          status: 1,
          opinion: ''
        },
        adjustDetail: {},
        currentDay: {},
        calendarTypeOptions,
        dateTypeOptions,
        adjustTypeOptions,
        approvalStatusOptions,
        yearOptions: [
          currentYear - 1,
          currentYear,
          currentYear + 1,
          currentYear + 2
        ],
        monthOptions: Array.from({ length: 12 }, (_, index) => ({
          label: `${index + 1}月`,
          value: index + 1
        })),
        weekNames: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
        restModeOptions: [
          { label: '双休', value: 'double' },
          { label: '单休', value: 'single' },
          { label: '大小周', value: 'alternate' },
          { label: '无休', value: 'none' },
          { label: '自定义', value: 'custom' }
        ],
        restWeekdayOptions: [
          { label: '周一', value: 1 },
          { label: '周二', value: 2 },
          { label: '周三', value: 3 },
          { label: '周四', value: 4 },
          { label: '周五', value: 5 },
          { label: '周六', value: 6 },
          { label: '周日', value: 0 }
        ],
        timePickerOptions: {
          start: '00:00',
          step: '00:30',
          end: '23:30'
        },
        statusFilterOptions: [
          { label: '已排班/已排产', value: 'scheduled' },
          { label: '未排班/空闲', value: 'idle' },
          { label: '休息日/节假日', value: 'rest' },
          { label: '设备检修期', value: 'maintenance' },
          { label: '临时调整时段', value: 'temp' },
          { label: '时间冲突时段', value: 'conflict' }
        ],
        legendList: [
          {
            key: 'scheduled',
            label: '已排班/已排产',
            desc: '已完成生产、维保或人员排班'
          },
          {
            key: 'idle',
            label: '未排班/空闲',
            desc: '有效工作时间，暂无计划'
          },
          {
            key: 'rest',
            label: '休息日/节假日',
            desc: '不支持排产排班'
          },
          {
            key: 'holiday',
            label: '法定节假日',
            desc: '未配置为上班时默认休息'
          },
          {
            key: 'maintenance',
            label: '设备检修期',
            desc: '设备不可用时段'
          },
          {
            key: 'temp',
            label: '临时调整时段',
            desc: '审批通过后的加班、停工、调班'
          },
          {
            key: 'conflict',
            label: '时间冲突时段',
            desc: '存在多维度时间冲突'
          }
        ],
        calendarColumns: [
          {
            width: 60,
            label: '序号',
            type: 'index',
            columnKey: 'index',
            align: 'center'
          },
          {
            minWidth: 170,
            prop: 'calendarCode',
            label: '日历编码',
            align: 'center',
            showOverflowTooltip: true
          },
          {
            minWidth: 180,
            prop: 'calendarName',
            label: '日历名称',
            align: 'center',
            showOverflowTooltip: true
          },
          {
            minWidth: 140,
            prop: 'calendarType',
            label: '日历类型',
            align: 'center',
            slot: 'calendarType'
          },
          {
            minWidth: 130,
            prop: 'applyYear',
            label: '适用年份',
            align: 'center',
            formatter: (row) => row.applyYear.join(',')
          },
          // {
          //   minWidth: 240,
          //   prop: 'timeRanges',
          //   label: '每日时段配置',
          //   align: 'center',
          //   slot: 'timeRanges'
          // },
          // {
          //   minWidth: 150,
          //   prop: 'productionLineName',
          //   label: '产线名称',
          //   align: 'center',
          //   showOverflowTooltip: true
          // },
          // {
          //   minWidth: 140,
          //   prop: 'workStationName',
          //   label: '工位名称',
          //   align: 'center',
          //   showOverflowTooltip: true
          // },
          {
            minWidth: 150,
            prop: 'status',
            label: '状态',
            align: 'center',
            slot: 'status'
          },
          {
            minWidth: 130,
            prop: 'createUserName',
            label: '创建人',
            align: 'center'
          },
          {
            minWidth: 170,
            prop: 'updateTime',
            label: '创建时间',
            align: 'center'
          },
          {
            columnKey: 'action',
            label: '操作',
            width: 230,
            align: 'center',
            slot: 'action',
            fixed: 'right'
          }
        ],
        conflictColumns: [
          { width: 60, label: '序号', type: 'index', align: 'center' },
          {
            minWidth: 130,
            prop: 'calendarDate',
            label: '冲突日期',
            align: 'center'
          },
          {
            minWidth: 150,
            prop: 'timeRange',
            label: '冲突时段',
            align: 'center'
          },
          { minWidth: 180, prop: 'scene', label: '冲突场景', align: 'center' },
          {
            minWidth: 120,
            prop: 'level',
            label: '等级',
            align: 'center',
            slot: 'level'
          },
          {
            minWidth: 300,
            prop: 'message',
            label: '提示信息',
            align: 'center',
            showOverflowTooltip: true
          },
          {
            columnKey: 'action',
            label: '操作',
            width: 120,
            align: 'center',
            slot: 'action',
            fixed: 'right'
          }
        ],
        adjustColumns: [
          { width: 60, label: '序号', type: 'index', align: 'center' },
          {
            minWidth: 160,
            prop: 'applyNo',
            label: '申请单号',
            align: 'center'
          },
          {
            minWidth: 180,
            prop: 'calendarName',
            label: '关联日历',
            align: 'center',
            showOverflowTooltip: true
          },
          {
            minWidth: 130,
            prop: 'adjustType',
            label: '调整类型',
            align: 'center',
            slot: 'adjustType'
          },
          {
            minWidth: 120,
            prop: 'adjustDate',
            label: '调整日期',
            align: 'center'
          },
          {
            minWidth: 220,
            prop: 'newContent',
            label: '调整后数据',
            align: 'center',
            slot: 'newContent',
            showOverflowTooltip: true
          },
          {
            minWidth: 130,
            prop: 'applyStatus',
            label: '审批状态',
            align: 'center',
            slot: 'applyStatus'
          },
          {
            minWidth: 130,
            prop: 'applyUserName',
            label: '申请人',
            align: 'center'
          },
          {
            minWidth: 170,
            prop: 'applyTime',
            label: '申请时间',
            align: 'center'
          },
          {
            columnKey: 'action',
            label: '操作',
            width: 120,
            align: 'center',
            slot: 'action',
            fixed: 'right'
          }
        ],
        planColumns: [
          { width: 60, label: '序号', type: 'index', align: 'center' },
          { minWidth: 150, prop: 'planNo', label: '计划编号', align: 'center' },
          {
            minWidth: 150,
            prop: 'orderNo',
            label: '订单编号',
            align: 'center'
          },
          {
            minWidth: 180,
            prop: 'taskName',
            label: '任务名称',
            align: 'center',
            showOverflowTooltip: true
          },
          {
            minWidth: 130,
            prop: 'calendarDate',
            label: '计划日期',
            align: 'center'
          },
          {
            minWidth: 150,
            prop: 'timeRange',
            label: '计划时段',
            align: 'center',
            formatter: (row) =>
              row.timeRange || `${row.startTime}-${row.endTime}`
          },
          {
            minWidth: 150,
            prop: 'productionLineName',
            label: '产线名称',
            align: 'center',
            showOverflowTooltip: true
          },
          {
            minWidth: 140,
            prop: 'workStationName',
            label: '工位名称',
            align: 'center',
            showOverflowTooltip: true
          },
          {
            minWidth: 120,
            prop: 'workingHours',
            label: '工时(分钟)',
            align: 'center'
          },
          {
            minWidth: 120,
            prop: 'restTime',
            label: '休息(分钟)',
            align: 'center'
          },
          {
            minWidth: 110,
            prop: 'status',
            label: '状态',
            align: 'center',
            slot: 'status'
          },
          {
            columnKey: 'action',
            label: '操作',
            width: 120,
            align: 'center',
            slot: 'action',
            fixed: 'right'
          }
        ],
        calendarRules: {
          calendarName: [
            { required: true, message: '请输入日历名称', trigger: 'blur' }
          ],
          calendarType: [
            { required: true, message: '请选择日历类型', trigger: 'change' }
          ],
          applyYear: [
            { required: true, message: '请选择适用年份', trigger: 'change' }
          ],
          applyMonth: [
            { required: true, message: '请选择适用月份', trigger: 'change' }
          ]
        },
        adjustRules: {
          calendarId: [
            { required: true, message: '请选择关联日历', trigger: 'change' }
          ],
          adjustType: [
            { required: true, message: '请选择调整类型', trigger: 'change' }
          ],
          adjustDate: [
            { required: true, message: '请选择调整日期', trigger: 'change' }
          ],
          effectiveTime: [
            { required: true, message: '请选择生效时间', trigger: 'change' }
          ],
          expireTime: [
            { required: true, message: '请选择失效时间', trigger: 'change' }
          ],
          applyReason: [
            { required: true, message: '请输入调整原因', trigger: 'blur' }
          ]
        }
      };
    },
    computed: {
      vm() {
        return this;
      },
      enabledCalendars() {
        return this.calendars.filter((item) => item.status === 1);
      },
      calendarDialogTitle() {
        return this.calendarForm.id ? '编辑日历' : '新增日历';
      },
      calendarFormApplyYear: {
        get() {
          return normalizeYearList(this.calendarForm.applyYear)[0] || '';
        },
        set(value) {
          this.calendarForm.applyYear = value ? [Number(value)] : [];
          this.calendarForm.legalHolidayWorkDates = [];
          this.loadLegalHolidaysByYears(this.calendarForm.applyYear);
        }
      },
      showWeekHeader() {
        return ['week', 'month'].includes(this.viewQuery.viewType);
      },
      calendarViewTitle() {
        const date = dayjs(this.viewQuery.date);
        if (this.viewQuery.viewType === 'day') {
          return `${date.format('YYYY年MM月DD日')} 日视图`;
        }
        if (this.viewQuery.viewType === 'week') {
          const start = date.startOf('week').add(1, 'day');
          const end = start.add(6, 'day');
          return `${start.format('YYYY年MM月DD日')} - ${end.format(
            'MM月DD日'
          )} 周视图`;
        }
        if (this.viewQuery.viewType === 'quarter') {
          const startMonth = Math.floor((this.viewQuery.month - 1) / 3) * 3 + 1;
          const endMonth = startMonth + 2;
          return `${this.viewQuery.year}年${startMonth}月 - ${endMonth}月 季视图`;
        }
        return `${this.viewQuery.year}年${this.viewQuery.month}月 月视图`;
      },
      calendarDateRange() {
        const current = dayjs(this.viewQuery.date);
        if (this.viewQuery.viewType === 'day') {
          return [current];
        }
        if (this.viewQuery.viewType === 'week') {
          const start = current.startOf('week').add(1, 'day');
          return Array.from({ length: 7 }).map((_, index) =>
            start.add(index, 'day')
          );
        }
        if (this.viewQuery.viewType === 'quarter') {
          const startMonth = Math.floor((this.viewQuery.month - 1) / 3) * 3 + 1;
          const start = dayjs(
            `${this.viewQuery.year}-${String(startMonth).padStart(2, '0')}-01`
          );
          const dates = [];
          for (let i = 0; i < 3; i += 1) {
            const month = start.add(i, 'month');
            for (let day = 1; day <= month.daysInMonth(); day += 1) {
              dates.push(month.date(day));
            }
          }
          return dates;
        }
        const firstDay = dayjs(
          `${this.viewQuery.year}-${String(this.viewQuery.month).padStart(
            2,
            '0'
          )}-01`
        );
        const dates = [];
        for (let day = 1; day <= firstDay.daysInMonth(); day += 1) {
          dates.push(firstDay.date(day));
        }
        return dates;
      },
      calendarCells() {
        return this.buildCalendarCells(true);
      },
      statCells() {
        const dates = this.getMonthDateRange();
        return this.buildCalendarCellsByDates(dates, false, 'month').filter(
          (item) => item.date
        );
      },
      monthStats() {
        const visibleDays = this.statCells;
        const workDays = visibleDays.filter((item) => !item.isRest).length;
        const scheduledDays = visibleDays.filter(
          (item) => !item.isRest && item.scheduleStatus
        ).length;
        const conflictDays = visibleDays.filter(
          (item) => item.isConflict
        ).length;
        const tempDays = visibleDays.filter((item) => item.isTempAdjust).length;
        const restDays = visibleDays.filter((item) => item.isRest).length;
        return [
          { key: 'work', label: '总工作日', value: workDays },
          { key: 'scheduled', label: '已排班天数', value: scheduledDays },
          {
            key: 'idle',
            label: '未排班天数',
            value: Math.max(workDays - scheduledDays, 0)
          },
          { key: 'conflict', label: '冲突天数', value: conflictDays },
          { key: 'temp', label: '临时调整天数', value: tempDays },
          { key: 'rest', label: '休息/节假日', value: restDays }
        ];
      },
      dayDrawerTitle() {
        return this.currentDay.date
          ? `${this.currentDay.date} 日历详情`
          : '日历详情';
      },
      drawerDateMonth() {
        return this.currentDay.date
          ? dayjs(this.currentDay.date).format('YYYY-MM')
          : '';
      },
      drawerDateDay() {
        return this.currentDay.date
          ? dayjs(this.currentDay.date).format('DD')
          : '';
      },
      relatedPlans() {
        return this.currentDay.relatedPlans || [];
      },
      relatedSectionTitle() {
        return this.isTeamQueueDrawer ? '排班详情' : '关联计划';
      },
      isEquipmentDrawer() {
        return (
          Number(this.viewQuery.calendarType) === 2 ||
          (this.currentDay.details || []).some(
            (item) => Number(item.calendarType) === 2
          )
        );
      },
      isTeamQueueDrawer() {
        return (
          Number(this.viewQuery.calendarType) === 3 ||
          (this.currentDay.details || []).some(
            (item) => Number(item.calendarType) === 3
          )
        );
      },
      legalHolidayOptions() {
        return this.getLegalHolidayOptions(
          this.calendarForm.applyYear,
          this.calendarForm.applyMonth
        );
      },
      legalHolidayGroups() {
        const groupMap = {};
        this.legalHolidayOptions.forEach((item) => {
          if (!groupMap[item.name]) {
            groupMap[item.name] = {
              name: item.name,
              dates: []
            };
          }
          groupMap[item.name].dates.push(item);
        });
        return Object.values(groupMap);
      },
      showLegalHolidaySection() {
        return this.legalHolidayLoading || this.legalHolidayGroups.length > 0;
      },
      legalHolidayVisibleWorkCount() {
        const visibleDates = new Set(
          this.legalHolidayOptions.map((item) => item.calendarDate)
        );
        return (this.calendarForm.legalHolidayWorkDates || []).filter((date) =>
          visibleDates.has(date)
        ).length;
      },
      legalHolidaySourceText() {
        const year = normalizeYearList(this.calendarForm.applyYear)[0];
        if (this.legalHolidayStatusMap[year] === 'remote') {
          return '已同步公共节假日';
        }
        if (this.legalHolidayStatusMap[year] === 'fallback') {
          return '使用本地节假日';
        }
        return '使用本地节假日';
      },
      visualKpis() {
        const workDays =
          this.monthStats.find((item) => item.key === 'work')?.value || 0;
        const scheduledDays =
          this.monthStats.find((item) => item.key === 'scheduled')?.value || 0;
        const tempDays =
          this.monthStats.find((item) => item.key === 'temp')?.value || 0;
        const conflictDays =
          this.monthStats.find((item) => item.key === 'conflict')?.value || 0;
        return [
          {
            key: 'work',
            label: '总工作日',
            value: workDays,
            desc: `${this.viewQuery.year}年${this.viewQuery.month}月`,
            icon: 'el-icon-date'
          },
          {
            key: 'scheduled',
            label: '已排班',
            value: scheduledDays,
            desc: `完成率 ${this.scheduleRate}%`,
            icon: 'el-icon-finished'
          },
          {
            key: 'conflict',
            label: '冲突天数',
            value: conflictDays,
            desc: '当前月需确认修正',
            icon: 'el-icon-warning-outline'
          },
          {
            key: 'temp',
            label: '临时调整',
            value: tempDays,
            desc: '审批通过后生效',
            icon: 'el-icon-refresh'
          }
        ];
      },
      scheduleRate() {
        const workDays =
          this.monthStats.find((item) => item.key === 'work')?.value || 0;
        const scheduledDays =
          this.monthStats.find((item) => item.key === 'scheduled')?.value || 0;
        return workDays ? Math.round((scheduledDays / workDays) * 100) : 0;
      },
      scheduleSegments() {
        const scheduled =
          this.monthStats.find((item) => item.key === 'scheduled')?.value || 0;
        const idle =
          this.monthStats.find((item) => item.key === 'idle')?.value || 0;
        const rest =
          this.monthStats.find((item) => item.key === 'rest')?.value || 0;
        return [
          {
            key: 'scheduled',
            label: '已排班',
            value: scheduled,
            color: '#409eff'
          },
          { key: 'idle', label: '未排班', value: idle, color: '#67c23a' },
          { key: 'rest', label: '休息/节假日', value: rest, color: '#c0c4cc' }
        ];
      },
      scheduleDonutBackground() {
        const total = this.scheduleSegments.reduce(
          (sum, item) => sum + item.value,
          0
        );
        if (!total) {
          return '#ebeef5';
        }
        let start = 0;
        const segments = this.scheduleSegments.map((item) => {
          const end = start + (item.value / total) * 100;
          const value = `${item.color} ${start}% ${end}%`;
          start = end;
          return value;
        });
        return `conic-gradient(${segments.join(', ')})`;
      },
      scheduleTooltip() {
        return `已排班率 ${this.scheduleRate}%，已排班 ${this.scheduleSegments[0].value} 天，未排班 ${this.scheduleSegments[1].value} 天，休息/节假日 ${this.scheduleSegments[2].value} 天`;
      },
      visualMonthBars() {
        const colorMap = {
          work: '#409eff',
          scheduled: '#36cfc9',
          idle: '#67c23a',
          conflict: '#f56c6c',
          temp: '#9254de',
          rest: '#909399'
        };
        const max = Math.max(...this.monthStats.map((item) => item.value), 1);
        return this.monthStats.map((item) => ({
          ...item,
          color: colorMap[item.key],
          percent: Math.round((item.value / max) * 100)
        }));
      },
      calendarTypeStats() {
        const colorMap = {
          1: '#409eff',
          2: '#e6a23c',
          3: '#67c23a'
        };
        return this.calendarTypeOptions.map((item) => {
          const list = this.calendars.filter(
            (calendar) => calendar.calendarType === item.value
          );
          return {
            ...item,
            color: colorMap[item.value],
            count: list.length,
            enabled: list.filter((calendar) => calendar.status === 1).length,
            disabled: list.filter((calendar) => calendar.status === 0).length
          };
        });
      },
      conflictTrend() {
        const days = Array.from({ length: 6 }).map((_, index) => {
          const date = dayjs(
            `${this.viewQuery.year}-${String(this.viewQuery.month).padStart(
              2,
              '0'
            )}-01`
          ).date(index * 5 + 1);
          const count = this.statCells.filter(
            (item) => item.isConflict && dayjs(item.date).isSame(date, 'week')
          ).length;
          return {
            label: date.format('MM/DD'),
            count
          };
        });
        const max = Math.max(...days.map((item) => item.count), 1);
        return days.map((item) => ({
          ...item,
          height: Math.max(12, Math.round((item.count / max) * 100))
        }));
      }
    },
    created() {
      this.loadRemoteCalendarData();
    },
    methods: {
      getDefaultCalendarForm() {
        return {
          id: '',
          calendarCode: createCode('CLD'),
          calendarName: '',
          calendarType: 1,
          applyYear: [currentYear],
          applyMonth: Array.from({ length: 12 }, (_, index) => index + 1),
          status: 0,
          shiftId: '',
          shiftName: '',
          shiftCode: '',
          timeRanges: [],
          restMode: 'double',
          restWeekdays: [6, 0],
          legalHolidayWorkDates: [],
          holidayRules: [],
          remark: ''
        };
      },
      getDefaultAdjustForm(day = {}) {
        const enabledCalendars = (this.calendars || []).filter(
          (item) => item.status === 1
        );
        return {
          calendarId: enabledCalendars[0]?.id || '',
          adjustType: 1,
          adjustDate: day.date || dayjs().format('YYYY-MM-DD'),
          startTime: '08:00',
          endTime: '17:00',
          effectiveTime: `${day.date || dayjs().format('YYYY-MM-DD')} 08:00:00`,
          expireTime: `${day.date || dayjs().format('YYYY-MM-DD')} 17:00:00`,
          applyReason: ''
        };
      },
      buildCalendarCells(applyFilter = true) {
        return this.buildCalendarCellsByDates(
          this.calendarDateRange,
          applyFilter,
          this.viewQuery.viewType
        );
      },
      getMonthDateRange(
        year = this.viewQuery.year,
        month = this.viewQuery.month
      ) {
        const firstDay = dayjs(`${year}-${String(month).padStart(2, '0')}-01`);
        const dates = [];
        for (let day = 1; day <= firstDay.daysInMonth(); day += 1) {
          dates.push(firstDay.date(day));
        }
        return dates;
      },
      buildCalendarCellsByDates(
        dates,
        applyFilter = true,
        viewType = this.viewQuery.viewType
      ) {
        const startOffset =
          viewType === 'month' && dates.length ? (dates[0].day() + 6) % 7 : 0;
        const cells = [];
        for (let i = 0; i < startOffset; i += 1) {
          cells.push({ key: `empty-${i}`, date: '', dayText: '', details: [] });
        }
        dates.forEach((dateItem) => {
          const date = dateItem.format('YYYY-MM-DD');
          const details = this.getViewDetails(date);
          const planCount = this.getDetailPlanCount(details);
          const disabled = !this.hasEnabledCalendarForView();
          const holidayName = this.getLegalHolidayName(dateItem);
          const hasWorkDetail = details.some(
            (item) => Number(item.dateType || 1) === 1
          );
          const cell = {
            key: date,
            date,
            dayText:
              viewType === 'quarter'
                ? dateItem.format('M/D')
                : dateItem.format('D'),
            details,
            planCount: disabled ? 0 : planCount,
            scheduleStatus:
              !disabled &&
              (details.some((item) => item.scheduleStatus === 1) ||
                planCount > 0),
            isConflict:
              !disabled && details.some((item) => item.isConflict === 1),
            isTempAdjust:
              !disabled && details.some((item) => item.isTempAdjust === 1),
            isMaintenance:
              !disabled && details.some((item) => item.dateType === 4),
            isHoliday:
              !disabled &&
              (details.some((item) => Number(item.dateType) === 3) ||
                (holidayName && !hasWorkDetail)),
            holidayName,
            isRest: disabled ? true : this.isRestDay(date, details),
            isDisabledCalendar: disabled,
            conflictText: disabled ? '日历已禁用' : this.getConflictText(date)
          };
          if (!applyFilter || this.matchStatusFilter(cell)) {
            cells.push(cell);
          } else {
            cells.push({ ...cell, hiddenByFilter: true });
          }
        });
        return cells;
      },
      hasEnabledCalendarForView() {
        return !!this.getCurrentViewCalendar();
      },
      getCurrentViewCalendar() {
        return this.enabledCalendars.find(
          (item) => item.calendarType === this.viewQuery.calendarType
        );
      },
      getCurrentViewCalendars() {
        return this.enabledCalendars.filter(
          (item) => item.calendarType === this.viewQuery.calendarType
        );
      },
      getRemoteCalendarId(calendar = this.getCurrentViewCalendar()) {
        return calendar?.isRemote ? calendar.id : null;
      },
      getScheduleSegmentTooltip(item) {
        const total = this.scheduleSegments.reduce(
          (sum, segment) => sum + segment.value,
          0
        );
        const percent = total ? Math.round((item.value / total) * 100) : 0;
        return `${item.label}：${item.value} 天，占比 ${percent}%`;
      },
      getMonthBarTooltip(item) {
        return `${this.viewQuery.year}年${this.viewQuery.month}月，${item.label}：${item.value} 天，占当前最大值 ${item.percent}%`;
      },
      getCalendarTypeTooltip(item) {
        return `${item.label}：共 ${item.count} 个，启用 ${item.enabled} 个，禁用 ${item.disabled} 个`;
      },
      getConflictTrendTooltip(item) {
        return `${item.label} 所在周：${item.count} 条冲突`;
      },
      loadData() {
        this.calendars = [];
        this.details = [];
        this.adjusts = [];
        this.plans = [];
      },
      getCalendarQueryParams(query = this.query) {
        return {
          year: query.year || undefined,
          calendarType: query.calendarType || undefined,
          status: query.status === '' ? undefined : query.status
        };
      },
      async loadRemoteCalendarData() {
        try {
          const data = await pageCalendar({
            pageNum: 1,
            size: 999,
            ...this.getCalendarQueryParams()
          });
          const { list } = normalizePage(data);
          this.calendars = list.map((item) =>
            this.normalizeRemoteCalendar(item)
          );
          this.details = list
            .map((item) =>
              (item.detailList || []).map((detail) =>
                this.normalizeRemoteDetail(detail, item)
              )
            )
            .flat();
          await this.loadRemoteViewData(true);
          await this.loadRemoteAdjustData();
          this.syncFilteredCalendars();
        } catch (e) {
          this.calendars = [];
          this.details = [];
          this.adjusts = [];
          this.plans = [];
          this.syncFilteredCalendars();
        }
      },
      getViewDataKey(query = this.viewQuery) {
        return [
          query.calendarType || '',
          query.year || '',
          query.month || '',
          query.viewType || ''
        ].join('|');
      },
      async loadRemoteViewData(force = false, queryOverride = null) {
        const query = {
          ...this.viewQuery,
          ...(queryOverride || {})
        };
        const dataKey = this.getViewDataKey(query);
        if (!force && this.viewDataKey === dataKey) {
          return;
        }
        if (!force && this.viewDataPromise) {
          await this.viewDataPromise;
          return;
        }
        try {
          this.viewDataPromise = viewCalendar({
            calendarType: query.calendarType,
            year: query.year,
            month: query.month,
            viewType: query.viewType
          });
          const data = await this.viewDataPromise;
          if (!Array.isArray(data)) {
            this.viewDataKey = '';
            this.viewDataPromise = null;
            return;
          }
          const remoteDetails = data.map((item) =>
            this.normalizeRemoteDetail(item)
          );
          const visibleDates =
            queryOverride && query.viewType === 'month'
              ? this.getMonthDateRange(query.year, query.month).map((item) =>
                  item.format('YYYY-MM-DD')
                )
              : this.calendarDateRange.map((item) => item.format('YYYY-MM-DD'));
          const viewCalendarIds = this.calendars
            .filter((item) => item.calendarType === query.calendarType)
            .map((item) => item.id);
          this.details = [
            ...this.details.filter(
              (item) =>
                !(
                  item.calendarType === query.calendarType ||
                  viewCalendarIds.includes(item.calendarId)
                ) || !visibleDates.includes(item.calendarDate)
            ),
            ...remoteDetails
          ];
          this.viewDataKey = dataKey;
        } catch (e) {
          this.viewDataKey = '';
        }
        this.viewDataPromise = null;
      },
      async loadRemoteAdjustData() {
        try {
          const data = await pageCalendarAdjust({
            pageNum: 1,
            size: 999
          });
          const { list } = normalizePage(data);
          this.adjusts = list;
        } catch (e) {
          this.adjusts = [];
        }
      },
      normalizeRemoteCalendar(item) {
        const detailList = Array.isArray(item.detailList)
          ? item.detailList
          : [];
        const ruleConfig = this.parseCalendarRuleConfig(item.remark);
        const workdayDetails = detailList.filter(
          (detail) => Number(detail.dateType || 1) === 1
        );
        const applyMonth = normalizeYearList(item.applyMonth);
        const inferredApplyMonth = this.getApplyMonthsFromDetails(detailList);
        const inferredRestRule = this.getRestRuleFromDetails(detailList);
        const restWeekdays = Array.isArray(item.restWeekdays)
          ? item.restWeekdays.map(Number)
          : ruleConfig.restWeekdays === undefined
          ? inferredRestRule.restWeekdays
          : ruleConfig.restWeekdays;
        return {
          ...item,
          remark: this.stripCalendarRuleConfig(item.remark),
          isRemote: true,
          applyYear: normalizeYearList(item.applyYear),
          applyMonth: applyMonth.length
            ? applyMonth
            : ruleConfig.applyMonth ||
              inferredApplyMonth ||
              this.getFullMonthList(),
          shiftId: item.shiftId || ruleConfig.shiftId || '',
          shiftName: item.shiftName || ruleConfig.shiftName || '',
          shiftCode: item.shiftCode || ruleConfig.shiftCode || '',
          timeRanges: workdayDetails.length
            ? this.mergeTimeRanges(
                [],
                workdayDetails.map((detail, index) => ({
                  id: detail.id || `${item.id}-${index}`,
                  name:
                    detail.dateTypeName || detail.remark || `时段${index + 1}`,
                  startTime: String(detail.startTime || '').slice(0, 5),
                  endTime: String(detail.endTime || '').slice(0, 5),
                  timeType: '1'
                }))
              )
            : item.timeRanges || [],
          restWeekdays,
          legalHolidayWorkDates: Array.isArray(item.legalHolidayWorkDates)
            ? item.legalHolidayWorkDates
            : ruleConfig.legalHolidayWorkDates || [],
          restMode:
            item.restMode ||
            ruleConfig.restMode ||
            inferredRestRule.restMode ||
            this.getRestModeByWeekdays(restWeekdays),
          holidayRules: Array.isArray(item.holidayRules)
            ? item.holidayRules
            : ruleConfig.holidayRules || []
        };
      },
      getApplyMonthsFromDetails(detailList = []) {
        const months = [
          ...new Set(
            detailList
              .map((detail) => {
                const month = dayjs(detail.calendarDate).month() + 1;
                return Number.isNaN(month) ? null : month;
              })
              .filter(Boolean)
          )
        ].sort((a, b) => a - b);
        return months.length ? months : null;
      },
      getRestRuleFromDetails(detailList = []) {
        const dayMap = {};
        detailList.forEach((detail) => {
          if (!detail.calendarDate) {
            return;
          }
          if (!dayMap[detail.calendarDate]) {
            dayMap[detail.calendarDate] = [];
          }
          dayMap[detail.calendarDate].push(detail);
        });
        const days = Object.keys(dayMap).map((date) => {
          const details = dayMap[date];
          return {
            date,
            weekDay: dayjs(date).day(),
            isRest: details.every((detail) =>
              [2, 3].includes(Number(detail.dateType))
            )
          };
        });
        const restWeekdays = [
          ...new Set(
            days.filter((item) => item.isRest).map((item) => item.weekDay)
          )
        ].sort((a, b) => a - b);
        const weekdayText = restWeekdays.join(',');
        if (!restWeekdays.length) {
          return { restMode: 'none', restWeekdays: [] };
        }
        if (weekdayText === '0') {
          return { restMode: 'single', restWeekdays };
        }
        if (weekdayText === '0,6') {
          const saturdayDays = days.filter((item) => item.weekDay === 6);
          const saturdayRestCount = saturdayDays.filter(
            (item) => item.isRest
          ).length;
          if (
            saturdayDays.length &&
            saturdayRestCount > 0 &&
            saturdayRestCount < saturdayDays.length
          ) {
            return { restMode: 'alternate', restWeekdays };
          }
          return { restMode: 'double', restWeekdays };
        }
        return { restMode: 'custom', restWeekdays };
      },
      parseCalendarRuleConfig(remark) {
        const match = String(remark || '').match(
          /\[calendarRules\]([\s\S]*?)\[\/calendarRules\]/
        );
        if (!match) {
          return {};
        }
        try {
          const config = JSON.parse(match[1]);
          return {
            applyMonth: Array.isArray(config.applyMonth)
              ? config.applyMonth.map(Number).filter(Boolean)
              : undefined,
            shiftId: config.shiftId || undefined,
            shiftName: config.shiftName || undefined,
            shiftCode: config.shiftCode || undefined,
            restMode: config.restMode || undefined,
            restWeekdays: Array.isArray(config.restWeekdays)
              ? config.restWeekdays.map(Number)
              : undefined,
            legalHolidayWorkDates: Array.isArray(config.legalHolidayWorkDates)
              ? config.legalHolidayWorkDates.filter(Boolean)
              : undefined,
            holidayRules: Array.isArray(config.holidayRules)
              ? config.holidayRules
                  .filter((item) => item && item.calendarDate)
                  .map((item) =>
                    createHolidayRule(
                      Number(item.dateType || 3),
                      item.calendarDate,
                      item.remark || ''
                    )
                  )
              : undefined
          };
        } catch (e) {
          return {};
        }
      },
      stripCalendarRuleConfig(remark) {
        return String(remark || '')
          .replace(/\s*\[calendarRules\][\s\S]*?\[\/calendarRules\]\s*/g, '')
          .trim();
      },
      getCalendarRulePayload(form) {
        return {
          applyMonth: form.applyMonth || this.getFullMonthList(),
          shiftId: form.shiftId || '',
          shiftName: form.shiftName || '',
          shiftCode: form.shiftCode || '',
          restMode: form.restMode || 'double',
          restWeekdays: form.restWeekdays || [],
          legalHolidayWorkDates: form.legalHolidayWorkDates || [],
          holidayRules: (form.holidayRules || [])
            .filter((item) => item.calendarDate)
            .map((item) => ({
              calendarDate: item.calendarDate,
              dateType: Number(item.dateType || 3),
              remark: item.remark || ''
            }))
        };
      },
      mergeTimeRanges(...groups) {
        const map = {};
        groups.flat().forEach((item, index) => {
          if (!item) {
            return;
          }
          const startTime = String(item.startTime || '').slice(0, 5);
          const endTime = String(item.endTime || '').slice(0, 5);
          if (!startTime || !endTime) {
            return;
          }
          const name = item.name || item.remark || `时段${index + 1}`;
          const key = `${name}-${startTime}-${endTime}`;
          if (!map[key]) {
            map[key] = {
              ...item,
              id: item.id || key,
              name,
              startTime,
              endTime,
              timeType: item.timeType || '1'
            };
          }
        });
        return Object.values(map);
      },
      normalizeRemoteDetail(detail, calendar = {}) {
        const calendarType = detail.calendarType || calendar.calendarType;
        const relationPlanList = this.normalizeRemotePlans(
          detail.relationPlanList || []
        );
        const relationEamPlanList = this.normalizeRemotePlans(
          detail.relationEamPlanList || []
        );
        const relationTeamQueueList = this.normalizeRemotePlans(
          detail.relationTeamQueueList || []
        );
        const relationPlanCountMap = {
          2: relationEamPlanList.length,
          3: relationTeamQueueList.length
        };
        const relationPlanCount =
          relationPlanCountMap[Number(calendarType)] ??
          Number(detail.relationPlanCount || relationPlanList.length || 0);
        return {
          ...detail,
          id:
            detail.id ||
            `${detail.calendarId}-${detail.calendarDate}-${detail.startTime}`,
          calendarId: detail.calendarId || calendar.id,
          calendarName: detail.calendarName || calendar.calendarName,
          calendarType,
          calendarDate: detail.calendarDate,
          startTime: String(detail.startTime || '').slice(0, 5),
          endTime: String(detail.endTime || '').slice(0, 5),
          dateType: Number(detail.dateType || 1),
          scheduleStatus: Number(detail.scheduleStatus || 0),
          isConflict: Number(detail.isConflict || 0),
          isTempAdjust: Number(detail.isTempAdjust || 0),
          relationPlanCount,
          relationPlanList,
          relationEamPlanList,
          relationTeamQueueList
        };
      },
      saveData() {
        // 工厂日历页面以后台接口为唯一数据源，不再写入本地演示缓存。
      },
      openImport() {
        this.$message.warning(
          '批量导入需要后端导入接口支持，当前页面不再写入本地数据'
        );
      },
      async calendarDatasource({
        page = 1,
        limit = this.pageSize,
        where = {},
        order = {}
      } = {}) {
        try {
          const data = await pageCalendar({
            ...this.getCalendarQueryParams({
              ...this.query,
              ...where
            }),
            ...order,
            pageNum: page,
            size: limit
          });
          const { list, count } = normalizePage(data);
          const rows = list.map((item) => this.normalizeRemoteCalendar(item));
          return { code: 0, list: rows, count };
        } catch (e) {
          return this.getPagedResult([], page, limit);
        }
      },
      getPagedResult(list, page = 1, limit = this.pageSize) {
        const start = (page - 1) * limit;
        return {
          code: 0,
          list: list.slice(start, start + limit),
          count: list.length
        };
      },
      reloadConflictTable(page = 1) {
        this.$nextTick(() => {
          this.$refs.conflictPanel?.reload?.({ page });
        });
      },
      reloadAdjustTable(page = 1) {
        this.$nextTick(() => {
          this.$refs.adjustApproval?.reload?.({ page });
        });
      },
      reloadPlanTable(page = 1) {
        this.$nextTick(() => {
          this.$refs.planLinkage?.reload?.({ page });
        });
      },
      reloadBaseTable(page = 1) {
        this.$nextTick(() => {
          this.$refs.baseManage?.reload?.({ page });
        });
      },
      async adjustDatasource({
        page = 1,
        limit = this.pageSize,
        where = {},
        order = {}
      } = {}) {
        try {
          const data = await pageCalendarAdjust({
            ...where,
            ...order,
            pageNum: page,
            size: limit
          });
          const { list, count } = normalizePage(data);
          return { code: 0, list, count };
        } catch (e) {
          return this.getPagedResult([], page, limit);
        }
      },
      async planDatasource({ page = 1, limit = this.pageSize } = {}) {
        await this.ensurePlanRefreshData(false);
        if (this.plans.length) {
          return this.getPagedResult(this.plans, page, limit);
        }
        const calendars = this.getCurrentViewCalendars();
        if (calendars.some((item) => this.getRemoteCalendarId(item))) {
          try {
            await this.loadRemoteViewData();
            const details = this.getViewDetails(this.viewQuery.date);
            const plans = this.getDetailRelatedPlans(details);
            return this.getPagedResult(plans, page, limit);
          } catch (e) {
            return this.getPagedResult([], page, limit);
          }
        }
        return this.getPagedResult([], page, limit);
      },
      normalizeRemoteConflicts(data = {}) {
        return (data.conflictDetail || []).map((item, index) => ({
          id:
            item.currentDetailId ||
            `${item.calendarDate}-${item.startTime}-${item.endTime}-${index}`,
          calendarDate: item.calendarDate,
          timeRange: `${String(item.startTime || '').slice(0, 5)}-${String(
            item.endTime || ''
          ).slice(0, 5)}`,
          scene: `${item.currentCalendarType || ''} VS ${
            item.conflictCalendarType || ''
          }`,
          level: data.isConflict ? '高' : '低',
          message: item.conflictMsg || data.msg || ''
        }));
      },
      normalizeRemotePlans(data = {}) {
        const list = Array.isArray(data)
          ? data
          : [
              ...(data.records || []),
              ...(data.list || []),
              ...(data.orderList || []),
              ...(data.taskList || []),
              ...(data.schedulePlanList || []),
              ...(data.relationPlanList || []),
              ...(data.relationEamPlanList || []),
              ...(data.relationTeamQueueList || []),
              ...(data.affectedPlanList || [])
            ];
        return list.map((item, index) => {
          const planNo =
            item.planNo ||
            item.orderNo ||
            item.code ||
            item.teamQueueName ||
            item.teamQueueId ||
            `PLAN-${index + 1}`;
          const salesCode = Array.isArray(item.salesCode)
            ? item.salesCode.join(',')
            : item.salesCode;
          const rawStartTime =
            item.rawStartTime ||
            item.planStartTime ||
            item.queueDate ||
            item.startTime;
          const rawEndTime =
            item.rawEndTime ||
            item.planEndTime ||
            item.queueEndTime ||
            item.endTime;
          const startTime = this.formatPlanTime(rawStartTime);
          const endTime = this.formatPlanTime(rawEndTime);
          const displayStartTime = this.formatPlanDateTime(rawStartTime);
          const displayEndTime = this.formatPlanDateTime(rawEndTime);
          const hasDateRange =
            /^\d{4}-\d{2}-\d{2}/.test(String(rawStartTime || '')) ||
            /^\d{4}-\d{2}-\d{2}/.test(String(rawEndTime || ''));
          return {
            ...item,
            id: item.id || planNo || index,
            planNo,
            orderNo:
              item.orderNo ||
              item.salesCodeList ||
              salesCode ||
              item.batchNo ||
              item.teamName ||
              item.teamId ||
              item.userName ||
              item.userId ||
              '',
            taskName:
              item.taskName ||
              item.productName ||
              item.bomCategoryName ||
              item.teamQueueName ||
              item.queueName ||
              item.teamName ||
              item.userName ||
              item.name ||
              item.title ||
              '关联计划',
            calendarId: item.calendarId,
            calendarDate:
              item.calendarDate ||
              this.formatPlanDate(
                item.planDeliveryTime ||
                  item.reqMoldTime ||
                  item.planEndTime ||
                  item.planStartTime ||
                  item.queueDate ||
                  item.endTime ||
                  item.startTime
              ),
            startTime,
            endTime,
            rawStartTime,
            rawEndTime,
            displayStartTime,
            displayEndTime,
            timeRange: hasDateRange
              ? `${displayStartTime} 至 ${displayEndTime}`
              : `${startTime}-${endTime}`,
            status: item.statusName || item.status || '正常',
            canReschedule: !!item.canReschedule,
            productionLineName:
              item.productionLineName ||
              item.productionLine ||
              item.productionCodes ||
              '',
            workStationName: item.workStationName || item.workStation || ''
          };
        });
      },
      formatPlanDate(value) {
        const text = String(value || '');
        const matched = text.match(/^\d{4}-\d{2}-\d{2}/);
        return matched ? matched[0] : this.viewQuery.date;
      },
      formatPlanTime(value) {
        const text = String(value || '');
        const matched = text.match(/\b(\d{2}):(\d{2})(?::\d{2})?\b/);
        return matched ? `${matched[1]}:${matched[2]}` : text.slice(0, 5);
      },
      formatPlanDateTime(value) {
        const text = String(value || '');
        const matched = text.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}):(\d{2})/);
        if (matched) {
          return `${matched[1]} ${matched[2]}:${matched[3]}`;
        }
        return this.formatPlanTime(value);
      },
      getDetailRelatedPlans(details = []) {
        const map = {};
        details.forEach((detail) => {
          [
            ...(detail.relationPlanList || []),
            ...(detail.relationEamPlanList || []),
            ...(detail.relationTeamQueueList || [])
          ].forEach((plan, index) => {
            const normalized = this.normalizeRemotePlans([plan])[0];
            if (!normalized) {
              return;
            }
            const key =
              normalized.id ||
              normalized.planNo ||
              normalized.code ||
              `${detail.id}-${index}`;
            map[key] = {
              ...normalized,
              calendarId: normalized.calendarId || detail.calendarId,
              calendarDate: detail.calendarDate || normalized.calendarDate,
              calendarType: normalized.calendarType || detail.calendarType
            };
          });
        });
        return Object.values(map);
      },
      getRelatedPlanCode(plan = {}) {
        return (
          (this.isEquipmentDrawer
            ? plan.deviceCode ||
              plan.equipmentCode ||
              plan.eamCode ||
              plan.assetCode ||
              plan.planNo
            : plan.planNo) || '-'
        );
      },
      getRelatedPlanName(plan = {}) {
        return (
          (this.isEquipmentDrawer
            ? plan.deviceName ||
              plan.equipmentName ||
              plan.eamName ||
              plan.assetName ||
              plan.taskName
            : plan.taskName) || '-'
        );
      },
      getRelatedQueueName(plan = {}) {
        return plan.teamQueueName || '-';
      },
      getRelatedQueueUserName(plan = {}) {
        return plan.userName || '';
      },
      getDetailPlanCount(details = []) {
        const relatedPlans = this.getDetailRelatedPlans(details);
        if (relatedPlans.length) {
          return relatedPlans.length;
        }
        return details.reduce(
          (sum, item) => sum + Number(item.relationPlanCount || 0),
          0
        );
      },
      syncFilteredCalendars() {
        this.filteredCalendars = this.calendars.filter((item) => {
          const yearMatch =
            !this.query.year || item.applyYear.includes(this.query.year);
          const typeMatch =
            !this.query.calendarType ||
            item.calendarType === this.query.calendarType;
          const statusMatch =
            this.query.status === '' || item.status === this.query.status;
          return yearMatch && typeMatch && statusMatch;
        });
      },
      async filterCalendar() {
        this.viewDataKey = '';
        this.invalidateConflictCache();
        await this.loadRemoteCalendarData();
        this.reloadBaseTable(1);
      },
      resetQuery() {
        this.query = {
          year: currentYear,
          month: '',
          calendarType: '',
          status: ''
        };
        this.filterCalendar();
      },
      openCalendarDialog(row) {
        this.calendarForm = row ? clone(row) : this.getDefaultCalendarForm();
        this.calendarDialogVisible = true;
        this.loadLegalHolidaysByYears(this.calendarForm.applyYear);
        this.$nextTick(() => {
          this.$refs.calendarForm?.clearValidate?.();
          this.resetCalendarDialogScroll();
        });
      },
      resetCalendarDialogScroll() {
        const dialogBody = document.querySelector(
          '.factory-calendar-dialog .el-dialog__body'
        );
        if (dialogBody) {
          dialogBody.scrollTop = 0;
        }
        document
          .querySelectorAll('.factory-calendar-dialog .legal-holiday-list')
          .forEach((item) => {
            item.scrollTop = 0;
          });
      },
      saveCalendar() {
        this.$refs.calendarForm.validate(async (valid) => {
          if (!valid) {
            return;
          }
          if (this.hasInvalidRestRule()) {
            return;
          }
          if (this.hasInvalidHolidayRules()) {
            return;
          }
          if (this.hasDuplicateCalendarTemplate()) {
            this.$message.error('同类型同年份日历已存在，请勿重复创建');
            return;
          }
          try {
            const payload = this.toRemoteCalendarPayload(this.calendarForm);
            if (this.calendarForm.id) {
              await editCalendar(payload);
            } else {
              const result = await addCalendar(payload);
              if (result?.id) {
                this.calendarForm.id = result.id;
                await updateCalendarStatus({
                  id: result.id,
                  status: Number(this.calendarForm.status || 0)
                });
              }
              if (result?.calendarCode) {
                this.calendarForm.calendarCode = result.calendarCode;
              }
            }
            await this.loadRemoteCalendarData();
            this.invalidateConflictCache();
            this.reloadBaseTable(1);
            this.calendarDialogVisible = false;
            this.$message.success('保存成功');
            return;
          } catch (e) {
            this.$message.error(e?.message || '保存失败，请检查接口返回');
          }
        });
      },
      hasDuplicateCalendarTemplate() {
        return this.calendars.some((item) => {
          if (item.id === this.calendarForm.id) {
            return false;
          }
          const sameType = item.calendarType === this.calendarForm.calendarType;
          const sameYear = item.applyYear.some((year) =>
            this.calendarForm.applyYear.includes(year)
          );
          return (
            sameType &&
            sameYear &&
            item.calendarName === this.calendarForm.calendarName
          );
        });
      },
      toRemoteCalendarPayload(form) {
        const rulePayload = this.getCalendarRulePayload(form);
        return {
          id: form.id || undefined,
          calendarName: form.calendarName,
          calendarType: form.calendarType,
          status: Number(form.status || 0),
          applyYear: Array.isArray(form.applyYear)
            ? form.applyYear.join(',')
            : form.applyYear,
          remark: this.stripCalendarRuleConfig(form.remark),
          ...rulePayload,
          segmentList: this.buildCalendarSegments(form)
        };
      },
      getFullMonthList() {
        return Array.from({ length: 12 }, (_, index) => index + 1);
      },
      getRestModeByWeekdays(weekdays) {
        const values = (weekdays || []).map(Number).sort().join(',');
        if (values === '0') {
          return 'single';
        }
        if (values === '0,6') {
          return 'double';
        }
        if (!values) {
          return 'none';
        }
        return 'double';
      },
      isRestDateByMode(dateItem, restMode) {
        const weekDay = dateItem.day();
        if (restMode === 'none') {
          return false;
        }
        if (restMode === 'single') {
          return weekDay === 0;
        }
        if (restMode === 'alternate') {
          const yearStart = dayjs(`${dateItem.year()}-01-01`);
          const weekIndex = Math.floor(dateItem.diff(yearStart, 'day') / 7);
          return weekDay === 0 || (weekDay === 6 && weekIndex % 2 === 0);
        }
        if (restMode === 'custom') {
          return false;
        }
        return weekDay === 0 || weekDay === 6;
      },
      getLegalHolidayInfo(dateItem) {
        if (!dateItem || !dateItem.isValid()) {
          return null;
        }
        const holidayInfo = this.getConfiguredHolidayRanges(
          dateItem.year()
        ).find(
          (item) =>
            !dateItem.isBefore(dayjs(item.start), 'day') &&
            !dateItem.isAfter(dayjs(item.end), 'day')
        );
        if (holidayInfo) {
          return {
            name: holidayInfo.name,
            calendarDate: dateItem.format('YYYY-MM-DD')
          };
        }
        const lunar = solarToLunar(
          dateItem.year(),
          dateItem.month() + 1,
          dateItem.date()
        );
        const festivalText = [
          lunar.solarFestival,
          lunar.lunarFestival,
          lunar.term
        ]
          .filter(Boolean)
          .join(' ');
        const match = legalHolidayKeywords.find((item) =>
          festivalText.includes(item.keyword)
        );
        return match
          ? {
              name: match.label,
              calendarDate: dateItem.format('YYYY-MM-DD')
            }
          : null;
      },
      getLegalHolidayFestivalDates(year) {
        const festivalMap = {};
        const firstDay = dayjs(`${year}-01-01`);
        const end = firstDay.endOf('year');
        let cursor = firstDay;
        while (!cursor.isAfter(end, 'day')) {
          const lunar = solarToLunar(
            cursor.year(),
            cursor.month() + 1,
            cursor.date()
          );
          const festivalText = [
            lunar.solarFestival,
            lunar.lunarFestival,
            lunar.term
          ]
            .filter(Boolean)
            .join(' ');
          const match = legalHolidayKeywords.find((item) =>
            festivalText.includes(item.keyword)
          );
          if (match && !festivalMap[match.label]) {
            festivalMap[match.label] = cursor.format('YYYY-MM-DD');
          }
          cursor = cursor.add(1, 'day');
        }
        return festivalMap;
      },
      getInferredHolidayRanges(year) {
        if (inferredHolidayRangeCache[year]) {
          return inferredHolidayRangeCache[year];
        }
        const festivalDates = this.getLegalHolidayFestivalDates(year);
        const ranges = Object.keys(festivalDates).map((name) => {
          const rule = inferredHolidayRules[name] || { before: 0, after: 0 };
          const date = dayjs(festivalDates[name]);
          return {
            name,
            start: date.subtract(rule.before, 'day').format('YYYY-MM-DD'),
            end: date.add(rule.after, 'day').format('YYYY-MM-DD')
          };
        });
        inferredHolidayRangeCache[year] = ranges;
        return ranges;
      },
      getConfiguredHolidayRanges(year) {
        const remoteRanges = this.legalHolidayCache[year];
        if (remoteRanges && remoteRanges.length) {
          return remoteRanges;
        }
        return (
          officialHolidayRanges[year] || this.getInferredHolidayRanges(year)
        );
      },
      async loadLegalHolidaysByYears(applyYear) {
        const years = normalizeYearList(applyYear);
        if (!years.length) {
          return;
        }
        this.legalHolidayLoading = true;
        try {
          await Promise.all(
            years.map((year) => this.loadLegalHolidayYear(year))
          );
        } finally {
          this.legalHolidayLoading = false;
        }
      },
      async loadLegalHolidayYear(year) {
        if (
          this.legalHolidayCache[year] ||
          this.legalHolidayStatusMap[year] === 'fallback'
        ) {
          return this.legalHolidayCache[year];
        }
        if (!this.legalHolidayPromises[year]) {
          this.$set(this.legalHolidayStatusMap, year, 'loading');
          this.legalHolidayPromises[year] = getPublicLegalHolidays(year)
            .then((data) => {
              const ranges = this.normalizePublicHolidayRanges(data, year);
              if (ranges.length) {
                this.$set(this.legalHolidayCache, year, ranges);
                this.$set(this.legalHolidayStatusMap, year, 'remote');
              } else {
                this.$set(this.legalHolidayStatusMap, year, 'fallback');
              }
              return ranges;
            })
            .catch(() => {
              this.$set(this.legalHolidayStatusMap, year, 'fallback');
              return [];
            })
            .finally(() => {
              this.$delete(this.legalHolidayPromises, year);
            });
        }
        return this.legalHolidayPromises[year];
      },
      normalizePublicHolidayRanges(data, year) {
        const rawList = this.extractPublicHolidayList(data);
        const dateMap = {};
        rawList.forEach((item) => {
          const source = this.normalizePublicHolidayItem(item);
          const dateText = this.normalizePublicHolidayDate(
            source.calendarDate,
            year
          );
          if (!dateText) {
            return;
          }
          const isWorkday =
            source.isWorkday === true ||
            source.is_workday === true ||
            source.workday === true ||
            source.is_holiday === false ||
            source.holiday === false ||
            source.type === 'workday' ||
            source.type === 'work' ||
            source.type === 0;
          if (isWorkday) {
            return;
          }
          dateMap[dateText] = {
            name:
              source.name ||
              source.holidayName ||
              source.festival ||
              source.title ||
              '法定节假日',
            calendarDate: dateText
          };
        });
        return this.mergeHolidayDatesToRanges(Object.values(dateMap));
      },
      normalizePublicHolidayItem(item) {
        if (typeof item === 'string') {
          return {
            calendarDate: item
          };
        }
        if (!item || typeof item !== 'object') {
          return {};
        }
        return {
          ...item,
          calendarDate:
            item.date ||
            item.calendarDate ||
            item.holidayDate ||
            item.day ||
            item.time
        };
      },
      normalizePublicHolidayDate(value, year) {
        if (!value && value !== 0) {
          return '';
        }
        const text = String(value).trim();
        let match = text.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})$/);
        if (match) {
          const date = dayjs(
            `${match[1]}-${String(match[2]).padStart(2, '0')}-${String(
              match[3]
            ).padStart(2, '0')}`
          );
          return date.isValid() && date.year() === Number(year)
            ? date.format('YYYY-MM-DD')
            : '';
        }
        match = text.match(/^(\d{4})(\d{2})(\d{2})$/);
        if (match) {
          const date = dayjs(`${match[1]}-${match[2]}-${match[3]}`);
          return date.isValid() && date.year() === Number(year)
            ? date.format('YYYY-MM-DD')
            : '';
        }
        match = text.match(/^(\d{1,2})[-/](\d{1,2})$/);
        if (match) {
          const date = dayjs(
            `${year}-${String(match[1]).padStart(2, '0')}-${String(
              match[2]
            ).padStart(2, '0')}`
          );
          return date.isValid() ? date.format('YYYY-MM-DD') : '';
        }
        return '';
      },
      extractPublicHolidayList(data) {
        if (Array.isArray(data)) {
          return data;
        }
        if (Array.isArray(data?.data)) {
          return data.data;
        }
        if (Array.isArray(data?.holidays)) {
          return data.holidays;
        }
        if (Array.isArray(data?.data?.holidays)) {
          return data.data.holidays;
        }
        const source =
          data?.data && typeof data.data === 'object' ? data.data : data;
        if (source && typeof source === 'object') {
          return Object.keys(source).map((key) => {
            const value = source[key];
            if (typeof value === 'string') {
              return {
                calendarDate: key,
                name: value
              };
            }
            return {
              calendarDate: key,
              ...(value || {})
            };
          });
        }
        return [];
      },
      mergeHolidayDatesToRanges(holidayList) {
        const list = holidayList
          .filter((item) => item.calendarDate)
          .sort((a, b) => a.calendarDate.localeCompare(b.calendarDate));
        const ranges = [];
        list.forEach((item) => {
          const last = ranges[ranges.length - 1];
          if (
            last &&
            last.name === item.name &&
            dayjs(item.calendarDate).diff(dayjs(last.end), 'day') === 1
          ) {
            last.end = item.calendarDate;
          } else {
            ranges.push({
              name: item.name,
              start: item.calendarDate,
              end: item.calendarDate
            });
          }
        });
        return ranges;
      },
      getLegalHolidayName(dateItem) {
        return this.getLegalHolidayInfo(dateItem)?.name || '';
      },
      getLegalHolidayOptions(applyYear, applyMonth) {
        const monthSet = new Set(
          (applyMonth || this.getFullMonthList()).map(Number)
        );
        const holidayMap = {};
        normalizeYearList(applyYear).forEach((year) => {
          const ranges = this.getConfiguredHolidayRanges(year);
          ranges.forEach((range) => {
            let cursor = dayjs(range.start);
            const end = dayjs(range.end);
            while (!cursor.isAfter(end, 'day')) {
              if (monthSet.has(cursor.month() + 1)) {
                const calendarDate = cursor.format('YYYY-MM-DD');
                if (!holidayMap[calendarDate]) {
                  holidayMap[calendarDate] = {
                    name: range.name,
                    calendarDate
                  };
                }
              }
              cursor = cursor.add(1, 'day');
            }
          });
        });
        return Object.values(holidayMap).sort((a, b) =>
          a.calendarDate.localeCompare(b.calendarDate)
        );
      },
      getCalendarDateType(cursor, restMode, customRestWeekdays, specialRule) {
        if (specialRule) {
          return specialRule.dateType;
        }
        if (this.getLegalHolidayName(cursor)) {
          return 3;
        }
        if (restMode === 'custom') {
          return customRestWeekdays.has(cursor.day()) ? 2 : 1;
        }
        return this.isRestDateByMode(cursor, restMode) ? 2 : 1;
      },
      getLegalHolidayRules(form) {
        const holidayInfoMap = {};
        this.getLegalHolidayOptions(form.applyYear, form.applyMonth).forEach(
          (item) => {
            holidayInfoMap[item.calendarDate] = item;
          }
        );
        return (form.legalHolidayWorkDates || [])
          .filter((date) => holidayInfoMap[date])
          .map((date) => ({
            calendarDate: date,
            dateType: 1,
            remark: holidayInfoMap[date].name
          }));
      },
      buildCalendarSegments(form) {
        const ranges = form.timeRanges || [];
        const monthList = (form.applyMonth || this.getFullMonthList())
          .map(Number)
          .filter(Boolean);
        const applyMonths = new Set(monthList);
        const restMode =
          form.restMode || this.getRestModeByWeekdays(form.restWeekdays);
        const customRestWeekdays = new Set(
          (form.restWeekdays || []).map(Number)
        );
        const specialRuleMap = {};
        const specialRules = [
          ...this.getLegalHolidayRules(form),
          ...(form.holidayRules || [])
        ];
        specialRules.forEach((rule) => {
          if (rule.calendarDate) {
            specialRuleMap[rule.calendarDate] = {
              ...rule,
              dateType: Number(rule.dateType || 3)
            };
          }
        });
        const segments = [];
        if (!applyMonths.size) {
          return segments;
        }
        normalizeYearList(form.applyYear).forEach((year) => {
          const months = Array.from(applyMonths).sort((a, b) => a - b);
          let cursor = dayjs(
            `${year}-${String(months[0]).padStart(2, '0')}-01`
          );
          const lastMonth = months[months.length - 1];
          const end = dayjs(
            `${year}-${String(lastMonth).padStart(2, '0')}-01`
          ).endOf('month');
          while (!cursor.isAfter(end, 'day')) {
            if (!applyMonths.has(cursor.month() + 1)) {
              cursor = cursor.add(1, 'day');
              continue;
            }
            const calendarDate = cursor.format('YYYY-MM-DD');
            const specialRule = specialRuleMap[calendarDate];
            const legalHolidayName = this.getLegalHolidayName(cursor);
            const dateType = this.getCalendarDateType(
              cursor,
              restMode,
              customRestWeekdays,
              specialRule
            );
            if (dateType === 1) {
              const workRanges = ranges.length
                ? ranges
                : [
                    {
                      startTime: '00:00',
                      endTime: '23:59',
                      scheduleStatus: 0,
                      remark:
                        specialRule?.remark || legalHolidayName || '工作日'
                    }
                  ];
              workRanges.forEach((range) => {
                segments.push({
                  calendarDate,
                  startTime: range.startTime,
                  endTime: range.endTime,
                  dateType: 1,
                  scheduleStatus:
                    range.scheduleStatus === undefined
                      ? 0
                      : range.scheduleStatus,
                  remark:
                    range.name ||
                    range.remark ||
                    specialRule?.remark ||
                    legalHolidayName ||
                    ''
                });
              });
            } else {
              segments.push({
                calendarDate,
                startTime: '00:00',
                endTime: '23:59',
                dateType,
                scheduleStatus: 0,
                remark:
                  specialRule?.remark ||
                  legalHolidayName ||
                  (dateType === 3 ? '法定节假日' : '休息日')
              });
            }
            cursor = cursor.add(1, 'day');
          }
        });
        return segments;
      },
      hasInvalidRestRule() {
        if (
          this.calendarForm.restMode === 'custom' &&
          !(this.calendarForm.restWeekdays || []).length
        ) {
          this.$message.error('自定义休息请至少选择一个休息星期');
          return true;
        }
        return false;
      },
      hasInvalidHolidayRules() {
        const dateMap = {};
        (this.calendarForm.legalHolidayWorkDates || []).forEach((date) => {
          dateMap[date] = true;
        });
        const years = new Set(
          normalizeYearList(this.calendarForm.applyYear).map(String)
        );
        const months = new Set(
          (this.calendarForm.applyMonth || []).map((item) => Number(item))
        );
        for (const rule of this.calendarForm.holidayRules || []) {
          if (!rule.calendarDate) {
            this.$message.error('特殊日期存在未选择日期的配置，请补充或删除');
            return true;
          }
          if (!years.has(dayjs(rule.calendarDate).format('YYYY'))) {
            this.$message.error('特殊日期必须在适用年份范围内');
            return true;
          }
          if (!months.has(dayjs(rule.calendarDate).month() + 1)) {
            this.$message.error('特殊日期必须在适用月份范围内');
            return true;
          }
          if (dateMap[rule.calendarDate]) {
            this.$message.error('特殊日期存在重复日期，请调整后保存');
            return true;
          }
          dateMap[rule.calendarDate] = true;
        }
        return false;
      },
      hasDuplicateDetail(
        calendarId,
        calendarDate,
        startTime,
        endTime,
        excludeId
      ) {
        const calendar = this.calendars.find((item) => item.id === calendarId);
        if (!calendar) {
          return false;
        }
        return this.details.some((item) => {
          const itemCalendar = this.calendars.find(
            (calendarItem) => calendarItem.id === item.calendarId
          );
          return (
            item.id !== excludeId &&
            item.calendarDate === calendarDate &&
            itemCalendar?.calendarType === calendar.calendarType &&
            isOverlap(startTime, endTime, item.startTime, item.endTime)
          );
        });
      },
      addHolidayRule() {
        this.calendarForm.holidayRules.push(createHolidayRule(1));
      },
      removeHolidayRule(index) {
        this.calendarForm.holidayRules.splice(index, 1);
      },
      handleApplyMonthChange() {
        this.pruneLegalHolidayWorkDates();
      },
      pruneLegalHolidayWorkDates() {
        const visibleDates = new Set(
          this.legalHolidayOptions.map((item) => item.calendarDate)
        );
        this.calendarForm.legalHolidayWorkDates = (
          this.calendarForm.legalHolidayWorkDates || []
        ).filter((date) => visibleDates.has(date));
      },
      changeLegalHolidayWorkStatus(calendarDate, checked) {
        const dates = new Set(this.calendarForm.legalHolidayWorkDates || []);
        if (checked) {
          dates.add(calendarDate);
        } else {
          dates.delete(calendarDate);
        }
        this.calendarForm.legalHolidayWorkDates = Array.from(dates).sort();
      },
      toggleLegalHolidayWorkStatus(calendarDate) {
        const checked = !(
          this.calendarForm.legalHolidayWorkDates || []
        ).includes(calendarDate);
        this.changeLegalHolidayWorkStatus(calendarDate, checked);
      },
      formatHolidayDate(calendarDate) {
        return dayjs(calendarDate).format('MM-DD');
      },
      getTimePickerOptions(minTime) {
        return minTime
          ? {
              ...this.timePickerOptions,
              minTime
            }
          : this.timePickerOptions;
      },
      async changeCalendarStatus(row) {
        try {
          await updateCalendarStatus({
            id: row.id,
            status: row.status
          });
          await this.loadRemoteCalendarData();
          this.invalidateConflictCache();
          this.reloadBaseTable(1);
          this.$message.success(row.status ? '已启用' : '已禁用');
          return;
        } catch (e) {
          row.status = row.status === 1 ? 0 : 1;
          this.$message.error(e?.message || '状态更新失败，请检查接口返回');
        }
      },
      async deleteCalendarRow(row) {
        if (!row?.id) {
          this.$message.error('未获取到日历ID，无法删除');
          return;
        }
        try {
          const calendarName = row.calendarName || row.calendarCode || row.id;
          await this.$confirm(`确认删除日历 ${calendarName} 吗？`, '删除确认', {
            type: 'warning'
          });
          await deleteCalendar([row.id]);
          await this.loadRemoteCalendarData();
          this.invalidateConflictCache();
          this.reloadBaseTable(1);
          await this.refreshRemoteView();
          this.$message.success('删除成功');
        } catch (e) {
          if (e === 'cancel' || e === 'close') {
            return;
          }
          this.$message.error(e?.message || '删除失败，请检查接口返回');
        }
      },
      openCopyDialog() {
        this.copyForm = {
          sourceId: this.calendars[0]?.id || '',
          targetYear: currentYear + 1
        };
        this.copyDialogVisible = true;
      },
      async copyCalendar() {
        const source = this.calendars.find(
          (item) => item.id === this.copyForm.sourceId
        );
        if (!source || !this.copyForm.targetYear) {
          this.$message.error('请选择源日历和目标年份');
          return;
        }
        try {
          await copyCalendar({
            sourceId: this.copyForm.sourceId,
            targetYear: this.copyForm.targetYear
          });
          await this.loadRemoteCalendarData();
          this.invalidateConflictCache();
          this.reloadBaseTable(1);
          this.copyDialogVisible = false;
          this.$message.success('复制成功，新日历默认禁用');
          return;
        } catch (e) {
          this.$message.error(e?.message || '复制失败，请检查接口返回');
        }
      },
      async locateCalendar(row) {
        this.activeTab = 'view';
        this.viewQuery.calendarType = row.calendarType;
        this.viewQuery.year = row.applyYear[0] || currentYear;
        this.viewQuery.date = `${this.viewQuery.year}-${String(
          this.viewQuery.month
        ).padStart(2, '0')}-01`;
        this.viewMonth = `${this.viewQuery.year}-${String(
          this.viewQuery.month
        ).padStart(2, '0')}`;
        await this.refreshRemoteView();
      },
      async changeViewCalendarType() {
        await this.refreshRemoteView();
      },
      async changeViewMonth(value) {
        if (!value) {
          return;
        }
        const [year, month] = value.split('-');
        this.viewQuery.year = Number(year);
        this.viewQuery.month = Number(month);
        this.viewQuery.date = `${year}-${month}-01`;
        await this.refreshRemoteView();
      },
      async changeViewType(type) {
        if (type === 'quarter') {
          const quarterStartMonth =
            Math.floor((this.viewQuery.month - 1) / 3) * 3 + 1;
          this.viewQuery.month = quarterStartMonth;
          this.viewQuery.date = `${this.viewQuery.year}-${String(
            quarterStartMonth
          ).padStart(2, '0')}-01`;
          this.viewMonth = `${this.viewQuery.year}-${String(
            quarterStartMonth
          ).padStart(2, '0')}`;
          await this.refreshRemoteView();
          return;
        }
        if (type === 'month') {
          this.viewQuery.date = `${this.viewQuery.year}-${String(
            this.viewQuery.month
          ).padStart(2, '0')}-01`;
        }
        await this.refreshRemoteView();
      },
      async refreshRemoteView() {
        await this.loadRemoteViewData(true);
        this.plans = [];
        this.planDataKey = '';
        this.reloadPlanTable(1);
      },
      async refreshStatView() {
        await this.loadRemoteViewData(true, {
          viewType: 'month',
          date: `${this.viewQuery.year}-${String(this.viewQuery.month).padStart(
            2,
            '0'
          )}-01`
        });
      },
      getViewDetails(date) {
        const calendarIds = this.getCurrentViewCalendars().map(
          (item) => item.id
        );
        return this.details.filter(
          (item) =>
            calendarIds.includes(item.calendarId) && item.calendarDate === date
        );
      },
      isWeekend(date) {
        const week = dayjs(date).day();
        return week === 0 || week === 6;
      },
      isRestDay(date, details) {
        if (!details.length) {
          return this.isWeekend(date);
        }
        return details.every((item) => [2, 3].includes(item.dateType));
      },
      getConflictText(date) {
        const conflict = this.conflictList.find(
          (item) => item.calendarDate === date
        );
        return conflict?.message || '';
      },
      matchStatusFilter(day) {
        const filter = this.viewQuery.statusFilter;
        if (!filter || !day.date) {
          return true;
        }
        const matchMap = {
          scheduled: day.scheduleStatus,
          idle: !day.scheduleStatus && !day.isRest,
          rest: day.isRest,
          maintenance: day.isMaintenance,
          temp: day.isTempAdjust,
          conflict: day.isConflict
        };
        return matchMap[filter];
      },
      getDayClass(day) {
        if (!day.date) {
          return ['empty'];
        }
        if (day.isDisabledCalendar) {
          return ['disabled-calendar'];
        }
        if (day.hiddenByFilter) {
          return ['muted'];
        }
        if (day.isConflict) {
          return ['conflict'];
        }
        if (day.isTempAdjust) {
          return ['temp'];
        }
        if (day.isMaintenance) {
          return ['maintenance'];
        }
        if (day.isHoliday) {
          return ['holiday'];
        }
        if (day.isRest) {
          return ['rest'];
        }
        if (day.scheduleStatus) {
          return ['scheduled'];
        }
        return ['idle'];
      },
      getDateTypeText(day) {
        if (!day.date) {
          return '';
        }
        if (day.isHoliday) {
          return day.holidayName || '法定节假日';
        }
        if (day.isRest && !day.details.length) {
          return '休息日';
        }
        const first = day.details[0];
        return first
          ? this.getLabel(dateTypeOptions, first.dateType)
          : '正常工作日';
      },
      getRangeText(details) {
        if (!details.length) {
          return '无有效时段';
        }
        return details
          .map((item) => `${item.startTime}-${item.endTime}`)
          .join('、');
      },
      buildAdjustSegments() {
        return [
          {
            calendarDate: this.adjustForm.adjustDate,
            startTime: this.adjustForm.startTime,
            endTime: this.adjustForm.endTime,
            dateType: this.getAdjustDateType(this.adjustForm.adjustType),
            scheduleStatus: this.adjustForm.adjustType === 2 ? 0 : 1,
            remark: this.adjustForm.applyReason || ''
          }
        ];
      },
      stringifySegments(segments) {
        return JSON.stringify(segments || []);
      },
      parseSegmentContent(content) {
        if (Array.isArray(content)) {
          return content;
        }
        if (!content || typeof content !== 'string') {
          return [];
        }
        try {
          const parsed = JSON.parse(content);
          return Array.isArray(parsed) ? parsed : [parsed];
        } catch (e) {
          const [timeRange] = content.split(' ');
          const [startTime, endTime] = timeRange.split('-');
          return startTime && endTime ? [{ startTime, endTime }] : [];
        }
      },
      formatSegmentContent(content, row = {}) {
        const segments = this.parseSegmentContent(content);
        if (!segments.length) {
          return content || '';
        }
        return segments
          .map((segment) => {
            const startTime = String(segment.startTime || '').slice(0, 5);
            const endTime = String(segment.endTime || '').slice(0, 5);
            const dateType =
              segment.dateType || this.getAdjustDateType(row.adjustType);
            const dateText = segment.calendarDate
              ? `${segment.calendarDate} `
              : '';
            const typeText = this.getLabel(dateTypeOptions, dateType);
            return `${dateText}${startTime}-${endTime} ${typeText}`.trim();
          })
          .join('、');
      },
      formatApproveNodes(approveNode) {
        if (!approveNode) {
          return [];
        }
        let nodes = approveNode;
        if (typeof approveNode === 'string') {
          try {
            nodes = JSON.parse(approveNode);
          } catch (e) {
            return [
              {
                statusText: '审批记录',
                approveOpinion: approveNode,
                approveTime: '',
                approveUserId: ''
              }
            ];
          }
        }
        const list = Array.isArray(nodes) ? nodes : [nodes];
        return list
          .filter((item) => item && typeof item === 'object')
          .map((item) => {
            const status = Number(item.applyStatus ?? item.approveStatus);
            return {
              statusText:
                this.getLabel(approvalStatusOptions, status) ||
                item.statusText ||
                '审批记录',
              approveOpinion:
                item.approveOpinion || item.opinion || item.remark || '',
              approveTime: String(item.approveTime || item.createTime || '')
                .replace('T', ' ')
                .slice(0, 19),
              approveUserId: item.approveUserId || item.userId || ''
            };
          });
      },
      async openDayDrawer(day) {
        if (!day.date || day.hiddenByFilter) {
          return;
        }
        if (day.isDisabledCalendar) {
          this.$message.warning('当前类型日历已禁用，不可编辑');
          return;
        }
        await this.loadRemoteViewData();
        const details = this.getViewDetails(day.date);
        const relatedPlans = this.getDetailRelatedPlans(details);
        this.currentDay = {
          ...day,
          details,
          relatedPlans,
          planCount: this.getDetailPlanCount(details)
        };
        this.dayDrawerVisible = true;
      },
      openAdjustDialog(day = {}) {
        this.adjustForm = this.getDefaultAdjustForm(day);
        const calendar = this.enabledCalendars.find(
          (item) => item.calendarType === this.viewQuery.calendarType
        );
        if (calendar) {
          this.adjustForm.calendarId = calendar.id;
        }
        this.adjustDialogVisible = true;
        this.$nextTick(() => {
          this.$refs.adjustForm?.clearValidate?.();
        });
      },
      submitAdjust() {
        this.$refs.adjustForm.validate((valid) => {
          if (!valid) {
            return;
          }
          if (!this.adjustForm.startTime || !this.adjustForm.endTime) {
            this.$message.error('请选择调整时段');
            return;
          }
          const calendar = this.calendars.find(
            (item) => item.id === this.adjustForm.calendarId
          );
          if (!calendar) {
            this.$message.error('请选择关联日历');
            return;
          }
          const saveApply = async () => {
            const originalDetails = this.details.filter(
              (item) =>
                item.calendarId === calendar.id &&
                item.calendarDate === this.adjustForm.adjustDate
            );
            const newSegments = this.buildAdjustSegments();
            const adjustRow = {
              id: Date.now(),
              applyNo: createCode('ADJ'),
              calendarId: calendar.id,
              calendarName: calendar.calendarName,
              adjustType: this.adjustForm.adjustType,
              adjustDate: this.adjustForm.adjustDate,
              oldContent: this.getRangeText(originalDetails),
              originalDetails: clone(originalDetails),
              newContent: this.stringifySegments(newSegments),
              newContentText: `${this.adjustForm.startTime}-${
                this.adjustForm.endTime
              } ${this.getLabel(
                adjustTypeOptions,
                this.adjustForm.adjustType
              )}`,
              effectiveTime: this.adjustForm.effectiveTime,
              expireTime: this.adjustForm.expireTime,
              applyReason: this.adjustForm.applyReason,
              applyStatus: 0,
              isConflict: 0,
              applyUserName: '当前用户',
              applyTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
              approveNode: '',
              canReactivate: true
            };
            try {
              await applyCalendarAdjust({
                calendarId: calendar.id,
                adjustType: this.adjustForm.adjustType,
                adjustDate: this.adjustForm.adjustDate,
                oldContent: adjustRow.oldContent,
                newContent: adjustRow.newContent,
                effectiveTime: this.adjustForm.effectiveTime,
                expireTime: this.adjustForm.expireTime,
                applyReason: this.adjustForm.applyReason
              });
              await this.loadRemoteAdjustData();
              this.invalidateConflictCache();
              this.reloadAdjustTable(1);
              this.adjustDialogVisible = false;
              this.$message.success('已提交审批');
              return;
            } catch (e) {
              this.$message.error(e?.message || '提交审批失败，请检查接口返回');
            }
          };
          saveApply();
        });
      },
      openApproveDialog(row, status) {
        if (row.applyStatus === 3) {
          this.$message.warning('已失效单据仅可查询追溯，无法重新生效');
          return;
        }
        this.approveForm = {
          row,
          status,
          opinion: status === 1 ? '审批通过' : ''
        };
        this.approveDialogVisible = true;
      },
      async confirmApproveAdjust() {
        if (this.approveForm.status === 2 && !this.approveForm.opinion) {
          this.$message.error('请输入驳回原因');
          return;
        }
        await this.approveAdjust(
          this.approveForm.row,
          this.approveForm.status,
          this.approveForm.opinion
        );
        this.approveDialogVisible = false;
      },
      async approveAdjust(row, status, opinion = '') {
        try {
          await approveCalendarAdjust({
            id: row.id,
            applyStatus: status,
            approveOpinion: opinion
          });
          await this.loadRemoteAdjustData();
          await this.loadRemoteViewData(true);
          this.invalidateConflictCache();
          this.reloadAdjustTable();
          if (status === 1) {
            this.reloadPlanTable(1);
          }
          this.$message.success(status === 1 ? '审批通过并已生效' : '已驳回');
          return;
        } catch (e) {
          this.$message.error(e?.message || '审批失败，请检查接口返回');
        }
      },
      getAdjustDateType(adjustType) {
        return (
          {
            1: 5,
            2: 2,
            3: 4,
            4: 5
          }[adjustType] || 5
        );
      },
      expireAdjustments() {
        // 已停用：临时调整失效由后端任务/接口处理。
      },
      async checkSingleDay(day) {
        if (!day.date || !day.details.length) {
          this.$message.success('当前日期未发现冲突');
          return;
        }
        try {
          const results = await Promise.all(
            day.details.map((detail) =>
              checkCalendarConflict({
                calendarType:
                  detail.calendarType || this.viewQuery.calendarType,
                calendarDate: day.date,
                startTime: detail.startTime,
                endTime: detail.endTime,
                dateType: detail.dateType,
                scheduleStatus: detail.scheduleStatus,
                excludeDetailId: detail.id
              })
            )
          );
          const conflicts = results
            .map((item) => item?.conflictDetail || [])
            .flat();
          if (conflicts.length) {
            this.$message.warning(`发现 ${conflicts.length} 条冲突`);
          } else {
            this.$message.success('当前日期未发现冲突');
          }
        } catch (e) {
          this.$message.error(e?.message || '冲突检测失败，请检查接口返回');
        }
      },
      async toggleScheduleStatus(status) {
        if (!this.currentDay.date || this.currentDay.isDisabledCalendar) {
          this.$message.warning('当前日历不可编辑');
          return;
        }
        const calendar = this.enabledCalendars.find(
          (item) => item.calendarType === this.viewQuery.calendarType
        );
        if (!calendar) {
          this.$message.warning('当前类型日历已禁用');
          return;
        }
        const currentDetails = this.getViewDetails(this.currentDay.date);
        const nextDetails = currentDetails.map((detail) => ({
          ...detail,
          calendarId: calendar.id,
          calendarDate: this.currentDay.date,
          scheduleStatus: status
        }));
        try {
          await Promise.all(
            nextDetails.map((nextDetail) =>
              saveCalendarDetail({
                id: nextDetail.id,
                calendarId: nextDetail.calendarId,
                calendarDate: nextDetail.calendarDate,
                startTime: nextDetail.startTime,
                endTime: nextDetail.endTime,
                dateType: nextDetail.dateType || 1,
                scheduleStatus: nextDetail.scheduleStatus,
                remark: nextDetail.remark,
                forceSave: true
              })
            )
          );
          await this.loadRemoteViewData(true);
          const details = this.getViewDetails(this.currentDay.date);
          const relatedPlans = this.getDetailRelatedPlans(details);
          this.currentDay = {
            ...this.currentDay,
            details,
            relatedPlans,
            planCount: this.getDetailPlanCount(details),
            scheduleStatus: !!status
          };
          this.reloadPlanTable(1);
          this.invalidateConflictCache();
          this.$message.success(status ? '已完成排班并回写状态' : '已取消排班');
          return;
        } catch (e) {
          this.$message.error(e?.message || '排班状态回写失败，请检查接口返回');
        }
      },
      getConflictDataKey() {
        return [
          this.viewQuery.year || '',
          this.viewQuery.calendarType || ''
        ].join('|');
      },
      invalidateConflictCache() {
        this.conflictDataKey = '';
      },
      async runGlobalConflictCheck(showMessage = true, force = showMessage) {
        const dataKey = this.getConflictDataKey();
        if (!force && this.conflictDataKey === dataKey) {
          this.reloadConflictTable(1);
          return;
        }
        if (this.conflictCheckPromise) {
          await this.conflictCheckPromise;
          return;
        }
        try {
          this.conflictCheckPromise = checkAllCalendarConflict({
            year: this.viewQuery.year,
            calendarType: this.viewQuery.calendarType || undefined
          });
          const data = await this.conflictCheckPromise;
          this.conflictList = this.normalizeRemoteConflicts(data);
          this.conflictDataKey = dataKey;
          this.reloadConflictTable(1);
          if (showMessage) {
            this.$message[this.conflictList.length ? 'warning' : 'success'](
              this.conflictList.length
                ? `检测完成，发现 ${this.conflictList.length} 条冲突`
                : '检测完成，未发现冲突'
            );
          }
          return;
        } catch (e) {
          this.conflictList = [];
          this.conflictDataKey = '';
          if (showMessage) {
            this.$message.error(e?.message || '冲突检测失败，请检查接口返回');
          }
        } finally {
          this.conflictCheckPromise = null;
        }
        this.reloadConflictTable(1);
      },
      checkConflict({
        calendarType,
        calendarDate,
        startTime,
        endTime,
        dateType = 1,
        scheduleStatus = 1,
        sourceId
      }) {
        const sameDay = this.details.filter((item) => {
          const calendar = this.calendars.find(
            (calendarItem) => calendarItem.id === item.calendarId
          );
          return (
            item.id !== sourceId &&
            item.calendarDate === calendarDate &&
            calendar?.status === 1 &&
            isOverlap(startTime, endTime, item.startTime, item.endTime)
          );
        });
        const conflicts = [];
        sameDay.forEach((item) => {
          const targetCalendar = this.calendars.find(
            (calendarItem) => calendarItem.id === item.calendarId
          );
          const targetType = targetCalendar?.calendarType;
          const source = {
            calendarType,
            dateType,
            scheduleStatus
          };
          const target = {
            calendarType: targetType,
            dateType: item.dateType,
            scheduleStatus: item.scheduleStatus
          };
          const timeRange = `${Math.max(
            toMinute(startTime),
            toMinute(item.startTime)
          )}-${Math.min(toMinute(endTime), toMinute(item.endTime))}`;
          const readableRange = `${startTime}-${endTime}`;
          if (
            [source.calendarType, target.calendarType].includes(1) &&
            [source.calendarType, target.calendarType].includes(2) &&
            [source, target].some(
              (segment) => segment.calendarType === 2 && segment.dateType === 4
            )
          ) {
            conflicts.push({
              id: `${calendarDate}-${timeRange}-1-2`,
              calendarDate,
              timeRange: readableRange,
              scene: '生产日历 VS 设备维护日历',
              level: '高',
              message: '当前生产作业时段设备停机，无法排产'
            });
          }
          if (
            [source.calendarType, target.calendarType].includes(1) &&
            [source.calendarType, target.calendarType].includes(3) &&
            [source, target].some(
              (segment) =>
                segment.calendarType === 3 &&
                (segment.dateType === 2 ||
                  segment.dateType === 3 ||
                  segment.scheduleStatus === 0)
            )
          ) {
            conflicts.push({
              id: `${calendarDate}-${timeRange}-1-3`,
              calendarDate,
              timeRange: readableRange,
              scene: '生产日历 VS 人员排班日历',
              level: '高',
              message: '当前生产时段无在岗人员，人力缺失'
            });
          }
          if (
            [source.calendarType, target.calendarType].includes(2) &&
            [source.calendarType, target.calendarType].includes(3) &&
            [source, target].some(
              (segment) =>
                segment.calendarType === 3 &&
                (segment.dateType === 2 ||
                  segment.dateType === 3 ||
                  segment.scheduleStatus === 0)
            )
          ) {
            conflicts.push({
              id: `${calendarDate}-${timeRange}-2-3`,
              calendarDate,
              timeRange: readableRange,
              scene: '设备维护日历 VS 人员排班日历',
              level: '中',
              message: '设备检修时段维保人员缺失'
            });
          }
        });
        if (calendarType === 1) {
          const hasStaff = sameDay.some((item) => {
            const targetCalendar = this.calendars.find(
              (calendarItem) => calendarItem.id === item.calendarId
            );
            return (
              targetCalendar?.calendarType === 3 &&
              item.scheduleStatus === 1 &&
              ![2, 3].includes(item.dateType)
            );
          });
          if (!hasStaff) {
            conflicts.push({
              id: `${calendarDate}-${startTime}-${endTime}-1-3-missing`,
              calendarDate,
              timeRange: `${startTime}-${endTime}`,
              scene: '生产日历 VS 人员排班日历',
              level: '高',
              message: '当前生产时段无在岗人员，人力缺失'
            });
          }
        }
        if (calendarType === 2) {
          const hasMaintainer = sameDay.some((item) => {
            const targetCalendar = this.calendars.find(
              (calendarItem) => calendarItem.id === item.calendarId
            );
            return (
              targetCalendar?.calendarType === 3 &&
              item.scheduleStatus === 1 &&
              ![2, 3].includes(item.dateType)
            );
          });
          if (!hasMaintainer) {
            conflicts.push({
              id: `${calendarDate}-${startTime}-${endTime}-2-3-missing`,
              calendarDate,
              timeRange: `${startTime}-${endTime}`,
              scene: '设备维护日历 VS 人员排班日历',
              level: '中',
              message: '设备检修时段维保人员缺失'
            });
          }
        }
        return conflicts;
      },
      uniqueConflicts(conflicts) {
        const map = {};
        conflicts.forEach((item) => {
          map[item.id] = item;
        });
        return Object.values(map);
      },
      async locateConflict(row) {
        this.activeTab = 'view';
        this.viewQuery.viewType = 'day';
        this.viewMonth = dayjs(row.calendarDate).format('YYYY-MM');
        this.viewQuery.year = Number(dayjs(row.calendarDate).format('YYYY'));
        this.viewQuery.month = Number(dayjs(row.calendarDate).format('M'));
        this.viewQuery.date = row.calendarDate;
        this.viewQuery.statusFilter = 'conflict';
        await this.refreshRemoteView();
      },
      handleTabChange() {
        if (this.activeTab === 'base') {
          this.reloadBaseTable(1);
        }
        if (this.activeTab === 'view') {
          this.refreshRemoteView();
        }
        if (this.activeTab === 'stat') {
          this.refreshStatView();
        }
        if (this.activeTab === 'conflict') {
          this.runGlobalConflictCheck(false);
        }
        if (this.activeTab === 'adjust') {
          this.reloadAdjustTable(1);
        }
        if (this.activeTab === 'plan') {
          this.reloadPlanTable(1);
        }
      },
      getPlanDataKey() {
        const calendar = this.getCurrentViewCalendar();
        return [
          this.getRemoteCalendarId(calendar) || '',
          this.viewQuery.year || '',
          this.viewQuery.month || ''
        ].join('|');
      },
      async ensurePlanRefreshData(force = false) {
        const calendar = this.getCurrentViewCalendar();
        const calendarId = this.getRemoteCalendarId(calendar);
        if (!calendarId) {
          this.plans = [];
          this.planDataKey = '';
          return;
        }
        const dataKey = this.getPlanDataKey();
        if (!force && this.planDataKey === dataKey && this.plans.length) {
          return;
        }
        if (!force && this.planDataPromise) {
          await this.planDataPromise;
          return;
        }
        try {
          this.planDataPromise = refreshCalendarPlan({
            calendarId,
            startDate: `${this.viewQuery.year}-${String(
              this.viewQuery.month
            ).padStart(2, '0')}-01`,
            endDate: dayjs(
              `${this.viewQuery.year}-${String(this.viewQuery.month).padStart(
                2,
                '0'
              )}-01`
            )
              .endOf('month')
              .format('YYYY-MM-DD')
          });
          const data = await this.planDataPromise;
          this.plans = this.normalizeRemotePlans(data);
          this.planDataKey = dataKey;
        } catch (e) {
          this.plans = [];
          this.planDataKey = '';
        } finally {
          this.planDataPromise = null;
        }
      },
      async refreshPlans() {
        try {
          await this.ensurePlanRefreshData(true);
          await this.loadRemoteViewData(true);
          this.reloadPlanTable(1);
          this.$message.success(`已刷新受影响计划，共 ${this.plans.length} 条`);
        } catch (e) {
          this.plans = [];
          this.reloadPlanTable(1);
        }
      },
      reschedulePlans() {
        this.$message.warning('批量重排需要后端返回可重排计划后执行');
        this.reloadPlanTable(1);
      },
      async jumpPlanToCalendar(row) {
        const calendar = this.calendars.find(
          (item) => item.id === row.calendarId
        );
        this.activeTab = 'view';
        this.viewQuery.calendarType = calendar?.calendarType || 1;
        this.viewQuery.viewType = 'day';
        this.viewQuery.date = row.calendarDate;
        this.viewMonth = dayjs(row.calendarDate).format('YYYY-MM');
        await this.changeViewMonth(this.viewMonth);
        this.viewQuery.date = row.calendarDate;
        this.$nextTick(async () => {
          await this.loadRemoteViewData(true);
          const details = this.getViewDetails(row.calendarDate);
          const relatedPlans = this.getDetailRelatedPlans(details);
          const day = {
            key: row.calendarDate,
            date: row.calendarDate,
            dayText: dayjs(row.calendarDate).format('D'),
            details,
            relatedPlans,
            planCount: this.getDetailPlanCount(details),
            scheduleStatus: details.some((item) => item.scheduleStatus === 1),
            isConflict: details.some((item) => item.isConflict === 1),
            isTempAdjust: details.some((item) => item.isTempAdjust === 1),
            isMaintenance: details.some((item) => item.dateType === 4),
            isRest: this.isRestDay(row.calendarDate, details),
            isDisabledCalendar: !this.hasEnabledCalendarForView(),
            conflictText: this.getConflictText(row.calendarDate)
          };
          this.openDayDrawer(day);
        });
      },
      exportCalendar() {
        const rows = this.filteredCalendars.map((item) => ({
          calendarCode: item.calendarCode,
          calendarName: item.calendarName,
          calendarType: this.getLabel(calendarTypeOptions, item.calendarType),
          applyYear: item.applyYear.join(','),
          status: item.status ? '启用' : '禁用'
        }));
        this.downloadJson(
          rows,
          `factory-calendar-${dayjs().format('YYYYMMDDHHmmss')}.json`
        );
        this.$message.success(`导出成功，共 ${rows.length} 条`);
      },
      openAdjustDetail(row) {
        this.adjustDetail = clone(row);
        this.adjustDetailVisible = true;
      },
      exportAdjustRecord() {
        if (!this.adjustDetail.id) {
          return;
        }
        this.downloadJson(
          this.adjustDetail,
          `calendar-adjust-${this.adjustDetail.applyNo}.json`
        );
        this.$message.success('追溯记录已导出');
      },
      downloadJson(data, filename) {
        const blob = new Blob([JSON.stringify(data, null, 2)], {
          type: 'application/json;charset=utf-8'
        });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = filename;
        link.click();
        URL.revokeObjectURL(url);
      },
      getCalendarCount(type) {
        return this.calendars.filter((item) => item.calendarType === type)
          .length;
      },
      getStatPercent(value) {
        const total = Math.max(...this.monthStats.map((item) => item.value), 1);
        return Math.round((value / total) * 100);
      },
      calendarTypeTag(type) {
        return {
          1: '',
          2: 'warning',
          3: 'success'
        }[type];
      },
      approvalStatusTag(status) {
        return {
          0: 'warning',
          1: 'success',
          2: 'danger',
          3: 'info'
        }[status];
      },
      getLabel(options, value) {
        return options.find((item) => item.value === value)?.label || '';
      }
    }
  };
</script>

<style lang="scss">
  @import './components/factoryCalendar.scss';
</style>
