import React, { useState, useEffect } from 'react';

export default function TypingEffect({
  steps = [],
  loop = true,
  typingSpeed = [40, 120],
  deleteSpeed = [20, 60],
}) {
  const [displayed, setDisplayed] = useState('');
  const [showCursor, setShowCursor] = useState(true);
  const [cursorBlink, setCursorBlink] = useState(true);

  const configKey = JSON.stringify({ steps, loop, typingSpeed, deleteSpeed });

  useEffect(() => {
    let cancelled = false;
    let timerId = null;

    const sleep = (ms) =>
      new Promise((resolve) => {
        timerId = setTimeout(resolve, ms);
      });

    const rand = ([min, max]) =>
      Math.floor(min + Math.random() * (max - min + 1));

    const run = async () => {
      do {
        let chars = [];
        setDisplayed('');

        for (const step of steps) {
          if (cancelled) return;

          if ('text' in step) {
            const speed = step.speed || typingSpeed;
            for (const c of Array.from(step.text)) {
              if (cancelled) return;
              chars.push(c);
              setDisplayed(chars.join(''));
              await sleep(rand(speed));
            }
          } else if ('pause' in step) {
            await sleep(step.pause);
          } else if ('delete' in step) {
            const speed = step.speed || deleteSpeed;
            const count =
              step.delete === 'all'
                ? chars.length
                : Math.min(step.delete, chars.length);
            for (let i = 0; i < count; i++) {
              if (cancelled) return;
              chars.pop();
              setDisplayed(chars.join(''));
              await sleep(rand(speed));
            }
          }
        }

        if (!loop) {
          setShowCursor(false);
          return;
        }

        await sleep(500);
      } while (!cancelled);
    };

    run();

    return () => {
      cancelled = true;
      if (timerId != null) clearTimeout(timerId);
    };
  }, [configKey]);

  useEffect(() => {
    if (!showCursor) return;
    const id = setInterval(() => setCursorBlink((v) => !v), 530);
    return () => clearInterval(id);
  }, [showCursor]);

  const parts = displayed.split('\n');

  return (
    <span>
      {parts.map((part, i) => (
        <React.Fragment key={i}>
          {i > 0 && <br />}
          {part}
        </React.Fragment>
      ))}
      {showCursor && (
        <span
          aria-hidden="true"
          style={{
            display: 'inline-block',
            width: '2px',
            height: '1em',
            backgroundColor: 'currentColor',
            marginLeft: '1px',
            verticalAlign: 'text-bottom',
            opacity: cursorBlink ? 1 : 0,
          }}
        />
      )}
    </span>
  );
}
