<script setup>
import { ref, computed, watch, onMounted, onBeforeUnmount, shallowRef, nextTick } from "vue";
import { useNamespace, useLocale } from "@yh-ui/hooks";
import { useComponentTheme } from "@yh-ui/theme";
import { YhSpin } from "../../spin";
import {
  infiniteScrollProps,
  infiniteScrollEmits
} from "./infinite-scroll";
defineOptions({
  name: "YhInfiniteScroll"
});
const props = defineProps(infiniteScrollProps);
const emit = defineEmits(infiniteScrollEmits);
const ns = useNamespace("infinite-scroll");
const { t } = useLocale();
const { themeStyle } = useComponentTheme(
  "infinite-scroll",
  computed(() => props.themeOverrides)
);
const rootRef = ref();
const placeholderRef = ref();
const scrollContainer = shallowRef();
const isLoading = computed(() => props.loading);
let observer = null;
const isScrollableElement = (element) => {
  if (!element || typeof window === "undefined") return false;
  const style = window.getComputedStyle(element);
  return /(auto|scroll|overlay)/.test(`${style.overflow}${style.overflowX}${style.overflowY}`);
};
const resolveTargetElement = () => {
  if (!props.target || typeof document === "undefined") return null;
  return document.querySelector(props.target);
};
const resolveScrollContainer = () => {
  const explicitTarget = resolveTargetElement();
  if (explicitTarget) {
    return explicitTarget;
  }
  if (isScrollableElement(rootRef.value)) {
    return rootRef.value;
  }
  let current = rootRef.value?.parentElement ?? null;
  while (current && current !== document.body) {
    if (isScrollableElement(current)) {
      return current;
    }
    current = current.parentElement;
  }
  return window;
};
const getObserverRootMargin = () => {
  if (props.rootMargin) return props.rootMargin;
  return props.direction === "horizontal" ? `0px ${props.threshold}px 0px 0px` : `0px 0px ${props.threshold}px 0px`;
};
const checkLoad = () => {
  if (props.disabled || isLoading.value || props.finished || props.error) {
    return;
  }
  if (props.useObserver && observer) {
    return;
  }
  if (!scrollContainer.value || !placeholderRef.value) return;
  const container = scrollContainer.value;
  let shouldLoad = false;
  if (props.direction === "vertical") {
    if (container instanceof Window) {
      const { scrollHeight, clientHeight } = document.documentElement;
      const scrollTop = window.scrollY;
      shouldLoad = scrollHeight - scrollTop - clientHeight <= props.threshold;
    } else {
      shouldLoad = container.scrollHeight - container.scrollTop - container.clientHeight <= props.threshold;
    }
  } else {
    if (container instanceof Window) {
      const { scrollWidth, clientWidth } = document.documentElement;
      const scrollLeft = window.scrollX;
      shouldLoad = scrollWidth - scrollLeft - clientWidth <= props.threshold;
    } else {
      shouldLoad = container.scrollWidth - container.scrollLeft - container.clientWidth <= props.threshold;
    }
  }
  if (shouldLoad) {
    emit("load");
  }
};
let rafId = null;
const handleScroll = () => {
  if (rafId !== null) return;
  rafId = requestAnimationFrame(() => {
    rafId = null;
    checkLoad();
  });
};
const handleRetry = () => {
  emit("update:error", false);
  emit("load");
};
const setupObserver = () => {
  if (!props.useObserver || typeof IntersectionObserver === "undefined") {
    return;
  }
  if (observer) {
    observer.disconnect();
    observer = null;
  }
  if (!placeholderRef.value) return;
  const rootContainer = resolveScrollContainer();
  observer = new IntersectionObserver(
    (entries) => {
      const entry = entries[0];
      if (entry.isIntersecting && !props.disabled && !isLoading.value && !props.finished && !props.error) {
        emit("load");
      }
    },
    {
      root: rootContainer instanceof Window ? null : rootContainer,
      rootMargin: getObserverRootMargin()
    }
  );
  if (placeholderRef.value) {
    observer.observe(placeholderRef.value);
  }
};
const setupScrollListener = () => {
  if (props.useObserver) return;
  scrollContainer.value = resolveScrollContainer();
  if (scrollContainer.value) {
    scrollContainer.value.addEventListener("scroll", handleScroll, { passive: true });
  }
};
onMounted(() => {
  nextTick(() => {
    if (props.useObserver) {
      setupObserver();
    } else {
      setupScrollListener();
    }
    if (props.immediateCheck) {
      checkLoad();
    }
  });
});
onBeforeUnmount(() => {
  if (rafId !== null) {
    cancelAnimationFrame(rafId);
  }
  if (observer) {
    observer.disconnect();
    observer = null;
  }
  if (scrollContainer.value && !props.useObserver) {
    scrollContainer.value.removeEventListener("scroll", handleScroll);
  }
});
watch(
  () => props.loading,
  (val, oldVal) => {
    if (oldVal && !val && props.immediateCheck) {
      nextTick(() => {
        if (props.useObserver && observer && placeholderRef.value) {
          observer.unobserve(placeholderRef.value);
          observer.observe(placeholderRef.value);
        } else {
          checkLoad();
        }
      });
    }
  }
);
watch(
  () => props.useObserver,
  async () => {
    await nextTick();
    if (props.useObserver) {
      if (scrollContainer.value) {
        scrollContainer.value.removeEventListener("scroll", handleScroll);
      }
      setupObserver();
    } else {
      if (observer) {
        observer.disconnect();
        observer = null;
      }
      setupScrollListener();
    }
  }
);
defineExpose({
  /** 手动检查是否需要加载 */
  check: checkLoad,
  /** 重试加载 */
  retry: handleRetry
});
</script>

<template>
  <div ref="rootRef" :class="[ns.b(), ns.is(direction)]" :style="themeStyle">
    <!-- 内容区域 -->
    <div :class="ns.e('content')">
      <slot />
    </div>

    <!-- 状态占位区域 -->
    <div ref="placeholderRef" :class="ns.e('placeholder')">
      <!-- 加载中 -->
      <div v-if="loading" :class="ns.e('loading')">
        <slot name="loading">
          <YhSpin size="small" />
          <span :class="ns.e('text')">{{ loadingText || t("infinitescroll.loading") }}</span>
        </slot>
      </div>

      <!-- 加载完成 -->
      <div v-else-if="finished" :class="ns.e('finished')">
        <slot name="finished">
          <span :class="ns.e('text')">{{ finishedText || t("infinitescroll.finished") }}</span>
        </slot>
      </div>

      <!-- 加载失败 -->
      <div v-else-if="error" :class="ns.e('error')" @click="handleRetry">
        <slot name="error">
          <span :class="ns.e('text')">{{ errorText || t("infinitescroll.error") }}</span>
        </slot>
      </div>
    </div>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-infinite-scroll {
  display: flex;
  flex-direction: column;
}
.yh-infinite-scroll.is-horizontal {
  flex-direction: row;
}

.yh-infinite-scroll__content {
  flex: 1;
  min-width: 0;
  min-height: 0;
}

.yh-infinite-scroll__placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 16px;
}

.yh-infinite-scroll__loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: var(--yh-text-color-secondary, #86868b);
}

.yh-infinite-scroll__finished {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--yh-text-color-placeholder, #c0c4cc);
  font-size: 13px;
}

.yh-infinite-scroll__error {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--yh-color-danger, #f56c6c);
  font-size: 13px;
  cursor: pointer;
  transition: opacity 0.2s ease;
}
.yh-infinite-scroll__error:hover {
  opacity: 0.7;
}

.yh-infinite-scroll__text {
  display: inline-block;
  line-height: 1.5;
}

.yh-infinite-scroll.is-horizontal .yh-infinite-scroll__placeholder {
  min-height: auto;
  min-width: 50px;
  padding: 16px 8px;
  flex-direction: column;
}
.yh-infinite-scroll.is-horizontal .yh-infinite-scroll__loading {
  flex-direction: column;
}

:root.dark .yh-infinite-scroll .yh-infinite-scroll__loading, html.dark .yh-infinite-scroll .yh-infinite-scroll__loading, body.dark .yh-infinite-scroll .yh-infinite-scroll__loading {
  color: var(--yh-text-color-secondary, #909399);
}

:root.dark .yh-infinite-scroll .yh-infinite-scroll__finished, html.dark .yh-infinite-scroll .yh-infinite-scroll__finished, body.dark .yh-infinite-scroll .yh-infinite-scroll__finished {
  color: var(--yh-text-color-placeholder, #6b6b6b);
}
</style>
