import { keyframes } from '@emotion/react';
import styled from '@emotion/styled';
import { useCallback } from 'react';

import { useContextSelector } from '@/hooks/useContextSelector';

import DragAndDropContext, {
  DraggedObject,
  DraggedObjectType,
} from './DragAndDropContext';

const Wrapper = styled.div`
  position: relative;
  height: 100%;
`;

const ChildrenWrapper = styled.div`
  position: relative;
  height: 100%;
  z-index: 1;
`;

const fadeIn = keyframes`
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
`;

const OverlayWrapper = styled.div<{ active: boolean }>`
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: ${({ active }) => (active ? 'auto' : 'none')};
  z-index: 2;
  > * {
    opacity: ${({ active }) => (active ? 1 : 0)};
    animation: ${({ active }) => (active ? fadeIn : 'none')} 0.2s ease-in-out
      both;
    transition: opacity 0.3s ease-in-out;
  }
`;

export const DropOverlay = styled.div`
  position: absolute;
  top: 2px;
  left: 2px;
  right: 2px;
  bottom: 2px;
  border-radius: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  color: var(--color-foreground-secondary-glass);
  border: 2px dashed var(--color-foreground-inactive);
`;

export default function Droppable<T extends DraggedObjectType[]>({
  children,
  accept,
  overlay,
  onDrop,
  onDragMove,
  onDragLeave,
}: {
  children: React.ReactNode;
  accept: T;
  overlay: React.ReactNode;
  onDrop: (
    draggedObject: DraggedObject<T[number]>,
    event: React.MouseEvent<HTMLDivElement>
  ) => void;
  onDragMove?: (
    draggedObject: DraggedObject<T[number]>,
    event: React.MouseEvent<HTMLDivElement>
  ) => void;
  onDragLeave?: (
    draggedObject: DraggedObject<T[number]>,
    event: React.MouseEvent<HTMLDivElement>
  ) => void;
}) {
  const draggedObject = useContextSelector(
    DragAndDropContext,
    (ctx) => ctx.draggedObject
  );
  const contextOnDrop = useContextSelector(
    DragAndDropContext,
    (ctx) => ctx.onDrop
  );
  const canDrop = Boolean(draggedObject && accept.includes(draggedObject.type));

  const handleDrop = useCallback(
    (event: React.MouseEvent<HTMLDivElement>) => {
      if (!draggedObject || !accept.includes(draggedObject.type)) return;
      event.preventDefault();
      onDrop(draggedObject as DraggedObject<T[number]>, event);
      contextOnDrop();
    },
    [onDrop, draggedObject, accept, contextOnDrop]
  );

  const handleDragMove = useCallback(
    (event: React.MouseEvent<HTMLDivElement>) => {
      if (!draggedObject || !accept.includes(draggedObject.type)) return;
      event.preventDefault();
      onDragMove?.(draggedObject as DraggedObject<T[number]>, event);
    },
    [onDragMove, draggedObject, accept]
  );

  const handleDragLeave = useCallback(
    (event: React.MouseEvent<HTMLDivElement>) => {
      if (!draggedObject || !accept.includes(draggedObject.type)) return;
      event.preventDefault();
      onDragLeave?.(draggedObject as DraggedObject<T[number]>, event);
    },
    [onDragLeave, draggedObject, accept]
  );

  return (
    <Wrapper>
      <ChildrenWrapper>{children}</ChildrenWrapper>
      <OverlayWrapper
        active={canDrop}
        onMouseUp={handleDrop}
        onMouseMove={handleDragMove}
        onMouseLeave={handleDragLeave}
        onDrop={handleDrop}
        onDragOver={handleDragMove}
        onDragLeave={handleDragLeave}
      >
        {overlay}
      </OverlayWrapper>
    </Wrapper>
  );
}
