<template>
  <div class="label-content">
    <div class="title">条文内容</div>
    <div id="mark-text" ref="contentRef" class="text">
      {{ content }}
    </div>
    <yx-title class="mt40">条文属性</yx-title>
    <div class="tags mt20">
      <el-tag
        v-for="item in allAttrs"
        :key="item.attributeId"
        class="mr20 mb10"
        size="large"
        :closable="!readonly"
        @close="removeAttrAction(item.id)"
      >
        {{ item.attributeName }}
      </el-tag>
      <el-popover
        v-if="!readonly"
        placement="bottom-start"
        :width="550"
        trigger="click"
        @hide="popoverHide"
        @after-leave="tagSelectRef?.setFirstActived()"
        :show-arrow="false"
      >
        <TagSelect ref="tagSelectRef" v-model="attrIds" :api="getAttrTreeList" />
        <template #reference>
          <el-button class="mb10" type="primary">添加</el-button>
        </template>
      </el-popover>
    </div>
    <AnnoDialog ref="annoDialogRef" />
  </div>
</template>

<script lang="ts" setup>
import { ElMessage } from "element-plus";
import Mark from "mark.js";
import { ref, defineProps, defineEmits, withDefaults, computed, watch, onMounted } from "vue";
import { DataAnno } from "@/api/interface/index";
import { getAttrTreeList } from "@/api/modules/common";
import { addArticleAttr, delArticleAttr } from "@/api/modules/annotation/labeling";
import AnnoDialog from "./AnnoDialog.vue";
import TagSelect from "./TagSelect.vue";
interface Props {
  articleId: number;
  readonly: Boolean;
  content: string;
  labelList: DataAnno.LabelList[];
  attrList: DataAnno.AttrList[];
}

const props = withDefaults(defineProps<Props>(), { readonly: () => true, content: "", labelList: () => [], attrList: () => [] });
const emit = defineEmits(["refreshAttr", "refreshLabel"]);

// ref
const contentRef = ref();
const annoDialogRef = ref<typeof AnnoDialog>();
const tagSelectRef = ref<typeof TagSelect>();
const attrIds = ref<number[]>([]);
const allAttrs = computed<DataAnno.AttrItem[]>(() => {
  return props.attrList.reduce((a, b) => ({ detailRespList: a.detailRespList.concat(b.detailRespList) }), {
    detailRespList: []
  } as any).detailRespList;
});
watch(() => props.labelList, drawMark);
watch(
  allAttrs,
  val => {
    attrIds.value = val.map(item => item.attributeId);
  },
  {
    immediate: true
  }
);

interface Range {
  start: number;
  length: number;
  color: string;
  dataId: number;
}

// 绘制标注
function drawMark() {
  if (!contentRef.value) return;
  const ranges: Range[] = [];
  props.labelList.forEach(m => {
    ranges.push({ start: m.beginIndex, length: m.endIndex - m.beginIndex, color: m.colour, dataId: m.id });
  });
  const marker = new Mark(contentRef.value);
  marker.unmark();
  if (ranges.length === 0) return;
  marker.markRanges(ranges, {
    element: "span",
    className: "marker-text",
    each: (node: any, range: Range) => {
      node.setAttribute("data-id", range.dataId);
      node.style.setProperty("--marker-color", range.color);
      if (!props.readonly) {
        node.style.setProperty("user-select", "none");
        node.onclick = function (e: any) {
          e.preventDefault();
          const dataId = e.target.getAttribute("data-id");
          const curData = dataId && props.labelList.find(item => item.id === Number(dataId));
          if (!curData) return;
          const params = {
            ...curData,
            tagIdList: curData.tagDetailList?.map((item: any) => item.id) || [],
            articleId: props.articleId,
            top: e.clientY + 20,
            left: e.clientX - 20
          };
          delete params.tagDetailList;
          delete params.parentStandardWordName;
          delete params.parentStandardWordId;
          delete params.synonymWordId;
          delete params.synonymWordName;
          annoDialogRef.value?.open(params).then(() => {
            emit("refreshLabel");
          });
        };
      }
    }
  });
}

// 获取选中文字的索引位置
function getSelectionIndex() {
  const selection = window.getSelection() as any;

  if (selection.rangeCount > 0) {
    const range = selection.getRangeAt(0);
    const startContainer = range.startContainer;
    // const commonAncestor = range.commonAncestorContainer;
    const beginIndex = getAbsoluteOffset(startContainer) + range.startOffset;
    const endIndex = beginIndex + (range.endOffset - range.startOffset);
    return {
      beginIndex,
      endIndex
    };
  } else {
    throw new Error("未选中文本");
  }
}

// 获取划词相对整个文本的绝对偏移量
function getAbsoluteOffset(node: any) {
  let offset = 0;

  for (const childNode of contentRef.value.childNodes) {
    if (childNode === node) return offset;
    if (childNode.nodeName === "#text") {
      offset += childNode.length;
    } else {
      offset += childNode.innerText.length;
    }
  }

  return offset;
}

// 注册鼠标抬起事件
function addMouseupEvent() {
  contentRef.value?.addEventListener("mouseup", (e: any) => {
    // 获取文字节点
    const selections = window?.getSelection() as any;
    const text = selections?.toString() || "";
    const nodeValues = selections.focusNode?.nodeValue;
    // 已经标注的，不能在重复选择
    if (nodeValues?.indexOf(text) === -1) {
      ElMessage.warning("已经标注的，不能在重复选择");
      return;
    }

    if (text.length > 0) {
      const marker = new Mark(contentRef.value);
      const { beginIndex, endIndex } = getSelectionIndex();
      marker.markRanges([{ start: beginIndex, length: endIndex - beginIndex }], {
        element: "span",
        className: "marker-select-text"
      });
      annoDialogRef.value
        ?.open({
          beginIndex,
          endIndex,
          articleId: props.articleId,
          keyword: text,
          top: e.clientY + 30,
          left: e.clientX
        })
        .then(() => {
          emit("refreshLabel");
        })
        .catch(() => {
          marker.unmark({
            className: "marker-select-text"
          });
        });
    }
  });
}

// 删除条文属性
async function removeAttrAction(id: number) {
  await delArticleAttr({ id: id });
  emit("refreshAttr");
}

// popover hide
async function popoverHide() {
  // 1.过滤掉已经存在的
  const addIds = attrIds.value.filter(id => !allAttrs.value.map(item => item.attributeId).includes(id));
  if (addIds.length === 0) return;
  // 2.保存条文属性
  await addArticleAttr({ articleId: props.articleId, attributeIds: addIds });
  emit("refreshAttr");
}

// 钩子
onMounted(() => {
  if (props.labelList.length > 0) {
    drawMark();
  }
  if (!props.readonly) {
    addMouseupEvent();
  }
});
</script>

<style lang="scss" scoped>
.label-content {
  padding: 30px 20px;
  .title {
    font-size: 24px;
    font-weight: bold;
    line-height: 36px;
    color: #333333;
    text-align: center;
  }
  .text {
    font-size: 16px;
    line-height: 38px;
    color: #333333;
  }
  .el-tag {
    background: #fddfd7;
    border: 1px solid #d67f66;
    border-radius: 4px;
    :deep(.el-tag__content) {
      font-size: 16px;
      color: #d67f66;
    }
  }
}
</style>
<style lang="scss">
.marker-text {
  --marker-color: #33333333;

  margin: 0 2px;
  font-size: 16px;
  color: var(--marker-color);
  border-bottom: 2px solid var(--marker-color);
}
.marker-select-text {
  padding: 8px 0;
  font-size: 16px;
  line-height: 38px;
  background: rgb(64 158 255 / 70%);
}
</style>
