<script setup lang="tsx">
import {computed, nextTick, onMounted, reactive, ref, watch} from 'vue';
import type {TableInstance} from 'element-plus';
import {ElButton, ElPopconfirm, ElSwitch, ElTag} from 'element-plus';
import dayjs from 'dayjs';
import type {FlatResponseData} from '@sa/axios';
import {userGenderRecord} from '@/constants/business';
import {
  fetchBatchDeleteUser,
  fetchDeleteDept,
  fetchDeleteUser,
  fetchGetDeptList,
  fetchGetPostSimpleList,
  fetchGetRoleSimpleList,
  fetchGetUser,
  fetchGetUserPage,
  fetchUpdateUser,
  fetchUpdateUserStatus
} from '@/service/api';
import {useUIPaginatedTable} from '@/hooks/common/table';
import BusinessTableActionCell from '@/components/custom/business-table-action-cell';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import {$t} from '@/locales';
import {buildMenuTree} from '@/views/system/shared/menu-tree';
import UserOperateDialog from './modules/user-operate-dialog.vue';
import UserOrgLeaderDialog from './modules/user-org-leader-dialog.vue';
import UserOrgOperateDialog from './modules/user-org-operate-dialog.vue';
import UserOrgPanel from './modules/user-org-panel.vue';
import UserResignedDialog from './modules/user-resigned-dialog.vue';
import UserResetPasswordDialog from './modules/user-reset-password-dialog.vue';
import UserSearch from './modules/user-search.vue';
import UserManagementRelation from '@/views/system/user-management-relation/index.vue';

defineOptions({name: 'UserManage'});

function getInitSearchParams(): Api.SystemManage.UserSearchParams {
  return {
    pageNo: 1,
    pageSize: 10,
    username: undefined,
    mobile: undefined,
    status: undefined,
    deptId: undefined,
    roleId: undefined
  };
}

function createEmptyUserPageResult(): Promise<FlatResponseData<any, Api.SystemManage.UserList>> {
  return Promise.resolve({
    data: {
      list: [],
      total: 0
    },
    error: null,
    response: undefined
  } as unknown as FlatResponseData<any, Api.SystemManage.UserList>);
}

function transformUserPage(
  response: FlatResponseData<any, Api.SystemManage.UserList>,
  pageNo: number,
  pageSize: number
) {
  if (!response.error) {
    return {
      data: response.data.list,
      pageNum: pageNo,
      pageSize,
      total: response.data.total
    };
  }

  return {
    data: [],
    pageNum: pageNo,
    pageSize,
    total: 0
  };
}

function formatTime(value?: number | null) {
  if (!value) {
    return '--';
  }

  return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
}

function getNullableLabel(value?: string | null) {
  return value?.trim() || '--';
}

type UserResignedState = 'active' | 'pending' | 'resigned';

function getUserResignedState(row: Api.SystemManage.User): UserResignedState {
  if (!row.resignedAt) {
    return 'active';
  }

  return row.resignedAt > Date.now() ? 'pending' : 'resigned';
}

function getResignedActionConfig(row: Api.SystemManage.User) {
  const state = getUserResignedState(row);

  if (state === 'active') {
    return {
      label: $t('page.system.user.resignUser'),
      buttonType: 'warning' as const
    };
  }

  if (state === 'pending') {
    return {
      label: $t('page.system.user.adjustResignUser'),
      buttonType: 'warning' as const
    };
  }

  return {
    label: $t('page.system.user.restoreUser'),
    buttonType: 'success' as const
  };
}

const searchParams = reactive(getInitSearchParams());
const deptLoading = ref(false);
const userTableRef = ref<TableInstance>();
const userCheckedRowKeys = ref<number[]>([]);
const statusLoadingIds = ref<number[]>([]);
const deptList = ref<Api.SystemManage.Dept[]>([]);
const currentDeptId = ref<number | null>(null);
const operateVisible = ref(false);
const operateType = ref<UI.TableOperateType>('add');
const editingUserId = ref<number | null>(null);
const postOptions = ref<Api.SystemManage.PostSimple[]>([]);
const roleOptions = ref<Api.SystemManage.RoleSimple[]>([]);
const resetPasswordVisible = ref(false);
const resetPasswordUserId = ref<number | null>(null);
const resetPasswordUsername = ref<string | null>(null);
const resignedVisible = ref(false);
const resignedUserId = ref<number | null>(null);
const resignedUsername = ref<string | null>(null);
const resignedAt = ref<number | null>(null);
const orgOperateVisible = ref(false);
const orgOperateType = ref<UI.TableOperateType>('add');
const editingDeptData = ref<Api.SystemManage.Dept | null>(null);
const orgParentId = ref<number | null>(0);
const orgLeaderVisible = ref(false);
const leaderDeptData = ref<Api.SystemManage.Dept | null>(null);
const userManagementRelationVisible = ref(false);

const deptTree = computed(() => buildMenuTree(deptList.value));
const currentDept = computed(() => deptList.value.find(item => item.id === currentDeptId.value) ?? null);
const deptCount = computed(() => deptList.value.length);

const {columns, columnChecks, data, loading, getDataByPage, mobilePagination} = useUIPaginatedTable<
  FlatResponseData<any, Api.SystemManage.UserList>,
  Api.SystemManage.User
>({
  paginationProps: {
    currentPage: searchParams.pageNo,
    pageSize: searchParams.pageSize
  },
  api: () => {
    if (!currentDeptId.value) {
      return createEmptyUserPageResult();
    }

    return fetchGetUserPage({
      ...searchParams,
      deptId: currentDeptId.value
    });
  },
  transform: response => transformUserPage(response, searchParams.pageNo ?? 1, searchParams.pageSize ?? 10),
  onPaginationParamsChange: params => {
    searchParams.pageNo = params.currentPage ?? 1;
    searchParams.pageSize = params.pageSize ?? 10;
  },
  columns: () => [
    {prop: 'selection', type: 'selection', width: 48},
    {prop: 'index', type: 'index', label: $t('common.index'), width: 64},
    {prop: 'username', label: $t('page.system.user.userName'), minWidth: 140, showOverflowTooltip: true},
    {
      prop: 'nickname',
      label: $t('page.system.user.nickName'),
      minWidth: 120,
      formatter: row => getNullableLabel(row.nickname)
    },
    {
      prop: 'deptName',
      label: $t('page.system.user.deptName'),
      minWidth: 180,
      showOverflowTooltip: true,
      formatter: row => getNullableLabel(row.deptName)
    },
    {
      prop: 'positionName',
      label: $t('page.system.user.positionName'),
      minWidth: 140,
      showOverflowTooltip: true,
      formatter: row => getNullableLabel(row.positionName)
    },
    {
      prop: 'mobile',
      label: $t('page.system.user.userPhone'),
      width: 140,
      formatter: row => getNullableLabel(row.mobile)
    },
    {
      prop: 'email',
      label: $t('page.system.user.userEmail'),
      minWidth: 180,
      showOverflowTooltip: true,
      formatter: row => getNullableLabel(row.email)
    },
    {
      prop: 'sex',
      label: $t('page.system.user.userGender'),
      width: 100,
      align: 'center',
      formatter: row => {
        const value = row.sex ?? 0;
        const tagMap: Record<Api.SystemManage.UserGender, UI.ThemeColor> = {
          0: 'info',
          1: 'primary',
          2: 'danger'
        };

        return <ElTag type={tagMap[value]}>{$t(userGenderRecord[value])}</ElTag>;
      }
    },
    {
      prop: 'status',
      label: $t('page.system.user.userStatus'),
      width: 110,
      align: 'center',
      formatter: row => (
        <ElSwitch
          modelValue={row.status === 0}
          loading={statusLoadingIds.value.includes(row.id)}
          inlinePrompt
          activeText={$t('page.system.common.status.enable')}
          inactiveText={$t('page.system.common.status.disable')}
          onChange={value => handleToggleStatus(row, Boolean(value))}
        />
      )
    },
    {
      prop: 'resignedAt',
      label: $t('page.system.user.resignedAt'),
      minWidth: 170,
      formatter: row => formatTime(row.resignedAt)
    },
    {
      prop: 'resignedState',
      label: $t('page.system.user.resignedState'),
      width: 110,
      align: 'center',
      formatter: row => {
        const state = getUserResignedState(row);
        const stateMap: Record<UserResignedState, { type: UI.ThemeColor; label: App.I18n.I18nKey }> = {
          active: {type: 'success', label: 'page.system.user.resignedStateEnum.active'},
          pending: {type: 'warning', label: 'page.system.user.resignedStateEnum.pending'},
          resigned: {type: 'info', label: 'page.system.user.resignedStateEnum.resigned'}
        };

        return <ElTag type={stateMap[state].type}>{$t(stateMap[state].label)}</ElTag>;
      }
    },
    {
      prop: 'loginDate',
      label: $t('page.system.user.loginDate'),
      minWidth: 170,
      formatter: row => formatTime(row.loginDate)
    },
    {
      prop: 'createTime',
      label: $t('page.system.user.createTime'),
      minWidth: 170,
      formatter: row => formatTime(row.createTime)
    },
    {
      prop: 'operate',
      label: $t('common.operate'),
      width: 210,
      align: 'center',
      fixed: 'right',
      formatter: row => (
        <BusinessTableActionCell
          actions={[
            {
              key: 'edit',
              label: $t('common.edit'),
              buttonType: 'primary',
              onClick: () => openEdit(row.id)
            },
            {
              key: 'reset-password',
              label: $t('page.system.user.resetPassword'),
              buttonType: 'warning',
              onClick: () => openResetPassword(row)
            },
            {
              key: 'resigned',
              label: getResignedActionConfig(row).label,
              buttonType: getResignedActionConfig(row).buttonType,
              onClick: () => handleResignedAction(row)
            },
            {
              key: 'delete',
              label: $t('common.delete'),
              buttonType: 'danger',
              onClick: () => handleDeleteAction(row)
            }
          ]}
        />
      )
    }
  ]
});

async function loadDeptTree() {
  deptLoading.value = true;

  const {error, data: deptItems} = await fetchGetDeptList({
    status: 0
  });

  deptLoading.value = false;

  if (error) {
    deptList.value = [];
    currentDeptId.value = null;
    return;
  }

  deptList.value = deptItems;

  if (!deptItems.length) {
    currentDeptId.value = null;
    return;
  }

  const matched = deptItems.find(item => item.id === currentDeptId.value);
  currentDeptId.value = matched?.id ?? deptItems[0].id;
}

async function loadFormOptions() {
  const [postResult, roleResult] = await Promise.all([fetchGetPostSimpleList(), fetchGetRoleSimpleList()]);

  if (!postResult.error) {
    postOptions.value = postResult.data;
  }

  if (!roleResult.error) {
    roleOptions.value = roleResult.data.filter(item => item.status === 0);
  }
}

async function reloadUserTable(page = searchParams.pageNo) {
  userCheckedRowKeys.value = [];
  await getDataByPage(page);
  await nextTick();
  userTableRef.value?.clearSelection();
}

function handleDeptSelect(nodeData: Api.SystemManage.Dept) {
  currentDeptId.value = nodeData.id;
}

function handleUserSelectionChange(rows: Api.SystemManage.User[]) {
  userCheckedRowKeys.value = rows.map(item => item.id);
}

function openAdd() {
  operateType.value = 'add';
  editingUserId.value = null;
  operateVisible.value = true;
}

function openEdit(id: number) {
  operateType.value = 'edit';
  editingUserId.value = id;
  operateVisible.value = true;
}

function openResetPassword(row: Api.SystemManage.User) {
  resetPasswordUserId.value = row.id;
  resetPasswordUsername.value = row.username;
  resetPasswordVisible.value = true;
}

function openResignedDialog(row: Api.SystemManage.User) {
  resignedUserId.value = row.id;
  resignedUsername.value = row.username;
  resignedAt.value = row.resignedAt ?? null;
  resignedVisible.value = true;
}

function openAddRootOrg() {
  orgOperateType.value = 'add';
  editingDeptData.value = null;
  orgParentId.value = 0;
  orgOperateVisible.value = true;
}

function openAddChildOrg(row: Api.SystemManage.Dept) {
  orgOperateType.value = 'add';
  editingDeptData.value = null;
  orgParentId.value = row.id;
  orgOperateVisible.value = true;
}

function openEditOrg(row: Api.SystemManage.Dept) {
  orgOperateType.value = 'edit';
  editingDeptData.value = row;
  orgParentId.value = row.parentId;
  orgOperateVisible.value = true;
}

function openOrgLeader(row: Api.SystemManage.Dept) {
  leaderDeptData.value = row;
  orgLeaderVisible.value = true;
}

async function handleDeleteDeptAction(row: Api.SystemManage.Dept) {
  const {error} = await fetchDeleteDept(row.id);

  if (error) {
    return;
  }

  if (currentDeptId.value === row.id) {
    currentDeptId.value = null;
  }

  window.$message?.success($t('common.deleteSuccess'));
  await loadDeptTree();
}

async function handleDeleteAction(row: Api.SystemManage.User) {
  try {
    await window.$messageBox?.confirm($t('common.confirmDelete'), $t('common.warning'), {
      confirmButtonText: $t('common.confirm'),
      cancelButtonText: $t('common.cancel'),
      type: 'warning'
    });
  } catch {
    return;
  }

  const {error} = await fetchDeleteUser(row.id);

  if (error) {
    return;
  }

  window.$message?.success($t('common.deleteSuccess'));
  await reloadUserTable();
}

async function updateUserResignedAt(userId: number, value: number | null) {
  const detailResult = await fetchGetUser(userId);

  if (detailResult.error) {
    return false;
  }

  const user = detailResult.data;

  const {error} = await fetchUpdateUser({
    id: userId,
    username: user.username,
    nickname: user.nickname ?? null,
    remark: user.remark ?? null,
    deptId: user.deptId,
    positionId: user.positionId ?? null,
    resignedAt: value,
    email: user.email ?? null,
    mobile: user.mobile ?? null,
    sex: user.sex ?? 0,
    avatar: user.avatar ?? null
  });

  return !error;
}

async function handleRestoreUser(row: Api.SystemManage.User) {
  try {
    await window.$messageBox?.confirm($t('common.confirmDelete'), $t('page.system.user.restoreUser'), {
      confirmButtonText: $t('common.confirm'),
      cancelButtonText: $t('common.cancel'),
      type: 'warning'
    });
  } catch {
    return;
  }

  const success = await updateUserResignedAt(row.id, null);

  if (!success) {
    return;
  }

  window.$message?.success($t('common.updateSuccess'));
  await reloadUserTable();
}

async function handleResignedAction(row: Api.SystemManage.User) {
  if (getUserResignedState(row) === 'resigned') {
    await handleRestoreUser(row);
    return;
  }

  openResignedDialog(row);
}

async function handleBatchDelete() {
  if (!userCheckedRowKeys.value.length) {
    return;
  }

  const {error} = await fetchBatchDeleteUser(userCheckedRowKeys.value);

  if (error) {
    return;
  }

  window.$message?.success($t('common.deleteSuccess'));
  await reloadUserTable();
}

async function handleToggleStatus(row: Api.SystemManage.User, enabled: boolean) {
  statusLoadingIds.value = [...statusLoadingIds.value, row.id];

  const {error} = await fetchUpdateUserStatus({
    id: row.id,
    status: enabled ? 0 : 1
  });

  statusLoadingIds.value = statusLoadingIds.value.filter(item => item !== row.id);

  if (error) {
    return;
  }

  row.status = enabled ? 0 : 1;
  window.$message?.success($t('common.updateSuccess'));
}

async function handleSearch() {
  await reloadUserTable(1);
}

async function handleResetSearch() {
  const pageSize = searchParams.pageSize;

  Object.assign(searchParams, getInitSearchParams(), {
    pageSize,
    deptId: currentDeptId.value ?? undefined
  });

  await reloadUserTable(1);
}

async function handleSubmitted() {
  operateVisible.value = false;
  await reloadUserTable();
}

async function handleResignedSubmitted() {
  resignedVisible.value = false;
  await reloadUserTable();
}

async function handleDeptSubmitted(deptId: number) {
  orgOperateVisible.value = false;
  await loadDeptTree();
  currentDeptId.value = deptId;
}

watch(currentDeptId, async (value, oldValue) => {
  if (!value || value === oldValue) {
    return;
  }

  const pageSize = searchParams.pageSize;

  Object.assign(searchParams, getInitSearchParams(), {
    pageSize,
    deptId: value
  });

  await reloadUserTable(1);
});

onMounted(async () => {
  await Promise.all([loadDeptTree(), loadFormOptions()]);
});
</script>

<template>
  <div
    class="min-h-560px gap-16px overflow-hidden xl:grid xl:grid-cols-[320px_minmax(0,1fr)] lt-xl:flex lt-xl:flex-col lt-xl:overflow-auto"
  >
    <div class="flex-col-stretch gap-16px xl:min-h-0">
      <UserOrgPanel
        :loading="deptLoading"
        :tree-data="deptTree"
        :current-dept-id="currentDeptId"
        :total="deptCount"
        @add-root="openAddRootOrg"
        @add-child="openAddChildOrg"
        @leader="openOrgLeader"
        @edit="openEditOrg"
        @delete="handleDeleteDeptAction"
        @refresh="loadDeptTree"
        @select="handleDeptSelect"
      />
    </div>

    <div class="flex-col-stretch gap-16px xl:min-h-0">
      <UserSearch
        v-model:model="searchParams"
        :role-options="roleOptions"
        :disabled="!currentDept"
        @reset="handleResetSearch"
        @search="handleSearch"
      />

      <ElCard class="card-wrapper xl:flex-1-hidden" body-class="user-table-card-body">
        <template #header>
          <div class="flex items-center justify-between gap-12px">
            <div class="flex flex-wrap items-center gap-8px">
              <p>{{ $t('page.system.user.title') }}</p>
              <ElTag v-if="currentDept" type="primary" effect="light">
                {{ currentDept.name }}
              </ElTag>
              <ElTag effect="plain">{{ mobilePagination.total || data.length }}</ElTag>
            </div>
            <TableHeaderOperation v-model:columns="columnChecks" :loading="loading" @refresh="reloadUserTable">
              <template #default>
                <ElButton plain type="primary" :disabled="!currentDept" @click="openAdd">
                  <template #icon>
                    <icon-ic-round-plus class="text-icon"/>
                  </template>
                  {{ $t('common.add') }}
                </ElButton>
                <ElButton plain type="primary" :disabled="!currentDept" @click="userManagementRelationVisible = true">
                  <template #icon>
                    <icon-ic-round-plus class="text-icon"/>
                  </template>
                  带人关系
                </ElButton>
                <ElPopconfirm :title="$t('common.confirmDelete')" @confirm="handleBatchDelete">
                  <template #reference>
                    <ElButton type="danger" plain :disabled="userCheckedRowKeys.length === 0">
                      <template #icon>
                        <icon-ic-round-delete class="text-icon"/>
                      </template>
                      {{ $t('common.batchDelete') }}
                    </ElButton>
                  </template>
                </ElPopconfirm>
              </template>
            </TableHeaderOperation>
          </div>
        </template>

        <template v-if="currentDept">
          <div class="flex-1">
            <ElTable
              ref="userTableRef"
              v-loading="loading"
              height="100%"
              border
              row-key="id"
              :data="data"
              @selection-change="handleUserSelectionChange"
            >
              <ElTableColumn v-for="col in columns" :key="String(col.prop)" v-bind="col"/>
            </ElTable>
          </div>
          <div class="mt-20px flex justify-end">
            <ElPagination
              v-if="mobilePagination.total"
              layout="total,prev,pager,next,sizes"
              v-bind="mobilePagination"
              @current-change="mobilePagination['current-change']"
              @size-change="mobilePagination['size-change']"
            />
          </div>
        </template>

        <div v-else class="h-full flex items-center justify-center">
          <ElEmpty :description="$t('page.system.user.emptyOrg')"/>
        </div>
      </ElCard>
    </div>

    <UserOperateDialog
      v-model:visible="operateVisible"
      :operate-type="operateType"
      :user-id="editingUserId"
      :current-dept-id="currentDeptId"
      :dept-tree="deptTree"
      :post-options="postOptions"
      :role-options="roleOptions"
      @submitted="handleSubmitted"
    />

    <UserResetPasswordDialog
      v-model:visible="resetPasswordVisible"
      :user-id="resetPasswordUserId"
      :username="resetPasswordUsername"
    />

    <UserResignedDialog
      v-model:visible="resignedVisible"
      :user-id="resignedUserId"
      :username="resignedUsername"
      :resigned-at="resignedAt"
      @submitted="handleResignedSubmitted"
    />

    <UserOrgOperateDialog
      v-model:visible="orgOperateVisible"
      :operate-type="orgOperateType"
      :row-data="editingDeptData"
      :parent-id="orgParentId"
      :dept-tree="deptTree"
      @submitted="handleDeptSubmitted"
    />

    <UserOrgLeaderDialog v-model:visible="orgLeaderVisible" :dept="leaderDeptData"/>

    <BusinessFormDialog
      v-model="userManagementRelationVisible"
      title="用户带人关系"
      preset="lg"
      :show-footer="false"
      max-body-height="70vh"
    >
      <UserManagementRelation :fromUserIndex="true" :deptId="currentDeptId"/>
    </BusinessFormDialog>
  </div>
</template>

<style lang="scss" scoped>
:deep(.user-table-card-body) {
  height: calc(100% - 56px);
  display: flex;
  flex-direction: column;
}

// 带人关系对话框内的搜索框样式优化
:deep(.business-form-dialog) {
  width: 800px;
}
</style>
