<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch, computed, useTemplateRef } from 'vue';
import { gsap } from 'gsap';

interface TextTypeProps {
  className?: string;
  showCursor?: boolean;
  hideCursorWhileTyping?: boolean;
  cursorCharacter?: string;
  cursorBlinkDuration?: number;
  cursorClassName?: string;
  text: string | string[];
  as?: string;
  typingSpeed?: number;
  initialDelay?: number;
  pauseDuration?: number;
  deletingSpeed?: number;
  loop?: boolean;
  textColors?: string[];
  variableSpeed?: { min: number; max: number };
  onSentenceComplete?: (sentence: string, index: number) => void;
  startOnVisible?: boolean;
  reverseMode?: boolean;
}

const props = withDefaults(defineProps<TextTypeProps>(), {
  as: 'div',
  typingSpeed: 50,
  initialDelay: 0,
  pauseDuration: 2000,
  deletingSpeed: 30,
  loop: true,
  className: '',
  showCursor: true,
  hideCursorWhileTyping: false,
  cursorCharacter: '|',
  cursorBlinkDuration: 0.5,
  textColors: () => [],
  startOnVisible: false,
  reverseMode: false
});

const displayedText = ref('');
const currentCharIndex = ref(0);
const isDeleting = ref(false);
const currentTextIndex = ref(0);
const isVisible = ref(!props.startOnVisible);
const cursorRef = useTemplateRef('cursorRef');
const containerRef = useTemplateRef('containerRef');

const textArray = computed(() => (Array.isArray(props.text) ? props.text : [props.text]));

const getRandomSpeed = () => {
  if (!props.variableSpeed) return props.typingSpeed;
  const { min, max } = props.variableSpeed;
  return Math.random() * (max - min) + min;
};

const getCurrentTextColor = () => {
  if (!props.textColors.length) return '#ffffff';
  return props.textColors[currentTextIndex.value % props.textColors.length];
};

let timeout: ReturnType<typeof setTimeout> | null = null;

const clearTimeoutIfNeeded = () => {
  if (timeout) clearTimeout(timeout);
};

const executeTypingAnimation = () => {
  const currentText = textArray.value[currentTextIndex.value];
  const processedText = props.reverseMode ? currentText.split('').reverse().join('') : currentText;

  if (isDeleting.value) {
    if (displayedText.value === '') {
      isDeleting.value = false;
      if (currentTextIndex.value === textArray.value.length - 1 && !props.loop) return;

      props.onSentenceComplete?.(textArray.value[currentTextIndex.value], currentTextIndex.value);

      currentTextIndex.value = (currentTextIndex.value + 1) % textArray.value.length;
      currentCharIndex.value = 0;
      timeout = setTimeout(() => {}, props.pauseDuration);
    } else {
      timeout = setTimeout(() => {
        displayedText.value = displayedText.value.slice(0, -1);
      }, props.deletingSpeed);
    }
  } else {
    if (currentCharIndex.value < processedText.length) {
      timeout = setTimeout(
        () => {
          displayedText.value += processedText[currentCharIndex.value];
          currentCharIndex.value += 1;
        },
        props.variableSpeed ? getRandomSpeed() : props.typingSpeed
      );
    } else if (textArray.value.length > 1) {
      timeout = setTimeout(() => {
        isDeleting.value = true;
      }, props.pauseDuration);
    }
  }
};

watch(
  [displayedText, currentCharIndex, isDeleting, isVisible],
  () => {
    if (!isVisible.value) return;
    clearTimeoutIfNeeded();

    if (currentCharIndex.value === 0 && !isDeleting.value && displayedText.value === '') {
      timeout = setTimeout(() => {
        executeTypingAnimation();
      }, props.initialDelay);
    } else {
      executeTypingAnimation();
    }
  },
  { immediate: true }
);

onMounted(() => {
  if (props.showCursor && cursorRef.value) {
    gsap.set(cursorRef.value, { opacity: 1 });
    gsap.to(cursorRef.value, {
      opacity: 0,
      duration: props.cursorBlinkDuration,
      repeat: -1,
      yoyo: true,
      ease: 'power2.inOut'
    });
  }

  if (props.startOnVisible && containerRef.value) {
    const observer = new IntersectionObserver(
      entries => {
        entries.forEach(entry => {
          if (entry.isIntersecting) isVisible.value = true;
        });
      },
      { threshold: 0.1 }
    );
    if (containerRef.value instanceof Element) {
      observer.observe(containerRef.value);
    }
    onBeforeUnmount(() => observer.disconnect());
  }
});

onBeforeUnmount(() => {
  clearTimeoutIfNeeded();
});
</script>

<template>
  <component
    :is="as"
    ref="containerRef"
    :class="`inline-block whitespace-pre-wrap tracking-tight ${className}`"
    v-bind="$attrs"
  >
    <span class="inline" :style="{ color: getCurrentTextColor() }">
      {{ displayedText }}
    </span>
    <span
      v-if="showCursor"
      ref="cursorRef"
      :class="`ml-1 inline-block opacity-100 ${
        hideCursorWhileTyping && (currentCharIndex < textArray[currentTextIndex].length || isDeleting) ? 'hidden' : ''
      } ${cursorClassName}`"
    >
      {{ cursorCharacter }}
    </span>
  </component>
</template>
