<script setup>
import { getExternalFaviconUrl } from '~/lib/external-favicon-url.js'
import { getVisibleSocialLinks } from '~/lib/social-links.js'

const route = useRoute()
const postToc = useState('post-detail-toc', () => [])
const tocNavRef = ref(null)
const activeTocId = ref('')
let tocScrollFrame = 0

const { data: siteSettings } = await useFetch('/api/site-settings', {
  default: () => ({
    title: 'sori.studio',
    description: 'sori.studio 개인 블로그',
    logoText: '井',
    logoUrl: '',
    socialLinks: [],
    adSidebarCode: '',
    adPostSidebarCode: '',
    copyrightText: '©2026 sori.studio'
  })
})

const { data: navigation } = await useFetch('/api/navigation', {
  default: () => ({
    primary: [],
    footer: [],
    recommended: []
  })
})

/**
 * 공개 추천 사이트 목록(비가시 제외)
 * @returns {Array<{ id: string, label: string, url: string, descriptionText?: string, thumbnailUrl?: string }>}
 */
const recommendedSites = computed(() => {
  const list = navigation.value?.recommended
  if (!Array.isArray(list)) {
    return []
  }
  return list.filter((x) => x?.isVisible !== false)
})
const isPostDetailRoute = computed(() => route.path.startsWith('/post/'))
const sidebarAdCode = computed(() => isPostDetailRoute.value ? siteSettings.value?.adPostSidebarCode : siteSettings.value?.adSidebarCode)
const postTocItems = computed(() => Array.isArray(postToc.value) ? postToc.value : [])
const followLinks = computed(() => getVisibleSocialLinks(siteSettings.value?.socialLinks || []))

/**
 * 고정 상단 영역을 고려한 TOC 판정 기준선을 반환한다.
 * @returns {number} 뷰포트 상단 기준 오프셋
 */
const getTocActivationOffset = () => {
  if (!import.meta.client) {
    return 96
  }

  const topChromeHeight = Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--site-top-chrome-height'))

  return (Number.isFinite(topChromeHeight) ? topChromeHeight : 57) + 28
}

/**
 * 현재 본문 스크롤 위치에 해당하는 TOC 항목 ID를 계산한다.
 * @returns {string} 활성 제목 ID
 */
const findActiveTocId = () => {
  if (!import.meta.client || !postTocItems.value.length) {
    return ''
  }

  const offset = getTocActivationOffset()
  const currentY = window.scrollY + offset
  let activeId = postTocItems.value[0].id

  for (const item of postTocItems.value) {
    const target = document.getElementById(item.id)

    if (!target) {
      continue
    }

    const targetY = target.getBoundingClientRect().top + window.scrollY

    if (targetY <= currentY) {
      activeId = item.id
    } else {
      break
    }
  }

  return activeId
}

/**
 * 활성 TOC 링크가 내부 스크롤 영역 안에 보이도록 보정한다.
 * @param {string} id - 활성 제목 ID
 * @returns {void}
 */
const scrollActiveTocIntoView = (id) => {
  if (!import.meta.client || !id || !(tocNavRef.value instanceof HTMLElement)) {
    return
  }

  const nav = tocNavRef.value
  const scrollContainer = nav.closest('.site-sidebar-scroll')
  const link = nav.querySelector(`[data-toc-id="${id}"]`)

  if (!(link instanceof HTMLElement) || !(scrollContainer instanceof HTMLElement)) {
    return
  }

  const containerRect = scrollContainer.getBoundingClientRect()
  const linkRect = link.getBoundingClientRect()
  const navTop = scrollContainer.scrollTop
  const navBottom = navTop + scrollContainer.clientHeight
  const linkTop = navTop + linkRect.top - containerRect.top
  const linkBottom = linkTop + link.offsetHeight
  const buffer = 24

  if (linkTop < navTop + buffer) {
    scrollContainer.scrollTo({
      top: Math.max(0, linkTop - buffer),
      behavior: 'smooth'
    })
    return
  }

  if (linkBottom > navBottom - buffer) {
    scrollContainer.scrollTo({
      top: linkBottom - scrollContainer.clientHeight + buffer,
      behavior: 'smooth'
    })
  }
}

/**
 * 스크롤 이벤트에서 TOC 활성 항목을 갱신한다.
 * @returns {void}
 */
const updateActiveToc = () => {
  if (!import.meta.client || tocScrollFrame) {
    return
  }

  tocScrollFrame = window.requestAnimationFrame(() => {
    tocScrollFrame = 0
    const nextActiveId = findActiveTocId()

    if (!nextActiveId || nextActiveId === activeTocId.value) {
      return
    }

    activeTocId.value = nextActiveId
    scrollActiveTocIntoView(nextActiveId)
  })
}

/**
 * 새 탭으로 열 외부 URL인지
 * @param {string} url - 링크
 * @returns {boolean}
 */
const isExternalNavUrl = (url) => /^https?:\/\//i.test(String(url || '').trim())

/**
 * 추천 사이트 보조 문구를 반환한다.
 * @param {Object} item - 추천 사이트 항목
 * @returns {string} 표시 문구
 */
const getRecommendedDisplayText = (item) => {
  return String(item?.descriptionText || '').trim() || String(item?.url || '').trim()
}

/**
 * 추천 사이트 이미지 URL을 반환한다.
 * @param {Object} item - 추천 사이트 항목
 * @returns {string} 이미지 URL
 */
const getRecommendedImageUrl = (item) => {
  const thumbnailUrl = String(item?.thumbnailUrl || '').trim()
  return thumbnailUrl || getExternalFaviconUrl(item?.url, 64)
}

/**
 * 게시글 목차 링크를 부드럽게 이동한다.
 * @param {MouseEvent} event - 클릭 이벤트
 * @param {string} id - 이동할 제목 ID
 * @returns {void}
 */
const scrollToTocItem = (event, id) => {
  if (!import.meta.client) {
    return
  }

  const target = document.getElementById(id)

  if (!target) {
    return
  }

  event.preventDefault()
  activeTocId.value = id
  scrollActiveTocIntoView(id)
  target.scrollIntoView({
    behavior: 'smooth',
    block: 'start'
  })
  window.history.replaceState(null, '', `${route.path}#${id}`)
}

/** 소개 영역 공개 여부 */
const showAboutSection = false

onMounted(() => {
  if (!import.meta.client) {
    return
  }

  window.addEventListener('scroll', updateActiveToc, { passive: true })
  window.addEventListener('resize', updateActiveToc)
  nextTick(updateActiveToc)
})

onBeforeUnmount(() => {
  if (!import.meta.client) {
    return
  }

  window.removeEventListener('scroll', updateActiveToc)
  window.removeEventListener('resize', updateActiveToc)

  if (tocScrollFrame) {
    window.cancelAnimationFrame(tocScrollFrame)
    tocScrollFrame = 0
  }
})

watch([postTocItems, () => route.fullPath], async () => {
  activeTocId.value = ''
  await nextTick()
  updateActiveToc()
})
</script>

<template>
  <aside class="right-sidebar site-sidebar flex w-full flex-col overflow-hidden border-[var(--site-line)] max-lg:border-l-0 max-lg:border-t max-lg:px-4 max-lg:pb-10 lg:sticky lg:top-[57px] lg:z-10 lg:h-[calc(100vh-57px)] lg:max-h-[calc(100vh-57px)] lg:w-[287px] lg:self-start lg:border-l lg:border-t-0 lg:px-0">
    <div class="right-sidebar__scroll site-sidebar-scroll flex min-h-0 flex-1 flex-col">
      <div v-if="!isPostDetailRoute" class="right-sidebar__block site-sidebar-section py-5 pl-5 pr-5 sm:pr-0 max-lg:px-0">
        <div class="right-sidebar__profile flex items-center gap-3">
          <div class="right-sidebar__logo grid h-12 w-12 shrink-0 place-items-center overflow-hidden rounded-2xl text-2xl font-bold text-[var(--site-invert-text)]">
            <img
              v-if="siteSettings.logoUrl"
              class="h-full w-full object-cover"
              :src="siteSettings.logoUrl"
              :alt="siteSettings.title"
            >
            <span v-else>{{ siteSettings.logoText }}</span>
          </div>
          <div>
            <p class="right-sidebar__title font-semibold">
              {{ siteSettings.title }}
            </p>
            <p class="right-sidebar__description text-sm site-muted">
              {{ siteSettings.description }}
            </p>
          </div>
        </div>
      </div>

      <div v-if="!isPostDetailRoute && followLinks.length" class="right-sidebar__block site-sidebar-section py-5 sm:pl-5 pr-0">
        <div class="right-sidebar__row flex items-center justify-between">
          <p class="right-sidebar__eyebrow text-xs font-semibold uppercase site-muted">
            Follow
          </p>
          <nav class="right-sidebar__social relative z-10 flex flex-wrap items-center gap-1 text-sm text-[var(--site-text)]">
            <a
              v-for="item in followLinks"
              :key="item.id"
              class="right-sidebar__social-link site-interactive inline-flex h-5 w-5 items-center justify-center p-0.5 leading-none hover:opacity-75"
              :href="item.href"
              :aria-label="item.label"
              :target="item.external ? '_blank' : undefined"
              :rel="item.external ? 'noreferrer' : undefined"
            >
              <svg
                v-if="item.icon === 'facebook'"
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="block h-4 w-4 shrink-0"
              >
                <path d="M7 10v4h3v7h4v-7h3l1-4h-4V8a1 1 0 0 1 1-1h3V3h-3a5 5 0 0 0-5 5v2z" />
              </svg>
              <svg
                v-else-if="item.icon === 'x'"
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="block h-4 w-4 shrink-0"
              >
                <path d="M4 4l11.733 16H20L8.267 4z" />
                <path d="M4 20l6.768-6.768m2.46-2.46L20 4" />
              </svg>
              <svg
                v-else-if="item.icon === 'github'"
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="block h-4 w-4 shrink-0"
              >
                <path d="M9 19c-4.3 1.4-4.3-2.5-6-3m12 5v-3.5c0-1 .1-1.4-.5-2c2.8-.3 5.5-1.4 5.5-6a4.6 4.6 0 0 0-1.3-3.2 4.2 4.2 0 0 0-.1-3.2s-1.1-.3-3.5 1.3a12.3 12.3 0 0 0-6.2 0c-2.4-1.6-3.5-1.3-3.5-1.3a4.2 4.2 0 0 0-.1 3.2A4.6 4.6 0 0 0 4 9.5c0 4.6 2.7 5.7 5.5 6-.6.6-.6 1.2-.5 2V21" />
              </svg>
              <svg
                v-else-if="item.icon === 'instagram'"
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="block h-4 w-4 shrink-0"
              >
                <rect x="4" y="4" width="16" height="16" rx="4" />
                <circle cx="12" cy="12" r="3" />
                <line x1="16.5" y1="7.5" x2="16.5" y2="7.501" />
              </svg>
              <svg
                v-else-if="item.icon === 'linkedin'"
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="block h-4 w-4 shrink-0"
              >
                <path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" />
                <rect x="2" y="9" width="4" height="12" />
                <circle cx="4" cy="4" r="2" />
              </svg>
              <svg
                v-else-if="item.icon === 'youtube'"
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="block h-4 w-4 shrink-0"
              >
                <path d="M2.5 17a24.1 24.1 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.6 49.6 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24.1 24.1 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.6 49.6 0 0 1-16.2 0A2 2 0 0 1 2.5 17" />
                <path d="m10 15 5-3-5-3z" />
              </svg>
              <svg
                v-else-if="item.icon === 'rss'"
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="block h-4 w-4 shrink-0"
              >
                <circle cx="5" cy="19" r="1" />
                <path d="M4 4a16 16 0 0 1 16 16" />
                <path d="M4 11a9 9 0 0 1 9 9" />
              </svg>
              <span
                v-else-if="item.icon === 'custom' && item.iconSvg"
                class="right-sidebar__custom-social-icon inline-flex h-4 w-4 items-center justify-center fill-[var(--site-text)]"
                aria-hidden="true"
                v-html="item.iconSvg"
              />
              <svg
                v-else
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="block h-4 w-4 shrink-0"
              >
                <path d="M10 13a5 5 0 0 0 7.54.54l2-2a5 5 0 0 0-7.07-7.07l-1.15 1.15" />
                <path d="M14 11a5 5 0 0 0-7.54-.54l-2 2a5 5 0 0 0 7.07 7.07l1.15-1.15" />
              </svg>
              <span class="sr-only">{{ item.label }}</span>
            </a>
          </nav>
        </div>
      </div>

      <div
        v-if="isPostDetailRoute"
        class="right-sidebar__block right-sidebar__toc py-5 pl-5 pr-0 max-lg:hidden"
      >
        <div class="right-sidebar__row flex shrink-0 items-center justify-between">
          <p class="right-sidebar__eyebrow text-xs font-semibold uppercase site-muted">
            목차
          </p>
        </div>
        <nav ref="tocNavRef" class="right-sidebar__toc-nav mt-4 pr-2" aria-label="게시글 목차">
          <ul v-if="postTocItems.length" class="right-sidebar__toc-list flex list-none flex-col gap-2 border-l border-[var(--site-line)] p-0">
            <li
              v-for="item in postTocItems"
              :key="item.id"
              class="right-sidebar__toc-item relative flex min-h-6 items-center transition-colors"
              :class="activeTocId === item.id ? 'right-sidebar__toc-item--active' : ''"
            >
              <a
                class="right-sidebar__toc-link site-interactive flex min-h-6 w-full items-center rounded-md pr-3 text-sm leading-snug transition-colors hover:text-[var(--site-accent)]"
                :class="{
                  'bg-[var(--site-panel)] text-[var(--site-accent)] font-semibold': activeTocId === item.id,
                  'text-[var(--site-text)]': activeTocId !== item.id,
                  'pl-4 font-semibold': item.level === 1,
                  'pl-7': item.level === 2,
                  'pl-10': item.level === 3,
                  'site-muted': item.level === 3 && activeTocId !== item.id
                }"
                :href="`#${item.id}`"
                :aria-current="activeTocId === item.id ? 'location' : undefined"
                :data-toc-id="item.id"
                @click="scrollToTocItem($event, item.id)"
              >
                {{ item.text }}
              </a>
            </li>
          </ul>
          <p v-else class="right-sidebar__toc-empty text-sm site-muted">
            목차로 표시할 제목이 없습니다.
          </p>
        </nav>
      </div>

      <div
        v-else-if="recommendedSites.length"
        class="right-sidebar__block site-sidebar-section py-5 sm:pl-5 pr-0"
      >
        <div class="right-sidebar__row flex items-center justify-between">
          <p class="right-sidebar__eyebrow text-xs font-semibold uppercase site-muted">
            Recommended
          </p>
        </div>
        <ul class="right-sidebar__recommended-list mt-4 list-none flex flex-col gap-2.5 p-0">
          <li v-for="item in recommendedSites" :key="item.id">
            <a
              class="right-sidebar__recommended-card site-interactive flex items-center gap-3 rounded-xl border border-[var(--site-line)] bg-[var(--site-panel)] px-3 py-2.5 transition-colors hover:border-[var(--site-accent)]"
              :href="item.url"
              :target="isExternalNavUrl(item.url) ? '_blank' : undefined"
              :rel="isExternalNavUrl(item.url) ? 'nofollow noopener noreferrer' : undefined"
            >
              <span class="right-sidebar__recommended-icon grid h-9 w-9 shrink-0 place-items-center overflow-hidden rounded-lg bg-[var(--site-paper)] text-xs font-bold text-[var(--site-text)] ring-1 ring-[var(--site-line)]">
                <img
                  v-if="getRecommendedImageUrl(item)"
                  class="h-full w-full object-cover"
                  :src="getRecommendedImageUrl(item)"
                  width="36"
                  height="36"
                  :alt="item.thumbnailUrl ? item.label : ''"
                  loading="lazy"
                  referrerpolicy="no-referrer"
                >
                <span v-else class="px-1 text-center leading-none">{{ (item.label || '?').slice(0, 1) }}</span>
              </span>
              <span class="min-w-0 flex-1">
                <span class="block truncate text-sm font-semibold text-[var(--site-text)]">{{ item.label }}</span>
                <span class="mt-0.5 block truncate text-[11px] site-muted" :class="item.descriptionText ? '' : 'font-mono'">{{ getRecommendedDisplayText(item) }}</span>
              </span>
              <span class="shrink-0 text-xs site-muted" aria-hidden="true">↗</span>
            </a>
          </li>
        </ul>
      </div>

      <div v-if="showAboutSection" class="right-sidebar__block site-sidebar-section py-5 pl-5 pr-0">
        <p class="right-sidebar__about text-sm leading-6 site-muted">
          {{ siteSettings.description }}
        </p>
        <NuxtLink class="right-sidebar__about-button mt-4 inline-flex rounded-lg px-4 py-2 text-sm font-semibold site-accent-button" to="/pages/about">
          About {{ siteSettings.title }}
        </NuxtLink>
      </div>

      <SiteAdSlot
        class="right-sidebar__ad-slot py-5 pl-5 pr-0 max-lg:px-0"
        :code="sidebarAdCode"
        :location="isPostDetailRoute ? 'post-sidebar-right' : 'sidebar'"
      />
    </div>

    <footer class="right-sidebar__footer shrink-0 py-4 pl-5 pr-3 text-xs site-muted max-lg:px-0">
      {{ siteSettings.copyrightText }}
    </footer>
  </aside>
</template>

<style scoped>
.right-sidebar__custom-social-icon :deep(svg) {
  display: block;
  width: 1rem !important;
  height: 1rem !important;
  max-width: 1rem;
  max-height: 1rem;
  flex-shrink: 0;
  line-height: 1;
}

.right-sidebar__toc-item--active::before {
  content: "";
  position: absolute;
  left: -2px;
  top: 50%;
  width: 3px;
  height: 100%;
  background: var(--site-accent);
  transform: translateY(-50%);
}
</style>
