import type { BlockSelection } from '@blockeditor/block-std';
import { assertExists } from '@blockeditor/global/utils';
import type { BlockElement } from '@blockeditor/lit';

const getSelection = (blockComponent: BlockElement) =>
  blockComponent.host.selection;

function getBlockSelectionBySide(blockElement: BlockElement, tail: boolean) {
  const selection = getSelection(blockElement);
  const selections = selection.filter('block');
  const sel = selections.at(tail ? -1 : 0) as BlockSelection | undefined;
  return sel ?? null;
}

function getTextSelection(blockElement: BlockElement) {
  const selection = getSelection(blockElement);
  return selection.find('text');
}

const pathToBlock = (blockElement: BlockElement, path: string[]) =>
  blockElement.host.view.viewFromPath('block', path);

interface MoveBlockConfig {
  name: string;
  hotkey: string[];
  action: (blockElement: BlockElement) => void;
}

export const moveBlockConfigs: MoveBlockConfig[] = [
  {
    name: 'Move Up',
    hotkey: ['Mod-Alt-ArrowUp', 'Mod-Shift-ArrowUp'],
    action: blockElement => {
      const page = blockElement.page;
      const textSelection = getTextSelection(blockElement);
      if (textSelection) {
        const currentModel = pathToBlock(blockElement, textSelection.from.path)
          ?.model;
        if (!currentModel) return;

        const previousSiblingModel = page.getPreviousSibling(currentModel);
        if (!previousSiblingModel) return;

        const parentModel = blockElement.page.getParent(previousSiblingModel);
        if (!parentModel) return;

        blockElement.page.moveBlocks(
          [currentModel],
          parentModel,
          previousSiblingModel,
          true
        );
        blockElement.updateComplete
          .then(() => {
            const rangeManager = blockElement.host.rangeManager;
            assertExists(rangeManager);
            rangeManager.syncTextSelectionToRange(textSelection);
          })
          .catch(console.error);
        return true;
      }
      const blockSelection = getBlockSelectionBySide(blockElement, true);
      if (blockSelection) {
        const currentModel = pathToBlock(blockElement, blockSelection.path)
          ?.model;
        if (!currentModel) return;

        const previousSiblingModel = page.getPreviousSibling(currentModel);
        if (!previousSiblingModel) return;

        const parentModel = page.getParent(previousSiblingModel);
        if (!parentModel) return;

        page.moveBlocks(
          [currentModel],
          parentModel,
          previousSiblingModel,
          false
        );
        return true;
      }
      return;
    },
  },
  {
    name: 'Move Down',
    hotkey: ['Mod-Alt-ArrowDown', 'Mod-Shift-ArrowDown'],
    action: blockElement => {
      const page = blockElement.page;
      const textSelection = getTextSelection(blockElement);
      if (textSelection) {
        const currentModel = pathToBlock(blockElement, textSelection.from.path)
          ?.model;
        if (!currentModel) return;

        const nextSiblingModel = page.getNextSibling(currentModel);
        if (!nextSiblingModel) return;

        const parentModel = page.getParent(nextSiblingModel);
        if (!parentModel) return;

        page.moveBlocks([currentModel], parentModel, nextSiblingModel, false);
        blockElement.updateComplete
          .then(() => {
            // `textSelection` will not change so we need wo sync it manually
            const rangeManager = blockElement.host.rangeManager;
            assertExists(rangeManager);
            rangeManager.syncTextSelectionToRange(textSelection);
          })
          .catch(console.error);
        return true;
      }
      const blockSelection = getBlockSelectionBySide(blockElement, true);
      if (blockSelection) {
        const currentModel = pathToBlock(blockElement, blockSelection.path)
          ?.model;
        if (!currentModel) return;

        const nextSiblingModel = page.getNextSibling(currentModel);
        if (!nextSiblingModel) return;

        const parentModel = page.getParent(nextSiblingModel);
        if (!parentModel) return;

        page.moveBlocks([currentModel], parentModel, nextSiblingModel, false);
        return true;
      }
      return;
    },
  },
];
