<script setup lang="ts">
import {
  deletePermissionApi,
  detailBaseEmployeeApi,
  pagePermissionApi,
  savePermissionApi,
  type BaseEmployeeResultVO,
  type NdmPermissionResultVO,
  type NdmPermissionSaveVO,
  type Station,
} from '@/apis';
import { PERMISSION_TYPE_NAMES, type PermissionType } from '@/enums';
import { useStationStore } from '@/stores';
import { parseErrorFeedback } from '@/utils';
import { useMutation } from '@tanstack/vue-query';
import { objectEntries } from '@vueuse/core';
import { isCancel } from 'axios';
import { cloneDeep, groupBy } from 'es-toolkit';
import { NButton, NCheckbox, NDataTable, NFlex, NModal, NText, type DataTableColumn, type DataTableColumns } from 'naive-ui';
import { storeToRefs } from 'pinia';
import { computed, h, ref, toRefs } from 'vue';

/**
 * 为避免修改权限记录而带来复杂的状态管理，引入 `action` 字段
 *
 * - `save`：新增权限
 * - `delete`：删除权限
 *
 * 修改权限的过程中会对本地的权限列表进行修改：
 * - 当勾选权限时，在本地新增一条权限，`action` 为 `save`
 * - 当取消勾选权限时，如果该权限来自服务器，则将`action` 改为 `delete`，否则直接删除权限记录
 *
 * 最后，保存权限时，只需要遍历本地权限列表：
 * - 如果 `action` 为 `save`，则调用 `savePermissionApi` 新增权限
 * - 如果 `action` 为 `delete`，则调用 `deletePermissionApi` 删除权限
 */
type PermissionAction = 'save' | 'delete';

type NdmPermissionSaveOrResultVO = NdmPermissionSaveVO | NdmPermissionResultVO;

type NdmPermissionExtendedSaveVO = NdmPermissionSaveVO & { action?: PermissionAction };

type NdmPermissionExtendedResultVO = NdmPermissionResultVO & { action?: PermissionAction };

type NdmPermissionExtendedSaveOrResultVO = NdmPermissionExtendedSaveVO | NdmPermissionExtendedResultVO;

const omitActionField = (extendedSaveOrResultVO: NdmPermissionExtendedSaveOrResultVO) => {
  const copy = cloneDeep(extendedSaveOrResultVO);
  delete copy.action;
  const saveOrResultVO = copy as NdmPermissionSaveOrResultVO;
  return saveOrResultVO;
};

const isSaveVO = (saveOrResultVO: NdmPermissionSaveOrResultVO): saveOrResultVO is NdmPermissionSaveVO => {
  return !('id' in saveOrResultVO);
};

const isResultVO = (saveOrResultVO: NdmPermissionSaveOrResultVO): saveOrResultVO is NdmPermissionResultVO => {
  return 'id' in saveOrResultVO;
};

const props = defineProps<{
  employeeId?: string;
}>();

const stationStore = useStationStore();
const { stations } = storeToRefs(stationStore);

const show = defineModel<boolean>('show', { default: false });

const { employeeId } = toRefs(props);

const abortController = ref<AbortController>(new AbortController());

const employee = ref<BaseEmployeeResultVO>();
const { mutateAsync: getEmployeeAsync } = useMutation({
  mutationFn: async () => {
    abortController.value.abort();
    abortController.value = new AbortController();

    if (!employeeId.value) return;

    const signal = abortController.value.signal;

    const data = await detailBaseEmployeeApi(employeeId.value, { signal });
    return data;
  },
  onSuccess: (data) => {
    if (!data) return;
    employee.value = data;
  },
  onError: (error) => {
    if (isCancel(error)) return;
    console.error(error);
    const errorFeedback = parseErrorFeedback(error);
    window.$message.error(errorFeedback);
  },
});

const permissions = ref<Record<Station['code'], NdmPermissionExtendedSaveOrResultVO[]>>({});
const { mutate: getPermissions, isPending: permissionsLoading } = useMutation({
  mutationFn: async () => {
    abortController.value.abort();
    abortController.value = new AbortController();

    if (!employeeId.value) return;

    const signal = abortController.value.signal;

    const data = await pagePermissionApi(
      {
        model: {
          employeeId: employeeId.value,
        },
        extra: {},
        current: 1,
        size: Object.keys(PERMISSION_TYPE_NAMES).length * stations.value.length,
        sort: 'id',
        order: 'ascending',
      },
      { signal },
    );

    return data;
  },
  onSuccess: (data) => {
    if (!data) return;
    const { records } = data;
    permissions.value = groupBy(records, (record) => record.stationCode ?? '');
  },
  onError: (error) => {
    if (isCancel(error)) return;
    console.error(error);
    const errorFeedback = parseErrorFeedback(error);
    window.$message.error(errorFeedback);
  },
});

const onUpdatePermissionChecked = (checked: boolean, stationCode: Station['code'], permissionType: PermissionType) => {
  if (!employeeId.value) return;
  if (checked) {
    // 勾选时，新增一条权限记录，`action` 为 `save`
    if (!permissions.value[stationCode]) permissions.value[stationCode] = [];
    permissions.value[stationCode].push({ employeeId: employeeId.value, stationCode, type: permissionType, action: 'save' });
  } else {
    if (!permissions.value[stationCode]) return;
    // 取消勾选时，先找到所有该类型的权限并遍历，
    // 如果权限来自数据库（即有 `id` 字段），则将其 `action` 设为 `delete`，
    // 否则直接从权限记录中移除
    const targets = permissions.value[stationCode].filter((permission) => permission.type === permissionType);
    for (const permission of targets) {
      if (isResultVO(permission)) {
        permission.action = 'delete';
      } else {
        permissions.value[stationCode].splice(permissions.value[stationCode].indexOf(permission), 1);
      }
    }
  }
};

const tableColumns = computed<DataTableColumns<Station>>(() => {
  return [
    { title: '车站编号', key: 'code', align: 'center', width: 120 },
    { title: '车站名称', key: 'name', align: 'center', width: 360 },
    // 「权限」列
    ...objectEntries(PERMISSION_TYPE_NAMES).map<DataTableColumn<Station>>(([permissionType, title]) => ({
      title: title,
      key: permissionType,
      align: 'center',
      render: (rowData) => {
        const { code: stationCode } = rowData;
        return h(NCheckbox, {
          // 如果权限记录中存在该权限且 `action` 不为 `delete`，则处于勾选状态
          checked: !!(permissions.value[stationCode] ?? []).find((permission) => permission.type === permissionType && permission.action !== 'delete'),
          // 改变勾选状态
          onUpdateChecked: (checked) => onUpdatePermissionChecked(checked, stationCode, permissionType),
        });
      },
    })),
  ];
});

const { mutate: savePermissions, isPending: permissionsSaving } = useMutation({
  mutationFn: async () => {
    abortController.value.abort();
    abortController.value = new AbortController();

    const signal = abortController.value.signal;

    // 遍历所有的权限记录，根据 `action` 进行保存或删除
    for (const stationPermissions of Object.values(permissions.value)) {
      for (const permission of stationPermissions) {
        const saveOrResultVO = omitActionField(permission);
        if (permission.action === 'save' && isSaveVO(saveOrResultVO)) {
          await savePermissionApi(saveOrResultVO, { signal });
        }
        if (permission.action === 'delete' && isResultVO(saveOrResultVO)) {
          const id = saveOrResultVO.id;
          if (!id) continue;
          await deletePermissionApi([id], { signal });
        }
      }
    }
  },
  onSuccess: () => {
    window.$message.success('权限配置保存成功');
    getPermissions();
  },
  onError: (error) => {
    if (isCancel(error)) return;
    console.error(error);
    const errorFeedback = parseErrorFeedback(error);
    window.$message.error(errorFeedback);
  },
});

const onAfterEnter = () => {
  getEmployeeAsync().then(() => getPermissions());
};

const onAfterLeave = () => {
  employee.value = undefined;
  permissions.value = {};
};

const onClose = () => {
  abortController.value.abort();
  show.value = false;
};
</script>

<template>
  <NModal
    v-model:show="show"
    preset="card"
    style="width: 100vw; height: 100vh"
    :content-style="{ height: '100%', overflow: 'hidden' }"
    :close-on-esc="false"
    :mask-closable="false"
    :auto-focus="false"
    @after-enter="onAfterEnter"
    @after-leave="onAfterLeave"
    @close="onClose"
  >
    <template #header>
      <span>{{ `配置权限 - ${employee?.realName ?? ''}` }}</span>
    </template>
    <template #default>
      <NDataTable flex-height style="height: 100%" :columns="tableColumns" :data="stations" :loading="permissionsLoading" :single-line="false" />
    </template>
    <template #footer>
      <NText depth="3" style="font-size: smaller">*未勾选任何权限的用户将被认为拥有所有权限</NText>
    </template>
    <template #action>
      <NFlex justify="end">
        <NButton size="small" @click="onClose">取消</NButton>
        <NButton type="primary" size="small" :loading="permissionsSaving" @click="() => savePermissions()">保存</NButton>
      </NFlex>
    </template>
  </NModal>
</template>

<style scoped lang="scss"></style>
