<script setup>
import {
  nextTick,
  onMounted,
  onUnmounted,
  ref,
  toRefs,
  watch,
  watchEffect,
} from "vue";
import InfiniteLoading from "v3-infinite-loading";
import "v3-infinite-loading/lib/style.css";
import { debounce } from "lodash-es"; //required if you're not going to override default slots
import { ElInput, ElPopover, ElScrollbar } from "element-plus";

const props = defineProps({
  modelValue: {},
  remoteMethod: {
    type: Function,
    required: true,
  },
  size: {
    type: String,
  },
  width: {
    type: Number,
  },
  prefixIcon: { type: String },
  query: {
    type: Object,
    required: true,
  },
  prop: {
    type: Object,
    required: true,
  },
  placeholder: {
    type: String,
    default: "请选择",
  },
  defaultLabel: {
    type: String,
  },
});

const { modelValue } = toRefs(props);
const loadKey = ref(0);
const emit = defineEmits(["update:modelValue", "change", "confirm"]);

const showPopOver = ref(false);

const inputRefWhenShowPop = ref();
const placeholderWhenShowPop = ref("");
const placeholderWhenNotShowPop = ref("");
const optionLabelWhenShowPop = ref("");
const optionLabelWhenNotShowPop = ref("");
const echoLabel = ref("");
const options = ref([]);
const page = ref(0);

const initOptions = (keyword) => {
  props
    .remoteMethod({
      [props.query.page]: page.value,
      [props.query.size]: 10,
      [props.query.searchKey]: keyword,
    })
    .then((rows) => {
      options.value = rows;
    });
};

const loadMore = async ($state) => {
  page.value++;
  props
    .remoteMethod({
      [props.query.page]: page.value,
      [props.query.size]: 10,
      [props.query.searchKey]: showPopOver.value
        ? optionLabelWhenShowPop.value ?? null
        : null,
    })
    .then((rows) => {
      options.value.push(...rows);
      if (rows.length < 10) {
        $state.complete();
      } else {
        $state.loaded();
      }
    })
    .catch(() => {
      $state.error();
    });
};

const handleInputClick = () => {
  showPopOver.value = true;
  placeholderWhenShowPop.value = optionLabelWhenNotShowPop.value;
  nextTick(() => {
    if (inputRefWhenShowPop.value) {
      inputRefWhenShowPop.value.focus();
    }
  });
};

/**
 * 清除选项
 */
const handleClearSeletion = () => {
  emit("update:modelValue", null);
};
/**
 * 点击选项
 * @param option
 */
const selectOption = (option) => {
  emit("update:modelValue", option[props.prop.value]);
  emit("confirm", option);
  /*重新加载选项*/
  if (optionLabelWhenShowPop.value) {
    page.value = 0;
    options.value = [];
    loadKey.value++;
  }

  showPopOver.value = false;

  echoLabel.value = option[props.prop.label];
  optionLabelWhenShowPop.value = "";
};

/**
 * 输入时,搜索选项
 * @type {DebouncedFuncLeading<(function(*): void)|*> | DebouncedFunc<(function(*): void)|*>}
 */
const handleInputChange = debounce((value) => {
  page.value = 0;
  options.value = [];
  loadKey.value++;
}, 500);

watchEffect(() => {
  if (showPopOver.value) return;
  if (modelValue.value) {
    optionLabelWhenNotShowPop.value =
      options.value.find((el) => el[props.prop.value] === modelValue.value)?.[
        props.prop.label
      ] ??
      echoLabel.value ??
      props.defaultLabel ??
      "...";
  } else {
    optionLabelWhenNotShowPop.value = "";
    placeholderWhenNotShowPop.value = props.placeholder;
  }
});

watch(modelValue, (value) => {
  emit("change", value);
});

/**
 * 点击空白关闭弹出选项列表
 * @param event
 */
const handleBodyClick = (event) => {
  if (showPopOver.value) {
    if (optionLabelWhenShowPop.value) {
      page.value = 0;
      options.value = [];
      loadKey.value++;
    }
    showPopOver.value = false;
    optionLabelWhenShowPop.value = "";
  }
};

onMounted(() => {
  page.value++;
  initOptions();
  document.body.addEventListener("click", handleBodyClick);
});

onUnmounted(() => {
  document.body.removeEventListener("click", handleBodyClick);
});
</script>

<template>
  <el-popover
    :popper-style="{
      padding: 0,
    }"
    :visible="showPopOver"
    :width="width ?? 240"
    placement="bottom"
  >
    <template #reference>
      <div :style="`width: ${width ?? 240}px`">
        <!--选项显示时-->
        <el-input
          v-if="showPopOver"
          ref="inputRefWhenShowPop"
          v-model="optionLabelWhenShowPop"
          :placeholder="placeholderWhenShowPop"
          :prefix-icon="prefixIcon"
          :size="size ?? 'default'"
          class="select-inner"
          suffix-icon="ArrowUp"
          @input="handleInputChange"
          @click.stop
        >
          <template #prefix>
            <slot name="prefix" />
          </template>
        </el-input>
        <!--选项隐藏时-->
        <el-input
          v-else
          v-model="optionLabelWhenNotShowPop"
          :placeholder="placeholderWhenNotShowPop"
          :prefix-icon="prefixIcon"
          :size="size ?? 'default'"
          class="select-inner"
          clearable
          suffix-icon="ArrowDown"
          @clear="handleClearSeletion"
          @click.stop="handleInputClick"
        >
          <!--            @blur="handleInputBlur"-->
          <template #prefix>
            <slot name="prefix" />
          </template>
        </el-input>
      </div>
    </template>
    <el-scrollbar class="options-wrap" height="260" @click.stop>
      <ul class="options">
        <li
          v-for="option in options"
          :key="option[prop.value]"
          :class="`option-item ${
            option[prop.value] === modelValue ? 'selected' : null
          }`"
          @click.stop="selectOption(option)"
        >
          {{ option[prop.label] }}
        </li>
      </ul>
      <infinite-loading :identifier="loadKey" @infinite="loadMore">
        <template #spinner>
          <div style="text-align: center; font-size: 12px">......</div>
        </template>
        <template #complete>
          <div
            style="display: flex; justify-content: center; align-items: center"
          >
            -
          </div>
        </template>
      </infinite-loading>
    </el-scrollbar>
  </el-popover>
</template>

<style lang="scss" scoped>
.options-wrap {
  margin: 6px 0;

  .options {
    list-style: none;
    margin: 0;
    padding: 0;

    .option-item {
      height: 32px;
      cursor: pointer;
      font-size: 14px;
      display: flex;
      align-items: center;
      padding: 0 32px 0 20px;
      font-weight: 700;

      &.selected {
        color: #409eff;
      }

      &:hover {
        background-color: #f5f7fa;
      }
    }
  }
}

.select-inner {
  cursor: pointer;
}
</style>
