<script setup lang="ts">
// 주간 타임라인(간트형) — 카테고리=레인, 일정=막대(다중일 span). 가로 스크롤 + 라벨 고정.
import { computed, ref } from 'vue'
import type { ApiScheduleCategory, ApiScheduleEvent } from '@/types/schedule'
import type { WeekDay } from '@/shared/utils/scheduleDate'

const props = defineProps<{
  categories: ApiScheduleCategory[]
  events: ApiScheduleEvent[]
  days: WeekDay[]
  activeTypeIds: Set<string>
  selId: string | null
}>()

const emit = defineEmits<{
  (e: 'select', id: string): void
  (e: 'add-category'): void
}>()

const COLW = 156
const LABELW = 132
const ROWH = 32

const cols = computed(() => props.days.length)
const colTpl = computed(
  () => `${LABELW}px repeat(${cols.value}, minmax(${COLW}px, 1fr))`,
)
const trackCols = computed(
  () => `repeat(${cols.value}, minmax(${COLW}px, 1fr))`,
)
const minW = computed(() => LABELW + cols.value * COLW)

// 활성 + 정렬된 레인
const lanes = computed(() =>
  [...props.categories]
    .filter((c) => props.activeTypeIds.has(c.id))
    .sort((a, b) => a.sortOrder - b.sortOrder),
)

interface Cov {
  ev: ApiScheduleEvent
  startIdx: number
  endIdx: number
  span: number
  row: number
}

// 이벤트가 이번 주 days 안에서 차지하는 시작/끝 인덱스
function coverage(ev: ApiScheduleEvent): Omit<Cov, 'row'> | null {
  let startIdx = -1
  let endIdx = -1
  props.days.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, span: endIdx - startIdx + 1 }
}

// 겹침 회피 그리디 행 배치
function packLane(catId: string): { items: Cov[]; rows: number } {
  const cov = props.events
    .filter((e) => e.categoryId === catId)
    .map(coverage)
    .filter((c): c is Omit<Cov, 'row'> => c !== null)
    .sort(
      (a, b) =>
        a.startIdx - b.startIdx || String(a.ev.time).localeCompare(String(b.ev.time)),
    )
  const rowEnd: number[] = []
  const items: Cov[] = cov.map((it) => {
    let r = 0
    while (r < rowEnd.length && (rowEnd[r] ?? -1) >= it.startIdx) r++
    rowEnd[r] = it.endIdx
    return { ...it, row: r }
  })
  return { items, rows: Math.max(rowEnd.length, 1) }
}

const lanesPacked = computed(() =>
  lanes.value.map((c) => ({ cat: c, ...packLane(c.id) })),
)

// 카테고리 색 → 막대 음영(약)
function weakOf(color: string): string {
  return `color-mix(in srgb, ${color} 13%, #fff)`
}

// 헤더/본문 가로 스크롤 동기화
const headRef = ref<HTMLElement | null>(null)
const bodyRef = ref<HTMLElement | null>(null)
function onBodyScroll(): void {
  if (headRef.value && bodyRef.value)
    headRef.value.scrollLeft = bodyRef.value.scrollLeft
}
</script>

<template>
  <div class="tl">
    <!-- 요일 헤더 -->
    <div
      ref="headRef"
      class="tl-head-scroll"
    >
      <div
        class="tl-headrow"
        :style="{ gridTemplateColumns: colTpl, minWidth: `${minW}px` }"
      >
        <div class="tl-corner" />
        <div
          v-for="d in days"
          :key="d.iso"
          class="tl-dayhead"
          :class="{ 'is-today': d.isToday, weekend: d.weekend }"
        >
          <span class="sdow">{{ d.dow }}</span>
          <span class="sd">{{ d.d }}</span>
        </div>
      </div>
    </div>

    <!-- 본문 -->
    <div
      ref="bodyRef"
      class="tl-body-scroll"
      @scroll="onBodyScroll"
    >
      <div
        class="tl-body"
        :style="{ minWidth: `${minW}px` }"
      >
        <div
          v-if="lanesPacked.length === 0"
          class="tl-empty"
        >
          표시할 카테고리가 없습니다. 유형 필터에서 켜거나 카테고리를 추가하세요.
        </div>

        <div
          v-for="lane in lanesPacked"
          :key="lane.cat.id"
          class="tl-row"
          :style="{ gridTemplateColumns: `${LABELW}px 1fr` }"
        >
          <!-- 레인 라벨 -->
          <div class="tl-label">
            <span
              class="dot"
              :style="{ background: lane.cat.color }"
            />
            <span class="ll-name">{{ lane.cat.label }}</span>
          </div>

          <!-- 트랙 -->
          <div class="tl-track">
            <div
              class="tl-guides"
              :style="{ gridTemplateColumns: trackCols }"
            >
              <div
                v-for="d in days"
                :key="d.iso"
                class="g"
                :class="{ 'is-today': d.isToday, weekend: d.weekend }"
              />
            </div>
            <div
              class="tl-bars"
              :style="{
                gridTemplateColumns: trackCols,
                gridTemplateRows: `repeat(${lane.rows}, ${ROWH}px)`,
              }"
            >
              <div
                v-for="it in lane.items"
                :key="it.ev.id"
                class="tl-bar"
                :class="{ sel: it.ev.id === selId }"
                :style="{
                  '--bar-color': lane.cat.color,
                  '--bar-weak': weakOf(lane.cat.color),
                  gridColumn: `${it.startIdx + 1} / span ${it.span}`,
                  gridRow: it.row + 1,
                }"
                @click="emit('select', it.ev.id)"
              >
                <span class="bt">{{ it.ev.title }}</span>
              </div>
            </div>
          </div>
        </div>

        <!-- 카테고리 추가 -->
        <div class="tl-addrow">
          <button
            class="tl-addcat"
            @click="emit('add-category')"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2.2"
              stroke-linecap="round"
              stroke-linejoin="round"
            ><path d="M5 12h14M12 5v14" /></svg>카테고리 추가
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.tl {
  background: var(--panel);
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  box-shadow: var(--shadow-sm);
}
.tl-head-scroll {
  flex-shrink: 0;
  overflow: hidden;
}
.tl-body-scroll {
  flex: 1;
  min-height: 0;
  overflow: auto;
}
.tl-body {
  display: flex;
  flex-direction: column;
}
.tl-headrow {
  display: grid;
  background: var(--panel);
  border-bottom: 1px solid var(--border-strong);
}
.tl-corner {
  position: sticky;
  left: 0;
  z-index: 6;
  background: var(--panel);
  border-right: 1px solid var(--border);
}
.tl-dayhead {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4375rem;
  padding: 0.5625rem 0.5rem;
  text-align: center;
  border-left: 1px solid var(--border);
}
.tl-dayhead .sdow {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-3);
  letter-spacing: 0.0125rem;
}
.tl-dayhead .sd {
  display: inline-grid;
  place-items: center;
  min-width: 1.625rem;
  height: 1.625rem;
  padding: 0 0.3125rem;
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.0187rem;
  border-radius: 0.8125rem;
}
.tl-dayhead.is-today .sdow {
  color: var(--accent);
}
.tl-dayhead.is-today .sd {
  background: var(--accent);
  color: #fff;
}
.tl-dayhead.weekend {
  background: #fafbfc;
}
.tl-dayhead.weekend .sdow,
.tl-dayhead.weekend .sd {
  color: var(--text-3);
}

.tl-row {
  display: grid;
  border-top: 1px solid var(--border);
}
.tl-row:first-of-type {
  border-top: none;
}
.tl-label {
  display: flex;
  align-items: center;
  gap: 0.5625rem;
  padding: 0 0.5rem 0 0.875rem;
  font-size: 0.8438rem;
  font-weight: 600;
  color: var(--text);
  position: sticky;
  left: 0;
  z-index: 3;
  background: var(--panel);
  border-right: 1px solid var(--border);
  white-space: nowrap;
}
.tl-label .dot {
  width: 0.6875rem;
  height: 0.6875rem;
  border-radius: 0.1875rem;
  flex-shrink: 0;
}
.tl-label .ll-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tl-addrow {
  border-top: 1px solid var(--border);
}
.tl-addcat {
  position: sticky;
  left: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  padding: 0.6875rem 0.875rem;
  background: var(--panel);
  border: none;
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text-3);
  cursor: pointer;
}
.tl-addcat:hover {
  color: var(--accent);
}
.tl-addcat svg {
  width: 0.9375rem;
  height: 0.9375rem;
}

.tl-track {
  position: relative;
  min-width: 0;
}
.tl-guides {
  position: absolute;
  inset: 0;
  display: grid;
}
.tl-guides .g {
  border-left: 1px solid var(--border);
}
.tl-guides .g.is-today {
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}
.tl-guides .g.weekend {
  background: #fafbfc;
}
.tl-bars {
  position: relative;
  display: grid;
  row-gap: 0.4375rem;
  column-gap: 0;
  padding: 0.5625rem 0;
}
.tl-bar {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.0625rem;
  margin: 0 0.375rem;
  padding: 0 0.6875rem;
  height: 100%;
  border-radius: 0.4375rem;
  background: var(--bar-weak);
  border-left: 3px solid var(--bar-color);
  cursor: pointer;
  overflow: hidden;
  transition:
    box-shadow 0.12s ease,
    transform 0.12s ease;
}
.tl-bar:hover {
  box-shadow: 0 3px 10px rgba(20, 24, 33, 0.13);
  transform: translateY(-0.0625rem);
}
.tl-bar .bt {
  font-size: 0.7813rem;
  font-weight: 600;
  color: var(--text);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tl-bar.sel {
  box-shadow: 0 0 0 2px var(--accent);
}
.tl-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.625rem;
  padding: 2.5rem;
  color: var(--text-3);
  font-size: 0.8438rem;
  text-align: center;
}
</style>
