import {AdjustmentFilter} from '@pixi/filter-adjustment';
import {GlowFilter} from '@pixi/filter-glow';
import {Container} from '@pixi/react';
import {useCallback, useEffect, useRef, useState} from 'react';

import {usePacket} from '@/react/context/client.js';
import {useDebug} from '@/react/context/debug.js';
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
import {useMainEntity} from '@/react/context/main-entity.js';
import {useRadians} from '@/react/context/radians.js';

import Entity from './entity.js';

export default function Entities({monopolizers, particleWorker}) {
  const [debug] = useDebug();
  const [ecs] = useEcs();
  const containerRef = useRef();
  const latestTick = useRef();
  const entities = useRef({});
  const pool = useRef([]);
  const [mainEntity] = useMainEntity();
  const radians = useRadians();
  const willInteractWith = useRef(0);
  const [interactionFilters] = useState([
    new AdjustmentFilter(),
    new GlowFilter({color: 0x0}),
  ]);
  const pulse = (Math.cos(radians / 4) + 1) * 0.5;
  interactionFilters[0].brightness = (pulse * 0.75) + 1;
  interactionFilters[1].outerStrength = pulse * 0.5;
  const updateEntities = useCallback((payload) => {
    for (const id in payload) {
      if ('1' === id) {
        continue;
      }
      if (!payload[id]) {
        entities.current[id].removeFromContainer();
        pool.current.push(entities.current[id]);
        delete entities.current[id];
        continue;
      }
      if (!entities.current[id]) {
        entities.current[id] = pool.current.length > 0
          ? pool.current.pop()
          : new Entity();
        entities.current[id].reset(ecs.get(id), debug);
        if (mainEntity === id) {
          entities.current[id].setMainEntity();
        }
      }
      entities.current[id].update(payload[id], containerRef.current);
    }
  }, [debug, ecs, mainEntity])
  useEffect(() => {
    if (0 === pool.current.length) {
      for (let i = 0; i < 1000; ++i) {
        pool.current.push(new Entity());
      }
    }
  }, []);
  useEffect(() => {
    for (const key in entities.current) {
      entities.current[key].setDebug(debug);
    }
  }, [debug]);
  usePacket('EcsChange', async () => {
    for (const id in entities.current) {
      entities.current[id].removeFromContainer();
    }
    entities.current = {};
  });
  const onEcsTickEntities = useCallback((payload) => {
    updateEntities(payload);
  }, [updateEntities]);
  useEcsTick(onEcsTickEntities);
  useEffect(() => {
    if (!ecs || !particleWorker) {
      return;
    }
    async function onMessage(diff) {
      latestTick.current = Promise.resolve(latestTick.current).then(async () => {
        await ecs.apply(diff.data);
        updateEntities(diff.data);
      });
    }
    particleWorker.addEventListener('message', onMessage);
    return () => {
      particleWorker.removeEventListener('message', onMessage);
    };
  }, [ecs, particleWorker, updateEntities]);
  const onEcsTickParticles = useCallback((payload) => {
    for (const id in payload) {
      const update = payload[id];
      if (update.Emitter?.emit) {
        for (const id in update.Emitter.emit) {
          particleWorker?.postMessage(update.Emitter.emit[id]);
        }
      }
    }
  }, [particleWorker]);
  useEcsTick(onEcsTickParticles);
  const onEcsTickInteractions = useCallback((payload, ecs) => {
    const main = ecs.get(mainEntity);
    if (main) {
      if (willInteractWith.current !== main.Interacts.willInteractWith) {
        if (entities.current[willInteractWith.current]) {
          entities.current[willInteractWith.current].diffuse.filters = [];
        }
        willInteractWith.current = main.Interacts.willInteractWith;
      }
      const interacting = entities.current[main.Interacts.willInteractWith];
      if (interacting) {
        interacting.diffuse.filters = 0 === monopolizers.length
          ? interactionFilters
          : [];
      }
    }
  }, [interactionFilters, mainEntity, monopolizers]);
  useEcsTick(onEcsTickInteractions);
  return (
    <Container
      ref={containerRef}
      sortableChildren
    />
  );
}