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

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

import styles from './dialogue.module.css';

const CARET_SIZE = 12;

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],
  );
  const origin = 'function' === typeof dialogue.origin
    ? dialogue.origin()
    : dialogue.origin || {x: 0, y: 0};
  const bounds = {
    x: dimensions.w / (2 * scale),
    y: dimensions.h / (2 * scale),
  };
  const left = Math.max(
    Math.min(
      dialogue.position.x * scale - camera.x,
      RESOLUTION.x - bounds.x * scale - 16,
    ),
    bounds.x * scale + 16,
  );
  const top = Math.max(
    Math.min(
      dialogue.position.y * scale - camera.y,
      RESOLUTION.y - bounds.y * scale - 16,
    ),
    bounds.y * scale + 88,
  );
  const offsetPosition = {
    x: ((dialogue.position.x * scale - camera.x) - left) / scale,
    y: ((dialogue.position.y * scale - camera.y) - top) / scale,
  };
  const difference = {
    x: origin.x - dialogue.position.x + offsetPosition.x,
    y: origin.y - dialogue.position.y + offsetPosition.y,
  };
  const within = {
    x: Math.abs(difference.x) < bounds.x,
    y: Math.abs(difference.y) < bounds.y,
  };
  const caretPosition = {
    x: Math.max(-bounds.x, Math.min(origin.x - dialogue.position.x + offsetPosition.x, bounds.x)),
    y: Math.max(-bounds.y, Math.min(origin.y - dialogue.position.y + offsetPosition.y, bounds.y)),
  };
  let caretRotation = Math.atan2(
    difference.y - caretPosition.y,
    difference.x - caretPosition.x,
  );
  caretRotation += Math.PI * 1.5;
  if (within.x) {
    caretPosition.y = bounds.y * Math.sign(difference.y);
    if (within.y) {
      if (Math.sign(difference.y) > 0) {
        caretRotation = Math.PI;
      }
      else {
        caretRotation = 0;
      }
    }
  }
  else if (within.y) {
    caretPosition.x = bounds.x * Math.sign(difference.x);
  }
  caretPosition.x *= scale;
  caretPosition.y *= scale;
  caretPosition.x += -Math.sin(caretRotation) * (CARET_SIZE / 2);
  caretPosition.y += Math.cos(caretRotation) * (CARET_SIZE / 2);
  return (
    <div
      className={styles.dialogue}
      ref={ref}
      style={{
        left: `${left}px`,
        top: `${top}px`,
      }}
    >
      <svg
        className={styles.caret}
        viewBox="0 0 24 24"
        width={CARET_SIZE}
        height={CARET_SIZE}
        style={{
          transform: `
            translate(
              calc(-50% + ${caretPosition.x}px),
              calc(-50% + ${caretPosition.y}px)
            )
            rotate(${caretRotation}rad)
          `,
        }}
      >
        <polygon points="0 0, 24 0, 12 24" />
      </svg>
      <p className={styles.letters}>
        {localRender(caret, radians)}
      </p>
    </div>
  );
}
