<template>
  <div
    class="mark-body-container tw-flex-auto tw-p-2 tw-relative"
    ref="dragContainer"
  >
    <div v-if="!store.currentTask" class="tw-text-center">
      {{ store.message }}
    </div>
    <div
      v-else-if="store.setting.examType === 'SCAN_IMAGE'"
      :style="{ width: answerPaperScale }"
    >
      <div
        v-for="(item, index) in sliceImagesWithTrackList"
        :key="index"
        class="single-image-container"
      >
        <img
          :src="item.url"
          @click="(event) => innerMakeTrack(event, item)"
          draggable="false"
          @contextmenu="showBigImage"
        />
        <MarkDrawTrack
          :track-list="item.trackList"
          :special-tag-list="item.tagList"
          :slice-image="item.sliceImage"
          :dx="item.dx"
          :dy="item.dy"
        />
        <hr class="image-seperator" />
      </div>
    </div>
    <div v-else-if="store.setting.examType === 'MULTI_MEDIA'">
      <MultiMediaMarkBody />
    </div>
    <div v-else>impossible</div>
    <div v-if="markStatus" class="status-container">
      {{ markStatus }}
      <div class="double-triangle"></div>
    </div>
    <ZoomPaper v-if="isScanImage()" :store="store" />
  </div>
  <slot name="slot-cursor" />
</template>

<script setup lang="ts">
import {
  computed,
  onMounted,
  onUnmounted,
  reactive,
  ref,
  watch,
  watchEffect,
} from "vue";
import { getMarkStatus, isScanImage } from "./store";
import MarkDrawTrack from "./MarkDrawTrack.vue";
import type {
  MarkResult,
  MarkStore,
  SliceImage,
  SpecialTag,
  Track,
} from "@/types";
import { useTimers } from "@/setups/useTimers";
import {
  getDataUrlForSliceConfig,
  getDataUrlForSplitConfig,
  loadImage,
} from "@/utils/utils";
import { dragImage } from "./use/draggable";
import MultiMediaMarkBody from "./MultiMediaMarkBody.vue";
import "viewerjs/dist/viewer.css";
import Viewer from "viewerjs";
import ZoomPaper from "@/components/ZoomPaper.vue";

const props = defineProps<{
  useMarkResult?: boolean;
  makeTrack: Function;
  store: MarkStore; // 实际上不是同一个store!!! 最新的统一成一个相同的store了
  uniquePropName: string; // TODO: 这个字段不需要了，是以前的rendering字段附带要求的
}>();

const emit = defineEmits(["error"]);

const {
  useMarkResult = false,
  makeTrack,
  store,
  uniquePropName = "libraryId",
} = props;

// start: 图片拖动。在轨迹模式下，仅当没有选择分数时可用。
const { dragContainer } = dragImage();
// end: 图片拖动

const { addTimeout } = useTimers();

// start: 缩略图定位
watch(
  () => store.minimapScrollTo,
  () => {
    const container = document.querySelector(
      ".mark-body-container"
    ) as HTMLDivElement;
    addTimeout(() => {
      if (container) {
        const { scrollHeight } = container;
        container.scrollTo({
          top: scrollHeight * store.minimapScrollTo,
        });
      }
    }, 10);
  }
);
// end: 缩略图定位

// start: 快捷键定位
const scrollContainerByKey = (e: KeyboardEvent) => {
  const container = document.querySelector(
    ".mark-body-container"
  ) as HTMLDivElement;
  if (e.key === "w") {
    container.scrollBy({ top: -100, behavior: "smooth" });
  } else if (e.key === "s") {
    container.scrollBy({ top: 100, behavior: "smooth" });
  } else if (e.key === "a") {
    container.scrollBy({ left: -100, behavior: "smooth" });
  } else if (e.key === "d") {
    container.scrollBy({ left: 100, behavior: "smooth" });
  }
};
onMounted(() => {
  document.addEventListener("keypress", scrollContainerByKey);
});
onUnmounted(() => {
  document.removeEventListener("keypress", scrollContainerByKey);
});
// end: 快捷键定位

// start: 计算裁切图和裁切图上的分数轨迹和特殊标记轨迹
let sliceImagesWithTrackList: Array<SliceImage> = reactive([]);
let maxSliceWidth = 0; // 最大的裁切块宽度，图片容器以此为准
let theFinalHeight = 0; // 最终宽度，用来定位轨迹在第几张图片，不包括image-seperator高度

async function getImageUsingDataUrl(
  dataUrl: string
): Promise<HTMLImageElement> {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.src = dataUrl;
    image.onload = function () {
      resolve(image);
    };
    image.onerror = reject;
  });
}

async function processSliceConfig() {
  let markResult = store.currentMarkResult as MarkResult;
  if (useMarkResult) {
    // check if have MarkResult for currentTask
    if (!markResult) return;
  }

  if (!store.currentTask) return;

  const images = [];
  // 必须要先加载一遍，把“选择整图”的宽高重置后，再算总高度
  for (const sliceConfig of store.currentTask.sliceConfig) {
    const url = store.currentTask.sliceUrls[sliceConfig.i - 1];
    const image = await loadImage(url);
    images[sliceConfig.i] = image;
    if (sliceConfig.w === 0 && sliceConfig.h === 0) {
      // 选择整图时，w/h 为0
      sliceConfig.w = image.naturalWidth;
      sliceConfig.h = image.naturalHeight;
    }
  }

  theFinalHeight = store.currentTask.sliceConfig
    .map((v) => v.h)
    .reduce((acc, v) => (acc += v));
  maxSliceWidth = Math.max(...store.currentTask.sliceConfig.map((v) => v.w));

  // 用来保存sliceImage在整个图片容器中(不包括image-seperator)的高度范围
  let accumTopHeight = 0;
  let accumBottomHeight = 0;
  const tempSliceImagesWithTrackList = [] as Array<SliceImage>;
  for (const sliceConfig of store.currentTask.sliceConfig) {
    accumBottomHeight += sliceConfig.h;
    const url = store.currentTask.sliceUrls[sliceConfig.i - 1];
    const indexInSliceUrls = sliceConfig.i;
    const image = images[sliceConfig.i];

    const dataUrl = (await getDataUrlForSliceConfig(
      image,
      sliceConfig,
      maxSliceWidth,
      url
    )) as string;

    let trackLists = [] as Array<Track>;
    if (useMarkResult) {
      trackLists = markResult.trackList;
    } else {
      trackLists = store.currentTask.questionList
        .map((q) => q.trackList)
        .reduce((acc, t) => {
          acc = acc.concat(t);
          return acc;
        }, [] as Array<Track>);
    }
    const thisImageTrackList = trackLists.filter(
      (t) => t.offsetIndex === indexInSliceUrls
    );

    let tagLists = [] as Array<SpecialTag>;
    if (useMarkResult) {
      tagLists = markResult.specialTagList ?? [];
    } else {
      tagLists = store.currentTask.specialTagList ?? [];
    }
    const thisImageTagList = tagLists.filter(
      (t) => t.offsetIndex === indexInSliceUrls
    );

    const sliceImage = await getImageUsingDataUrl(dataUrl);
    tempSliceImagesWithTrackList.push({
      url: dataUrl,
      indexInSliceUrls: sliceConfig.i,
      // 通过positionY来定位是第几张slice的还原，并过滤出相应的track
      trackList: thisImageTrackList.filter(
        (t) =>
          t.positionY >= accumTopHeight / theFinalHeight &&
          t.positionY < accumBottomHeight / theFinalHeight
      ),
      tagList: thisImageTagList.filter(
        (t) =>
          t.positionY >= accumTopHeight / theFinalHeight &&
          t.positionY < accumBottomHeight / theFinalHeight
      ),
      originalImage: image,
      sliceImage,
      dx: sliceConfig.x,
      dy: sliceConfig.y,
      accumTopHeight,
      effectiveWidth: sliceConfig.w,
    });
    accumTopHeight = accumBottomHeight;
  }
  sliceImagesWithTrackList.push(...tempSliceImagesWithTrackList);
}

async function processSplitConfig() {
  let markResult = store.currentMarkResult as MarkResult;
  if (useMarkResult) {
    // check if have MarkResult for currentTask
    if (!markResult) return;
  }

  if (!store.currentTask) return;

  const images = [];
  for (const url of store.currentTask.sliceUrls) {
    const image = await loadImage(url);
    images.push(image);
  }

  const splitConfigPairs = store.setting.splitConfig
    .map((v, index, ary) => (index % 2 === 0 ? [v, ary[index + 1]] : false))
    .filter((v) => v) as unknown as Array<[number, number]>;

  const maxSplitConfig = Math.max(...store.setting.splitConfig);
  maxSliceWidth =
    Math.max(...images.map((v) => v.naturalWidth)) * maxSplitConfig;

  theFinalHeight =
    splitConfigPairs.length *
    images.reduce((acc, v) => (acc += v.naturalHeight), 0);

  let accumTopHeight = 0;
  let accumBottomHeight = 0;
  const tempSliceImagesWithTrackList = [] as Array<SliceImage>;
  for (const url of store.currentTask.sliceUrls) {
    for (const config of splitConfigPairs) {
      const indexInSliceUrls = store.currentTask.sliceUrls.indexOf(url) + 1;
      const image = images[indexInSliceUrls - 1];

      accumBottomHeight += image.naturalHeight;

      const dataUrl = (await getDataUrlForSplitConfig(
        image,
        config,
        maxSliceWidth,
        url
      )) as string;

      let trackLists = [] as Array<Track>;
      if (useMarkResult) {
        trackLists = markResult.trackList;
      } else {
        trackLists = store.currentTask.questionList
          .map((q) => q.trackList)
          .reduce((acc, t) => {
            acc = acc.concat(t);
            return acc;
          }, [] as Array<Track>);
      }
      const thisImageTrackList = trackLists.filter(
        (t) => t.offsetIndex === indexInSliceUrls
      );

      let tagLists = [] as Array<SpecialTag>;
      if (useMarkResult) {
        tagLists = markResult.specialTagList ?? [];
      } else {
        tagLists = store.currentTask.specialTagList ?? [];
      }
      const thisImageTagList = tagLists.filter(
        (t) => t.offsetIndex === indexInSliceUrls
      );
      const sliceImage = await getImageUsingDataUrl(dataUrl);
      tempSliceImagesWithTrackList.push({
        url: dataUrl,
        indexInSliceUrls: store.currentTask.sliceUrls.indexOf(url) + 1,
        trackList: thisImageTrackList.filter(
          (t) =>
            t.positionY >= accumTopHeight / theFinalHeight &&
            t.positionY < accumBottomHeight / theFinalHeight
        ),
        tagList: thisImageTagList.filter(
          (t) =>
            t.positionY >= accumTopHeight / theFinalHeight &&
            t.positionY < accumBottomHeight / theFinalHeight
        ),
        originalImage: image,
        sliceImage,
        dx: image.naturalWidth * config[0],
        dy: 0,
        accumTopHeight,
        effectiveWidth: image.naturalWidth * config[1],
      });
      accumTopHeight = accumBottomHeight;
    }
  }
  sliceImagesWithTrackList.push(...tempSliceImagesWithTrackList);
}

// should not render twice at the same time
let renderLock = false;
const renderPaperAndMark = async () => {
  if (!store) return;
  if (!isScanImage()) return;
  if (renderLock) {
    console.log("上个任务还未渲染完毕，稍等一秒再尝试渲染");
    await new Promise((res) => setTimeout(res, 1000));
    await renderPaperAndMark();
    return;
  }
  renderLock = true;
  // for (const s of sliceImagesWithTrackList) {
  //   // console.log("revoke", s.url);
  //   URL.revokeObjectURL(s.url);
  // }
  sliceImagesWithTrackList.splice(0);
  // check if have MarkResult for currentTask
  let markResult = store.currentMarkResult;

  if ((useMarkResult && !markResult) || !store.currentTask) {
    renderLock = false;
    return;
  }

  try {
    store.globalMask = true;

    const hasSliceConfig = store.currentTask?.sliceConfig?.length;

    if (hasSliceConfig) {
      await processSliceConfig();
    } else {
      await processSplitConfig();
    }
  } catch (error) {
    sliceImagesWithTrackList.splice(0);
    console.log("render error ", error);
    // 图片加载出错，自动加载下一个任务
    emit("error");
  } finally {
    renderLock = false;
    store.globalMask = false;
  }
};

watchEffect(renderPaperAndMark);
// end: 计算裁切图和裁切图上的分数轨迹和特殊标记轨迹

// start: 放大缩小和之后的滚动
const answerPaperScale = computed(() => {
  // 放大、缩小不影响页面之前的滚动条定位
  let percentWidth = 0;
  let percentTop = 0;
  const container = document.querySelector(
    ".mark-body-container"
  ) as HTMLDivElement;
  if (container) {
    const { scrollLeft, scrollTop, scrollWidth, scrollHeight } = container;
    percentWidth = scrollLeft / scrollWidth;
    percentTop = scrollTop / scrollHeight;
  }

  addTimeout(() => {
    if (container) {
      const { scrollWidth, scrollHeight } = container;
      container.scrollTo({
        left: scrollWidth * percentWidth,
        top: scrollHeight * percentTop,
      });
    }
  }, 10);
  const scale = store.setting.uiSetting["answer.paper.scale"];
  return scale * 100 + "%";
});
// end: 放大缩小和之后的滚动

// start: 显示评分状态和清除轨迹
const markStatus = ref("");
if (useMarkResult) {
  watch(
    () => store.currentTask,
    () => {
      markStatus.value = getMarkStatus();
    }
  );

  // 清除分数轨迹
  watchEffect(() => {
    for (const track of store.removeScoreTracks) {
      for (const sliceImage of sliceImagesWithTrackList) {
        sliceImage.trackList = sliceImage.trackList.filter(
          (t) =>
            !(
              t.mainNumber === track.mainNumber &&
              t.subNumber === track.subNumber &&
              t.number === track.number
            )
        );
      }
    }
    // 清除后，删除，否则会影响下次切换
    store.removeScoreTracks.splice(0);
  });

  // 清除特殊标记轨迹
  watchEffect(() => {
    for (const track of store.currentMarkResult?.specialTagList || []) {
      for (const sliceImage of sliceImagesWithTrackList) {
        sliceImage.tagList = sliceImage.tagList.filter((t) =>
          store.currentMarkResult?.specialTagList.find(
            (st) =>
              st.offsetIndex === t.offsetIndex &&
              st.offsetX === t.offsetX &&
              st.offsetY === t.offsetY
          )
        );
      }
    }
    if (store.currentMarkResult?.specialTagList.length === 0) {
      for (const sliceImage of sliceImagesWithTrackList) {
        sliceImage.tagList = [];
      }
    }
  });
}
// end: 显示评分状态和清除轨迹

// start: 评分
const innerMakeTrack = (event: MouseEvent, item: SliceImage) => {
  makeTrack && makeTrack(event, item, maxSliceWidth, theFinalHeight);
};
// end: 评分

// start: 显示大图，供查看和翻转
const showBigImage = (event: MouseEvent) => {
  event.preventDefault();
  // console.log(event);
  let viewer: Viewer = null as unknown as Viewer;
  viewer && viewer.destroy();
  viewer = new Viewer(event.target as HTMLElement, {
    // inline: true,
    viewed() {
      viewer.zoomTo(1);
    },
    hidden() {
      viewer.destroy();
    },
    zIndex: 1000000,
  });
  viewer.show();
};
// end: 显示大图，供查看和翻转

// onRenderTriggered(({ key, target, type }) => {
//   console.log({ key, target, type });
// });
</script>

<style scoped>
.mark-body-container {
  min-height: calc(100vh - 56px);
  height: calc(100vh - 56px);
  overflow: auto;
  /* background-size: 8px 8px;
  background-image: linear-gradient(to right, #e7e7e7 4px, transparent 4px),
    linear-gradient(to bottom, transparent 4px, #e7e7e7 4px); */
  background-color: white;
  background-image: linear-gradient(45deg, #e0e0e0 25%, transparent 25%),
    linear-gradient(-45deg, #e0e0e0 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, #e0e0e0 75%),
    linear-gradient(-45deg, transparent 75%, #e0e0e0 75%);
  background-size: 20px 20px;
  background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
}
.mark-body-container img {
  width: 100%;
}
.single-image-container {
  position: relative;
}
.image-seperator {
  border: 2px solid transparent;
}
.status-container {
  position: sticky;
  bottom: calc(100% - 40px);
  left: calc(100% - 40px);
  margin-top: -40px;
  color: white;
  pointer-events: none;
  font-size: 16px;
  background-color: #ef7c78;

  width: 30px;
  height: 50px;
  text-align: center;
  z-index: 1000;
}
.double-triangle {
  background-color: #ef7c78;
  width: 30px;
  height: 6px;
  clip-path: polygon(0 0, 0 6px, 50% 0, 100% 0, 100% 6px, 50% 0);

  position: absolute;
  bottom: -6px;
}
</style>
