<template>
  <div :class="embedded ? 'factory-calendar-board embedded' : 'factory-calendar-board app-container'">
    <div class="calendar-shell">
      <div class="calendar-head">
        <div>
          <div class="calendar-title">工厂日历排产看板</div>
          <div class="calendar-sub">同一日期聚合展示全部产线×班次，悬浮查看完整任务信息，支持快速编辑日历</div>
        </div>
        <div class="head-actions">
          <el-button size="mini" @click="shiftRangeDays(-pager.pageSize)">上一段</el-button>
          <el-button size="mini" @click="shiftRangeDays(pager.pageSize)">下一段</el-button>
          <el-button size="mini" @click="setHistoryRange">近30天</el-button>
          <el-button size="mini" @click="setHybridRange">前后30天</el-button>
          <el-button size="mini" type="primary" plain @click="goToday">回到今天</el-button>
          <el-date-picker
            v-model="jumpDate"
            size="mini"
            type="date"
            value-format="yyyy-MM-dd"
            placeholder="跳转日期"
            style="width: 140px"
            @change="jumpToDate"
          />
          <el-select v-model="pager.pageSize" size="mini" style="width: 110px" @change="pager.pageNum = 1">
            <el-option :value="7" label="每页7天" />
            <el-option :value="14" label="每页14天" />
            <el-option :value="28" label="每页28天" />
          </el-select>
          <el-button size="mini" icon="el-icon-refresh" @click="handleQuery">刷新</el-button>
        </div>
      </div>

      <el-form v-if="!embedded" :inline="true" size="small" class="calendar-filter">
        <el-form-item label="日期范围">
          <el-date-picker
            v-model="query.range"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            value-format="yyyy-MM-dd"
            style="width: 260px"
          />
        </el-form-item>
        <el-form-item label="产线">
          <el-select v-model="query.lineId" clearable filterable placeholder="全部产线" style="width: 220px">
            <el-option
              v-for="line in lineOptions"
              :key="line.lineId"
              :label="line.lineName || line.lineCode || ('产线' + line.lineId)"
              :value="line.lineId"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="订单/产品">
          <el-input v-model="query.keyword" clearable placeholder="订单号、产品名" style="width: 220px" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
          <el-button icon="el-icon-refresh-left" @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>

      <div class="legend-wrap">
        <span class="legend-title">日历图例：</span>
        <span class="legend-item"><i class="legend-dot day-work" />工作日</span>
        <span class="legend-item"><i class="legend-dot day-rest" />休息日</span>
      </div>

      <div class="calendar-body" :style="boardStyle" v-loading="loading">
        <el-empty v-if="!lineBoard.length || !pagedDateList.length || !shifts.length" description="暂无产线或班次基础数据" />

        <div v-else class="date-grid" :style="dateGridStyle">
          <div class="date-col" v-for="date in displayDateList" :key="date">
            <div class="date-head">{{ date }}</div>
            <div class="slot-list">
              <el-tooltip
                v-for="slot in getDateSlots(date)"
                :key="date + '-' + slot.lineId + '-' + slot.shiftId"
                placement="top"
                effect="dark"
              >
                <div slot="content" class="slot-tooltip">
                  <div>产线：{{ slot.lineName }}</div>
                  <div>班次：{{ slot.shiftName }}（{{ slot.startTime }}-{{ slot.endTime }}<span v-if="slot.crossDay === 1">+1</span>）</div>
                  <div>状态：{{ workdayLabel(slot.lineId, date, slot.shiftId) }}</div>
                  <div v-if="slot.tasks.length">任务：{{ slot.tasks.map(i => taskTooltipText(i)).join('；') }}</div>
                  <div v-else>任务：无</div>
                </div>
                <div class="slot-row" :class="workdayClass(slot.lineId, date, slot.shiftId)">
                  <div class="slot-left">
                    <span class="slot-line">{{ shortText(slot.lineName, 6) }}</span>
                    <span class="slot-shift">{{ shortText(slot.shiftName, 4) }}</span>
                  </div>
                  <div class="slot-mid">
                    <span v-if="slot.tasks.length" class="task-chip" :style="{ backgroundColor: getTaskColor(slot.tasks[0]), borderColor: getTaskColor(slot.tasks[0]) }">
                      {{ shortText(taskDisplayName(slot.tasks[0]), 14) }}
                    </span>
                    <span v-else class="no-task">无任务</span>
                    <span v-if="slot.tasks.length > 1" class="task-count">+{{ slot.tasks.length - 1 }}</span>
                  </div>
                  <el-button type="text" size="mini" class="slot-edit" @click.stop="openShiftEditor(slot, date)">编辑</el-button>
                </div>
              </el-tooltip>
            </div>
          </div>
        </div>
      </div>

      <div class="pager-wrap" v-if="dateList.length > pager.pageSize">
        <el-pagination
          small
          background
          layout="prev, pager, next, total"
          :current-page.sync="pager.pageNum"
          :page-size="pager.pageSize"
          :total="dateList.length"
        />
      </div>
    </div>

    <el-dialog title="编辑工厂日历班次" :visible.sync="shiftEditor.visible" width="500px" append-to-body>
      <el-form ref="shiftEditorFormRef" :model="shiftEditor.form" :rules="shiftEditorRules" label-width="100px" size="small">
        <el-form-item label="日期"><el-input :value="shiftEditor.meta.date" readonly /></el-form-item>
        <el-form-item label="产线"><el-input :value="shiftEditor.meta.lineName" readonly /></el-form-item>
        <el-form-item label="班次"><el-input :value="shiftEditor.meta.shiftName" readonly /></el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="shiftEditor.form.status">
            <el-radio :label="1">工作日</el-radio>
            <el-radio :label="0">停用/休息</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="计划工时" prop="plannedHours">
          <el-input-number v-model="shiftEditor.form.plannedHours" :min="0" :max="24" :step="0.5" :precision="1" style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注"><el-input v-model="shiftEditor.form.remark" type="textarea" :rows="3" /></el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button size="small" @click="shiftEditor.visible = false">取 消</el-button>
        <el-button size="small" type="primary" :loading="shiftEditor.saving" @click="submitShiftEditor">保 存</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import { fetchFactoryCalendar, listShiftTemplate, addCalendarShift, updateCalendarShift } from '@/api/aps/aps'
import { listProductionLine } from '@/api/wms/productionLine'

const TASK_COLORS = ['#5B8FF9', '#61DDAA', '#65789B', '#F6BD16', '#7262FD', '#78D3F8', '#9661BC', '#F6903D', '#008685', '#F08BB4']

export default {
  name: 'ApsFactoryCalendarBoard',
  props: {
    embedded: { type: Boolean, default: false },
    height: { type: Number, default: 420 },
    externalQuery: { type: Object, default: () => ({}) }
  },
  data() {
    return {
      loading: false,
      query: { range: [], lineId: undefined, keyword: '', orderId: undefined },
      jumpDate: '',
      pager: { pageNum: 1, pageSize: 7 },
      lineOptions: [],
      shifts: [],
      calendarPayload: {
        overview: {},
        dateList: [],
        lineNameMap: {},
        shiftNameMap: {},
        lineDayMap: {},
        lineShiftDayMap: {}
      },
      shiftEditor: {
        visible: false,
        saving: false,
        meta: { configId: null, date: '', lineId: null, lineName: '', shiftId: null, shiftName: '' },
        form: { status: 1, plannedHours: 8, remark: '' }
      },
      shiftEditorRules: {
        status: [{ required: true, message: '请选择状态', trigger: 'change' }],
        plannedHours: [{ required: true, message: '请填写计划工时', trigger: 'blur' }]
      }
    }
  },
  computed: {
    boardStyle() { return { minHeight: `${this.height}px` } },
    dateGridStyle() {
      const count = this.displayDateList.length || 1
      return { gridTemplateColumns: `repeat(${count}, minmax(260px, 1fr))` }
    },
    lineBoard() {
      const map = this.calendarPayload.lineNameMap || {}
      let rows = Object.keys(map).map(id => ({ lineId: String(id), lineName: map[id] || ('产线' + id) }))
      if (this.query.lineId) rows = rows.filter(i => String(i.lineId) === String(this.query.lineId))
      return rows
    },
    dateList() {
      if (!this.query.range || this.query.range.length !== 2) return []
      const start = new Date(`${this.query.range[0]} 00:00:00`)
      const end = new Date(`${this.query.range[1]} 00:00:00`)
      const arr = []
      const cursor = new Date(start.getTime())
      while (cursor.getTime() <= end.getTime()) {
        arr.push(this.formatDate(cursor))
        cursor.setDate(cursor.getDate() + 1)
      }
      return arr
    },
    pagedDateList() {
      const start = (this.pager.pageNum - 1) * this.pager.pageSize
      return this.dateList.slice(start, start + this.pager.pageSize)
    },
    displayDateList() {
      return this.pagedDateList || []
    },
    taskLegend() {
      return []
    }
  },
  watch: {
    dateList() {
      const maxPage = Math.max(1, Math.ceil(this.dateList.length / this.pager.pageSize))
      if (this.pager.pageNum > maxPage) this.pager.pageNum = maxPage
    },
    externalQuery: {
      deep: true,
      immediate: true,
      handler(value) {
        if (!value || Object.keys(value).length === 0) return
        if (value.range && value.range.length === 2) this.query.range = [value.range[0], value.range[1]]
        this.query.lineId = value.lineId || undefined
        this.query.orderId = value.orderId || undefined
        this.query.keyword = value.keyword || ''
        if (this.shifts.length) this.handleQuery()
      }
    }
  },
  created() {
    if (!this.query.range.length) this.initDefaultRange()
    this.loadLines()
    this.loadShifts().then(() => this.handleQuery())
  },
  methods: {
    initDefaultRange() {
      const now = new Date()
      const start = new Date(now.getFullYear(), now.getMonth(), now.getDate())
      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 30)
      this.query.range = [this.formatDate(start), this.formatDate(end)]
    },
    parseDate(dateText) {
      return new Date(`${dateText} 00:00:00`)
    },
    shiftRangeDays(delta) {
      if (!this.query.range || this.query.range.length !== 2) {
        this.initDefaultRange()
      }
      const start = this.parseDate(this.query.range[0])
      const end = this.parseDate(this.query.range[1])
      start.setDate(start.getDate() + delta)
      end.setDate(end.getDate() + delta)
      this.query.range = [this.formatDate(start), this.formatDate(end)]
      this.pager.pageNum = 1
      this.handleQuery()
    },
    setHistoryRange() {
      const now = new Date()
      const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 30)
      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate())
      this.query.range = [this.formatDate(start), this.formatDate(end)]
      this.pager.pageNum = 1
      this.handleQuery()
    },
    setHybridRange() {
      const now = new Date()
      const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 30)
      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 30)
      this.query.range = [this.formatDate(start), this.formatDate(end)]
      this.pager.pageNum = 1
      this.handleQuery()
    },
    goToday() {
      const now = new Date()
      const days = this.pager.pageSize
      const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - Math.floor(days / 2))
      const end = new Date(start.getTime())
      end.setDate(end.getDate() + days - 1)
      this.query.range = [this.formatDate(start), this.formatDate(end)]
      this.pager.pageNum = 1
      this.handleQuery()
    },
    jumpToDate(value) {
      if (!value) return
      const d = this.parseDate(value)
      const start = new Date(d.getFullYear(), d.getMonth(), d.getDate() - Math.floor(this.pager.pageSize / 2))
      const end = new Date(start.getTime())
      end.setDate(end.getDate() + this.pager.pageSize - 1)
      this.query.range = [this.formatDate(start), this.formatDate(end)]
      this.pager.pageNum = 1
      this.handleQuery()
    },
    formatDate(d) {
      const y = d.getFullYear()
      const m = `${d.getMonth() + 1}`.padStart(2, '0')
      const day = `${d.getDate()}`.padStart(2, '0')
      return `${y}-${m}-${day}`
    },
    shortText(text, maxLen) {
      const s = String(text || '')
      return s.length > maxLen ? `${s.slice(0, maxLen)}…` : s
    },
    shortTime(dt) {
      if (!dt) return '--:--'
      const d = new Date(dt)
      if (Number.isNaN(d.getTime())) return '--:--'
      return `${`${d.getHours()}`.padStart(2, '0')}:${`${d.getMinutes()}`.padStart(2, '0')}`
    },
    taskDisplayName(task) {
      return task && task.label ? task.label : '任务'
    },
    taskTimeLabel(task) {
      return task && task.label ? task.label : '--'
    },
    taskTooltipText(task) {
      return task && task.title ? task.title : (task && task.label ? task.label : '--')
    },
    async loadLines() {
      const res = await listProductionLine({ pageNum: 1, pageSize: 1000 })
      this.lineOptions = res.rows || []
    },
    async loadShifts() {
      const res = await listShiftTemplate({ pageNum: 1, pageSize: 1000 })
      this.shifts = (res.rows || []).sort((a, b) => String(a.startTime).localeCompare(String(b.startTime)))
    },
    async handleQuery() {
      if (!this.query.range || this.query.range.length !== 2) {
        this.$message.warning('请先选择日期范围')
        return
      }
      this.loading = true
      try {
        const params = {
          queryStart: `${this.query.range[0]} 00:00:00`,
          queryEnd: `${this.query.range[1]} 23:59:59`,
          lineId: this.query.lineId || undefined,
          orderId: this.query.orderId || undefined
        }
        const aggRes = await fetchFactoryCalendar(params)
        this.calendarPayload = aggRes.data || {
          overview: {}, dateList: [], lineNameMap: {}, shiftNameMap: {}, lineDayMap: {}, lineShiftDayMap: {}
        }
      } finally {
        this.loading = false
      }
    },
    resetQuery() {
      this.query.lineId = undefined
      this.query.orderId = undefined
      this.query.keyword = ''
      this.pager.pageNum = 1
      this.initDefaultRange()
      this.handleQuery()
    },
    getDateSlots(date) {
      const slots = []
      this.lineBoard.forEach(line => {
        this.shifts.forEach(shift => {
          const day = this.getLineShiftDay(line.lineId, shift.shiftId, date)
          slots.push({
            ...line,
            shiftId: shift.shiftId,
            shiftName: shift.shiftName,
            startTime: shift.startTime,
            endTime: shift.endTime,
            crossDay: shift.crossDay,
            tasks: day.tasks || [],
            taskCount: day.taskCount || 0,
            dayStatus: day.dayStatus
          })
        })
      })
      return slots
    },
    workdayLabel(lineId, date, shiftId) {
      const day = this.getLineShiftDay(lineId, shiftId, date)
      if (day && day.dayStatus !== undefined && day.dayStatus !== null) {
        return Number(day.dayStatus) === 1 ? '工作日' : '休息'
      }
      return '默认'
    },
    workdayClass(lineId, date, shiftId) {
      const day = this.getLineShiftDay(lineId, shiftId, date)
      if (day && day.dayStatus !== undefined && day.dayStatus !== null) {
        return Number(day.dayStatus) === 1 ? 'workday' : 'restday'
      }
      return 'neutral'
    },
    openShiftEditor(slot, date) {
      const day = this.getLineShiftDay(slot.lineId, slot.shiftId, date)
      this.shiftEditor.meta = {
        configId: day && day.configId ? day.configId : null,
        date,
        lineId: slot.lineId,
        lineName: slot.lineName,
        shiftId: slot.shiftId,
        shiftName: slot.shiftName
      }
      this.shiftEditor.form = {
        status: day && day.dayStatus !== undefined && day.dayStatus !== null ? Number(day.dayStatus) : 1,
        plannedHours: day && day.plannedHours !== undefined && day.plannedHours !== null ? Number(day.plannedHours) : 8,
        remark: day && day.remark ? day.remark : ''
      }
      this.shiftEditor.visible = true
      this.$nextTick(() => {
        this.$refs.shiftEditorFormRef && this.$refs.shiftEditorFormRef.clearValidate()
      })
    },
    submitShiftEditor() {
      this.$refs.shiftEditorFormRef.validate(valid => {
        if (!valid) return
        const payload = {
          configId: this.shiftEditor.meta.configId || undefined,
          calendarDate: this.shiftEditor.meta.date,
          lineId: this.shiftEditor.meta.lineId,
          shiftId: this.shiftEditor.meta.shiftId,
          status: this.shiftEditor.form.status,
          plannedHours: this.shiftEditor.form.plannedHours,
          remark: this.shiftEditor.form.remark
        }
        const req = payload.configId ? updateCalendarShift(payload) : addCalendarShift(payload)
        this.shiftEditor.saving = true
        req.then(() => {
          this.$message.success(payload.configId ? '日历班次已更新' : '日历班次已新增')
          this.shiftEditor.visible = false
          this.handleQuery()
        }).finally(() => {
          this.shiftEditor.saving = false
        })
      })
    },
    getLineShiftDay(lineId, shiftId, date) {
      const map = this.calendarPayload.lineShiftDayMap || {}
      return map[`${String(lineId)}|${String(shiftId)}|${date}`] || { taskCount: 0, dayStatus: null, tasks: [] }
    },
    resolveShiftRange(dateText, shift) {
      if (!shift || !shift.startTime || !shift.endTime || !dateText) return null
      const baseDate = new Date(`${dateText} 00:00:00`)
      const [sh, sm, ss] = shift.startTime.split(':').map(v => Number(v || 0))
      const [eh, em, es] = shift.endTime.split(':').map(v => Number(v || 0))
      const start = new Date(baseDate.getTime())
      start.setHours(sh, sm, ss, 0)
      const end = new Date(baseDate.getTime())
      end.setHours(eh, em, es, 0)
      if (shift.crossDay === 1 || end.getTime() < start.getTime()) end.setDate(end.getDate() + 1)
      return { start: start.getTime(), end: end.getTime() }
    },
    taskKey(task) {
      return `${task.orderId || task.orderCode || ''}_${task.processId || task.processName || ''}`
    },
    getTaskColor(task) {
      const key = this.taskKey(task)
      let hash = 0
      for (let i = 0; i < key.length; i++) hash = ((hash << 5) - hash) + key.charCodeAt(i)
      const idx = Math.abs(hash) % TASK_COLORS.length
      return TASK_COLORS[idx]
    }
  }
}
</script>

<style lang="scss" scoped>
.factory-calendar-board {
  .calendar-shell { border: 1px solid #eef2f7; border-radius: 8px; background: #fff; padding: 10px; }
  .calendar-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
  .calendar-title { font-size: 16px; font-weight: 600; }
  .calendar-sub { margin-top: 4px; color: #909399; font-size: 12px; }
  .head-actions { display: flex; gap: 8px; align-items: center; }
  .calendar-filter { margin-top: 8px; margin-bottom: 6px; }
  .legend-wrap { margin-bottom: 8px; font-size: 12px; color: #606266; }
  .legend-title { color: #909399; margin-right: 6px; }
  .legend-item { margin-right: 10px; }
  .legend-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 4px; }
  .legend-dot.day-work { background: #67c23a; }
  .legend-dot.day-rest { background: #f56c6c; }
  .date-grid { display: grid; gap: 10px; overflow-x: auto; }
  .date-col { border: 1px solid #f1f4f8; border-radius: 6px; padding: 8px; background: #fff; }
  .date-head { font-size: 13px; font-weight: 600; margin-bottom: 8px; color: #409eff; }
  .slot-list { display: flex; flex-direction: column; gap: 6px; }
  .slot-row { border: 1px solid #f2f5f9; border-radius: 4px; background: #fcfdff; padding: 4px 6px; display: flex; align-items: center; justify-content: space-between; gap: 6px; }
  .slot-row.workday { border-left: 3px solid #67c23a; background: #edf8ee; }
  .slot-row.restday { border-left: 3px solid #f56c6c; background: #fff2e6; }
  .slot-row.neutral { border-left: 3px solid #dcdfe6; }
  .slot-left { min-width: 82px; font-size: 12px; color: #606266; display: flex; flex-direction: column; }
  .slot-line { font-weight: 600; color: #303133; }
  .slot-shift { color: #909399; }
  .slot-mid { flex: 1; min-width: 0; display: flex; align-items: center; gap: 4px; }
  .task-chip { color: #fff; border: 1px solid transparent; border-radius: 10px; padding: 0 8px; font-size: 12px; line-height: 20px; max-width: 130px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .task-count { color: #909399; font-size: 12px; }
  .no-task { color: #c0c4cc; font-size: 12px; }
  .slot-edit { padding: 0; font-size: 12px; }
  .pager-wrap { margin-top: 10px; display: flex; justify-content: flex-end; }
}

.slot-tooltip {
  font-size: 12px;
  line-height: 1.6;
  max-width: 360px;
}

.embedded { padding: 0; }
</style>
