<script setup lang="ts">
// 월간 달력 그리드 — 주(행) × 요일(열). 일정은 주 단위로 다중일 막대(span)로 배치하고,
// 한 주에서 보이는 레인을 초과하면 해당 날짜 칸에 '+N' 으로 표기. 카테고리 색·유형 필터 반영.
import { computed, ref } from 'vue'
import { useClickOutside } from '@/composables/useClickOutside'
import { eventDateText } from '@/shared/utils/scheduleDate'
import type { ApiScheduleCategory, ApiScheduleEvent } from '@/types/schedule'
import type { MonthDay } from '@/shared/utils/scheduleDate'

const props = defineProps<{
  weeks: MonthDay[][]
  events: ApiScheduleEvent[]
  categories: ApiScheduleCategory[]
  activeTypeIds: Set<string>
  selId: string | null
}>()
const emit = defineEmits<{ (e: 'select', id: string): void }>()

const WEEKDAYS = ['월', '화', '수', '목', '금', '토', '일']
// 한 주에서 막대로 보일 최대 레인 수(초과분은 '+N')
const VISIBLE_LANES = 3

const colorMap = computed(() => {
  const m: Record<string, string> = {}
  for (const c of props.categories) m[c.id] = c.color
  return m
})
const colorOf = (catId: string): string => colorMap.value[catId] ?? '#9298a3'
const weakOf = (color: string): string => `color-mix(in srgb, ${color} 13%, #fff)`

const visibleEvents = computed(() =>
  props.events.filter((e) => props.activeTypeIds.has(e.categoryId)),
)

interface MBar {
  ev: ApiScheduleEvent
  startIdx: number
  span: number
  lane: number
}
interface MoreMark {
  col: number
  iso: string
  n: number
}
interface LaidWeek {
  week: MonthDay[]
  items: MBar[]
  mores: MoreMark[]
}

// 주별로 일정을 클립(해당 주 범위)·정렬·레인 패킹
const laidWeeks = computed<LaidWeek[]>(() =>
  props.weeks.map((week) => {
    const ws = week[0]?.iso ?? ''
    const we = week[6]?.iso ?? ''
    const cov = visibleEvents.value
      .map((ev) => {
        if (ev.end < ws || ev.start > we) return null
        let startIdx = -1
        let endIdx = -1
        week.forEach((d, i) => {
          if (d.iso >= ev.start && d.iso <= ev.end) {
            if (startIdx < 0) startIdx = i
            endIdx = i
          }
        })
        if (startIdx < 0) return null
        return { ev, startIdx, endIdx }
      })
      .filter((c): c is { ev: ApiScheduleEvent; startIdx: number; endIdx: number } => c !== null)
      .sort(
        (a, b) =>
          a.startIdx - b.startIdx ||
          String(a.ev.time).localeCompare(String(b.ev.time)),
      )

    const rowEnd: number[] = []
    const items: MBar[] = []
    const moreByDay = [0, 0, 0, 0, 0, 0, 0]
    for (const it of cov) {
      let r = 0
      while (r < rowEnd.length && (rowEnd[r] ?? -1) >= it.startIdx) r++
      rowEnd[r] = it.endIdx
      if (r < VISIBLE_LANES) {
        items.push({
          ev: it.ev,
          startIdx: it.startIdx,
          span: it.endIdx - it.startIdx + 1,
          lane: r,
        })
      } else {
        for (let d = it.startIdx; d <= it.endIdx; d++)
          moreByDay[d] = (moreByDay[d] ?? 0) + 1
      }
    }
    const mores: MoreMark[] = moreByDay
      .map((n, i) => ({ col: i + 1, iso: week[i]?.iso ?? '', n }))
      .filter((m) => m.n > 0)
    return { week, items, mores }
  }),
)

// '+N' 클릭 → 그날 전체 일정 팝오버
const dayPopover = ref<{ iso: string; top: number; left: number } | null>(null)
const popoverRef = ref<HTMLElement | null>(null)
const popoverOpen = computed(() => !!dayPopover.value)
useClickOutside(popoverOpen, () => (dayPopover.value = null), [popoverRef], {
  event: 'mousedown',
  esc: true,
  closeOnScroll: true,
})
function openDay(iso: string, e: MouseEvent): void {
  const r = (e.currentTarget as HTMLElement).getBoundingClientRect()
  const width = 240
  const left = Math.max(8, Math.min(r.left, window.innerWidth - width - 8))
  dayPopover.value = { iso, top: r.bottom + 4, left }
}
const popoverEvents = computed(() => {
  const iso = dayPopover.value?.iso
  if (!iso) return []
  return visibleEvents.value
    .filter((e) => e.start <= iso && e.end >= iso)
    .sort((a, b) => String(a.time).localeCompare(String(b.time)))
})
function dayLabel(iso: string): string {
  return eventDateText(iso, iso)
}
function pickEvent(id: string): void {
  emit('select', id)
  dayPopover.value = null
}
</script>

<template>
  <div class="mg">
    <div class="mg-head">
      <div
        v-for="(d, i) in WEEKDAYS"
        :key="d"
        class="mg-hd"
        :class="{ weekend: i >= 5 }"
      >
        {{ d }}
      </div>
    </div>
    <div class="mg-body">
      <div
        v-for="(wk, wi) in laidWeeks"
        :key="wi"
        class="mg-week"
      >
        <!-- 날짜 칸 배경(테두리/오늘/주말/이전·다음달) -->
        <div
          v-for="(d, di) in wk.week"
          :key="'bg' + d.iso"
          class="mg-bg"
          :class="{ today: d.isToday, weekend: d.weekend, outside: !d.inMonth }"
          :style="{ gridColumn: di + 1, gridRow: '1 / -1' }"
        />
        <!-- 날짜 숫자 -->
        <div
          v-for="(d, di) in wk.week"
          :key="'n' + d.iso"
          class="mg-dnum"
          :class="{ today: d.isToday, outside: !d.inMonth }"
          :style="{ gridColumn: di + 1, gridRow: 1 }"
        >
          <span class="mg-dn">{{ d.d }}</span>
        </div>
        <!-- 일정 막대 -->
        <button
          v-for="b in wk.items"
          :key="b.ev.id"
          type="button"
          class="mg-bar"
          :class="{ sel: b.ev.id === selId }"
          :style="{
            gridColumn: `${b.startIdx + 1} / span ${b.span}`,
            gridRow: b.lane + 2,
            '--c': colorOf(b.ev.categoryId),
            '--w': weakOf(colorOf(b.ev.categoryId)),
          }"
          @click="emit('select', b.ev.id)"
        >
          <span class="mg-bt">{{ b.ev.title }}</span>
        </button>
        <!-- 초과(+N) — 클릭 시 그날 전체 일정 팝오버 -->
        <button
          v-for="m in wk.mores"
          :key="'m' + m.iso"
          type="button"
          class="mg-more"
          :style="{ gridColumn: m.col, gridRow: VISIBLE_LANES + 2 }"
          @click.stop="openDay(m.iso, $event)"
        >
          +{{ m.n }}
        </button>
      </div>
    </div>

    <!-- 날짜별 전체 일정 팝오버 -->
    <Teleport to="body">
      <div
        v-if="dayPopover"
        ref="popoverRef"
        class="mg-pop"
        :style="{ top: dayPopover.top + 'px', left: dayPopover.left + 'px' }"
      >
        <div class="mg-pop-h">
          {{ dayLabel(dayPopover.iso) }}
        </div>
        <button
          v-for="e in popoverEvents"
          :key="e.id"
          type="button"
          class="mg-pop-row"
          :class="{ sel: e.id === selId }"
          @click="pickEvent(e.id)"
        >
          <span
            class="mg-pop-dot"
            :style="{ background: colorOf(e.categoryId) }"
          />
          <span
            v-if="e.time"
            class="mg-pop-tm"
          >{{ e.time }}</span>
          <span class="mg-pop-tt">{{ e.title }}</span>
        </button>
        <div
          v-if="popoverEvents.length === 0"
          class="mg-pop-empty"
        >
          일정이 없습니다.
        </div>
      </div>
    </Teleport>
  </div>
</template>

<style scoped>
.mg {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  background: var(--panel);
  box-shadow: var(--shadow-sm);
}
.mg-head {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  border-bottom: 1px solid var(--border-strong);
  flex-shrink: 0;
}
.mg-hd {
  padding: 0.5rem 0;
  text-align: center;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-3);
  border-left: 1px solid var(--border);
}
.mg-hd:first-child {
  border-left: none;
}
.mg-hd.weekend {
  color: var(--text-3);
  background: #fafbfc;
}
.mg-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}
.mg-week {
  flex: 1;
  min-height: 6rem;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-template-rows: 1.625rem repeat(3, 1.55rem) 1fr;
  row-gap: 0.125rem;
  border-top: 1px solid var(--border);
}
.mg-week:first-child {
  border-top: none;
}
.mg-bg {
  border-left: 1px solid var(--border);
  min-width: 0;
}
.mg-bg:nth-child(7n + 1) {
  border-left: none;
}
.mg-bg.weekend {
  background: #fafbfc;
}
.mg-bg.outside {
  background: #f7f8fa;
}
.mg-bg.today {
  background: color-mix(in srgb, var(--accent) 6%, #fff);
}
.mg-dnum {
  z-index: 1;
  display: flex;
  justify-content: flex-end;
  padding: 0.25rem 0.4375rem 0;
}
.mg-dn {
  display: inline-grid;
  place-items: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.25rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-2);
  border-radius: 0.625rem;
}
.mg-dnum.outside .mg-dn {
  color: var(--text-3);
}
.mg-dnum.today .mg-dn {
  background: var(--accent);
  color: #fff;
}
.mg-bar {
  z-index: 1;
  margin: 0 0.25rem;
  padding: 0 0.4375rem;
  height: 1.45rem;
  display: flex;
  align-items: center;
  border: none;
  border-left: 3px solid var(--c);
  border-radius: 0.3125rem;
  background: var(--w);
  cursor: pointer;
  overflow: hidden;
  font-family: inherit;
}
.mg-bar:hover {
  filter: brightness(0.97);
}
.mg-bar.sel {
  box-shadow: 0 0 0 2px var(--accent);
}
.mg-bt {
  font-size: 0.719rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mg-more {
  z-index: 1;
  align-self: start;
  justify-self: start;
  margin: 0 0.4375rem;
  padding: 0.0625rem 0.25rem;
  border: none;
  background: transparent;
  border-radius: 0.25rem;
  font-family: inherit;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--text-3);
  cursor: pointer;
}
.mg-more:hover {
  background: #eceef1;
  color: var(--text-2);
}

/* 날짜별 전체 일정 팝오버 */
.mg-pop {
  position: fixed;
  z-index: 1000;
  width: 15rem;
  max-height: 18rem;
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 0.625rem;
  box-shadow: var(--shadow-pop);
  padding: 0.375rem;
}
.mg-pop-h {
  padding: 0.375rem 0.5rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-3);
}
.mg-pop-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4375rem 0.5rem;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: inherit;
  text-align: left;
}
.mg-pop-row:hover {
  background: #f5f6f8;
}
.mg-pop-row.sel {
  background: var(--accent-weak);
}
.mg-pop-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  flex-shrink: 0;
}
.mg-pop-tm {
  font-size: 0.719rem;
  font-weight: 600;
  color: var(--text-3);
  flex-shrink: 0;
}
.mg-pop-tt {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mg-pop-empty {
  padding: 0.625rem 0.5rem;
  font-size: 0.75rem;
  color: var(--text-3);
  text-align: center;
}
</style>
