<template>
  <div class="user-select-box w-100%">
    <div
      class="user-input"
      :class="[
        { disabled, 'cursor-not-allowed!': disabled && !props.column?.multiple },
        type,
        size
      ]"
      @click="openTableSelect"
    >
      <template v-if="selectId.length">
        <div class="user-text-list" v-if="props.column?.multiple">
          <el-tag
            v-for="id in selectId"
            :key="id"
            :closable="!disabled && !column.readonly"
            type="info"
            :size="size"
            @close="tagValueClose(id)"
          >
            {{ getCurrText(id) }}
          </el-tag>
        </div>
        <div v-else class="px-11px overflow-hidden text-ellipsis ws-nowrap">
          {{ getCurrText(selectId[0]) }}
          <span
            v-if="
              ['add', 'edit'].includes(type) &&
              props.column?.clearable !== false &&
              !disabled &&
              !column.readonly
            "
            class="user-input-clear pos-absolute right-11px top-50% cursor-pointer hidden"
            style="transform: translateY(-50%)"
            @click.stop="tagValueClose(selectId[0])"
          >
            <Icon :size="14" color="#909399" icon="ep:circle-close"></Icon>
          </span>
        </div>
      </template>
      <div class="empty-text" v-else>{{ placeholderText }}</div>
      <div class="user-num" v-if="column.multiple && selectId.length >= 2">
        {{ t('Avue.control.selectPrepend') }}
        <span>{{ selectId.length }}</span>
        {{ t('Avue.control.selectpeople') }}
      </div>
    </div>

    <DesignPopup
      v-if="['add', 'edit'].includes(type)"
      v-model="dialogData.value"
      v-model:isFull="dialogData.isFull"
      v-bind="dialogData.params"
    >
      <template #default>
        <el-container class="h-100%">
          <el-aside width="200px">
            <avue-tabs ref="tasbRef" :option="tabsOption" @change="tabsHandleChange"></avue-tabs>
            <div class="tree-box" v-show="tabValue.prop == 'dept'">
              <avue-tree
                ref="deptRef"
                :option="treeDeptOption"
                :data="currTreeDeptData"
                @node-click="nodeClick"
              >
                <template #default="{ data }">
                  <span
                    class="el-tree-node__label"
                    :class="{ active: data[treeDeptOption.props.value] == deptId }"
                  >
                    {{ data[treeDeptOption.props.label] }}
                  </span>
                </template>
              </avue-tree>
            </div>
            <div class="tree-box" v-show="tabValue.prop == 'role'">
              <avue-tree
                ref="roleRef"
                :option="treeRoleOption"
                :data="currRoleData"
                @node-click="nodeClick"
              >
                <template #default="{ data }">
                  <span
                    class="el-tree-node__label"
                    :class="{ active: data[treeRoleOption.props.value] == roleId }"
                  >
                    {{ data[treeRoleOption.props.label] }}
                  </span>
                </template>
              </avue-tree>
            </div>
          </el-aside>
          <el-main class="pb-0!">
            <avue-crud
              ref="crudRef"
              v-model:page="tablePage"
              v-model:search="tableSearch"
              :table-loading="loading"
              :option="Object.assign(tableOption, { calcHeight })"
              :data="tableData"
              @search-change="searchChange"
              @search-reset="resetChange"
              @size-change="sizeChange"
              @current-change="currentChange"
              @selection-change="selectionChange"
              @select-all="selectAll"
            >
              <!-- 单选 -->
              <template #lowSelectRadio="{ row, index }">
                <el-radio
                  class="low-select-radio"
                  v-model="radioValue"
                  :label="row.userId"
                  :disabled="!tableOption['selectable'](row)"
                  @click="radioClick(row, index)"
                />
              </template>
              <template #nickName="{ row }">
                <div class="flex items-center justify-center">
                  <el-avatar
                    class="mr-5px"
                    v-if="column.avatar && row.avatar"
                    :size="22"
                    :src="row.avatar"
                  />
                  <el-avatar class="mr-5px" v-else-if="column.avatar" :size="22">
                    {{ row.nickName && row.nickName.substring(0, 1) }}
                  </el-avatar>
                  <span>{{ row.nickName }}</span>
                </div>
              </template>
            </avue-crud>
          </el-main>
          <el-aside width="200px">
            <div
              class="h-100% flex-basis-200px flex-shrink-0"
              :style="{ borderLeft: 'var(--el-border)' }"
            >
              <div
                class="box-border h-31px pb-5px pt-5px text-center"
                :style="{ borderBottom: 'var(--el-border)' }"
              >
                <span>{{ t('Avue.control.selectPrepend') }}（{{ tableSelectId.length }}）</span>
                <el-button
                  class="mt--3px p-0"
                  type="primary"
                  link
                  v-if="tableSelectId.length"
                  @click="clearSelect"
                >
                  {{ t('Avue.crud.emptyBtn') }}
                </el-button>
              </div>
              <div
                class="box-border flex flex-wrap items-center overflow-x-hidden overflow-y-auto p-10px"
                :style="{
                  height: 'calc(100% - 31px)',
                  columnGap: '6px',
                  rowGap: '6px',
                  alignContent: 'start'
                }"
              >
                <el-tag
                  v-for="id in tableSelectId"
                  :key="id"
                  :closable="true"
                  type="info"
                  :size="size"
                  class="h-auto! py-4px! ws-break-spaces! break-anywhere! line-height-none"
                  @close="tagTableClose(id)"
                >
                  {{ getCurrText(id) }}
                </el-tag>
              </div>
            </div>
          </el-aside>
        </el-container>
      </template>
    </DesignPopup>
    <DesignPopup
      v-else
      v-model="detailDialog"
      :width="800"
      :title="`<span>${column.label}</span><span class='text-14px'>（${t('Avue.control.totalPrepend')} ${selectId.length} ${t('Avue.control.selectpeople')}）</span>`"
      :dialogParams="{ alignCenter: true }"
    >
      <template #default>
        <div class="box-border flex p-20px h-100%">
          <div
            class="box-border flex flex-wrap items-center overflow-x-hidden overflow-y-auto"
            :style="{
              height: '100%',
              columnGap: '6px',
              rowGap: '6px',
              alignContent: 'start'
            }"
          >
            <el-tag
              v-for="id in selectId"
              :key="id"
              type="info"
              :size="size"
              class="h-auto! py-4px! ws-break-spaces! break-anywhere! line-height-none"
            >
              {{ getCurrText(id) }}
            </el-tag>
          </div>
        </div>
      </template>
    </DesignPopup>
  </div>
</template>

<script setup lang="ts">
import { listToTree, filter } from '@/utils/tree'
import { useLowStoreWithOut } from '@/store/modules/low'
import * as DicApi from '@/api/design/dic/index'
import { cloneDeep } from 'lodash-es'
defineOptions({ name: 'UserSelect' })

/**
 * findType 参数解析
 * all 所有
 * now 本级
 * sub 本级和所有下级
 * direct_sub 直接下级
 * all_sub 所有下级
 */

interface Column {
  label: string
  findType: 'all' | 'now' | 'sub' | 'all_sub' | 'direct_sub' //查询类型
  columnKey: Array<'mobile' | 'email' | 'sex' | 'post' | 'deptName'> //扩展显示列
  disabledIds?: Array<string> //禁止选择的用户id
  assignDeptIds?: Array<string> //指定的部门
  assignRoleIds?: Array<string> //指定的角色
  filterDeptIds?: Array<string> //过滤的部门
  filterRoleIds?: Array<string> //过滤的角色
  defaultSearchDept?: string //默认部门id
  defaultSearchRole?: string //默认角色id
  hideTabsKey?: string[] //需要隐藏的tab：dept role
  requiredKey?: string[] //必填：dept role
  textFormatter?: string //回显名称格式化
  avatar?: boolean //是否显示头像
  multiple?: boolean //多选
  limit?: number //最大选择数
  separator?: string //分隔符
  readonly?: boolean
  placeholder?: string
  clearable?: boolean
}
interface Props {
  column: Column
  prop: string
  type: string
  size?: 'small' | 'large' | 'default'
  disabled?: boolean
  scope?: object
}
const props = defineProps<Props>()
const model = defineModel<string | number>()
const emit = defineEmits(['set-form-data'])
const { t } = useI18n()
const lowStore = useLowStoreWithOut()
const dicKey = 'userSelect'

const columnObj = {
  nickName: { label: t('Avue.control.userColumn.nickName'), search: true, minWidth: 100 },
  mobile: { label: t('Avue.control.userColumn.mobile'), search: true, minwidth: 120 },
  email: { label: t('Avue.control.userColumn.email'), minwidth: 140, overHidden: true },
  sex: { label: t('Avue.control.userColumn.sex'), minWidth: 50 },
  post: { label: t('Avue.control.userColumn.post'), minWidth: 100, overHidden: true },
  deptName: { label: t('Avue.control.dept'), minWidth: 100, overHidden: true }
}
const deptConfig = { id: 'deptId', pid: 'deptPid', children: 'children' }
const dialogData = ref({
  value: false,
  isFull: false,
  params: {
    title: t('Avue.control.select') + ' ' + (props.column.label || t('Avue.control.user')),
    footerBtn: [
      {
        params: {},
        name: t('Avue.common.cancelBtn'),
        icon: 'material-symbols:close-rounded',
        clickFun: () => {
          dialogData.value.value = false
        }
      },
      {
        params: { type: 'primary' },
        name: t('Avue.common.submitBtn'),
        icon: 'material-symbols:check-rounded',
        clickFun: () => {
          model.value = getCurrTableSelect('confirm').join(',')
          setTimeout(() => {
            dialogData.value.value = false
          }, 30)
        }
      }
    ]
  }
})
const detailDialog = ref(false)
const tabsOption = computed(() => {
  const option = {
    column: [
      { label: t('Avue.control.userColumn.tabsDept'), prop: 'dept' },
      { label: t('Avue.control.userColumn.tabsRole'), prop: 'role' }
    ]
  }
  const { hideTabsKey } = props.column
  if (hideTabsKey?.length) {
    option.column = option.column.filter((tab) => !hideTabsKey.includes(tab.prop))
  }

  return option
})

const tabValue = ref({ ...tabsOption.value.column[0] })

const treeDeptOption = ref({
  addBtn: false,
  menu: false,
  nodeKey: 'deptId',
  props: { label: 'deptName', value: 'deptId', childern: 'childern' }
})
const treeDeptData = ref([])
const currTreeDeptData = computed(() => {
  const { assignDeptIds, filterDeptIds } = props.column
  const isAssign = !!assignDeptIds?.length
  const isfilter = !!filterDeptIds?.length
  if (isAssign || isfilter) {
    return filter(
      treeDeptData.value,
      (item: any) => {
        if (isAssign && assignDeptIds.includes(item.deptId)) {
          return true
        }
        if (isfilter && !filterDeptIds.includes(item.deptId)) {
          return true
        }
        return false
      },
      deptConfig
    )
  } else return treeDeptData.value
})
const deptId = ref('')

const treeRoleOption = ref({
  addBtn: false,
  menu: false,
  nodeKey: 'roleId',
  props: { label: 'roleName', value: 'roleId' }
})
const roleData = ref([])
const currRoleData = computed(() => {
  const { assignRoleIds, filterRoleIds } = props.column
  const isAssign = !!assignRoleIds?.length
  const isfilter = !!filterRoleIds?.length
  if (isAssign || isfilter) {
    return roleData.value.filter((item: any) => {
      if (isAssign && assignRoleIds.includes(item.roleId)) {
        return true
      }
      if (isfilter && !filterRoleIds.includes(item.roleId)) {
        return true
      }
      return false
    })
  } else return roleData.value
})
const roleId = ref('')

const loading = ref(true)
const tableOption = ref({
  rowKey: 'userId',
  height: 'auto',
  addBtn: false,
  selection: true,
  refreshBtn: false,
  columnBtn: false,
  searchShowBtn: false,
  header: false,
  menu: false,
  reserveSelection: true,
  tip: false,
  searchMenuSpan: 8,
  searchSpan: 8,
  column: {},
  selectable: (row) => {
    if (props.column.limit) {
      const leng = tableSelect.value.length
      if (leng >= props.column.limit && !tableSelectId.value.includes(row.userId)) return false
    }
    if (props.column.disabledIds) {
      const disabledIds = props.column.disabledIds.map((id) => id + '')
      return !disabledIds.includes(row.userId)
    }
    return true
  }
})
const tableData = ref<any>([])
const tablePage = ref({ total: 1, currentPage: 1, pageSize: 10 })
const tableSearch = ref({})
const tableSelect = ref<any>([])
const tableSelectId = ref<Array<number | string>>([])
const radioValue = ref('')
const valueTimer = ref<any>(null)

const crudRef = ref()
const deptRef = ref()
const roleRef = ref()
const tasbRef = ref()

const { initTableLayout, windowSize } = useCrudHeight(crudRef)

const selectId = computed(() => {
  if (!model.value) return []
  if (typeof model.value != 'string') {
    try {
      return [(model.value as Number).toString()]
    } catch (error) {
      return []
    }
  }
  return model.value.split(',')
})

const calcHeight = computed(() => {
  return dialogData.value.isFull ? 60 : windowSize.height.value * 0.1 + 100 + 20
})
const placeholderText = computed(() => {
  let text = ''
  if (!props.disabled && !props.column.readonly) {
    text =
      props.column.placeholder ||
      `${t('Avue.control.select')} ${props.column.label || t('Avue.control.user')}`
  }
  return text
})

const getCurrText = (id) => {
  const text = lowStore.dicObj[dicKey]?.[id] || ''
  const formatter = props.column.textFormatter
  if (formatter) {
    return formatter.replace(/{dicCode}/g, id).replace(/{dicText}/g, text)
  }
  return text || id
}

const getTableData = () => {
  return new Promise(async (resolve) => {
    loading.value = true
    const apiData = {
      pageNo: tablePage.value.currentPage,
      pageSize: tablePage.value.pageSize,
      fieldList: ['userId', 'nickName', ...props.column.columnKey],
      type: props.column.findType,
      ...tableSearch.value
    }
    if (props.column.avatar) apiData.fieldList.push('avatar')
    if (deptId.value) apiData['deptId'] = deptId.value
    if (roleId.value) apiData['roleId'] = roleId.value
    const data = await DicApi.getUserSelectList(apiData)
    tableData.value = data.records
    tablePage.value.total = data.total
    loading.value = false
    resolve(true)
  })
}
const getDeptAndRoleData = () => {
  DicApi.getUserSelectDeptList(props.column.findType).then((data) => {
    treeDeptData.value = listToTree(data, deptConfig)
    nextTick(() => {
      if (deptId.value) deptRef.value.setCurrentKey(deptId.value)
    })
  })
  DicApi.getUserSelectRoleList().then((data) => {
    roleData.value = data
    nextTick(() => {
      if (roleId.value) roleRef.value.setCurrentKey(roleId.value)
    })
  })
}

const tabsHandleChange = (column) => {
  tabValue.value = column
}

const nodeClick = (data) => {
  const { requiredKey, hideTabsKey, defaultSearchRole, defaultSearchDept } = props.column
  if (tabValue.value.prop == 'dept') {
    if (deptId.value == data.deptId) {
      //不是必填 清空部门id
      if (!requiredKey?.includes('dept')) {
        deptId.value = ''
        deptRef.value.setCurrentKey(null)
      }
    } else deptId.value = data.deptId
    //判断是否清空角色
    if (!(hideTabsKey?.includes('role') && defaultSearchRole)) {
      roleId.value = ''
      roleRef.value.setCurrentKey(null)
    }
  } else if (tabValue.value.prop == 'role') {
    if (roleId.value == data.roleId) {
      //不是必填 清空角色id
      if (!requiredKey?.includes('role')) {
        roleId.value = ''
        roleRef.value.setCurrentKey(null)
      }
    } else roleId.value = data.roleId
    //判断是否清空部门
    if (!(hideTabsKey?.includes('dept') && defaultSearchDept)) {
      deptId.value = ''
      deptRef.value.setCurrentKey(null)
    }
  }
  resetChange()
}

const radioClick = (row, index) => {
  const bool = tableOption.value['selectable'](row)
  if (!bool) return
  if (row.userId == radioValue.value) {
    setTimeout(() => {
      radioValue.value = ''
      tableSelect.value = []
    }, 30)
  } else {
    tableSelect.value = [row]
  }
}

const getCurrTableSelect = (type?) => {
  const dicObj = {}
  const textList: string[] = []
  const ids = tableSelect.value.map((item) => {
    if (item.userId && item.nickName) {
      dicObj[item.userId] = item.nickName
      textList.push(item.nickName)
    }
    return item.userId
  })
  if (type == 'confirm') {
    emit('set-form-data', '$' + props.prop, textList.join(props.column.separator || ' | '))
  }
  lowStore.setDicObj(dicKey, dicObj)
  return ids
}

const openTableSelect = () => {
  const index = props.scope?.['index']
  if (props.column['onClick']) {
    props.column['onClick']({ value: model.value, column: props.column, index })
  } else if (props.column['click']) {
    props.column['click']({ value: model.value, column: props.column, index })
  }
  if (props.disabled || props.column.readonly) {
    if (selectId.value && selectId.value.length) detailDialog.value = true
    return
  }
  const { defaultSearchDept, defaultSearchRole } = props.column
  if (!deptId.value && defaultSearchDept) deptId.value = defaultSearchDept
  if (!roleId.value && defaultSearchRole) roleId.value = defaultSearchRole

  if (!deptId.value && roleId.value) {
    tabValue.value.prop = 'role'
    setTimeout(() => {
      nextTick(() => {
        tasbRef.value.active = '1'
      })
    }, 10)
  }
  const title = t('Avue.control.select') + ' ' + (props.column.label || t('Avue.control.user'))
  dialogData.value.params.title = title
  dialogData.value.value = true
}

const setTableSelect = (ids) => {
  const dicObj = {}
  const rows: Array<any> = []
  tableData.value.forEach((item) => (dicObj[item.userId] = item))
  ids.forEach((id) => {
    if (dicObj[id]) rows.push(dicObj[id])
    else rows.push({ id })
  })
  if (tableOption.value['column']['lowSelectRadio']) {
    tableSelect.value = rows
    radioValue.value = rows[0]?.userId || ''
  } else {
    crudRef.value.clearSelection()
    crudRef.value.toggleSelection(rows, true)
  }
}

const selectAll = () => {
  if (props.column.limit) {
    const limit = props.column.limit
    if (tableSelect.value.length > limit) {
      const clearSelect = tableSelect.value.filter((item, index) => limit <= index)
      crudRef.value.toggleSelection(clearSelect)
    }
  }
}

const tagValueClose = (id) => {
  let list = model.value ? (model.value + '').split(',') : []
  list = list.filter((key) => key != id)
  model.value = list.join(',')
}
const tagTableClose = (id) => {
  if (props.column.multiple) {
    const selelctData = crudRef.value.$refs.table.getSelectionRows()
    const currRow = selelctData.filter((item) => item.userId == id)
    crudRef.value.toggleSelection(currRow, false)
  } else {
    tableSelect.value = []
    radioValue.value = ''
  }
}

const selectionChange = (data) => {
  tableSelect.value = data
}
const searchChange = (params, done) => {
  tablePage.value['currentPage'] = 1
  getTableData().finally(() => {
    done()
  })
}
const resetChange = () => {
  for (let prop in tableSearch.value) tableSearch.value[prop] = ''
  searchChange({}, () => {})
}

const sizeChange = (pageSize) => {
  tablePage.value['pageSize'] = pageSize
  resetChange()
}

const currentChange = (currentPage) => {
  tablePage.value['currentPage'] = currentPage
  getTableData()
}

const clearSelect = () => {
  if (tableOption.value['column']['lowSelectRadio']) {
    tableSelect.value = []
    radioValue.value = ''
  } else crudRef.value.clearSelection()
}

const initSelect = (val) => {
  delete tableOption.value.column['lowSelectRadio']
  tableOption.value.selection = val || false
  if (!val) {
    tableOption.value.column = {
      lowSelectRadio: {
        label: '',
        display: false,
        width: 50,
        overHidden: false,
        fixed: true,
        showColumn: false
      },
      ...tableOption.value.column
    }
  }
}

watch(
  () => model.value,
  (value) => {
    const selectObj = {}
    cloneDeep(tableSelect.value).forEach(
      (item) => (selectObj[item[tableOption.value.rowKey]] = item).value
    )
    const changeObj = {
      value,
      column: props.column,
      selectObj,
      row: props.scope?.['row'],
      index: props.scope?.['index']
    }
    if (props.column['onChange']) props.column['onChange'](changeObj)
    else if (props.column['change']) props.column['change'](changeObj)
  }
)

watch(
  () => props.column.multiple,
  (val) => initSelect(val),
  { immediate: true }
)

watch(
  () => props.column.columnKey,
  (val) => {
    const column = { nickName: columnObj.nickName }
    val.forEach((prop) => (column[prop] = { ...columnObj[prop] }))
    Object.keys(tableOption.value.column).forEach((prop) => {
      if (prop != 'lowSelectRadio') delete tableOption.value.column[prop]
    })
    tableOption.value.column = { ...tableOption.value.column, ...column }
  },
  { immediate: true }
)

watch(
  () => dialogData.value.isFull,
  () => initTableLayout()
)

watch(
  () => dialogData.value.value,
  (val) => {
    clearInterval(valueTimer.value)
    if (!val) {
      if (crudRef.value) crudRef.value.clearSelection()
      deptId.value = ''
      roleId.value = ''
      deptRef.value.setCurrentKey(null)
      roleRef.value.setCurrentKey(null)
      return
    }
    getDeptAndRoleData()
    initTableLayout()
    resetChange()
    valueTimer.value = setInterval(() => {
      if (loading.value == false) {
        clearInterval(valueTimer.value)
        setTableSelect(selectId.value)
      }
    }, 100)
  }
)

watch(
  () => tableSelect.value,
  (val) => {
    tableSelectId.value = getCurrTableSelect()
    dialogData.value.params.footerBtn[1].name = `${t('Avue.common.submitBtn')}（${val.length}）`
  }
)
</script>

<style lang="scss" scoped>
.el-container {
  height: 100%;

  .el-aside:nth-child(1) {
    border-right: var(--el-border);
  }
}

::v-deep(.el-tabs__header) {
  margin-bottom: 0;
}

.tree-box {
  height: calc(100% - 44px);
  padding: 10px;
  box-sizing: border-box;

  .el-tree-node__label {
    &.active {
      color: var(--el-color-primary);
    }
  }
}

.select-box {
  height: 100%;
  padding: 10px;
  border-left: var(--el-border);
  box-sizing: border-box;
}

.user-input {
  display: flex;
  height: var(--el-component-size);
  font-size: var(--el-text-base);
  color: var(--el-input-text-color, var(--el-text-color-regular));
  cursor: pointer;
  background-color: var(--el-bg-color);
  background-image: none;
  border: var(--el-border);
  border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
  box-sizing: border-box;
  align-items: center;

  .user-text-list {
    display: flex;
    height: 100%;
    flex: 1;
    padding: 0 11px;
    overflow-x: hidden;
    align-items: center;
    flex-wrap: nowrap;
    column-gap: 6px;
  }

  .empty-text {
    margin: 0 10px;
    overflow: hidden;
    color: #999;
    text-align: left;
    flex: 1;
    text-wrap: nowrap;
  }

  .user-num {
    height: 100%;
    padding-right: 10px;
    padding-left: 10px;
    line-height: var(--el-component-size);
    color: var(--el-text-color-regular);
    background-color: var(--el-fill-color-light);

    span {
      display: inline-block;
      padding: 0 2px;
      color: var(--el-color-primary);
    }
  }

  &.disabled {
    &.edit,
    &.add {
      background-color: var(--el-disabled-bg-color);
    }

    &.view {
      border: none;

      .user-text-list {
        padding: 0;
      }

      .user-num {
        margin-right: -20px;
      }
    }
  }

  &.small {
    height: var(--el-component-size-small);
    font-size: var(--el-font-size-extra-small);

    .user-num {
      height: var(--el-component-size-small);
    }
  }

  &.large {
    height: var(--el-component-size-large);
    font-size: var(--el-font-size-large);

    .user-num {
      height: var(--el-component-size-large);
    }
  }

  &:hover {
    .user-input-clear {
      display: flex;
    }
  }
}
</style>
