<template>
  <div
    :class="classes"
    :style="styles"
    v-move-ele.prevent.stop="{
      moveStart,
      moveElement,
      moveStop: moveElementOver,
    }"
  >
    <slot></slot>
    <div class="resize-control">
      <div
        v-for="(control, index) in controlPoints"
        :key="index"
        :class="control.classes"
        v-move-ele.prevent.stop="{
          moveElement: control.movePoint,
          moveStop: control.movePointOver,
        }"
      ></div>
      <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>
import MoveEle from "../../directives/move-ele";

export default {
  name: "element-resize",
  directives: { MoveEle },
  props: {
    value: {
      type: Object,
      required: true,
    },
    active: {
      type: Array,
      default() {
        return ["r", "rb", "b", "lb", "l", "lt", "t", "rt"];
      },
    },
    move: {
      type: Boolean,
      default: true,
    },
    minWidth: {
      type: Number,
      default: 30,
      validator(val) {
        return val >= 0;
      },
    },
    maxWidth: {
      type: Number,
      default: 0,
      validator(val) {
        return val >= 0;
      },
    },
    minHeight: {
      type: Number,
      default: 30,
      validator(val) {
        return val >= 0;
      },
    },
    maxHeight: {
      type: Number,
      default: 0,
      validator(val) {
        return val >= 0;
      },
    },
    fitParent: {
      type: Array,
      default() {
        return ["w", "h"];
      },
    },
    isCompact: {
      type: Boolean,
      default: false,
    },
    transformFit: {
      type: Function,
    },
    elementPk: {
      type: String,
      default: "",
    },
  },
  data() {
    return {
      sizePosOrigin: {
        x: 0,
        y: 0,
        w: 0,
        h: 0,
      },
      offsetTopOrigin: 0,
      sizePos: {
        x: 0,
        y: 0,
        w: 0,
        h: 0,
      },
      lastSizePos: {},
      initOver: false,
      controlPoints: [],
      positionType: "static",
      parentNodeSize: {
        w: 0,
        h: 0,
      },
    };
  },
  computed: {
    styles() {
      return this.initOver
        ? {
            left: this.sizePos.x + "px",
            top: this.sizePos.y + "px",
            width: this.sizePos.w + "px",
            height: this.sizePos.h + "px",
            zIndex: this.sizePos.zindex,
            position: this.positionType,
          }
        : {};
    },
    classes() {
      return [
        "element-resize",
        {
          "element-resize-move": this.move,
          "element-resize-init": this.initOver,
          "element-resize-compact": this.isCompact,
        },
      ];
    },
    fitParentTypeWidth() {
      return this.fitParent.includes("w");
    },
    fitParentTypeHeight() {
      return this.fitParent.includes("h");
    },
  },
  created() {
    this.initControlPoints();
  },
  mounted() {
    this.initSize();
  },
  methods: {
    initControlPoints() {
      const posName = {
        l: "Left",
        r: "Right",
        t: "Top",
        b: "Bottom",
      };
      this.controlPoints = this.active.map((type) => {
        const posFullName = type
          .split("")
          .map((item) => {
            return posName[item];
          })
          .join("");
        return {
          classes: ["control-point", `control-point-${type}`],
          movePoint: this[`move${posFullName}Point`],
          movePointOver: this.moveOver,
        };
      });
    },
    initSize() {
      const resizeDom = this.$el.childNodes[0];
      this.positionType = window.getComputedStyle(resizeDom).position;
      this.sizePos = { ...this.value };
      this.lastSizePos = { ...this.value };
      this.sizePosOrigin = { ...this.value };
      if (this.positionType === "relative")
        this.offsetTopOrigin = this.$el.offsetTop;
      this.initOver = true;
    },
    fetchValidSizePos(sizePos, actionType) {
      if (sizePos.w <= this.minWidth) {
        sizePos.w = this.minWidth;
      }
      if (this.maxWidth !== 0 && sizePos.w >= this.maxWidth) {
        sizePos.w = this.maxWidth;
      }

      if (sizePos.h <= this.minHeight) {
        sizePos.h = this.minHeight;
      }
      if (this.maxHeight !== 0 && sizePos.h >= this.maxHeight) {
        sizePos.h = this.maxHeight;
      }

      if (!this.fitParent.length) {
        this.lastSizePos = { ...sizePos };
        return sizePos;
      }

      // 不同的定位方式,计算方式有差异
      this.parentNodeSize = {
        w: this.$el.offsetParent.offsetWidth,
        h: this.$el.offsetParent.offsetHeight,
      };

      if (this.fitParentTypeWidth) {
        if (sizePos.x <= 0) {
          sizePos.x = 0;
          if (actionType.includes("left")) sizePos.w = this.lastSizePos.w;
        }

        if (sizePos.x + sizePos.w > this.parentNodeSize.w) {
          sizePos.x = this.lastSizePos.x;
          sizePos.w = this.parentNodeSize.w - sizePos.x;
        }
      }

      if (this.fitParentTypeHeight) {
        if (this.positionType === "relative") {
          const elOffsetTop = this.$el.offsetTop;
          if (this.sizePosOrigin.y - sizePos.y >= this.offsetTopOrigin) {
            sizePos.h = this.lastSizePos.h;
            sizePos.y = this.sizePosOrigin.y - this.offsetTopOrigin;
          }
          if (elOffsetTop + sizePos.h >= this.parentNodeSize.h) {
            sizePos.y = this.lastSizePos.y;
            sizePos.h = this.lastSizePos.h;
            // TODO:这里如果拖动太快,会有问题
            // console.log(this.parentNodeSize.h, elOffsetTop, sizePos.h);
          }
        } else {
          if (sizePos.y <= 0) {
            sizePos.y = 0;
            if (actionType.includes("top")) sizePos.h = this.lastSizePos.h;
          }
          if (sizePos.y + sizePos.h > this.parentNodeSize.h) {
            sizePos.y = this.lastSizePos.y;
            sizePos.h = this.parentNodeSize.h - sizePos.y;
          }
        }
      }

      this.lastSizePos = { ...sizePos };
      return this.transformFit
        ? Object.assign(
            {},
            sizePos,
            this.transformFit({ id: this.elementPk, ...sizePos }, actionType)
          )
        : sizePos;
    },
    getLeftSize(left) {
      return {
        w: -left + this.sizePosOrigin.w,
        x: left + this.sizePosOrigin.x,
      };
    },
    getRightSize(left) {
      return {
        w: left + this.sizePosOrigin.w,
      };
    },
    getTopSize(top) {
      return {
        h: -top + this.sizePosOrigin.h,
        y: top + this.sizePosOrigin.y,
      };
    },
    getBottomSize(top) {
      return {
        h: top + this.sizePosOrigin.h,
      };
    },
    moveLeftPoint({ left }) {
      const sp = { ...this.sizePos, ...this.getLeftSize(left) };
      this.sizePos = { ...this.fetchValidSizePos(sp, "left") };
      this.emitChange();
    },
    moveRightPoint({ left }) {
      const sp = { ...this.sizePos, ...this.getRightSize(left) };
      this.sizePos = { ...this.fetchValidSizePos(sp, "right") };
      this.emitChange();
    },
    moveTopPoint({ top }) {
      const sp = { ...this.sizePos, ...this.getTopSize(top) };
      this.sizePos = { ...this.fetchValidSizePos(sp, "top") };
      this.emitChange();
    },
    moveBottomPoint({ top }) {
      const sp = { ...this.sizePos, ...this.getBottomSize(top) };
      this.sizePos = { ...this.fetchValidSizePos(sp, "bottom") };
      this.emitChange();
    },
    moveLeftTopPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getLeftSize(left),
        ...this.getTopSize(top),
      };
      this.sizePos = { ...this.fetchValidSizePos(sp, "left-top") };
      this.emitChange();
    },
    moveRightTopPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getRightSize(left),
        ...this.getTopSize(top),
      };
      this.sizePos = { ...this.fetchValidSizePos(sp, "right-top") };
      this.emitChange();
    },
    moveLeftBottomPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getLeftSize(left),
        ...this.getBottomSize(top),
      };
      this.sizePos = { ...this.fetchValidSizePos(sp, "left-bottom") };
      this.emitChange();
    },
    moveRightBottomPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getRightSize(left),
        ...this.getBottomSize(top),
      };
      this.sizePos = { ...this.fetchValidSizePos(sp, "right-bottom") };
      this.emitChange();
    },
    moveOver() {
      this.sizePosOrigin = { ...this.sizePos };
      this.lastSizePos = { ...this.sizePos };
      if (this.positionType === "relative")
        this.offsetTopOrigin = this.$el.offsetTop < 0 ? 0 : this.$el.offsetTop;
      this.emitChange();
      this.$emit("resize-over", this.sizePos);
    },
    moveStart() {
      this.$emit("on-click");
    },
    moveElement({ left, top }) {
      if (!this.move) return;

      const sp = {
        ...this.sizePos,
        ...{
          x: left + this.sizePosOrigin.x,
          y: top + this.sizePosOrigin.y,
        },
      };
      this.sizePos = { ...this.fetchValidSizePos(sp, "move") };
      this.emitChange();
    },
    moveElementOver() {
      if (!this.move) return;
      this.moveOver();
    },
    emitChange() {
      this.$emit("input", this.sizePos);
      this.$emit("change", this.sizePos);
    },
  },
};
</script>

<style lang="scss" scope>
.element-resize {
  position: static;
  z-index: auto;
  background: #fff;
  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: 8px;
    height: 8px;
    border-radius: 50%;
    background: #617bea;
    z-index: 99;
    &-l {
      left: 0;
      top: 50%;
      width: 5px;
      height: 20px;
      margin-top: -10px;
      margin-left: -3px;
      border-radius: 0;
      padding-top: 3px;
      cursor: w-resize;
      text-align: center;
      color: #fff;

      &::before {
        content: ".";
        display: block;
        font-size: 16px;
        line-height: 1;
        margin-top: -9px;
      }
      &::after {
        content: ".";
        display: block;
        font-size: 16px;
        line-height: 1;
        margin-top: -9px;
      }
    }
    &-lt {
      left: 0;
      top: 0;
      margin-top: -5px;
      margin-left: -5px;
      cursor: nw-resize;
    }
    &-lb {
      left: 0;
      bottom: 0;
      margin-bottom: -5px;
      margin-left: -5px;
      cursor: sw-resize;
    }
    &-r {
      right: 0;
      top: 50%;
      width: 5px;
      height: 20px;
      margin-top: -10px;
      margin-right: -3px;
      cursor: e-resize;
      border-radius: 0;
      padding-top: 3px;
      text-align: center;
      color: #fff;

      &::before {
        content: ".";
        display: block;
        font-size: 16px;
        line-height: 1;
        margin-top: -9px;
      }
      &::after {
        content: ".";
        display: block;
        font-size: 16px;
        line-height: 1;
        margin-top: -9px;
      }
    }
    &-rt {
      right: 0;
      top: 0;
      margin-top: -5px;
      margin-right: -5px;
      cursor: ne-resize;
    }
    &-rb {
      right: 0;
      bottom: 0;
      margin-bottom: -5px;
      margin-right: -5px;
      cursor: se-resize;
    }
    &-t {
      left: 50%;
      top: 0;
      width: 30px;
      height: 5px;
      border-radius: 0;
      margin-top: -3px;
      margin-left: -15px;
      cursor: n-resize;
      text-align: center;
      color: #fff;
      &::before {
        content: "...";
        display: inline-block;
        vertical-align: top;
        font-size: 16px;
        line-height: 1;
        margin-top: -10px;
      }
    }
    &-b {
      left: 50%;
      bottom: 0;
      width: 30px;
      height: 5px;
      border-radius: 0;
      margin-bottom: -3px;
      margin-left: -15px;
      cursor: s-resize;
      text-align: center;
      color: #fff;

      &::before {
        content: "...";
        display: inline-block;
        vertical-align: top;
        font-size: 16px;
        line-height: 1;
        margin-top: -10px;
      }
    }
  }
  .control-line {
    position: absolute;
    z-index: 98;

    &-left {
      height: 100%;
      left: -1px;
      top: 0;
      border-left: 1px solid #617bea;
    }
    &-right {
      height: 100%;
      right: -1px;
      top: 0;
      border-left: 1px solid #617bea;
    }
    &-top {
      width: 100%;
      left: 0;
      top: -1px;
      border-top: 1px solid #617bea;
    }
    &-bottom {
      width: 100%;
      left: 0;
      bottom: -1px;
      border-top: 1px solid #617bea;
    }
  }

  &-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>