import { ref, computed, type Ref, type ComputedRef } from 'vue';
import type { Sprite, Layer } from '@/types/sprites';
import { getMaxDimensions } from './useSprites';
import { calculateNegativeSpacing } from './useNegativeSpacing';

export interface CellPosition {
  col: number;
  row: number;
  index: number;
}

export interface SpritePosition {
  id: string;
  canvasX: number;
  canvasY: number;
  cellX: number;
  cellY: number;
  width: number;
  height: number;
  maxWidth: number;
  maxHeight: number;
  col: number;
  row: number;
  index: number;
  x: number;
  y: number;
}

export interface DragSpriteOptions {
  sprites: Ref<Sprite[]> | ComputedRef<Sprite[]> | Sprite[];
  layers?: Ref<Layer[]> | ComputedRef<Layer[]> | Layer[];
  columns: Ref<number> | number;
  zoom?: Ref<number>;
  allowCellSwap?: Ref<boolean>;
  negativeSpacingEnabled?: Ref<boolean>;
  manualCellSizeEnabled?: Ref<boolean>;
  manualCellWidth?: Ref<number>;
  manualCellHeight?: Ref<number>;
  getMousePosition: (event: MouseEvent, zoom?: number) => { x: number; y: number } | null;
  onUpdateSprite: (id: string, x: number, y: number) => void;
  onUpdateSpriteCell?: (id: string, newIndex: number) => void;
  onDraw: () => void;
}

export function useDragSprite(options: DragSpriteOptions) {
  const { getMousePosition, onUpdateSprite, onUpdateSpriteCell, onDraw } = options;

  // Helper to get reactive values
  const getSprites = () => (Array.isArray(options.sprites) ? options.sprites : options.sprites.value);
  const getLayers = () => (options.layers ? (Array.isArray(options.layers) ? options.layers : options.layers.value) : null);
  const getColumns = () => (typeof options.columns === 'number' ? options.columns : options.columns.value);
  const getZoom = () => options.zoom?.value ?? 1;
  const getAllowCellSwap = () => options.allowCellSwap?.value ?? false;
  const getNegativeSpacingEnabled = () => options.negativeSpacingEnabled?.value ?? false;
  const getManualCellSizeEnabled = () => options.manualCellSizeEnabled?.value ?? false;
  const getManualCellWidth = () => options.manualCellWidth?.value ?? 64;
  const getManualCellHeight = () => options.manualCellHeight?.value ?? 64;

  // Drag state
  const isDragging = ref(false);
  const activeSpriteId = ref<string | null>(null);
  const activeSpriteCellIndex = ref<number | null>(null);
  const dragStartX = ref(0);
  const dragStartY = ref(0);
  const dragOffsetX = ref(0);
  const dragOffsetY = ref(0);
  const currentHoverCell = ref<CellPosition | null>(null);

  // Visual feedback
  const ghostSprite = ref<{ id: string; x: number; y: number } | null>(null);
  const highlightCell = ref<CellPosition | null>(null);

  // Cache for max dimensions
  const lastMaxWidth = ref(1);
  const lastMaxHeight = ref(1);

  const calculateMaxDimensions = () => {
    const negativeSpacingEnabled = getNegativeSpacingEnabled();
    const manualCellSizeEnabled = getManualCellSizeEnabled();

    // If manual cell size is enabled, use manual dimensions
    if (manualCellSizeEnabled) {
      const maxWidth = getManualCellWidth();
      const maxHeight = getManualCellHeight();
      // When manual cell size is used, negative spacing is not applied
      const negativeSpacing = 0;
      // Don't update lastMaxWidth/lastMaxHeight when in manual mode
      return { maxWidth, maxHeight, negativeSpacing };
    }

    // Get all sprites to calculate dimensions from
    // If layers are provided, use all visible layers; otherwise use current sprites
    const layers = getLayers();
    const spritesToMeasure = layers ? layers.filter(l => l.visible).flatMap(l => l.sprites) : getSprites();

    // Otherwise, calculate based on sprite dimensions across all visible layers
    const base = getMaxDimensions(spritesToMeasure);
    // When switching back from manual mode, reset to actual sprite dimensions
    const baseMaxWidth = Math.max(1, base.maxWidth);
    const baseMaxHeight = Math.max(1, base.maxHeight);
    lastMaxWidth.value = baseMaxWidth;
    lastMaxHeight.value = baseMaxHeight;

    // Calculate negative spacing using shared composable
    const negativeSpacing = calculateNegativeSpacing(spritesToMeasure, negativeSpacingEnabled);

    // Add negative spacing to expand each cell
    const maxWidth = baseMaxWidth + negativeSpacing;
    const maxHeight = baseMaxHeight + negativeSpacing;
    return { maxWidth, maxHeight, negativeSpacing };
  };

  // Computed sprite positions
  const spritePositions = computed<SpritePosition[]>(() => {
    const sprites = getSprites();
    const columns = getColumns();
    const { maxWidth, maxHeight, negativeSpacing } = calculateMaxDimensions();

    return sprites.map((sprite, index) => {
      const col = index % columns;
      const row = Math.floor(index / columns);

      // With negative spacing, sprites are positioned at bottom-right of cell
      // (spacing added to top and left)
      return {
        id: sprite.id,
        canvasX: col * maxWidth + negativeSpacing + sprite.x,
        canvasY: row * maxHeight + negativeSpacing + sprite.y,
        cellX: col * maxWidth,
        cellY: row * maxHeight,
        width: sprite.width,
        height: sprite.height,
        maxWidth,
        maxHeight,
        col,
        row,
        index,
        x: sprite.x,
        y: sprite.y,
      };
    });
  });

  const findCellAtPosition = (x: number, y: number): CellPosition | null => {
    const sprites = getSprites();
    const columns = getColumns();
    const { maxWidth, maxHeight } = calculateMaxDimensions();
    const col = Math.floor(x / maxWidth);
    const row = Math.floor(y / maxHeight);

    const totalRows = Math.ceil(sprites.length / columns);
    if (col >= 0 && col < columns && row >= 0 && row < totalRows) {
      const index = row * columns + col;
      if (index < sprites.length) {
        return { col, row, index };
      }
    }
    return null;
  };

  const findSpriteAtPosition = (x: number, y: number): Sprite | null => {
    const sprites = getSprites();
    const positions = spritePositions.value;

    for (let i = positions.length - 1; i >= 0; i--) {
      const pos = positions[i];
      if (x >= pos.canvasX && x <= pos.canvasX + pos.width && y >= pos.canvasY && y <= pos.canvasY + pos.height) {
        return sprites.find(s => s.id === pos.id) || null;
      }
    }
    return null;
  };

  const startDrag = (event: MouseEvent) => {
    const pos = getMousePosition(event, getZoom());
    if (!pos) return;

    const clickedSprite = findSpriteAtPosition(pos.x, pos.y);
    if (clickedSprite) {
      isDragging.value = true;
      activeSpriteId.value = clickedSprite.id;
      dragStartX.value = pos.x;
      dragStartY.value = pos.y;

      const spritePosition = spritePositions.value.find(p => p.id === clickedSprite.id);
      if (spritePosition) {
        dragOffsetX.value = pos.x - spritePosition.canvasX;
        dragOffsetY.value = pos.y - spritePosition.canvasY;
        activeSpriteCellIndex.value = spritePosition.index;

        const startCell = findCellAtPosition(pos.x, pos.y);
        if (startCell) {
          currentHoverCell.value = startCell;
          highlightCell.value = null;
        }
      }
    }
  };

  const handleInCellMovement = (mouseX: number, mouseY: number, spriteIndex: number) => {
    if (!activeSpriteId.value) return;
    const sprites = getSprites();
    const columns = getColumns();
    const { maxWidth, maxHeight, negativeSpacing } = calculateMaxDimensions();

    // Use the sprite's current index in the array to calculate cell position
    const cellCol = spriteIndex % columns;
    const cellRow = Math.floor(spriteIndex / columns);
    const cellX = cellCol * maxWidth;
    const cellY = cellRow * maxHeight;

    // Calculate new position relative to cell origin (without the negative spacing offset)
    // The sprite's x,y is stored relative to where it would be drawn after the negativeSpacing offset
    const newX = mouseX - cellX - negativeSpacing - dragOffsetX.value;
    const newY = mouseY - cellY - negativeSpacing - dragOffsetY.value;

    // The sprite can move within the full expanded cell area
    // Allow negative values up to -negativeSpacing so sprite can fill the expanded area
    const constrainedX = Math.floor(Math.max(-negativeSpacing, Math.min(maxWidth - negativeSpacing - sprites[spriteIndex].width, newX)));
    const constrainedY = Math.floor(Math.max(-negativeSpacing, Math.min(maxHeight - negativeSpacing - sprites[spriteIndex].height, newY)));

    onUpdateSprite(activeSpriteId.value, constrainedX, constrainedY);
    onDraw();
  };

  const drag = (event: MouseEvent) => {
    if (!isDragging.value || !activeSpriteId.value || activeSpriteCellIndex.value === null) return;

    const pos = getMousePosition(event, getZoom());
    if (!pos) return;

    const sprites = getSprites();
    const spriteIndex = sprites.findIndex(s => s.id === activeSpriteId.value);
    if (spriteIndex === -1) return;

    const hoverCell = findCellAtPosition(pos.x, pos.y);
    currentHoverCell.value = hoverCell;

    if (getAllowCellSwap() && hoverCell) {
      if (hoverCell.index !== activeSpriteCellIndex.value) {
        highlightCell.value = hoverCell;
        ghostSprite.value = {
          id: activeSpriteId.value,
          x: pos.x - dragOffsetX.value,
          y: pos.y - dragOffsetY.value,
        };
        onDraw();
      } else {
        highlightCell.value = null;
        ghostSprite.value = null;
        handleInCellMovement(pos.x, pos.y, spriteIndex);
      }
    } else {
      handleInCellMovement(pos.x, pos.y, spriteIndex);
    }
  };

  const stopDrag = () => {
    if (isDragging.value && getAllowCellSwap() && activeSpriteId.value && activeSpriteCellIndex.value !== null && currentHoverCell.value && activeSpriteCellIndex.value !== currentHoverCell.value.index) {
      if (onUpdateSpriteCell) {
        onUpdateSpriteCell(activeSpriteId.value, currentHoverCell.value.index);
      }
      onUpdateSprite(activeSpriteId.value, 0, 0);
    }

    isDragging.value = false;
    activeSpriteId.value = null;
    activeSpriteCellIndex.value = null;
    currentHoverCell.value = null;
    highlightCell.value = null;
    ghostSprite.value = null;

    onDraw();
  };

  // Touch event handlers
  const handleTouchStart = (event: TouchEvent) => {
    if (event.touches.length === 1) {
      const touch = event.touches[0];
      const mouseEvent = {
        clientX: touch.clientX,
        clientY: touch.clientY,
        preventDefault: () => {},
      } as unknown as MouseEvent;
      startDrag(mouseEvent);
    }
  };

  const handleTouchMove = (event: TouchEvent) => {
    if (isDragging.value) {
      event.preventDefault();
    }

    if (event.touches.length === 1) {
      const touch = event.touches[0];
      const mouseEvent = {
        clientX: touch.clientX,
        clientY: touch.clientY,
        preventDefault: () => {},
      } as unknown as MouseEvent;
      drag(mouseEvent);
    }
  };

  return {
    // State
    isDragging,
    activeSpriteId,
    ghostSprite,
    highlightCell,
    spritePositions,

    // Methods
    startDrag,
    drag,
    stopDrag,
    handleTouchStart,
    handleTouchMove,
    findSpriteAtPosition,
    findCellAtPosition,
    calculateMaxDimensions,
  };
}
