<script setup>
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'

const props = defineProps({ days: { type: Array, default: () => [] }, selected: String })
const emit = defineEmits(['select'])
const { locale, t } = useI18n()
const root = ref(null)
const hover = ref(null)
const cell = ref(13)
const gap = ref(3)
let ro

function ymd(d) {
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}

const zhMonths = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
const enMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

function monthText(m) {
  return locale.value === 'zh-CN' ? zhMonths[m] : enMonths[m]
}

const weekdays = computed(() => [
  t('weekdays.sun'),
  t('weekdays.mon'),
  t('weekdays.tue'),
  t('weekdays.wed'),
  t('weekdays.thu'),
  t('weekdays.fri'),
  t('weekdays.sat'),
])

const matrix = computed(() => {
  const end = new Date()
  end.setHours(0, 0, 0, 0)
  const start = new Date(end)
  start.setDate(start.getDate() - 364)
  start.setDate(start.getDate() - start.getDay())
  const counts = new Map(props.days.map(x => [x.date, x]))
  const cells = []
  let week = 0
  for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
    const date = ymd(d)
    const day = counts.get(date)
    const count = Number(day?.count || 0)
    cells.push({ date, count, added: Number(day?.added || 0), deleted: Number(day?.deleted || 0), week, dow: d.getDay(), level: Math.min(4, count === 0 ? 0 : count < 2 ? 1 : count < 5 ? 2 : count < 10 ? 3 : 4) })
    if (d.getDay() === 6) week++
  }
  const weeks = Math.max(1, ...cells.map(x => x.week + 1))
  const labels = []
  let lastMonth = -1
  for (const item of cells) {
    const m = Number(item.date.slice(5, 7)) - 1
    if (m === lastMonth) continue
    const prev = labels[labels.length - 1]
    if (prev && prev.week === item.week) {
      prev.text = monthText(m)
    } else {
      if (prev) prev.span = Math.max(1, item.week - prev.week)
      labels.push({ week: item.week, span: 1, text: monthText(m) })
    }
    lastMonth = m
  }
  if (labels.length) labels[labels.length - 1].span = Math.max(1, weeks - labels[labels.length - 1].week)
  return { cells, labels, weeks }
})

const monthLabels = computed(() => {
  const unit = cell.value + gap.value
  const total = matrix.value.weeks * unit - gap.value
  return matrix.value.labels.map((m, i, arr) => {
    const next = arr[i + 1]
    let left = m.week * unit
    let width = Math.max(24, (next ? next.week * unit : total) - left)
    if (left + width > total) {
      width = Math.min(width, total)
      left = Math.max(0, total - width)
    }
    return { week: m.week, text: m.text, left, width }
  })
})

function fit() {
  const el = root.value
  if (!el) return
  const weeks = matrix.value.weeks || 53
  const avail = Math.max(0, el.clientWidth - 34)
  const nextGap = avail < 720 ? 2 : 3
  const nextCell = Math.floor((avail - (weeks - 1) * nextGap) / weeks)
  gap.value = nextGap
  cell.value = Math.max(4, Math.min(14, nextCell > 0 ? nextCell : 4))
}

function pick(item) {
  if (!item.date) return
  emit('select', item.date === props.selected ? '' : item.date)
}

onMounted(() => {
  fit()
  ro = new ResizeObserver(fit)
  if (root.value) ro.observe(root.value)
})
onUnmounted(() => ro?.disconnect())
watch(() => matrix.value.weeks, fit)
</script>

<template>
  <div ref="root" class="heat-grid-wrap" :style="{ '--weeks': matrix.weeks, '--cell': cell + 'px', '--gap': gap + 'px' }">
    <div class="heat-months">
      <span v-for="m in monthLabels" :key="m.week + m.text" :style="{ left: m.left + 'px', width: m.width + 'px' }">{{ m.text }}</span>
    </div>
    <div class="heat-body">
      <div class="heat-weekdays"><span v-for="(d, i) in weekdays" :key="i">{{ d }}</span></div>
      <div class="heat-grid">
        <button
          v-for="item in matrix.cells"
          :key="item.date"
          type="button"
          class="heat-cell"
          :class="['l' + item.level, { selected: item.date === selected }]"
          :style="{ gridColumnStart: item.week + 1, gridRowStart: item.dow + 1 }"
          :title="item.date"
          @mouseenter="hover = item"
          @mouseleave="hover = null"
          @click="pick(item)"
        />
      </div>
    </div>
    <p class="heat-tip">
      <template v-if="hover">{{ hover.date }} · {{ hover.count }} {{ t('commitsUnit') }}<template v-if="hover.count"> · <b class="positive">+{{ hover.added }}</b> <b class="negative">-{{ hover.deleted }}</b></template></template>
      <template v-else>{{ t('heatHint') }}</template>
    </p>
  </div>
</template>
