<template>
  <div class="vm-dt-cal">
    <header class="vm-dt-cal__head">
      <button type="button" class="vm-dt-cal__title" @click="toggleView">
        <span>{{ headTitle }}</span>
        <i
          class="ri-arrow-down-s-line"
          :class="{ 'is-up': view !== 'date' }"
          aria-hidden="true"
        />
      </button>
      <div class="vm-dt-cal__nav">
        <button type="button" class="vm-dt-cal__nav-btn" :title="crudLabel('prevPage')" @click="prevPage">
          <i class="ri-arrow-up-s-line" aria-hidden="true" />
        </button>
        <button type="button" class="vm-dt-cal__nav-btn" :title="crudLabel('nextPage')" @click="nextPage">
          <i class="ri-arrow-down-s-line" aria-hidden="true" />
        </button>
      </div>
    </header>

    <div v-if="view === 'date'" class="vm-dt-cal__body">
      <div class="vm-dt-cal__weeks">
        <span v-for="w in weekdays" :key="w" class="vm-dt-cal__week">{{ w }}</span>
      </div>
      <div class="vm-dt-cal__grid">
        <button
          v-for="cell in monthCells"
          :key="cell.iso"
          type="button"
          class="vm-dt-cal__day"
          :class="{
            'is-out': !cell.inMonth,
            'is-today': cell.isToday,
            'is-on': selectedIso === cell.iso,
          }"
          @click="emitValue(cell.iso)"
        >
          {{ cell.day }}
        </button>
      </div>
    </div>

    <div v-else-if="view === 'month'" class="vm-dt-cal__body vm-dt-cal__body--months">
      <button
        v-for="(lab, idx) in monthLabels"
        :key="lab"
        type="button"
        class="vm-dt-cal__cell"
        :class="{ 'is-on': viewMonth === idx }"
        @click="pickMonth(idx)"
      >
        {{ lab }}
      </button>
    </div>

    <div v-else class="vm-dt-cal__body vm-dt-cal__body--months">
      <button
        v-for="y in yearCells"
        :key="y"
        type="button"
        class="vm-dt-cal__cell"
        :class="{ 'is-on': viewYear === y }"
        @click="pickYear(y)"
      >
        {{ y }}
      </button>
    </div>

    <div v-if="isDatetime" class="vm-dt-cal__time" @pointerdown.stop>
      <label v-for="f in timeFields" :key="f.key" class="vm-dt-cal__time-field">
        <span>{{ f.label }}</span>
        <input
          type="text"
          inputmode="numeric"
          autocomplete="off"
          maxlength="2"
          :value="draft[f.key]"
          @pointerdown.stop
          @mousedown.stop
          @click.stop
          @keydown.stop="onTimeKeydown(f.key, $event)"
          @focus="onTimeFocus"
          @input="(e) => onTimeInput(f.key, e)"
          @blur="onTimeBlur(f.key)"
        />
      </label>
    </div>

    <footer class="vm-dt-cal__foot">
      <button type="button" class="vm-dt-cal__link" @click="emit('clear')">{{ crudLabel('clear') }}</button>
      <button type="button" class="vm-dt-cal__link" @click="pickToday">{{ crudLabel('today') }}</button>
    </footer>
  </div>
</template>

<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'
import { crudFormat, crudLabel } from '../style'
import {
  buildMonthGrid,
  clampTimePart,
  formatYmd,
  normalizeDateTimePrecision,
  parseApiDateTime,
  type DateTimePrecision,
} from '../../../shared/datetime-picker'

defineOptions({ name: 'vm-date-calendar-panel' })

const props = withDefaults(
  defineProps<{
    modelValue?: string | null
    precision?: DateTimePrecision | string
  }>(),
  {
    modelValue: null,
    precision: 'day',
  },
)

const emit = defineEmits<{
  'update:modelValue': [v: string]
  clear: []
}>()

const precision = computed(() => normalizeDateTimePrecision(props.precision))
const isDatetime = computed(() => precision.value === 'datetime')
const weekdays = computed(() => [
  crudLabel('weekdaySun'),
  crudLabel('weekdayMon'),
  crudLabel('weekdayTue'),
  crudLabel('weekdayWed'),
  crudLabel('weekdayThu'),
  crudLabel('weekdayFri'),
  crudLabel('weekdaySat'),
])
const monthLabels = computed(() =>
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((n) => crudLabel(`month${n}`)),
)
const timeFields = computed(() => [
  { key: 'h' as const, label: crudLabel('hour'), max: 23 },
  { key: 'm' as const, label: crudLabel('minute'), max: 59 },
  { key: 's' as const, label: crudLabel('second'), max: 59 },
])

type PanelView = 'date' | 'month' | 'year'
const view = ref<PanelView>('date')
const draft = reactive({ h: '0', m: '0', s: '0' })
const timeFocused = ref(false)
const viewYear = ref(new Date().getFullYear())
const viewMonth = ref(new Date().getMonth())

watch(
  () => props.modelValue,
  (v) => {
    const d = parseApiDateTime(v) || new Date()
    viewYear.value = d.getFullYear()
    viewMonth.value = d.getMonth()
    if (timeFocused.value) return
    draft.h = String(d.getHours())
    draft.m = String(d.getMinutes())
    draft.s = String(d.getSeconds())
  },
  { immediate: true },
)

const selectedIso = computed(() => {
  const d = parseApiDateTime(props.modelValue)
  return d ? formatYmd(d) : ''
})

const monthCells = computed(() => buildMonthGrid(viewYear.value, viewMonth.value))
const yearStart = computed(() => Math.floor(viewYear.value / 10) * 10)
const yearCells = computed(() =>
  Array.from({ length: 12 }, (_, i) => yearStart.value + i),
)

const headTitle = computed(() => {
  if (view.value === 'year')
    return crudFormat(crudLabel('yearRange'), yearStart.value, yearStart.value + 11)
  if (view.value === 'month') return crudFormat(crudLabel('yearLabel'), viewYear.value)
  return crudFormat(
    crudLabel('yearMonth'),
    viewYear.value,
    String(viewMonth.value + 1).padStart(2, '0'),
  )
})

function currentTime() {
  return {
    h: clampTimePart('h', Number(draft.h) || 0),
    m: clampTimePart('m', Number(draft.m) || 0),
    s: clampTimePart('s', Number(draft.s) || 0),
  }
}

function emitDatetime(isoDate: string, t = currentTime()) {
  const pad = (n: number) => String(n).padStart(2, '0')
  emit(
    'update:modelValue',
    `${isoDate} ${pad(t.h)}:${pad(t.m)}:${pad(t.s)}`,
  )
}

function onTimeFocus() {
  timeFocused.value = true
}

/** 手输过程只改 draft，失焦 / Enter 再提交，避免受控回写打乱光标 */
function onTimeInput(k: 'h' | 'm' | 's', e: Event) {
  const raw = (e.target as HTMLInputElement).value.replace(/\D/g, '').slice(0, 2)
  draft[k] = raw
  ;(e.target as HTMLInputElement).value = raw
}

function commitTimeField(k: 'h' | 'm' | 's') {
  const n = Number(draft[k])
  const next = currentTime()
  next[k] = Number.isFinite(n) ? clampTimePart(k, n) : 0
  draft[k] = String(next[k])
  emitDatetime(selectedIso.value || formatYmd(new Date()), next)
}

function onTimeBlur(k: 'h' | 'm' | 's') {
  commitTimeField(k)
  timeFocused.value = false
}

function onTimeKeydown(k: 'h' | 'm' | 's', e: KeyboardEvent) {
  if (e.key === 'Enter') {
    e.preventDefault()
    commitTimeField(k)
    ;(e.target as HTMLInputElement).blur()
  }
}

function toggleView() {
  view.value = view.value === 'date' ? 'month' : view.value === 'month' ? 'year' : 'date'
}

function prevPage() {
  if (view.value === 'year') {
    viewYear.value -= 12
    return
  }
  if (view.value === 'month') {
    viewYear.value -= 1
    return
  }
  if (viewMonth.value <= 0) {
    viewMonth.value = 11
    viewYear.value -= 1
  } else {
    viewMonth.value -= 1
  }
}

function nextPage() {
  if (view.value === 'year') {
    viewYear.value += 12
    return
  }
  if (view.value === 'month') {
    viewYear.value += 1
    return
  }
  if (viewMonth.value >= 11) {
    viewMonth.value = 0
    viewYear.value += 1
  } else {
    viewMonth.value += 1
  }
}

function emitValue(isoDate: string) {
  if (isDatetime.value) {
    emitDatetime(isoDate)
    return
  }
  if (precision.value === 'month') {
    emit('update:modelValue', isoDate.slice(0, 7))
    return
  }
  if (precision.value === 'year') {
    emit('update:modelValue', isoDate.slice(0, 4))
    return
  }
  emit('update:modelValue', isoDate)
}

function pickMonth(idx: number) {
  viewMonth.value = idx
  view.value = 'date'
  if (precision.value === 'month') {
    emit('update:modelValue', `${viewYear.value}-${String(idx + 1).padStart(2, '0')}`)
  }
}

function pickYear(y: number) {
  viewYear.value = y
  view.value = 'month'
  if (precision.value === 'year') {
    emit('update:modelValue', String(y))
  }
}

function pickToday() {
  const now = new Date()
  viewYear.value = now.getFullYear()
  viewMonth.value = now.getMonth()
  view.value = 'date'
  emitValue(formatYmd(now))
}
</script>

<style lang="scss" scoped>
.vm-dt-cal {
  box-sizing: border-box;
  width: 280px;
  padding: 8px 10px 6px;
  border: 1px solid var(--border, #e8ebf5);
  border-radius: 12px;
  background: var(--card, #fff);
  box-shadow: 0 8px 24px rgb(15 23 42 / 10%);
  color: var(--foreground, #1f2937);
}

.vm-dt-cal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}

.vm-dt-cal__title {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin: 0;
  padding: 4px 6px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;

  &:hover {
    background: var(--muted, #f4f6fb);
  }

  i {
    font-size: 16px;
    color: var(--muted-foreground, #94a3b8);
    transition: transform 0.15s ease;

    &.is-up {
      transform: rotate(180deg);
    }
  }
}

.vm-dt-cal__nav {
  display: flex;
  gap: 2px;
}

.vm-dt-cal__nav-btn {
  display: inline-flex;
  width: 24px;
  height: 24px;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted-foreground, #64748b);
  cursor: pointer;

  &:hover {
    background: var(--muted, #f4f6fb);
    color: var(--foreground, #1f2937);
  }
}

.vm-dt-cal__weeks {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  margin-bottom: 4px;
}

.vm-dt-cal__week {
  height: 28px;
  color: var(--muted-foreground, #64748b);
  font-size: 12px;
  line-height: 28px;
  text-align: center;
}

.vm-dt-cal__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px 0;
}

.vm-dt-cal__day {
  width: 32px;
  height: 32px;
  margin: 0 auto;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font-size: 13px;
  line-height: 32px;
  text-align: center;
  cursor: pointer;

  &.is-out {
    color: var(--muted-foreground, #cbd5e1);
  }

  &.is-today:not(.is-on) {
    color: var(--primary, var(--vm-brand, #4e5dff));
    font-weight: 600;
  }

  &.is-on {
    background: var(--primary, var(--vm-brand, #4e5dff));
    color: #fff;
    font-weight: 600;
  }

  &:hover:not(.is-on) {
    background: color-mix(in srgb, var(--primary, #4e5dff) 10%, transparent);
  }
}

.vm-dt-cal__body--months {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px 6px;
  padding: 4px 0 8px;
}

.vm-dt-cal__cell {
  height: 36px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font-size: 13px;
  cursor: pointer;

  &.is-on {
    background: var(--primary, var(--vm-brand, #4e5dff));
    color: #fff;
    font-weight: 600;
  }

  &:hover:not(.is-on) {
    background: var(--muted, #f4f6fb);
  }
}

.vm-dt-cal__time {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 8px 2px 4px;
  border-top: 1px solid var(--border, #e8ebf5);
  margin-top: 4px;
}

.vm-dt-cal__time-field {
  display: flex;
  flex: 1;
  align-items: center;
  gap: 4px;
  min-width: 0;
  font-size: 12px;
  color: var(--muted-foreground, #64748b);

  input {
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    height: 28px;
    padding: 0 6px;
    border: 1px solid var(--border, #e8ebf5);
    border-radius: 8px;
    background: #fff;
    color: var(--foreground, #1f2937);
    font-size: 13px;
    line-height: 28px;
    text-align: center;
    outline: none;
    cursor: text;
    user-select: text;
    -webkit-user-select: text;
    caret-color: var(--primary, var(--vm-brand, #4e5dff));

    &:focus {
      border-color: var(--primary, var(--vm-brand, #4e5dff));
      background: #fff;
      box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary, #4e5dff) 18%, transparent);
    }
  }
}

.vm-dt-cal__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 6px;
  border-top: 1px solid var(--border, #e8ebf5);
  margin-top: 4px;
}

.vm-dt-cal__link {
  border: 0;
  background: transparent;
  color: var(--primary, var(--vm-brand, #4e5dff));
  font-size: 13px;
  cursor: pointer;
  padding: 4px 6px;
  border-radius: 6px;

  &:hover {
    background: color-mix(in srgb, var(--primary, #4e5dff) 8%, transparent);
  }
}
</style>
