<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue';

const props = withDefaults(defineProps<{ speed?: number; strings?: string; lifeLike?: boolean; loop?: boolean }>(), {
  speed: 120,
  strings: '你们好！你嘿！嘿，你好！嘿，你们好！嘿，你们好！嘿，你们好！嘿，你们好！嘿，你们好！',
  lifeLike: true, // 使每次打字在延迟跳动，更像人类打字的样式
  loop: false
});

// 用于loop: false
const complete = defineModel<boolean>('complete', { default: false });

/** 光标跟随逻辑 */
const contentRef = ref<HTMLElement | null>(null); // 包含text和光标的容器节点
const textRef = ref<HTMLElement | null>(null); // text节点
const cursorRef = ref<HTMLElement | null>(null); // 光标节点
const isMounted = ref(true); // 循环中增加退出判断

const loopNum = 30;

function startDelay(delay = 0) {
  return new Promise(resolve => {
    if (delay) {
      setTimeout(resolve, delay);
      return;
    }
    if (props.lifeLike) {
      const randomDelay = Math.floor(Math.random() * (loopNum * 2 + 1)) - loopNum + props.speed; // 生成-10到10的随机数
      setTimeout(resolve, randomDelay);
    } else setTimeout(resolve, props.speed);
  });
}

/** 模拟请求 */
async function mockResponse() {
  cursorRef.value!.style.display = 'block';
  if (props.loop) {
    while (props.loop && isMounted.value) {
      for (let i = 0; i <= props.strings.length; i++) {
        if (textRef.value) {
          textRef.value.innerHTML = props.strings.slice(0, i);
          updateCursor();
          // eslint-disable-next-line no-await-in-loop
          await startDelay();
        }
      }
      // eslint-disable-next-line no-await-in-loop
      await startDelay(2000);
      for (let i = props.strings.length - 1; i >= 0; i--) {
        if (textRef.value) {
          textRef.value.innerHTML = props.strings.slice(0, i);
          updateCursor();
          // eslint-disable-next-line no-await-in-loop
          await startDelay(40);
        }
      }
    }
  } else {
    for (let i = 0; i <= props.strings.length; i++) {
      if (textRef.value) {
        textRef.value.innerHTML = props.strings.slice(0, i);
        updateCursor();
        // eslint-disable-next-line no-await-in-loop
        await startDelay();
      }
    }

    complete.value = true;
  }

  if (cursorRef.value) cursorRef.value!.style.display = 'none';
}

/** 获取最后一个文本节点 */
function getLastTextNode(node: Node | null): Node | null {
  if (!node) return null;
  if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) {
    return node;
  }
  for (let i = node.childNodes.length - 1; i >= 0; i--) {
    const childNode = node.childNodes[i];
    const textNode = getLastTextNode(childNode);
    if (textNode) {
      return textNode;
    }
  }
  return null;
}

function updateCursor() {
  // 1. 找到最后一个文本节点
  const lastTextNode = getLastTextNode(textRef.value);
  // 2. 创建一个临时文本节点
  const tempText = document.createTextNode('\u200B'); // 零宽字符
  // 3. 将临时文本节点放在最后一个文本节点之后
  if (lastTextNode && lastTextNode.parentNode) {
    lastTextNode.parentNode.appendChild(tempText);
  } else if (textRef.value) {
    textRef.value.appendChild(tempText);
  }
  // 4. 获取临时文本节点距离父节点的距离（x,y）
  const range = document.createRange(); // 设置范围
  range.setStart(tempText, 0);
  range.setEnd(tempText, 0);
  const rect = range.getBoundingClientRect(); // 获取距离信息
  // 5. 获取当前文本容器距离视图的距离(x,y)
  const textRect = contentRef.value && contentRef.value.getBoundingClientRect();
  // 6. 获取到当前文本节点的位置，并将光标的位置插入到相应位置
  if (textRect) {
    const x = rect.left - textRect.left;
    const y = rect.top - textRect.top;
    cursorRef.value!.style.transform = `translate(${x}px,${y}px)`;
  }
  // 7. 移除临时文本节点
  tempText.remove();
}

onMounted(() => {
  if (!props.strings.trim()) return;
  mockResponse();
});

onUnmounted(() => {
  isMounted.value = false;
});
</script>

<template>
  <div ref="contentRef" class="relative">
    <div ref="textRef" class="text"></div>
    <div ref="cursorRef" class="cursor absolute left-0 top--1px hidden">|</div>
  </div>
</template>

<style lang="scss" scoped>
.cursor {
  animation: cursorAnimate 0.8s infinite;
}

@keyframes cursorAnimate {
  0%,
  100% {
    opacity: 0;
  }
  50% {
    opacity: 1;
  }
}
</style>
