<template>
  <section class="resizable-layout-item" :style="styleObj" ref="layoutItemRef">
    <div class="content">
      <slot></slot>
    </div>
    <div
      class="drag-bar"
      :class="[isColumn ? 'column' : 'horizontal', dragBarActive ? 'active' : '']"
      :style="dragBarStyleObj"
      @mousedown.prevent="handleDragBarMouseDown"
      ref="dragBarRef"
    >
      <div class="drag-bar-inner"></div>
      <div class="drag-handle" :style="dragHandleStyleObj">
        <i class="dot" />
        <i class="dot" />
        <i class="dot" />
      </div>
    </div>
  </section>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { Emitter } from 'mitt';
import { debounce } from 'lodash';
import { EmitterType } from '@/constants/emitter';
import ProvideInject from '@/constants/provideInject';

export default defineComponent({
  name: 'resizable-layout-item',
  props: {
    // flex 子布局对应的权重
    weight: {
      type: Number,
      default: 1,
    },
    // 最小宽度，仅在水平布局时有效
    minWidth: {
      type: Number,
      default: 32,
    },
    // 最小高度，仅在垂直布局时有效
    minHeight: {
      type: Number,
      default: 32,
    },
  },
  data() {
    return {
      dragBarActive: false,
    };
  },
  emits: ['resize'],
  inject: [ProvideInject.DIRECTION, ProvideInject.DEBOUNCE, ProvideInject.MAX_WAIT],
  computed: {
    // 是否是垂直布局
    isColumn(): boolean {
      return (this as any).direction === 'column';
    },

    // 最小尺寸，水平布局时为最小宽度，垂直布局时为最小高度
    minSize(): string {
      return (this as any).isColumn ? 'minHeight' : 'minWidth';
    },

    // layout-item 的样式
    styleObj(): any {
      return {
        flexGrow: `${this.weight}`,
        [this.minSize]: `${(this as any)[this.minSize]}px`, // minWidth: `${props.minWidth}px`,
        flexDirection: (this as any).direction,
      };
    },

    // 拖拽条的样式
    dragBarStyleObj(): any {
      const self = this as any;
      const width = self.isColumn ? '100%' : '5px';
      const height = self.isColumn ? '5px' : '100%';
      const cursor = self.isColumn ? 'ns-resize' : 'ew-resize';
      const flexDirection = self.isColumn ? 'row' : 'column';
      return { width, height, cursor, flexDirection };
    },

    // 拖拽手柄的样式
    dragHandleStyleObj() {
      const self = this as any;
      const width = self.isColumn ? '36px' : '9px';
      const height = self.isColumn ? '9px' : '36px';
      const cursor = self.isColumn ? 'ns-resize' : 'ew-resize';
      return { width, height, cursor };
    },
  },
  created() {
    const self = this as any;
    self.emitter = null;
    self.debouncedEmit = debounce(
      () => {
        const size = this.isColumn
          ? (this.$refs.layoutItemRef as HTMLElement).offsetHeight
          : (this.$refs.layoutItemRef as HTMLElement).offsetWidth;
        this.$emit('resize', size);
      },
      self.debounce,
      { maxWait: self.maxWait }
    );
  },
  mounted() {
    // 向父组件要emitter实例
    this.RequestEmitter();
  },
  unmounted() {
    (this as any).$unmounted = true; // 标记该对象已经 unmounted
  },
  methods: {
    // 获取当前 layoutItem 在父元素中的索引
    getLayoutItemIndex() {
      const index = (this.$refs.layoutItemRef as HTMLElement).getAttribute('data-index') as string;
      return parseInt(index, 10);
    },

    // 约定大写字母开头表示为供外界调用的方法
    // 调整 flex-grow 的大小
    AdjustFlexGrow(value: number) {
      (this.$refs.layoutItemRef as HTMLElement).style.flexGrow = `${value}`;
    },

    // 约定大写字母开头表示为供外界调用的方法
    // 父组件调用该方法，获取到父组件的emitter实例
    RegisterEmitter(mitter: Emitter) {
      (this as any).emitter = mitter;
    },

    // 约定大写字母开头表示为供外界调用的方法
    // 向外触发 resize 事件
    EmitResize() {
      (this as any).debouncedEmit();
    },

    // 约定大写字母开头表示为供外界调用的方法
    // 向父组件要emitter实例
    RequestEmitter() {
      setTimeout(() => {
        if (this.$parent && this.$refs.layoutItemRef) {
          (this.$parent as any).GiveEmitter({
            instance: this,
            index: this.getLayoutItemIndex(),
            minSize: (this as any)[this.minSize],
          } as ResizableLayout.IDataParam);
        }
      }, 500);
    },

    // 鼠标松开时，移除 mouse 事件
    detachMouseEvent() {
      document.onmouseup = () => {
        document.onmousemove = null;
        document.onmouseup = null;

        // 加入 setTimeout 是为了解决快速拖动导致布局抖动的问题
        setTimeout(() => {
          this.dragBarActive = false;
        }, 150);
        return false;
      };
    },
    // 向外触发 drag-bar-move
    emitMoveEvent(distance: number, moveId: symbol) {
      (this as any).emitter &&
        (this as any).emitter.emit(EmitterType.DRAG_BAR_MOVE, {
          index: this.getLayoutItemIndex(),
          distance,
          moveId,
        } as ResizableLayout.IMoveParam);
    },
    // 拖拽条移动
    handleDragBarMouseDown(downEvent: MouseEvent) {
      this.dragBarActive = true;
      const moveId = Symbol('move事件的唯一标志');
      if ((this as any).direction !== 'column') {
        // 水平布局
        const originClientX = downEvent.clientX; // 拖动条距离视口左边的距离
        document.onmousemove = (moveEvent: MouseEvent) => {
          const distance = moveEvent.clientX - originClientX; // 当前鼠标移动的距离
          // 告诉父组件鼠标移动的距离
          this.emitMoveEvent(distance, moveId);
          return false;
        };
      } else {
        // 垂直布局
        const originClientY = downEvent.clientY; // 拖动条距离视口左边的距离
        document.onmousemove = (moveEvent: MouseEvent) => {
          const distance = moveEvent.clientY - originClientY; // 当前鼠标移动的距离
          // 告诉父组件鼠标移动的距离
          this.emitMoveEvent(distance, moveId);
          return false;
        };
      }
      this.detachMouseEvent();
    },
  },
});
</script>

<style lang="scss" scoped>
.resizable-layout-item {
  display: flex;
  flex-basis: 0;

  > .content {
    width: 100%;
    height: 100%;
    overflow: hidden;
    flex-grow: 1;
  }

  > .drag-bar {
    position: relative;
    z-index: 1000;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-grow: 0;
    flex-shrink: 0;

    &.horizontal {
      &:hover,
      &.active {
        .drag-bar-inner {
          transform: scaleX(3);
        }

        .drag-handle {
          padding: 9px 0;
          flex-direction: column;
        }
      }
    }

    &.column {
      &:hover,
      &.active {
        .drag-bar-inner {
          transform: scaleY(3);
        }

        .drag-handle {
          padding: 0 9px;
          flex-direction: row;
        }
      }
    }

    &:hover,
    &.active {
      .drag-handle {
        display: flex;
      }
    }

    .drag-bar-inner {
      position: absolute;
      left: 0;
      right: 0;
      top: 0;
      bottom: 0;
    }

    .drag-handle {
      display: none;
      justify-content: space-around;
      align-items: center;
      position: relative;
      background-color: #f1f4f9;
      border: 1px solid #ddd;

      .dot {
        width: 3px;
        height: 3px;
        border-radius: 50%;
        background-color: #8a9194;
      }
    }
  }
}
</style>
