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

import type { WorkflowListRouteState } from './workflow-list-route-state';

import type {
  ActionButton,
  CardPrimaryAction,
} from '#/components/page/CardList.vue';
import type { OfflineImpactCheck } from '#/views/ai/shared/offline-impact';

import {
  computed,
  defineAsyncComponent,
  h,
  markRaw,
  onMounted,
  ref,
} from 'vue';
import { useRoute } from 'vue-router';

import { useAccess } from '@easyflow/access';
import { EasyFlowFormModal } from '@easyflow/common-ui';
import { useAppConfig } from '@easyflow/hooks';

import {
  Check,
  CopyDocument,
  Delete,
  Download,
  Edit,
  Link,
  Lock,
  OfficeBuilding,
  Plus,
  Promotion,
  Share,
  Tickets,
  Upload,
  VideoPlay,
} from '@element-plus/icons-vue';
import {
  ElDialog,
  ElForm,
  ElFormItem,
  ElIcon,
  ElInput,
  ElInputNumber,
  ElMessage,
  ElMessageBox,
  ElPopover,
  ElSkeleton,
} from 'element-plus';
import { tryit } from 'radash';

import { api } from '#/api/request';
import workflowIcon from '#/assets/ai/workflow/workflowIcon.png';
// import workflowSvg from '#/assets/workflow.svg';
import HeaderSearch from '#/components/headerSearch/HeaderSearch.vue';
import CardList from '#/components/page/CardList.vue';
import PageData from '#/components/page/PageData.vue';
import PageSide from '#/components/page/PageSide.vue';
import {
  buildListRouteFullPath,
  watchListRouteState,
} from '#/composables/useListRouteState';
import { $t } from '#/locales';
import { router } from '#/router';
import { withListReturnTo } from '#/router/list-return-context';
import { useDictStore } from '#/store';
import { copyTextWithFeedback } from '#/utils/clipboard-feedback';
import { createLazyComponentController } from '#/utils/lazy-component';
import { buildAbsoluteAppRouteUrl } from '#/utils/share-route-context';
import AiResourceCornerMeta from '#/views/ai/shared/AiResourceCornerMeta.vue';
import { confirmPublishSubmission } from '#/views/ai/shared/approval-application-reason';
import { buildOfflineImpactMessage } from '#/views/ai/shared/offline-impact';
import {
  canAiResourceDelete,
  canAiResourceOffline,
  canAiResourcePublish,
  canAiResourceRepublish,
  isAiResourceApprovalPending,
  resolveAiResourceDisplayStatus,
} from '#/views/ai/shared/publish-status';

import {
  mergeWorkflowListRouteQuery,
  parseWorkflowListRouteState,
  workflowListRouteSchema,
} from './workflow-list-route-state';

const ElXMarkdown = defineAsyncComponent(
  () => import('vue-element-plus-x/es/XMarkdown/index.js'),
);

const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
const route = useRoute();
const initialListState = parseWorkflowListRouteState(route.query);

interface FieldDefinition {
  // 字段名称
  prop: string;
  // 字段标签
  label: string;
  // 字段类型：input, number, select, radio, checkbox, switch, date, datetime
  type?: 'input' | 'number';
  // 是否必填
  required?: boolean;
  // 占位符
  placeholder?: string;
}

type VisibilityScope = 'DEPT' | 'PRIVATE' | 'PUBLIC';

interface ApiFieldDoc {
  key: string;
  label: string;
  type: string;
  required: boolean;
  description: string;
  placeholder: string;
}

interface WorkflowModalInstance {
  openDialog: (row: Record<string, unknown>, importMode?: boolean) => void;
}

const {
  component: WorkflowModalComponent,
  componentRef: saveDialog,
  ensureInstance: ensureWorkflowModal,
  loading: workflowModalLoading,
} = createLazyComponentController<WorkflowModalInstance>(
  () => import('./WorkflowModal.vue'),
);

const primaryAction: CardPrimaryAction = {
  showIndicator: false,
  text: $t('button.design'),
  permission: '/api/v1/workflow/save',
  onClick: (row: any) => {
    toDesignPage(row);
  },
};

const { hasAccessByCodes } = useAccess();
const canManageWorkflow = computed(() =>
  hasAccessByCodes(['/api/v1/workflow/save']),
);
const updatingScopeId = ref<null | number | string>(null);
const sharingWorkflowId = ref<null | number | string>(null);
const visibilityScopePopoverRefs = ref<Record<string, any>>({});
const apiInstructionVisible = ref(false);
const apiInstructionRow = ref<any>(null);
const visibilityScopeMeta = computed(() => ({
  PRIVATE: {
    label: $t('aiWorkflow.visibilityScopePrivate'),
    description: $t('aiWorkflow.visibilityScopePrivateDesc'),
    icon: Lock,
    tone: 'private',
  },
  DEPT: {
    label: $t('aiWorkflow.visibilityScopeDept'),
    description: $t('aiWorkflow.visibilityScopeDeptDesc'),
    icon: OfficeBuilding,
    tone: 'dept',
  },
  PUBLIC: {
    label: $t('aiWorkflow.visibilityScopePublic'),
    description: $t('aiWorkflow.visibilityScopePublicDesc'),
    icon: Promotion,
    tone: 'public',
  },
}));
const visibilityScopeOptions = computed(() => {
  return (['PRIVATE', 'DEPT', 'PUBLIC'] as VisibilityScope[]).map((value) => ({
    value,
    ...visibilityScopeMeta.value[value],
  }));
});

const actions: ActionButton[] = [
  {
    icon: Edit,
    text: $t('button.edit'),
    permission: '/api/v1/workflow/save',
    placement: 'inline',
    onClick: (row: any) => {
      showDialog(row);
    },
  },
  {
    icon: VideoPlay,
    text: $t('button.run'),
    placement: 'inline',
    onClick: (row: any) => {
      router.push({
        name: 'RunPage',
        query: {
          id: row.id,
          ...withListReturnTo(currentListFullPath()),
        },
      });
    },
  },
  {
    icon: Share,
    text: $t('button.share'),
    permission: '/api/v1/workflow/save',
    placement: 'menu',
    disabled: (row: any) =>
      row.publishStatus !== 'PUBLISHED' || sharingWorkflowId.value === row.id,
    loading: (row: any) => sharingWorkflowId.value === row.id,
    onClick: (row: any) => {
      shareWorkflow(row);
    },
  },
  {
    icon: Tickets,
    text: $t('aiWorkflowExecRecord.moduleName'),
    permission: '/api/v1/workflow/save',
    placement: 'menu',
    onClick: (row: any) => {
      router.push({
        name: 'ExecRecord',
        query: {
          workflowId: row.id,
          ...withListReturnTo(currentListFullPath()),
        },
      });
    },
  },
  {
    icon: Link,
    text: $t('aiWorkflow.apiInstruction'),
    placement: 'menu',
    onClick: (row: any) => {
      showApiInstruction(row);
    },
  },
  {
    icon: Download,
    text: $t('button.export'),
    permission: '/api/v1/workflow/save',
    placement: 'menu',
    onClick: (row: any) => {
      exportJson(row);
    },
  },
  {
    icon: CopyDocument,
    text: $t('button.copy'),
    permission: '/api/v1/workflow/save',
    placement: 'menu',
    onClick: (row: any) => {
      showDialog({
        title: `${row.title}Copy`,
        content: row.content,
      });
    },
  },
  {
    icon: Promotion,
    text: (row: any) =>
      canAiResourceRepublish(row.displayPublishStatus, row.publishStatus)
        ? $t('button.republish')
        : $t('button.publish'),
    permission: '/api/v1/workflow/save',
    placement: 'inline',
    visible: (row: any) =>
      canAiResourcePublish(row.displayPublishStatus, row.publishStatus) ||
      canAiResourceRepublish(row.displayPublishStatus, row.publishStatus),
    onClick: (row: any) => {
      submitPublishAction(row);
    },
  },
  {
    icon: Promotion,
    text: $t('button.offline'),
    permission: '/api/v1/workflow/save',
    placement: 'menu',
    visible: (row: any) =>
      canAiResourceOffline(row.displayPublishStatus, row.publishStatus),
    onClick: (row: any) => {
      submitOfflineAction(row);
    },
  },
  {
    icon: Delete,
    text: $t('button.delete'),
    tone: 'danger',
    permission: '/api/v1/workflow/remove',
    placement: 'menu',
    visible: (row: any) =>
      canAiResourceDelete(row.displayPublishStatus, row.publishStatus),
    onClick: (row: any) => {
      submitDeleteApproval(row);
    },
  },
];
onMounted(() => {
  initDict();
  getSideList();
});
const pageDataRef = ref();
const selectedCategoryId = ref<number | string>(initialListState.categoryId);
const searchKeyword = ref(initialListState.keyword);
const currentPageNumber = ref(initialListState.pageNumber);
const currentPageSize = ref(initialListState.pageSize);
const suppressInitialCategoryChange = ref(Boolean(initialListState.categoryId));
const initialQueryParams = {
  categoryId: initialListState.categoryId || undefined,
  keyword: initialListState.keyword || undefined,
};
watchListRouteState(route, workflowListRouteSchema, (state) => {
  if (String(state.categoryId) !== String(selectedCategoryId.value)) {
    suppressInitialCategoryChange.value = Boolean(state.categoryId);
  }
  selectedCategoryId.value = state.categoryId;
  searchKeyword.value = state.keyword;
  currentPageNumber.value = state.pageNumber;
  currentPageSize.value = state.pageSize;
  pageDataRef.value?.restoreState?.({
    pageNumber: state.pageNumber,
    pageSize: state.pageSize,
    queryParams: {
      categoryId: state.categoryId || undefined,
      keyword: state.keyword || undefined,
    },
  });
});
const dictStore = useDictStore();
const headerButtons = [
  {
    key: 'create',
    text: $t('button.add'),
    icon: markRaw(Plus),
    type: 'primary',
    data: { action: 'create' },
    permission: '/api/v1/workflow/save',
  },
  {
    key: 'import',
    text: $t('button.import'),
    icon: markRaw(Upload),
    type: 'default',
    data: { action: 'import' },
    permission: '/api/v1/workflow/save',
  },
];

function initDict() {
  dictStore.fetchDictionary('dataStatus');
}
function resolveVisibilityScopeMeta(scope?: string) {
  return (
    visibilityScopeMeta.value[(scope || 'PRIVATE') as VisibilityScope] ||
    visibilityScopeMeta.value.PRIVATE
  );
}
function setVisibilityScopePopoverRef(id: number | string, el: any) {
  const cacheKey = String(id);
  if (el) {
    visibilityScopePopoverRefs.value[cacheKey] = el;
    return;
  }
  delete visibilityScopePopoverRefs.value[cacheKey];
}
function closeVisibilityScopePopover(id: number | string) {
  visibilityScopePopoverRefs.value[String(id)]?.hide?.();
}
async function updateVisibilityScope(
  row: any,
  visibilityScope: VisibilityScope,
) {
  if (
    !canManageWorkflow.value ||
    !row?.id ||
    updatingScopeId.value === row.id ||
    row.visibilityScope === visibilityScope
  ) {
    closeVisibilityScopePopover(row.id);
    return;
  }
  updatingScopeId.value = row.id;
  try {
    const res = await api.post('/api/v1/workflow/update', {
      id: row.id,
      visibilityScope,
    });
    if (res.errorCode === 0) {
      row.visibilityScope = visibilityScope;
      ElMessage.success(res.message);
      closeVisibilityScopePopover(row.id);
      pageDataRef.value?.reload?.();
    }
  } finally {
    updatingScopeId.value = null;
  }
}
function applyFilters() {
  pageDataRef.value?.setQuery({
    categoryId: selectedCategoryId.value || undefined,
    keyword: searchKeyword.value || undefined,
  });
}
function currentListState(): WorkflowListRouteState {
  const pageState = pageDataRef.value?.getPageState?.();
  return {
    categoryId: String(selectedCategoryId.value || ''),
    keyword: searchKeyword.value,
    pageNumber: pageState?.pageNumber ?? currentPageNumber.value,
    pageSize: pageState?.pageSize ?? currentPageSize.value,
  };
}
function currentListFullPath() {
  return buildListRouteFullPath(
    router,
    route.query,
    currentListState(),
    workflowListRouteSchema,
  );
}
function syncListRouteState() {
  const target = {
    path: '/ai/workflow',
    query: mergeWorkflowListRouteQuery(route.query, currentListState()),
  };
  if (router.resolve(target).fullPath !== route.fullPath) {
    void router.replace(target);
  }
}
function handlePageStateChange(state: {
  pageNumber: number;
  pageSize: number;
}) {
  currentPageNumber.value = state.pageNumber;
  currentPageSize.value = state.pageSize;
  syncListRouteState();
}
function handleSearch(keyword: string) {
  searchKeyword.value = keyword;
  applyFilters();
}
function reloadCurrentList() {
  pageDataRef.value?.reload?.();
}
async function showDialog(row: any, importMode = false) {
  try {
    const modal = await ensureWorkflowModal();
    modal.openDialog({ ...row }, importMode);
  } catch (error) {
    console.error('Failed to load workflow modal:', error);
    ElMessage.error($t('message.getDataError'));
  }
}
function resolveNavTitle(row: any) {
  return row?.title || row?.name || '';
}
function isRepublishAction(row: any) {
  return canAiResourceRepublish(row.displayPublishStatus, row.publishStatus);
}
function isWorkflowPublished(row: any) {
  return (
    resolveAiResourceDisplayStatus(
      row.displayPublishStatus,
      row.publishStatus,
    ) === 'PUBLISHED'
  );
}
function showApiInstruction(row: any) {
  if (!isWorkflowPublished(row) || !getPublishedWorkflowContent(row)) {
    ElMessage.warning($t('aiWorkflow.apiInstructionPublishRequired'));
    return;
  }
  apiInstructionRow.value = row;
  apiInstructionVisible.value = true;
}
function getPublishedWorkflowContent(row: any) {
  const snapshot = row?.publishedSnapshotJson;
  if (snapshot && typeof snapshot === 'object' && snapshot.content) {
    return snapshot.content;
  }
  return null;
}
function parseWorkflowContent(row: any) {
  const content = getPublishedWorkflowContent(row);
  if (!content) {
    return null;
  }
  if (typeof content === 'object') {
    return content;
  }
  try {
    return JSON.parse(String(content));
  } catch {
    return null;
  }
}
function normalizeApiOrigin(value?: string) {
  const rawValue = String(value || '').trim();
  if (!rawValue) {
    return window.location.origin;
  }
  if (/^https?:\/\//i.test(rawValue)) {
    return rawValue.replace(/\/+$/, '');
  }
  const normalizedPath = rawValue.startsWith('/') ? rawValue : `/${rawValue}`;
  return `${window.location.origin}${normalizedPath}`.replace(/\/+$/, '');
}
function resolveApiBaseUrl() {
  return `${normalizeApiOrigin(apiURL)}/public-api/workflow`;
}
function resolveStartNodeData(row: any) {
  const workflow = parseWorkflowContent(row);
  const nodes = Array.isArray(workflow?.nodes) ? workflow.nodes : [];
  const startNode = nodes.find((node: any) => node?.type === 'startNode');
  return startNode?.data || {};
}
function resolveApiFields(row: any): ApiFieldDoc[] {
  const startNodeData = resolveStartNodeData(row);
  const schema = Array.isArray(startNodeData.startFormSchema)
    ? startNodeData.startFormSchema
    : [];
  const parameters = Array.isArray(startNodeData.parameters)
    ? startNodeData.parameters
    : [];
  const source = schema.length > 0 ? schema : parameters;
  return source
    .map((item: any) => {
      const key = String(item?.key || item?.name || '').trim();
      if (!key) {
        return null;
      }
      return {
        key,
        label: String(item?.label || item?.title || item?.name || key),
        type: String(item?.type || item?.contentType || 'text'),
        required: Boolean(item?.required),
        description: String(item?.description || item?.formDescription || ''),
        placeholder: String(item?.placeholder || item?.formPlaceholder || ''),
      };
    })
    .filter(Boolean) as ApiFieldDoc[];
}
function isFileApiField(field: ApiFieldDoc) {
  return (
    field.type.trim().toLowerCase() === 'file' ||
    field.type.trim().toLowerCase() === 'files'
  );
}
function buildExampleVariables(row: any, includeFileReferences = true) {
  const variables: Record<string, any> = {};
  for (const field of resolveApiFields(row)) {
    if (isFileApiField(field)) {
      if (!includeFileReferences) {
        continue;
      }
      variables[field.key] = 'https://files.example.com/example.pdf';
      continue;
    }
    if (field.type === 'checkbox') {
      variables[field.key] = [];
      continue;
    }
    variables[field.key] = field.placeholder || field.label;
  }
  return variables;
}
function buildRunRequestExample(row: any) {
  return JSON.stringify(
    {
      id: row?.id,
      variables: buildExampleVariables(row),
    },
    null,
    2,
  );
}
function quoteShellFormValue(value: string) {
  return `'${value.replaceAll(`'`, String.raw`'"'"'`)}'`;
}
function buildMultipartCurlExample(row: any, url: string) {
  const fileFields = resolveApiFields(row).filter((field) =>
    isFileApiField(field),
  );
  const metadata = JSON.stringify({
    id: row?.id,
    variables: buildExampleVariables(row, false),
  });
  const parts = [
    `curl --request POST '${url}'`,
    `  --header 'ApiKey: <your-api-key>'`,
    `  --form ${quoteShellFormValue(
      `metadata=${metadata};type=application/json`,
    )}`,
    ...fileFields.map(
      (field) =>
        `  --form ${quoteShellFormValue(`files.${field.key}=@/path/to/example.pdf;type=application/pdf`)}`,
    ),
  ];
  return parts
    .map((line, index) => (index < parts.length - 1 ? `${line} \\` : line))
    .join('\n');
}
function buildRunResponseExample(row: any) {
  return JSON.stringify(
    {
      errorCode: 0,
      message: '成功',
      data: '执行ID',
      workflow: {
        workflowId: String(row?.id ?? ''),
        alias: row?.alias || null,
        title: row?.title || null,
        description: row?.description || null,
        revision: row?.revision ?? null,
        publishedAt: row?.publishedAt || null,
        nodes: [
          {
            nodeId: '<节点 ID>',
            nodeType: '<节点类型>',
            nodeName: '<节点名称>',
            description: null,
            parentNodeId: null,
            definitionIndex: 0,
            topologyIndex: 0,
            topologyLevel: 0,
            inDegree: 0,
            outDegree: 1,
            startNode: true,
            endNode: false,
            predecessorNodeIds: [],
            successorNodeIds: ['<后继节点 ID>'],
            incomingEdgeIds: [],
            outgoingEdgeIds: ['<边 ID>'],
            inputParameters: [
              {
                parameterId: '<参数 ID>',
                name: 'documents',
                label: '文档',
                dataType: 'File',
                contentType: 'file',
                required: true,
                description: null,
                multipartPartName: 'files.documents',
              },
            ],
            outputParameters: [],
          },
        ],
        edges: [
          {
            edgeId: '<边 ID>',
            edgeType: '<边类型>',
            label: null,
            sourceNodeId: '<源节点 ID>',
            targetNodeId: '<目标节点 ID>',
            sourceHandle: null,
            targetHandle: null,
            parentNodeId: null,
            definitionIndex: 0,
            dangling: false,
          },
        ],
        topologicalOrder: ['<开始节点 ID>', '<处理节点 ID>', '<结束节点 ID>'],
        topologyLevels: [
          ['<开始节点 ID>'],
          ['<处理节点 ID>'],
          ['<结束节点 ID>'],
        ],
        hasCycle: false,
        unresolvedNodeIds: [],
      },
    },
    null,
    2,
  );
}
function buildResumeRequestExample() {
  return JSON.stringify(
    {
      executeId: '执行ID',
      confirmParams: {
        confirm: true,
      },
    },
    null,
    2,
  );
}
async function copyApiContent(content: string) {
  await copyTextWithFeedback(
    content,
    $t('message.copySuccess'),
    $t('message.copyFail'),
  );
}
function handleApiDocClick(e: MouseEvent) {
  const target = (e.target as HTMLElement).closest('.api-url-copy-btn');
  if (!target) return;
  const url = (target as HTMLElement).dataset.copy;
  if (url) copyApiContent(url);
}
function apiUrlLine(method: string, url: string) {
  const escaped = url.replaceAll('"', '&quot;');
  return `\`${method}\` \`${url}\` <button type="button" class="api-url-copy-btn" data-copy="${escaped}" title="复制接口地址" aria-label="复制接口地址">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>`;
}

function escapeMarkdownTableCell(value: unknown) {
  return String(value ?? '')
    .replaceAll('|', String.raw`\|`)
    .replaceAll(/\r?\n/g, '<br>');
}

/* eslint-disable unicorn/prefer-single-call -- 文档按接口章节顺序增量组装，可读性优先。 */
const apiDocMarkdown = computed(() => {
  const row = apiInstructionRow.value;
  if (!row) return '';

  const baseUrl = resolveApiBaseUrl();
  const fields = resolveApiFields(row);

  const lines: string[] = [];

  // ---- 概述 ----
  lines.push(`## 概述`);
  lines.push(``);
  lines.push(`通过 Public API 可异步执行已发布的工作流并获取执行结果。`);
  lines.push(``);
  lines.push(
    `**调用流程**：发起执行 → 轮询查询执行结果 → （若有确认节点）恢复执行`,
  );
  lines.push(``);

  // ---- 鉴权 ----
  lines.push(`## 鉴权`);
  lines.push(``);
  lines.push(
    `所有请求需在 Header 中携带访问令牌，访问令牌需开启 **工作流 API 调用授权**。`,
  );
  lines.push(``);
  lines.push('```');
  lines.push(`ApiKey: <your-api-key>`);
  lines.push('```');
  lines.push(``);
  lines.push(`---`);
  lines.push(``);

  // ---- 1. 发起执行 ----
  lines.push(`## 1. 发起执行`);
  lines.push(``);
  lines.push(apiUrlLine('POST', `${baseUrl}/runAsync`));
  lines.push(``);
  lines.push(
    `异步执行工作流，立即返回执行 ID。工作流必须已发布且存在发布快照。`,
  );
  lines.push(``);
  lines.push(`### JSON 请求`);
  lines.push(``);
  lines.push(
    `> 顶层 \`Content-Type\` 必须为 \`application/json\`。多数 HTTP 客户端在发送 JSON 对象时会自动设置；直接发送文本或文件内容时请显式声明。`,
  );
  lines.push(``);
  lines.push('```json');
  lines.push(buildRunRequestExample(row));
  lines.push('```');
  lines.push(``);
  if (fields.some((field) => isFileApiField(field))) {
    lines.push(`### Multipart 文件直传`);
    lines.push(``);
    lines.push(
      `文件参数可直接通过 \`multipart/form-data\` 上传，无需先获取文件地址。\`metadata\` 只承载 API 请求元数据；工作流文件统一使用 \`files.<开始节点文件参数名>\`，例如参数 \`documents\` 对应 Part \`files.documents\`。`,
    );
    lines.push(``);
    lines.push(
      `> 请勿手工添加顶层 \`Content-Type: multipart/form-data\`。由 cURL、Postman 或 Hoppscotch 根据请求体自动生成包含 boundary 的请求头；\`metadata\` Part 必须使用 \`application/json\`。`,
    );
    lines.push(``);
    lines.push('```bash');
    lines.push(buildMultipartCurlExample(row, `${baseUrl}/runAsync`));
    lines.push('```');
    lines.push(``);
    lines.push(
      `同一文件参数需要上传多个文件时，重复传入同名 \`files.<参数名>\` Part。文件 Part 可声明合法 MIME；客户端发送空值、\`Other\` 或非法值时，服务端会按扩展名推断，仍无法识别时使用 \`application/octet-stream\`。`,
    );
    lines.push(``);
  }

  // 入参说明
  lines.push(`### 入参说明`);
  lines.push(``);
  lines.push(`| 参数 | 类型 | 必填 | 说明 |`);
  lines.push(`| --- | --- | --- | --- |`);
  lines.push(`| id | string | 是 | 工作流 ID |`);
  lines.push(`| variables | object | 否 | 运行参数，字段说明见下方 |`);
  lines.push(``);

  if (fields.length > 0) {
    lines.push(`#### variables 字段明细`);
    lines.push(``);
    lines.push(`以下字段基于当前发布快照的开始节点配置生成。`);
    lines.push(``);
    lines.push(`| 参数 | 类型 | 必填 | 说明 |`);
    lines.push(`| --- | --- | --- | --- |`);
    for (const f of fields) {
      const fileDescription = isFileApiField(f)
        ? 'JSON 可直接填写 HTTP/HTTPS 文件 URL；多文件使用 URL 数组。URL 路径需包含文件名和扩展名；继续兼容 fileName + filePath 文件对象'
        : '';
      const desc = [f.label, f.description, fileDescription]
        .filter(Boolean)
        .join(' · ');
      const type = isFileApiField(f) ? 'string / string[]' : f.type;
      lines.push(
        `| ${escapeMarkdownTableCell(f.key)} | ${escapeMarkdownTableCell(type)} | ${f.required ? '是' : '否'} | ${escapeMarkdownTableCell(desc)} |`,
      );
    }
    lines.push(``);
  }

  lines.push(`### 响应`);
  lines.push(``);
  lines.push('```json');
  lines.push(buildRunResponseExample(row));
  lines.push('```');
  lines.push(``);
  lines.push(
    `\`data\` 为 **执行 ID**（executeId），后续查询和恢复均需使用此 ID。`,
  );
  lines.push(``);
  lines.push(
    `\`workflow.nodes\` 提供全部公开节点信息；\`topologicalOrder\` 提供稳定的节点 ID 拓扑顺序；\`topologyLevels\` 按可并行层级分组。节点配置中的提示词、脚本和连接密钥等运行配置不会返回。`,
  );
  lines.push(``);
  lines.push(`---`);
  lines.push(``);

  // ---- 2. 查询执行结果 ----
  lines.push(`## 2. 查询执行结果`);
  lines.push(``);
  lines.push(apiUrlLine('POST', `${baseUrl}/getChainStatus`));
  lines.push(``);
  lines.push(
    `查询工作流执行的整体状态与各节点执行详情。工作流为异步执行，建议**轮询**该接口直到状态为终态。`,
  );
  lines.push(``);
  lines.push(`### 请求体`);
  lines.push(``);
  lines.push('```json');
  lines.push(
    JSON.stringify(
      {
        executeId: '<runAsync 返回的执行 ID>',
        nodes: [{ nodeId: '<runAsync.workflow.nodes[].nodeId>' }],
      },
      null,
      2,
    ),
  );
  lines.push('```');
  lines.push(``);
  lines.push(
    `> \`nodes\` 参数可选。不传则只返回工作流整体状态；需要节点详情时，可将 \`runAsync.workflow.nodes\` 中的 \`nodeId\` 组成对象数组传入，服务端会从该执行实例的定义快照补齐 \`nodeName\`。`,
  );
  lines.push(``);
  lines.push(`### 响应示例`);
  lines.push(``);
  lines.push('```json');
  lines.push(
    JSON.stringify(
      {
        errorCode: 0,
        message: '成功',
        data: {
          executeId: 'abc5358c-a310-4caa-97ec-455062b2235e',
          status: 'failed',
          terminal: true,
          message: '工作流执行失败，请检查输入或稍后重试',
          result: null,
          nodes: {
            '<节点 ID>': {
              nodeId: '<节点 ID>',
              nodeName: '文档解析',
              status: 'failed',
              message: '节点执行失败，请检查输入或稍后重试',
            },
          },
          error: {
            code: 'WORKFLOW_EXECUTION_FAILED',
            message: '工作流执行失败，请检查输入或稍后重试',
            nodeId: '<节点 ID>',
            nodeName: '文档解析',
            retryable: false,
          },
        },
      },
      null,
      2,
    ),
  );
  lines.push('```');
  lines.push(``);
  lines.push(`### 状态值说明`);
  lines.push(``);
  lines.push(`| status | terminal | 说明 |`);
  lines.push(`| --- | --- | --- |`);
  lines.push(`| ready | false | 就绪，尚未开始 |`);
  lines.push(`| running | false | 执行中 |`);
  lines.push(`| suspended | false | 已暂停，等待确认参数恢复 |`);
  lines.push(`| error | false | 执行异常，运行时可能仍在处理 |`);
  lines.push(`| done | true | 执行成功 |`);
  lines.push(`| failed | true | 执行失败 |`);
  lines.push(`| cancelled | true | 已取消 |`);
  lines.push(`| unknown | false | 无法识别的兼容状态 |`);
  lines.push(``);
  lines.push(
    `调用方应优先根据 \`terminal\` 判断是否结束轮询；值为 \`true\` 时，状态为 \`done\`、\`failed\` 或 \`cancelled\`。`,
  );
  lines.push(``);

  // ---- 3. 恢复执行 ----
  lines.push(`---`);
  lines.push(``);
  lines.push(`## 3. 恢复执行（确认节点）`);
  lines.push(``);
  lines.push(apiUrlLine('POST', `${baseUrl}/resume`));
  lines.push(``);
  lines.push(
    `当工作流包含**确认节点**时，执行到该节点后状态变为 \`suspended\`，需要调用此接口传入确认参数后恢复执行。仅暂停中的执行实例允许恢复；若工作流不包含确认节点则无需调用。`,
  );
  lines.push(``);
  lines.push(`### 请求体`);
  lines.push(``);
  lines.push('```json');
  lines.push(buildResumeRequestExample());
  lines.push('```');
  lines.push(``);
  lines.push(`### 响应`);
  lines.push(``);
  lines.push('```json');
  lines.push(
    JSON.stringify({ errorCode: 0, message: '成功', data: null }, null, 2),
  );
  lines.push('```');
  lines.push(``);
  lines.push(`恢复后可继续轮询 \`getChainStatus\` 获取后续执行状态。`);
  lines.push(``);

  // ---- 错误码 ----
  lines.push(`---`);
  lines.push(``);
  lines.push(`## 错误处理`);
  lines.push(``);
  lines.push(
    `请求尚未受理时会返回真实的 HTTP 4xx/5xx，\`errorCode\` 为稳定业务码，\`data.requestId\` 可用于服务端排查。请求已返回 executeId 后的节点失败通过 \`getChainStatus.data.error\` 获取。`,
  );
  lines.push(``);
  lines.push(`| HTTP | errorCode | 场景与处理 |`);
  lines.push(`| --- | --- | --- |`);
  lines.push(
    `| 400 | 40011 | JSON 请求体无效；文件直传请改用 multipart/form-data |`,
  );
  lines.push(
    `| 400 | 40012 | Multipart 缺少或无法解析 boundary；删除手工设置的顶层 Content-Type |`,
  );
  lines.push(`| 400 | 40013 | 缺少 metadata Part |`);
  lines.push(`| 400 | 40014 | metadata Part 不是有效 JSON |`);
  lines.push(`| 400 | 40015 | metadata.id 缺失 |`);
  lines.push(`| 400 | 40016 | files.<参数名> 与开始节点文件参数不匹配 |`);
  lines.push(
    `| 400 | 40017 | 其他工作流运行参数不合法，例如文件 URL 无法识别文件名或扩展名 |`,
  );
  lines.push(`| 401 | 40101 | 缺少 ApiKey 请求头 |`);
  lines.push(`| 401 | 40102 / 40103 | ApiKey 无效、禁用或过期 |`);
  lines.push(`| 403 | 40301 / 40302 | 缺少接口权限或工作流调用权限 |`);
  lines.push(`| 404 | 40401 | 工作流不存在、未发布或不可公开调用 |`);
  lines.push(`| 404 | 40402 | 执行记录不存在、过期或不可访问 |`);
  lines.push(`| 409 | 40901 | 当前执行状态不允许恢复 |`);
  lines.push(`| 413 | 41301 | 文件、文件数量或请求总量超限 |`);
  lines.push(`| 415 | 41501 | 顶层 Content-Type 缺失或不支持 |`);
  lines.push(`| 415 | 41502 | metadata Part 未使用 application/json |`);
  lines.push(`| 500 | 50001 | 服务端内部错误；携带 requestId 联系管理员 |`);
  lines.push(`| 503 | 50301 | 文件存储暂时不可用，可稍后重试 |`);
  lines.push(``);
  lines.push(
    `Hoppscotch 等工具若显示自动生成的 \`multipart/form-data\` 请求头，请保持自动模式，不要覆盖该请求头；覆盖后通常会丢失 boundary。`,
  );

  return lines.join('\n');
});
/* eslint-enable unicorn/prefer-single-call */

async function submitPublishAction(row: any) {
  if (
    isAiResourceApprovalPending(row.displayPublishStatus, row.publishStatus)
  ) {
    ElMessage.warning($t('aiWorkflow.publishPendingHint'));
    return;
  }
  const confirmation = await confirmPublishSubmission({
    api,
    confirmMessage: isRepublishAction(row)
      ? $t('aiWorkflow.submitRepublishApprovalConfirm')
      : $t('aiWorkflow.submitPublishApprovalConfirm'),
    id: row.id,
    resourcePath: '/api/v1/workflow',
    title: $t('message.noticeTitle'),
  });
  if (!confirmation) {
    return;
  }
  const res = await api.post('/api/v1/workflow/submitPublishApproval', {
    applicationReason: confirmation.applicationReason,
    id: row.id,
  });
  if (res.errorCode === 0) {
    ElMessage.success(res.message || $t('message.saveOkMessage'));
    pageDataRef.value?.reload?.();
  }
}
async function submitOfflineAction(row: any) {
  if (
    isAiResourceApprovalPending(row.displayPublishStatus, row.publishStatus)
  ) {
    ElMessage.warning($t('aiWorkflow.publishPendingHint'));
    return;
  }
  const impactRes = await api.get<{
    data: OfflineImpactCheck;
    errorCode: number;
  }>('/api/v1/workflow/offlineImpactCheck', {
    params: { id: row.id },
  });
  if (impactRes.errorCode !== 0) {
    return;
  }
  try {
    const sections = [];
    if (impactRes.data?.hasAgentBindings) {
      sections.push(
        buildOfflineImpactMessage(
          $t('aiWorkflow.offlineImpactBoundAgentsIntro'),
          impactRes.data.agentBindings,
        ),
      );
    }
    if (impactRes.data?.hasSkillBindings) {
      sections.push(
        buildOfflineImpactMessage(
          $t('aiWorkflow.offlineImpactBoundSkillsIntro'),
          impactRes.data.skillBindings,
        ),
      );
    }
    if (impactRes.data?.hasPluginBindings) {
      sections.push(
        buildOfflineImpactMessage(
          $t('aiWorkflow.offlineImpactBoundPluginsIntro'),
          impactRes.data.pluginBindings,
        ),
      );
    }
    if (!impactRes.data?.canProceed) {
      const blockedMessage =
        sections.length > 0
          ? h('div', [
              ...sections,
              h(
                'p',
                { style: 'margin-top: 12px;' },
                $t('aiWorkflow.offlineImpactBlockedFooter'),
              ),
            ])
          : impactRes.data?.message ||
            $t('aiWorkflow.offlineImpactBlockedFooter');
      await ElMessageBox.alert(blockedMessage, $t('message.noticeTitle'), {
        confirmButtonText: $t('button.confirm'),
        type: 'warning',
      });
      return;
    }
    await ElMessageBox.confirm(
      $t('aiWorkflow.submitOfflineApprovalConfirm'),
      $t('message.noticeTitle'),
      {
        confirmButtonText: $t('button.confirm'),
        cancelButtonText: $t('button.cancel'),
        type: 'warning',
      },
    );
  } catch {
    return;
  }
  const res = await api.post('/api/v1/workflow/submitOfflineApproval', {
    id: row.id,
  });
  if (res.errorCode === 0) {
    ElMessage.success(res.message || $t('message.saveOkMessage'));
    pageDataRef.value?.reload?.();
  }
}
async function submitDeleteApproval(row: any) {
  if (
    isAiResourceApprovalPending(row.displayPublishStatus, row.publishStatus)
  ) {
    ElMessage.warning($t('aiWorkflow.deletePendingHint'));
    return;
  }
  try {
    await ElMessageBox.confirm(
      $t('aiWorkflow.submitDeleteApprovalConfirm'),
      $t('message.noticeTitle'),
      {
        confirmButtonText: $t('button.confirm'),
        cancelButtonText: $t('button.cancel'),
        type: 'warning',
      },
    );
  } catch {
    return;
  }
  const res = await api.post('/api/v1/workflow/submitDeleteApproval', {
    id: row.id,
  });
  if (res.errorCode === 0) {
    ElMessage.success(res.message || $t('message.saveOkMessage'));
    pageDataRef.value?.reload?.();
  }
}
function resolvePublishStatusMetaByInstance(
  displayPublishStatus?: string,
  publishStatus?: string,
) {
  switch (resolveAiResourceDisplayStatus(displayPublishStatus, publishStatus)) {
    case 'DELETE_PENDING': {
      return {
        label: $t('aiWorkflow.publishStatusDeletePending'),
        tone: 'danger',
      };
    }
    case 'OFFLINE': {
      return {
        label: $t('aiWorkflow.publishStatusOffline'),
        tone: 'draft',
      };
    }
    case 'OFFLINE_PENDING': {
      return {
        label: $t('aiWorkflow.publishStatusOfflinePending'),
        tone: 'pending',
      };
    }
    case 'PUBLISH_PENDING': {
      return {
        label: $t('aiWorkflow.publishStatusPublishPending'),
        tone: 'pending',
      };
    }
    case 'PUBLISHED': {
      return {
        label: $t('aiWorkflow.publishStatusPublished'),
        tone: 'published',
      };
    }
    default: {
      return {
        label: $t('aiWorkflow.publishStatusDraft'),
        tone: 'draft',
      };
    }
  }
}
function toDesignPage(row: any) {
  router.push({
    name: 'WorkflowDesign',
    query: {
      id: row.id,
      pageKey: '/ai/workflow',
      navTitle: resolveNavTitle(row),
      ...withListReturnTo(currentListFullPath()),
    },
  });
}
async function shareWorkflow(row: any) {
  if (!row?.id || sharingWorkflowId.value === row.id) {
    return;
  }
  sharingWorkflowId.value = row.id;
  try {
    const res = await api.post('/api/v1/workflowShare/url/create', {
      workflowId: row.id,
    });
    if (res.errorCode !== 0 || !res.data?.shareKey) {
      return;
    }
    const shareUrl = buildAbsoluteAppRouteUrl(
      router.resolve({
        name: 'WorkflowShare',
        query: {
          shareKey: res.data.shareKey,
        },
      }).href,
    );
    await copyTextWithFeedback(
      shareUrl,
      $t('message.copySuccess'),
      $t('message.copyFail'),
    );
  } finally {
    sharingWorkflowId.value = null;
  }
}
function exportJson(row: any) {
  api
    .get('/api/v1/workflow/exportWorkFlow', {
      params: {
        id: row.id,
      },
    })
    .then((res) => {
      const text = res.data;
      const element = document.createElement('a');
      element.setAttribute(
        'href',
        `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`,
      );
      element.setAttribute('download', `${row.title}.json`);
      element.style.display = 'none';
      document.body.append(element);
      element.click();
      element.remove();
      ElMessage.success($t('message.downloadSuccess'));
    });
}
const fieldDefinitions = ref<FieldDefinition[]>([
  {
    prop: 'categoryName',
    label: $t('aiWorkflowCategory.categoryName'),
    type: 'input',
    required: true,
    placeholder: $t('aiWorkflowCategory.categoryName'),
  },
  {
    prop: 'sortNo',
    label: $t('aiWorkflowCategory.sortNo'),
    type: 'number',
    required: false,
    placeholder: $t('aiWorkflowCategory.sortNo'),
  },
]);

const formData = ref<any>({});
const dialogVisible = ref(false);
const formRef = ref<FormInstance>();
const saveLoading = ref(false);
const sideList = ref<any[]>([]);
const controlBtns = [
  {
    icon: Edit,
    label: $t('button.edit'),
    onClick(row: any) {
      showControlDialog(row);
    },
  },
  {
    type: 'danger',
    icon: Delete,
    label: $t('button.delete'),
    onClick(row: any) {
      removeCategory(row);
    },
  },
];
const footerButton = {
  icon: Plus,
  label: $t('button.add'),
  onClick() {
    showControlDialog({});
  },
};

const formRules = computed(() => {
  const rules: Record<string, any[]> = {};
  fieldDefinitions.value.forEach((field) => {
    const fieldRules = [];
    if (field.required) {
      fieldRules.push({
        required: true,
        message: `${$t('message.required')}`,
        trigger: 'blur',
      });
    }
    if (fieldRules.length > 0) {
      rules[field.prop] = fieldRules;
    }
  });
  return rules;
});

function changeCategory(category: any) {
  const categoryId = category?.id ?? '';
  if (
    suppressInitialCategoryChange.value &&
    String(categoryId) === String(selectedCategoryId.value)
  ) {
    suppressInitialCategoryChange.value = false;
    selectedCategoryId.value = categoryId;
    return;
  }
  suppressInitialCategoryChange.value = false;
  selectedCategoryId.value = categoryId;
  applyFilters();
}
function showControlDialog(item: any) {
  formRef.value?.resetFields();
  formData.value = { ...item };
  dialogVisible.value = true;
}
function removeCategory(row: any) {
  ElMessageBox.confirm($t('message.deleteAlert'), $t('message.noticeTitle'), {
    confirmButtonText: $t('message.ok'),
    cancelButtonText: $t('message.cancel'),
    type: 'warning',
    beforeClose: (action, instance, done) => {
      if (action === 'confirm') {
        instance.confirmButtonLoading = true;
        api
          .post('/api/v1/workflowCategory/remove', { id: row.id })
          .then((res) => {
            instance.confirmButtonLoading = false;
            if (res.errorCode === 0) {
              ElMessage.success(res.message);
              done();
              getSideList();
            }
          })
          .catch(() => {
            instance.confirmButtonLoading = false;
          });
      } else {
        done();
      }
    },
  }).catch(() => {});
}
function handleSubmit() {
  formRef.value?.validate((valid) => {
    if (valid) {
      saveLoading.value = true;
      const url = formData.value.id
        ? '/api/v1/workflowCategory/update'
        : '/api/v1/workflowCategory/save';
      api.post(url, formData.value).then((res) => {
        saveLoading.value = false;
        if (res.errorCode === 0) {
          ElMessage.success(res.message);
          dialogVisible.value = false;
          getSideList();
        }
      });
    }
  });
}
const getSideList = async () => {
  const [, res] = await tryit(api.get)('/api/v1/workflowCategory/visibleList', {
    params: { sortKey: 'sortNo', sortType: 'asc' },
  });

  if (res && res.errorCode === 0) {
    sideList.value = [
      {
        id: '',
        categoryName: $t('common.allCategories'),
      },
      ...res.data,
    ];
    if (
      selectedCategoryId.value &&
      !res.data.some(
        (category: any) =>
          String(category.id) === String(selectedCategoryId.value),
      )
    ) {
      suppressInitialCategoryChange.value = false;
      selectedCategoryId.value = '';
      applyFilters();
    }
  }
};
function handleHeaderButtonClick(data: any) {
  if (data.data.action === 'import') {
    showDialog({}, true);
  } else {
    showDialog({});
  }
}
</script>

<template>
  <div v-loading="workflowModalLoading" class="flex h-full flex-col gap-6 p-6">
    <component
      :is="WorkflowModalComponent"
      v-if="WorkflowModalComponent"
      ref="saveDialog"
      @reload="reloadCurrentList"
    />
    <ElDialog
      v-model="apiInstructionVisible"
      width="min(960px, calc(100vw - 32px))"
      class="workflow-api-dialog"
      :footer="false"
    >
      <template #header>
        <div class="workflow-api-dialog__header">
          <h2 class="workflow-api-dialog__title">
            {{ $t('aiWorkflow.apiInstruction') }}
          </h2>
          <p
            v-if="apiInstructionRow?.title"
            class="workflow-api-dialog__context"
          >
            {{ apiInstructionRow.title }}
            <span v-if="apiInstructionRow.alias">
              · {{ apiInstructionRow.alias }}
            </span>
          </p>
        </div>
      </template>
      <div
        v-if="apiInstructionRow"
        class="workflow-api-markdown-wrap"
        @click="handleApiDocClick"
      >
        <Suspense>
          <ElXMarkdown
            :markdown="apiDocMarkdown"
            :allow-html="true"
            :sanitize="false"
          />
          <template #fallback>
            <ElSkeleton :rows="6" animated />
          </template>
        </Suspense>
      </div>
    </ElDialog>
    <HeaderSearch
      :buttons="headerButtons"
      :initial-value="searchKeyword"
      @search="handleSearch"
      @button-click="handleHeaderButtonClick"
    />
    <div class="flex max-h-[calc(100vh-191px)] flex-1 gap-6">
      <PageSide
        label-key="categoryName"
        value-key="id"
        :default-selected="selectedCategoryId"
        :menus="sideList"
        :control-btns="controlBtns"
        :footer-button="footerButton"
        @change="changeCategory"
      />
      <div class="h-full flex-1 overflow-auto">
        <PageData
          ref="pageDataRef"
          page-url="/api/v1/workflow/page"
          :page-sizes="[12, 18, 24]"
          :page-size="initialListState.pageSize"
          :initial-page-number="initialListState.pageNumber"
          :initial-query-params="initialQueryParams"
          @state-change="handlePageStateChange"
        >
          <template #default="{ pageList }">
            <CardList
              :default-icon="workflowIcon"
              :data="pageList"
              :primary-action="primaryAction"
              :actions="actions"
              density="compact"
              meta-layout="compact"
            >
              <template #details="{ item }">
                <AiResourceCornerMeta>
                  <template #publish>
                    <div
                      class="workflow-publish-chip"
                      :class="`workflow-publish-chip--${resolvePublishStatusMetaByInstance(item.displayPublishStatus, item.publishStatus).tone}`"
                    >
                      <span class="workflow-publish-chip__dot"></span>
                      <span>{{
                        resolvePublishStatusMetaByInstance(
                          item.displayPublishStatus,
                          item.publishStatus,
                        ).label
                      }}</span>
                    </div>
                  </template>
                  <template #scope>
                    <ElPopover
                      v-if="canManageWorkflow"
                      :ref="(el) => setVisibilityScopePopoverRef(item.id, el)"
                      trigger="click"
                      placement="bottom-end"
                      :width="208"
                      popper-class="workflow-visibility-popover"
                    >
                      <template #reference>
                        <button
                          type="button"
                          class="workflow-scope-chip"
                          :class="`workflow-scope-chip--${resolveVisibilityScopeMeta(item.visibilityScope).tone}`"
                          :disabled="updatingScopeId === item.id"
                          @click.stop
                        >
                          <ElIcon class="workflow-scope-chip__icon">
                            <component
                              :is="
                                resolveVisibilityScopeMeta(item.visibilityScope)
                                  .icon
                              "
                            />
                          </ElIcon>
                          <span class="workflow-scope-chip__label">
                            {{
                              resolveVisibilityScopeMeta(item.visibilityScope)
                                .label
                            }}
                          </span>
                        </button>
                      </template>
                      <div class="workflow-scope-panel" @click.stop>
                        <button
                          v-for="option in visibilityScopeOptions"
                          :key="option.value"
                          type="button"
                          class="workflow-scope-option"
                          :class="[
                            `workflow-scope-option--${option.tone}`,
                            {
                              'workflow-scope-option--active':
                                item.visibilityScope === option.value,
                            },
                          ]"
                          :disabled="updatingScopeId === item.id"
                          @click.stop="
                            updateVisibilityScope(item, option.value)
                          "
                        >
                          <span class="workflow-scope-option__leading">
                            <span class="workflow-scope-option__icon-wrap">
                              <ElIcon class="workflow-scope-option__icon">
                                <component :is="option.icon" />
                              </ElIcon>
                            </span>
                            <span class="workflow-scope-option__text">
                              <span class="workflow-scope-option__label">
                                {{ option.label }}
                              </span>
                              <span class="workflow-scope-option__desc">
                                {{ option.description }}
                              </span>
                            </span>
                          </span>
                          <ElIcon
                            v-if="item.visibilityScope === option.value"
                            class="workflow-scope-option__check"
                          >
                            <Check />
                          </ElIcon>
                        </button>
                      </div>
                    </ElPopover>
                    <div
                      v-else
                      class="workflow-scope-chip workflow-scope-chip--readonly"
                      :class="`workflow-scope-chip--${resolveVisibilityScopeMeta(item.visibilityScope).tone}`"
                    >
                      <ElIcon class="workflow-scope-chip__icon">
                        <component
                          :is="
                            resolveVisibilityScopeMeta(item.visibilityScope)
                              .icon
                          "
                        />
                      </ElIcon>
                      <span class="workflow-scope-chip__label">
                        {{
                          resolveVisibilityScopeMeta(item.visibilityScope).label
                        }}
                      </span>
                    </div>
                  </template>
                </AiResourceCornerMeta>
              </template>
            </CardList>
          </template>
        </PageData>
      </div>
    </div>

    <EasyFlowFormModal
      v-model:open="dialogVisible"
      :closable="!saveLoading"
      :title="formData.id ? `${$t('button.edit')}` : `${$t('button.add')}`"
      :confirm-loading="saveLoading"
      :confirm-text="$t('button.confirm')"
      :submitting="saveLoading"
      @confirm="handleSubmit"
    >
      <ElForm
        ref="formRef"
        :model="formData"
        :rules="formRules"
        label-position="top"
        class="easyflow-modal-form easyflow-modal-form--compact"
      >
        <!-- 动态生成表单项 -->
        <ElFormItem
          v-for="field in fieldDefinitions"
          :key="field.prop"
          :label="field.label"
          :prop="field.prop"
        >
          <ElInput
            v-if="!field.type || field.type === 'input'"
            v-model="formData[field.prop]"
            :placeholder="field.placeholder"
          />
          <ElInputNumber
            v-else-if="field.type === 'number'"
            v-model="formData[field.prop]"
            :placeholder="field.placeholder"
            style="width: 100%"
          />
        </ElFormItem>
      </ElForm>
    </EasyFlowFormModal>
  </div>
</template>

<style scoped>
.workflow-publish-chip {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  justify-content: center;
  min-height: 22px;
  padding: 0 8px;
  font-size: 11px;
  font-weight: 500;
  line-height: 1;
  border: 1px solid transparent;
  border-radius: 999px;
  box-shadow: inset 0 1px 0 hsl(var(--card) / 46%);
}

.workflow-publish-chip__dot {
  width: 6px;
  height: 6px;
  background: currentColor;
  border-radius: 999px;
  opacity: 0.88;
}

.workflow-publish-chip--draft {
  color: hsl(var(--muted-foreground));
  background: hsl(var(--muted) / 42%);
  border-color: hsl(var(--line-subtle));
}

.workflow-publish-chip--pending {
  color: hsl(var(--warning));
  background: hsl(var(--warning) / 12%);
  border-color: hsl(var(--warning) / 14%);
}

.workflow-publish-chip--published {
  color: hsl(var(--success));
  background: hsl(var(--success) / 12%);
  border-color: hsl(var(--success) / 14%);
}

.workflow-publish-chip--danger {
  color: hsl(var(--destructive));
  background: hsl(var(--destructive) / 10%);
  border-color: hsl(var(--destructive) / 14%);
}

.workflow-scope-chip {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  justify-content: center;
  min-height: 22px;
  padding: 0 8px;
  font-size: 11px;
  font-weight: 500;
  line-height: 1;
  color: hsl(var(--text-strong));
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  transition:
    border-color 0.18s ease,
    background-color 0.18s ease,
    color 0.18s ease,
    transform 0.18s ease,
    box-shadow 0.18s ease;
}

button.workflow-scope-chip {
  cursor: pointer;
}

button.workflow-scope-chip:hover {
  background: hsl(var(--card) / 76%);
  box-shadow: 0 8px 16px -16px hsl(var(--foreground) / 28%);
}

button.workflow-scope-chip:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 4px hsl(var(--primary) / 12%),
    0 10px 22px -18px hsl(var(--foreground) / 32%);
}

button.workflow-scope-chip:disabled {
  cursor: not-allowed;
  opacity: 0.72;
}

.workflow-scope-chip--readonly {
  cursor: default;
}

.workflow-scope-chip__icon {
  font-size: 12px;
}

.workflow-scope-chip__label {
  white-space: nowrap;
}

.workflow-scope-chip--private {
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 10%);
  border-color: hsl(var(--primary) / 14%);
}

.workflow-scope-chip--dept {
  color: hsl(var(--warning));
  background: hsl(var(--warning) / 12%);
  border-color: hsl(var(--warning) / 14%);
}

.workflow-scope-chip--public {
  color: hsl(var(--success));
  background: hsl(var(--success) / 12%);
  border-color: hsl(var(--success) / 14%);
}

.workflow-scope-panel {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.workflow-scope-option {
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 10px 8px;
  text-align: left;
  background: transparent;
  border: none;
  border-radius: 12px;
  transition:
    background-color 0.18s ease,
    transform 0.18s ease,
    color 0.18s ease;
}

.workflow-scope-option:hover {
  background: hsl(var(--foreground) / 4%);
}

.workflow-scope-option:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px hsl(var(--primary) / 12%);
}

.workflow-scope-option:disabled {
  cursor: not-allowed;
  opacity: 0.72;
}

.workflow-scope-option__leading {
  display: flex;
  gap: 8px;
  align-items: center;
  min-width: 0;
}

.workflow-scope-option__icon-wrap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  background: transparent;
  border-radius: 9px;
}

.workflow-scope-option__icon {
  font-size: 15px;
}

.workflow-scope-option__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.workflow-scope-option__label {
  font-size: 13px;
  font-weight: 600;
  color: hsl(var(--text-strong));
}

.workflow-scope-option__desc {
  font-size: 11px;
  line-height: 1.35;
  color: hsl(var(--text-muted));
}

.workflow-scope-option__check {
  flex-shrink: 0;
  font-size: 15px;
}

.workflow-scope-option--private .workflow-scope-option__icon-wrap {
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 10%);
}

.workflow-scope-option--dept .workflow-scope-option__icon-wrap {
  color: hsl(var(--warning));
  background: hsl(var(--warning) / 12%);
}

.workflow-scope-option--public .workflow-scope-option__icon-wrap {
  color: hsl(var(--success));
  background: hsl(var(--success) / 12%);
}

.workflow-scope-option--private.workflow-scope-option--active {
  background: hsl(var(--primary) / 8%);
}

.workflow-scope-option--dept.workflow-scope-option--active {
  background: hsl(var(--warning) / 8%);
}

.workflow-scope-option--public.workflow-scope-option--active {
  background: hsl(var(--success) / 8%);
}

.workflow-scope-option--private .workflow-scope-option__check {
  color: hsl(var(--primary));
}

.workflow-scope-option--dept .workflow-scope-option__check {
  color: hsl(var(--warning));
}

.workflow-scope-option--public .workflow-scope-option__check {
  color: hsl(var(--success));
}

:global(.workflow-visibility-popover.el-popover.el-popper) {
  padding: 8px;
  border-color: hsl(var(--line-subtle));
  border-radius: 16px;
  box-shadow: 0 18px 34px -28px hsl(var(--foreground) / 20%);
}

.workflow-api-dialog__header {
  min-width: 0;
  padding-right: 40px;
}

.workflow-api-dialog__title {
  margin: 0;
  font-size: 18px;
  font-weight: 650;
  line-height: 1.4;
  color: hsl(var(--text-strong));
}

.workflow-api-dialog__context {
  margin: var(--space-1) 0 0;
  overflow: hidden;
  font-size: 13px;
  line-height: 1.5;
  color: hsl(var(--text-muted));
  text-overflow: ellipsis;
  white-space: nowrap;
}

.workflow-api-markdown-wrap {
  max-height: min(76vh, 760px);
  padding: var(--space-5) 36px var(--space-8);
  overflow-y: auto;
  font-size: 14px;
  line-height: 1.75;
  color: hsl(var(--foreground));
  scrollbar-gutter: stable;
}

.workflow-api-markdown-wrap::-webkit-scrollbar {
  width: 6px;
}

.workflow-api-markdown-wrap::-webkit-scrollbar-thumb {
  background-color: hsl(var(--muted-foreground) / 20%);
  border-radius: 3px;
}

.workflow-api-markdown-wrap :deep(h2) {
  margin: 40px 0 14px;
  font-size: 20px;
  font-weight: 650;
  line-height: 1.4;
  color: hsl(var(--text-strong));
  letter-spacing: -0.01em;
}

.workflow-api-markdown-wrap :deep(h2:first-child) {
  margin-top: 0;
}

.workflow-api-markdown-wrap :deep(h3) {
  margin: 28px 0 10px;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.5;
  color: hsl(var(--text-strong));
}

.workflow-api-markdown-wrap :deep(h4) {
  margin: 22px 0 8px;
  font-size: 14px;
  font-weight: 600;
  color: hsl(var(--text-strong));
}

.workflow-api-markdown-wrap :deep(hr) {
  display: none;
}

.workflow-api-markdown-wrap :deep(p) {
  margin: 8px 0 12px;
  color: hsl(var(--foreground) / 84%);
}

.workflow-api-markdown-wrap :deep(ul),
.workflow-api-markdown-wrap :deep(ol) {
  padding-left: 22px;
  margin: 10px 0 16px;
}

.workflow-api-markdown-wrap :deep(code) {
  padding: 2px 5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.92em;
  color: hsl(var(--foreground));
  overflow-wrap: anywhere;
  background: hsl(var(--surface-contrast-soft));
  border-radius: var(--space-1);
}

.workflow-api-markdown-wrap :deep(pre) {
  max-height: 340px;
  padding: var(--space-4);
  margin: 12px 0 20px;
  overflow: auto;
  line-height: 1.65;
  background: hsl(var(--surface-subtle));
  border: 0;
  border-radius: var(--radius-toolbar);
}

.workflow-api-markdown-wrap :deep(pre code) {
  padding: 0 !important;
  font-size: 12.5px;
  color: hsl(var(--foreground));
  overflow-wrap: normal;
  background: transparent;
  border-radius: 0;
}

.workflow-api-markdown-wrap :deep(.pre-md) {
  background: transparent !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}

.workflow-api-markdown-wrap :deep(.markdown-elxLanguage-header-div) {
  padding: 0 0 var(--space-2) !important;
  background: transparent !important;
  border: 0 !important;
  box-shadow: none !important;
}

.workflow-api-markdown-wrap :deep(table) {
  width: 100%;
  margin: 12px 0 22px;
  border-collapse: collapse;
  border: 0 !important;
  border-top: 1px solid hsl(var(--table-row-border)) !important;
}

.workflow-api-markdown-wrap :deep(tr) {
  background: transparent !important;
}

.workflow-api-markdown-wrap :deep(th),
.workflow-api-markdown-wrap :deep(td) {
  padding: 10px 12px;
  font-size: 13px;
  line-height: 1.55;
  text-align: left;
  vertical-align: top;
  border: 0 !important;
  border-bottom: 1px solid hsl(var(--table-row-border)) !important;
}

.workflow-api-markdown-wrap :deep(th) {
  font-weight: 600;
  color: hsl(var(--text-strong));
  background: hsl(var(--table-header-bg));
}

.workflow-api-markdown-wrap :deep(td) {
  color: hsl(var(--foreground) / 82%);
  background: transparent !important;
}

.workflow-api-markdown-wrap :deep(th:nth-child(1)),
.workflow-api-markdown-wrap :deep(td:nth-child(1)) {
  width: 22%;
}

.workflow-api-markdown-wrap :deep(th:nth-child(2)),
.workflow-api-markdown-wrap :deep(td:nth-child(2)) {
  width: 20%;
  white-space: nowrap;
}

.workflow-api-markdown-wrap :deep(th:nth-child(3)),
.workflow-api-markdown-wrap :deep(td:nth-child(3)) {
  width: 10%;
  white-space: nowrap;
}

.workflow-api-markdown-wrap :deep(blockquote) {
  padding: 2px 0 2px var(--space-4);
  margin: 12px 0 20px;
  color: hsl(var(--text-muted));
  border-left: 2px solid hsl(var(--primary) / 55%);
}

.workflow-api-markdown-wrap :deep(blockquote p) {
  margin: 0;
  color: inherit;
}

.workflow-api-markdown-wrap :deep(strong) {
  font-weight: 600;
  color: hsl(var(--text-strong));
}

.workflow-api-markdown-wrap :deep(.api-url-copy-btn) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  margin-left: 6px;
  color: hsl(var(--muted-foreground));
  vertical-align: middle;
  cursor: pointer;
  background: transparent;
  border: 0;
  border-radius: var(--space-2);
  transition:
    color var(--motion-duration-fast) var(--motion-ease-standard),
    background-color var(--motion-duration-fast) var(--motion-ease-standard);
}

.workflow-api-markdown-wrap :deep(.api-url-copy-btn:hover) {
  color: hsl(var(--primary));
  background: hsl(var(--surface-contrast-soft));
}

.workflow-api-markdown-wrap :deep(.api-url-copy-btn:focus-visible) {
  color: hsl(var(--primary));
  outline: 2px solid hsl(var(--primary) / 45%);
  outline-offset: 2px;
}

:global(.workflow-api-dialog.el-dialog) {
  overflow: hidden;
  background: hsl(var(--modal-surface));
  border: 1px solid hsl(var(--modal-shell-border-soft));
  border-radius: var(--radius-modal);
  box-shadow: var(--modal-shadow);
}

:global(.workflow-api-dialog .el-dialog__header) {
  padding: var(--space-5) var(--space-6);
  margin-right: 0;
  border-bottom: 1px solid hsl(var(--modal-divider));
}

:global(.workflow-api-dialog .el-dialog__body) {
  max-height: none;
  padding: 0;
  overflow: hidden;
  background: hsl(var(--modal-content-surface));
}

@media (max-width: 767px) {
  :global(.workflow-api-dialog.el-dialog) {
    display: flex;
    flex-direction: column;
    width: calc(100vw - 16px) !important;
    max-height: calc(
      100vh - var(--easyflow-header-height, 0px) - var(--space-4)
    );
    margin: calc(var(--easyflow-header-height, 0px) + var(--space-2)) auto
      var(--space-2) !important;
  }

  :global(.workflow-api-dialog .el-dialog__header) {
    flex: 0 0 auto;
    padding: var(--space-4) 18px;
  }

  :global(.workflow-api-dialog .el-dialog__body) {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
  }

  .workflow-api-markdown-wrap {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
    padding: var(--space-4) 18px var(--space-6);
    scrollbar-gutter: auto;
  }

  .workflow-api-markdown-wrap :deep(h2) {
    margin-top: var(--space-8);
    font-size: 18px;
  }

  .workflow-api-markdown-wrap :deep(h3) {
    margin-top: var(--space-6);
  }

  .workflow-api-markdown-wrap :deep(pre) {
    max-height: 300px;
    padding: var(--space-3);
  }

  .workflow-api-markdown-wrap :deep(table) {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    white-space: nowrap;
  }
}
</style>
