<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue';
import type { TreeInstance } from 'element-plus';
import { menuTypeRecord } from '@/constants/business';
import { fetchAssignRoleMenus, fetchGetRoleMenuIds } from '@/service/api';
import { $t } from '@/locales';
import { normalizeRoleMenuCheckedIds } from './role-resource-tree';

defineOptions({ name: 'RoleResourcePanel' });

interface Props {
  role: Api.SystemManage.Role | null;
  menuTree: Api.SystemManage.MenuSimple[];
  loading?: boolean;
}

const props = defineProps<Props>();

interface Emits {
  (e: 'saved'): void;
}

const emit = defineEmits<Emits>();

const treeRef = ref<TreeInstance | null>(null);
const permissionLoading = ref(false);
const submitting = ref(false);
const filterKeyword = ref('');
const checkedKeys = ref<string[]>([]);
const baselineMenuIds = ref<string[]>([]);

const disabled = computed(() => !props.role || props.role.status === 1);
const checkedCount = computed(() => checkedKeys.value.length);
const defaultExpandedKeys = computed(() => collectExpandableNodeIds(props.menuTree));
const treeRenderKey = computed(() => `${props.role?.id ?? 'empty'}:${defaultExpandedKeys.value.join(',')}`);

const treeProps = {
  children: 'children',
  label: 'name'
} as const;

function syncCheckedKeys() {
  checkedKeys.value = (treeRef.value?.getCheckedKeys(false) as string[]) ?? [];
}

async function applyCheckedKeys(keys: string[]) {
  checkedKeys.value = [];

  await nextTick();

  const tree = treeRef.value;

  if (!tree) {
    return;
  }

  tree.setCheckedKeys(keys);

  syncCheckedKeys();
}

function getTagType(type: Api.SystemManage.MenuType): UI.ThemeColor {
  const tagMap: Record<Api.SystemManage.MenuType, UI.ThemeColor> = {
    1: 'info',
    2: 'primary',
    3: 'warning'
  };

  return tagMap[type];
}

function getMenuTypeLabel(type: number) {
  return $t(menuTypeRecord[type as Api.SystemManage.MenuType]);
}

function filterNode(value: string, data: any) {
  const node = data as Api.SystemManage.MenuSimple;

  if (!value.trim()) {
    return true;
  }

  return node.name.toLowerCase().includes(value.trim().toLowerCase());
}

function collectExpandableNodeIds(nodes: Api.SystemManage.MenuSimple[]) {
  const ids: string[] = [];

  const walk = (items: Api.SystemManage.MenuSimple[]) => {
    items.forEach(item => {
      const children = item.children ?? [];
      const hasNonButtonChild = children.some(child => child.type !== 3);

      if (hasNonButtonChild) {
        ids.push(item.id);
        walk(children);
      }
    });
  };

  walk(nodes);

  return ids;
}

async function loadRoleMenus() {
  if (!props.role) {
    baselineMenuIds.value = [];
    await applyCheckedKeys([]);
    treeRef.value?.filter(filterKeyword.value);
    return;
  }

  permissionLoading.value = true;

  const { error, data } = await fetchGetRoleMenuIds(props.role.id);

  permissionLoading.value = false;

  if (error) {
    baselineMenuIds.value = [];
    await applyCheckedKeys([]);
    treeRef.value?.filter(filterKeyword.value);
    return;
  }

  const normalizedMenuIds = normalizeRoleMenuCheckedIds({
    menuTree: props.menuTree,
    checkedIds: data
  });

  baselineMenuIds.value = normalizedMenuIds;
  await applyCheckedKeys(normalizedMenuIds);
  treeRef.value?.filter(filterKeyword.value);
}

function handleCheck() {
  syncCheckedKeys();
}

async function handleSave() {
  if (!props.role) {
    return;
  }

  // 直接提交树上完整勾选的节点（getCheckedKeys(false) 自动排除半选父节点），保证“所见即所得”。
  const menuIds = (treeRef.value?.getCheckedKeys(false) as string[]) ?? [];

  submitting.value = true;

  const { error } = await fetchAssignRoleMenus({
    roleId: props.role.id,
    menuIds
  });

  submitting.value = false;

  if (error) {
    return;
  }

  baselineMenuIds.value = [...menuIds];
  syncCheckedKeys();

  window.$message?.success($t('common.modifySuccess'));
  emit('saved');
}

watch(filterKeyword, value => {
  treeRef.value?.filter(value);
});

watch(
  () => props.role?.id,
  () => {
    loadRoleMenus();
  },
  { immediate: true }
);

watch(
  () => props.menuTree.length,
  async value => {
    if (value && props.role) {
      const normalizedMenuIds = normalizeRoleMenuCheckedIds({
        menuTree: props.menuTree,
        checkedIds: baselineMenuIds.value
      });

      baselineMenuIds.value = normalizedMenuIds;
      await applyCheckedKeys(normalizedMenuIds);
      treeRef.value?.filter(filterKeyword.value);
    }
  }
);
</script>

<template>
  <ElCard class="h-full card-wrapper" body-class="role-resource-card-body">
    <template #header>
      <div class="flex items-center justify-between gap-12px">
        <p>{{ $t('page.system.role.resourceAuth') }}</p>
        <ElButton type="primary" size="small" :disabled="disabled" :loading="submitting" @click="handleSave">
          {{ $t('page.system.role.saveAuth') }}
        </ElButton>
      </div>
    </template>

    <template v-if="role">
      <div class="mb-12px flex flex-col gap-10px">
        <ElInput v-model="filterKeyword" clearable :placeholder="$t('page.system.role.form.resourceKeyword')">
          <template #prefix>
            <icon-ic-round-search class="text-icon" />
          </template>
        </ElInput>
        <div class="flex items-center gap-8px text-13px text-[#606266]">
          <span>{{ $t('page.system.role.selectedCount') }}</span>
          <ElTag type="primary" effect="plain">{{ checkedCount }}</ElTag>
        </div>
      </div>

      <ElAlert
        v-if="role.status === 1"
        :title="$t('page.system.role.disabledTip')"
        type="warning"
        class="mb-12px"
        :closable="false"
      />

      <div
        v-loading="permissionLoading || props.loading"
        :class="{ 'pointer-events-none opacity-70': disabled }"
        class="min-h-0 flex-1 overflow-hidden border border-[#ebeef5] rounded-12px bg-[#fcfdff]"
      >
        <ElScrollbar class="h-full px-12px py-12px">
          <ElTree
            :key="treeRenderKey"
            ref="treeRef"
            node-key="id"
            show-checkbox
            :default-expanded-keys="defaultExpandedKeys"
            :data="menuTree"
            :props="treeProps"
            :filter-node-method="filterNode"
            :check-on-click-node="true"
            @check="handleCheck"
          >
            <template #default="{ data }">
              <div class="min-w-0 flex items-center gap-8px">
                <span class="truncate text-14px">{{ data.name }}</span>
                <ElTag size="small" effect="plain" :type="getTagType(data.type)">
                  {{ getMenuTypeLabel(data.type) }}
                </ElTag>
              </div>
            </template>
          </ElTree>
        </ElScrollbar>
      </div>
    </template>

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

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