import {
  IEquipmentSet,
  IItem,
  IItemContainer,
  isMobile,
  ItemContainerType,
  ItemSlotType,
  ItemSubType,
  ItemType
} from '@rpg-engine/shared';

import { observer } from 'mobx-react-lite';
import React, { useCallback, useEffect, useRef } from 'react';
import Draggable, { DraggableEventHandler } from 'react-draggable';
import styled from 'styled-components';
import useTouchTarget from '../../../hooks/useTouchTarget';
import { IPosition } from '../../../types/eventTypes';
import { useItemSlotDetails } from './context/ItemSlotDetailsContext';
import { useItemSlotDragging } from './context/ItemSlotDraggingContext';
import { qualityColorHex } from './ItemSlotQuality';
import { rarityColor } from './ItemSlotRarity';
import { ItemSlotRenderer } from './ItemSlotRenderer';

export const EquipmentSlotSpriteByType: any = {
  Neck: 'accessories/corruption-necklace.png',
  LeftHand: 'swords/broad-sword.png',
  Ring: 'rings/iron-ring.png',
  Head: 'helmets/viking-helmet.png',
  Torso: 'armors/iron-armor.png',
  Legs: 'legs/studded-legs.png',
  Feet: 'boots/iron-boots.png',
  Inventory: 'containers/bag.png',
  RightHand: 'shields/plate-shield.png',
  Accessory: 'ranged-weapons/arrow.png',
};

interface IProps {
  slotIndex: number;
  item: IItem | null;
  itemContainer?: IItemContainer | null;
  itemContainerType?: ItemContainerType | null;
  slotSpriteMask?: ItemSlotType | null;
  onMouseOver?: (
    event: any,
    slotIndex: number,
    item: IItem | null,
    x: number,
    y: number
  ) => void;
  onMouseOut?: () => void;
  onPointerDown: (
    ItemType: ItemType,
    itemContainerType: ItemContainerType | null,
    item: IItem
  ) => void;
  onDragStart?: (
    item: IItem,
    slotIndex: number,
    itemContainerType: ItemContainerType | null
  ) => void;
  onDragEnd?: (quantity?: number) => void;
  onOutsideDrop?: (item: IItem, position: IPosition) => void;
  dragScale?: number;
  checkIfItemCanBeMoved?: () => boolean;
  checkIfItemShouldDragEnd?: () => boolean;
  openQuantitySelector?: (maxQuantity: number, callback: () => void) => void;
  onPlaceDrop?: (
    item: IItem | null,
    slotIndex: number,
    itemContainerType: ItemContainerType | null
  ) => void;
  atlasJSON: any;
  atlasIMG: any;
  isContextMenuDisabled?: boolean;
  isSelectingShortcut?: boolean;
  equipmentSet?: IEquipmentSet | null;
  setItemShortcut?: (item: IItem, shortcutIndex: number) => void;
  isDepotSystem?: boolean;
}

interface IProps {
  slotIndex: number;
  item: IItem | null;
  itemContainer?: IItemContainer | null;
  itemContainerType?: ItemContainerType | null;
  slotSpriteMask?: ItemSlotType | null;
  onMouseOver?: (
    event: any,
    slotIndex: number,
    item: IItem | null,
    x: number,
    y: number
  ) => void;
  onMouseOut?: () => void;
  onPointerDown: (
    ItemType: ItemType,
    itemContainerType: ItemContainerType | null,
    item: IItem
  ) => void;
  onDragStart?: (
    item: IItem,
    slotIndex: number,
    itemContainerType: ItemContainerType | null
  ) => void;
  onDragEnd?: (quantity?: number) => void;
  onOutsideDrop?: (item: IItem, position: IPosition) => void;
  dragScale?: number;
  checkIfItemCanBeMoved?: () => boolean;
  checkIfItemShouldDragEnd?: () => boolean;
  openQuantitySelector?: (maxQuantity: number, callback: () => void) => void;
  onPlaceDrop?: (
    item: IItem | null,
    slotIndex: number,
    itemContainerType: ItemContainerType | null
  ) => void;
  atlasJSON: any;
  atlasIMG: any;
  isContextMenuDisabled?: boolean;
  isSelectingShortcut?: boolean;
  equipmentSet?: IEquipmentSet | null;
  setItemShortcut?: (item: IItem, shortcutIndex: number) => void;
  isDepotSystem?: boolean;
}

export const ItemSlot: React.FC<IProps> = observer(
  ({
    slotIndex,
    item,
    itemContainerType: containerType,
    slotSpriteMask,
    onMouseOver,
    onMouseOut,
    onPointerDown,
    atlasJSON,
    atlasIMG,
    isContextMenuDisabled = false,
    onDragEnd,
    onDragStart,
    onPlaceDrop,
    onOutsideDrop: onDrop,
    checkIfItemCanBeMoved,
    openQuantitySelector,
    checkIfItemShouldDragEnd,
    dragScale,
    isSelectingShortcut,

    setItemShortcut,
    isDepotSystem,
  }) => {
    const {
      detailsState,
      updateDetailsState,
      setContextActions,
    } = useItemSlotDetails();

    const getTouchTarget = useTouchTarget();

    const {
      isContextMenuVisible,
      clearDetailsState,
      clearContextActions,
    } = detailsState;

    const dragContainer = useRef<HTMLDivElement>(null);

    const {
      draggingState,
      updateDraggingState,
      clearDraggingState,
    } = useItemSlotDragging();

    const { isFocused, isDragging, draggingDistance } = draggingState;

    useEffect(() => {
      // Reset drag position and focus when item changes
      updateDraggingState({
        position: { x: 0, y: 0 },
        isFocused: false,
      });

      clearDraggingState();

      // Clear context actions when component unmounts or dependencies change
      return () => {
        clearDraggingState();
        clearDetailsState();
        clearContextActions();
      };
    }, [
      containerType,
      isDepotSystem,
      setContextActions,
      clearContextActions,
      isContextMenuDisabled,
      clearDraggingState,
      clearDetailsState,
      updateDraggingState,
    ]);

    const resetItem = () => {
      clearDraggingState();
      clearDetailsState();
    };

    const onSuccessfulDrag = (quantity?: number) => {
      resetItem();

      if (quantity === -1) {
        updateDraggingState({
          position: { x: 0, y: 0 },
          isFocused: false,
        });
      } else if (item) {
        onDragEnd?.(quantity);
      }
    };

    const refreshContextActions = () => {
      setContextActions(
        item,
        containerType as ItemContainerType,
        isDepotSystem ?? false,
        isContextMenuDisabled
      );
    };

    const onDraggableStop: DraggableEventHandler = (e, data) => {
      requestAnimationFrame(() => {
        updateDraggingState({
          item: null,
        });

        let target = e.target as HTMLElement;

        if (isMobile()) {
          target = getTouchTarget(e as MouseEvent);
        }

        handleShortcutSetter(target);
        removeDraggingClass(target);

        const shouldHandleDraggedItemResult = shouldHandleDraggedItem();

        if (shouldHandleDraggedItemResult) {
          handleDraggedItem(e, data);
        } else if (item && !isDragging) {
          handleContextMenuOrTooltip(e as React.MouseEvent);
        }
      });
    };

    /**
     * Handles the shortcut setter logic if the target element is a shortcut setter.
     */
    const handleShortcutSetter = (target: HTMLElement) => {
      if (target?.id.includes('shortcutSetter') && setItemShortcut && item) {
        const index = parseInt(target.id.split('_')[1], 10);
        if (!isNaN(index)) {
          setItemShortcut(item, index);
        }
      }
    };

    /**
     * Removes the dragging CSS class from the target element.
     */
    const removeDraggingClass = (target: HTMLElement) => {
      target.classList.remove('react-draggable-dragging');
    };

    /**
     * Determines whether the dragged item should be processed.
     */
    const shouldHandleDraggedItem = (): boolean => {
      return !!(isDragging && draggingState.item && !isSelectingShortcut);
    };

    /**
     * Handles the logic when an item has been dragged.
     */
    const handleDraggedItem = useCallback(
      (e: any, data: any) => {
        const targetClasses = Array.from((e.target as HTMLElement).classList);
        const isOutsideDrop =
          targetClasses.some(elm => elm.includes('rpgui-content')) ||
          targetClasses.length === 0;

        if (isOutsideDrop && draggingState.isDragging && draggingState.item) {
          console.log('ITEM SLOT: Outside drop', draggingState.item.key);
          onDrop?.(draggingState.item, { x: data.x, y: data.y });
          clearDraggingState();
        } else {
          updateDraggingState({
            dropPosition: {
              x: data.x,
              y: data.y,
            },
            isDragging: false,
          });

          const targetElement = dragContainer.current;
          if (!targetElement) return;

          const { x, y } = getElementTransform(targetElement);

          updateDraggingState({
            position: { x, y },
          });

          processDragEnd(item!);
        }
      },
      [
        draggingState.isDragging,
        draggingState.item,
        onDrop,
        clearDraggingState,
        updateDraggingState,
      ]
    );

    /**
     * Retrieves the current transform position of the dragged element.
     */
    const getElementTransform = (
      element: HTMLElement
    ): { x: number; y: number } => {
      const style = window.getComputedStyle(element);
      const matrix = new DOMMatrixReadOnly(style.transform);
      return { x: matrix.m41, y: matrix.m42 };
    };

    const processDragEnd = useCallback(
      (item: IItem) => {
        if (checkIfItemCanBeMoved?.()) {
          if (checkIfItemShouldDragEnd && !checkIfItemShouldDragEnd()) {
            resetItem();
            return;
          }

          if (item.stackQty && item.stackQty !== 1 && openQuantitySelector) {
            openQuantitySelector(item.stackQty, onSuccessfulDrag);
          } else {
            onSuccessfulDrag(item.stackQty);
          }
        } else {
          resetItem();
        }
      },
      [
        checkIfItemCanBeMoved,
        checkIfItemShouldDragEnd,
        openQuantitySelector,
        onSuccessfulDrag,
        resetItem,
      ]
    );

    /**
     * Handles the context menu or tooltip display after dragging stops without a drop.
     */
    const handleContextMenuOrTooltip = (
      e: React.MouseEvent | React.TouchEvent
    ) => {
      const isTouchEvent = e.type === 'touchend';
      const shouldShowMobileTooltip =
        !isContextMenuDisabled && isTouchEvent && !isSelectingShortcut;
      const shouldToggleContextMenu =
        !isContextMenuDisabled && !isSelectingShortcut && !isTouchEvent;

      if (shouldShowMobileTooltip) {
        updateDetailsState({
          item,
          isTooltipMobileVisible: true,
          isContextMenuVisible: false, // Ensure context menu is hidden
        });
      } else if (shouldToggleContextMenu) {
        const mouseEvent = e as React.MouseEvent;
        updateDetailsState({
          item,
          isContextMenuVisible: !isContextMenuVisible && !isMobile(),
          isTooltipMobileVisible: false, // Ensure mobile tooltip is hidden
          contextMenuPosition: {
            x: mouseEvent.clientX - 10,
            y: mouseEvent.clientY - 5,
          },
        });
      }

      if (item) {
        onPointerDown(item.type, containerType ?? null, item);
      }
    };

    const onDraggableStart: DraggableEventHandler = () => {
      if (!item || isSelectingShortcut) {
        return;
      }

      refreshContextActions();

      if (onDragStart && containerType) {
        onDragStart(item, slotIndex, containerType);
      }
    };

    const onDraggableProgress: DraggableEventHandler = (_e, _data) => {
      // increment draggingDistance by 1
      updateDraggingState({
        draggingDistance: draggingState.draggingDistance + 1,
      });

      if (draggingDistance > 10) {
        updateDraggingState({
          isDragging: true,
          isFocused: true,
        });
      }

      if (!draggingState.item) {
        updateDraggingState({
          item: item,
        });
      }
    };

    return (
      <Container
        isDraggingItem={!!draggingState.item}
        item={item}
        className="rpgui-icon empty-slot"
        onMouseUp={() => {
          const data = item ? item : null;
          if (onPlaceDrop && containerType)
            onPlaceDrop(data, slotIndex, containerType);
        }}
        onTouchEnd={e => {
          const { clientX, clientY } = e.changedTouches[0];
          const simulatedEvent = new MouseEvent('mouseup', {
            clientX,
            clientY,
            bubbles: true,
          });

          document
            .elementFromPoint(clientX, clientY)
            ?.dispatchEvent(simulatedEvent);
        }}
        onPointerDown={
          onDragStart !== undefined && onDragEnd !== undefined
            ? undefined
            : () => {
                if (item) onPointerDown(item.type, containerType ?? null, item);
              }
        }
        isSelectingShortcut={
          isSelectingShortcut &&
          (item?.type === ItemType.Consumable ||
            item?.type === ItemType.Tool ||
            item?.subType === ItemSubType.Seed)
        }
      >
        <Draggable
          axis={isSelectingShortcut ? 'none' : 'both'}
          defaultClassName={item ? 'draggable' : 'empty-slot'}
          scale={dragScale}
          disabled={onDragStart === undefined || onDragEnd === undefined}
          onStop={onDraggableStop}
          onStart={onDraggableStart}
          onDrag={onDraggableProgress}
          position={draggingState.position}
          cancel=".empty-slot"
          bounds=".item-container-body, .equipment-container-body"
        >
          <ItemContainer
            ref={dragContainer}
            isFocused={isFocused}
            onMouseOver={event => {
              onMouseOver?.(
                event,
                slotIndex,
                item,
                event.clientX,
                event.clientY
              );
            }}
            onMouseOut={() => {
              if (onMouseOut) onMouseOut();
            }}
            onMouseEnter={() => {
              updateDetailsState({
                item,
                isTooltipVisible: true,
              });
            }}
            onMouseLeave={() => {
              updateDetailsState({
                item,
                isTooltipVisible: false,
              });
            }}
          >
            <ItemSlotRenderer
              item={item}
              slotSpriteMask={slotSpriteMask}
              atlasIMG={atlasIMG}
              atlasJSON={atlasJSON}
              containerType={containerType}
            />
          </ItemContainer>
        </Draggable>
      </Container>
    );
  }
);

interface ContainerTypes {
  item: IItem | null;
  containerType?: ItemContainerType | null;
  isSelectingShortcut?: boolean;
  isDraggingItem?: boolean;
}

const Container = styled.div<ContainerTypes>`
  margin: 0.1rem;

  .react-draggable-dragging {
    // If dragging item, set opacity to 0
    opacity: ${({ isDraggingItem }) => (isDraggingItem ? 0 : 1)};
  }

  position: relative;

  .sprite-from-atlas-img--item {
    position: relative;
    top: 1.5rem;
    left: 1.5rem;
    border-color: ${({ item }) => rarityColor(item)};
    box-shadow: ${({ item }) => `0 0 5px 2px ${rarityColor(item)}`} inset,
      ${({ item }) => `0 0 4px 3px ${rarityColor(item)}`};
  }

  .quality-star {
    position: absolute;
    top: 0.5rem;
    left: 0.5rem;
    font-size: 1.2rem;
    z-index: 2;
    text-shadow: 0 0 3px black;
    pointer-events: none;
    color: ${({ item }) => qualityColorHex(item)};
  }

  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
    border-radius: 12px;
    pointer-events: none;
    animation: ${({ isSelectingShortcut }) =>
      isSelectingShortcut ? 'bg-color-change 1s infinite' : 'none'};

    @keyframes bg-color-change {
      0% {
        background-color: rgba(255 255 255 / 0.5);
      }
      50% {
        background-color: transparent;
      }
      100% {
        background-color: rgba(255 255 255 / 0.5);
      }
    }
  }
`;

const ItemContainer = styled.div<{ isFocused?: boolean }>`
  width: 64px;
  height: 64px;

  position: relative;
  ${props => props.isFocused && 'z-index: 100; pointer-events: none;'};
`;
