import { useRef, useEffect, useState } from '@wordpress/element';
import classNames from 'classnames/dedupe';
import { useDispatch } from '@wordpress/data';
import SizeIcon from 'blockbite-icons/dist/Size';
import MoveIcon from 'blockbite-icons/dist/Move';
import ArrowUpIcon from 'blockbite-icons/dist/ArrowUp';
import ArrowDownIcon from 'blockbite-icons/dist/ArrowDown';
import useMousePosition from './useMousePosition';
import EyeOpenIcon from 'blockbite-icons/dist/EyeOpen';
import EyeClosedIcon from 'blockbite-icons/dist/EyeClosed';
import type { PositionProps } from './AreaHelpers';
import has from 'lodash/has';

import { biteStyleToPosition, getAreaModifiers } from './AreaHelpers';
import type { BiteStyleProps } from '@components/DesignPanel/types';

import {
  getNewDesignClasses,
  getMergedDesignList,
} from '@components/DesignPanel/Helpers';

export type mousePositionProps = {
  x: number;
  y: number;
};

export type AreaBlockProps = {
  clientId: string;
  key: string;
  isCanvas: boolean;
  isActive: boolean;
  className?: string;
  gridSizeX: number;
  gridSizeY: number;
  gridGapX: number;
  gridGapY: number;
  currentResponsive: string;
  setActiveBlockId: (id: string) => void;
  biteStyle: BiteStyleProps[];
};

export default function AreaBlock({
  clientId,
  isCanvas,
  isActive,
  gridSizeX,
  gridSizeY,
  gridGapX,
  gridGapY,
  currentResponsive,
  setActiveBlockId,
  biteStyle,
}: AreaBlockProps) {
  const ref = useRef(null);
  const moveHandleRef = useRef(null);
  const resizeHandleRef = useRef(null);
  const [isResizing, setIsResizing] = useState(false);
  const [isDragging, setIsDragging] = useState(false);

  const [isVisible, setIsVisible] = useState(
    !(biteStyle.find((style) => style.id === 'overflow')?.value === 'hidden')
  );

  const [position, setPosition] = useState({
    x: null,
    y: null,
    w: null,
    h: null,
  });

  const [initialPosition, setInitialPosition] = useState({
    x: 0,
    y: 0,
    w: 0,
    h: 0,
  });

  const mousePosition = useMousePosition();
  const targetRef = useRef(null);
  const { getBlockRootClientId, getBlockIndex, getBlocks } =
    wp.data.select('core/editor');
  const { moveBlockToPosition } = useDispatch('core/block-editor');

  const buttonWidth = 32.8;
  const halfButtonWidth = buttonWidth / 2;

  /* iniate block position */
  useEffect(() => {
    if (clientId) {
      targetRef.current = document.getElementById(`block-${clientId}`);
      // biteStyleToPosition returns an object fromout the classnames b_area-x-2 b_area-y-1 b_area-w-2 b_area-h-5
      // for example pos:  { x : 2, y: 1, w :2, h : 5}
      const { pos } = biteStyleToPosition(biteStyle);
      const gridToPercent = convertGridToPercent(pos);

      setPosition(gridToPercent);
    }
  }, [clientId, gridGapX, gridGapY, gridSizeX, gridSizeY]);

  useEffect(() => {
    if (isResizing && isCanvas) {
      handleResize(mousePosition);
      setTargetRefPosition();
    }
    if (isDragging && isCanvas) {
      handleDrag(mousePosition);
      setTargetRefPosition();
    }
  }, [mousePosition.x, mousePosition.y]);

  /*
   * Canvas Release Handler
   * Reset dragging and resizing state
   * Update block biteStyle and className
   */
  useEffect(() => {
    if (!isCanvas) {
      setIsDragging(false);
      setIsResizing(false);
      update();
    }
  }, [isCanvas]);

  const update = () => {
    if (
      position.x === null ||
      position.y === null ||
      position.w === null ||
      position.h === null
    ) {
      return;
    }

    const percentToGrid = convertPercentToGrid(position);
    const updatedAreaStyles = [] as BiteStyleProps[];

    getAreaModifiers().forEach((style) => {
      // safely pick style.position value from percentToGrid
      let value = null;
      if (has(percentToGrid, style.position)) {
        value = percentToGrid[style.position].toString();
      } else if (style.id === 'b_area') {
        value = 'b_area';
      } else if (style.id === 'overflow' && isVisible) {
        value = isVisible ? 'hidden' : 'visible';
      }

      if (value) {
        updatedAreaStyles.push({
          id: style.id,
          screen: currentResponsive,
          value: value,
        });
      }
    });

    const updatedBiteStyles = getMergedDesignList(biteStyle, updatedAreaStyles);

    targetRef.current.style.removeProperty('top');
    targetRef.current.style.removeProperty('left');
    targetRef.current.style.removeProperty('width');
    targetRef.current.style.removeProperty('height');
    targetRef.current.style.removeProperty('position');

    const biteClass = getNewDesignClasses(updatedBiteStyles);

    // check if biteclass contains NaN in any string
    if (biteClass.includes('NaN')) {
      return;
    }

    blockbite.updateBlockAttributes(clientId, {
      biteStyle: updatedBiteStyles,
      biteClass: biteClass,
    });
  };

  const setTargetRefPosition = () => {
    const style = {
      left: `${position.x}%`,
      top: `${position.y}%`,
      width: `${position.w}%`,
      height: `${position.h}%`,
      position: 'absolute',
    };

    targetRef.current.classList.remove('b_area');
    targetRef.current.style.setProperty('top', style.top, 'important');
    targetRef.current.style.setProperty('left', style.left, 'important');
    targetRef.current.style.setProperty('width', style.width, 'important');
    targetRef.current.style.setProperty('height', style.height, 'important');
    targetRef.current.style.setProperty(
      'position',
      style.position,
      'important'
    );
  };

  const getParentCoordinates = () => {
    const parentWidth = ref.current.parentElement.offsetWidth;
    const parentHeight = ref.current.parentElement.offsetHeight;

    const totalGridGapX = gridGapX * (gridSizeX - 1);
    const totalGridGapY = gridGapY * (gridSizeY - 1);

    const gridWidth = Math.round((parentWidth - totalGridGapX) / gridSizeX);
    const gridHeight = Math.round((parentHeight - totalGridGapY) / gridSizeY);

    return {
      parentWidth,
      parentHeight,
      parentLeft: ref.current.parentElement.getBoundingClientRect().left,
      parentTop: ref.current.parentElement.getBoundingClientRect().top,
      gridWidth,
      gridHeight,
    };
  };

  const convertPercentToPixels = (percentPosition: PositionProps) => {
    const { parentWidth, parentHeight } = getParentCoordinates();

    if (!percentPosition) {
      return null;
    }

    return {
      x: (percentPosition.x / 100) * parentWidth,
      y: (percentPosition.y / 100) * parentHeight,
      w: (percentPosition.w / 100) * parentWidth,
      h: (percentPosition.h / 100) * parentHeight,
    };
  };

  const convertPercentToGrid = (percentPosition: any) => {
    const { parentWidth, parentHeight, gridWidth, gridHeight } =
      getParentCoordinates();

    if (!percentPosition) {
      return null;
    }

    const pixelPosition = {
      x: (percentPosition.x / 100) * parentWidth,
      y: (percentPosition.y / 100) * parentHeight,
      w: (percentPosition.w / 100) * parentWidth,
      h: (percentPosition.h / 100) * parentHeight,
    };

    // Adjust pixel values to grid units, accounting for 1-based indexing
    const gridPosition = {
      x: Math.round(pixelPosition.x / (gridWidth + gridGapX)) + 1,
      y: Math.round(pixelPosition.y / (gridHeight + gridGapY)) + 1,
      w: Math.round(pixelPosition.w / (gridWidth + gridGapX)),
      h: Math.round(pixelPosition.h / (gridHeight + gridGapY)),
    };

    return gridPosition;
  };

  const convertGridToPercent = (gridPosition: PositionProps) => {
    const { parentWidth, parentHeight, gridWidth, gridHeight } =
      getParentCoordinates();

    const adjustedGridPosition = {
      x: gridPosition.x - 1, // Convert from 1-based to 0-based index for calculation
      y: gridPosition.y - 1,
      w: gridPosition.w,
      h: gridPosition.h,
    };

    const pixelPosition = {
      x: adjustedGridPosition.x * gridWidth + adjustedGridPosition.x * gridGapX,
      y:
        adjustedGridPosition.y * gridHeight + adjustedGridPosition.y * gridGapY,
      w:
        adjustedGridPosition.w * gridWidth +
        (adjustedGridPosition.w - 1) * gridGapX,
      h:
        adjustedGridPosition.h * gridHeight +
        (adjustedGridPosition.h - 1) * gridGapY,
    };

    const percentPosition = {
      x: (pixelPosition.x / parentWidth) * 100,
      y: (pixelPosition.y / parentHeight) * 100,
      w: (pixelPosition.w / parentWidth) * 100,
      h: (pixelPosition.h / parentHeight) * 100,
    };

    return percentPosition;
  };

  const handleDrag = (mousePosition: mousePositionProps) => {
    const {
      parentWidth,
      parentHeight,
      parentLeft,
      parentTop,
      gridWidth,
      gridHeight,
    } = getParentCoordinates();

    const dragLeft = Math.round(mousePosition.x - parentLeft);
    const dragTop = Math.round(mousePosition.y - parentTop);

    // Calculate how many columns are crossed and adjust the position
    const columnsCrossedX = Math.floor(dragLeft / (gridWidth + gridGapX));
    const columnsCrossedY = Math.floor(dragTop / (gridHeight + gridGapY));

    const snapLeft = columnsCrossedX * (gridWidth + gridGapX);
    const snapTop = columnsCrossedY * (gridHeight + gridGapY);

    const dragLeftPercent = (snapLeft / parentWidth) * 100;
    const dragTopPercent = (snapTop / parentHeight) * 100;

    const pos = { ...position };
    pos.x = dragLeftPercent;
    pos.y = dragTopPercent;

    setPosition(pos);
  };

  const handleResize = (mousePosition: mousePositionProps) => {
    const {
      parentWidth,
      parentHeight,
      parentLeft,
      parentTop,
      gridWidth,
      gridHeight,
    } = getParentCoordinates();

    const mouseX = mousePosition.x + halfButtonWidth - parentLeft;
    const mouseY = mousePosition.y + halfButtonWidth - parentTop;

    // Convert initial position from percent to pixels
    const percentToPixels = convertPercentToPixels(initialPosition);
    const initialPixelPosition = percentToPixels;

    // Calculate new dimensions in pixels based on the mouse position
    const newWidth = mouseX - initialPixelPosition.x;
    const newHeight = mouseY - initialPixelPosition.y;

    // Align new dimensions to the grid while considering gaps
    const resizeWidthGrid =
      Math.round(newWidth / (gridWidth + gridGapX)) * (gridWidth + gridGapX);
    const resizeHeightGrid =
      Math.round(newHeight / (gridHeight + gridGapY)) * (gridHeight + gridGapY);

    // Calculate how many columns/rows are crossed during resize
    const columnsCrossedWidth = Math.floor(
      resizeWidthGrid / (gridWidth + gridGapX)
    );
    const rowsCrossedHeight = Math.floor(
      resizeHeightGrid / (gridHeight + gridGapY)
    );

    // Adjust the new position to snap to the end of the last column/row
    const widthPercentage =
      ((columnsCrossedWidth * gridWidth +
        (columnsCrossedWidth - 1) * gridGapX) /
        parentWidth) *
      100;
    const heightPercentage =
      ((rowsCrossedHeight * gridHeight + (rowsCrossedHeight - 1) * gridGapY) /
        parentHeight) *
      100;

    // Update the position with the new dimensions in percentages
    const newPosition = {
      ...initialPosition,
      w: widthPercentage,
      h: heightPercentage,
    };

    setPosition(newPosition);
  };

  function handleMoveBlock(direction: 'up' | 'down') {
    // Get all the blocks in the current block's root.
    const blocks = getBlocks(getBlockRootClientId(clientId));
    if (blocks.length < 2) {
      // There's only one block, nothing to move.
      return;
    }

    // Get the target block depending on the direction.
    // Target block is the block above or below the current block.
    const targetBlock =
      direction === 'up'
        ? blocks[getBlockIndex(clientId) + 1]
        : blocks[getBlockIndex(clientId) - 1];

    if (!targetBlock) {
      // There's no block to move to.
      return;
    }

    const targetBlockClientId = targetBlock.clientId;
    const targetBlockRootClientId = getBlockRootClientId(targetBlockClientId);
    const sourceClientId = clientId;
    const sourceRootClientId = getBlockRootClientId(clientId);
    const fromRootClientId = sourceRootClientId;
    const toRootClientId = targetBlockRootClientId;
    const targetIndex = getBlockIndex(targetBlockClientId);

    moveBlockToPosition(
      sourceClientId,
      fromRootClientId,
      toRootClientId,
      targetIndex
    );
  }

  function handleVisibility() {
    setIsVisible(!isVisible);
  }

  const setHandleResize = (e: any) => {
    // Only handle left mouse button
    if (e.button !== 0) return;

    setInitialPosition({
      x: position.x,
      y: position.y,
      w: position.w,
      h: position.h,
    });
    setIsResizing(true);
  };

  const setHandleDrag = (e: any) => {
    // Only handle left mouse button
    if (e.button !== 0) return;

    setInitialPosition({
      x: position.x,
      y: position.y,
      w: position.w,
      h: position.h,
    });
    setIsDragging(true);
  };

  return (
    <div
      ref={ref}
      className={classNames(
        isActive ? 'border-primary z-50 border' : 'z-0',
        'ring-primary/50 relative bg-slate-100 bg-opacity-20 hover:ring-2'
      )}
      style={{
        top: `${position.y}%`,
        left: `${position.x}%`,
        width: `${position.w}%`,
        height: `${position.h}%`,
        position: 'absolute',
      }}
      onClick={() => setActiveBlockId(clientId)}
    >
      <button
        ref={moveHandleRef}
        onMouseDown={setHandleDrag}
        className="move-handle absolute left-0 top-0 z-10 cursor-move bg-white/50 p-2 ring-1 group-hover:opacity-100 hover:bg-white"
      >
        <MoveIcon />
      </button>

      <div className="layer-handle absolute bottom-0 right-0 z-10 flex flex-wrap ring-1">
        <button
          onClick={() => handleVisibility()}
          className="bg-white/50 p-2 group-hover:opacity-100 hover:bg-white"
        >
          {isVisible ? <EyeOpenIcon /> : <EyeClosedIcon />}
        </button>
        <button
          onClick={() => handleMoveBlock('up')}
          title="Bring to front"
          className="bg-white/50 p-2 group-hover:opacity-100 hover:bg-white"
        >
          <ArrowUpIcon />
        </button>
        <button
          onClick={() => handleMoveBlock('down')}
          title="Bring to back"
          className="bg-white/50 p-2 group-hover:opacity-100 hover:bg-white"
        >
          <ArrowDownIcon />
        </button>
        <button
          ref={resizeHandleRef}
          title="Resize block"
          onMouseDown={setHandleResize}
          className="resize-handle z-10 cursor-se-resize bg-white/50 p-2 group-hover:opacity-100 hover:bg-white"
        >
          <SizeIcon className="scale-x-[-1]" />
        </button>
      </div>
    </div>
  );
}
