<script setup lang="ts">
import { ElPopover } from 'element-plus'
import type { VxeTableInstance } from 'vxe-table'
import type { PopoverProps, TagProps } from 'element-plus'

interface TableColumn {
  // 类型
  type?: string
  // 列名
  field?: string
  // 表格宽度
  width?: number | string
  // 对齐方式
  align?: string
  // 标题
  title?: string
  // 插槽
  slot?: string
}

interface TableConfig {
  // 表格列
  column?: Array<TableColumn>
  // 表格数据
  request: (...args: any) => Promise<any>
  // 每页显示条数
  pageSize?: Number
}

interface TableSelectProps {
  // 绑定值
  modelValue: any
  // 是否多选
  multiple?: boolean
  // 是否禁用
  disabled?: boolean
  // 是否支持清除
  clearable?: boolean
  // value 的属性名
  valueKey?: string
  // label 的属性名
  labelKey?: string
  // 回显数据，用于后端分页显示
  initValue?: any
  // 气泡位置
  placement?: PopoverProps['placement']
  // 占位符
  placeholder?: string
  // popover 宽度
  popperWidth?: number | string
  // 自定义 popper 类名
  popperClass?: string
  // popper 配置项
  popperOptions?: PopoverProps['popperOptions']
  // 表格配置
  tableConfig?: TableConfig
  // tag 类型
  tagType?: TagProps['type']
  // 最多显示多少个tag
  maxTagCount?: number
  // 自定义数据转换方法
  transformData?: Function
}

const props = withDefaults(defineProps<TableSelectProps>(), {
  modelValue: '',
  clearable: true,
  valueKey: 'id',
  labelKey: 'name',
  placement: 'bottom-start',
  placeholder: '请选择',
  popperWidth: 560,
  tagType: 'info',
  maxTagCount: 5
})
const emits = defineEmits(['update:modelValue', 'change', 'clear', 'item-clear', 'focus', 'blur'])

/* 格式化提交数据 */
const transformData = (value: any) => {
  let result = ''
  if (value) {
    if (Array.isArray(value)) {
      result = value
        .map(item => {
          return JSON.stringify({
            [props.valueKey]: item[props.valueKey],
            [props.labelKey]: item[props.labelKey]
          })
        })
        .join(',')
    } else {
      result = JSON.stringify({ [props.valueKey]: value[props.valueKey], [props.labelKey]: value[props.labelKey] })
    }
  }
  return result
}

// popover 实例
const popoverRef = ref<InstanceType<typeof ElPopover>>()
// 表格实例
const tableRef = ref<VxeTableInstance<any>>()
// 是否显示
const visible = ref(false)
// 选中展示的数据
const selectLabel = ref<string | Array<any>>('')
// 加载状态
const loading = ref(false)
// 表格数据
const tableData = ref<any[]>([])
// 页码
const pageNo = ref<number>(1)
// 总数
const tableTotal = ref<number>(0)

// 是否未选中
const isEmpty = computed<boolean>(() => {
  return props.modelValue == null || props.modelValue === ''
})

// 是否需要清空图标
const closeEnable = computed<boolean>(() => {
  return props.clearable && !props.disabled && !isEmpty.value
})

// 多选显示的标签
const currentValues = computed(() => {
  if (selectLabel.value == null) {
    return selectLabel.value ?? []
  }
  return selectLabel.value.slice(0, props.maxTagCount)
})
// 多选折叠的标签
const omittedValues = computed(() => {
  if (selectLabel.value == null) {
    return []
  }
  return selectLabel.value.slice(props.maxTagCount)
})

/* 打开弹窗 */
const onFocus = (e: FocusEvent) => {
  emits('focus', e)
}

/* 关闭弹窗 */
const onBlur = (e: FocusEvent) => {
  emits('blur', e)
}

/* 清除事件 */
const onClear = () => {
  updateModelValue('')
  selectLabel.value = ''
  // 取消表格全部选中
  tableRef.value?.clearCheckboxRow()
  emits('clear')
}

/* 单个清除事件 */
const onItemClear = (item: any) => {
  const list = [...(selectLabel.value as Array<any>)]
  const index = list.findIndex(x => x[props.labelKey] === item[props.labelKey])
  list.splice(index, 1)
  selectLabel.value = list

  // 取消表格选中数据
  tableRef.value?.toggleCheckboxRow(item)
  updateModelValue(list)
  emits('item-clear', { item, list })
}

/* 表格单选事件 */
const tableRadioChange = (data: any) => {
  selectLabel.value = data.row[props.labelKey]
  visible.value = false
  // 发出选择事件
  updateModelValue(data.row)
  emits('change', data.row)
}

/* 表格多选择事件 */
const tableCheckboxChange = (data: any) => {
  let result: Array<any> = []
  if (data.checked) {
    // 使用 Set 去重
    const uniqueArray = Array.from(
      new Set([...selectLabel.value, ...data.records].map((x: any) => JSON.stringify(x)))
    ).map((str: any) => JSON.parse(str))
    selectLabel.value = uniqueArray
    result = uniqueArray
  } else {
    const selects = selectLabel.value as Array<any>
    const index = selects.findIndex(x => x[props.valueKey] === data.row[props.valueKey])
    selects?.splice(index, 1)
    result = selects
  }
  // 发出选择事件
  updateModelValue(result)
  emits('change', selectLabel.value)
}

/* initValue 改变 */
const initValueChange = (value: any) => {
  // 如果是字符串，则解析成对象
  value = typeof value === 'string' ? JSON.parse(value) : value
  if (props.initValue) {
    // 处理回显数据
    if (props.multiple) {
      selectLabel.value = value
    } else {
      selectLabel.value = value[props.labelKey] as string
    }
    updateModelValue(value)
  }
}

/* 分页改变事件 */
const paginationChange = (data: number) => {
  pageNo.value = data
  request()
}

/* 表格请求完成 */
const tableDone = () => {
  nextTick(() => {
    const newTableData = tableRef.value?.getTableData().tableData
    newTableData?.forEach(item => {
      if (props.multiple) {
        const temp =
          Array.isArray(selectLabel.value) && selectLabel.value.find(x => x[props.valueKey] === item[props.valueKey])
        temp && tableRef.value?.setCheckboxRow(item, true)
      } else {
        const temp = item[props.valueKey] === (props.modelValue && JSON.parse(props.modelValue)[props.valueKey])
        temp && tableRef.value?.setRadioRow(item)
      }
    })
  })
}

/* 请求数据 */
const request = (where?: any) => {
  if (typeof props.tableConfig?.request === 'function') {
    loading.value = true
    if (where && where.pageNo) {
      pageNo.value = where.pageNo
    }
    props.tableConfig
      .request({
        pageNo: pageNo.value,
        pageSize: props.tableConfig.pageSize,
        ...where
      })
      .then((res: any) => {
        tableTotal.value = res.total || res.totalCount
        tableData.value = res.infos || res.list || res.records || res.data || res.info
        tableDone()
      })
      .catch((e: any) => {
        console.warn(e)
      })
      .finally(() => {
        loading.value = false
      })
  } else {
    console.warn('tableConfig.request 必须为 Promise')
  }
}
request()

/* 更新选中值 */
const updateModelValue = (value: any) => {
  if (value) {
    emits('update:modelValue', props.transformData ? props.transformData(value) : transformData(value))
  } else {
    emits('update:modelValue', '')
  }
}

/* 更新气泡位置 */
watch(currentValues, () => {
  if (
    popoverRef.value &&
    popoverRef.value.popperRef &&
    popoverRef.value.popperRef.popperInstanceRef &&
    popoverRef.value.popperRef.popperInstanceRef.update
  ) {
    popoverRef.value.popperRef.popperInstanceRef.update()
  }
})

watch(
  () => props.initValue,
  () => {
    initValueChange(props.initValue)
  },
  { immediate: true }
)

defineExpose({
  request
})
</script>

<template>
  <el-popover
    ref="popoverRef"
    v-model:visible="visible"
    :placement="placement"
    :width="popperWidth"
    :popper-class="popperClass"
    :popper-options="popperOptions"
    :disabled="disabled"
    trigger="click"
    transition="el-zoom-in-top"
    teleported
  >
    <template #reference>
      <div class="table-select-container" :class="{ 'is-multiple': multiple, 'is-visible': visible }">
        <el-input
          :disabled="disabled"
          :placeholder="multiple && !isEmpty ? '' : placeholder"
          :readonly="true"
          :validateEvent="false"
          :modelValue="multiple ? '' : selectLabel"
          @focus="onFocus"
          @blur="onBlur"
        >
          <template v-if="$slots.prefix" #prefix>
            <slot name="prefix"></slot>
          </template>
          <template #suffix>
            <div class="select-suffix">
              <ElIcon v-if="closeEnable" class="select-clear" @click.stop="onClear">
                <slot name="clearIcon">
                  <CircleClose />
                </slot>
              </ElIcon>
              <ElIcon class="select-down" :class="{ 'select-down-rotate': visible }" v-else>
                <slot name="suffixIcon">
                  <ArrowDown />
                </slot>
              </ElIcon>
            </div>
          </template>
        </el-input>
        <div class="table-multiple-tag" :class="{ 'is-disabled': disabled }" v-if="multiple">
          <el-tag
            :type="tagType"
            size="small"
            disable-transitions
            :closable="!disabled"
            style="margin-right: 5px"
            v-for="item in currentValues"
            :key="item[valueKey]"
            @close="onItemClear(item)"
          >
            {{ item[labelKey] }}
          </el-tag>
          <el-tag v-if="omittedValues && omittedValues.length" size="small" disable-transitions :type="tagType">
            + {{ omittedValues.length }} ...
          </el-tag>
        </div>
      </div>
    </template>
    <!-- 上方插槽 -->
    <slot name="top-extra"></slot>
    <vxe-table
      :data="tableData"
      :row-config="{ isCurrent: true, isHover: true }"
      :radio-config="{ trigger: multiple ? '' : 'row' }"
      :checkbox-config="{ trigger: multiple ? 'row' : '' }"
      ref="tableRef"
      @radio-change="tableRadioChange"
      @checkbox-all="tableCheckboxChange"
      @checkbox-change="tableCheckboxChange"
      v-loading="loading"
    >
      <vxe-column type="checkbox" width="60" v-if="multiple"></vxe-column>
      <vxe-column type="radio" width="40" v-else> </vxe-column>
      <vxe-column v-for="(item, index) in tableConfig?.column" :key="index" v-bind="item">
        <template #default="slotProps" v-if="item.slot">
          <slot :name="item.slot" v-bind="slotProps || {}"></slot>
        </template>
      </vxe-column>
    </vxe-table>
    <div class="table-select-pagination" v-if="tableTotal">
      <el-pagination
        background
        layout="total, prev, pager, next, jumper"
        size="small"
        :pager-count="5"
        :page-size="tableConfig && tableConfig.pageSize"
        :total="tableTotal"
        v-model:current-page="pageNo"
        @current-change="paginationChange"
      />
    </div>
    <!-- 下方插槽 -->
    <slot name="bottom-extra"></slot>
  </el-popover>
</template>

<style scoped lang="scss">
.table-select-container {
  width: 100%;
  position: relative;

  .select-clear {
    position: absolute;
    right: 10px;
    top: 49%;
    transform: translateY(-50%);
    cursor: pointer;
    border-radius: 50%;
    overflow: hidden;
    z-index: 2;
    opacity: 0;
    &:hover {
      opacity: 100;
    }
  }
  .select-down {
    transition: transform 0.2s;
  }
  .select-down-rotate {
    transform: rotate(180deg);
  }

  &.is-multiple {
    :deep(.el-input) {
      position: absolute;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
    }
  }
  &.is-visible {
    :deep(.el-input:not(.is-disabled) .el-input__wrapper) {
      box-shadow: 0 0 0 1px var(--el-color-primary) inset;
    }
  }

  &:hover {
    .select-clear {
      opacity: 1;
    }
  }
}

.table-multiple-tag {
  position: relative;
  top: 0px;
  width: calc(100% - 24px);
  height: 100%;
  min-height: 34px;
  z-index: 2;
  padding: 0px 10px 2px 10px;
  box-sizing: border-box;
  &.is-disabled {
    cursor: not-allowed;
  }
}
.table-select-pagination {
  display: flex;
  justify-content: center;
  margin-top: 12px;
}
</style>
