<template>
  <div
    ref="elRef"
    v-ele-move-directive.prevent.stop="{
      moveStart,
      moveElement,
      moveStop: moveElementOver,
    }"
    :class="classes"
    :style="styles"
  >
    <slot></slot>
    <div class="resize-control">
      <template v-for="(control, index) in controlPoints" :key="index">
        <div
          v-ele-move-directive.prevent.stop="{
            moveElement: control.movePoint,
            moveStop: control.movePointOver,
          }"
          :class="control.classes"
        ></div>
      </template>
      <div class="control-line control-line-left"></div>
      <div class="control-line control-line-right"></div>
      <div class="control-line control-line-top"></div>
      <div class="control-line control-line-bottom"></div>
    </div>
  </div>
</template>

<script setup lang="ts">
import {
  reactive,
  ref,
  computed,
  onMounted,
  onBeforeMount,
  CSSProperties,
} from "vue";
import { vEleMoveDirective } from "../../directives/eleMove";
import { objModifyAssign } from "../../utils/tool";

import {
  defaultActive,
  defaultFitParent,
  SizeData,
  ActionType,
  FitParentItem,
  ContronItem,
  PositionData,
} from "./types";

defineOptions({
  name: "ElementResize",
});

const emit = defineEmits<{
  (event: "update:modelValue", data: SizeData): void;
  (event: "change", data: SizeData): void;
  (event: "resizeOver", data: SizeData): void;
  (event: "onClick"): void;
}>();

interface Props {
  modelValue: SizeData;
  active?: ActionType[];
  move?: boolean;
  minWidth?: number;
  maxWidth?: number;
  minHeight?: number;
  maxHeight?: number;
  fitParent?: FitParentItem[];
  isCompact?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
  active: () => [...defaultActive],
  move: true,
  minWidth: 30,
  maxWidth: 0,
  minHeight: 30,
  maxHeight: 0,
  fitParent: () => [...defaultFitParent],
  isCompact: false,
});

const sizePosOrigin = reactive({ x: 0, y: 0, w: 0, h: 0 });
const sizePos = reactive({ x: 0, y: 0, w: 0, h: 0 });
const offsetTopOrigin = ref(0);
const lastSizePos = reactive({ x: 0, y: 0, w: 0, h: 0 });
const initOver = ref(false);
const controlPoints = ref<ContronItem[]>([]);
const parentNodeSize = reactive({ w: 0, h: 0 });
const elRef = ref();

const styles = computed(() => {
  return initOver.value
    ? {
        left: `${sizePos.x}px`,
        top: `${sizePos.y}px`,
        width: `${sizePos.w}px`,
        height: `${sizePos.h}px`,
        zIndex: props.modelValue.zindex || "auto",
        position: "absolute" as CSSProperties["position"],
      }
    : undefined;
});

const classes = computed(() => {
  return [
    "element-resize",
    {
      "element-resize-move": props.move,
      "element-resize-init": initOver.value,
      "element-resize-compact": props.isCompact,
    },
  ];
});

const fitParentTypeWidth = computed(() => {
  return props.fitParent.includes("w");
});
const fitParentTypeHeight = computed(() => {
  return props.fitParent.includes("h");
});

function initControlPoints() {
  const actions = {
    l: moveLeftPoint,
    r: moveRightPoint,
    t: moveTopPoint,
    b: moveBottomPoint,
    lt: moveLeftTopPoint,
    rt: moveRightTopPoint,
    lb: moveLeftBottomPoint,
    rb: moveRightBottomPoint,
  };
  controlPoints.value = props.active.map((type) => {
    return {
      classes: ["control-point", `control-point-${type}`],
      movePoint: actions[type],
      movePointOver: (data: PositionData) => {
        actions[type](data);
        movePointOver();
      },
    };
  });
}

function initSize() {
  const elDom = elRef.value as HTMLElement;
  const resizeDom = elDom.firstElementChild as Element;

  objModifyAssign(sizePos, props.modelValue);
  objModifyAssign(lastSizePos, props.modelValue);
  objModifyAssign(sizePosOrigin, props.modelValue);
  initOver.value = true;
}

function fetchValidSizePos(
  sizeData: SizeData,
  actionType: ActionType | "move"
) {
  if (sizeData.w <= props.minWidth) {
    sizeData.w = props.minWidth;

    if (actionType.includes("l"))
      sizeData.x = lastSizePos.x + lastSizePos.w - sizeData.w;
  }
  if (props.maxWidth !== 0 && sizeData.w >= props.maxWidth) {
    sizeData.w = props.maxWidth;
  }

  if (sizeData.h <= props.minHeight) {
    sizeData.h = props.minHeight;

    if (actionType.includes("t"))
      sizeData.y = lastSizePos.y + lastSizePos.h - sizeData.h;
  }
  if (props.maxHeight !== 0 && sizeData.h >= props.maxHeight) {
    sizeData.h = props.maxHeight;
  }

  if (!props.fitParent.length) {
    objModifyAssign(lastSizePos, sizeData);
    return sizeData;
  }

  // 不同的定位方式，计算方式有差异
  const elDom = elRef.value as HTMLElement;
  const elParentDom = elDom.offsetParent as HTMLElement;
  parentNodeSize.w = elParentDom.offsetWidth;
  parentNodeSize.h = elParentDom.offsetHeight;

  if (fitParentTypeWidth.value) {
    if (sizeData.x <= 0) {
      sizeData.x = 0;
      if (actionType.includes("l")) sizeData.w = lastSizePos.w + lastSizePos.x;
    }

    if (sizeData.x + sizeData.w >= parentNodeSize.w) {
      if (actionType === "move") {
        sizeData.x = parentNodeSize.w - sizeData.w;
      } else {
        sizeData.w = parentNodeSize.w - sizeData.x;
      }
    }
  }

  if (fitParentTypeHeight.value) {
    if (sizeData.y <= 0) {
      sizeData.y = 0;
      if (actionType.includes("t")) sizeData.h = lastSizePos.h + lastSizePos.y;
    }
    if (sizeData.y + sizeData.h >= parentNodeSize.h) {
      if (actionType === "move") {
        sizeData.y = parentNodeSize.h - sizeData.h;
      } else {
        sizeData.h = parentNodeSize.h - sizeData.y;
      }
    }
  }

  objModifyAssign(lastSizePos, sizeData);
  return sizeData;
}

function getLeftSize(left: number) {
  return {
    w: -left + sizePosOrigin.w,
    x: left + sizePosOrigin.x,
  };
}
function getRightSize(left: number) {
  return {
    w: left + sizePosOrigin.w,
  };
}
function getTopSize(top: number) {
  return {
    h: -top + sizePosOrigin.h,
    y: top + sizePosOrigin.y,
  };
}
function getBottomSize(top: number) {
  return {
    h: top + sizePosOrigin.h,
  };
}

function moveLeftPoint({ left }: Pick<PositionData, "left">) {
  const sp = { ...sizePos, ...getLeftSize(left) };
  objModifyAssign(sizePos, fetchValidSizePos(sp, "l"));
  emitChange();
}
function moveRightPoint({ left }: Pick<PositionData, "left">) {
  const sp = { ...sizePos, ...getRightSize(left) };
  objModifyAssign(sizePos, fetchValidSizePos(sp, "r"));
  emitChange();
}

function moveTopPoint({ top }: Pick<PositionData, "top">) {
  const sp = { ...sizePos, ...getTopSize(top) };
  objModifyAssign(sizePos, fetchValidSizePos(sp, "t"));
  emitChange();
}

function moveBottomPoint({ top }: Pick<PositionData, "top">) {
  const sp = { ...sizePos, ...getBottomSize(top) };
  objModifyAssign(sizePos, fetchValidSizePos(sp, "b"));
  emitChange();
}

function moveLeftTopPoint({ left, top }: PositionData) {
  const sp = {
    ...sizePos,
    ...getLeftSize(left),
    ...getTopSize(top),
  };
  objModifyAssign(sizePos, fetchValidSizePos(sp, "lt"));
  emitChange();
}

function moveRightTopPoint({ left, top }: PositionData) {
  const sp = {
    ...sizePos,
    ...getRightSize(left),
    ...getTopSize(top),
  };
  objModifyAssign(sizePos, fetchValidSizePos(sp, "rt"));
  emitChange();
}

function moveLeftBottomPoint({ left, top }: PositionData) {
  const sp = {
    ...sizePos,
    ...getLeftSize(left),
    ...getBottomSize(top),
  };
  objModifyAssign(sizePos, fetchValidSizePos(sp, "lb"));
  emitChange();
}

function moveRightBottomPoint({ left, top }: PositionData) {
  const sp = {
    ...sizePos,
    ...getRightSize(left),
    ...getBottomSize(top),
  };
  objModifyAssign(sizePos, fetchValidSizePos(sp, "rb"));
  emitChange();
}

function movePointOver() {
  objModifyAssign(sizePosOrigin, sizePos);
  objModifyAssign(lastSizePos, sizePos);
  emit("resizeOver", sizePos);
}

function moveStart() {
  emit("onClick");
}

function moveElement({ left, top }: PositionData) {
  if (!props.move) return;

  const sp = {
    ...sizePos,
    ...{
      x: left + sizePosOrigin.x,
      y: top + sizePosOrigin.y,
    },
  };
  objModifyAssign(sizePos, fetchValidSizePos(sp, "move"));
  emitChange();
}

function moveElementOver({ left, top }: PositionData) {
  if (!props.move) return;
  moveElement({ left, top });

  objModifyAssign(sizePosOrigin, sizePos);
  objModifyAssign(lastSizePos, sizePos);
  emit("resizeOver", sizePos);
}

function emitChange() {
  emit("update:modelValue", sizePos);
  emit("change", sizePos);
}

onMounted(() => {
  initSize();
});
onBeforeMount(() => {
  initControlPoints();
});
</script>

<style lang="less" scoped>
.element-resize {
  position: static;
  z-index: auto;
  background: transparent;
  box-sizing: content-box;

  &-move {
    cursor: move;
  }

  &-init {
    > div:first-child {
      width: 100% !important;
      height: 100% !important;
      position: relative !important;
      top: 0 !important;
      left: 0 !important;
      overflow: hidden;
    }
  }
  .control-point {
    position: absolute;
    width: 12px;
    height: 12px;
    background: #f53f3f;
    z-index: 99;
    &-l {
      left: 0;
      top: 50%;
      width: 12px;
      height: 12px;
      margin-top: -6px;
      border-radius: 0;
      cursor: w-resize;
    }
    &-lt {
      left: 0;
      top: 0;
      cursor: nw-resize;
    }
    &-lb {
      left: 0;
      bottom: 0;
      cursor: sw-resize;
    }
    &-r {
      right: 0;
      top: 50%;
      margin-top: -6px;
      cursor: e-resize;
    }
    &-rt {
      right: 0;
      top: 0;
      cursor: ne-resize;
    }
    &-rb {
      right: 0;
      bottom: 0;
      cursor: se-resize;
    }
    &-t {
      left: 50%;
      top: 0;
      margin-left: -6px;
      cursor: n-resize;
    }
    &-b {
      left: 50%;
      bottom: 0;
      margin-left: -6px;
      cursor: s-resize;
    }
  }
  .control-line {
    position: absolute;
    z-index: 98;

    &-left {
      height: 100%;
      left: 0;
      top: 0;
      border-left: 4px solid #f53f3f;
    }
    &-right {
      height: 100%;
      right: 0;
      top: 0;
      border-left: 4px solid #f53f3f;
    }
    &-top {
      width: 100%;
      left: 0;
      top: 0;
      border-top: 4px solid #f53f3f;
    }
    &-bottom {
      width: 100%;
      left: 0;
      bottom: 0;
      border-top: 4px solid #f53f3f;
    }
  }

  &-compact {
    .control-line {
      &-left {
        left: 0;
        border-left: 1px dashed #bbb;
      }
      &-right {
        right: 0;
        border-left: 1px dashed #bbb;
      }
      &-top {
        top: 0;
        border-top: 1px dashed #bbb;
      }
      &-bottom {
        bottom: 0;
        border-top: 1px dashed #bbb;
      }
    }
  }
}
</style>
