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

import {usePacket} from '@/react/context/client.js';
import {useEcsTick} from '@/react/context/ecs.js';
import {parseLetters} from '@/util/dialogue.js';

import Damages from './damages.jsx';
import Entity from './entity.jsx';

export default function Entities({
  camera,
  scale,
  setChatMessages,
  setMonopolizers,
}) {
  const [entities, setEntities] = useState({});
  const [damages, setDamages] = useState({});
  const {current: pendingDamage} = useRef({accumulated: [], handle: undefined});
  usePacket('EcsChange', async () => {
    setEntities({});
  });
  const onEcsTick = useCallback((payload, ecs) => {
    const deleting = {};
    const updating = {};
    for (const id in payload) {
      if ('1' === id) {
        continue;
      }
      const update = payload[id];
      if (false === update) {
        deleting[id] = true;
        continue;
      }
      updating[id] = ecs.get(id);
      const {dialogue} = update.Interlocutor || {};
      if (dialogue) {
        const {dialogues} = updating[id].Interlocutor;
        for (const key in dialogue) {
          dialogues[key] = dialogue[key];
          if (!dialogues[key].offset) {
            dialogues[key].offset = {x: 0, y: 0};
          }
          if ('track' === dialogues[key].origin) {
            dialogues[key].origin = () => updating[id].Position;
          }
          if ('track' === dialogues[key].position) {
            dialogues[key].position = () => updating[id].Position;
          }
          dialogues[key].letters = parseLetters(dialogues[key].body);
          setChatMessages((chatMessages) => ({
            [[id, key].join('-')]: dialogues[key].letters,
            ...chatMessages,
          }));
          const skipListeners = new Set();
          dialogues[key].addSkipListener = (listener) => {
            skipListeners.add(listener);
            return () => {
              skipListeners.delete(listener);
            }
          }
          const monopolizer = {
            trigger: () => {
              for (const listener of skipListeners) {
                listener();
              }
            },
          };
          if (dialogues[key].monopolizer) {
            setMonopolizers((monopolizers) => [...monopolizers, monopolizer]);
          }
          dialogues[key].onClose = () => {
            setEntities((entities) => ({
              ...entities,
              [id]: ecs.rebuild(id),
            }));
            if (dialogues[key].monopolizer) {
              setMonopolizers((monopolizers) => {
                const index = monopolizers.indexOf(monopolizer);
                if (-1 === index) {
                  return monopolizers;
                }
                monopolizers.splice(index, 1);
                return [...monopolizers];
              });
            }
            delete dialogues[key];
          };
        }
      }
      const {damage} = update.Vulnerable || {};
      if (damage) {
        for (const key in damage) {
          damage[key].onClose = () => {
            setDamages((damages) => {
              const {[key]: _, ...rest} = damages; // eslint-disable-line no-unused-vars
              return rest;
            })
          };
        }
        pendingDamage.accumulated.push(damage);
        if (!pendingDamage.handle) {
          pendingDamage.handle = setTimeout(() => {
            const update = {};
            for (const damage of pendingDamage.accumulated) {
              for (const key in damage) {
                update[key] = damage[key];
              }
            }
            pendingDamage.accumulated.length = 0;
            setDamages((damages) => {
              return {
                ...damages,
                ...update,
              };
            });
            pendingDamage.handle = undefined;
          }, 50);
        }
      }
    }
    setEntities((entities) => {
      for (const id in deleting) {
        delete entities[id];
      }
      return {
        ...entities,
        ...updating,
      };
    });
  }, [pendingDamage, setChatMessages, setMonopolizers]);
  useEcsTick(onEcsTick);
  const renderables = [];
  for (const id in entities) {
    renderables.push(
      <Entity
        camera={camera}
        entity={entities[id]}
        key={id}
        scale={scale}
      />
    );
  }
  return (
    <div
      style={{
        translate: `
          calc(-1px * ${camera.x})
          calc(-1px * ${camera.y})
        `,
      }}
    >
      {renderables}
      <Damages
        damages={damages}
        scale={scale}
      />
    </div>
  );
}
