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

import type { Point, Rect } from '../../../_common/utils/index.js';
import type { DragPreview } from './components/drag-preview.js';

export const DRAG_HANDLE_CONTAINER_HEIGHT = 24;
export const DRAG_HANDLE_CONTAINER_WIDTH = 16;
export const DRAG_HANDLE_CONTAINER_WIDTH_TOP_LEVEL = 8;
export const DRAG_HANDLE_CONTAINER_OFFSET_LEFT = 2;
export const DRAG_HANDLE_CONTAINER_OFFSET_LEFT_LIST = 18;
export const DRAG_HANDLE_CONTAINER_OFFSET_LEFT_TOP_LEVEL = 5;
export const DRAG_HANDLE_CONTAINER_PADDING = 8;

export const DRAG_HANDLE_GRABBER_HEIGHT = 12;
export const DRAG_HANDLE_GRABBER_WIDTH = 4;
export const DRAG_HANDLE_GRABBER_WIDTH_HOVERED = 2;
export const DRAG_HANDLE_GRABBER_BORDER_RADIUS = 4;
export const DRAG_HANDLE_GRABBER_MARGIN = 4;

export const HOVER_AREA_RECT_PADDING_TOP_LEVEL = 6;

export const NOTE_CONTAINER_PADDING = 24;
export const DRAG_HOVER_RECT_PADDING = 4;

export const BLOCK_CHILDREN_CONTAINER_PADDING_LEFT = 26;

export type DropType = 'before' | 'after' | 'in';
export type DropResult = {
  rect: Rect | null;
  dropBlockId: string;
  dropType: DropType;
};

export type OnDragStartProps = {
  state: PointerEventState;
  startDragging: (
    blockElements: BlockElement[],
    state: PointerEventState,
    dragPreview?: HTMLElement,
    dragPreviewOffset?: Point
  ) => void;
  anchorBlockId: string;
  anchorBlockPath: string[] | null;
};

export type OnDragEndProps = {
  state: PointerEventState;
  draggingElements: BlockElement[];
  dropBlockId: string;
  dropType: DropType | null;
  dragPreview: DragPreview;
};

export type DragHandleOption = {
  flavour: string | RegExp;
  edgeless?: boolean;
  onDragStart?: (props: OnDragStartProps) => boolean;
  onDragMove?: (
    state: PointerEventState,
    draggingElements?: BlockElement[]
  ) => boolean;
  onDragEnd?: (props: OnDragEndProps) => boolean;
};

export class DragHandleOptionsRunner {
  private optionMap: Map<DragHandleOption, number> = new Map();

  get options(): DragHandleOption[] {
    return Array.from(this.optionMap.keys());
  }

  getOption(flavour: string): DragHandleOption | undefined {
    return this.options.find(option => {
      if (typeof option.flavour === 'string') {
        return option.flavour === flavour;
      } else {
        return option.flavour.test(flavour);
      }
    });
  }

  register(option: DragHandleOption): Disposable {
    const currentOption =
      this._getExistingOptionWithSameFlavour(option) || option;
    const count = this.optionMap.get(currentOption) || 0;
    this.optionMap.set(currentOption, count + 1);

    return {
      dispose: () => {
        this._decreaseOptionCount(currentOption);
      },
    };
  }

  private _getExistingOptionWithSameFlavour(
    option: DragHandleOption
  ): DragHandleOption | undefined {
    return Array.from(this.optionMap.keys()).find(
      op => op.flavour === option.flavour
    );
  }

  private _decreaseOptionCount(option: DragHandleOption) {
    const count = this.optionMap.get(option) || 0;
    if (count > 1) {
      this.optionMap.set(option, count - 1);
    } else {
      this.optionMap.delete(option);
    }
  }
}
