<script lang="ts" setup>
import type { TraceabilityApi } from '#/api';

import { computed, onMounted, reactive, ref } from 'vue';

import { Plus } from '@vben/icons';

import {
  Button,
  Card,
  Col,
  DatePicker,
  Dropdown,
  Empty,
  Input,
  Menu,
  message,
  Modal,
  Row,
  Select,
  Space,
  Switch,
  Tabs,
  Tag,
} from 'ant-design-vue';

import {
  createTraceabilityNodeLibrary,
  createTraceabilityTemplate,
  deleteTraceabilityFileAsset,
  deleteTraceabilityNodeLibrary,
  deleteTraceabilityTemplate,
  getTraceabilityFileAssets,
  getTraceabilityNodeLibrary,
  getTraceabilityOverview,
  getTraceabilityTemplate,
  getTraceabilityTemplates,
  updateTraceabilityNodeLibrary,
  updateTraceabilityTemplate,
  uploadTraceabilityImage,
} from '#/api';

import CoordinateFieldEditor from './components/CoordinateFieldEditor.vue';
import {
  buildOssStoredValue,
  cloneTemplateForSave,
  createEmptyField,
  createEmptyNode,
  createLocalUniqueId,
  fieldTypeOptions,
  getFieldTypeLabel,
  getImagePreviewSrc,
  serializeImageValue,
  stripOssTempUrl,
} from './shared';

type EditableTemplateNode = TraceabilityApi.TemplateNode & {
  libraryId?: string;
  locked?: boolean;
  persisted?: boolean;
};

const activeTab = ref<'library' | 'stats' | 'templates'>('templates');
const libraryTab = ref<'business' | 'public'>('business');
const loading = ref(false);
const saving = ref(false);
const uploadingFieldKey = ref('');
const selectedTemplateId = ref('');
const selectedLibraryNodeId = ref('');
const selectedTemplateFieldKey = ref('');
const selectedLibraryFieldKey = ref('');
const draggingTemplateNodeId = ref('');
const draggingTemplateFieldKey = ref('');
const draggingLibraryFieldKey = ref('');
const overview = reactive<TraceabilityApi.Overview>({
  batchCount: 0,
  feedbackCount: 0,
  publishedCount: 0,
  templateCount: 0,
  totalScans: 0,
});
const templates = ref<TraceabilityApi.TemplateSummary[]>([]);
const nodeLibrary = ref<EditableTemplateNode[]>([]);
const editor = reactive<
  Partial<TraceabilityApi.TemplateDetail & { nodes: EditableTemplateNode[] }>
>({
  coverImage: '',
  description: '',
  industryName: '',
  name: '',
  nodes: [],
  productName: '',
  status: 'draft',
  themeColor: '#1f4fd6',
});
const nodeIndex = ref(0);
const templateSnapshot = ref('');
const leaveDialogVisible = ref(false);
const pendingTemplateSelectId = ref('');
const bypassTemplateLeaveGuard = ref(false);

const createTemplateVisible = ref(false);
const coverHistoryVisible = ref(false);
const coverHistoryLoading = ref(false);
const deletingCoverAssetId = ref('');
const coverHistoryItems = ref<TraceabilityApi.FileAssetItem[]>([]);
const createTemplateForm = reactive<{
  coverImage: any;
  coverImagePreviewUrl: string;
  description: string;
  industryName: string;
  name: string;
  productName: string;
  remark: string;
  themeColor: string;
}>({
  coverImage: '',
  coverImagePreviewUrl: '',
  description: '',
  remark: '',
  industryName: '',
  name: '新建溯源模板',
  productName: '',
  themeColor: '#1f4fd6',
});

const currentNode = computed(() => editor.nodes?.[nodeIndex.value] ?? null);
const currentTemplateField = computed(() =>
  currentNode.value?.fields.find((field) => field.key === selectedTemplateFieldKey.value) ??
  currentNode.value?.fields[0] ??
  null,
);
const publicNodes = computed(() =>
  (editor.nodes ?? []).filter((item) => item.category === 'public'),
);
const businessNodes = computed(() =>
  (editor.nodes ?? []).filter((item) => item.category !== 'public'),
);
const selectedTemplateSummary = computed(() =>
  templates.value.find((item) => item.id === selectedTemplateId.value),
);
const isTemplatePublished = computed(
  () => selectedTemplateSummary.value?.status === 'active',
);
const isTemplateDraft = computed(() => !isTemplatePublished.value);
const currentLibraryNode = computed(() =>
  nodeLibrary.value.find((item) => item.libraryId === selectedLibraryNodeId.value),
);
const currentLibraryField = computed(() =>
  currentLibraryNode.value?.fields.find((field) => field.key === selectedLibraryFieldKey.value) ??
  currentLibraryNode.value?.fields[0] ??
  null,
);
const businessLibraryNodes = computed(() =>
  nodeLibrary.value.filter((item) => item.category === 'business'),
);
const publicLibraryNodes = computed(() =>
  nodeLibrary.value.filter((item) => item.category === 'public'),
);
const currentNodeLocked = computed(() => !!currentNode.value?.locked);

function syncSelectedTemplateField() {
  selectedTemplateFieldKey.value = currentNode.value?.fields[0]?.key || '';
}

function syncSelectedLibraryField() {
  selectedLibraryFieldKey.value = currentLibraryNode.value?.fields[0]?.key || '';
}

function toEditableLibraryNode(item: TraceabilityApi.NodeLibraryItem): EditableTemplateNode {
  return {
    category: item.category,
    consumerVisible: item.consumerVisible,
    description: item.description,
    fields: item.fields.map((field, fieldIndex) => ({
      ...field,
      sort: field.sort ?? fieldIndex,
      defaultPreviewUrl: field.defaultPreviewUrl,
      options: [...(field.options ?? [])],
      fieldStyle: {
        bold: field.fieldStyle?.bold ?? false,
        color: field.fieldStyle?.color ?? '',
      },
    })),
    libraryId: item.id,
    locked: false,
    name: item.name,
    persisted: true,
  };
}

function buildLibraryPayload(node: EditableTemplateNode) {
  return {
    category: node.category,
    consumerVisible: node.consumerVisible,
    description: node.description,
    fields: (node.fields ?? []).map((field, fieldIndex) => ({
      sort: field.sort ?? fieldIndex,
      defaultValue:
        field.type === 'image'
          ? stripOssTempUrl(field.defaultValue ?? '')
          : field.defaultValue ?? '',
      fieldStyle: {
        bold: field.fieldStyle?.bold ?? false,
        color: field.fieldStyle?.color ?? '',
      },
      fixedPreset: field.fixedPreset ?? false,
      key: field.key,
      label: field.label,
      options: field.options ?? [],
      placeholder: field.placeholder ?? '',
      required: field.required ?? false,
      type: field.type ?? 'string',
      visible: field.visible ?? true,
    })),
    name: node.name,
  };
}

async function loadNodeLibrary() {
  const list = await getTraceabilityNodeLibrary();
  nodeLibrary.value = list.map(toEditableLibraryNode);
}

async function loadAll() {
  loading.value = true;
  try {
    await loadNodeLibrary();
    Object.assign(overview, await getTraceabilityOverview());
    templates.value = await getTraceabilityTemplates();
    if (!selectedTemplateId.value && templates.value[0]) {
      selectedTemplateId.value = templates.value[0].id;
    }
    if (!selectedLibraryNodeId.value && nodeLibrary.value[0]) {
      selectedLibraryNodeId.value = nodeLibrary.value[0].libraryId || '';
    }
    if (selectedTemplateId.value) {
      const detail = await getTraceabilityTemplate(selectedTemplateId.value);
      applyEditor(detail);
    }
  } finally {
    loading.value = false;
  }
}

function applyEditor(detail: TraceabilityApi.TemplateDetail) {
  Object.assign(editor, structuredClone(detail));
  editor.nodes = (editor.nodes ?? []).map((node) => ({
    ...node,
    id: node.id || createLocalUniqueId('node'),
    fields: (node.fields ?? []).map((field, fieldIndex) => ({
      ...field,
      sort: field.sort ?? fieldIndex,
    })),
    locked: !!node.locked,
  }));
  nodeIndex.value = 0;
  syncSelectedTemplateField();
  templateSnapshot.value = JSON.stringify(cloneTemplateForSave(editor));
}

const hasTemplateChanges = computed(() => {
  if (!selectedTemplateId.value) return false;
  return templateSnapshot.value !== JSON.stringify(cloneTemplateForSave(editor));
});

function openTemplateLeaveDialog(nextId: string) {
  pendingTemplateSelectId.value = nextId;
  leaveDialogVisible.value = true;
}

async function confirmTemplateLeave(action: 'cancel' | 'discard' | 'save') {
  leaveDialogVisible.value = false;
  const nextId = pendingTemplateSelectId.value;
  pendingTemplateSelectId.value = '';
  if (action === 'cancel' || !nextId) return;
  if (action === 'save') {
    await saveTemplate();
  }
  bypassTemplateLeaveGuard.value = true;
  try {
    await selectTemplate(nextId);
  } finally {
    bypassTemplateLeaveGuard.value = false;
  }
}

async function selectTemplate(id: string) {
  if (
    !bypassTemplateLeaveGuard.value &&
    selectedTemplateId.value &&
    id !== selectedTemplateId.value &&
    hasTemplateChanges.value
  ) {
    openTemplateLeaveDialog(id);
    return;
  }
  selectedTemplateId.value = id;
  const detail = await getTraceabilityTemplate(id);
  applyEditor(detail);
}

function openCreateTemplateModal() {
  createTemplateForm.coverImage = '';
  createTemplateForm.coverImagePreviewUrl = '';
  createTemplateForm.description = '';
  createTemplateForm.remark = '';
  createTemplateForm.industryName = '';
  createTemplateForm.name = '新建溯源模板';
  createTemplateForm.productName = '';
  createTemplateForm.themeColor = '#1f4fd6';
  createTemplateVisible.value = true;
}

async function loadCoverHistory() {
  coverHistoryLoading.value = true;
  try {
    coverHistoryItems.value = await getTraceabilityFileAssets('cover', 30);
  } finally {
    coverHistoryLoading.value = false;
  }
}

async function openCoverHistoryModal() {
  coverHistoryVisible.value = true;
  await loadCoverHistory();
}

function clearTemplateCoverImage() {
  createTemplateForm.coverImage = '';
  createTemplateForm.coverImagePreviewUrl = '';
}

function selectHistoryCover(item: TraceabilityApi.FileAssetItem) {
  createTemplateForm.coverImage = item.bucketName
    ? {
        bucketName: item.bucketName,
        objectName: item.objectName,
      }
    : item.objectName;
  createTemplateForm.coverImagePreviewUrl = item.previewUrl;
  coverHistoryVisible.value = false;
}

async function deleteHistoryCover(item: TraceabilityApi.FileAssetItem) {
  if (item.id.startsWith('legacy-')) {
    message.warning('历史回溯封面图暂不支持直接删除');
    return;
  }
  Modal.confirm({
    title: '删除历史封面图',
    content: `确认删除历史封面图“${item.fileName || item.objectName}”吗？`,
    async onOk() {
      deletingCoverAssetId.value = item.id;
      try {
        await deleteTraceabilityFileAsset(item.id);
        coverHistoryItems.value = coverHistoryItems.value.filter((asset) => asset.id !== item.id);
        message.success('历史封面图已删除');
      } catch (error: any) {
        message.error(error?.message || '删除失败');
      } finally {
        deletingCoverAssetId.value = '';
      }
    },
  });
}

async function handleTemplateCoverUpload(event: Event) {
  const files = (event.target as HTMLInputElement).files;
  const file = files?.[0];
  if (!file) return;

  uploadingFieldKey.value = 'template-cover';
  try {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('assetType', 'cover');
    formData.append('objectDir', 'traceability/covers/templates');
    const result = await uploadTraceabilityImage(formData);
    createTemplateForm.coverImage = buildOssStoredValue(result);
    createTemplateForm.coverImagePreviewUrl = result.tempUrl || '';
    message.success('封面图上传成功');
    await loadCoverHistory();
  } catch {
    message.error('封面图上传失败');
  } finally {
    uploadingFieldKey.value = '';
    (event.target as HTMLInputElement).value = '';
  }
}

function triggerTemplateCoverSelect() {
  const input = document.querySelector('#template-cover-upload');
  input?.click();
}

async function createTemplate() {
  saving.value = true;
  try {
    const created = await createTraceabilityTemplate({
      coverImage: serializeImageValue(createTemplateForm.coverImage),
      description: createTemplateForm.description,
      remark: createTemplateForm.remark,
      industryName: createTemplateForm.industryName,
      name: createTemplateForm.name.trim() || '新建溯源模板',
      nodes: [createEmptyNode('public'), createEmptyNode('business')],
      productName: createTemplateForm.productName,
      status: 'draft',
      themeColor: createTemplateForm.themeColor,
    });
    createTemplateVisible.value = false;
    await loadAll();
    if (created?.id) {
      await selectTemplate(created.id);
    }
    message.success('模板已创建');
  } finally {
    saving.value = false;
  }
}

async function copyTemplate(id: string) {
  saving.value = true;
  try {
    const source = await getTraceabilityTemplate(id);
    const created = await createTraceabilityTemplate({
      ...cloneTemplateForSave({
        ...source,
        name: `${source.name} 副本`,
        status: 'draft',
      }),
      status: 'draft',
    });
    await loadAll();
    if (created?.id) {
      await selectTemplate(created.id);
    }
    message.success('模板副本已创建');
  } finally {
    saving.value = false;
  }
}

async function createLibraryNode(category: 'business' | 'public') {
  const node: EditableTemplateNode = {
    ...createEmptyNode(category),
    libraryId: createLocalUniqueId('library-draft'),
    locked: false,
    persisted: false,
  };
  nodeLibrary.value.unshift(node);
  selectedLibraryNodeId.value = node.libraryId || '';
  libraryTab.value = category;
  activeTab.value = 'library';
}

function selectLibraryTab(category: 'business' | 'public') {
  libraryTab.value = category;
  const target =
    category === 'business' ? businessLibraryNodes.value[0] : publicLibraryNodes.value[0];
  selectedLibraryNodeId.value = target?.libraryId || '';
  syncSelectedLibraryField();
}

function selectLibraryNode(libraryId: string) {
  selectedLibraryNodeId.value = libraryId;
  syncSelectedLibraryField();
}

function addPlainNode(category: 'business' | 'public') {
  if (isTemplatePublished.value) return;
  editor.nodes ??= [];
  editor.nodes.push(createEmptyNode(category));
  nodeIndex.value = editor.nodes.length - 1;
  syncSelectedTemplateField();
}

function addNodeFromLibrary(category: 'business' | 'public', libraryId?: string) {
  if (isTemplatePublished.value) return;
  const source = nodeLibrary.value.find(
    (item) => item.libraryId === libraryId && item.category === category,
  );
  if (!source) {
    message.warning('未找到对应节点库配置');
    return;
  }
  editor.nodes ??= [];
  editor.nodes.push({
    id: createLocalUniqueId('node'),
    category: source.category,
    consumerVisible: source.consumerVisible,
    description: source.description,
    fields: source.fields.map((field, fieldIndex) => ({
      ...field,
      sort: field.sort ?? fieldIndex,
      options: [...(field.options ?? [])],
    })),
    libraryId: source.libraryId,
    locked: true,
    name: source.name,
  });
  nodeIndex.value = editor.nodes.length - 1;
  syncSelectedTemplateField();
}

function removeNode(index: number) {
  if (isTemplatePublished.value) return;
  editor.nodes?.splice(index, 1);
  nodeIndex.value = Math.max(0, nodeIndex.value - 1);
  syncSelectedTemplateField();
}

function confirmRemoveTemplateNode(index: number) {
  const target = editor.nodes?.[index];
  if (!target || isTemplatePublished.value) return;
  Modal.confirm({
    title: '删除节点',
    content: `确认删除节点“${target.name || '未命名节点'}”吗？`,
    async onOk() {
      removeNode(index);
    },
  });
}

function selectNode(index: number) {
  nodeIndex.value = index;
  syncSelectedTemplateField();
}

function moveTemplateNode(
  category: 'business' | 'public',
  draggedId: string,
  targetId: string,
) {
  const nodes = editor.nodes ?? [];
  if (!draggedId || !targetId || draggedId === targetId) return;

  const categoryNodes = nodes.filter((node) => node.category === category);
  const fromIndex = categoryNodes.findIndex((node) => node.id === draggedId);
  const toIndex = categoryNodes.findIndex((node) => node.id === targetId);
  if (fromIndex === -1 || toIndex === -1) return;

  const movedNodes = [...categoryNodes];
  const [movedNode] = movedNodes.splice(fromIndex, 1);
  movedNodes.splice(toIndex, 0, movedNode);

  const reordered = [...nodes];
  let cursor = 0;
  editor.nodes = reordered.map((node) =>
    node.category === category ? movedNodes[cursor++] : node,
  );

  const nextIndex = (editor.nodes ?? []).findIndex((node) => node.id === draggedId);
  if (nextIndex !== -1) {
    nodeIndex.value = nextIndex;
  }
}

function handleTemplateNodeDragStart(nodeId?: string) {
  if (isTemplatePublished.value) return;
  draggingTemplateNodeId.value = nodeId || '';
}

function handleTemplateNodeDrop(
  category: 'business' | 'public',
  targetNodeId?: string,
) {
  if (isTemplatePublished.value) return;
  moveTemplateNode(
    category,
    draggingTemplateNodeId.value,
    targetNodeId || '',
  );
  draggingTemplateNodeId.value = '';
}

function clearTemplateNodeDragState() {
  draggingTemplateNodeId.value = '';
}

function addField(target: EditableTemplateNode | null | undefined) {
  if (target?.locked) return;
  const field = createEmptyField();
  field.sort = target?.fields.length ?? 0;
  target?.fields.push(field);
  if (target === currentNode.value) {
    selectedTemplateFieldKey.value = field.key;
  }
  if (target === currentLibraryNode.value) {
    selectedLibraryFieldKey.value = field.key;
  }
}

function removeField(target: EditableTemplateNode | null | undefined, index: number) {
  if (target?.locked) return;
  const removed = target?.fields[index];
  target?.fields.splice(index, 1);
  target?.fields.forEach((field, fieldIndex) => {
    field.sort = fieldIndex;
  });
  if (target === currentNode.value && removed?.key === selectedTemplateFieldKey.value) {
    selectedTemplateFieldKey.value = target?.fields[0]?.key || '';
  }
  if (target === currentLibraryNode.value && removed?.key === selectedLibraryFieldKey.value) {
    selectedLibraryFieldKey.value = target?.fields[0]?.key || '';
  }
}

function confirmRemoveField(target: EditableTemplateNode | null | undefined, index: number) {
  const field = target?.fields[index];
  if (!target || target.locked || !field) return;
  Modal.confirm({
    title: '删除字段',
    content: `确认删除字段“${field.label || field.key || '未命名字段'}”吗？`,
    async onOk() {
      removeField(target, index);
    },
  });
}

function moveField(
  target: EditableTemplateNode | null | undefined,
  draggedKey: string,
  targetKey: string,
) {
  const fields = target?.fields ?? [];
  if (!draggedKey || !targetKey || draggedKey === targetKey) return;
  const fromIndex = fields.findIndex((field) => field.key === draggedKey);
  const toIndex = fields.findIndex((field) => field.key === targetKey);
  if (fromIndex === -1 || toIndex === -1) return;
  const movedFields = [...fields];
  const [movedField] = movedFields.splice(fromIndex, 1);
  if (!movedField) return;
  movedFields.splice(toIndex, 0, movedField);
  target!.fields = movedFields.map((field, fieldIndex) => ({
    ...field,
    sort: fieldIndex,
  }));
}

function handleTemplateFieldDragStart(fieldKey?: string) {
  if (isTemplatePublished.value || currentNodeLocked.value) return;
  draggingTemplateFieldKey.value = fieldKey || '';
}

function handleTemplateFieldDrop(targetFieldKey?: string) {
  if (isTemplatePublished.value || currentNodeLocked.value) return;
  moveField(currentNode.value, draggingTemplateFieldKey.value, targetFieldKey || '');
  draggingTemplateFieldKey.value = '';
}

function clearTemplateFieldDragState() {
  draggingTemplateFieldKey.value = '';
}

function handleLibraryFieldDragStart(fieldKey?: string) {
  draggingLibraryFieldKey.value = fieldKey || '';
}

function handleLibraryFieldDrop(targetFieldKey?: string) {
  moveField(currentLibraryNode.value, draggingLibraryFieldKey.value, targetFieldKey || '');
  draggingLibraryFieldKey.value = '';
}

function clearLibraryFieldDragState() {
  draggingLibraryFieldKey.value = '';
}

function getFieldUploadKey(scope: string, field: TraceabilityApi.FieldDefinition) {
  return `${scope}:${field.key}`;
}

function getFieldInputId(scope: string, field: TraceabilityApi.FieldDefinition) {
  return `traceability-admin-upload-${getFieldUploadKey(scope, field)}`;
}

function triggerFieldImageSelect(scope: string, field: TraceabilityApi.FieldDefinition) {
  const input = document.getElementById(getFieldInputId(scope, field));
  input?.click();
}

async function handleFieldImageUpload(
  scope: string,
  field: TraceabilityApi.FieldDefinition,
  event: Event,
) {
  const files = (event.target as HTMLInputElement).files;
  const file = files?.[0];
  if (!file) return;

  const uploadKey = getFieldUploadKey(scope, field);
  uploadingFieldKey.value = uploadKey;
  try {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('objectDir', `traceability/node-library/${scope}/${field.key}`);
    const result = await uploadTraceabilityImage(formData);
    field.defaultValue = buildOssStoredValue(result);
    field.defaultPreviewUrl = result.tempUrl || '';
    message.success('默认图片已上传');
  } catch {
    message.error('默认图片上传失败');
  } finally {
    uploadingFieldKey.value = '';
    (event.target as HTMLInputElement).value = '';
  }
}

function clearFieldImage(field: TraceabilityApi.FieldDefinition) {
  field.defaultValue = '';
  field.defaultPreviewUrl = '';
}

function getFieldStyleConfig(field: TraceabilityApi.FieldDefinition) {
  if (!field.fieldStyle) {
    field.fieldStyle = {
      bold: false,
      color: '',
    };
  }
  return field.fieldStyle;
}

function updateFieldStyleBold(field: TraceabilityApi.FieldDefinition, checked: boolean) {
  getFieldStyleConfig(field).bold = checked;
}

function updateFieldStyleColor(field: TraceabilityApi.FieldDefinition, color: string) {
  getFieldStyleConfig(field).color = color || '';
}

function updateSelectedFieldKey(
  scope: 'library' | 'template',
  field: TraceabilityApi.FieldDefinition,
  value: string,
) {
  const nextValue = value || field.key;
  field.key = nextValue;
  if (scope === 'template') {
    selectedTemplateFieldKey.value = nextValue;
  } else {
    selectedLibraryFieldKey.value = nextValue;
  }
}

function isFieldPresetLocked(field: TraceabilityApi.FieldDefinition) {
  return isTemplatePublished.value || !!field.fixedPreset;
}

function getPresetValue(field: TraceabilityApi.FieldDefinition) {
  if (field.type === 'multi_select') {
    return Array.isArray(field.defaultValue)
      ? field.defaultValue
      : (field.defaultValue
        ? String(field.defaultValue)
            .split(',')
            .map((item) => item.trim())
            .filter(Boolean)
        : []);
  }
  return field.defaultValue;
}

function updatePresetValue(field: TraceabilityApi.FieldDefinition, value: any) {
  if (field.type === 'integer') {
    if (value === null || value === undefined || value === '') {
      field.defaultValue = '';
      return;
    }
    const parsed = Number(value);
    field.defaultValue = Number.isNaN(parsed) ? '' : Math.trunc(parsed);
    return;
  }
  if (field.type === 'decimal') {
    if (value === null || value === undefined || value === '') {
      field.defaultValue = '';
      return;
    }
    const parsed = Number(value);
    field.defaultValue = Number.isNaN(parsed) ? '' : parsed;
    return;
  }
  if (field.type === 'multi_select') {
    field.defaultValue = Array.isArray(value) ? value : [];
    return;
  }
  field.defaultValue = value;
}

function sanitizeIntegerPreset(value: string) {
  const cleaned = value.replaceAll(/[^\d-]/g, '');
  const hasLeadingMinus = cleaned.startsWith('-');
  const unsigned = hasLeadingMinus ? cleaned.slice(1).replaceAll('-', '') : cleaned.replaceAll('-', '');
  return hasLeadingMinus ? `-${unsigned}` : unsigned;
}

function sanitizeDecimalPreset(value: string) {
  const cleaned = value.replaceAll(/[^\d.-]/g, '');
  const firstDot = cleaned.indexOf('.');
  const normalizedDot =
    firstDot === -1
      ? cleaned
      : `${cleaned.slice(0, firstDot + 1)}${cleaned.slice(firstDot + 1).replaceAll('.', '')}`;
  const hasLeadingMinus = normalizedDot.startsWith('-');
  const unsigned = hasLeadingMinus ? normalizedDot.slice(1).replaceAll('-', '') : normalizedDot;
  return hasLeadingMinus ? `-${unsigned}` : unsigned.replaceAll('-', '');
}

function sanitizeIntegerInput(value: string) {
  return value.replaceAll(/[^\d-]/g, '');
}

function sanitizeDecimalInput(value: string) {
  const cleaned = value.replaceAll(/[^\d.-]/g, '');
  const firstDot = cleaned.indexOf('.');
  const normalizedDot =
    firstDot === -1
      ? cleaned
      : `${cleaned.slice(0, firstDot + 1)}${cleaned
          .slice(firstDot + 1)
          .replaceAll('.', '')}`;
  const firstMinus = normalizedDot.indexOf('-');
  return firstMinus <= 0
    ? normalizedDot
    : `-${normalizedDot.replaceAll('-', '')}`;
}

async function removeLibraryNode() {
  const currentId = currentLibraryNode.value?.libraryId;
  if (!currentId) return;
  const currentName = currentLibraryNode.value?.name || '未命名节点';
  Modal.confirm({
    title: '删除节点库节点',
    content: `确认删除节点库节点“${currentName}”吗？`,
    async onOk() {
  if (currentLibraryNode.value?.persisted) {
    await deleteTraceabilityNodeLibrary(currentId);
    await loadNodeLibrary();
  } else {
    nodeLibrary.value = nodeLibrary.value.filter((item) => item.libraryId !== currentId);
  }
  selectedLibraryNodeId.value = nodeLibrary.value[0]?.libraryId || '';
  message.success('节点库节点已删除');
    },
  });
}

async function saveLibraryNode() {
  const currentId = currentLibraryNode.value?.libraryId;
  if (!currentId || !currentLibraryNode.value) {
    message.warning('请先选择节点库节点');
    return;
  }
  const payload = buildLibraryPayload(currentLibraryNode.value);
  if (currentLibraryNode.value.persisted === false) {
    const created = await createTraceabilityNodeLibrary(payload);
    await loadNodeLibrary();
    selectedLibraryNodeId.value = created.id;
    message.success('节点库节点已保存');
    return;
  }
  await updateTraceabilityNodeLibrary(currentId, payload);
  await loadNodeLibrary();
  selectedLibraryNodeId.value = currentId;
  message.success('节点库节点已保存');
}

async function persistTemplate(status?: 'active' | 'draft') {
  if (!editor.name?.trim()) {
    message.warning('请先填写模板名称');
    return;
  }
  if (!editor.nodes?.length) {
    message.warning('至少保留一个节点');
    return;
  }
  saving.value = true;
  try {
    const payload = {
      ...cloneTemplateForSave(editor),
      status: status ?? editor.status ?? 'draft',
    };
    if (selectedTemplateId.value) {
      await updateTraceabilityTemplate(selectedTemplateId.value, payload);
      message.success(status === 'active' ? '模板已发布' : '模板已更新');
    } else {
      await createTraceabilityTemplate(payload);
      message.success(status === 'active' ? '模板已创建并发布' : '模板已创建');
    }
    await loadAll();
  } finally {
    saving.value = false;
  }
}

async function saveTemplate() {
  if (isTemplatePublished.value) {
    message.warning('模板已发布，不可修改');
    return;
  }
  await persistTemplate('draft');
}

async function publishTemplate() {
  if (!selectedTemplateId.value) {
    message.warning('请先创建并选择模板');
    return;
  }
  await persistTemplate('active');
}

function removeTemplate(id: string) {
  const target = templates.value.find((item) => item.id === id);
  Modal.confirm({
    title: '删除模板',
    content: `确认删除模板“${target?.name || '未命名模板'}”吗？删除后关联批次也会一起删除。`,
    async onOk() {
      await deleteTraceabilityTemplate(id);
      message.success('模板已删除');
      if (selectedTemplateId.value === id) {
        selectedTemplateId.value = '';
        Object.assign(editor, {
          coverImage: '',
          description: '',
          industryName: '',
          name: '',
          nodes: [],
          productName: '',
          status: 'draft',
          themeColor: '#1f4fd6',
        });
      }
      await loadAll();
    },
  });
}

onMounted(async () => {
  await loadAll();
});
</script>

<template>
  <div class="trace-admin-page">
    <div class="trace-admin">
        <Tabs v-model:active-key="activeTab">
        <Tabs.TabPane key="templates" tab="模板中心">
          <Row :gutter="[16, 16]" align="stretch">
            <Col :lg="8" :xs="24">
              <Card :loading="loading" class="panel-card editor-panel" title="模板列表">
                <template #extra>
                  <Button type="primary" @click="openCreateTemplateModal">
                    <Plus class="size-5" />
                    新建模板
                  </Button>
                </template>
                <div class="template-list">
                  <button
                    v-for="item in templates"
                    :key="item.id"
                    class="template-card compact-template"
                    :class="{ active: item.id === selectedTemplateId }"
                    @click="selectTemplate(item.id)"
                  >
                    <div class="template-card__top">
                      <strong>{{ item.name }}</strong>
                      <Tag :color="item.status === 'active' ? 'blue' : 'default'">
                        {{ item.status === 'active' ? '已发布' : '草稿' }}
                      </Tag>
                    </div>
                    <div class="template-card__meta">
                      <span>{{ item.nodeCount }} 节点</span>
                      <span>{{ item.batchCount }} 批次</span>
                    </div>
                    <p class="template-card__remark">
                      {{ item.remark || '暂无备注' }}
                    </p>
                    <p>{{ item.description || '暂无模板说明' }}</p>
                    <div class="template-card__footer">
                      <span>{{ item.productName || '未设置产品名称' }}</span>
                      <div class="template-card__actions">
                        <Button size="small" @click.stop="copyTemplate(item.id)">复制模板</Button>
                        <Button danger size="small" @click.stop="removeTemplate(item.id)">删除模板</Button>
                      </div>
                    </div>
                  </button>
                </div>
              </Card>
            </Col>

            <Col :lg="16" :xs="24">
              <div class="template-main">
                <Card
                  v-if="selectedTemplateId"
                  class="panel-card template-summary"
                  :bordered="false"
                  title="基础信息"
                >
                  <template #extra>
                    <Space>
                      <Button
                        type="primary"
                        :disabled="!isTemplateDraft"
                        :loading="saving"
                        @click="saveTemplate"
                      >
                        保存草稿
                      </Button>
                      <Button
                        type="primary"
                        :disabled="!isTemplateDraft"
                        :loading="saving"
                        @click="publishTemplate"
                      >
                        发布模板
                      </Button>
                    </Space>
                  </template>
                  <Row :gutter="[12, 12]">
                    <Col :md="12" :xs="24">
                      <label class="field-label">模板名称</label>
                      <Input v-model:value="editor.name" :disabled="!isTemplateDraft" />
                    </Col>
                    <Col :md="12" :xs="24">
                      <label class="field-label">产品名称</label>
                      <Input v-model:value="editor.productName" :disabled="!isTemplateDraft" />
                    </Col>
                    <Col :md="12" :xs="24">
                      <label class="field-label">行业名称</label>
                      <Input v-model:value="editor.industryName" :disabled="!isTemplateDraft" />
                    </Col>
                    <Col :md="12" :xs="24">
                      <label class="field-label">模板备注</label>
                      <Input v-model:value="editor.remark" :disabled="!isTemplateDraft" placeholder="仅内部可见" />
                    </Col>
                    <Col :span="24">
                      <label class="field-label">模板说明</label>
                      <Input.TextArea
                        v-model:value="editor.description"
                        :disabled="!isTemplateDraft"
                        :auto-size="{ minRows: 2, maxRows: 4 }"
                      />
                    </Col>
                  </Row>
                </Card>
                <Empty v-else description="点击左侧模板或新建模板开始配置" />

                <Card class="panel-card editor-panel template-main__editor" title="节点编排">
                  <template #extra>
                    <div class="editor-toolbar">
                      <Space wrap class="editor-toolbar__left">
                        <Dropdown>
                          <Button :disabled="isTemplatePublished">新增业务节点</Button>
                          <template #overlay>
                            <Menu>
                              <Menu.Item key="business-temp" @click="addPlainNode('business')">
                                临时节点
                              </Menu.Item>
                              <Menu.SubMenu key="business-library">
                                <template #title>节点库</template>
                                <Menu.Item
                                  v-for="item in businessLibraryNodes"
                                  :key="`business-${item.libraryId}`"
                                  @click="addNodeFromLibrary('business', item.libraryId)"
                                >
                                  {{ item.name }}
                                </Menu.Item>
                                <Menu.Item v-if="businessLibraryNodes.length === 0" key="business-empty" disabled>
                                  暂无业务节点
                                </Menu.Item>
                              </Menu.SubMenu>
                            </Menu>
                          </template>
                        </Dropdown>

                        <Dropdown>
                          <Button :disabled="isTemplatePublished">新增公共资料节点</Button>
                          <template #overlay>
                            <Menu>
                              <Menu.Item key="public-temp" @click="addPlainNode('public')">
                                临时节点
                              </Menu.Item>
                              <Menu.SubMenu key="public-library">
                                <template #title>节点库</template>
                                <Menu.Item
                                  v-for="item in publicLibraryNodes"
                                  :key="`public-${item.libraryId}`"
                                  @click="addNodeFromLibrary('public', item.libraryId)"
                                >
                                  {{ item.name }}
                                </Menu.Item>
                                <Menu.Item v-if="publicLibraryNodes.length === 0" key="public-empty" disabled>
                                  暂无公共资料节点
                                </Menu.Item>
                              </Menu.SubMenu>
                            </Menu>
                          </template>
                        </Dropdown>
                      </Space>
</div>
                  </template>

                  <div class="node-lane">
                    <div class="node-lane__title">公共资料</div>
                    <div class="node-strip">
                      <button
                        v-for="item in publicNodes"
                        :key="item.id"
                        class="node-pill"
                        :class="{ active: currentNode?.id === item.id }"
                        :draggable="!isTemplatePublished"
                        type="button"
                        @dragstart="handleTemplateNodeDragStart(item.id)"
                        @dragover.prevent
                        @drop.prevent="handleTemplateNodeDrop('public', item.id)"
                        @dragend="clearTemplateNodeDragState"
                        @click="selectNode((editor.nodes ?? []).findIndex((node) => node.id === item.id))"
                      >
                        <span>公共资料</span>
                        <strong>{{ item.name || '未命名节点' }}</strong>
                        <small>
                          {{ item.fields.length }} 个字段
                          <template v-if="item.locked"> · 节点库</template>
                        </small>
                        <Button
                          v-if="!isTemplatePublished"
                          class="node-pill__remove"
                          danger
                          size="small"
                          type="text"
                          @click.stop="confirmRemoveTemplateNode((editor.nodes ?? []).findIndex((node) => node.id === item.id))"
                        >
                          删除节点
                        </Button>
                      </button>
                      <div v-if="publicNodes.length === 0" class="lane-empty">还没有公共资料节点</div>
                    </div>
                  </div>

                  <div class="node-lane">
                    <div class="node-lane__title">业务流程节点</div>
                    <div class="node-strip">
                      <button
                        v-for="item in businessNodes"
                        :key="item.id"
                        class="node-pill"
                        :class="{ active: currentNode?.id === item.id }"
                        :draggable="!isTemplatePublished"
                        type="button"
                        @dragstart="handleTemplateNodeDragStart(item.id)"
                        @dragover.prevent
                        @drop.prevent="handleTemplateNodeDrop('business', item.id)"
                        @dragend="clearTemplateNodeDragState"
                        @click="selectNode((editor.nodes ?? []).findIndex((node) => node.id === item.id))"
                      >
                        <span>业务流程</span>
                        <strong>{{ item.name || '未命名节点' }}</strong>
                        <small>
                          {{ item.fields.length }} 个字段
                          <template v-if="item.locked"> · 节点库</template>
                        </small>
                        <Button
                          v-if="!isTemplatePublished"
                          class="node-pill__remove"
                          danger
                          size="small"
                          type="text"
                          @click.stop="confirmRemoveTemplateNode((editor.nodes ?? []).findIndex((node) => node.id === item.id))"
                        >
                          删除节点
                        </Button>
                      </button>
                      <div v-if="businessNodes.length === 0" class="lane-empty">还没有业务流程节点</div>
                    </div>
                  </div>

                  <div v-if="currentNode" class="node-editor">
                    <div class="node-editor__header">
                      <div>
                        <h3>节点详情</h3>
                        <p v-if="currentNode.locked" class="locked-tip">
                          当前节点来自节点库，已复制到模板中并锁定；后续修改节点库不会影响当前模板和已创建批次。
                        </p>
                      </div>
                    </div>
                    <Row :gutter="[16, 16]">
                      <Col :md="12" :xs="24">
                        <label class="field-label">节点名称</label>
                        <Input v-model:value="currentNode.name" :disabled="currentNodeLocked || isTemplatePublished" />
                      </Col>
                      <Col :md="12" :xs="24">
                        <label class="field-label">节点类型</label>
                        <div class="readonly-box">
                          {{ currentNode.category === 'public' ? '公共资料节点' : '业务节点' }}
                        </div>
                      </Col>
                      <Col :md="18" :xs="24">
                        <label class="field-label">节点说明</label>
                        <Input
                          v-model:value="currentNode.description"
                          :disabled="currentNodeLocked || isTemplatePublished"
                        />
                      </Col>
                      <Col :md="6" :xs="24">
                        <label class="field-label">消费者可见</label>
                        <div class="switch-line">
                          <Switch
                            v-model:checked="currentNode.consumerVisible"
                            :disabled="currentNodeLocked || isTemplatePublished"
                          />
                        </div>
                      </Col>
                    </Row>

                    <div class="field-editor">
                      <div class="field-editor__header">
                        <h4>字段设计</h4>
                        <Button
                          type="dashed"
                          :disabled="currentNodeLocked || isTemplatePublished"
                          @click="addField(currentNode)"
                        >
                          新增字段
                        </Button>
                      </div>
                      <div class="field-strip">
                        <button
                          v-for="(field, fieldIndex) in currentNode.fields"
                          :key="`${field.key}-${fieldIndex}`"
                          class="field-pill"
                          :class="{ active: field.key === currentTemplateField?.key }"
                          :draggable="!isTemplatePublished && !currentNodeLocked"
                          type="button"
                          @dragstart="handleTemplateFieldDragStart(field.key)"
                          @dragover.prevent
                          @drop.prevent="handleTemplateFieldDrop(field.key)"
                          @dragend="clearTemplateFieldDragState"
                          @click="selectedTemplateFieldKey = field.key"
                        >
                          <span>{{ getFieldTypeLabel(field.type) }}</span>
                          <strong>{{ field.label || '新字段' }}</strong>
                          <small>{{ field.key }}</small>
                          <Button
                            class="field-pill__remove"
                            danger
                            size="small"
                            type="text"
                            :disabled="currentNodeLocked || isTemplatePublished"
                            @click.stop="confirmRemoveField(currentNode, fieldIndex)"
                          >
                            删除字段
                          </Button>
                        </button>
                        <div v-if="currentNode.fields.length === 0" class="lane-empty">还没有字段，请先新增字段</div>
                      </div>
                      <div v-if="currentTemplateField" class="field-card">
                        <div class="field-card__header">
                          <div class="field-card__title">
                            <strong>{{ currentTemplateField.label || '新字段' }}</strong>
                            <span>{{ getFieldTypeLabel(currentTemplateField.type) }}</span>
                          </div>
                          <Button
                            class="field-delete-button"
                            danger
                            :disabled="currentNodeLocked || isTemplatePublished"
                            @click="confirmRemoveField(currentNode, currentNode.fields.findIndex((item) => item.key === currentTemplateField.key))"
                          >
                            删除字段
                          </Button>
                        </div>
                        <Row :gutter="[12, 12]" class="field-row field-row--base">
                          <Col :md="6" :xs="24">
                            <label class="field-label">字段 Key</label>
                            <Input :value="currentTemplateField.key" :disabled="currentNodeLocked || isTemplatePublished" @update:value="(value) => updateSelectedFieldKey('template', currentTemplateField, String(value ?? ''))" />
                          </Col>
                          <Col :md="6" :xs="24">
                            <label class="field-label">字段名称</label>
                            <Input v-model:value="currentTemplateField.label" :disabled="currentNodeLocked || isTemplatePublished" />
                          </Col>
                          <Col :md="6" :xs="24">
                            <label class="field-label">类型</label>
                            <Select
                              v-model:value="currentTemplateField.type"
                              :disabled="currentNodeLocked || isTemplatePublished"
                              :options="fieldTypeOptions"
                              style="width: 100%"
                            />
                          </Col>
                          <Col :md="6" :xs="24">
                            <label class="field-label">占位提示</label>
                            <Input
                              v-model:value="currentTemplateField.placeholder"
                              :disabled="currentNodeLocked || isTemplatePublished"
                            />
                          </Col>
                        </Row>
                        <div class="field-toggle-row">
                          <div class="field-toggle-chip">
                            <span>必填</span>
                            <Switch
                              v-model:checked="currentTemplateField.required"
                              :disabled="currentNodeLocked || isTemplatePublished"
                            />
                          </div>
                          <div class="field-toggle-chip">
                            <span>消费者可见</span>
                            <Switch
                              v-model:checked="currentTemplateField.visible"
                              :disabled="currentNodeLocked || isTemplatePublished"
                            />
                          </div>
                          <div class="field-toggle-chip">
                            <span>固定预设值</span>
                            <Switch
                              v-model:checked="currentTemplateField.fixedPreset"
                              :disabled="currentNodeLocked || isTemplatePublished"
                            />
                          </div>
                          <div class="field-toggle-chip">
                            <span>文字加粗</span>
                            <Switch
                              :checked="getFieldStyleConfig(currentTemplateField).bold"
                              :disabled="currentNodeLocked || isTemplatePublished"
                              @update:checked="(checked) => updateFieldStyleBold(currentTemplateField, checked)"
                            />
                          </div>
                        </div>
                        <Row :gutter="[12, 12]" class="field-row field-row--content">
                          <Col :md="14" :xs="24">
                            <label class="field-label">预设值</label>
                            <template v-if="currentTemplateField.type === 'image'">
                              <div class="default-image-editor">
                                <img
                                  v-if="currentTemplateField.defaultValue"
                                  :src="getImagePreviewSrc(currentTemplateField.defaultValue, currentTemplateField.defaultPreviewUrl)"
                                  alt="默认图片"
                                  class="default-image-preview"
                                />
                                <div class="default-image-actions">
                                  <input
                                    :id="getFieldInputId(`template-${nodeIndex}-${currentTemplateField.key}`, currentTemplateField)"
                                    accept="image/*"
                                    class="upload-input"
                                    type="file"
                                    :disabled="isFieldPresetLocked(currentTemplateField)"
                                    @change="
                                      (event) =>
                                        handleFieldImageUpload(
                                          `template-${nodeIndex}-${currentTemplateField.key}`,
                                          currentTemplateField,
                                          event,
                                        )
                                    "
                                  />
                                  <Button
                                    :disabled="isFieldPresetLocked(currentTemplateField)"
                                    :loading="
                                      uploadingFieldKey ===
                                        getFieldUploadKey(`template-${nodeIndex}-${currentTemplateField.key}`, currentTemplateField)
                                    "
                                    size="small"
                                    type="primary"
                                    @click="triggerFieldImageSelect(`template-${nodeIndex}-${currentTemplateField.key}`, currentTemplateField)"
                                  >
                                    {{ currentTemplateField.defaultValue ? '重新上传' : '上传默认图' }}
                                  </Button>
                                  <Button
                                    v-if="currentTemplateField.defaultValue"
                                    :disabled="isFieldPresetLocked(currentTemplateField)"
                                    size="small"
                                    @click="clearFieldImage(currentTemplateField)"
                                  >
                                    移除
                                  </Button>
                                </div>
                              </div>
                            </template>
                            <Select
                              v-else-if="currentTemplateField.type === 'select'"
                              :disabled="isFieldPresetLocked(currentTemplateField)"
                              :options="(currentTemplateField.options || []).map((item) => ({ label: item, value: item }))"
                              :value="getPresetValue(currentTemplateField)"
                              style="width: 100%"
                              @update:value="(value) => updatePresetValue(currentTemplateField, value)"
                            />
                            <Select
                              v-else-if="currentTemplateField.type === 'multi_select'"
                              :disabled="isFieldPresetLocked(currentTemplateField)"
                              :options="(currentTemplateField.options || []).map((item) => ({ label: item, value: item }))"
                              :value="getPresetValue(currentTemplateField)"
                              mode="multiple"
                              style="width: 100%"
                              @update:value="(value) => updatePresetValue(currentTemplateField, value)"
                            />
                            <Input
                              v-else-if="currentTemplateField.type === 'integer'"
                              :disabled="isFieldPresetLocked(currentTemplateField)"
                              :placeholder="currentTemplateField.placeholder || '请输入整数'"
                              :value="String(getPresetValue(currentTemplateField) ?? '')"
                              style="width: 100%"
                              @update:value="
                                (value) =>
                                  updatePresetValue(currentTemplateField, sanitizeIntegerPreset(String(value ?? '')))
                              "
                            />
                            <Input
                              v-else-if="currentTemplateField.type === 'decimal'"
                              :disabled="isFieldPresetLocked(currentTemplateField)"
                              :placeholder="currentTemplateField.placeholder || '请输入小数'"
                              :value="String(getPresetValue(currentTemplateField) ?? '')"
                              style="width: 100%"
                              @update:value="
                                (value) =>
                                  updatePresetValue(currentTemplateField, sanitizeDecimalPreset(String(value ?? '')))
                              "
                            />
                            <DatePicker
                              v-else-if="currentTemplateField.type === 'date'"
                              :disabled="isFieldPresetLocked(currentTemplateField)"
                              :value="getPresetValue(currentTemplateField)"
                              style="width: 100%"
                              value-format="YYYY-MM-DD"
                              @update:value="(value) => updatePresetValue(currentTemplateField, value)"
                            />
                            <DatePicker
                              v-else-if="currentTemplateField.type === 'datetime'"
                              :disabled="isFieldPresetLocked(currentTemplateField)"
                              :value="getPresetValue(currentTemplateField)"
                              format="YYYY-MM-DD HH:mm:ss"
                              show-time
                              style="width: 100%"
                              value-format="YYYY-MM-DD HH:mm:ss"
                              @update:value="(value) => updatePresetValue(currentTemplateField, value)"
                            />
                            <CoordinateFieldEditor
                              v-else-if="currentTemplateField.type === 'coordinate'"
                              :disabled="isFieldPresetLocked(currentTemplateField)"
                              :model-value="getPresetValue(currentTemplateField)"
                              @update:model-value="(value) => updatePresetValue(currentTemplateField, value)"
                            />
                            <Input.TextArea
                              v-else-if="currentTemplateField.type === 'json'"
                              :auto-size="{ minRows: 3, maxRows: 5 }"
                              :disabled="isFieldPresetLocked(currentTemplateField)"
                              :value="String(getPresetValue(currentTemplateField) || '')"
                              @update:value="(value) => updatePresetValue(currentTemplateField, value)"
                            />
                            <Input
                              v-else
                              :disabled="isFieldPresetLocked(currentTemplateField)"
                              :value="getPresetValue(currentTemplateField)"
                              @update:value="(value) => updatePresetValue(currentTemplateField, value)"
                            />
                          </Col>
                          <Col :md="10" :xs="24">
                            <label class="field-label">文字颜色</label>
                            <div class="color-picker-line compact-color-line field-color-inline">
                              <input
                                :disabled="currentNodeLocked || isTemplatePublished"
                                :value="getFieldStyleConfig(currentTemplateField).color || '#1f2937'"
                                class="color-input"
                                type="color"
                                @input="
                                  (event) =>
                                    updateFieldStyleColor(
                                      currentTemplateField,
                                      (event.target as HTMLInputElement).value,
                                    )
                                "
                              />
                              <span>{{ getFieldStyleConfig(currentTemplateField).color || '#1f2937' }}</span>
                            </div>
                          </Col>
                          <Col v-if="['select', 'multi_select'].includes(currentTemplateField.type)" :span="24">
                            <label class="field-label">选项</label>
                            <Select
                              v-model:value="currentTemplateField.options"
                              :disabled="currentNodeLocked || isTemplatePublished"
                              mode="tags"
                              placeholder="输入后回车"
                            />
                          </Col>
                          <Col :span="24">
                            <div class="field-type-hint">当前字段类型：{{ getFieldTypeLabel(currentTemplateField.type) }}</div>
                          </Col>
                        </Row>
                      </div>
                    </div>
                  </div>

                  <Empty v-else description="请选择或新增节点" />
                </Card>
              </div>
            </Col>
          </Row>
        </Tabs.TabPane>

        <Tabs.TabPane key="library" tab="节点库">
          <Row :gutter="[16, 16]">
            <Col :lg="8" :xs="24">
              <Card class="panel-card editor-panel" title="节点列表">
                <template #extra>
                  <Space>
                    <Button @click="createLibraryNode('business')">新增业务节点</Button>
                    <Button @click="createLibraryNode('public')">新增公共资料节点</Button>
                  </Space>
                </template>
                <Tabs :active-key="libraryTab" @change="(key) => selectLibraryTab(key as 'business' | 'public')">
                  <Tabs.TabPane key="business" tab="业务节点">
                    <div class="template-list">
                      <button
                        v-for="item in businessLibraryNodes"
                        :key="item.libraryId"
                        class="template-card compact-template"
                        :class="{ active: item.libraryId === selectedLibraryNodeId }"
                        @click="selectLibraryNode(item.libraryId || '')"
                      >
                        <div class="template-card__top">
                          <strong>{{ item.name }}</strong>
                          <Tag color="blue">业务</Tag>
                        </div>
                        <p>{{ item.description || '暂无节点说明' }}</p>
                        <span>{{ item.fields.length }} 个字段</span>
                      </button>
                    </div>
                  </Tabs.TabPane>
                  <Tabs.TabPane key="public" tab="公共资料">
                    <div class="template-list">
                      <button
                        v-for="item in publicLibraryNodes"
                        :key="item.libraryId"
                        class="template-card compact-template"
                        :class="{ active: item.libraryId === selectedLibraryNodeId }"
                        @click="selectLibraryNode(item.libraryId || '')"
                      >
                        <div class="template-card__top">
                          <strong>{{ item.name }}</strong>
                          <Tag color="green">公共资料</Tag>
                        </div>
                        <p>{{ item.description || '暂无节点说明' }}</p>
                        <span>{{ item.fields.length }} 个字段</span>
                      </button>
                    </div>
                  </Tabs.TabPane>
                </Tabs>
              </Card>
            </Col>

            <Col :lg="16" :xs="24">
              <Card class="panel-card editor-panel" title="节点配置项">
                <template #extra>
                  <Space>
                    <Button type="primary" @click="saveLibraryNode">保存节点</Button>
                    <Button danger @click="removeLibraryNode">删除节点</Button>
                  </Space>
                </template>
                <div v-if="currentLibraryNode" class="node-editor library-editor">
                  <div class="library-tip">
                    节点库是模板素材库。这里的修改只会影响后续新加入模板的节点，以及基于新模板创建的批次；不会追溯修改已有模板和历史批次。
                  </div>
                  <Row :gutter="[16, 16]">
                    <Col :md="12" :xs="24">
                      <label class="field-label">节点名称</label>
                      <Input v-model:value="currentLibraryNode.name" />
                    </Col>
                    <Col :md="12" :xs="24">
                      <label class="field-label">节点类型</label>
                      <div class="readonly-box">
                        {{ currentLibraryNode.category === 'public' ? '公共资料节点' : '业务节点' }}
                      </div>
                    </Col>
                    <Col :md="18" :xs="24">
                      <label class="field-label">节点说明</label>
                      <Input v-model:value="currentLibraryNode.description" />
                    </Col>
                    <Col :md="6" :xs="24">
                      <label class="field-label">消费者可见</label>
                      <div class="switch-line">
                        <Switch v-model:checked="currentLibraryNode.consumerVisible" />
                      </div>
                    </Col>
                  </Row>

                  <div class="field-editor">
                    <div class="field-editor__header">
                      <h4>字段设计</h4>
                      <Button type="dashed" @click="addField(currentLibraryNode)">新增字段</Button>
                    </div>
                    <div class="field-strip">
                      <button
                        v-for="(field, fieldIndex) in currentLibraryNode.fields"
                        :key="`${field.key}-${fieldIndex}`"
                        class="field-pill"
                        :class="{ active: field.key === currentLibraryField?.key }"
                        draggable="true"
                        type="button"
                        @dragstart="handleLibraryFieldDragStart(field.key)"
                        @dragover.prevent
                        @drop.prevent="handleLibraryFieldDrop(field.key)"
                        @dragend="clearLibraryFieldDragState"
                        @click="selectedLibraryFieldKey = field.key"
                      >
                        <span>{{ getFieldTypeLabel(field.type) }}</span>
                        <strong>{{ field.label || '新字段' }}</strong>
                        <small>{{ field.key }}</small>
                        <Button class="field-pill__remove" danger size="small" type="text" @click.stop="confirmRemoveField(currentLibraryNode, fieldIndex)">
                          删除字段
                        </Button>
                      </button>
                      <div v-if="currentLibraryNode.fields.length === 0" class="lane-empty">还没有字段，请先新增字段</div>
                    </div>
                    <div v-if="currentLibraryField" class="field-card">
                      <div class="field-card__header">
                        <div class="field-card__title">
                          <strong>{{ currentLibraryField.label || '新字段' }}</strong>
                          <span>{{ getFieldTypeLabel(currentLibraryField.type) }}</span>
                        </div>
                        <Button class="field-delete-button" danger @click="confirmRemoveField(currentLibraryNode, currentLibraryNode.fields.findIndex((item) => item.key === currentLibraryField.key))">
                          删除字段
                        </Button>
                      </div>
                      <Row :gutter="[12, 12]" class="field-row field-row--base">
                        <Col :md="6" :xs="24">
                          <label class="field-label">字段 Key</label>
                          <Input :value="currentLibraryField.key" @update:value="(value) => updateSelectedFieldKey('library', currentLibraryField, String(value ?? ''))" />
                        </Col>
                        <Col :md="6" :xs="24">
                          <label class="field-label">字段名称</label>
                          <Input v-model:value="currentLibraryField.label" />
                        </Col>
                        <Col :md="6" :xs="24">
                          <label class="field-label">类型</label>
                          <Select v-model:value="currentLibraryField.type" :options="fieldTypeOptions" style="width: 100%" />
                        </Col>
                        <Col :md="6" :xs="24">
                          <label class="field-label">占位提示</label>
                          <Input v-model:value="currentLibraryField.placeholder" />
                        </Col>
                      </Row>
                      <div class="field-toggle-row">
                        <div class="field-toggle-chip">
                          <span>必填</span>
                          <Switch v-model:checked="currentLibraryField.required" />
                        </div>
                        <div class="field-toggle-chip">
                          <span>消费者可见</span>
                          <Switch v-model:checked="currentLibraryField.visible" />
                        </div>
                        <div class="field-toggle-chip">
                          <span>固定预设值</span>
                          <Switch v-model:checked="currentLibraryField.fixedPreset" />
                        </div>
                        <div class="field-toggle-chip">
                          <span>文字加粗</span>
                          <Switch
                            :checked="getFieldStyleConfig(currentLibraryField).bold"
                            @update:checked="(checked) => updateFieldStyleBold(currentLibraryField, checked)"
                          />
                        </div>
                      </div>
                      <Row :gutter="[12, 12]" class="field-row field-row--content">
                        <Col :md="14" :xs="24">
                          <label class="field-label">预设值</label>
                          <template v-if="currentLibraryField.type === 'image'">
                            <div class="default-image-editor">
                              <img
                                v-if="currentLibraryField.defaultValue"
                                :src="getImagePreviewSrc(currentLibraryField.defaultValue, currentLibraryField.defaultPreviewUrl)"
                                alt="默认图片"
                                class="default-image-preview"
                              />
                              <div class="default-image-actions">
                                <input
                                  :id="getFieldInputId(`library-${currentLibraryField.key}`, currentLibraryField)"
                                  accept="image/*"
                                  class="upload-input"
                                  type="file"
                                  @change="
                                    (event) =>
                                      handleFieldImageUpload(`library-${currentLibraryField.key}`, currentLibraryField, event)
                                  "
                                />
                                <Button
                                  :loading="
                                    uploadingFieldKey ===
                                      getFieldUploadKey(`library-${currentLibraryField.key}`, currentLibraryField)
                                  "
                                  size="small"
                                  type="primary"
                                  @click="triggerFieldImageSelect(`library-${currentLibraryField.key}`, currentLibraryField)"
                                >
                                  {{ currentLibraryField.defaultValue ? '重新上传' : '上传默认图' }}
                                </Button>
                                <Button
                                  v-if="currentLibraryField.defaultValue"
                                  size="small"
                                  @click="clearFieldImage(currentLibraryField)"
                                >
                                  移除
                                </Button>
                              </div>
                            </div>
                          </template>
                          <Select
                            v-else-if="currentLibraryField.type === 'select'"
                            :options="(currentLibraryField.options || []).map((item) => ({ label: item, value: item }))"
                            :value="getPresetValue(currentLibraryField)"
                            style="width: 100%"
                            @update:value="(value) => updatePresetValue(currentLibraryField, value)"
                          />
                          <Select
                            v-else-if="currentLibraryField.type === 'multi_select'"
                            :options="(currentLibraryField.options || []).map((item) => ({ label: item, value: item }))"
                            :value="getPresetValue(currentLibraryField)"
                            mode="multiple"
                            style="width: 100%"
                            @update:value="(value) => updatePresetValue(currentLibraryField, value)"
                          />
                          <Input
                            v-else-if="currentLibraryField.type === 'integer'"
                            :placeholder="currentLibraryField.placeholder || '请输入整数'"
                            :value="String(getPresetValue(currentLibraryField) ?? '')"
                            style="width: 100%"
                            @update:value="
                              (value) =>
                                updatePresetValue(currentLibraryField, sanitizeIntegerPreset(String(value ?? '')))
                            "
                          />
                          <Input
                            v-else-if="currentLibraryField.type === 'decimal'"
                            :placeholder="currentLibraryField.placeholder || '请输入小数'"
                            :value="String(getPresetValue(currentLibraryField) ?? '')"
                            style="width: 100%"
                            @update:value="
                              (value) =>
                                updatePresetValue(currentLibraryField, sanitizeDecimalPreset(String(value ?? '')))
                            "
                          />
                          <DatePicker
                            v-else-if="currentLibraryField.type === 'date'"
                            :value="getPresetValue(currentLibraryField)"
                            style="width: 100%"
                            value-format="YYYY-MM-DD"
                            @update:value="(value) => updatePresetValue(currentLibraryField, value)"
                          />
                          <DatePicker
                            v-else-if="currentLibraryField.type === 'datetime'"
                            :value="getPresetValue(currentLibraryField)"
                            format="YYYY-MM-DD HH:mm:ss"
                            show-time
                            style="width: 100%"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            @update:value="(value) => updatePresetValue(currentLibraryField, value)"
                          />
                          <CoordinateFieldEditor
                            v-else-if="currentLibraryField.type === 'coordinate'"
                            :model-value="getPresetValue(currentLibraryField)"
                            @update:model-value="(value) => updatePresetValue(currentLibraryField, value)"
                          />
                          <Input.TextArea
                            v-else-if="currentLibraryField.type === 'json'"
                            :auto-size="{ minRows: 3, maxRows: 5 }"
                            :value="String(getPresetValue(currentLibraryField) || '')"
                            @update:value="(value) => updatePresetValue(currentLibraryField, value)"
                          />
                          <Input
                            v-else
                            :value="getPresetValue(currentLibraryField)"
                            @update:value="(value) => updatePresetValue(currentLibraryField, value)"
                          />
                        </Col>
                        <Col :md="10" :xs="24">
                          <label class="field-label">文字颜色</label>
                          <div class="color-picker-line compact-color-line field-color-inline">
                            <input
                              :value="getFieldStyleConfig(currentLibraryField).color || '#1f2937'"
                              class="color-input"
                              type="color"
                              @input="
                                (event) =>
                                  updateFieldStyleColor(
                                    currentLibraryField,
                                    (event.target as HTMLInputElement).value,
                                  )
                              "
                            />
                            <span>{{ getFieldStyleConfig(currentLibraryField).color || '#1f2937' }}</span>
                          </div>
                        </Col>
                        <Col v-if="['select', 'multi_select'].includes(currentLibraryField.type)" :span="24">
                          <label class="field-label">选项</label>
                          <Select v-model:value="currentLibraryField.options" mode="tags" placeholder="输入后回车" />
                        </Col>
                      </Row>
                    </div>
                  </div>
                </div>
                <Empty v-else description="请选择左侧节点进行配置" />
              </Card>
            </Col>
          </Row>
        </Tabs.TabPane>

        <Tabs.TabPane key="stats" tab="统计信息">
          <Card class="panel-card compact-panel" title="统计信息总览">
            <div class="stat-grid">
              <div class="stat-item">
                <span>模板</span>
                <strong>{{ overview.templateCount }}</strong>
              </div>
              <div class="stat-item">
                <span>批次</span>
                <strong>{{ overview.batchCount }}</strong>
              </div>
              <div class="stat-item">
                <span>已发布</span>
                <strong>{{ overview.publishedCount }}</strong>
              </div>
              <div class="stat-item">
                <span>扫码量</span>
                <strong>{{ overview.totalScans }}</strong>
              </div>
            </div>
          </Card>
        </Tabs.TabPane>
      </Tabs>
    </div>

    <Modal
      v-model:open="createTemplateVisible"
      title="新建模板"
      ok-text="开始编排"
      cancel-text="取消"
      @ok="createTemplate"
    >
      <Row :gutter="[16, 16]">
        <Col :span="24">
          <label class="field-label">模板名称</label>
          <Input v-model:value="createTemplateForm.name" placeholder="如：茶叶溯源模板" />
        </Col>
        <Col :md="12" :xs="24">
          <label class="field-label">产品名称</label>
          <Input v-model:value="createTemplateForm.productName" placeholder="如：高山有机绿茶" />
        </Col>
        <Col :md="12" :xs="24">
          <label class="field-label">行业名称</label>
          <Input v-model:value="createTemplateForm.industryName" placeholder="如：农产品 / 食品加工" />
        </Col>
        <Col :md="12" :xs="24">
          <label class="field-label">主题色</label>
          <div class="color-picker-line">
            <input
              v-model="createTemplateForm.themeColor"
              class="color-input"
              type="color"
            />
            <span>{{ createTemplateForm.themeColor }}</span>
          </div>
        </Col>
        <Col :md="12" :xs="24">
          <label class="field-label">封面图</label>
          <div class="cover-selector">
            <div
              v-if="getImagePreviewSrc(createTemplateForm.coverImage, createTemplateForm.coverImagePreviewUrl)"
              class="cover-selector__preview"
            >
              <img
                :src="getImagePreviewSrc(createTemplateForm.coverImage, createTemplateForm.coverImagePreviewUrl)"
                alt="模板封面图"
              />
            </div>
            <div class="cover-selector__actions">
              <input
                id="template-cover-upload"
                hidden
                type="file"
                accept="image/*"
                @change="handleTemplateCoverUpload"
              />
              <Button
                :loading="uploadingFieldKey === 'template-cover'"
                @click="triggerTemplateCoverSelect"
              >
                选择本地文件
              </Button>
              <Button @click="openCoverHistoryModal">
                选择历史文件
              </Button>
              <Button
                v-if="getImagePreviewSrc(createTemplateForm.coverImage, createTemplateForm.coverImagePreviewUrl)"
                danger
                @click="clearTemplateCoverImage"
              >
                清空
              </Button>
            </div>
          </div>
        </Col>
        <Col :span="24">
          <label class="field-label">模板说明</label>
          <Input.TextArea
            v-model:value="createTemplateForm.description"
            :auto-size="{ minRows: 3, maxRows: 5 }"
            placeholder="说明此模板适用对象、展示重点和字段规范"
          />
        </Col>
        <Col :span="24">
          <label class="field-label">备注</label>
          <Input.TextArea
            v-model:value="createTemplateForm.remark"
            :auto-size="{ minRows: 2, maxRows: 4 }"
            placeholder="仅内部可见，用于记录模板背景、使用限制或补充说明"
          />
        </Col>
      </Row>
    </Modal>

    <Modal
      v-model:open="coverHistoryVisible"
      title="选择历史封面图"
      :footer="null"
    >
      <div v-if="coverHistoryItems.length > 0" class="cover-history-grid">
        <div
          v-for="item in coverHistoryItems"
          :key="item.id"
          class="cover-history-card"
        >
          <img :src="item.previewUrl" :alt="item.fileName" />
          <div class="cover-history-card__meta">
            <strong>{{ item.fileName || item.objectName }}</strong>
            <span>{{ item.createdAt }}</span>
          </div>
          <div class="cover-history-card__actions">
            <Button size="small" type="primary" @click="selectHistoryCover(item)">
              选择
            </Button>
            <Button
              size="small"
              danger
              :disabled="item.id.startsWith('legacy-')"
              :loading="deletingCoverAssetId === item.id"
              @click="deleteHistoryCover(item)"
            >
              删除
            </Button>
          </div>
        </div>
      </div>
      <Empty v-else :description="coverHistoryLoading ? '正在加载历史封面图…' : '暂无历史封面图'" />
    </Modal>
    <Modal
      v-model:open="leaveDialogVisible"
      title="有未保存的改动"
      :mask-closable="false"
      :closable="true"
      :footer="null"
      @cancel="confirmTemplateLeave('cancel')"
    >
      <p>当前模板有未保存的修改，切换前要怎么处理？</p>
      <div class="leave-dialog__actions">
        <Button block @click="confirmTemplateLeave('save')">保存</Button>
        <Button block @click="confirmTemplateLeave('discard')">不保存</Button>
        <Button block @click="confirmTemplateLeave('cancel')">取消</Button>
      </div>
    </Modal>
</div>

</template>

<style scoped>
.trace-admin {
  padding: 4px;
}

.panel-card {
  border-radius: 18px;
}

.editor-panel {
  min-height: 620px;
}

.template-main {
  display: grid;
  gap: 16px;
  height: 100%;
}

.template-main__editor {
  min-height: 540px;
}

.editor-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  width: 100%;
  padding-bottom: 4px;
}

.editor-toolbar__left,
.editor-toolbar__right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.editor-toolbar__left {
  margin-right: auto;
}

.editor-toolbar__right {
  margin-left: 24px;
}

.template-summary {
  border: 1px solid #edf1f7;
  border-radius: 16px;
  background: #fff;
  padding: 14px 16px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}

.summary-inline {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
}

.summary-inline__item {
  min-width: 0;
}

.cover-selector {
  display: grid;
  gap: 12px;
}

.cover-selector__preview {
  width: 100%;
  max-width: 320px;
  overflow: hidden;
  border: 1px solid #e7edf7;
  border-radius: 16px;
  background: #fff;
}

.cover-selector__preview img {
  display: block;
  width: 100%;
  height: auto;
}

.cover-selector__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.cover-history-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.cover-history-card {
  display: grid;
  gap: 10px;
  padding: 10px;
  border: 1px solid #e7edf7;
  border-radius: 16px;
  background: #fff;
  text-align: left;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.cover-history-card:hover {
  border-color: #1f4fd6;
  box-shadow: 0 10px 24px rgba(31, 79, 214, 0.12);
}

.cover-history-card img {
  display: block;
  width: 100%;
  height: 140px;
  object-fit: cover;
  border-radius: 12px;
  background: #f7faff;
}

.cover-history-card__meta {
  display: grid;
  gap: 4px;
}

.cover-history-card__actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

.cover-history-card__meta strong,
.cover-history-card__meta span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cover-history-card__meta span {
  color: #7d8899;
  font-size: 12px;
}

.summary-inline__item--wide {
  grid-column: 1 / -1;
}

.summary-inline__item span,
.field-label {
  color: #7d8899;
  font-size: 12px;
}

.summary-inline__item strong {
  display: block;
  margin-top: 6px;
  color: #1f2937;
  font-size: 14px;
  line-height: 1.5;
  word-break: break-word;
}

.field-label {
  display: block;
  margin-bottom: 8px;
  font-weight: 600;
}

.compact-panel :deep(.ant-card-body) {
  padding: 16px;
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.stat-item {
  border: 1px solid #edf1f7;
  border-radius: 14px;
  padding: 12px;
  background: #fafcff;
}

.stat-item span {
  color: #7d8899;
  font-size: 12px;
}

.stat-item strong {
  display: block;
  margin-top: 8px;
  font-size: 24px;
}

.template-list {
  display: grid;
  gap: 10px;
}

.template-card {
  width: 100%;
  text-align: left;
  border: 1px solid #edf1f7;
  background: #fff;
  border-radius: 14px;
  padding: 14px;
}

.template-card.active {
  border-color: #adc4ff;
  background: #f6f9ff;
}

.template-card__top,
.template-card__meta,
.node-editor__header,
.field-editor__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
}

.template-card__meta {
  justify-content: flex-start;
  flex-wrap: wrap;
  margin: 8px 0 6px;
}

.template-card__remark {
  margin: 6px 0 0;
  color: #556070;
  font-size: 12px;
  line-height: 1.5;
}

.template-card__footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
}

.template-card__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.template-card p {
  margin: 0;
  color: #5f6b7c;
}

.template-card span {
  color: #8b96a8;
  font-size: 12px;
}

.node-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 10px;
}

.node-pill {
  cursor: grab;
  min-width: 180px;
  border: 1px solid #edf1f7;
  background: #fff;
  border-radius: 16px;
  padding: 14px;
  text-align: left;
}

.node-pill.active {
  border-color: #adc4ff;
  background: #f5f8ff;
}

.node-pill span {
  color: #1d4ed8;
  font-size: 12px;
}

.node-pill strong,
.node-pill small {
  display: block;
}

.node-pill__remove {
  margin-top: 10px;
  padding-left: 0;
}

.node-pill small {
  margin-top: 6px;
  color: #8b96a8;
}

.node-lane + .node-lane {
  margin-top: 18px;
}

.node-lane__title {
  color: #1f2937;
  font-size: 14px;
  font-weight: 700;
}

.lane-empty {
  min-width: 200px;
  border: 1px dashed #dbe3f0;
  border-radius: 16px;
  padding: 18px;
  color: #8b96a8;
  background: #fbfcff;
}

.node-editor {
  border-top: 1px solid #f0f2f5;
  padding-top: 20px;
  margin-top: 20px;
}

.node-editor__header h3,
.field-editor__header h4 {
  margin: 0;
}

.node-editor__header,
.field-editor__header {
  margin-bottom: 16px;
}

.locked-tip {
  margin: 6px 0 0;
  color: #d97706;
  font-size: 13px;
  line-height: 1.6;
}

.library-tip {
  margin-bottom: 16px;
  padding: 12px 14px;
  border-radius: 12px;
  background: #fff8eb;
  color: #b45309;
  font-size: 13px;
  line-height: 1.7;
}

.field-editor {
  margin-top: 20px;
}

.field-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding-bottom: 6px;
  margin-bottom: 14px;
}

.field-pill {
  cursor: grab;
  min-width: 200px;
  max-width: 220px;
  border: 1px solid #e5ebf5;
  border-radius: 16px;
  padding: 12px 14px;
  background: #fff;
  text-align: left;
  transition: all 0.2s ease;
}

.field-pill.active {
  border-color: #adc4ff;
  background: #f5f8ff;
}

.field-pill:hover {
  border-color: #cfdaf0;
  transform: translateY(-1px);
}

.field-pill span {
  color: #1d4ed8;
  font-size: 12px;
}

.field-pill strong,
.field-pill small {
  display: block;
}

.field-pill small {
  margin-top: 6px;
  color: #8b96a8;
}

.field-pill__remove {
  margin-top: 10px;
  padding-left: 0;
  font-size: 12px;
}

.field-card {
  border: 1px solid #edf1f7;
  border-radius: 16px;
  padding: 16px;
  background: linear-gradient(180deg, #fbfcff, #f7faff);
}

.field-card + .field-card {
  margin-top: 12px;
}

.field-card__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}

.field-card__title {
  display: grid;
  gap: 4px;
}

.field-card__title strong {
  color: #1f2937;
  font-size: 15px;
  line-height: 1.4;
}

.field-card__title span {
  color: #7d8899;
  font-size: 12px;
}

.field-delete-button {
  border-radius: 10px;
}

.field-row + .field-row {
  margin-top: 12px;
}

.field-toggle-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  margin: 14px 0;
}

.field-toggle-chip {
  min-height: 44px;
  border: 1px solid #e6edf8;
  border-radius: 12px;
  background: #fff;
  padding: 0 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.field-toggle-chip span {
  color: #556070;
  font-size: 13px;
}

.switch-line {
  min-height: 32px;
  display: flex;
  align-items: center;
}

.library-editor {
  border-top: none;
  padding-top: 0;
}

.readonly-box,
.field-type-hint {
  min-height: 32px;
  border: 1px solid #edf1f7;
  border-radius: 12px;
  background: #fafcff;
  padding: 8px 12px;
  color: #5f6b7c;
  display: flex;
  align-items: center;
}

.theme-dot {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.08);
}

.color-picker-line {
  display: flex;
  align-items: center;
  gap: 12px;
}

.field-style-editor {
  display: grid;
  gap: 8px;
}

.compact-switch-line {
  justify-content: space-between;
}

.compact-color-line span {
  color: #5f6b7c;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.field-color-inline {
  min-height: 54px;
  border: 1px solid #edf1f7;
  border-radius: 12px;
  background: #fff;
  padding: 9px 12px;
}

.color-input {
  width: 56px;
  height: 36px;
  border: none;
  padding: 0;
  background: transparent;
  cursor: pointer;
}

.default-image-editor {
  display: grid;
  gap: 10px;
}

.default-image-preview {
  display: block;
  max-width: 100%;
  max-height: 180px;
  border: 1px solid #dbe3f0;
  border-radius: 14px;
  object-fit: cover;
  background: #fff;
}

.default-image-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.leave-dialog__actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.leave-dialog__actions :deep(.ant-btn) {
  width: 100%;
}

@media (max-width: 960px) {
  .field-toggle-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .field-card__header {
    flex-direction: column;
    align-items: stretch;
  }

  .field-pill {
    min-width: 100%;
    max-width: none;
  }

  .field-toggle-row {
    grid-template-columns: minmax(0, 1fr);
  }
}
</style>
