import React, { Dispatch, SetStateAction, useEffect, useRef } from 'react';
import './Editor.scss';
import { IConfiguration } from '../../Interfaces/IConfiguration';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { UI } from '../UI/UI';
import { SelectContainer, DeleteContainer, OnPropertyChange, ReplaceByContainer } from './Actions/ContainerOperations';
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
import { OnKey } from './Actions/Shortcuts';
import { UseCustomEvents, UseEditorListener } from '../../Events/EditorEvents';
import { MAX_HISTORY } from '../../utils/default';
import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations';
import { FindContainerById } from '../../utils/itertools';
import { Menu } from '../Menu/Menu';
import { InitActions } from './Actions/ContextMenuActions';
import { AddContainerToSelectedContainer, AddContainer } from './Actions/AddContainer';
import { IReplaceContainer } from '../../Interfaces/IReplaceContainer';

interface IEditorProps {
  root: Element | Document
  configuration: IConfiguration
  history: IHistoryState[]
  historyCurrentStep: number
}

function UseShortcuts(
  history: IHistoryState[],
  historyCurrentStep: number,
  setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
  deleteAction: () => void
): void {
  useEffect(() => {
    function OnKeyUp(event: KeyboardEvent): void {
      return OnKey(
        event,
        history,
        historyCurrentStep,
        setHistoryCurrentStep,
        deleteAction
      );
    }

    window.addEventListener('keyup', OnKeyUp);
    return () => {
      window.removeEventListener('keyup', OnKeyUp);
    };
  });
}

/**
 * Return a macro function to use both setHistory
 * and setHistoryCurrentStep at the same time
 * @param setHistory
 * @param setHistoryCurrentStep
 * @returns
 */
function UseNewHistoryState(
  setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
  setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): (newHistory: IHistoryState[], historyCurrentStep?: number) => void {
  return (newHistory, historyCurrentStep?: number) => {
    setHistory(newHistory);
    setHistoryCurrentStep(historyCurrentStep !== undefined && historyCurrentStep !== null ? historyCurrentStep : newHistory.length - 1);
  };
}

export function Editor(props: IEditorProps): JSX.Element {
  // States
  const [history, setHistory] = React.useState<IHistoryState[]>(structuredClone(props.history));
  const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
  const [replaceContainer, setReplaceContainer] = React.useState<IReplaceContainer>({ isReplacing: false, id: undefined, category: undefined });

  const editorRef = useRef<HTMLDivElement>(null);
  const setNewHistory = UseNewHistoryState(setHistory, setHistoryCurrentStep);

  // Events
  UseShortcuts(
    history,
    historyCurrentStep,
    setHistoryCurrentStep,
    () => {
      const current = GetCurrentHistoryState(history, historyCurrentStep);
      setNewHistory(
        DeleteContainer(current.selectedContainerId, history, historyCurrentStep)
      );
    }
  );
  UseCustomEvents(
    props.root,
    history,
    historyCurrentStep,
    props.configuration,
    editorRef,
    setNewHistory
  );
  UseEditorListener(
    props.root,
    history,
    historyCurrentStep,
    props.configuration
  );

  // Context Menu
  const menuActions = new Map();
  InitActions(
    menuActions,
    props.configuration,
    history,
    historyCurrentStep,
    setNewHistory,
    setHistoryCurrentStep,
    setReplaceContainer
  );

  // Render
  const configuration = props.configuration;
  const current = GetCurrentHistoryState(history, historyCurrentStep);
  const selected = FindContainerById(current.containers, current.selectedContainerId);

  return (
    <div ref={editorRef} className="Editor font-sans h-full ">
      <UI
        editorState={{
          configuration: props.configuration,
          history,
          historyCurrentStep
        }}
        replaceContainer={replaceContainer}
        selectContainer={(container) => setNewHistory(
          SelectContainer(
            container,
            history,
            historyCurrentStep
          ))}
        deleteContainer={(containerId: string) => setNewHistory(
          DeleteContainer(
            containerId,
            history,
            historyCurrentStep
          ))}
        onPropertyChange={(key, value, type) => setNewHistory(
          OnPropertyChange(
            key, value, type,
            selected,
            history,
            historyCurrentStep
          ))}
        addOrReplaceContainer={(type) => {
          if (selected === null || selected === undefined) {
            return;
          }
          if (replaceContainer.isReplacing && replaceContainer.id !== undefined) {
            const newHistory = ReplaceByContainer(
              replaceContainer.id,
              type,
              configuration,
              history,
              historyCurrentStep
            );
            setReplaceContainer({ isReplacing: false, id: undefined, category: undefined });
            setNewHistory(newHistory);
          } else {
            setNewHistory(AddContainerToSelectedContainer(
              type,
              selected,
              configuration,
              history,
              historyCurrentStep
            ));
          }
        }}
        addContainerAt={(index, type, parent) => setNewHistory(
          AddContainer(
            index,
            type,
            parent,
            configuration,
            history,
            historyCurrentStep
          )
        )}
        addSymbol={(type) => setNewHistory(
          AddSymbol(
            type,
            configuration,
            history,
            historyCurrentStep
          ))}
        onSymbolPropertyChange={(key, value) => setNewHistory(
          OnSymbolPropertyChange(
            key, value,
            history,
            historyCurrentStep
          ))}
        selectSymbol={(symbolId) => setNewHistory(
          SelectSymbol(
            symbolId,
            history,
            historyCurrentStep
          ))}
        deleteSymbol={(symbolId) => setNewHistory(
          DeleteSymbol(
            symbolId,
            history,
            historyCurrentStep
          ))}
        saveEditorAsJSON={() => SaveEditorAsJSON(
          history,
          historyCurrentStep,
          configuration
        )}
        saveEditorAsSVG={() => SaveEditorAsSVG()}
        loadState={(move) => setHistoryCurrentStep(move)}
        setReplaceContainer={setReplaceContainer}
      />
      <Menu
        getListener={() => editorRef.current}
        configuration={configuration}
        actions={menuActions}
        className="z-30 transition-opacity rounded bg-slate-200 drop-shadow-xl"
      />
    </div>
  );
}

export function UpdateCounters(counters: Record<string, number>, type: string): void {
  if (counters[type] === null ||
    counters[type] === undefined) {
    counters[type] = 0;
  } else {
    counters[type]++;
  }
}

export function GetCurrentHistory(history: IHistoryState[], historyCurrentStep: number): IHistoryState[] {
  return history.slice(
    Math.max(0, history.length - MAX_HISTORY),
    historyCurrentStep + 1
  );
}

export function GetCurrentHistoryState(history: IHistoryState[], historyCurrentStep: number): IHistoryState {
  return history[historyCurrentStep];
}
