<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue';
import Spin from '../Spin/Spin.vue';
import { useResizeObserver } from '../Utils';
/*
  宽度固定，图片等比例缩放；使用JS获取每张图片宽度和高度，结合 `relative` 和 `absolute` 定位
  计算每个图片的位置 `top`，`left`，保证每张新的图片都追加在当前高度最小的那列末尾
*/
interface Image {
  name?: string // 图片名称
  src: string // 图片地址
}
interface Props {
  images?: Image[] // 图片数组
  columnCount?: number // 要划分的列数
  columnGap?: number // 各列之间的间隙，单位 px
  width?: string | number // 瀑布流区域的总宽度，单位 px
  borderRadius?: number // 瀑布流区域和图片圆角，单位 px
  backgroundColor?: string // 瀑布流区域背景填充色
  spinProps?: object // Spin 组件属性配置，参考 Spin Props，用于配置图片加载中样式
}
const props = withDefaults(defineProps<Props>(), {
  images: () => [],
  columnCount: 3,
  columnGap: 20,
  width: '100%',
  borderRadius: 8,
  backgroundColor: '#F2F4F8',
  spinProps: () => ({})
});
const waterfallRef = ref();
const waterfallWidth = ref<number>();
const loaded = ref(Array(props.images.length).fill(false)); // 图片是否加载完成
const imageWidth = ref<number>();
const imagesProperty = ref<{ width: number; height: number; top: number; left: number }[]>([]);
const preColumnHeight = ref<number[]>(Array(props.columnCount).fill(0)); // 每列的高度
const flag = ref(0);
const totalWidth = computed(() => {
  if (typeof props.width === 'number') {
    return `${props.width}px`;
  } else {
    return props.width;
  }
});
const height = computed(() => {
  return Math.max(...preColumnHeight.value) + props.columnGap;
});
const len = computed(() => {
  return props.images.length;
});
watch(
    () => [props.images, props.columnCount, props.columnGap, props.width],
    () => {
      waterfallWidth.value = waterfallRef.value.offsetWidth;
      preColumnHeight.value = Array(props.columnCount).fill(0);
      flag.value++;
      preloadImages(flag.value);
    },
    {
      deep: true, // 强制转成深层侦听器
      flush: 'post' // 在侦听器回调中访问被 Vue 更新之后的 DOM
    }
);
onMounted(() => {
  waterfallWidth.value = waterfallRef.value.offsetWidth;
  preloadImages(flag.value);
});
function updateWatefall() {
  const currentWidth = waterfallRef.value.offsetWidth;
  // 窗口宽度改变时重新计算瀑布流布局
  if (props.images.length && currentWidth !== waterfallWidth.value) {
    waterfallWidth.value = currentWidth;
    flag.value++;
    preloadImages(flag.value);
  }
}
useResizeObserver(waterfallRef, updateWatefall);
async function preloadImages(symbol: number) {
  // 计算图片宽高和位置（top，left）
  // 计算每列的图片宽度
  imageWidth.value = ((waterfallWidth.value as number) - (props.columnCount + 1) * props.columnGap) / props.columnCount;
  imagesProperty.value.splice(0);
  for (let i = 0; i < len.value; i++) {
    if (symbol === flag.value) {
      await loadImage(props.images[i].src, i);
    } else {
      return false;
    }
  }
}
function loadImage(url: string, n: number) {
  return new Promise((resolve) => {
    const image = new Image();
    image.src = url;
    image.onload = function () {
      // 图片加载完成时执行，此时可通过image.width和image.height获取到图片原始宽高
      const height = image.height / (image.width / (imageWidth.value as number));
      imagesProperty.value[n] = {
        // 存储图片宽高和位置信息
        width: imageWidth.value as number,
        height: height,
        ...getPosition(n, height)
      };
      resolve('load');
    };
  });
}
function getPosition(i: number, height: number) {
  // 获取图片位置信息（top，left）
  if (i < props.columnCount) {
    preColumnHeight.value[i] = props.columnGap + height;
    return {
      top: props.columnGap,
      left: ((imageWidth.value as number) + props.columnGap) * i + props.columnGap
    };
  } else {
    const top = Math.min(...preColumnHeight.value);
    let index = 0;
    for (let n = 0; n < props.columnCount; n++) {
      if (preColumnHeight.value[n] === top) {
        index = n;
        break;
      }
    }
    preColumnHeight.value[index] = top + props.columnGap + height;
    return {
      top: top + props.columnGap,
      left: ((imageWidth.value as number) + props.columnGap) * index + props.columnGap
    };
  }
}
function onLoaded(index: number) {
  loaded.value[index] = true;
}
function getImageName(image: Image) {
  // 从图像地址src中获取图像名称
  if (image) {
    if (image.name) {
      return image.name;
    } else {
      const res = image.src.split('?')[0].split('/');
      return res[res.length - 1];
    }
  }
}
</script>
<template>
  <div
      ref="waterfallRef"
      class="m-waterfall"
      :style="`--border-radius: ${borderRadius}px; background-color: ${backgroundColor}; width: ${totalWidth}; height: ${height}px;`"
  >
    <Spin
        class="waterfall-image"
        :style="`width: ${property.width}px; height: ${property.height}px; top: ${property && property.top}px; left: ${property && property.left}px;`"
        :spinning="!loaded[index]"
        size="small"
        indicator="dynamic-circle"
        v-bind="spinProps"
        v-for="(property, index) in imagesProperty"
        :key="index"
    >
      <img class="u-image" :src="images[index].src" :alt="getImageName(images[index])" @load="onLoaded(index)" />
    </Spin>
  </div>
</template>
<style lang="less" scoped>
.m-waterfall {
  position: relative;
  border-radius: var(--border-radius);
  .waterfall-image {
    position: absolute;
    .u-image {
      width: 100%;
      height: 100%;
      border-radius: var(--border-radius);
      display: inline-block;
      vertical-align: bottom;
    }
  }
}
</style>
