<script setup lang="ts">
import { getHeadingsFromBody } from '~/utils/content-ast'
import type { Heading } from '~/utils/content-ast'

const props = defineProps<{
  body?: Record<string, any> | null
}>()

const headings = ref<Heading[]>([])
const activeId = ref('')
let observer: IntersectionObserver | undefined

const NAV_HEIGHT = 40

const minLevel = computed(() => {
  if (!headings.value.length) return 1
  return Math.min(...headings.value.map((h) => h.level))
})

function indentClass(level: number): string {
  const depth = Math.max(0, level - minLevel.value)
  return ['pl-3', 'pl-6', 'pl-9', 'pl-12', 'pl-14', 'pl-16'][depth] || 'pl-16'
}

function buildHeadings() {
  headings.value = getHeadingsFromBody(props.body)
}

function startObserver() {
  observer?.disconnect()
  if (!headings.value.length) return
  nextTick(() => {
    const els = headings.value
      .map((h) => document.getElementById(h.id))
      .filter(Boolean) as HTMLElement[]
    if (!els.length) return
    observer = new IntersectionObserver(
      (entries) => {
        const visible = entries
          .filter((e) => e.isIntersecting)
          .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)
        if (visible.length > 0) {
          activeId.value = (visible[0].target as HTMLElement).id
        }
      },
      { rootMargin: `${-(NAV_HEIGHT + 24)}px 0px -60% 0px`, threshold: 0 }
    )
    for (const el of els) observer.observe(el)
    const firstVisible = els.find((el) => {
      const r = el.getBoundingClientRect()
      return r.bottom > NAV_HEIGHT + 24
    })
    activeId.value = (firstVisible || els[0]).id
  })
}

buildHeadings()

onMounted(() => { startObserver() })

watch(() => props.body, () => {
  buildHeadings()
  if (headings.value.length) startObserver()
}, { deep: true })

onUnmounted(() => { observer?.disconnect() })

function handleClick(e: MouseEvent, id: string) {
  const el = document.getElementById(id)
  if (!el) return
  e.preventDefault()
  activeId.value = id
  const top = el.getBoundingClientRect().top + window.scrollY - (NAV_HEIGHT + 16)
  window.scrollTo({ top, behavior: 'instant' })
  history.replaceState(null, '', `#${id}`)
}
</script>

<template>
  <aside
    v-if="headings.length > 0"
    class="hidden xl:block fixed top-24 right-[max(1rem,calc((100vw-48rem)/2-18rem))] w-56 max-h-[calc(100vh-8rem)] overflow-y-auto pr-2 text-sm z-30"
    aria-label="目录"
  >
    <div class="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">目录</div>
    <ul class="space-y-1 border-l border-border">
      <li v-for="h in headings" :key="h.id">
        <a
          :href="`#${h.id}`"
          class="block py-1 pr-2 border-l-2 -ml-px transition-colors"
          :class="[
            indentClass(h.level),
            activeId === h.id
              ? 'border-primary text-foreground font-medium'
              : 'border-transparent text-muted-foreground hover:text-foreground hover:border-muted-foreground/40',
          ]"
          @click="handleClick($event, h.id)"
        >
          {{ h.text }}
        </a>
      </li>
    </ul>
  </aside>
</template>
