<template>
  <teleport :to="mount">
    <div class="ui-floating" :style="style" :class="props.class" @mouseenter="emit('enter')" @mouseleave="emit('leave')">
      <slot />
    </div>
  </teleport>
</template>

<script setup>
import { defineProps, defineExpose, onUnmounted, reactive, watch, computed, onUpdated, onActivated } from "vue";
import { getPostionByTarget, getScrollParents, getZIndex } from "../utils";

const Horizontal = {
  center: "center",
  right: "right",
  left: "left",
};
const Vertical = {
  center: "center",
  top: "top",
  bottom: "bottom",
};
const Divide = "-";

const props = defineProps({
  mount: {
    require: true,
    default: document.body,
  },
  class: { type: String },
  refer: { type: Object },
  dire: { type: String },
  width: { type: [Number, String] },
  height: { type: [Number, String] },
});
const emit = defineEmits(["leave", "enter"]);

// 确定方向
const dires = computed(() => {
  const dire = props.dire || `${Vertical.bottom}${Divide}${Horizontal.left}`;
  const isPreset = (preset, val) => Object.keys(preset).some((key) => preset[key] === val);

  let [horizontal, vertical] = dire.split(Divide);

  if (!horizontal || !isPreset(Horizontal, horizontal)) {
    horizontal = Horizontal.left;
  }
  if (!vertical || !isPreset(Vertical, vertical)) {
    vertical = Vertical.bottom;
  }
  return [horizontal, vertical];
});

const normalizeUnit = (unit, total) => {
  if (unit === void 0) {
    return void 0;
  } else if (typeof unit === "number") {
    return unit ? ((unit <= 1) & (unit >= 0) ? total * unit : unit) : void 0;
  } else if (unit.includes("px")) {
    return normalizeUnit(parseFloat(unit), total);
  } else if (unit.includes("%")) {
    return normalizeUnit(parseFloat(unit) / 100, total);
  }
};

const width = computed(() => props.refer && normalizeUnit(props.width, props.refer.offsetWidth));
const height = computed(() => props.refer && normalizeUnit(props.height, props.refer.offsetHeight));

const location = reactive({ x: 0, y: 0 });
const scrollParents = computed(() => (props.refer ? getScrollParents(props.refer, props.mount) : []));

watch(
  [scrollParents, props],
  ([newParents], [oldParents]) => {
    oldParents && oldParents.forEach((dom) => dom.removeEventListener("scroll", updateLocation));
    newParents.forEach((dom) => dom.addEventListener("scroll", updateLocation));
    if (props.refer) {
      setTimeout(() => updateLocation());
    }
  },
  { immediate: true }
);

const zIndex = getZIndex();

const style = computed(() => {
  let style = {
    width: width.value && width.value + "px",
    height: height.value && height.value + "px",
    left: location.x + "px",
    top: location.y + "px",
    zIndex: zIndex,
  };
  if (location.x > 0 && location.y > 0) {
    return style;
  }
});

const updateLocation = () => {
  const pos = getPostionByTarget(props.refer, props.mount);
  const screenInfo = scrollParents.value.reduce(
    (t, c) => {
      t.y += c.scrollTop;
      t.x += c.scrollLeft;
      return t;
    },
    { x: 0, y: 0 }
  );

  const [horizontal, vertical] = dires.value;
  const start = {
    x: pos.x - screenInfo.x,
    y: pos.y - screenInfo.y,
  };

  switch (horizontal) {
    case Horizontal.left:
      location.x = start.x;
      break;
    case Horizontal.right:
      location.x = start.x + pos.width;
      break;
    case Horizontal.center:
      location.x = start.x + pos.width / 2;
      break;
  }

  switch (vertical) {
    case Vertical.top:
      location.y = start.y;
      break;
    case Vertical.bottom:
      location.y = start.y + pos.height;
      break;
    case Vertical.center:
      location.y = start.y + pos.height / 2;
      break;
  }
};

window.addEventListener("resize", updateLocation);
onUnmounted(() => {
  scrollParents.value.forEach((dom) => dom.removeEventListener("scroll", updateLocation));
  window.removeEventListener("resize", updateLocation);
});

onActivated(() => {
  if (props.refer) {
    updateLocation();
  }
});

defineExpose({
  updateLocation,
});
</script>

<script>
export default { name: "ui-floating" };
</script>

<style lang="scss">
.ui-floating {
  position: absolute;
}

.select-float {
  transition: transform 0.3s ease, opacity 0.3s ease;
  transform-origin: center top;

  &:not(.show) {
    transform: scale(1, 0);
    opacity: 0;
    pointer-events: none;
  }
  &.show {
    transform: scale(1, 1);
    opacity: 1;
  }
}

.select-replace {
  --colors-content-color: #fff;
  list-style: none;
  max-height: 288px;
  background: rgba(26, 26, 26, 0.8);
  overflow-y: auto;
  color: var(--colors-content-color);
  background: rgba(182, 30, 34, 0.9);
  border-radius: 10px;
  margin-top: 8px;
  &.hide-scroll {
    max-height: 100%;
  }
  li {
    padding: 10px 14px;
    font-size: 14px;

    &.un-data {
      padding: 20px;
      color: rgba(255, 255, 255, 0.3);
      pointer-events: none;
    }
    &.active {
      color: var(--font-active);
    }

    &:not(.active):hover {
      cursor: pointer;
      color: var(--font-active);
    }
  }
}
</style>
