import { useCommander } from './../plugin/command.plugin';
import { ReactVisualEditorBlock, ReactVisualEditorValue } from './ReactVisualEditor.utils';
import deepcopy from 'deepcopy';
import { useRef } from 'react';
import { useCallbackRef } from './../hook/useCallbackRef';

export function useVisualCommand({
  focusData,
  value,
  updateBlocks,
  updateValue,
  dragstart,
  dragend,
}: {
  focusData: {
    focus: ReactVisualEditorBlock[];
    unFocus: ReactVisualEditorBlock[];
  };
  value: ReactVisualEditorValue;
  updateBlocks: (blocks: ReactVisualEditorBlock[]) => void;
  updateValue: (value: ReactVisualEditorValue) => void;
  dragstart: { on: (cb: () => void) => void; off: (cb: () => void) => void };
  dragend: { on: (cb: () => void) => void; off: (cb: () => void) => void };
}) {
  const commander = useCommander();

  /*删除命令*/
  commander.useRegistry({
    name: 'delete',
    keyboard: ['delete', 'ctrl+d', 'backspace'],
    execute() {
      const before = deepcopy(value.blocks);
      const after = deepcopy(focusData.unFocus);
      return {
        redo: () => {
          updateBlocks(deepcopy(after));
        },
        undo: () => {
          updateBlocks(deepcopy(before));
        },
      };
    },
  });

  /**
   * 拖拽命令
   */
  (() => {
    const dragData = useRef({ before: null as null | ReactVisualEditorBlock[] });
    const handler = {
      dragstart: useCallbackRef(() => (dragData.current.before = deepcopy(value.blocks))),
      dragend: useCallbackRef(() => commander.state.commands.drag()),
    };
    /**
     * 拖拽命令，适用于三种情况：
     * - 从菜单拖拽组件到容器画布；
     * - 在容器中拖拽组件调整位置
     * - 拖拽调整组件的宽度和高度；
     */
    commander.useRegistry({
      name: 'drag',
      init() {
        dragData.current = { before: null };
        dragstart.on(handler.dragstart);
        dragend.on(handler.dragend);
        return () => {
          dragstart.off(handler.dragstart);
          dragend.off(handler.dragend);
        };
      },
      execute() {
        let before = deepcopy(dragData.current.before!);
        let after = deepcopy(value.blocks);
        return {
          redo: () => {
            updateBlocks(deepcopy(after));
          },
          undo: () => {
            updateBlocks(deepcopy(before));
          },
        };
      },
    });
  })();

  /*全选命令*/
  commander.useRegistry({
    name: 'selectAll',
    followQueue: false,
    keyboard: ['ctrl+a'],
    execute() {
      return {
        redo: () => {
          value.blocks.forEach((block) => (block.focus = true));
          updateBlocks(value.blocks);
        },
      };
    },
  });

  /*置顶命令*/
  commander.useRegistry({
    name: 'placeTop',
    keyboard: 'ctrl+up',
    execute() {
      const before = deepcopy(value.blocks);
      const after = deepcopy(
        (() => {
          const { focus, unFocus } = focusData;
          const maxUnFocusIndex = unFocus.reduce(
            (prev, item) => Math.max(prev, item.zIndex),
            -Infinity,
          );
          const minFocusIndex = focus.reduce((prev, item) => Math.min(prev, item.zIndex), Infinity);
          let dur = maxUnFocusIndex - minFocusIndex;
          if (dur >= 0) {
            dur++;
            focus.forEach((block) => (block.zIndex = block.zIndex + dur));
          }
          return value.blocks;
        })(),
      );
      return {
        redo: () => updateBlocks(after),
        undo: () => updateBlocks(before),
      };
    },
  });

  /*置底命令*/
  commander.useRegistry({
    name: 'placeBottom',
    keyboard: 'ctrl+down',
    execute() {
      const before = deepcopy(value.blocks);
      const after = deepcopy(
        (() => {
          const { focus, unFocus } = focusData;
          const minUnFocusIndex = unFocus.reduce(
            (prev, item) => Math.min(prev, item.zIndex),
            Infinity,
          );
          const maxFocusIndex = focus.reduce(
            (prev, item) => Math.max(prev, item.zIndex),
            -Infinity,
          );
          const minFocusIndex = focus.reduce((prev, item) => Math.min(prev, item.zIndex), Infinity);

          let dur = maxFocusIndex - minUnFocusIndex;
          if (dur >= 0) {
            dur++;
            focus.forEach((block) => (block.zIndex = block.zIndex - dur));
            if (minFocusIndex - dur < 0) {
              dur = dur - minFocusIndex;
              value.blocks.forEach((block) => (block.zIndex = block.zIndex + dur));
            }
          }
          return value.blocks;
        })(),
      );
      return {
        redo: () => updateBlocks(after),
        undo: () => updateBlocks(before),
      };
    },
  });

  /*清空命令*/
  commander.useRegistry({
    name: 'clear',
    execute: () => {
      const before = deepcopy(value.blocks);
      const after = deepcopy([]);
      return {
        redo: () => updateBlocks(after),
        undo: () => updateBlocks(before),
      };
    },
  });

  /**
   * 更新编辑数据的命令
   */
  commander.useRegistry({
    name: 'updateValue',
    execute: (newVal: ReactVisualEditorValue) => {
      const before = deepcopy(value);
      const after = deepcopy(newVal);
      return {
        redo: () => updateValue(after),
        undo: () => updateValue(before),
      };
    },
  });

  /**
   * 更新某个节点
   */
  commander.useRegistry({
    name: 'updateBlock',
    execute: (newBlock: ReactVisualEditorBlock, oldBlock: ReactVisualEditorBlock) => {
      const before = deepcopy(value);
      value.blocks.splice(value.blocks.indexOf(oldBlock), 1, newBlock);
      const after = deepcopy(value);
      return {
        redo: () => updateValue(after),
        undo: () => updateValue(before),
      };
    },
  });

  commander.useInit();

  return {
    delete: () => commander.state.commands.delete(),
    undo: () => commander.state.commands.undo(),
    redo: () => commander.state.commands.redo(),
    placeTop: () => commander.state.commands.placeTop(),
    placeBottom: () => commander.state.commands.placeBottom(),
    clear: () => commander.state.commands.clear(),
    updateValue: (val: ReactVisualEditorValue) => commander.state.commands.updateValue(val),
    updateBlock: (newBlock: ReactVisualEditorBlock, oldBlock: ReactVisualEditorBlock) =>
      commander.state.commands.updateBlock(newBlock, oldBlock),
  };
}
