import {useEffect, useMemo, useRef, useState} from 'react';

import {useDomScale} from '@/react/context/dom-scale.js';
import {useRadians} from '@/react/context/radians.js';
import {RESOLUTION} from '@/util/constants.js';
import {render} from '@/util/dialogue.js';

import DialogueCaret from './dialogue-caret.jsx';
import styles from './dialogue.module.css';

export default function Dialogue({
  camera,
  dialogue,
  scale,
}) {
  const domScale = useDomScale();
  const ref = useRef();
  const [dimensions, setDimensions] = useState({h: 0, w: 0});
  const [caret, setCaret] = useState(0);
  const radians = useRadians();
  useEffect(() => {
    return dialogue.addSkipListener(() => {
      if (caret >= dialogue.letters.length - 1) {
        dialogue.onClose();
      }
      else {
        setCaret(dialogue.letters.length - 1);
      }
    });
  }, [caret, dialogue]);
  useEffect(() => {
    const {params} = dialogue.letters[caret];
    let handle;
    if (caret >= dialogue.letters.length - 1) {
      const {linger} = dialogue;
      if (!linger) {
        return;
      }
      handle = setTimeout(() => {
        dialogue.onClose();
      }, linger * 1000);
    }
    else {
      let jump = caret;
      while (0 === dialogue.letters[jump].params.rate.frequency && jump < dialogue.letters.length - 1) {
        jump += 1;
      }
      setCaret(jump);
      if (jump < dialogue.letters.length - 1) {
        handle = setTimeout(() => {
          setCaret(caret + 1);
        }, params.rate.frequency * 1000)
      }
    }
    return () => {
      clearTimeout(handle);
    }
  }, [caret, dialogue]);
  useEffect(() => {
    let handle;
    function track() {
      if (ref.current) {
        const {height, width} = ref.current.getBoundingClientRect();
        setDimensions({h: height / domScale, w: width / domScale});
      }
      handle = requestAnimationFrame(track);
    }
    handle = requestAnimationFrame(track);
    return () => {
      cancelAnimationFrame(handle);
    };
  }, [dialogue, domScale, ref]);
  const localRender = useMemo(
    () => render(dialogue.letters, styles.letter),
    [dialogue.letters],
  );
  let position = 'function' === typeof dialogue.position
    ? dialogue.position()
    : dialogue.position || {x: 0, y: 0};
  position = {
    x: position.x + dialogue.offset.x,
    y: position.y + dialogue.offset.y,
  };
  const bounds = {
    x: dimensions.w / (2 * scale),
    y: dimensions.h / (2 * scale),
  };
  const left = Math.max(
    Math.min(
      position.x * scale,
      RESOLUTION.x - bounds.x * scale - 16 + camera.x,
    ),
    bounds.x * scale + 16 + camera.x,
  );
  const top = Math.max(
    Math.min(
      position.y * scale - dimensions.h / 2,
      RESOLUTION.y - bounds.y * scale - 16 + camera.y,
    ),
    bounds.y * scale + 16 + camera.y,
  );
  return (
    <div
      className={styles.dialogue}
      ref={ref}
      style={{
        translate: `
          ${left}px
          ${top}px
        `,
      }}
    >
      <DialogueCaret
        camera={camera}
        dialogue={dialogue}
        dimensions={dimensions}
        scale={scale}
      />
      <p className={styles.letters}>
        {localRender(caret, radians)}
      </p>
    </div>
  );
}
