<script setup>
import { ref, computed, nextTick, watch, onMounted, onUnmounted } from "vue";
import { useNamespace } from "@yh-ui/hooks";
import { useComponentTheme } from "@yh-ui/theme";
import { useLocale } from "@yh-ui/hooks";
import { categoryNavProps, categoryNavEmits } from "./category-nav";
defineOptions({ name: "YhCategoryNav" });
const props = defineProps(categoryNavProps);
const emit = defineEmits(categoryNavEmits);
const ns = useNamespace("category-nav");
const { themeStyle } = useComponentTheme(
  "category-nav",
  computed(() => props.themeOverrides)
);
const { t } = useLocale();
const activeId = ref(props.modelValue);
const activeSubId = ref(props.subValue);
watch(
  () => props.modelValue,
  (v) => {
    activeId.value = v;
  }
);
watch(
  () => props.subValue,
  (v) => {
    activeSubId.value = v;
  }
);
const sideRef = ref(null);
const contentRef = ref(null);
const sectionRefs = ref({});
const activeCategory = computed(() => {
  if (activeId.value === null) return null;
  return props.categories.find((c) => c.id === activeId.value) ?? null;
});
let isScrollByClick = false;
let scrollTimer = null;
let rafId = null;
let observer = null;
function scrollSideToActive() {
  const sideEl = sideRef.value;
  if (!sideEl) return;
  nextTick(() => {
    const activeEl = sideEl.querySelector(`[role="tab"].${ns.is("active")}`);
    if (activeEl) {
      const { height: containerHeight } = sideEl.getBoundingClientRect();
      const { height: activeHeight, top: activeTop } = activeEl.getBoundingClientRect();
      const containerTop = sideEl.getBoundingClientRect().top;
      const relativeTop = activeTop - containerTop + sideEl.scrollTop;
      const targetTop = relativeTop - (containerHeight - activeHeight) / 2;
      sideEl.scrollTo({
        top: Math.max(0, targetTop),
        behavior: "smooth"
      });
    }
  });
}
async function selectCategory(item) {
  const id = item ? item.id : null;
  if (activeId.value === id) return;
  isScrollByClick = true;
  activeId.value = id;
  emit("update:modelValue", id);
  if (item) emit("categoryClick", item);
  if (props.anchor && contentRef.value) {
    await nextTick();
    const target = id !== null ? sectionRefs.value[id] : null;
    if (target) {
      contentRef.value.scrollTo({
        top: target.offsetTop,
        behavior: "smooth"
      });
    } else {
      contentRef.value.scrollTo({ top: 0, behavior: "smooth" });
    }
  }
  scrollSideToActive();
  if (scrollTimer) clearTimeout(scrollTimer);
  scrollTimer = setTimeout(() => {
    isScrollByClick = false;
  }, 1500);
}
function selectSub(subItem, parent) {
  activeSubId.value = subItem.id;
  emit("update:subValue", subItem.id);
  emit("subCategoryClick", subItem, parent);
}
const intersectingMap = /* @__PURE__ */ new Map();
function initObserver() {
  if (!props.anchor || !contentRef.value) return;
  const options = {
    root: contentRef.value,
    rootMargin: "-5% 0px -80% 0px",
    threshold: 0
  };
  observer = new IntersectionObserver((entries) => {
    if (isScrollByClick) return;
    entries.forEach((entry) => {
      const rawId = entry.target.getAttribute("data-id");
      if (rawId === null) return;
      const matchedCat = props.categories.find((c) => String(c.id) === rawId);
      if (!matchedCat) return;
      if (entry.isIntersecting) {
        intersectingMap.set(matchedCat.id, entry.target);
      } else {
        intersectingMap.delete(matchedCat.id);
      }
    });
    const candidates = Array.from(intersectingMap.entries()).map(([id, el]) => ({
      id,
      offsetTop: el.offsetTop
    }));
    if (candidates.length > 0) {
      const topmost = candidates.reduce(
        (prev, curr) => curr.offsetTop < prev.offsetTop ? curr : prev
      );
      if (activeId.value !== topmost.id) {
        activeId.value = topmost.id;
        emit("update:modelValue", topmost.id);
        scrollSideToActive();
      }
    }
  }, options);
  Object.values(sectionRefs.value).forEach((el) => observer?.observe(el));
}
function onContentScroll() {
  if (isScrollByClick || !props.anchor || !contentRef.value) return;
  if (rafId) cancelAnimationFrame(rafId);
  rafId = requestAnimationFrame(() => {
    const container = contentRef.value;
    if (!container) return;
    const scrollTop = container.scrollTop;
    if (scrollTop < 10 && props.showAll) {
      if (activeId.value !== null) {
        activeId.value = null;
        emit("update:modelValue", null);
        scrollSideToActive();
      }
    } else if (scrollTop + container.clientHeight >= container.scrollHeight - 2) {
      const last = props.categories[props.categories.length - 1];
      if (last && activeId.value !== last.id) {
        activeId.value = last.id;
        emit("update:modelValue", last.id);
        scrollSideToActive();
      }
    }
  });
}
function setSectionRef(el, id) {
  if (el) sectionRefs.value[id] = el;
}
onMounted(() => {
  if (activeId.value === null && props.categories.length > 0 && !props.showAll) {
    activeId.value = props.categories[0].id;
  }
  nextTick(() => {
    initObserver();
    scrollSideToActive();
  });
});
onUnmounted(() => {
  if (scrollTimer) clearTimeout(scrollTimer);
  if (rafId) cancelAnimationFrame(rafId);
  if (observer) observer.disconnect();
});
</script>

<template>
  <div
    :class="ns.b()"
    :style="[themeStyle, {
  '--yh-category-nav-side-width': sideWidth,
  '--yh-category-nav-sub-image-size': `${subImageSize}px`,
  '--yh-category-nav-columns': columns
}]"
  >
    <!-- 左侧一级菜单 -->
    <aside ref="sideRef" :class="ns.e('side')" role="tablist" :aria-label="t('categorynav.all')">
      <!-- 全部 -->
      <button
        v-if="showAll"
        :class="[ns.e('side-item'), ns.is('active', activeId === null)]"
        role="tab"
        :aria-selected="activeId === null"
        @click="selectCategory(null)"
      >
        <slot name="all-icon" />
        <span :class="ns.e('side-name')">{{ t('categorynav.all') }}</span>
      </button>

      <!-- 加载骨架 -->
      <template v-if="loading">
        <div v-for="i in 8" :key="i" :class="[ns.e('side-item'), ns.e('skeleton')]" />
      </template>

      <template v-else>
        <button
          v-for="cat in categories"
          :key="cat.id"
          :class="[ns.e('side-item'), ns.is('active', activeId === cat.id)]"
          role="tab"
          :aria-selected="activeId === cat.id"
          @click="selectCategory(cat)"
        >
          <span v-if="cat.badge" :class="ns.e('badge')">{{ cat.badge }}</span>
          <img
            v-if="cat.image"
            :class="ns.e('side-img')"
            :src="cat.image"
            :alt="cat.name"
            loading="lazy"
          />
          <i v-else-if="cat.icon" :class="[ns.e('side-icon'), cat.icon]" aria-hidden="true" />
          <span :class="ns.e('side-name')">{{ cat.name }}</span>
        </button>
      </template>
    </aside>

    <!-- 右侧内容区 -->
    <main ref="contentRef" :class="ns.e('content')" role="tabpanel" @scroll="onContentScroll">
      <slot name="header" />

      <!-- 加载骨架 -->
      <template v-if="loading">
        <div :class="ns.e('section')">
          <div :class="ns.e('sub-grid')">
            <div v-for="i in 9" :key="i" :class="[ns.e('sub-item'), ns.e('skeleton')]" />
          </div>
        </div>
      </template>

      <!-- 普通视图：仅显示当前激活的子分类 -->
      <template v-else-if="!anchor">
        <div v-if="activeCategory" :class="ns.e('section')">
          <slot name="section-header" :category="activeCategory" />
          <div :class="ns.e('sub-grid')">
            <button
              v-for="sub in activeCategory.children ?? []"
              :key="sub.id"
              :class="[ns.e('sub-item'), ns.is('active', activeSubId === sub.id)]"
              @click="selectSub(sub, activeCategory)"
            >
              <img
                v-if="sub.image"
                :class="ns.e('sub-img')"
                :src="sub.image"
                :alt="sub.name"
                loading="lazy"
              />
              <span :class="ns.e('sub-name')">{{ sub.name }}</span>
              <span v-if="sub.count" :class="ns.e('sub-count')">{{ sub.count }}</span>
            </button>
          </div>
        </div>
        <p v-else :class="ns.e('empty')">{{ t("categorynav.noData") }}</p>
      </template>

      <!-- 锚定模式：所有分类的子项全部渲染，滚动联动 -->
      <template v-else>
        <div
          v-for="cat in categories"
          :key="cat.id"
          :ref="el => setSectionRef(el, cat.id)"
          :class="ns.e('section')"
          :data-id="cat.id"
        >
          <div :class="ns.e('section-title')">
            <slot name="section-header" :category="cat">
              <span>{{ cat.name }}</span>
            </slot>
          </div>
          <div :class="ns.e('sub-grid')">
            <button
              v-for="sub in cat.children ?? []"
              :key="sub.id"
              :class="[ns.e('sub-item'), ns.is('active', activeSubId === sub.id)]"
              @click="selectSub(sub, cat)"
            >
              <slot name="sub-item" :sub="sub" :parent="cat">
                <img
                  v-if="sub.image"
                  :class="ns.e('sub-img')"
                  :src="sub.image"
                  :alt="sub.name"
                  loading="lazy"
                />
                <span :class="ns.e('sub-name')">{{ sub.name }}</span>
                <span v-if="sub.count" :class="ns.e('sub-count')">{{ sub.count }}</span>
              </slot>
            </button>
          </div>
          <slot name="section-footer" :category="cat" />
        </div>
      </template>

      <slot name="footer" />
    </main>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-category-nav {
  --yh-category-nav-side-width: 84px;
  --yh-category-nav-side-bg: var(--yh-fill-color-lighter);
  --yh-category-nav-side-active-bg: var(--yh-bg-color);
  --yh-category-nav-side-active-color: var(--yh-color-primary);
  --yh-category-nav-side-active-indicator: 3px solid var(--yh-color-primary);
  --yh-category-nav-content-bg: var(--yh-bg-color);
  --yh-category-nav-sub-image-size: 64px;
  --yh-category-nav-columns: 3;
  --yh-category-nav-section-title-color: var(--yh-text-color-secondary);
  --yh-category-nav-sub-active-color: var(--yh-color-primary);
  --yh-category-nav-height: 100%;
  display: flex;
  height: var(--yh-category-nav-height);
  overflow: hidden;
  background: var(--yh-category-nav-content-bg);
}
.yh-category-nav__side {
  position: relative;
  width: var(--yh-category-nav-side-width);
  flex-shrink: 0;
  background: var(--yh-category-nav-side-bg);
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  border-right: 1px solid var(--yh-border-color-lighter);
}
.yh-category-nav__side::-webkit-scrollbar {
  display: none;
}

.yh-category-nav__side-item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 56px;
  padding: 8px 6px;
  gap: 4px;
  border: none;
  background: transparent;
  font-size: var(--yh-font-size-xs);
  color: var(--yh-text-color-regular);
  cursor: pointer;
  text-align: center;
  transition: background var(--yh-transition-base), color var(--yh-transition-base);
}
.yh-category-nav__side-item.is-active {
  background: var(--yh-category-nav-side-active-bg);
  color: var(--yh-category-nav-side-active-color);
  font-weight: 600;
}
.yh-category-nav__side-item.is-active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 20%;
  height: 60%;
  width: 3px;
  border-radius: 0 2px 2px 0;
  background: var(--yh-category-nav-side-active-color, var(--yh-color-primary));
}

.yh-category-nav__badge {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--yh-color-danger);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
  text-align: center;
}

.yh-category-nav__side-img {
  width: 32px;
  height: 32px;
  object-fit: cover;
  border-radius: var(--yh-radius-base);
}

.yh-category-nav__side-icon {
  font-size: 22px;
  color: inherit;
}

.yh-category-nav__side-name {
  font-size: var(--yh-font-size-xs);
  line-height: 1.3;
  word-break: break-all;
}

.yh-category-nav__content {
  flex: 1;
  overflow-y: auto;
  background: var(--yh-category-nav-content-bg);
  scrollbar-width: thin;
  scrollbar-color: var(--yh-border-color-lighter) transparent;
}
.yh-category-nav__content::-webkit-scrollbar {
  width: 4px;
}
.yh-category-nav__content::-webkit-scrollbar-thumb {
  background: var(--yh-border-color-lighter);
  border-radius: 2px;
}

.yh-category-nav__section {
  padding: 10px 12px 0;
}

.yh-category-nav__section-title {
  padding: 4px 0 8px;
  font-size: var(--yh-font-size-sm);
  font-weight: 600;
  color: var(--yh-category-nav-section-title-color);
  display: flex;
  align-items: center;
  gap: 6px;
}
.yh-category-nav__section-title::before {
  content: "";
  width: 3px;
  height: 14px;
  border-radius: 2px;
  background: var(--yh-category-nav-side-active-color, var(--yh-color-primary));
}

.yh-category-nav__sub-grid {
  display: grid;
  grid-template-columns: repeat(var(--yh-category-nav-columns), 1fr);
  gap: 8px;
  padding-bottom: 10px;
}

.yh-category-nav__sub-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 8px 4px;
  border: 1px solid transparent;
  border-radius: var(--yh-radius-base);
  background: transparent;
  cursor: pointer;
  transition: background var(--yh-transition-base), border-color var(--yh-transition-base);
}
.yh-category-nav__sub-item:hover {
  background: var(--yh-fill-color-light);
}
.yh-category-nav__sub-item.is-active {
  border-color: var(--yh-category-nav-sub-active-color);
  background: var(--yh-color-primary-light-9);
}

.yh-category-nav__sub-img {
  width: var(--yh-category-nav-sub-image-size);
  height: var(--yh-category-nav-sub-image-size);
  object-fit: cover;
  border-radius: var(--yh-radius-base);
  background: var(--yh-fill-color-light);
}

.yh-category-nav__sub-name {
  font-size: var(--yh-font-size-xs);
  color: var(--yh-text-color-regular);
  text-align: center;
  line-height: 1.3;
  word-break: break-all;
}

.yh-category-nav__sub-count {
  font-size: 10px;
  color: var(--yh-text-color-disabled);
}

.yh-category-nav__empty {
  text-align: center;
  color: var(--yh-text-color-placeholder);
  font-size: var(--yh-font-size-sm);
  padding: 40px 0;
}

.yh-category-nav__skeleton {
  background: linear-gradient(90deg, var(--yh-fill-color-lighter) 25%, var(--yh-fill-color-light) 50%, var(--yh-fill-color-lighter) 75%);
  background-size: 200% 100%;
  animation: yh-category-nav-shimmer 1.5s infinite;
  border-radius: var(--yh-radius-base);
  border: none !important;
}
.yh-category-nav__skeleton.yh-category-nav__side-item {
  height: 48px;
  width: calc(100% - 16px);
  margin: 8px auto;
}
.yh-category-nav__skeleton.yh-category-nav__sub-item {
  height: 80px;
  width: 100%;
}

@keyframes yh-category-nav-shimmer {
  to {
    background-position: -200% 0;
  }
}
</style>
