<template>
  <div class="app">
    <DraggableResizable
      v-for="comp in components"
      :key="comp.id"
      :id="comp.id"
      :x="comp.x"
      :y="comp.y"
      :width="comp.width"
      :height="comp.height"
      :zIndex="comp.zIndex"
      :snapDistance="snapDistance"
      @drag="handleDrag"
      @resize="handleResize"
      :detectCollision="detectCollision"
      :detectSnap="detectSnap"
      :handleCollision="handleCollision"
      :checkClosestComponent="getClosestComponent"
      :setCurrentComponent="getCurrentComponent"
      :directions="['right', 'bottom-right']"
    />
  </div>
</template>

<script setup lang="ts">
//todo 1 增加布局切换的功能，且切换有过渡效果
//todo 2 吸附功能也需要增加过渡效果
//todo 3 resize的操作节点需要可配置，并且允许用svg替代
//todo 4 拖拽区域需要可配置
//todo 5 增加drag预期位置显示，并设置吸附距离，达到吸附距离后立即移动到吸附后的位置，并增加过渡效果
//todo 6 当预期位置侵占其他组件时，需要移动被侵占组件的位置
import DraggableResizable from './DraggableResizable.vue';
interface ComponentState {
  id: string;
  x: number;
  y: number;
  width: number;
  height: number;
  zIndex?: number | string;
}
interface SnapResult {
  id?: string;
  left: number;
  top: number;
  width: number;
  height: number;
  cx?: number;
  cy?: number;
  isSnap: boolean;
}
const snapDistance = 50;
const defaultComponents = [
  { id: 'comp1', x: 100, y: 100, width: 200, height: 200, zIndex: 1 },
  { id: 'comp2', x: 400, y: 100, width: 200, height: 200, zIndex: 1 },
  { id: 'comp3', x: 100, y: 400, width: 200, height: 200, zIndex: 1 },
  { id: 'comp4', x: 400, y: 400, width: 200, height: 200, zIndex: 1 },
];
const components = reactive<ComponentState[]>([]);
const currentComp = reactive<ComponentState>({
  id: '',
  x: 0,
  y: 0,
  width: 0,
  height: 0,
});
const parentWidth = ref(0);
const parentHeight = ref(0);

const loadState = () => {
  const savedState = localStorage.getItem('componentsState');
  if (savedState) {
    Object.assign(components, JSON.parse(savedState));
  } else {
    Object.assign(components, defaultComponents);
  }
};

const saveState = () => {
  localStorage.setItem('componentsState', JSON.stringify(components));
};

const handleDrag = (id: string, x: number, y: number) => {
  const component = components.find((c) => c.id === id);
  if (component) {
    component.x = x;
    component.y = y;
    saveState();
  }
};

const handleResize = (id: string, width: number, height: number) => {
  const component = components.find((c) => c.id === id);
  if (component) {
    component.width = width;
    component.height = height;
    saveState();
  }
};

type CollidedDirection = 'top' | 'right' | 'bottom' | 'left' | '';

/**
 * 检测元素之间的碰撞
 * @param id
 * @param x
 * @param y
 * @param width
 * @param height
 */
const detectCollision = (id: string, x: number, y: number, width: number, height: number) => {
  const currentComponent = components.find((c) => c.id === id);
  if (!currentComponent) return false;
  let colliedComponent: ComponentState | null = null;
  let collidedDirection: CollidedDirection = '';
  const value = components.some((c) => {
    if (c.id === id) return false;
    colliedComponent = c;
    return !(x + width <= c.x || x >= c.x + c.width || y + height <= c.y || y >= c.y + c.height);
  });
  if (value && colliedComponent) {
    collidedDirection = getColliedDirection<ComponentState>(currentComponent, colliedComponent);
  }
  return { value, colliedComponent, collidedDirection };
};

/**
 * 获取被碰撞组件的方向
 * @param currentComponent
 * @param colliedComponent
 */
const getColliedDirection = <T extends ComponentState>(currentComponent: T, colliedComponent: T): CollidedDirection => {
  let collidedDirection: CollidedDirection = '';
  if (currentComponent.x + currentComponent.width === colliedComponent.x) {
    collidedDirection = 'left';
  }
  if (currentComponent.x === colliedComponent.x + colliedComponent.width) {
    collidedDirection = 'right';
  }
  if (currentComponent.y + currentComponent.height === colliedComponent.y) {
    collidedDirection = 'top';
  }
  if (currentComponent.y === colliedComponent.y + colliedComponent.height) {
    collidedDirection = 'bottom';
  }
  return collidedDirection;
};

const getCurrentComponent = (currentComponent: {
  width: string;
  height: string;
  top: string;
  left: string;
  id: string;
}): ComponentState => {
  return Object.assign(currentComp, {
    id: currentComponent.id,
    x: parseInt(currentComponent.left),
    y: parseInt(currentComponent.top),
    width: parseInt(currentComponent.width),
    height: parseInt(currentComponent.height),
  });
};

/**
 * 检测吸附
 * @param id
 * @param x
 * @param y
 * @param width
 * @param height
 * @param snapDistance
 */
const detectSnap = (id: string, x: number, y: number, width: number, height: number, snapDistance: number) => {
  let snapResult: SnapResult = { left: x, top: y, width, height, cx: 0, cy: 0, isSnap: false };

  const closestComponent = getClosestComponent(id, x, y, width, height);
  // console.log('closestComponent', closestComponent);

  //* 检测左边缘吸附
  if (Math.abs(x - (closestComponent.x + closestComponent.width)) <= snapDistance) {
    snapResult.left = closestComponent.x + closestComponent.width;
    console.log('snap left', snapResult.left);
    snapResult.isSnap = true;
  }
  //* 检测右边缘吸附
  else if (Math.abs(x + width - closestComponent.x) <= snapDistance) {
    snapResult.left = closestComponent.x - width;
    console.log('snap right', snapResult.left);
    snapResult.isSnap = true;
  }
  //* 检测上边缘吸附
  else if (
    Math.abs(y - (closestComponent.y + closestComponent.height)) <= snapDistance &&
    (Math.abs(x + width - closestComponent.x) <= snapDistance || x + width >= closestComponent.x)
  ) {
    snapResult.top = closestComponent.y + closestComponent.height;
    console.log('snap top', snapResult.top);
    snapResult.isSnap = true;
  }
  //* 检测下边缘吸附
  else if (Math.abs(y + height - closestComponent.y) <= snapDistance) {
    snapResult.top = closestComponent.y - height;
    console.log('snap bottom', snapResult.top);
    snapResult.isSnap = true;
  } else {
    snapResult.cx = closestComponent.x;
    snapResult.cy = closestComponent.y;
  }

  //* 检测父元素边界吸附
  if (x <= snapDistance) {
    console.log('parent left');
    snapResult.left = 0;
    snapResult.isSnap = true;
  }
  if (y <= snapDistance) {
    console.log('parent top');
    snapResult.top = 0;
    snapResult.isSnap = true;
  }
  if (parentWidth.value - (x + width) <= snapDistance) {
    console.log('parent right');
    snapResult.left = parentWidth.value - width;
    snapResult.isSnap = true;
  }
  if (parentHeight.value - (y + height) <= snapDistance) {
    console.log('parent bottom');
    snapResult.top = parentHeight.value - height;
    snapResult.isSnap = true;
  }

  // const result = adjustSnapResult(id, x, y, width, height, snapResult);
  // Object.assign(snapResult, result);

  return snapResult;
};

/**
 * 调整吸附参数
 * @param id
 * @param x
 * @param y
 * @param width
 * @param height
 * @param snapResult
 */
const adjustSnapResult = (
  id: string,
  x: number,
  y: number,
  width: number,
  height: number,
  snapResult: SnapResult,
): SnapResult => {
  const adjustPosition = adjustComponentPosition(id, x, y, width, height);
  console.log('adjustPosition', adjustPosition, 'x', x, 'y', y);
  console.log('currentComp', currentComp);

  //* 如果调整值有负数，则直接返回原始坐标，不进行后续逻辑判断（暴力调整，暂不开启）
  // if (adjustPosition.x < 0 || adjustPosition.y < 0) {
  //   snapResult.left = currentComp.x;
  //   snapResult.top = currentComp.y;
  //   return snapResult;
  // }

  //* 重新赋值，但需要避免不能将组件移动到负坐标上
  if (adjustPosition.x !== x) {
    if (adjustPosition.x <= 0) {
      snapResult.left = x;
    } else if (adjustPosition.x + width >= parentWidth.value) {
      snapResult.left = parentWidth.value - width;
    } else {
      snapResult.left = adjustPosition.x;
    }
    snapResult.isSnap = true;
  } else if (x < 0) {
    snapResult.left = 0;
    // snapResult.left = currentComp.x;
    // snapResult.top = currentComp.y;
    snapResult.isSnap = true;
  }
  if (adjustPosition.y !== y) {
    snapResult.top = adjustPosition.y <= 0 ? 0 : adjustPosition.y;
    snapResult.left = adjustPosition.x;
    snapResult.isSnap = true;
  }

  //* 返回前最后一次检查
  if (snapResult.left < 0 || snapResult.top < 0) {
    snapResult.left = currentComp.x;
    snapResult.top = currentComp.y;
  }

  return snapResult;
};

/**
 * 计算距离最近的组件
 * @param id
 * @param x
 * @param y
 * @param width
 * @param height
 */
const getClosestComponent = (id: string, x: number, y: number, width: number, height: number): ComponentState => {
  const otherComponents = components.filter((comp) => comp.id !== id);

  const calculateEdgeDistance = (
    comp1: { x: number; y: number; width: number; height: number },
    comp2: { x: number; y: number; width: number; height: number },
  ): number => {
    const left = Math.abs(comp1.x - (comp2.x + comp2.width));
    const right = Math.abs(comp1.x + comp1.width - comp2.x);
    const top = Math.abs(comp1.y - (comp2.y + comp2.height));
    const bottom = Math.abs(comp1.y + comp1.height - comp2.y);
    const minX = Math.min(left, right);
    const minY = Math.min(top, bottom);
    return Math.min(minX, minY);
  };

  let closestComponent = otherComponents[0];
  let shortestDistance = calculateEdgeDistance({ x, y, width, height }, closestComponent);

  for (const component of otherComponents) {
    const distance = calculateEdgeDistance({ x, y, width, height }, component);
    if (distance < shortestDistance) {
      shortestDistance = distance;
      closestComponent = component;
    }
  }

  return closestComponent;
};

const handleCollision = (id: string, x: number, y: number, width: number, height: number) => {
  //* 仅检测碰撞，不移动其他组件
  return detectCollision(id, x, y, width, height);
};

onMounted(() => {
  parentWidth.value = document.querySelector('.app')!.clientWidth;
  parentHeight.value = document.querySelector('.app')!.clientHeight;
  loadState();
});

/**
 * 是否与其他组件重叠
 * @param comp1
 * @param comp2
 */
const isOverlapping = (comp1: ComponentState, comp2: ComponentState): boolean => {
  return !(
    comp1.x >= comp2.x + comp2.width ||
    comp1.x + comp1.width <= comp2.x ||
    comp1.y >= comp2.y + comp2.height ||
    comp1.y + comp1.height <= comp2.y
  );
};

/**
 * 调整当前组件位置
 * @param id
 * @param x
 * @param y
 * @param width
 * @param height
 */
const adjustComponentPosition = (
  id: string,
  x: number,
  y: number,
  width: number,
  height: number,
): { x: number; y: number } => {
  const otherComponents = components.filter((comp) => comp.id !== id);
  let adjustedX = x;
  let adjustedY = y;

  const adjustPosition = () => {
    let adjusted = false;
    for (const component of otherComponents) {
      if (isOverlapping({ id, x: adjustedX, y: adjustedY, width, height }, component)) {
        const right = component.x + component.width;
        const bottom = component.y + component.height;
        const leftOverlap = adjustedX < component.x;
        const rightOverlap = adjustedX + width > component.x + component.width;
        const topOverlap = adjustedY < component.y;
        const bottomOverlap = adjustedY + height > component.y + component.height;

        if (leftOverlap) {
          console.log('leftOverlap');
          adjustedX = component.x - width;
          adjusted = true;
        } else if (rightOverlap) {
          console.log('rightOverlap', component);
          if (component.x + component.width + width > parentWidth.value) {
            console.log('rightOverlap overflow');
            adjustedX = currentComp.x;
            adjustedY = currentComp.y;
          }
          adjusted = true;
        } else if (topOverlap) {
          console.log('topOverlap');
          adjustedY = component.y - height;
          adjusted = true;
        } else if (bottomOverlap) {
          console.log('bottomOverlap');
          adjustedY = bottom;
          adjusted = true;
        } else {
          console.log('当前组件在其他组件内部');
        }

        //* 检查是否还是与其他组件重叠
        let stillOverlapping = false;
        for (const comp of otherComponents) {
          if (isOverlapping({ id, x: adjustedX, y: adjustedY, width, height }, comp)) {
            stillOverlapping = true;
            break;
          }
        }

        if (stillOverlapping) {
          //* 恢复组件移动前的原始位置
          console.log('stillOverlapping');
          adjustedX = currentComp.x;
          adjustedY = currentComp.y;
          adjusted = true;
          break;
        }
      }
    }
    return adjusted;
  };

  while (adjustPosition()) {
    const prevX = adjustedX;
    const prevY = adjustedY;
    if (adjustPosition()) {
      if (adjustedX === prevX && adjustedY === prevY) {
        console.log('full overlap');
        break;
      }
    }
  }

  return { x: adjustedX, y: adjustedY };
};
</script>
<style lang="less" scoped>
.app {
  position: relative;
  width: 100%;
  height: 100%;
  background-color: #f0f0f0;
  overflow-x: hidden;
  overflow-y: auto;
}
</style>
