<script setup lang="ts">
import type { FormInstance } from 'element-plus';

import { computed, ref } from 'vue';

import { EasyFlowFormModal } from '@easyflow/common-ui';

import { ElForm, ElFormItem, ElInput, ElMessage } from 'element-plus';

import { api } from '#/api/request';
import DictSelect from '#/components/dict/DictSelect.vue';
import Tree from '#/components/tree/Tree.vue';
import { $t } from '#/locales';

const emit = defineEmits(['reload']);
defineExpose({
  openDialog,
});

type ResourceType = 'BOT' | 'KNOWLEDGE' | 'PLUGIN' | 'RESOURCE' | 'WORKFLOW';

interface CategoryScopeItem {
  categoryIds: Array<number | string>;
  resourceType: ResourceType;
  scopeMode: 'ALL' | 'CUSTOM';
}

interface CategoryOption {
  id: number | string;
  label: string;
}

interface CategoryPermissionTreeNode {
  children?: CategoryPermissionTreeNode[];
  id: string;
  label: string;
}

const RESOURCE_SCOPE_GROUPS: Array<{
  label: string;
  resourceType: ResourceType;
}> = [
  { resourceType: 'BOT', label: $t('bot.chatAssistant') },
  { resourceType: 'PLUGIN', label: $t('menus.ai.plugin') },
  { resourceType: 'WORKFLOW', label: $t('menus.ai.workflow') },
  { resourceType: 'KNOWLEDGE', label: $t('menus.ai.documentCollection') },
  { resourceType: 'RESOURCE', label: $t('menus.ai.resources') },
];

const saveForm = ref<FormInstance>();
const dialogVisible = ref(false);
const isAdd = ref(true);
const entity = ref<any>(buildDefaultEntity());
const categoryScopeLoaded = ref(false);
const categoryScopeEditable = ref(false);
const categoryOptions = ref<Record<ResourceType, CategoryOption[]>>({
  BOT: [],
  KNOWLEDGE: [],
  PLUGIN: [],
  RESOURCE: [],
  WORKFLOW: [],
});
const categoryTreeCheckedKeys = ref<string[]>([]);
const categoryScopeDetail = ref<{
  roleId?: number | string;
  scopes: CategoryScopeItem[];
}>({
  scopes: buildDefaultScopes(),
});
const btnLoading = ref(false);
const rules = ref({
  roleName: [
    { required: true, message: $t('message.required'), trigger: 'blur' },
  ],
  roleKey: [
    { required: true, message: $t('message.required'), trigger: 'blur' },
  ],
  status: [
    { required: true, message: $t('message.required'), trigger: 'blur' },
  ],
});
const categoryPermissionTreeData = computed<CategoryPermissionTreeNode[]>(() =>
  RESOURCE_SCOPE_GROUPS.map(({ label, resourceType }) => ({
    id: buildGroupNodeKey(resourceType),
    label,
    children: categoryOptions.value[resourceType].map((option) => ({
      id: buildCategoryNodeKey(resourceType, option.id),
      label: option.label,
    })),
  })),
);

function buildDefaultEntity() {
  return {
    menuCheckStrictly: true,
    menuIds: [],
    remark: '',
    roleKey: '',
    roleName: '',
    status: '',
  };
}

function buildDefaultScopes(): CategoryScopeItem[] {
  return RESOURCE_SCOPE_GROUPS.map(({ resourceType }) => ({
    categoryIds: [],
    resourceType,
    scopeMode: 'CUSTOM',
  }));
}

function openDialog(row: any = {}) {
  entity.value = {
    ...buildDefaultEntity(),
    ...row,
    menuCheckStrictly: true,
  };
  categoryScopeDetail.value = {
    roleId: row.id,
    scopes: buildDefaultScopes(),
  };
  categoryScopeEditable.value = false;
  syncCategoryTreeCheckedKeys(buildDefaultScopes());

  if (row.id) {
    isAdd.value = false;
    getMenuIds(row.id);
  }
  getCategoryScopeDetail(row.id);
  void ensureCategoryOptions();
  dialogVisible.value = true;
}

function save() {
  saveForm.value?.validate(async (valid) => {
    if (valid) {
      btnLoading.value = true;
      try {
        const res = await api.post('/api/v1/sysRole/saveRole', {
          ...entity.value,
          menuCheckStrictly: true,
        });
        if (res.errorCode !== 0) {
          btnLoading.value = false;
          return;
        }
        const roleId = res.data || entity.value.id;
        if (roleId && categoryScopeEditable.value) {
          const scopeRes = await saveCategoryScope(roleId);
          if (scopeRes.errorCode !== 0) {
            btnLoading.value = false;
            return;
          }
        }
        btnLoading.value = false;
        ElMessage.success(res.message);
        emit('reload');
        closeDialog();
      } catch {
        btnLoading.value = false;
      }
    }
  });
}

function closeDialog() {
  saveForm.value?.resetFields();
  isAdd.value = true;
  entity.value = buildDefaultEntity();
  categoryScopeDetail.value = {
    scopes: buildDefaultScopes(),
  };
  categoryScopeEditable.value = false;
  syncCategoryTreeCheckedKeys(buildDefaultScopes());
  dialogVisible.value = false;
}

function getMenuIds(roleId: any) {
  api.get(`/api/v1/sysRole/getRoleMenuIds?roleId=${roleId}`).then((res) => {
    entity.value.menuIds = res.data;
  });
}

async function ensureCategoryOptions() {
  if (categoryScopeLoaded.value) {
    return;
  }
  const requests = [
    api.get('/api/v1/botCategory/list', {
      params: { sortKey: 'sortNo', sortType: 'asc' },
    }),
    api.get('/api/v1/pluginCategory/list'),
    api.get('/api/v1/workflowCategory/list', {
      params: { sortKey: 'sortNo', sortType: 'asc' },
    }),
    api.get('/api/v1/documentCollectionCategory/list', {
      params: { sortKey: 'sortNo', sortType: 'asc' },
    }),
    api.get('/api/v1/resourceCategory/list', {
      params: { sortKey: 'sortNo', sortType: 'asc' },
    }),
  ];

  const [botRes, pluginRes, workflowRes, knowledgeRes, resourceRes] =
    await Promise.all(requests);
  categoryOptions.value = {
    BOT: normalizeCategoryOptions(botRes.data, 'categoryName'),
    KNOWLEDGE: normalizeCategoryOptions(knowledgeRes.data, 'categoryName'),
    PLUGIN: normalizeCategoryOptions(pluginRes.data, 'name'),
    RESOURCE: normalizeCategoryOptions(resourceRes.data, 'categoryName'),
    WORKFLOW: normalizeCategoryOptions(workflowRes.data, 'categoryName'),
  };
  categoryScopeLoaded.value = true;
}

function normalizeCategoryOptions(data: any[] = [], labelKey: string) {
  return (Array.isArray(data) ? data : []).map((item) => ({
    id: item.id,
    label: item[labelKey],
  }));
}

function buildGroupNodeKey(resourceType: ResourceType) {
  return `group:${resourceType}`;
}

function buildCategoryNodeKey(
  resourceType: ResourceType,
  categoryId: number | string,
) {
  return `category:${resourceType}:${String(categoryId)}`;
}

function syncCategoryTreeCheckedKeys(scopes: CategoryScopeItem[]) {
  categoryTreeCheckedKeys.value = scopes.flatMap((scope) => {
    if (scope.scopeMode === 'ALL') {
      return [buildGroupNodeKey(scope.resourceType)];
    }
    return scope.categoryIds.map((categoryId) =>
      buildCategoryNodeKey(scope.resourceType, categoryId),
    );
  });
}

function buildScopeItemsFromTree(): CategoryScopeItem[] {
  const checkedKeySet = new Set(categoryTreeCheckedKeys.value);
  return RESOURCE_SCOPE_GROUPS.map(({ resourceType }) => {
    const groupNodeKey = buildGroupNodeKey(resourceType);
    const grantedCategoryIds = categoryOptions.value[resourceType]
      .filter((option) =>
        checkedKeySet.has(buildCategoryNodeKey(resourceType, option.id)),
      )
      .map((option) => option.id);
    const allCategoriesSelected =
      categoryOptions.value[resourceType].length > 0 &&
      grantedCategoryIds.length === categoryOptions.value[resourceType].length;
    const scopeMode: CategoryScopeItem['scopeMode'] =
      checkedKeySet.has(groupNodeKey) || allCategoriesSelected
        ? 'ALL'
        : 'CUSTOM';
    return {
      categoryIds: scopeMode === 'ALL' ? [] : grantedCategoryIds,
      resourceType,
      scopeMode,
    };
  });
}

function getCategoryScopeDetail(roleId: number | string) {
  api
    .get('/api/v1/sysRoleCategoryScope/detail', {
      params: { roleId },
    })
    .then((res) => {
      if (res.errorCode !== 0) {
        categoryScopeEditable.value = false;
        categoryScopeDetail.value = {
          roleId,
          scopes: buildDefaultScopes(),
        };
        syncCategoryTreeCheckedKeys(categoryScopeDetail.value.scopes);
        return;
      }
      categoryScopeEditable.value = !!res.data?.editable;
      categoryScopeDetail.value = {
        roleId,
        scopes: (res.data?.scopes || buildDefaultScopes()).map((item: any) => ({
          categoryIds: item.categoryIds || [],
          resourceType: item.resourceType,
          scopeMode: item.scopeMode || 'CUSTOM',
        })),
      };
      syncCategoryTreeCheckedKeys(categoryScopeDetail.value.scopes);
    });
}

async function saveCategoryScope(roleId: number | string) {
  await ensureCategoryOptions();
  const scopes = buildScopeItemsFromTree();
  categoryScopeDetail.value = {
    roleId,
    scopes,
  };
  return api.post('/api/v1/sysRoleCategoryScope/save', {
    roleId,
    scopes,
  });
}
</script>

<template>
  <EasyFlowFormModal
    v-model:open="dialogVisible"
    :title="isAdd ? $t('button.add') : $t('button.edit')"
    :before-close="closeDialog"
    :confirm-loading="btnLoading"
    :confirm-text="$t('button.save')"
    :submitting="btnLoading"
    width="xl"
    @confirm="save"
  >
    <ElForm
      ref="saveForm"
      :model="entity"
      status-icon
      :rules="rules"
      label-position="top"
      class="easyflow-modal-form easyflow-modal-form--compact"
    >
      <ElFormItem prop="roleName" :label="$t('sysRole.roleName')">
        <ElInput v-model.trim="entity.roleName" />
      </ElFormItem>
      <ElFormItem prop="roleKey" :label="$t('sysRole.roleKey')">
        <ElInput v-model.trim="entity.roleKey" />
      </ElFormItem>
      <ElFormItem prop="status" :label="$t('sysRole.status')">
        <DictSelect v-model="entity.status" dict-code="dataStatus" />
      </ElFormItem>
      <ElFormItem prop="remark" :label="$t('sysRole.remark')">
        <ElInput v-model.trim="entity.remark" />
      </ElFormItem>
      <ElFormItem :label="$t('sysRole.menuPermission')">
        <Tree
          data-url="/api/v1/sysMenu/list?asTree=true"
          v-model="entity.menuIds"
          :default-props="{
            label: 'menuTitle',
            children: 'children',
          }"
          :check-strictly="false"
        />
      </ElFormItem>
      <ElFormItem :label="$t('sysRole.categoryPermission')">
        <div class="role-category-scope-panel">
          <Tree
            :data="categoryPermissionTreeData"
            v-model="categoryTreeCheckedKeys"
            :default-props="{
              label: 'label',
              children: 'children',
            }"
            node-key="id"
            :default-expand-all="true"
            :check-strictly="false"
            :height="280"
            :disabled="!categoryScopeEditable"
          />
        </div>
      </ElFormItem>
    </ElForm>
  </EasyFlowFormModal>
</template>

<style scoped>
.role-category-scope-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
}

.role-category-scope-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  background: var(--el-fill-color-extra-light);
  border: 1px solid var(--el-border-color-lighter);
  border-radius: 10px;
}

.role-category-scope-head {
  display: flex;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
}

.role-category-scope-title {
  font-size: 14px;
  font-weight: 500;
  color: var(--el-text-color-primary);
}
</style>
