<script setup>
import { computed, ref, onMounted, onUnmounted, watch } from "vue";
import { useNamespace, useLocale } from "@yh-ui/hooks";
import { useComponentTheme } from "@yh-ui/theme";
import { isClient, getScrollContainer } from "@yh-ui/utils";
import { imageProps } from "./image";
import YhImageViewer from "./image-viewer.vue";
import { loadViewer } from "../../viewerjs";
defineOptions({
  name: "YhImage"
});
const props = defineProps(imageProps);
const emit = defineEmits(["load", "error", "switch", "close", "show"]);
const ns = useNamespace("image");
const { t } = useLocale();
const { themeStyle } = useComponentTheme(
  "image",
  computed(() => props.themeOverrides)
);
const isLoading = ref(true);
const error = ref(false);
const showViewer = ref(false);
const container = ref();
const isLazy = ref(false);
const useNativeLazy = ref(false);
let viewer = null;
let viewerList = null;
const isSupportNativeLazy = isClient && "loading" in HTMLImageElement.prototype;
const onImgLoad = (e) => {
  isLoading.value = false;
  emit("load", e);
};
const onImgError = (e) => {
  isLoading.value = false;
  error.value = true;
  emit("error", e);
};
const imageStyle = computed(() => {
  const { fit } = props;
  if (fit) {
    return { "object-fit": fit };
  }
  return {};
});
const preview = computed(() => {
  const { previewSrcList } = props;
  return Array.isArray(previewSrcList) && previewSrcList.length > 0;
});
const loadImage = () => {
  if (!isClient) return;
  isLoading.value = true;
  error.value = false;
  const img = new Image();
  img.src = props.src;
  if (props.crossorigin) img.crossOrigin = props.crossorigin;
  img.onload = (e) => {
    isLoading.value = false;
    emit("load", e);
  };
  img.onerror = (e) => {
    isLoading.value = false;
    error.value = true;
    emit("error", e);
  };
};
let observer = null;
const handleLazyLoad = () => {
  if (!isClient) return;
  if (props.lazy) {
    if (isSupportNativeLazy && props.loading === "lazy") {
      useNativeLazy.value = true;
      isLoading.value = true;
      error.value = false;
    } else {
      isLazy.value = true;
      initLazyLoad();
    }
  } else {
    loadImage();
  }
};
const initLazyLoad = () => {
  if (!isClient || !container.value) return;
  if (!isLoading.value && !error.value) return;
  observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          loadImage();
          stopLazyLoad();
        }
      });
    },
    {
      root: getRoot(),
      rootMargin: "200px"
      // 提前 200px 开始加载
    }
  );
  observer.observe(container.value);
};
const getRoot = () => {
  if (!isClient || !container.value) return null;
  if (typeof props.scrollContainer === "string") {
    if (typeof document === "undefined") return null;
    return document.querySelector(props.scrollContainer);
  } else if (typeof HTMLElement !== "undefined" && props.scrollContainer instanceof HTMLElement) {
    return props.scrollContainer;
  }
  const scrollContainer = getScrollContainer(container.value);
  return scrollContainer instanceof HTMLElement ? scrollContainer : null;
};
const stopLazyLoad = () => {
  if (observer) {
    observer.disconnect();
    observer = null;
  }
};
const initViewerJS = async () => {
  if (!isClient || !container.value) return;
  const imgElement = container.value.querySelector("img");
  if (!imgElement) return;
  let ViewerClass;
  try {
    ViewerClass = await loadViewer();
  } catch {
    return;
  }
  if (props.previewSrcList && props.previewSrcList.length > 0) {
    const list = document.createElement("div");
    list.style.display = "none";
    props.previewSrcList.forEach((src) => {
      const img = document.createElement("img");
      img.src = src;
      list.appendChild(img);
    });
    document.body.appendChild(list);
    viewerList = list;
    const nextViewer = new ViewerClass(list, {
      ...props.viewerOptions,
      hidden: () => {
        if (viewerList) {
          document.body.removeChild(viewerList);
          viewerList = null;
        }
        viewer?.destroy();
        viewer = null;
      }
    });
    viewer = nextViewer;
    nextViewer.view(props.initialIndex);
  } else {
    const nextViewer = new ViewerClass(imgElement, props.viewerOptions);
    viewer = nextViewer;
    nextViewer.show();
  }
};
watch(
  () => props.src,
  () => {
    if (useNativeLazy.value) {
      isLoading.value = true;
      error.value = false;
    } else if (isLazy.value) {
      stopLazyLoad();
      initLazyLoad();
    } else {
      loadImage();
    }
  }
);
onMounted(() => {
  handleLazyLoad();
});
onUnmounted(() => {
  stopLazyLoad();
  if (viewer) {
    viewer.destroy();
    viewer = null;
  }
  if (viewerList) {
    document.body.removeChild(viewerList);
    viewerList = null;
  }
});
const clickHandler = async () => {
  if (!preview.value) return;
  if (props.viewerMode === "viewerjs") {
    await initViewerJS();
  } else {
    showViewer.value = true;
  }
  emit("show");
};
const closeViewer = () => {
  showViewer.value = false;
  emit("close");
};
const handleSwitch = (index) => {
  emit("switch", index);
};
</script>

<template>
  <div ref="container" :class="ns.b()" :style="themeStyle">
    <slot v-if="isLoading && !useNativeLazy" name="placeholder">
      <div :class="ns.e('placeholder')">{{ t('image.loading') }}</div>
    </slot>
    <slot v-else-if="error" name="error">
      <div :class="ns.e('error')">{{ t("image.error") }}</div>
    </slot>
    <template v-else-if="!useNativeLazy">
      <img
        :src="src"
        :alt="alt"
        :class="[ns.e('inner'), preview && ns.is('preview')]"
        :style="imageStyle"
        :crossorigin="crossorigin"
        :loading="props.loading"
        @click="clickHandler"
      />
    </template>

    <!-- Native lazy loading branch -->
    <template v-if="useNativeLazy">
      <slot v-if="isLoading" name="placeholder">
        <div :class="ns.e('placeholder')">{{ t('image.loading') }}</div>
      </slot>
      <img
        :src="src"
        :alt="alt"
        :class="[ns.e('inner'), preview && ns.is('preview')]"
        :style="[imageStyle, isLoading || error ? {
  width: '0px',
  height: '0px',
  opacity: 0,
  position: 'absolute'
} : {}]"
        :crossorigin="crossorigin"
        loading="lazy"
        @load="onImgLoad"
        @error="onImgError"
        @click="clickHandler"
      />
    </template>

    <!-- Viewer -->
    <yh-image-viewer
      v-if="preview && showViewer"
      :url-list="previewSrcList"
      :z-index="zIndex"
      :initial-index="initialIndex"
      :infinite="infinite"
      :hide-on-click-modal="hideOnClickModal"
      :close-on-press-escape="closeOnPressEscape"
      :show-progress="showProgress"
      :zoom-rate="zoomRate"
      :teleported="previewTeleported"
      @close="closeViewer"
      @switch="handleSwitch"
    />
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-image {
  position: relative;
  display: inline-block;
  overflow: hidden;
  vertical-align: middle;
}
.yh-image__inner {
  width: 100%;
  height: 100%;
  vertical-align: top;
}
.yh-image__inner.is-preview {
  cursor: pointer;
}

.yh-image__placeholder {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background-color: var(--yh-image-placeholder-bg-color);
  color: var(--yh-image-placeholder-text-color);
  font-size: 14px;
  vertical-align: middle;
}

.yh-image__error {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background-color: var(--yh-image-error-bg-color);
  color: var(--yh-image-error-text-color);
  font-size: 14px;
  vertical-align: middle;
}

.yh-viewer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  justify-content: center;
  align-items: center;
}
.yh-viewer__mask {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  background: var(--yh-image-viewer-mask-bg-color);
}

.yh-viewer__btn {
  position: absolute;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  opacity: 0.8;
  cursor: pointer;
  box-sizing: border-box;
  user-select: none;
  color: var(--yh-image-viewer-btn-color);
  background-color: var(--yh-image-viewer-btn-bg-color);
  border: none;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.yh-viewer__btn:hover {
  opacity: 1;
  transform: scale(1.1);
  background-color: var(--yh-image-viewer-btn-hover-bg-color);
}

.yh-viewer__close {
  top: 40px;
  right: 40px;
  width: 44px;
  height: 44px;
  font-size: 24px;
}

.yh-viewer__prev {
  top: 50%;
  left: 40px;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  font-size: 24px;
  background-color: var(--yh-image-viewer-btn-bg-color);
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.yh-viewer__next {
  top: 50%;
  right: 40px;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  font-size: 24px;
  background-color: var(--yh-image-viewer-btn-bg-color);
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.yh-viewer__actions {
  position: absolute;
  z-index: 10;
  left: 50%;
  bottom: 30px;
  transform: translateX(-50%);
  width: 282px;
  height: 44px;
  padding: 0 23px;
  background-color: var(--yh-image-viewer-btn-bg-color);
  backdrop-filter: blur(4px);
  border-radius: 22px;
  display: flex;
  align-items: center;
  justify-content: space-around;
}

.yh-viewer__actions-inner {
  width: 100%;
  display: flex;
  justify-content: space-around;
  align-items: center;
  color: var(--yh-image-viewer-btn-color);
  font-size: 20px;
}
.yh-viewer__actions-inner i {
  cursor: pointer;
  display: flex;
  transition: color 0.2s;
}
.yh-viewer__actions-inner i:hover {
  color: var(--yh-color-primary);
}

.yh-viewer__canvas {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1;
}

.yh-viewer__img {
  max-width: 90%;
  max-height: 90%;
  transition: transform 0.3s cubic-bezier(0.21, 0.6, 0.35, 1);
  user-select: none;
  -webkit-user-drag: none;
}
</style>
