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

import addKeyListener from '@/add-key-listener.js';
import ClientEcs from '@/client-ecs';
import {RESOLUTION} from '@/constants.js';
import {useClient, usePacket} from '@/context/client.js';
import {useDebug} from '@/context/debug.js';
import {useEcs, useEcsTick} from '@/context/ecs.js';
import {useMainEntity} from '@/context/main-entity.js';

import Disconnected from './dom/disconnected.jsx';
import Chat from './dom/chat/chat.jsx';
import Dom from './dom/dom.jsx';
import Entities from './dom/entities.jsx';
import HotBar from './dom/hotbar.jsx';
import Pixi from './pixi/pixi.jsx';
import Devtools from './devtools.jsx';
import styles from './ui.module.css';

function emptySlots() {
  return Array(10).fill(undefined);
}

const devEventsChannel = {
  $$listeners: {},
  addListener(type, listener) {
    if (!this.$$listeners[type]) {
      this.$$listeners[type] = new Set();
    }
    this.$$listeners[type].add(listener);
  },
  invoke(type, payload) {
    const listeners = this.$$listeners[type];
    if (!listeners) {
      return;
    }
    for (const listener of listeners) {
      listener(payload);
    }
  },
  removeListener(type, listener) {
    const listeners = this.$$listeners[type];
    if (!listeners) {
      return;
    }
    listeners.delete(listener);
  },
};

export default function Ui({disconnected}) {
  // Key input.
  const client = useClient();
  const chatInputRef = useRef();
  const gameRef = useRef();
  const [mainEntity, setMainEntity] = useMainEntity();
  const [debug, setDebug] = useDebug();
  const [ecs, setEcs] = useEcs();
  const [showDisconnected, setShowDisconnected] = useState(false);
  const [bufferSlot, setBufferSlot] = useState();
  const [devtoolsIsOpen, setDevtoolsIsOpen] = useState(false);
  const ratio = (RESOLUTION.x * (devtoolsIsOpen ? 2 : 1)) / RESOLUTION.y;
  const [camera, setCamera] = useState({x: 0, y: 0});
  const [hotbarSlots, setHotbarSlots] = useState(emptySlots());
  const [activeSlot, setActiveSlot] = useState(0);
  const [scale, setScale] = useState(2);
  const [Components, setComponents] = useState();
  const [Systems, setSystems] = useState();
  const [applyFilters, setApplyFilters] = useState(true);
  const [monopolizers, setMonopolizers] = useState([]);
  const [message, setMessage] = useState('');
  const [chatIsOpen, setChatIsOpen] = useState(false);
  const [chatHistory, setChatHistory] = useState([]);
  const [chatHistoryCaret, setChatHistoryCaret] = useState(-1);
  const [chatMessages, setChatMessages] = useState([]);
  const [pendingMessage, setPendingMessage] = useState('');
  useEffect(() => {
    async function setEcsStuff() {
      const {default: Components} = await import('@/ecs-components/index.js');
      const {default: Systems} = await import('@/ecs-systems/index.js');
      setComponents(Components);
      setSystems(Systems);
    }
    setEcsStuff();
  }, []);
  useEffect(() => {
    setEcs(new ClientEcs({Components, Systems}));
  }, [Components, setEcs, Systems]);
  useEffect(() => {
    let handle;
    if (disconnected) {
      handle = setTimeout(() => {
        setShowDisconnected(true);
      }, 1000);
    }
    else {
      setShowDisconnected(false)
    }
    return () => {
      clearTimeout(handle);
    };
  }, [disconnected]);
  useEffect(() => {
    return addKeyListener(document.body, ({event, type, payload}) => {
      if ('Escape' === payload && 'keyDown' === type && chatIsOpen) {
        setChatIsOpen(false);
        return;
      }
      if (chatInputRef.current) {
        chatInputRef.current.focus();
      }
      if (chatIsOpen) {
        return;
      }
      const KEY_MAP = {
        keyDown: 1,
        keyUp: 0,
      };
      let actionPayload;
      switch (payload) {
        case '-':
          if ('keyDown' === type) {
            setScale((scale) => scale > 1 ? scale - 1 : 1);
          }
          break;
        case '=':
        case '+':
          if ('keyDown' === type) {
            setScale((scale) => scale < 4 ? Math.floor(scale + 1) : 4);
          }
          break;
        case 'F3': {
          if (event) {
            event.preventDefault();
          }
          if ('keyDown' === type) {
            setDebug(!debug);
          }
          break;
        }
        case 'F4': {
          if (event) {
            event.preventDefault();
          }
          if ('keyDown' === type) {
            setDevtoolsIsOpen(!devtoolsIsOpen);
          }
          break;
        }
        case 'w': {
          actionPayload = {type: 'moveUp', value: KEY_MAP[type]};
          break;
        }
        case 'a': {
          actionPayload = {type: 'moveLeft', value: KEY_MAP[type]};
          break;
        }
        case 's': {
          actionPayload = {type: 'moveDown', value: KEY_MAP[type]};
          break;
        }
        case 'd': {
          actionPayload = {type: 'moveRight', value: KEY_MAP[type]};
          break;
        }
        case ' ': {
          actionPayload = {type: 'use', value: KEY_MAP[type]};
          break;
        }
        case 'Enter': {
          if ('keyDown' === type) {
            setChatIsOpen(true);
          }
          break
        }
        case 'e': {
          if (KEY_MAP[type]) {
            if (monopolizers.length > 0) {
              monopolizers[0].trigger();
              break;
            }
          }
          actionPayload = {type: 'interact', value: KEY_MAP[type]};
          break;
        }
        case '1': {
          if ('keyDown' === type) {
            actionPayload = {type: 'changeSlot', value: 1};
          }
          break;
        }
        case '2': {
          if ('keyDown' === type) {
            actionPayload = {type: 'changeSlot', value: 2};
          }
          break;
        }
        case '3': {
          if ('keyDown' === type) {
            actionPayload = {type: 'changeSlot', value: 3};
          }
          break;
        }
        case '4': {
          if ('keyDown' === type) {
            actionPayload = {type: 'changeSlot', value: 4};
          }
          break;
        }
        case '5': {
          if ('keyDown' === type) {
            actionPayload = {type: 'changeSlot', value: 5};
          }
          break;
        }
        case '6': {
          if ('keyDown' === type) {
            actionPayload = {type: 'changeSlot', value: 6};
          }
          break;
        }
        case '7': {
          if ('keyDown' === type) {
            actionPayload = {type: 'changeSlot', value: 7};
          }
          break;
        }
        case '8': {
          if ('keyDown' === type) {
            actionPayload = {type: 'changeSlot', value: 8};
          }
          break;
        }
        case '9': {
          if ('keyDown' === type) {
            actionPayload = {type: 'changeSlot', value: 9};
          }
          break;
        }
        case '0': {
          if ('keyDown' === type) {
            actionPayload = {type: 'changeSlot', value: 10};
          }
          break;
        }
      }
      if (actionPayload) {
        client.send({
          type: 'Action',
          payload: actionPayload,
        });
      }
    });
  }, [chatIsOpen, client, debug, devtoolsIsOpen, monopolizers, setDebug, setScale]);
  usePacket('EcsChange', async () => {
    setEcs(new ClientEcs({Components, Systems}));
    setMainEntity(undefined);
    setMonopolizers([]);
  }, [Components, Systems, setEcs, setMainEntity]);
  usePacket('Tick', async (payload, client) => {
    if (0 === Object.keys(payload.ecs).length) {
      return;
    }
    await ecs.apply(payload.ecs);
    for (const listener of client.listeners[':Ecs'] ?? []) {
      listener(payload.ecs);
    }
  }, [ecs]);
  useEcsTick((payload) => {
    let localMainEntity = mainEntity;
    for (const id in payload) {
      const entity = ecs.get(id);
      const update = payload[id];
      if (!update) {
        continue;
      }
      if (update.Sound?.play) {
        for (const sound of update.Sound.play) {
          (new Audio(sound)).play();
        }
      }
      if (update.MainEntity) {
        setMainEntity(localMainEntity = id);
      }
      if (localMainEntity === id) {
        if (update.Inventory) {
          setBufferSlot(entity.Inventory.item(0));
          const newHotbarSlots = emptySlots();
          for (let i = 1; i < 11; ++i) {
            newHotbarSlots[i - 1] = entity.Inventory.item(i);
          }
          setHotbarSlots(newHotbarSlots);
        }
        if (update.Wielder && 'activeSlot' in update.Wielder) {
          setActiveSlot(update.Wielder.activeSlot);
        }
      }
    }
    if (localMainEntity) {
      const mainEntityEntity = ecs.get(localMainEntity);
      const x = Math.round((mainEntityEntity.Camera.x * scale) - RESOLUTION.x / 2);
      const y = Math.round((mainEntityEntity.Camera.y * scale) - RESOLUTION.y / 2);
      if (x !== camera.x || y !== camera.y) {
        setCamera({x, y});
      }
    }
  }, [camera, ecs, mainEntity, scale]);
  useEffect(() => {
    function onContextMenu(event) {
      event.preventDefault();
    }
    document.body.addEventListener('contextmenu', onContextMenu);
    return () => {
      document.body.removeEventListener('contextmenu', onContextMenu);
    };
  }, []);
  return (
    <div
      className={styles.ui}
    >
      <style>
        {`
          @media (max-aspect-ratio: ${ratio}) { .${styles.game} { width: 100%; } }
          @media (min-aspect-ratio: ${ratio}) { .${styles.game} { height: 100%; } }
          .${styles.game} {
            cursor: ${
              bufferSlot
                ? `url('${bufferSlot.icon}'), auto !important`
                : 'auto'
              };
          }
        `}
      </style>
      {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */ }
      <div
        className={[styles.game, devtoolsIsOpen && styles.devtoolsIsOpen].filter(Boolean).join(' ')}
        onMouseDown={(event) => {
          if (chatIsOpen) {
            event.preventDefault();
            return;
          }
          switch (event.button) {
            case 0:
              if (devtoolsIsOpen) {
                if (!gameRef.current || !mainEntity) {
                  return;
                }
                const {top, left, width} = gameRef.current.getBoundingClientRect();
                const master = ecs.get(1);
                if (!master) {
                  return;
                }
                const {Camera} = ecs.get(mainEntity);
                const size = width / RESOLUTION.x;
                const client = {
                  x: (event.clientX - left) / size,
                  y: (event.clientY - top) / size,
                };
                const camera = {
                  x: ((Camera.x * scale) - (RESOLUTION.x / 2)),
                  y: ((Camera.y * scale) - (RESOLUTION.y / 2)),
                }
                devEventsChannel.invoke(
                  'click',
                  {
                    x: (client.x + camera.x) / scale,
                    y: (client.y + camera.y) / scale,
                  },
                );
              }
              else {
                client.send({
                  type: 'Action',
                  payload: {type: 'use', value: 1},
                });
              }
              break;
            case 2:
              if (monopolizers.length > 0) {
                monopolizers[0].trigger();
                break;
              }
              client.send({
                type: 'Action',
                payload: {type: 'interact', value: 1},
              });
              break;
          }
        }}
        onMouseUp={(event) => {
          if (chatIsOpen) {
            event.preventDefault();
            return;
          }
          switch (event.button) {
            case 0:
              client.send({
                type: 'Action',
                payload: {type: 'use', value: 0},
              });
              break;
            case 2:
              client.send({
                type: 'Action',
                payload: {type: 'interact', value: 0},
              });
              break;
          }
          event.preventDefault();
        }}
        onWheel={(event) => {
          if (chatIsOpen) {
            event.preventDefault();
            return;
          }
          if (event.deltaY > 0) {
            client.send({
              type: 'Action',
              payload: {type: 'changeSlot', value: 1 + ((activeSlot + 1) % 10)},
            });
          }
          else {
            client.send({
              type: 'Action',
              payload: {type: 'changeSlot', value: 1 + ((activeSlot + 9) % 10)},
            });
          }
        }}
        ref={gameRef}
      >
        <Pixi
          applyFilters={applyFilters}
          camera={camera}
          monopolizers={monopolizers}
          scale={scale}
        />
        <Dom>
          <HotBar
            active={activeSlot}
            onActivate={(i) => {
              client.send({
                type: 'Action',
                payload: {type: 'swapSlots', value: [0, i + 1]},
              });
            }}
            slots={hotbarSlots}
          />
          <Entities
            camera={camera}
            scale={scale}
            setChatMessages={setChatMessages}
            setMonopolizers={setMonopolizers}
          />
          {chatIsOpen && (
            <Chat
              chatHistory={chatHistory}
              chatHistoryCaret={chatHistoryCaret}
              chatInputRef={chatInputRef}
              chatMessages={chatMessages}
              message={message}
              pendingMessage={pendingMessage}
              setChatHistory={setChatHistory}
              setChatHistoryCaret={setChatHistoryCaret}
              setMessage={setMessage}
              setPendingMessage={setPendingMessage}
              onClose={() => {
                setChatIsOpen(false);
              }}
            />
          )}
          {showDisconnected && (
            <Disconnected />
          )}
        </Dom>
      </div>
      <div className={[styles.devtools, devtoolsIsOpen && styles.devtoolsIsOpen].filter(Boolean).join(' ')}>
        <Devtools
          applyFilters={applyFilters}
          eventsChannel={devEventsChannel}
          setApplyFilters={setApplyFilters}
        />
      </div>
    </div>
  );
}

