import {memo, useCallback, useRef} from 'react';

import {DamageTypes} from '@/ecs/components/vulnerable.js';
import {useEcsTick} from '@/react/context/ecs.js';
import useAnimationFrame from '@/react/hooks/use-animation-frame.js';
import {easeInOutExpo, easeInQuint, easeOutQuad, linear} from '@/util/easing.js';

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

function damageHue(type) {
  let hue;
  switch(type) {
    case DamageTypes.PAIN: {
      hue = [0, 30];
      break;
    }
    case DamageTypes.HEALING: {
      hue = [90, 120];
      break;
    }
    case DamageTypes.MANA: {
      hue = [215, 220];
      break;
    }
  }
  return hue;
}

class Damage {
  elapsed = 0;
  hue = [0, 30];
  offsetX = 0;
  offsetY = 0;
  step = 0;
  constructor() {
    const element = document.createElement('div');
    element.classList.add(styles.damage);
    element.appendChild(document.createElement('p'));
    this.element = element;
  }
  reset(key, scale, {amount, position, type}) {
    this.elapsed = 0;
    [this.hueStart, this.hueEnd] = damageHue(type);
    this.offsetX = 20 * scale * 1 * (Math.random() - 0.5);
    this.offsetY = -1 * (10 + Math.random() * 45 * scale);
    this.step = 0;
    const {element} = this;
    element.style.setProperty('rotate', `${Math.random() * (Math.PI / 16) - (Math.PI / 32)}rad`);
    element.style.setProperty('--magnitude', Math.max(1, Math.floor(Math.log10(Math.abs(amount)))));
    element.style.setProperty('--positionX', `${position.x * scale}px`);
    element.style.setProperty('--positionY', `${position.y * scale}px`);
    element.style.setProperty('zIndex', key);
    const p = element.querySelector('p');
    p.style.scale = scale / 2;
    p.innerText = Math.abs(amount);
  }
  tick(elapsed, stepSize) {
    this.elapsed += elapsed;
    this.step += elapsed;
    if (this.step > stepSize) {
      this.step = this.step % stepSize;
      // offset
      let offsetX = 0, offsetY = 0;
      if (this.elapsed <= 0.5) {
        offsetX = easeOutQuad(this.elapsed, 0, this.offsetX, 0.5);
        offsetY = easeOutQuad(this.elapsed, 0, this.offsetY, 0.5);
      }
      else if (this.elapsed > 1.375) {
        offsetX = easeOutQuad(this.elapsed - 1.375, this.offsetX, -this.offsetX, 0.125);
        offsetY = easeOutQuad(this.elapsed - 1.375, this.offsetY, -this.offsetY, 0.125);
      }
      else {
        offsetX = this.offsetX;
        offsetY = this.offsetY;
      }
      this.element.style.setProperty('--offsetX', offsetX);
      this.element.style.setProperty('--offsetY', offsetY);
      // scale
      let scale = 0.35;
      if (this.elapsed <= 0.5) {
        scale = easeOutQuad(this.elapsed, 0, 1, 0.5);
      }
      else if (this.elapsed > 0.5 && this.elapsed < 0.6) {
        scale = linear(this.elapsed - 0.5, 1, 0.5, 0.1);
      }
      else if (this.elapsed > 0.6 && this.elapsed < 0.675) {
        scale = easeInQuint(this.elapsed - 0.6, 1, 0.25, 0.075);
      }
      else if (this.elapsed > 0.675 && this.elapsed < 1.375) {
        scale = 1;
      }
      else if (this.elapsed > 1.375) {
        scale = easeOutQuad(this.elapsed - 1.375, 1, -1, 0.125);
      }
      this.element.style.setProperty('--scale', scale);
      // opacity
      let opacity = 0.75;
      if (this.elapsed <= 0.375) {
        opacity = linear(this.elapsed, 0.75, 0.25, 0.375);
      }
      else if (this.elapsed > 0.375 && this.elapsed < 1.375) {
        opacity = 1;
      }
      else if (this.elapsed > 1.375) {
        opacity = linear(this.elapsed - 1.375, 1, -1, 0.125);
      }
      this.element.style.setProperty('--opacity', opacity);
      // hue
      this.element.style.setProperty(
        '--hue',
        easeInOutExpo(
          Math.abs((this.elapsed % 0.3) - 0.15) / 0.15,
          this.hueStart,
          this.hueEnd,
          1,
        ),
      );
    }
  }
}

function Damages({scale}) {
  const damages = useRef({});
  const damagesRef = useRef();
  const pool = useRef([]);
  const onEcsTick = useCallback((payload) => {
    for (const id in payload) {
      const update = payload[id];
      if (false === update || !update.Vulnerable?.damage) {
        continue;
      }
      const {damage: damageUpdate} = update.Vulnerable;
      for (const key in damageUpdate) {
        const damage = pool.current.length > 0 ? pool.current.pop() : new Damage();
        damage.reset(key, scale, damageUpdate[key]);
        damages.current[key] = damage;
        damagesRef.current.appendChild(damage.element);
      }
    }
  }, [scale]);
  useEcsTick(onEcsTick);
  const frame = useCallback((elapsed) => {
    if (!damagesRef.current) {
      return;
    }
    const keys = Object.keys(damages.current);
    if (0 === keys.length && 0 === pool.current.length) {
      for (let i = 0; i < 500; ++i) {
        pool.current.push(new Damage());
      }
    }
    const stepSize = keys.length > 150 ? (keys.length / 500) * 0.25 : elapsed;
    for (const key of keys) {
      const damage = damages.current[key];
      damage.tick(elapsed, stepSize);
      if (damage.elapsed > 1.5) {
        damagesRef.current.removeChild(damage.element);
        delete damages.current[key];
        if (pool.current.length < 1000) {
          pool.current.push(damage);
        }
      }
    }
  }, []);
  useAnimationFrame(frame);
  return (
    <div
      className={styles.damages}
      ref={damagesRef}
    />
  );
}

export default memo(Damages);
