import { type FrameBlockModel, on, once } from '@blocksuite/blocks';

import type { FramePanelBody } from '../body/frame-panel-body.js';

import { FrameCard } from '../card/frame-card.js';

/**
 * start drag frame cards
 * @param frames frames to drag
 */
export function startDragging(
  frames: {
    frame: FrameBlockModel;
    element: FrameCard;
    cardIndex: number;
    frameIndex: string;
  }[],
  options: {
    width: number;
    onDragEnd?: (insertIndex?: number) => void;
    onDragMove?: (insertIdx?: number, indicatorTranslateY?: number) => void;
    framePanelBody: HTMLElement;
    frameListContainer: HTMLElement;
    frameElementHeight: number;
    document: Document;
    domHost: Document | HTMLElement;
    container: FramePanelBody;
    start: {
      x: number;
      y: number;
    };
  }
) {
  const {
    document,
    domHost,
    container,
    onDragMove,
    onDragEnd,
    frameElementHeight,
    framePanelBody,
    frameListContainer,
    start,
  } = options;
  const cardElements = frames
    .slice(frames.length - 2, frames.length)
    .map((frame, idx, arr) => {
      const el = new FrameCard();

      el.frame = frame.frame;

      el.cardIndex = frame.cardIndex;
      el.frameIndex = frame.frameIndex;
      el.status = 'dragging';
      el.stackOrder = arr.length - 1 - idx;
      el.pos = start;
      el.width = options.width;
      if (frames.length > 1 && el.stackOrder === 0)
        el.draggingCardNumber = frames.length;

      return el;
    });
  const maskElement = createMaskElement(document);
  const listContainerRect = framePanelBody.getBoundingClientRect();
  const children = Array.from(frameListContainer.children) as FrameCard[];
  const computedStyle = getComputedStyle(frameListContainer);
  const frameListContainerGap =
    parseInt(computedStyle.getPropertyValue('gap')) ?? 16;
  let idx: undefined | number;
  let indicatorTranslateY: undefined | number;

  container.renderRoot.append(maskElement);
  container.renderRoot.append(...cardElements);

  const insideListContainer = (e: MouseEvent) => {
    return (
      e.clientX >= listContainerRect.left &&
      e.clientX <= listContainerRect.right &&
      e.clientY >= listContainerRect.top &&
      e.clientY <= listContainerRect.bottom
    );
  };

  const disposeMove = on(container, 'mousemove', e => {
    cardElements.forEach(el => {
      el.pos = {
        x: e.clientX,
        y: e.clientY,
      };
    });

    if (!insideListContainer(e)) {
      idx = undefined;
      onDragMove?.(idx, 0);
      return;
    }

    idx = 0;
    for (const card of children) {
      if (!card.frame) break;

      const topBoundary =
        listContainerRect.top +
        card.offsetTop -
        framePanelBody.scrollTop -
        frameListContainerGap / 2;
      const midBoundary = topBoundary + card.offsetHeight / 2;
      const bottomBoundary =
        topBoundary + card.offsetHeight + frameListContainerGap;

      if (e.clientY >= topBoundary && e.clientY <= bottomBoundary) {
        idx = e.clientY > midBoundary ? idx + 1 : idx;

        indicatorTranslateY =
          idx * (frameElementHeight + frameListContainerGap) -
          frameListContainerGap / 2;

        onDragMove?.(idx, indicatorTranslateY);
        return;
      }

      ++idx;
    }

    onDragMove?.(idx);
  });

  let ended = false;
  const dragEnd = () => {
    if (ended) return;

    ended = true;
    cardElements.forEach(child => child.remove());
    maskElement.remove();

    disposeMove();
    onDragEnd?.(idx);
  };

  once(domHost as Document, 'mouseup', dragEnd);
}

function createMaskElement(doc: Document) {
  const mask = doc.createElement('div');

  mask.style.height = '100vh';
  mask.style.width = '100vw';
  mask.style.position = 'fixed';
  mask.style.left = '0';
  mask.style.top = '0';
  mask.style.zIndex = 'calc(var(--affine-z-index-popover, 0) + 3)';
  mask.style.cursor = 'grabbing';

  return mask;
}
