<template>
  <AppLayout>
    <div class="docs-page-root">
      <!-- TOC sidebar — sticky within the page -->
      <aside v-if="toc.length > 0" class="docs-toc">
        <div class="docs-toc-inner">
          <p class="toc-title">目录</p>
          <nav class="space-y-0.5">
            <a
              v-for="item in toc"
              :key="item.id"
              :href="`#${item.id}`"
              class="toc-link"
              :class="[
                item.level === 1 ? 'toc-h1' : item.level === 2 ? 'toc-h2' : 'toc-h3',
                activeId === item.id ? 'toc-link-active' : 'toc-link-idle'
              ]"
              @click.prevent="scrollTo(item.id)"
            >{{ item.text }}</a>
          </nav>
        </div>
      </aside>

      <!-- Doc content -->
      <div class="docs-scroll">
        <article
          ref="articleEl"
          class="docs-content"
          v-html="renderedContent"
        ></article>
      </div>
    </div>
  </AppLayout>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref, nextTick } from 'vue'
import { Marked, Renderer } from 'marked'
import DOMPurify from 'dompurify'
import AppLayout from '@/components/layout/AppLayout.vue'
import docRaw from '@/assets/trafficapi_ai_call_documentation.md?raw'

// ─── Slug ─────────────────────────────────────────────────────────────────────
function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/\s+/g, '-')
    .replace(/[^\w\u4e00-\u9fff-]/g, '')
    .replace(/-+/g, '-')
    .replace(/^-|-$/g, '')
}

interface TocItem { id: string; text: string; level: number }

// ─── Parse once ───────────────────────────────────────────────────────────────
const toc: TocItem[] = []
const slugCount: Record<string, number> = {}

const renderer = new Renderer()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
renderer.heading = function (token: any) {
  const text: string = token.text ?? ''
  const depth: number = token.depth ?? 1
  const raw = text.replace(/<[^>]+>/g, '')
  let slug = slugify(raw)
  if (slugCount[slug] !== undefined) { slugCount[slug]++; slug = `${slug}-${slugCount[slug]}` }
  else { slugCount[slug] = 0 }
  toc.push({ id: slug, text: raw, level: depth })
  return `<h${depth} id="${slug}">${text}</h${depth}>`
}

const markedInstance = new Marked({ renderer, breaks: true, gfm: true })
const html = markedInstance.parse(docRaw) as string
const renderedContent = DOMPurify.sanitize(html, { ADD_ATTR: ['id'] })

// ─── Active heading — page-level scroll ───────────────────────────────────────
// AppLayout: header h-16 (64px) + main p-4~p-8. Use 88px as safe offset.
const SCROLL_OFFSET = 88
const activeId = ref(toc[0]?.id ?? '')
const articleEl = ref<HTMLElement | null>(null)
let observer: IntersectionObserver | null = null

function setupObserver() {
  if (observer) observer.disconnect()
  if (!articleEl.value) return
  const headings = articleEl.value.querySelectorAll('h1[id], h2[id], h3[id], h4[id]')
  if (!headings.length) return
  // root: null → observe against the viewport (page scrolls, not an inner div)
  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.id
    },
    { root: null, rootMargin: `-${SCROLL_OFFSET}px 0px -60% 0px`, threshold: 0 }
  )
  headings.forEach((el) => observer!.observe(el))
}

function scrollTo(id: string) {
  const el = document.getElementById(id)
  if (!el) return
  // Scroll at page (window) level, offset by sticky header height
  const top = el.getBoundingClientRect().top + window.scrollY - SCROLL_OFFSET
  window.scrollTo({ top, behavior: 'smooth' })
  activeId.value = id
}

onMounted(async () => { await nextTick(); setupObserver() })
onUnmounted(() => { observer?.disconnect() })
</script>

<style scoped>
/* ═══════════════════════════════════════════════════════════════
   Layout — page scrolls naturally; TOC is sticky
═══════════════════════════════════════════════════════════════ */
.docs-page-root {
  display: flex;
  align-items: flex-start;
  border-radius: 1rem;
  border: 1px solid #e5e7eb;
  background: #ffffff;
  overflow: visible; /* let page scroll */
}
:global(.dark) .docs-page-root {
  background: #111827;
  border-color: rgba(255, 255, 255, 0.08);
}

.docs-toc {
  width: 14rem;
  flex-shrink: 0;
  border-right: 1px solid #e5e7eb;
  display: none;
  background: #f9fafb;
  /* sticky: stays in view as page scrolls */
  position: sticky;
  top: 88px; /* header (64px) + main padding (24px) */
  max-height: calc(100vh - 100px);
  overflow-y: auto;
}
@media (min-width: 1280px) {
  .docs-toc { display: block; }
}
:global(.dark) .docs-toc {
  border-right-color: rgba(255, 255, 255, 0.07);
  background: #0d1525;
}

.docs-toc-inner {
  padding: 1rem;
}

.docs-scroll {
  min-width: 0;
  flex: 1;
}

.docs-content {
  max-width: 56rem;
  margin-left: auto;
  margin-right: auto;
  padding: 2rem 1.5rem;
}

/* Give headings scroll-margin so they aren't hidden behind the sticky header */
.docs-content :deep(h1),
.docs-content :deep(h2),
.docs-content :deep(h3),
.docs-content :deep(h4) {
  scroll-margin-top: 92px;
}

/* ═══════════════════════════════════════════════════════════════
   TOC
═══════════════════════════════════════════════════════════════ */
.toc-title {
  margin-bottom: 0.75rem;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #9ca3af;
}

.toc-link {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border-radius: 0.375rem;
  padding: 0.25rem 0.5rem;
  font-size: 0.8rem;
  text-decoration: none;
  transition: background-color 0.1s, color 0.1s;
}
.toc-h1 { font-weight: 600; }
.toc-h2 { padding-left: 1rem; }
.toc-h3 { padding-left: 1.5rem; font-size: 0.72rem; }

.toc-link-idle { color: #6b7280; }
.toc-link-idle:hover { background: #f3f4f6; color: #111827; }
:global(.dark) .toc-link-idle { color: #9ca3af; }
:global(.dark) .toc-link-idle:hover { background: rgba(255,255,255,0.07); color: #e2e8f0; }

.toc-link-active { background: #ede9fe; color: #6d28d9; }
:global(.dark) .toc-link-active { background: rgba(109,40,217,0.2); color: #a78bfa; }

/* ═══════════════════════════════════════════════════════════════
   Markdown typography — Light
═══════════════════════════════════════════════════════════════ */
.docs-content :deep(h1) {
  margin: 2rem 0 1rem;
  font-size: 1.875rem;
  font-weight: 700;
  color: #111827;
  line-height: 2.25rem;
}
.docs-content :deep(h2) {
  margin: 2rem 0 0.75rem;
  font-size: 1.5rem;
  font-weight: 600;
  color: #1f2937;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid #e5e7eb;
}
.docs-content :deep(h3) {
  margin: 1.5rem 0 0.5rem;
  font-size: 1.2rem;
  font-weight: 600;
  color: #374151;
}
.docs-content :deep(h4) {
  margin: 1rem 0 0.5rem;
  font-size: 1rem;
  font-weight: 600;
  color: #4b5563;
}
.docs-content :deep(p) {
  margin-bottom: 1rem;
  line-height: 1.75;
  color: #374151;
}
.docs-content :deep(ul),
.docs-content :deep(ol) {
  margin-bottom: 1rem;
  padding-left: 1.5rem;
  color: #374151;
}
.docs-content :deep(ul) { list-style-type: disc; }
.docs-content :deep(ol) { list-style-type: decimal; }
.docs-content :deep(li) {
  margin-bottom: 0.25rem;
  line-height: 1.75;
}
.docs-content :deep(a) {
  color: #6d28d9;
  text-decoration: underline;
}
.docs-content :deep(a:hover) { color: #5b21b6; }
.docs-content :deep(blockquote) {
  margin-bottom: 1rem;
  border-left: 4px solid #c4b5fd;
  padding-left: 1rem;
  font-style: italic;
  color: #6b7280;
}
.docs-content :deep(strong) { font-weight: 600; color: #111827; }
.docs-content :deep(hr) {
  margin: 2rem 0;
  border: none;
  border-top: 1px solid #e5e7eb;
}

/* Table — light */
.docs-content :deep(table) {
  margin-bottom: 1rem;
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}
.docs-content :deep(th) {
  border: 1px solid #d1d5db;
  background: #f3f4f6;
  padding: 0.5rem 0.75rem;
  text-align: left;
  font-weight: 600;
  color: #374151;
}
.docs-content :deep(td) {
  border: 1px solid #e5e7eb;
  padding: 0.5rem 0.75rem;
  color: #374151;
}
.docs-content :deep(tr:nth-child(even) td) { background: #f9fafb; }

/* Inline code — light */
.docs-content :deep(code) {
  background: #fef2f2;
  color: #be123c;
  padding: 0.125rem 0.375rem;
  border-radius: 0.25rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.85em;
}

/* Code block — always dark bg */
.docs-content :deep(pre) {
  margin-bottom: 1rem;
  overflow-x: auto;
  border-radius: 0.75rem;
  background: #1e293b;
  border: 1px solid rgba(255,255,255,0.12);
  padding: 1rem;
  font-size: 0.875rem;
  line-height: 1.6;
}
.docs-content :deep(pre code) {
  background: transparent !important;
  color: #e2e8f0 !important;
  padding: 0 !important;
  font-size: inherit;
  border-radius: 0;
}

/* ═══════════════════════════════════════════════════════════════
   Markdown typography — Dark
═══════════════════════════════════════════════════════════════ */
:global(.dark) .docs-content :deep(h1) { color: #f1f5f9; }
:global(.dark) .docs-content :deep(h2) { color: #e2e8f0; border-bottom-color: rgba(255,255,255,0.1); }
:global(.dark) .docs-content :deep(h3) { color: #cbd5e1; }
:global(.dark) .docs-content :deep(h4) { color: #94a3b8; }
:global(.dark) .docs-content :deep(p) { color: #cbd5e1; }
:global(.dark) .docs-content :deep(ul),
:global(.dark) .docs-content :deep(ol) { color: #cbd5e1; }
:global(.dark) .docs-content :deep(li) { color: #cbd5e1; }
:global(.dark) .docs-content :deep(a) { color: #a78bfa; }
:global(.dark) .docs-content :deep(a:hover) { color: #c4b5fd; }
:global(.dark) .docs-content :deep(blockquote) {
  border-left-color: #7c3aed;
  color: #9ca3af;
}
:global(.dark) .docs-content :deep(strong) { color: #f1f5f9; }
:global(.dark) .docs-content :deep(hr) { border-top-color: rgba(255,255,255,0.1); }

/* Table — dark */
:global(.dark) .docs-content :deep(th) {
  border-color: rgba(255,255,255,0.12);
  background: rgba(255,255,255,0.06);
  color: #e2e8f0;
}
:global(.dark) .docs-content :deep(td) {
  border-color: rgba(255,255,255,0.08);
  color: #cbd5e1;
}
:global(.dark) .docs-content :deep(tr:nth-child(even) td) {
  background: rgba(255,255,255,0.03);
}

/* Inline code — dark */
:global(.dark) .docs-content :deep(code) {
  background: rgba(239,68,68,0.12);
  color: #fca5a5;
}

/* Code block dark — slightly lighter to be distinct from page */
:global(.dark) .docs-content :deep(pre) {
  background: #0f172a;
  border-color: rgba(255,255,255,0.1);
}
</style>
