<script setup lang="ts">
import type { SkillCreateIntent } from './skill-create';
import type {
  SkillCapabilityCandidate,
  SkillCapabilityMapping,
  SkillCapabilityType,
  SkillCategory,
  SkillExportFormat,
  SkillImportPreview,
  SkillInfo,
} from './types';

import {
  computed,
  markRaw,
  onBeforeUnmount,
  onMounted,
  reactive,
  ref,
} from 'vue';
import { useRoute, useRouter } from 'vue-router';

import { downloadFileFromBlob, formatDate } from '@easyflow/utils';

import {
  CopyDocument,
  Delete,
  Download,
  Edit,
  MoreFilled,
  Plus,
  Promotion,
  Upload,
} from '@element-plus/icons-vue';
import {
  ElButton,
  ElCheckbox,
  ElDialog,
  ElDropdown,
  ElDropdownItem,
  ElDropdownMenu,
  ElEmpty,
  ElForm,
  ElFormItem,
  ElIcon,
  ElInput,
  ElMessage,
  ElMessageBox,
  ElOption,
  ElRadio,
  ElRadioGroup,
  ElSelect,
  ElStep,
  ElSteps,
  ElTable,
  ElTableColumn,
  ElTag,
} from 'element-plus';

import { hasPermission } from '#/api/common/hasPermission';
import HeaderSearch from '#/components/headerSearch/HeaderSearch.vue';
import PageData from '#/components/page/PageData.vue';
import PageSide from '#/components/page/PageSide.vue';
import {
  buildListRouteFullPath,
  mergeListRouteStateQuery,
  parseListRouteState,
  watchListRouteState,
} from '#/composables/useListRouteState';
import { $t } from '#/locales';
import { withListReturnTo } from '#/router/list-return-context';
import {
  canAiResourceDelete,
  canAiResourceOffline,
  canAiResourcePublish,
  canAiResourceRepublish,
  isAiResourceApprovalPending,
  resolveAiResourceDisplayStatus,
} from '#/views/ai/shared/publish-status';

import {
  cancelSkillImport,
  copySkill,
  deleteSkillCategory,
  exportSkills,
  getSkillCapabilityCandidates,
  getSkillCategories,
  importSkillConfirm,
  importSkillPreview,
  submitSkillDeleteApproval,
  submitSkillOfflineApproval,
  submitSkillPublishApproval,
} from './api';
import {
  isSkillAccessDeniedError,
  resolveSkillApiErrorMessage,
} from './skill-api-error';
import { flattenSkillCategories } from './skill-category';
import {
  buildSkillImportConfirmPayload,
  invalidateConsumedImportPreview,
  resolveSkillImportConflictReasonLabel,
  resolveSkillImportStep,
} from './skill-import';
import { skillListRouteSchema } from './skill-list-route-state';
import SkillCategoryFormDialog from './SkillCategoryFormDialog.vue';
import SkillCreateDialog from './SkillCreateDialog.vue';

const route = useRoute();
const router = useRouter();
const initialListState = parseListRouteState(route.query, skillListRouteSchema);
const pageDataRef = ref<any>();
const headerSearchRef = ref<{ reset: () => void }>();
const importInputRef = ref<HTMLInputElement>();
const categories = ref<SkillCategory[]>([]);
const categoryLoading = ref(false);
const selectedRows = ref<SkillInfo[]>([]);
const selectedCategoryId = ref<number | string>(initialListState.categoryId);
const filters = reactive({
  keyword: initialListState.keyword,
});
const currentPageNumber = ref(initialListState.pageNumber);
const currentPageSize = ref(initialListState.pageSize);
let initialCategoryChangePending = Boolean(initialListState.categoryId);
watchListRouteState(route, skillListRouteSchema, (state) => {
  if (String(state.categoryId) !== String(selectedCategoryId.value)) {
    initialCategoryChangePending = Boolean(state.categoryId);
  }
  selectedCategoryId.value = state.categoryId;
  filters.keyword = state.keyword;
  currentPageNumber.value = state.pageNumber;
  currentPageSize.value = state.pageSize;
  selectedRows.value = [];
  pageDataRef.value?.restoreState?.({
    pageNumber: state.pageNumber,
    pageSize: state.pageSize,
    queryParams: {
      categoryId: state.categoryId || undefined,
      displayName: state.keyword || undefined,
    },
  });
});

const importDialogOpen = ref(false);
const importStep = ref(0);
const importLoading = ref(false);
const importConfirmed = ref(false);
const importCanceling = ref(false);
const importRetryRequired = ref(false);
const mappingLoadError = ref('');
const importPreview = ref<SkillImportPreview>();
const importForm = reactive({
  categoryId: '' as number | string,
  conflictStrategy: 'REJECT' as 'OVERWRITE' | 'REJECT' | 'RENAME',
  renames: {} as Record<string, string>,
});
const capabilityMappings = ref<SkillCapabilityMapping[]>([]);
const mappingCandidates = reactive<
  Record<SkillCapabilityType, SkillCapabilityCandidate[]>
>({
  MCP: [],
  PLUGIN_ITEM: [],
  WORKFLOW: [],
});

const exportDialogOpen = ref(false);
const exportLoading = ref(false);
const exportFormat = ref<SkillExportFormat>('STANDARD');
const exportIds = ref<Array<number | string>>([]);
const exportName = ref('skill');
const rowAction = ref('');
const categoryDialogOpen = ref(false);
const createDialogOpen = ref(false);
const categoryAction = ref('');
const editingCategory = ref<SkillCategory>();
const copyDialogOpen = ref(false);
const copyLoading = ref(false);
const copyForm = reactive({
  categoryId: '' as number | string,
  displayName: '',
  name: '',
  sourceId: '' as number | string,
});
const canCreate = computed(() => hasPermission(['/api/v1/skill/save']));
const canExport = computed(() => hasPermission(['/api/v1/skill/export']));
const canViewDetail = computed(() =>
  hasPermission(['/api/v1/skill/getDetail']),
);
const canUpdate = computed(() => hasPermission(['/api/v1/skill/update']));
const canEdit = computed(() => canViewDetail.value && canUpdate.value);
const canManageCapabilities = computed(() =>
  hasPermission(['/api/v1/skill/capability']),
);
const canCopy = computed(() => canCreate.value && canManageCapabilities.value);
const canManageCategories = computed(() =>
  hasPermission(['/api/v1/skill/category']),
);
const canPublish = computed(() =>
  hasPermission(['/api/v1/skill/submitPublishApproval']),
);
const canOffline = computed(() =>
  hasPermission(['/api/v1/skill/submitOfflineApproval']),
);
const canDelete = computed(() =>
  hasPermission(['/api/v1/skill/submitDeleteApproval']),
);
const headerButtons = computed(() => [
  {
    data: { action: 'import' },
    disabled: importLoading.value || importCanceling.value,
    icon: markRaw(Upload),
    key: 'import',
    loading: importLoading.value && !importDialogOpen.value,
    permission: '/api/v1/skill/import',
    text: '导入',
  },
  {
    data: { action: 'export' },
    disabled: selectedRows.value.length === 0,
    icon: markRaw(Download),
    key: 'export',
    permission: '/api/v1/skill/export',
    text: '导出',
  },
  {
    data: { action: 'create' },
    icon: markRaw(Plus),
    key: 'create',
    permission: '/api/v1/skill/save',
    text: '新建 Skill',
    type: 'primary',
  },
]);

const unresolvedMappingCount = computed(
  () =>
    capabilityMappings.value.filter((item) => !item.disabled && !item.targetId)
      .length,
);
const conflictItems = computed(
  () => importPreview.value?.skills.filter((item) => item.conflict) || [],
);
const overwriteBlockedCount = computed(
  () =>
    conflictItems.value.filter((item) => item.overwriteAllowed === false)
      .length,
);
const flatCategories = computed(() => flattenSkillCategories(categories.value));
const categoryMenus = computed<SkillCategory[]>(() => [
  {
    categoryName: $t('common.allCategories'),
    id: '',
    status: 1,
  },
  ...categories.value,
]);
const categoryControlBtns = computed(() =>
  canManageCategories.value
    ? [
        {
          disabled: () => Boolean(categoryAction.value),
          icon: markRaw(Edit),
          label: '编辑',
          onClick: openCategoryDialog,
        },
        {
          disabled: () => Boolean(categoryAction.value),
          icon: markRaw(Delete),
          label: '删除',
          onClick: removeCategory,
          type: 'danger',
        },
      ]
    : [],
);
const categoryFooterButton = computed(() =>
  canManageCategories.value
    ? {
        disabled: Boolean(categoryAction.value),
        icon: markRaw(Plus),
        label: '添加',
        onClick: () => openCategoryDialog(),
      }
    : undefined,
);
const importCategoryLabel = computed(() => {
  if (!importForm.categoryId) return '未分类';
  return (
    flatCategories.value.find(
      (category) => String(category.id) === String(importForm.categoryId),
    )?.categoryName || '未知分类'
  );
});
const importConflictSummary = computed(() => {
  if (conflictItems.value.length === 0) return '无同名冲突';
  if (importForm.conflictStrategy === 'OVERWRITE')
    return `覆盖 ${conflictItems.value.length} 个同名草稿`;
  if (importForm.conflictStrategy === 'RENAME')
    return `重命名 ${conflictItems.value.length} 个同名 Skill`;
  return '拒绝同名冲突';
});
const importConfirmLabel = computed(() =>
  importForm.conflictStrategy === 'OVERWRITE' && conflictItems.value.length > 0
    ? '覆盖并导入'
    : '确认导入',
);
const importIssues = computed(() => [
  ...(importPreview.value?.issues || []),
  ...(importPreview.value?.skills.flatMap(
    (item) => item.validationIssues || [],
  ) || []),
]);
const importErrorCount = computed(
  () => importIssues.value.filter((issue) => issue.severity === 'ERROR').length,
);
const hasCapabilityMappings = computed(
  () => capabilityMappings.value.length > 0,
);
const visibleImportStep = computed(() =>
  resolveSkillImportStep(
    importStep.value,
    'visible',
    hasCapabilityMappings.value,
  ),
);
const hasActiveFilters = computed(
  () => Boolean(selectedCategoryId.value) || Boolean(filters.keyword.trim()),
);

onMounted(loadCategories);
onBeforeUnmount(() => {
  const token = importPreview.value?.importToken;
  if (token && !importConfirmed.value && !importCanceling.value) {
    void cancelSkillImport(token).catch(() => undefined);
  }
});

async function loadCategories() {
  if (categoryLoading.value) return;
  categoryLoading.value = true;
  try {
    const res = await getSkillCategories();
    if (res.errorCode !== 0) throw new Error(res.message);
    categories.value = res.data || [];
    if (
      selectedCategoryId.value &&
      !flatCategories.value.some(
        (category) => String(category.id) === String(selectedCategoryId.value),
      )
    ) {
      selectedCategoryId.value = '';
      initialCategoryChangePending = false;
      applyFilters();
    }
  } catch (error) {
    categories.value = [];
    ElMessage.error(
      resolveSkillApiErrorMessage(error, '分类加载失败，请稍后重试'),
    );
  } finally {
    categoryLoading.value = false;
  }
}

function applyFilters() {
  selectedRows.value = [];
  pageDataRef.value?.setQuery?.({
    categoryId: selectedCategoryId.value || undefined,
    displayName: filters.keyword || undefined,
  });
}

function clearFilters() {
  selectedCategoryId.value = '';
  if (headerSearchRef.value) {
    headerSearchRef.value.reset();
    return;
  }
  handleHeaderSearch('');
}

function handleHeaderSearch(keyword: string) {
  filters.keyword = keyword;
  applyFilters();
}

function handleHeaderButtonClick(payload: { key?: string }) {
  if (payload.key === 'import') {
    chooseImportFile();
    return;
  }
  if (payload.key === 'export') {
    openExport();
    return;
  }
  if (payload.key === 'create') {
    openCreate();
  }
}

function selectedTargetCategory() {
  return selectedCategoryId.value;
}

function selectCategory(data?: SkillCategory) {
  const categoryId = data?.id || '';
  if (
    initialCategoryChangePending &&
    String(categoryId) === String(selectedCategoryId.value)
  ) {
    initialCategoryChangePending = false;
    return;
  }
  initialCategoryChangePending = false;
  selectedCategoryId.value = categoryId;
  applyFilters();
}

function currentListFullPath() {
  return buildListRouteFullPath(
    router,
    route.query,
    {
      categoryId: String(selectedCategoryId.value || ''),
      keyword: filters.keyword,
      pageNumber: currentPageNumber.value,
      pageSize: currentPageSize.value,
    },
    skillListRouteSchema,
  );
}

function handlePageStateChange(state: {
  pageNumber: number;
  pageSize: number;
}) {
  currentPageNumber.value = state.pageNumber;
  currentPageSize.value = state.pageSize;
  const query = mergeListRouteStateQuery(
    route.query,
    {
      categoryId: String(selectedCategoryId.value || ''),
      keyword: filters.keyword,
      pageNumber: state.pageNumber,
      pageSize: state.pageSize,
    },
    skillListRouteSchema,
  );
  const target = router.resolve({ path: skillListRouteSchema.path, query });
  if (target.fullPath !== route.fullPath) void router.replace(target);
}

function formatModified(value?: string) {
  return value ? formatDate(value, 'YYYY-MM-DD HH:mm') : '—';
}

function openCreate() {
  if (!canCreate.value) {
    ElMessage.warning('当前没有新建 Skill 的权限');
    return;
  }
  createDialogOpen.value = true;
}

function openDetail(row: SkillInfo) {
  if (!canViewDetail.value) {
    ElMessage.warning('当前没有查看 Skill 详情的权限');
    return;
  }
  router.push({
    path: `/ai/skill/detail/${row.id}`,
    query: {
      navTitle: row.displayName || row.name || 'Skill 详情',
      pageKey: '/ai/skill',
      ...withListReturnTo(currentListFullPath()),
    },
  });
}

function handleSkillCreated(payload: {
  intent: SkillCreateIntent;
  skill: SkillInfo;
}) {
  router.push({
    path: `/ai/skill/detail/${payload.skill.id}`,
    query: {
      ...(payload.intent === 'PUBLISH' ? { publishIntent: '1' } : {}),
      navTitle: payload.skill.displayName || payload.skill.name || 'Skill 详情',
      pageKey: '/ai/skill',
      ...withListReturnTo(currentListFullPath()),
    },
  });
}

function handleRowDoubleClick(row: SkillInfo) {
  if (canViewDetail.value) openDetail(row);
}

function statusMeta(row: SkillInfo) {
  switch (
    resolveAiResourceDisplayStatus(row.displayPublishStatus, row.publishStatus)
  ) {
    case 'DELETE_PENDING': {
      return { label: '删除中', type: 'danger' as const };
    }
    case 'OFFLINE': {
      return { label: '已下线', type: 'info' as const };
    }
    case 'OFFLINE_PENDING': {
      return { label: '下线中', type: 'warning' as const };
    }
    case 'PUBLISH_PENDING': {
      return { label: '发布中', type: 'warning' as const };
    }
    case 'PUBLISHED': {
      return { label: '已发布', type: 'success' as const };
    }
    default: {
      return { label: '草稿', type: 'info' as const };
    }
  }
}

function abilitySummary(row: SkillInfo) {
  const count = Number(row.capabilityCount || 0);
  return count ? `${count} 项能力` : '未绑定';
}

function resourceSummary(row: SkillInfo) {
  const count = Number(row.resourceCount ?? 0);
  if (count) return `${count} 个资源`;
  const legacy =
    Number(row.referenceCount || 0) +
    Number(row.scriptCount || 0) +
    Number(row.assetCount || 0);
  return legacy ? `${legacy} 个资源` : '仅 SKILL.md';
}

function categoryLabel(row: SkillInfo) {
  if (!row.categoryId) return '未分类';
  return (
    flatCategories.value.find(
      (category) => String(category.id) === String(row.categoryId),
    )?.categoryName || '未知分类'
  );
}

async function confirmAction(
  message: string,
  type: 'info' | 'warning' = 'info',
) {
  try {
    await ElMessageBox.confirm(message, '确认操作', {
      cancelButtonText: '取消',
      confirmButtonText: '确认',
      type,
    });
    return true;
  } catch {
    return false;
  }
}

async function handleRowCommand(command: string, row: SkillInfo) {
  if (command === 'edit') {
    if (row.manageable === false) {
      ElMessage.warning('当前仅可查看该 Skill');
      return;
    }
    if (!canEdit.value) return denyRowAction();
    return openDetail(row);
  }
  if (command === 'export') {
    if (!canExport.value) return denyRowAction();
    return openExport([row.id!], row.name);
  }
  if (command === 'copy') {
    if (!canCopy.value) return denyRowAction();
    return openCopy(row);
  }
  if (row.manageable === false) {
    ElMessage.warning('当前仅可查看该 Skill');
    return;
  }
  if (command === 'publish') {
    if (!canPublish.value) return denyRowAction();
    if (!canPublishRow(row)) return denyRowStateAction();
    return publish(row);
  }
  if (command === 'offline') {
    if (!canOffline.value) return denyRowAction();
    if (!canOfflineRow(row)) return denyRowStateAction();
    return offline(row);
  }
  if (command === 'delete') {
    if (!canDelete.value) return denyRowAction();
    if (!canDeleteRow(row)) return denyRowStateAction();
    return remove(row);
  }
}

function denyRowAction() {
  ElMessage.warning('当前没有执行此操作的权限');
}

function denyRowStateAction() {
  ElMessage.warning('当前状态下不能执行此操作');
}

function canPublishRow(row: SkillInfo) {
  return (
    row.manageable !== false &&
    canPublish.value &&
    (canAiResourcePublish(row.displayPublishStatus, row.publishStatus) ||
      canAiResourceRepublish(row.displayPublishStatus, row.publishStatus))
  );
}

function canOfflineRow(row: SkillInfo) {
  return (
    row.manageable !== false &&
    canOffline.value &&
    canAiResourceOffline(row.displayPublishStatus, row.publishStatus)
  );
}

function canDeleteRow(row: SkillInfo) {
  return (
    row.manageable !== false &&
    canDelete.value &&
    canAiResourceDelete(row.displayPublishStatus, row.publishStatus)
  );
}

function canShowRowMenu(row: SkillInfo) {
  return (
    canExport.value ||
    canCopy.value ||
    canPublishRow(row) ||
    canOfflineRow(row) ||
    canDeleteRow(row)
  );
}

function openCopy(row: SkillInfo) {
  if (!row.id || copyLoading.value) return;
  copyForm.sourceId = row.id;
  copyForm.name = `${row.name || 'skill'}-copy`;
  copyForm.displayName = `${row.displayName || row.name || 'Skill'} 副本`;
  copyForm.categoryId = row.categoryId || '';
  copyDialogOpen.value = true;
}

async function confirmCopy() {
  if (copyLoading.value) return;
  const name = copyForm.name.trim();
  if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
    ElMessage.warning('标准名称仅支持小写字母、数字和连字符');
    return;
  }
  if (!copyForm.displayName.trim()) {
    ElMessage.warning('请输入展示名称');
    return;
  }
  copyLoading.value = true;
  try {
    const res = await copySkill({
      categoryId: copyForm.categoryId || undefined,
      displayName: copyForm.displayName.trim(),
      name,
      sourceId: copyForm.sourceId,
    });
    if (res.errorCode !== 0) return;
    copyDialogOpen.value = false;
    ElMessage.success('Skill 已复制为新草稿');
    pageDataRef.value?.reload?.();
  } finally {
    copyLoading.value = false;
  }
}

async function publish(row: SkillInfo) {
  const actionKey = `publish:${row.id}`;
  if (rowAction.value) return;
  rowAction.value = actionKey;
  try {
    if (
      isAiResourceApprovalPending(row.displayPublishStatus, row.publishStatus)
    ) {
      ElMessage.warning('当前 Skill 正在审批中');
      return;
    }
    const republish = canAiResourceRepublish(
      row.displayPublishStatus,
      row.publishStatus,
    );
    if (
      !(await confirmAction(
        republish ? '确认提交重新发布审批？' : '确认提交发布审批？',
      ))
    )
      return;
    const res = await submitSkillPublishApproval(row.id!);
    if (res.errorCode === 0) {
      ElMessage.success(res.message || '已提交审批');
      pageDataRef.value?.reload?.();
    }
  } finally {
    if (rowAction.value === actionKey) rowAction.value = '';
  }
}

async function offline(row: SkillInfo) {
  const actionKey = `offline:${row.id}`;
  if (rowAction.value) return;
  rowAction.value = actionKey;
  try {
    if (
      !(await confirmAction(
        '下线后当前发布版本将不可继续使用，确认提交审批？',
        'warning',
      ))
    )
      return;
    const res = await submitSkillOfflineApproval(row.id!);
    if (res.errorCode === 0) {
      ElMessage.success(res.message || '已提交下线审批');
      pageDataRef.value?.reload?.();
    }
  } finally {
    if (rowAction.value === actionKey) rowAction.value = '';
  }
}

async function remove(row: SkillInfo) {
  const actionKey = `delete:${row.id}`;
  if (rowAction.value) return;
  rowAction.value = actionKey;
  try {
    if (
      !(await confirmAction('删除后将无法继续编辑或使用此 Skill。', 'warning'))
    )
      return;
    const res = await submitSkillDeleteApproval(row.id!);
    if (res.errorCode === 0) {
      ElMessage.success(
        res.message ||
          (res.data === null || res.data === undefined
            ? 'Skill 已删除'
            : '已提交删除审批'),
      );
      pageDataRef.value?.reload?.();
    }
  } finally {
    if (rowAction.value === actionKey) rowAction.value = '';
  }
}

function chooseImportFile() {
  if (importLoading.value || importCanceling.value) return;
  importInputRef.value?.click();
}

async function handleImportFile(event: Event) {
  const input = event.target as HTMLInputElement;
  const file = input.files?.[0];
  input.value = '';
  if (!file || importLoading.value || importCanceling.value) return;
  importLoading.value = true;
  try {
    const res = await importSkillPreview(file);
    if (res.errorCode !== 0) return;
    importPreview.value = res.data;
    capabilityMappings.value = structuredClone(
      res.data.capabilityMappings || [],
    );
    importForm.categoryId = selectedTargetCategory();
    importForm.conflictStrategy = 'REJECT';
    importForm.renames = Object.fromEntries(
      (res.data.skills || []).map((item) => [
        item.packageId,
        item.suggestedName || `${item.name}-copy`,
      ]),
    );
    importStep.value = 0;
    importConfirmed.value = false;
    importRetryRequired.value = false;
    importDialogOpen.value = true;
    mappingLoadError.value = '';
    await loadMappingCandidates();
  } finally {
    importLoading.value = false;
  }
}

async function loadMappingCandidates() {
  const types = [
    ...new Set(capabilityMappings.value.map((item) => item.capabilityType)),
  ];
  mappingLoadError.value = '';
  for (const type of types) mappingCandidates[type] = [];
  await Promise.all(
    types.map(async (type) => {
      try {
        const res = await getSkillCapabilityCandidates(type);
        if (res.errorCode === 0) {
          mappingCandidates[type] = res.data || [];
        } else {
          mappingCandidates[type] = [];
          mappingLoadError.value = '能力候选加载失败，请重试';
        }
      } catch {
        mappingCandidates[type] = [];
        mappingLoadError.value = '能力候选加载失败，请重试';
      }
    }),
  );
}

function nextImportStep() {
  if (importStep.value === 0 && importErrorCount.value) {
    ElMessage.error(
      `导入包包含 ${importErrorCount.value} 个错误，请修复后重新上传`,
    );
    return;
  }
  if (
    importStep.value === 0 &&
    conflictItems.value.length > 0 &&
    importForm.conflictStrategy === 'REJECT'
  ) {
    ElMessage.warning('存在不可用名称，请选择重命名或覆盖可覆盖的草稿');
    return;
  }
  if (importStep.value === 0 && importForm.conflictStrategy === 'RENAME') {
    const error = validateImportRenames();
    if (error) {
      ElMessage.warning(error);
      return;
    }
  }
  if (
    importStep.value === 0 &&
    importForm.conflictStrategy === 'OVERWRITE' &&
    overwriteBlockedCount.value > 0
  ) {
    ElMessage.warning('所选包包含不可覆盖的 Skill，请改为重命名');
    return;
  }
  if (importStep.value === 1 && mappingLoadError.value) {
    ElMessage.warning(mappingLoadError.value);
    return;
  }
  if (importStep.value === 1 && unresolvedMappingCount.value) {
    ElMessage.warning('请完成能力映射，或明确禁用未解析能力');
    return;
  }
  importStep.value = resolveSkillImportStep(
    importStep.value,
    'next',
    hasCapabilityMappings.value,
  );
}

function previousImportStep() {
  importStep.value = resolveSkillImportStep(
    importStep.value,
    'previous',
    hasCapabilityMappings.value,
  );
}

async function confirmImport() {
  if (
    !importPreview.value?.importToken ||
    importLoading.value ||
    importCanceling.value
  )
    return;
  if (importErrorCount.value || unresolvedMappingCount.value) {
    ElMessage.warning('导入设置尚未完成');
    return;
  }
  if (importForm.conflictStrategy === 'RENAME') {
    const error = validateImportRenames();
    if (error) {
      ElMessage.warning(error);
      return;
    }
  }
  if (
    importForm.conflictStrategy === 'OVERWRITE' &&
    conflictItems.value.length > 0
  ) {
    try {
      await ElMessageBox.confirm(
        `将覆盖 ${conflictItems.value.length} 个当前仍为草稿且有权管理的同名 Skill。覆盖后原草稿内容会被替换。`,
        '确认覆盖导入',
        {
          cancelButtonText: '返回检查',
          confirmButtonText: '覆盖并导入',
          type: 'warning',
        },
      );
    } catch {
      return;
    }
  }
  importLoading.value = true;
  try {
    const res = await importSkillConfirm(
      buildSkillImportConfirmPayload(
        importPreview.value,
        {
          categoryId: importForm.categoryId || undefined,
          conflictStrategy: importForm.conflictStrategy,
          renames: importForm.renames,
        },
        capabilityMappings.value,
      ),
    );
    if (res.errorCode === 0) {
      importConfirmed.value = true;
      importDialogOpen.value = false;
      ElMessage.success(`已导入 ${res.data?.length || 0} 个 Skill`);
      pageDataRef.value?.reload?.();
      return;
    }
    requireImportRepreview();
  } catch {
    requireImportRepreview();
  } finally {
    importLoading.value = false;
  }
}

function requireImportRepreview() {
  if (importPreview.value) {
    importPreview.value = invalidateConsumedImportPreview(importPreview.value);
  }
  capabilityMappings.value = [];
  importRetryRequired.value = true;
  importStep.value = 2;
  ElMessage.error('导入未完成，请重新选择文件预览后再试');
}

async function closeImport(done?: () => void) {
  if (importLoading.value || importCanceling.value) {
    ElMessage.info('请等待当前操作完成');
    return;
  }
  importCanceling.value = true;
  const token = importPreview.value?.importToken;
  try {
    if (token && !importConfirmed.value) {
      try {
        await cancelSkillImport(token);
      } catch {
        // Temporary import data also expires server-side.
      }
    }
    importPreview.value = undefined;
    capabilityMappings.value = [];
    importRetryRequired.value = false;
    done?.();
  } finally {
    importCanceling.value = false;
  }
}

function openExport(ids?: Array<number | string>, canonicalName?: string) {
  if (exportLoading.value) return;
  const selectedSkills = ids
    ? []
    : selectedRows.value.filter((item) => Boolean(item.id));
  const selectedIds = ids || selectedSkills.map((item) => item.id!);
  if (selectedIds.length === 0) {
    ElMessage.warning('请先选择要导出的 Skill');
    return;
  }
  exportIds.value = selectedIds;
  exportName.value = normalizeExportName(
    canonicalName ||
      (selectedSkills.length === 1 ? selectedSkills[0]?.name : ''),
  );
  exportFormat.value = 'STANDARD';
  exportDialogOpen.value = true;
}

async function confirmExport() {
  if (exportLoading.value) return;
  if (exportFormat.value === 'EASYFLOW' && !canManageCapabilities.value) {
    ElMessage.warning('当前没有能力绑定查看权限');
    return;
  }
  exportLoading.value = true;
  try {
    const blob = await exportSkills(exportIds.value, exportFormat.value);
    const single = exportIds.value.length === 1;
    const suffix = exportFormat.value === 'EASYFLOW' ? 'efskill' : 'zip';
    downloadFileFromBlob({
      fileName: single
        ? `${exportName.value}.${suffix}`
        : `skills-${Date.now()}.${suffix}`,
      source: blob,
    });
    exportDialogOpen.value = false;
    ElMessage.success('导出完成');
  } finally {
    exportLoading.value = false;
  }
}

function normalizeExportName(value?: string) {
  const normalized = String(value || '')
    .trim()
    .toLowerCase()
    .replaceAll(/[^a-z0-9-]+/g, '-')
    .replaceAll(/^-+|-+$/g, '');
  return normalized || 'skill';
}

function openCategoryDialog(category?: SkillCategory) {
  if (!canManageCategories.value || categoryAction.value) return;
  editingCategory.value = category ? { ...category } : undefined;
  categoryDialogOpen.value = true;
}

async function removeCategory(category: SkillCategory) {
  if (!canManageCategories.value || categoryAction.value) return;
  try {
    await ElMessageBox.confirm(
      `确认删除分类“${category.categoryName}”？`,
      '删除分类',
      {
        cancelButtonText: '取消',
        confirmButtonText: '删除',
        type: 'warning',
      },
    );
  } catch {
    return;
  }

  categoryAction.value = `delete:${category.id}`;
  try {
    const res = await deleteSkillCategory(category.id);
    if (res.errorCode !== 0) {
      ElMessage.error(res.message || '分类删除失败，请重试');
      return;
    }
    ElMessage.success('分类已删除');
    await handleCategoryChanged();
  } catch (error) {
    ElMessage.error(resolveSkillApiErrorMessage(error, '分类删除失败，请重试'));
  } finally {
    categoryAction.value = '';
  }
}

async function handleCategorySaved() {
  editingCategory.value = undefined;
  await handleCategoryChanged();
}

async function handleCategoryChanged() {
  await loadCategories();
  if (
    selectedCategoryId.value &&
    !flatCategories.value.some(
      (category) => String(category.id) === String(selectedCategoryId.value),
    )
  ) {
    selectedCategoryId.value = '';
    applyFilters();
    return;
  }
  pageDataRef.value?.reload?.();
}

function validateImportRenames() {
  const renamed = new Map(
    conflictItems.value.map((item) => [
      item.packageId,
      String(importForm.renames[item.packageId] || '').trim(),
    ]),
  );
  if ([...renamed.values()].some((name) => !name)) {
    return '请填写所有重命名后的标准名称';
  }
  if (
    [...renamed.values()].some(
      (name) => !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name),
    )
  ) {
    return '重命名仅支持小写字母、数字和连字符';
  }
  const finalNames = (importPreview.value?.skills || []).map(
    (item) => renamed.get(item.packageId) || item.name,
  );
  if (
    new Set(finalNames.map((name) => name.toLowerCase())).size !==
    finalNames.length
  ) {
    return '导入后的 Skill 名称不能重复';
  }
  return '';
}

function typeLabel(type: SkillCapabilityType) {
  return { MCP: 'MCP', PLUGIN_ITEM: '插件工具', WORKFLOW: '工作流' }[type];
}

function isRowBusy(row: SkillInfo) {
  return rowAction.value.endsWith(`:${row.id}`);
}
</script>

<template>
  <div class="skill-list-page" v-loading="importLoading">
    <input
      ref="importInputRef"
      class="skill-list-page__file-input"
      type="file"
      accept=".zip,.efskill,application/zip"
      :disabled="importLoading || importCanceling"
      @change="handleImportFile"
    />

    <header class="skill-list-page__header">
      <HeaderSearch
        ref="headerSearchRef"
        :buttons="headerButtons"
        :initial-value="filters.keyword"
        search-placeholder="请输入 Skill 名称或描述"
        @search="handleHeaderSearch"
        @button-click="handleHeaderButtonClick"
      >
        <template #middle>
          <ElSelect
            v-model="selectedCategoryId"
            class="skill-list-page__mobile-category"
            clearable
            placeholder="全部分类"
            @change="applyFilters"
          >
            <ElOption
              v-for="category in flatCategories"
              :key="category.id"
              :label="`${'\u00a0\u00a0'.repeat(category.depth)}${category.categoryName}${category.status === 1 ? '' : '（停用）'}`"
              :value="category.id"
            />
          </ElSelect>
        </template>
      </HeaderSearch>
    </header>

    <section class="skill-list-page__workspace">
      <PageSide
        id="skill-category-sidebar"
        v-loading="categoryLoading"
        aria-label="Skill 分类"
        children-key="children"
        :control-btns="categoryControlBtns"
        :default-selected="selectedCategoryId"
        :footer-button="categoryFooterButton"
        label-key="categoryName"
        :menus="categoryMenus"
        value-key="id"
        @change="selectCategory"
      >
        <template #label="{ item }">
          <span class="skill-list-page__category-node">
            <span>{{ item.categoryName }}</span>
            <small v-if="item.status !== 1">停用</small>
          </span>
        </template>
      </PageSide>

      <main class="skill-list-page__content">
        <PageData
          ref="pageDataRef"
          page-url="/api/v1/skill/page"
          :page-size="initialListState.pageSize"
          :page-sizes="[12, 24, 48]"
          :initial-page-number="initialListState.pageNumber"
          :initial-query-params="{
            categoryId: initialListState.categoryId || undefined,
            displayName: initialListState.keyword || undefined,
          }"
          @state-change="handlePageStateChange"
        >
          <template #default="{ pageList }">
            <ElTable
              :data="pageList"
              row-key="id"
              class="skill-list-page__table"
              @selection-change="selectedRows = $event"
              @row-dblclick="handleRowDoubleClick"
            >
              <ElTableColumn type="selection" width="48" />
              <ElTableColumn label="Skill" min-width="280">
                <template #default="{ row }">
                  <button
                    v-if="canViewDetail"
                    type="button"
                    class="skill-list-page__identity"
                    @click="openDetail(row)"
                  >
                    <span
                      class="skill-list-page__skill-mark"
                      aria-hidden="true"
                    >
                      S
                    </span>
                    <span>
                      <strong>{{ row.displayName || row.name }}</strong>
                      <small>{{ row.description || '暂无描述' }}</small>
                    </span>
                  </button>
                  <div v-else class="skill-list-page__identity is-static">
                    <span
                      class="skill-list-page__skill-mark"
                      aria-hidden="true"
                    >
                      S
                    </span>
                    <span>
                      <strong>{{ row.displayName || row.name }}</strong>
                      <small>{{ row.description || '暂无描述' }}</small>
                    </span>
                  </div>
                </template>
              </ElTableColumn>
              <ElTableColumn label="分类" min-width="136">
                <template #default="{ row }">
                  <span class="skill-list-page__category-label">
                    {{ categoryLabel(row) }}
                  </span>
                </template>
              </ElTableColumn>
              <ElTableColumn label="资源" min-width="130">
                <template #default="{ row }">
                  {{ resourceSummary(row) }}
                </template>
              </ElTableColumn>
              <ElTableColumn label="能力" min-width="120">
                <template #default="{ row }">
                  {{ abilitySummary(row) }}
                </template>
              </ElTableColumn>
              <ElTableColumn label="状态" width="112">
                <template #default="{ row }">
                  <ElTag
                    size="small"
                    effect="plain"
                    :type="statusMeta(row).type"
                  >
                    {{ statusMeta(row).label }}
                  </ElTag>
                </template>
              </ElTableColumn>
              <ElTableColumn label="更新时间" min-width="160">
                <template #default="{ row }">
                  {{ formatModified(row.modified) }}
                </template>
              </ElTableColumn>
              <ElTableColumn label="操作" width="116" fixed="right">
                <template #default="{ row }">
                  <div class="skill-list-page__row-actions">
                    <ElButton
                      v-if="row.manageable !== false && canEdit"
                      v-access:code="'/api/v1/skill/update'"
                      :icon="Edit"
                      text
                      aria-label="编辑"
                      :disabled="isRowBusy(row)"
                      @click="openDetail(row)"
                    />
                    <ElDropdown
                      v-if="canShowRowMenu(row)"
                      :disabled="Boolean(rowAction)"
                      @command="handleRowCommand($event, row)"
                    >
                      <ElButton
                        :icon="MoreFilled"
                        text
                        aria-label="更多操作"
                        :loading="isRowBusy(row)"
                      />
                      <template #dropdown>
                        <ElDropdownMenu>
                          <ElDropdownItem
                            v-if="canExport"
                            command="export"
                            :icon="Download"
                          >
                            导出
                          </ElDropdownItem>
                          <ElDropdownItem
                            v-if="canCopy"
                            command="copy"
                            :icon="CopyDocument"
                          >
                            复制
                          </ElDropdownItem>
                          <ElDropdownItem
                            v-if="canPublishRow(row)"
                            command="publish"
                            :icon="Promotion"
                          >
                            {{
                              canAiResourceRepublish(
                                row.displayPublishStatus,
                                row.publishStatus,
                              )
                                ? '重新发布'
                                : '发布'
                            }}
                          </ElDropdownItem>
                          <ElDropdownItem
                            v-if="canOfflineRow(row)"
                            command="offline"
                          >
                            下线
                          </ElDropdownItem>
                          <ElDropdownItem
                            v-if="canDeleteRow(row)"
                            command="delete"
                            :icon="Delete"
                            divided
                          >
                            删除
                          </ElDropdownItem>
                        </ElDropdownMenu>
                      </template>
                    </ElDropdown>
                  </div>
                </template>
              </ElTableColumn>
            </ElTable>
          </template>
          <template #error="{ error, retry }">
            <ElEmpty
              v-if="isSkillAccessDeniedError(error)"
              description="你没有查看 Skill 列表的权限"
            />
            <ElEmpty v-else description="Skill 列表加载失败，请重试">
              <ElButton type="primary" @click="retry">重新加载</ElButton>
            </ElEmpty>
          </template>
          <template #empty>
            <ElEmpty
              :description="
                hasActiveFilters
                  ? '筛选条件下没有匹配的 Skill'
                  : '当前范围内没有 Skill'
              "
            >
              <ElButton v-if="hasActiveFilters" @click="clearFilters">
                清除筛选
              </ElButton>
              <ElButton
                v-else-if="canCreate"
                v-access:code="'/api/v1/skill/save'"
                type="primary"
                :icon="Plus"
                @click="openCreate"
              >
                新建 Skill
              </ElButton>
            </ElEmpty>
          </template>
        </PageData>
      </main>
    </section>

    <ElDialog
      v-model="copyDialogOpen"
      title="复制 Skill"
      width="min(480px, calc(100vw - 32px))"
      :close-on-click-modal="false"
      :close-on-press-escape="!copyLoading"
      :show-close="!copyLoading"
    >
      <ElForm label-position="top">
        <ElFormItem label="标准名称" required>
          <ElInput
            v-model="copyForm.name"
            maxlength="64"
            placeholder="例如 content-review-copy"
          />
        </ElFormItem>
        <ElFormItem label="展示名称" required>
          <ElInput v-model="copyForm.displayName" maxlength="128" />
        </ElFormItem>
        <ElFormItem label="目标分类">
          <ElSelect
            v-model="copyForm.categoryId"
            clearable
            placeholder="未分类"
          >
            <ElOption
              v-for="category in flatCategories"
              :key="category.id"
              :label="`${'\u00a0\u00a0'.repeat(category.depth)}${category.categoryName}`"
              :value="category.id"
              :disabled="category.status !== 1"
            />
          </ElSelect>
        </ElFormItem>
      </ElForm>
      <template #footer>
        <ElButton :disabled="copyLoading" @click="copyDialogOpen = false">
          取消
        </ElButton>
        <ElButton type="primary" :loading="copyLoading" @click="confirmCopy">
          复制为草稿
        </ElButton>
      </template>
    </ElDialog>

    <SkillCategoryFormDialog
      v-if="canManageCategories"
      v-model="categoryDialogOpen"
      :category="editingCategory"
      @saved="handleCategorySaved"
    />

    <SkillCreateDialog
      v-if="canCreate"
      v-model="createDialogOpen"
      :can-publish="canPublish"
      :categories="categories"
      :categories-loading="categoryLoading"
      :default-category-id="selectedCategoryId"
      @created="handleSkillCreated"
    />

    <ElDialog
      v-model="importDialogOpen"
      title="导入 Skill"
      width="min(760px, 94vw)"
      :close-on-click-modal="false"
      :close-on-press-escape="!importLoading && !importCanceling"
      :show-close="!importLoading && !importCanceling"
      :before-close="closeImport"
    >
      <ElSteps :active="visibleImportStep" finish-status="success" align-center>
        <ElStep title="检查内容" />
        <ElStep v-if="hasCapabilityMappings" title="能力映射" />
        <ElStep title="确认导入" />
      </ElSteps>
      <div class="skill-import__content">
        <template v-if="importStep === 0">
          <div class="skill-import__summary">
            <div>
              <strong>{{ importPreview?.skills.length || 0 }}</strong>
              <span>Skill</span>
            </div>
            <div>
              <strong>{{
                importPreview?.format === 'EASYFLOW' ? '.efskill' : '.zip'
              }}</strong>
              <span>格式</span>
            </div>
            <div>
              <strong>{{ conflictItems.length }}</strong>
              <span>同名冲突</span>
            </div>
          </div>
          <div
            v-if="importIssues.length > 0"
            class="skill-import__issues"
            aria-live="polite"
          >
            <div
              v-for="(issue, index) in importIssues"
              :key="`${issue.path}-${issue.line}-${issue.code}-${index}`"
              :class="`is-${issue.severity.toLowerCase()}`"
            >
              <ElTag
                size="small"
                effect="plain"
                :type="
                  issue.severity === 'ERROR'
                    ? 'danger'
                    : issue.severity === 'WARNING'
                      ? 'warning'
                      : 'info'
                "
              >
                {{
                  issue.severity === 'ERROR'
                    ? '错误'
                    : issue.severity === 'WARNING'
                      ? '警告'
                      : '提示'
                }}
              </ElTag>
              <span>{{ issue.message }}</span>
              <small v-if="issue.path">
                {{ issue.path
                }}<template v-if="issue.line">:{{ issue.line }}</template>
              </small>
            </div>
          </div>
          <ElForm label-position="top">
            <ElFormItem label="目标分类">
              <ElSelect
                v-model="importForm.categoryId"
                clearable
                class="skill-import__field"
                placeholder="未分类"
              >
                <ElOption
                  v-for="category in flatCategories"
                  :key="category.id"
                  :label="`${'\u00a0\u00a0'.repeat(category.depth)}${category.categoryName}${category.status === 1 ? '' : '（停用）'}`"
                  :value="category.id"
                  :disabled="category.status !== 1"
                />
              </ElSelect>
            </ElFormItem>
            <ElFormItem v-if="conflictItems.length > 0" label="名称冲突处理">
              <ElRadioGroup v-model="importForm.conflictStrategy">
                <ElRadio value="REJECT">冲突时阻止整包导入</ElRadio>
                <ElRadio value="RENAME">重命名</ElRadio>
                <ElRadio
                  value="OVERWRITE"
                  :disabled="overwriteBlockedCount > 0"
                >
                  覆盖有权管理的草稿
                </ElRadio>
              </ElRadioGroup>
            </ElFormItem>
          </ElForm>
          <div class="skill-import__items">
            <div
              v-for="item in importPreview?.skills"
              :key="item.packageId"
              class="skill-import__item"
            >
              <div class="skill-import__item-main">
                <div class="skill-import__item-copy">
                  <strong>{{ item.name }}</strong>
                  <span>{{ item.description || '暂无描述' }}</span>
                </div>
                <ElTag
                  v-if="item.conflict"
                  type="warning"
                  effect="plain"
                  size="small"
                >
                  名称冲突
                </ElTag>
                <ElTag
                  v-if="item.conflict && item.overwriteAllowed === false"
                  type="danger"
                  effect="plain"
                  size="small"
                >
                  {{
                    resolveSkillImportConflictReasonLabel(item.conflictReason)
                  }}
                </ElTag>
                <ElInput
                  v-if="
                    item.conflict && importForm.conflictStrategy === 'RENAME'
                  "
                  v-model="importForm.renames[item.packageId]"
                  class="skill-import__rename"
                />
              </div>
              <details
                v-if="item.files?.length"
                class="skill-import__file-tree"
              >
                <summary>查看文件（{{ item.files.length }}）</summary>
                <ul>
                  <li v-for="file in item.files" :key="file.path">
                    <code>{{ file.path }}</code>
                    <span>{{
                      file.text ? '文本' : file.mediaType || '二进制'
                    }}</span>
                  </li>
                </ul>
              </details>
            </div>
          </div>
        </template>

        <template v-else-if="importStep === 1">
          <div
            v-if="mappingLoadError"
            class="skill-import__mapping-error"
            role="alert"
          >
            <span>{{ mappingLoadError }}</span>
            <ElButton text @click="loadMappingCandidates">重新加载</ElButton>
          </div>
          <div
            v-if="capabilityMappings.length > 0"
            class="skill-import__mappings"
          >
            <div
              v-for="mapping in capabilityMappings"
              :key="mapping.bindingKey"
              class="skill-import__mapping"
            >
              <div>
                <strong>{{ mapping.targetName || mapping.runtimeName }}</strong>
                <span>
                  {{ typeLabel(mapping.capabilityType) }} ·
                  {{ mapping.targetLogicalRef || '待映射' }}
                </span>
              </div>
              <ElSelect
                v-model="mapping.targetId"
                clearable
                filterable
                :disabled="mapping.disabled"
                placeholder="选择目标资源"
              >
                <ElOption
                  v-for="candidate in mappingCandidates[mapping.capabilityType]"
                  :key="candidate.targetId"
                  :label="candidate.name"
                  :value="candidate.targetId"
                  :disabled="candidate.status !== 'AVAILABLE'"
                />
              </ElSelect>
              <ElCheckbox v-model="mapping.disabled">导入后禁用</ElCheckbox>
            </div>
          </div>
          <ElEmpty v-else description="标准 Skill 包不包含平台能力绑定" />
        </template>

        <template v-else>
          <div class="skill-import__confirm">
            <ElIcon><Promotion /></ElIcon>
            <h3>{{ importRetryRequired ? '需要重新预览' : '准备导入' }}</h3>
            <p v-if="importRetryRequired">
              本次一次性导入令牌已失效，请重新选择文件并检查内容。
            </p>
            <p v-else>确认后将按当前设置导入，无需重新上传文件。</p>
            <ul v-if="!importRetryRequired">
              <li>{{ importPreview?.skills.length || 0 }} 个 Skill</li>
              <li>目标分类：{{ importCategoryLabel }}</li>
              <li>冲突处理：{{ importConflictSummary }}</li>
              <li>
                {{ capabilityMappings.filter((item) => !item.disabled).length }}
                项启用能力
              </li>
              <li>能力映射不会导入平台凭据</li>
            </ul>
          </div>
        </template>
      </div>
      <template #footer>
        <ElButton
          v-if="importRetryRequired"
          type="primary"
          :disabled="importLoading || importCanceling"
          @click="chooseImportFile"
        >
          重新选择文件
        </ElButton>
        <ElButton
          v-else-if="importStep > 0"
          :disabled="importLoading || importCanceling"
          @click="previousImportStep"
        >
          上一步
        </ElButton>
        <ElButton
          v-if="!importRetryRequired && importStep < 2"
          type="primary"
          :disabled="importLoading || importCanceling"
          @click="nextImportStep"
        >
          下一步
        </ElButton>
        <ElButton
          v-else-if="!importRetryRequired"
          type="primary"
          :loading="importLoading"
          :disabled="importCanceling"
          @click="confirmImport"
        >
          {{ importConfirmLabel }}
        </ElButton>
      </template>
    </ElDialog>

    <ElDialog
      v-model="exportDialogOpen"
      title="导出 Skill"
      width="min(560px, 92vw)"
      :close-on-click-modal="!exportLoading"
      :close-on-press-escape="!exportLoading"
      :show-close="!exportLoading"
    >
      <div class="skill-export__options">
        <label :class="{ 'is-active': exportFormat === 'STANDARD' }">
          <ElRadio v-model="exportFormat" value="STANDARD">
            标准 Skill 包
          </ElRadio>
          <strong>.zip</strong>
          <p>只导出 SKILL.md 与可移植资源，不包含分类、权限和能力绑定。</p>
        </label>
        <label
          :class="{
            'is-active': exportFormat === 'EASYFLOW',
            'is-disabled': !canManageCapabilities,
          }"
        >
          <ElRadio
            v-model="exportFormat"
            value="EASYFLOW"
            :disabled="!canManageCapabilities"
          >
            EasyFlow 增强包
          </ElRadio>
          <strong>.efskill</strong>
          <p v-if="canManageCapabilities">
            额外包含能力逻辑引用和非敏感配置，跨环境导入时可重新映射。
          </p>
          <p v-else>需要能力绑定查看权限。</p>
        </label>
      </div>
      <p class="skill-export__notice">
        平台能力绑定与连接配置不会携带 Token、Header、连接密钥或插件凭据；请勿在
        Skill 文件中写入敏感信息。
      </p>
      <template #footer>
        <ElButton :disabled="exportLoading" @click="exportDialogOpen = false">
          取消
        </ElButton>
        <ElButton
          type="primary"
          :loading="exportLoading"
          @click="confirmExport"
        >
          导出
        </ElButton>
      </template>
    </ElDialog>
  </div>
</template>

<style scoped>
.skill-list-page {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  height: 100%;
  min-height: 0;
  padding: var(--space-6);
  font-family: var(--font-family);
  color: hsl(var(--foreground));
  background: transparent;
}

.skill-list-page__header {
  display: flex;
  align-items: center;
  min-height: 40px;
}

.skill-list-page__row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

.skill-list-page__file-input {
  display: none;
}

.skill-list-page__workspace {
  display: grid;
  flex: 1;
  grid-template-columns: 225px minmax(0, 1fr);
  gap: var(--space-6);
  min-height: 0;
  overflow: hidden;
}

.skill-list-page__category-node {
  display: flex;
  flex: 1;
  gap: var(--space-2);
  align-items: center;
  justify-content: space-between;
  min-width: 0;
}

.skill-list-page__category-node > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.skill-list-page__category-node small {
  flex: none;
  font-size: 11px;
  color: hsl(var(--text-muted));
}

.skill-list-page__content {
  display: flex;
  flex: 1;
  flex-direction: column;
  height: 100%;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: hsl(var(--surface-panel));
  border: 1px solid hsl(var(--line-subtle));
  border-radius: var(--radius-panel);
}

.skill-list-page__mobile-category {
  display: none;
}

.skill-list-page :deep(.page-data-container) {
  width: 100%;
  height: 100%;
  padding: 0;
  overflow: hidden;
}

.skill-list-page :deep(.page-data-container > .el-empty) {
  flex: 1;
  justify-content: center;
}

.skill-list-page__table {
  height: 100%;
}

.skill-list-page__identity {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  width: 100%;
  padding: 0;
  color: inherit;
  text-align: left;
  cursor: pointer;
  background: transparent;
  border: 0;
}

.skill-list-page__identity.is-static {
  cursor: default;
}

.skill-list-page__identity > span:last-child {
  display: grid;
  gap: 3px;
  min-width: 0;
}

.skill-list-page__identity strong,
.skill-list-page__identity small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.skill-list-page__identity small {
  color: hsl(var(--text-muted));
}

.skill-list-page__identity:focus-visible {
  outline: 2px solid hsl(var(--primary) / 72%);
  outline-offset: 3px;
}

.skill-list-page__category-label {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.skill-list-page__skill-mark {
  display: grid;
  flex: none;
  place-items: center;
  width: 34px;
  height: 34px;
  font-weight: 650;
  color: hsl(var(--nav-item-active-foreground));
  background: hsl(var(--nav-item-active));
  border-radius: var(--radius-control);
}

.skill-import__content {
  min-height: 360px;
  padding: var(--space-6) 0 var(--space-2);
}

.skill-import__summary {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  margin-bottom: var(--space-6);
  background: hsl(var(--surface-subtle));
  border-radius: var(--radius-toolbar);
}

.skill-import__summary > div {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-4);
  text-align: center;
}

.skill-import__summary span,
.skill-import__item span,
.skill-import__mapping span {
  font-size: 12px;
  color: hsl(var(--text-muted));
}

.skill-import__field {
  width: 100%;
}

.skill-import__items,
.skill-import__mappings {
  display: grid;
  gap: var(--space-2);
  max-height: 260px;
  overflow: auto;
}

.skill-import__issues {
  display: grid;
  gap: var(--space-2);
  max-height: 160px;
  padding: var(--space-3);
  margin-bottom: var(--space-4);
  overflow: auto;
  background: hsl(var(--surface-subtle));
  border-radius: var(--radius-toolbar);
}

.skill-import__mapping-error {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
  color: hsl(var(--destructive));
  background: hsl(var(--destructive) / 8%);
  border-radius: var(--radius-control);
}

.skill-import__issues > div {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-2);
  align-items: center;
}

.skill-import__issues small {
  color: hsl(var(--text-muted));
}

.skill-import__item,
.skill-import__mapping {
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: hsl(var(--surface-subtle));
  border-radius: var(--radius-toolbar);
}

.skill-import__item {
  display: grid;
}

.skill-import__mapping,
.skill-import__item-main {
  display: flex;
  gap: var(--space-3);
  align-items: center;
}

.skill-import__item-copy,
.skill-import__mapping > div {
  display: grid;
  flex: 1;
  gap: var(--space-1);
  min-width: 0;
}

.skill-import__file-tree {
  padding-top: var(--space-2);
  border-top: 1px solid hsl(var(--line-subtle));
}

.skill-import__file-tree summary {
  width: fit-content;
  font-size: 12px;
  color: hsl(var(--primary));
  cursor: pointer;
}

.skill-import__file-tree summary:focus-visible {
  outline: 2px solid hsl(var(--primary) / 72%);
  outline-offset: 2px;
}

.skill-import__file-tree ul {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-2) 0 0 var(--space-4);
  margin: 0;
  list-style: none;
}

.skill-import__file-tree li {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  justify-content: space-between;
  min-width: 0;
}

.skill-import__file-tree code {
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 12px;
  color: hsl(var(--text-strong));
  white-space: nowrap;
}

.skill-import__rename {
  width: 200px;
}

.skill-import__mapping :deep(.el-select) {
  width: 220px;
}

.skill-import__confirm {
  display: grid;
  gap: var(--space-2);
  place-items: center;
  max-width: 480px;
  padding: var(--space-8);
  margin: auto;
  text-align: center;
}

.skill-import__confirm > .el-icon {
  width: 56px;
  height: 56px;
  font-size: 26px;
  color: hsl(var(--nav-item-active-foreground));
  background: hsl(var(--nav-item-active));
  border-radius: 50%;
}

.skill-import__confirm h3,
.skill-import__confirm p {
  margin: 0;
}

.skill-import__confirm p,
.skill-import__confirm li {
  color: hsl(var(--text-muted));
}

.skill-export__options {
  display: grid;
  gap: var(--space-3);
}

.skill-export__options label {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-2);
  padding: var(--space-4);
  cursor: pointer;
  border: 1px solid hsl(var(--line-subtle));
  border-radius: var(--radius-toolbar);
  transition:
    border-color var(--motion-duration-fast) var(--motion-ease-standard),
    background-color var(--motion-duration-fast) var(--motion-ease-standard);
}

.skill-export__options label:not(.is-disabled):hover,
.skill-export__options label.is-active {
  background: hsl(var(--nav-item-hover));
  border-color: hsl(var(--primary) / 42%);
}

.skill-export__options label.is-disabled {
  color: hsl(var(--text-muted));
  cursor: not-allowed;
  background: hsl(var(--surface-subtle));
}

.skill-export__options p {
  grid-column: 1 / -1;
  margin: 0;
  line-height: 1.6;
  color: hsl(var(--text-muted));
}

.skill-export__notice {
  padding: var(--space-3);
  margin: var(--space-4) 0 0;
  font-size: 12px;
  color: hsl(var(--text-muted));
  background: hsl(var(--surface-subtle));
  border-radius: var(--radius-control);
}

@media (max-width: 900px) {
  .skill-list-page {
    padding: var(--space-4);
  }

  .skill-list-page__workspace {
    grid-template-columns: minmax(0, 1fr);
  }

  .skill-list-page :deep(#skill-category-sidebar) {
    display: none;
  }

  .skill-list-page__mobile-category {
    display: block;
    width: min(240px, 100%);
  }
}

@media (max-width: 640px) {
  .skill-list-page__mobile-category {
    width: 100%;
  }

  .skill-import__summary {
    grid-template-columns: 1fr;
  }

  .skill-import__mapping {
    flex-direction: column;
    align-items: stretch;
  }

  .skill-import__item-main {
    flex-wrap: wrap;
  }

  .skill-import__mapping :deep(.el-select) {
    width: 100%;
  }
}
</style>
