<template>
  <view class="calendar-grid">
    <view class="calendar-header">
      <view class="nav-btn" @tap="prevMonth">‹</view>
      <text class="month-text">{{ displayYear }}年{{ displayMonthText }}月</text>
      <view class="nav-btn" @tap="nextMonth">›</view>
      <text class="back-today" @tap="backToday">今天</text>
    </view>

    <view class="weekdays">
      <text v-for="(d, wi) in weekNames" :key="wi" class="weekday">{{ d }}</text>
    </view>

    <view ref="grid" class="grid" :style="'height:' + gridHeightPx + 'rpx'">
      <!-- 日期格子 -->
      <view class="row" v-for="(row, rIdx) in weeks" :key="rIdx">
        <view class="cell" v-for="cell in row" :key="cell.fullDate" @tap="onDayClick(cell.fullDate)">
          <text class="date-num" :class="{ dim: !cell.inMonth }">{{ cell.date }}</text>
          <view class="cell-events">
            <view v-for="ev in eventsForDate(cell.fullDate)" :key="ev.id" class="event-chip" :class="'event-type-' + (ev.type || 'default')">
              {{ ev.title }}
            </view>
          </view>
        </view>
      </view>

      <!-- 跨天覆盖层 -->
      <view class="overlay">
        <view v-for="(seg, si) in continuousSegments" :key="si" class="continuous-bar" :style="'left:'+seg._style.left+';width:'+seg._style.width+';top:'+seg._style.top+';height:'+seg._style.height" :class="'event-type-' + (seg.type || 'default')" @tap="onEdit(seg.id)">
          {{ seg.title }}
        </view>
      </view>
    </view>
  </view>
  
</template>

<script>
export default {
  name: 'CalendarGrid',
  props: {
    month: { // YYYY-MM
      type: String,
      required: true
    },
    events: { // [{ id, title, type, start_time, end_time }]
      type: Array,
      default: () => []
    },
    // 每行高度，单位 rpx
    rowHeightRpx: {
      type: Number,
      default: 140
    },
    headerHeightRpx: {
      type: Number,
      default: 80
    },
    weekHeaderHeightRpx: {
      type: Number,
      default: 56
    },
    dateNumberHeightRpx: {
      type: Number,
      default: 48
    },
    barHeightRpx: {
      type: Number,
      default: 28
    },
    barSpacingRpx: {
      type: Number,
      default: 6
    }
  },
  data() {
    return {
      FIRST_DOW: 0, // 0=Sunday
      // rpx 单位（仅存数值，使用时拼接 rpx）
      cellHeight: this.rowHeightRpx,
      headerHeight: this.headerHeightRpx,
      weekHeaderHeight: this.weekHeaderHeightRpx,
      dateNumberHeight: this.dateNumberHeightRpx,
      barHeight: this.barHeightRpx,
      barSpacing: this.barSpacingRpx
    }
  },
  watch: {
    rowHeightRpx(val) {
      this.cellHeight = Number(val) || this.cellHeight
    },
    headerHeightRpx(val) {
      this.headerHeight = Number(val) || this.headerHeight
    },
    weekHeaderHeightRpx(val) {
      this.weekHeaderHeight = Number(val) || this.weekHeaderHeight
    },
    dateNumberHeightRpx(val) {
      this.dateNumberHeight = Number(val) || this.dateNumberHeight
    },
    barHeightRpx(val) {
      this.barHeight = Number(val) || this.barHeight
    },
    barSpacingRpx(val) {
      this.barSpacing = Number(val) || this.barSpacing
    }
  },
  computed: {
    displayMonthText() {
      const m = this.displayMonth
      return (m < 10 ? ('0' + m) : '' + m)
    },
    displayYear() {
      const [y] = this.month.split('-').map(Number)
      return y
    },
    displayMonth() {
      const [, m] = this.month.split('-').map(Number)
      return m
    },
    baseDate() {
      // 兼容 month 传入为空或异常
      if (!this.month || typeof this.month !== 'string') {
        const t = new Date()
        return new Date(t.getFullYear(), t.getMonth(), 1)
      }
      const [yStr, mStr] = this.month.split('-')
      const y = parseInt(yStr, 10)
      const m = parseInt(mStr, 10)
      if (!y || !m) {
        const t = new Date()
        return new Date(t.getFullYear(), t.getMonth(), 1)
      }
      return new Date(y, m - 1, 1)
    },
    weekNames() {
      return ['日','一','二','三','四','五','六']
    },
    weeks() {
      // 6x7 网格
      const firstDay = new Date(this.baseDate)
      if (!(firstDay instanceof Date) || isNaN(firstDay.getTime())) return []
      const startDow = firstDay.getDay()
      const offset = (startDow - this.FIRST_DOW + 7) % 7
      const gridStart = new Date(firstDay)
      gridStart.setDate(1 - offset)

      const weeks = []
      for (let w = 0; w < 6; w += 1) {
        const row = []
        for (let d = 0; d < 7; d += 1) {
          const cur = new Date(gridStart)
          cur.setDate(gridStart.getDate() + (w * 7 + d))
          row.push({
            date: cur.getDate(),
            month: cur.getMonth() + 1,
            year: cur.getFullYear(),
            inMonth: cur.getMonth() === this.baseDate.getMonth(),
            fullDate: `${cur.getFullYear()}-${this.pad2(cur.getMonth()+1)}-${this.pad2(cur.getDate())}`
          })
        }
        weeks.push(row)
      }
      return weeks
    },
    gridHeightPx() {
      // 返回 rpx 数值
      return this.headerHeight + this.weekHeaderHeight + this.cellHeight * 6
    },
    continuousSegments() {
      // 拆分跨天事件为每周分段
      const events = (this.events || []).filter(ev => ev && ev.start_time)
      const y = this.displayYear
      const m = this.displayMonth - 1
      const monthStart = new Date(y, m, 1)
      const monthEnd = new Date(y, m + 1, 0)

      const multiDay = events.filter(ev => {
        if (!ev.end_time) return false
        const s = this.parseDateTime(ev.start_time)
        const e = this.parseDateTime(ev.end_time)
        return s.toDateString() !== e.toDateString()
      })

      const segs = []

      const getWeekStart = (date) => {
        const d = new Date(date)
        const jsDow = d.getDay()
        const off = (jsDow - this.FIRST_DOW + 7) % 7
        d.setDate(d.getDate() - off)
        d.setHours(0,0,0,0)
        return d
      }

      multiDay.forEach(ev => {
        const s = this.parseDateTime(ev.start_time)
        const e = this.parseDateTime(ev.end_time)
        if (e < monthStart || s > monthEnd) return
        const clampedStart = s < monthStart ? monthStart : s
        const clampedEnd = e > monthEnd ? monthEnd : e
        let cursor = getWeekStart(clampedStart)
        while (cursor <= clampedEnd) {
          const weekStart = new Date(cursor)
          const weekEnd = new Date(cursor)
          weekEnd.setDate(weekEnd.getDate() + 6)

          const segStart = clampedStart > weekStart ? clampedStart : weekStart
          const segEnd = clampedEnd < weekEnd ? clampedEnd : weekEnd
          if (segStart <= segEnd) {
            const startCol = (segStart.getDay() - this.FIRST_DOW + 7) % 7
            const endCol = (segEnd.getDay() - this.FIRST_DOW + 7) % 7
            const spanCols = endCol - startCol + 1
            segs.push({
              ...ev,
              weekStartISO: weekStart.toISOString(),
              // 保留本地毫秒时间，避免 ISO/时区转换带来的日期偏移
              segStartMs: segStart.getTime(),
              segEndMs: segEnd.getTime(),
              startCol,
              endCol,
              spanCols
            })
          }
          cursor.setDate(cursor.getDate() + 7)
        }
      })

      // 为每周的分段分配 lane，避免重叠
      const byWeek = {}
      segs.forEach(s => {
        const key = s.weekStartISO
        if (!byWeek[key]) byWeek[key] = []
        byWeek[key].push(s)
      })

      Object.values(byWeek).forEach(arr => {
        arr.sort((a,b) => (a.startCol - b.startCol) || (b.spanCols - a.spanCols))
        const laneEnd = []
        arr.forEach(seg => {
          let placed = false
          for (let i=0;i<laneEnd.length;i+=1) {
            if (laneEnd[i] < seg.startCol) {
              seg.laneIndex = i
              laneEnd[i] = seg.endCol
              placed = true
              break
            }
          }
          if (!placed) {
            seg.laneIndex = laneEnd.length
            laneEnd.push(seg.endCol)
          }
        })
      })

      // 计算样式以网格起点为基准
      const y2 = this.displayYear
      const m2 = this.displayMonth - 1
      const firstDay = new Date(y2, m2, 1)
      const startDow = firstDay.getDay()
      const offset = (startDow - this.FIRST_DOW + 7) % 7
      const gridStart = new Date(firstDay)
      gridStart.setDate(1 - offset)
      gridStart.setHours(0,0,0,0)
      const msPerDay = 86400000
      const cellWidthPct = 100 / 7
      const heightRpx = this.barHeight

      segs.forEach(seg => {
        const segStart = new Date(seg.segStartMs)

        // 基于 weeks 网格直接定位行列，避免周起始偏差
        const startStr = `${segStart.getFullYear()}-${this.pad2(segStart.getMonth()+1)}-${this.pad2(segStart.getDate())}`
        let row = 0
        let col = seg.startCol
        outer: for (let r = 0; r < this.weeks.length; r += 1) {
          const rowArr = this.weeks[r]
          for (let c = 0; c < rowArr.length; c += 1) {
            if (rowArr[c].fullDate === startStr) {
              row = r
              col = c
              break outer
            }
          }
        }

        const leftPct = col * cellWidthPct
        const widthPct = seg.spanCols * cellWidthPct
        const vOffset = (seg.laneIndex || 0) * (this.barHeight + this.barSpacing)
        const topRpx = (row * this.cellHeight) + this.dateNumberHeight + vOffset

        seg._style = {
          left: leftPct + '%',
          width: widthPct + '%',
          top: topRpx + 'rpx',
          height: heightRpx + 'rpx'
        }
      })

      return segs
    }
  },
  methods: {
    prevMonth() {
      const d = new Date(this.baseDate)
      d.setMonth(d.getMonth() - 1)
      this.$emit('monthChange', { year: d.getFullYear(), month: d.getMonth() + 1 })
    },
    nextMonth() {
      const d = new Date(this.baseDate)
      d.setMonth(d.getMonth() + 1)
      this.$emit('monthChange', { year: d.getFullYear(), month: d.getMonth() + 1 })
    },
    backToday() {
      const t = new Date()
      const isSameMonth = t.getFullYear() === this.displayYear && (t.getMonth()+1) === this.displayMonth
      if (!isSameMonth) {
        this.$emit('monthChange', { year: t.getFullYear(), month: t.getMonth() + 1 })
      }
      // 同时触发当天点击
      const full = `${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,'0')}-${String(t.getDate()).padStart(2,'0')}`
      this.onDayClick(full)
    },
    onDayClick(fullDate) {
      this.$emit('dayClick', { fulldate: fullDate })
    },
    onEdit(id) {
      // 向外抛出用于编辑的事件（如有需要）
      this.$emit('edit', id)
    },
    eventsForDate(fullDate) {
      const d0 = new Date(fullDate)
      d0.setHours(0,0,0,0)
      return (this.events || []).filter(ev => {
        if (!ev || !ev.start_time) return false
        const s = this.parseDateTime(ev.start_time)
        const e = ev.end_time ? this.parseDateTime(ev.end_time) : this.parseDateTime(ev.start_time)
        s.setHours(0,0,0,0); e.setHours(0,0,0,0)
        // 单天事件才在格子里显示，跨天事件走覆盖层
        const isMulti = ev.end_time && (s.getTime() !== e.getTime())
        if (isMulti) return false
        return d0.getTime() === s.getTime()
      })
    },
    pad2(n) {
      n = Number(n) || 0
      return n < 10 ? ('0' + n) : String(n)
    },
    parseDateTime(dateTimeStr) {
      if (!dateTimeStr) return null
      const [datePart, timePart = '00:00:00'] = String(dateTimeStr).trim().split(/[T\s]+/)
      const [y, m, d] = datePart.split('-').map(n => parseInt(n, 10))
      const [hh = 0, mm = 0, ss = 0] = timePart.split(':').map(n => parseInt(n, 10))
      // 以本地时间构建，避免不同时区解析成前一天/后一天
      return new Date(y, (m || 1) - 1, d || 1, hh || 0, mm || 0, ss || 0)
    }
  }
}
</script>

<style scoped>
.calendar-grid {
  background: #fff;
  border-radius: 18rpx;
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
  margin: 20rpx;
  padding: 10rpx 0 20rpx 0;
}
.calendar-header {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 46px;
  border-bottom: 1px solid #ededed;
}
.nav-btn {
  width: 40px;
  text-align: center;
  font-size: 20px;
  color: #666;
}
.month-text {
  width: 140px;
  text-align: center;
  font-size: 15px;
  color: #333;
}
.back-today {
  position: absolute;
  right: 8px;
  top: 8px;
  padding: 4px 8px;
  border-radius: 12px;
  font-size: 12px;
  color: #333;
  background: #f1f1f1;
}
.weekdays {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  padding: 6px 10px;
  height: 32px;
  box-sizing: border-box;
}
.weekday {
  width: 14.2857%;
  text-align: center;
  font-size: 12px;
  color: #666;
}
.grid {
  position: relative;
}
.row {
  display: flex;
  flex-direction: row;
}
.cell {
  width: 14.2857%;
  height: 120rpx;
  border-bottom: 1px solid #f5f5f5;
  border-top: 1px solid #f5f5f5;
  border-right: 1px solid #f5f5f5;
  position: relative;
  padding: 2px 2px 2px 2px;
}
.row .cell:first-child {
  border-left: 1px solid #f5f5f5;
}
.date-num {
  font-size: 12px;
  font-weight: 600;
  color: #333;
  position: relative;
  z-index: 3;
}
.date-num.dim {
  color: #bfbfbf;
}
.cell-events {
  position: relative;
  z-index: 3;
}
.event-chip {
  font-size: 11px;
  line-height: 14px;
  padding: 1px 3px;
  margin: 1px 0;
  color: #fff;
  border-radius: 3px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.event-chip.event-type-1 { background: #67C23A; }
.event-chip.event-type-2 { background: #409EFF; }
.event-chip.event-type-3 { background: #E6A23C; }
.event-chip.event-type-4 { background: #F56C6C; }
.event-chip.event-type-5 { background: #909399; }
.event-chip.event-type-default { background: #409EFF; }

.overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  pointer-events: none;
  z-index: 2;
}
.continuous-bar {
  position: absolute;
  pointer-events: auto;
  z-index: 2;
  color: #fff;
  font-size: 11px;
  line-height: 18px;
  padding: 0 4px;
  border-radius: 3px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
  background: #409EFF;
}
.continuous-bar.event-type-1 { background: linear-gradient(90deg, #67C23A 0%, #5CB85C 100%); }
.continuous-bar.event-type-2 { background: linear-gradient(90deg, #409EFF 0%, #337ecc 100%); }
.continuous-bar.event-type-3 { background: linear-gradient(90deg, #E6A23C 0%, #D4952B 100%); }
.continuous-bar.event-type-4 { background: linear-gradient(90deg, #F56C6C 0%, #E85555 100%); }
.continuous-bar.event-type-5 { background: linear-gradient(90deg, #909399 0%, #73767A 100%); }
.continuous-bar.event-type-default { background: linear-gradient(90deg, #409EFF 0%, #337ecc 100%); }
</style>


