import React, { Dispatch, SetStateAction, useEffect, useRef } from 'react';
import './Editor.scss';
import { IConfiguration } from '../../Interfaces/IConfiguration';
import { SVG } from '../SVG/SVG';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { UI } from '../UI/UI';
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, OnPropertyChange, AddContainers } from './Actions/ContainerOperations';
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
import { OnKey } from './Actions/Shortcuts';
import { events as EVENTS } from '../../Events/EditorEvents';
import { IEditorState } from '../../Interfaces/IEditorState';
import { MAX_HISTORY } from '../../utils/default';
import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations';
import { FindContainerById } from '../../utils/itertools';
import { IMenuAction, Menu } from '../Menu/Menu';
import { GetAction } from './Actions/ContextMenuActions';

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

function InitActions(
  menuActions: Map<string, IMenuAction[]>,
  configuration: IConfiguration,
  history: IHistoryState[],
  historyCurrentStep: number,
  setNewHistory: (newHistory: IHistoryState[]) => void
): void {
  menuActions.set(
    'elements-sidebar-row',
    [{
      text: 'Delete',
      action: (target: HTMLElement) => {
        const id = target.id;
        const newHistory = DeleteContainer(
          id,
          history,
          historyCurrentStep
        );
        setNewHistory(newHistory);
      }
    }]
  );
  menuActions.set(
    'symbols-sidebar-row',
    [{
      text: 'Delete',
      action: (target: HTMLElement) => {
        const id = target.id;
        const newHistory = DeleteSymbol(
          id,
          history,
          historyCurrentStep
        );
        setNewHistory(newHistory);
      }
    }]
  );

  // API Actions
  for (const availableContainer of configuration.AvailableContainers) {
    if (availableContainer.Actions === undefined || availableContainer.Actions === null) {
      continue;
    }

    for (const action of availableContainer.Actions) {
      if (menuActions.get(availableContainer.Type) === undefined) {
        menuActions.set(availableContainer.Type, []);
      }

      const currentState = GetCurrentHistoryState(history, historyCurrentStep);
      const newAction: IMenuAction = {
        text: action.Label,
        action: GetAction(
          action,
          currentState,
          configuration,
          history,
          historyCurrentStep,
          setNewHistory
        )
      };

      menuActions.get(availableContainer.Type)?.push(newAction);
    }
  }
}

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

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

function UseWindowEvents(
  root: Element | Document,
  history: IHistoryState[],
  historyCurrentStep: number,
  configuration: IConfiguration,
  editorRef: React.RefObject<HTMLDivElement>,
  setNewHistory: (newHistory: IHistoryState[]) => void
): void {
  useEffect(() => {
    const editorState: IEditorState = {
      history,
      historyCurrentStep,
      configuration
    };

    const funcs = new Map<string, () => void>();
    for (const event of EVENTS) {
      function Func(eventInitDict?: CustomEventInit): void {
        return event.func(
          root,
          editorState,
          setNewHistory,
          eventInitDict
        );
      }
      editorRef.current?.addEventListener(event.name, Func);
      funcs.set(event.name, Func);
    }
    return () => {
      for (const event of EVENTS) {
        const func = funcs.get(event.name);
        if (func === undefined) {
          continue;
        }
        editorRef.current?.removeEventListener(event.name, func);
      }
    };
  });
}

/**
 * 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[]) => void {
  return (newHistory) => {
    setHistory(newHistory);
    setHistoryCurrentStep(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 editorRef = useRef<HTMLDivElement>(null);
  const setNewHistory = UseNewHistoryState(setHistory, setHistoryCurrentStep);

  // Events
  UseShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
  UseWindowEvents(
    props.root,
    history,
    historyCurrentStep,
    props.configuration,
    editorRef,
    setNewHistory
  );

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

  // Render
  const configuration = props.configuration;
  const current = GetCurrentHistoryState(history, historyCurrentStep);
  const selected = FindContainerById(current.mainContainer, current.selectedContainerId);
  return (
    <div ref={editorRef} className="Editor font-sans h-full">
      <UI
        selectedContainer={selected}
        current={current}
        history={history}
        historyCurrentStep={historyCurrentStep}
        availableContainers={configuration.AvailableContainers}
        availableSymbols={configuration.AvailableSymbols}
        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
          ))}
        addContainer={(type) => {
          const newHistory = AddContainerToSelectedContainer(
            type,
            selected,
            configuration,
            history,
            historyCurrentStep
          );
          if (newHistory !== null) {
            setNewHistory(newHistory);
          }
        }}
        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)} />
      <SVG
        width={current.mainContainer?.properties.width}
        height={current.mainContainer?.properties.height}
        selected={selected}
        symbols={current.symbols}
      >
        {current.mainContainer}
      </SVG>
      <Menu
        getListener={() => editorRef.current}
        actions={menuActions}
        className="z-30 transition-opacity rounded bg-slate-200 py-1 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];
}
