<template>
  <div class="element-wrapper" :class="{ active }" :style="wrapperStyle" @pointerdown.stop="startDrag" @click.stop="emit('select', element.id)">
    <slot />
    <template v-if="active && resizable">
      <span v-for="handle in handles" :key="handle" class="resize-handle" :class="`handle-${handle}`" @pointerdown.stop="startResize($event, handle)" />
    </template>
  </div>
</template>

<script lang="ts" setup>
  import { computed } from 'vue';
  import { calcDragRect, calcResizeRect } from '../core/dragResize';
  import type { NativeElement } from '../core/types';
  const PX_PER_MM = 3.7795275591;

  const props = defineProps<{
    element: NativeElement;
    active: boolean;
    scale: number;
    gridSize: number;
    pageWidth: number;
    pageHeight: number;
    movable?: boolean;
    resizable?: boolean;
    dragBounds?: {
      minX: number;
      maxX: number;
      minY: number;
      maxY: number;
    };
  }>();

  const emit = defineEmits<{
    (e: 'update', payload: { id: string; patch: Partial<NativeElement> }): void;
    (e: 'select', id: string): void;
    (e: 'dragging', payload: { id: string; rect: { x: number; y: number; w: number; h: number }; active: boolean }): void;
  }>();

  const handles = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];

  const wrapperStyle = computed(() => ({
    position: 'absolute',
    left: `${props.element.x}mm`,
    top: `${props.element.y}mm`,
    width: `${props.element.w}mm`,
    height: `${props.element.h}mm`,
    zIndex: props.element.zIndex,
    outline: props.active ? '1px solid #1677ff' : '1px dashed transparent',
    userSelect: 'none',
  }));
  const movable = computed(() => props.movable !== false);
  const resizable = computed(() => props.resizable !== false);

  function clampByBounds(rect: { x: number; y: number; w: number; h: number }) {
    const bounds = props.dragBounds;
    if (!bounds) return rect;
    return {
      ...rect,
      x: Math.max(bounds.minX, Math.min(bounds.maxX, rect.x)),
      y: Math.max(bounds.minY, Math.min(bounds.maxY, rect.y)),
    };
  }

  function startDrag(event: PointerEvent) {
    emit('select', props.element.id);
    if ((event.target as HTMLElement)?.classList.contains('resize-handle')) return;
    if (!movable.value) return;
    const start = { x: props.element.x, y: props.element.y, w: props.element.w, h: props.element.h };
    const startX = event.clientX;
    const startY = event.clientY;
    const onMove = (moveEvent: PointerEvent) => {
      // 鼠标位移是 px，画布坐标是 mm，这里必须做单位换算才会跟手
      const deltaX = (moveEvent.clientX - startX) / props.scale / PX_PER_MM;
      const deltaY = (moveEvent.clientY - startY) / props.scale / PX_PER_MM;
      const next = calcDragRect(start, { width: props.pageWidth, height: props.pageHeight }, deltaX, deltaY, props.gridSize);
      const bounded = clampByBounds(next);
      emit('update', { id: props.element.id, patch: bounded });
      emit('dragging', { id: props.element.id, rect: bounded, active: true });
    };
    const onUp = () => {
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
      emit('dragging', { id: props.element.id, rect: { x: props.element.x, y: props.element.y, w: props.element.w, h: props.element.h }, active: false });
    };
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
  }

  function startResize(event: PointerEvent, direction: any) {
    emit('select', props.element.id);
    if (!resizable.value) return;
    const start = { x: props.element.x, y: props.element.y, w: props.element.w, h: props.element.h };
    const startX = event.clientX;
    const startY = event.clientY;
    const onMove = (moveEvent: PointerEvent) => {
      // 鼠标位移是 px，画布尺寸是 mm，缩放时同样要按单位换算
      const deltaX = (moveEvent.clientX - startX) / props.scale / PX_PER_MM;
      const deltaY = (moveEvent.clientY - startY) / props.scale / PX_PER_MM;
      const next = calcResizeRect(direction, start, { width: props.pageWidth, height: props.pageHeight }, deltaX, deltaY, props.gridSize);
      emit('update', { id: props.element.id, patch: next });
    };
    const onUp = () => {
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
    };
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
  }
</script>

<style scoped lang="less">
  .element-wrapper {
    box-sizing: border-box;

    .resize-handle {
      position: absolute;
      width: 6px;
      height: 6px;
      background: #1677ff;
      border-radius: 50%;
      margin: -3px;
    }
    .handle-nw {
      left: 0;
      top: 0;
      cursor: nwse-resize;
    }
    .handle-n {
      left: 50%;
      top: 0;
      cursor: ns-resize;
    }
    .handle-ne {
      left: 100%;
      top: 0;
      cursor: nesw-resize;
    }
    .handle-e {
      left: 100%;
      top: 50%;
      cursor: ew-resize;
    }
    .handle-se {
      left: 100%;
      top: 100%;
      cursor: nwse-resize;
    }
    .handle-s {
      left: 50%;
      top: 100%;
      cursor: ns-resize;
    }
    .handle-sw {
      left: 0;
      top: 100%;
      cursor: nesw-resize;
    }
    .handle-w {
      left: 0;
      top: 50%;
      cursor: ew-resize;
    }
  }
</style>
