<template>
  <section class="page diagram-center-page">
    <div class="panel">
      <div class="panel-header">
        <div>
          <h1 class="panel-title">图表中心</h1>
          <p class="muted">{{ project.projectName || '项目' }} 的 ER 图、功能模块图、架构图和 AI 图表。</p>
        </div>
        <div class="toolbar">
          <el-button @click="router.push('/projects')">返回项目</el-button>
          <el-button type="primary" @click="router.push({ path: '/generate', query: { projectId, mode: 'expert' } })">编辑项目</el-button>
        </div>
      </div>

      <div class="diagram-layout" v-loading="loading">
        <aside class="diagram-sidebar">
          <el-radio-group v-model="activeDiagram" class="diagram-kind-list">
            <el-radio-button label="er">ER 图</el-radio-button>
            <el-radio-button label="module">功能模块图</el-radio-button>
            <el-radio-button label="architecture">系统架构图</el-radio-button>
            <el-radio-button label="flowchart">流程图</el-radio-button>
            <el-radio-button label="three_line_table">三线表</el-radio-button>
            <el-radio-button label="overview">项目总览图</el-radio-button>
            <el-radio-button label="ai">AI 生成图</el-radio-button>
          </el-radio-group>

          <div class="sidebar-actions">
            <el-button type="primary" :loading="savingDiagram" @click="saveCurrentDiagram">保存当前图表</el-button>
          </div>

          <div v-if="savedDiagrams.length" class="saved-list">
            <strong>已保存图表</strong>
            <button
              v-for="diagram in savedDiagrams"
              :key="diagram.diagramId"
              type="button"
              class="saved-item"
              @click="loadSavedDiagram(diagram)"
            >
              <span>{{ diagram.title || diagram.diagramType }}</span>
              <small>{{ diagram.diagramType }} · v{{ diagram.version || 1 }}</small>
            </button>
          </div>

          <div v-if="activeDiagram === 'ai'" class="ai-form">
            <el-form :model="aiForm" label-position="top">
              <el-form-item label="图表类型">
                <el-select v-model="aiForm.diagramType">
                  <el-option label="业务流程图" value="flowchart" />
                  <el-option label="用例图" value="use_case" />
                  <el-option label="数据流图" value="data_flow" />
                  <el-option label="活动图" value="activity" />
                  <el-option label="状态图" value="state" />
                  <el-option label="架构图" value="architecture" />
                </el-select>
              </el-form-item>
              <el-form-item label="标题">
                <el-input v-model="aiForm.title" placeholder="例如：用户借阅业务流程图" />
              </el-form-item>
              <el-form-item label="补充要求">
                <el-input
                  v-model="aiForm.prompt"
                  type="textarea"
                  :rows="5"
                  placeholder="描述你想画的流程、角色、状态或模块"
                />
              </el-form-item>
              <el-button type="primary" :loading="generatingAiDiagram" @click="handleGenerateAiDiagram">生成图表</el-button>
            </el-form>
          </div>
        </aside>

        <main class="diagram-main">
          <ErDiagramView
            v-if="activeDiagram === 'er'"
            v-model:draft="erDraft"
            :tables="database.tables"
            :project-id="projectId"
          />

          <AppModuleDiagramView
            v-else-if="activeDiagram === 'module'"
            v-model:source-mode="moduleDiagramSourceMode"
            v-model:outline-text="moduleDiagramOutlineText"
            v-model:layout-options="moduleDiagramLayoutOptions"
            :blueprint="appBlueprint"
            :project-name="project.projectName"
            :editable="true"
          />

          <section v-else-if="activeDiagram === 'architecture'" class="architecture-editor">
            <div class="architecture-toolbar">
              <div>
                <strong>{{ architectureDiagram.title }}</strong>
                <span>{{ architectureStatsText }}</span>
              </div>
              <div class="toolbar">
                <el-checkbox v-model="architectureBlackWhite">黑白模式</el-checkbox>
                <el-button :icon="Download" @click="downloadArchitectureSvg">SVG</el-button>
                <el-button :icon="Picture" @click="downloadArchitecturePng">PNG</el-button>
              </div>
            </div>

            <div class="architecture-workbench">
              <aside class="architecture-dsl-panel">
                <div class="architecture-dsl-header">
                  <div>
                    <strong>架构图 DSL</strong>
                    <span>顶格为层名，缩进 2 空格为组件，缩进 4 空格为说明。</span>
                  </div>
                  <div class="architecture-dsl-actions">
                    <el-button :icon="Delete" text @click="clearArchitectureDsl">清空</el-button>
                    <el-button :icon="DocumentCopy" text @click="loadArchitectureExample">加载示例</el-button>
                    <el-button :icon="Refresh" text @click="regenerateArchitectureDsl">换一换</el-button>
                  </div>
                </div>

                <div class="architecture-grammar">
                  层名可写成“表现层 - Vue 3 前端”；名为“横切关注点”的层会自动渲染到右侧侧栏。
                </div>

                <el-input
                  v-model="architectureDsl"
                  class="architecture-dsl-input"
                  type="textarea"
                  resize="none"
                  :autosize="{ minRows: 26, maxRows: 38 }"
                  spellcheck="false"
                />

                <div class="architecture-dsl-footer">
                  <span v-if="parsedArchitecture.errors.length">{{ parsedArchitecture.errors.length }} 条格式提示</span>
                  <span v-else>DSL 已就绪</span>
                  <el-button type="primary" :icon="MagicStick" @click="refreshArchitecturePreview">生成预览</el-button>
                </div>
              </aside>

              <section class="architecture-preview-panel">
                <div v-if="parsedArchitecture.errors.length" class="architecture-errors">
                  <span v-for="error in parsedArchitecture.errors.slice(0, 2)" :key="error">{{ error }}</span>
                </div>

                <div class="architecture-preview-scroll">
                  <svg
                    ref="architectureSvgRef"
                    class="architecture-svg"
                    :viewBox="`0 0 ${architectureDiagram.canvas.width} ${architectureDiagram.canvas.height}`"
                    :style="{ width: `${architectureDiagram.canvas.width}px`, height: `${architectureDiagram.canvas.height}px` }"
                  >
                    <defs>
                      <marker id="architecture-arrow-preview" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto">
                        <path d="M0,0 L0,6 L8,3 z" fill="#475569" />
                      </marker>
                    </defs>
                    <rect width="100%" height="100%" fill="#ffffff" />
                    <text class="architecture-svg-title" :x="architectureDiagram.canvas.width / 2" y="24">
                      {{ architectureDiagram.title }}
                    </text>

                    <path
                      v-for="arrow in architectureDiagram.arrows"
                      :key="arrow.id"
                      class="architecture-arrow"
                      :d="architectureArrowPath(arrow)"
                      marker-end="url(#architecture-arrow-preview)"
                    />

                    <g v-for="layer in architectureDiagram.layers" :key="layer.id" class="architecture-layer">
                      <rect
                        :x="layer.x"
                        :y="layer.y"
                        :width="layer.width"
                        :height="layer.height"
                        rx="8"
                        :fill="layer.palette.fill"
                        :stroke="layer.palette.stroke"
                        stroke-width="1.4"
                      />
                      <text class="architecture-layer-title" :x="layer.x + 14" :y="layer.y + 22" :fill="layer.palette.title">
                        {{ layer.title }}
                      </text>
                      <text v-if="layer.subtitle" class="architecture-layer-subtitle" :x="architectureLayerSubtitleX(layer)" :y="layer.y + 22">
                        {{ layer.subtitle }}
                      </text>

                      <g v-for="component in layer.components" :key="component.id" class="architecture-component">
                        <rect
                          :x="component.x"
                          :y="component.y"
                          :width="component.width"
                          :height="component.height"
                          rx="6"
                          :fill="layer.palette.componentFill"
                          :stroke="layer.palette.componentStroke"
                        />
                        <text class="architecture-component-title" :x="component.x + component.width / 2" :y="component.y + 21">
                          {{ shortText(component.title, 14) }}
                        </text>
                        <text
                          v-for="(detail, detailIndex) in componentDetailLines(component)"
                          :key="`${component.id}-${detailIndex}`"
                          class="architecture-detail"
                          :x="component.x + component.width / 2"
                          :y="component.y + 40 + detailIndex * 14"
                        >
                          {{ shortText(detail, 15) }}
                        </text>
                      </g>
                    </g>

                    <g class="architecture-concerns">
                      <rect
                        :x="architectureDiagram.concernPanel.x"
                        :y="architectureDiagram.concernPanel.y"
                        :width="architectureDiagram.concernPanel.width"
                        :height="architectureDiagram.concernPanel.height"
                        rx="8"
                        :fill="architectureDiagram.concernPanel.palette.fill"
                        :stroke="architectureDiagram.concernPanel.palette.stroke"
                        stroke-width="1.4"
                      />
                      <text
                        class="architecture-concern-heading"
                        :x="architectureDiagram.concernPanel.x + 14"
                        :y="architectureDiagram.concernPanel.y + 24"
                        :fill="architectureDiagram.concernPanel.palette.title"
                      >
                        横切关注点
                      </text>
                      <g v-for="concern in architectureDiagram.concerns" :key="concern.id" class="architecture-concern">
                        <rect
                          :x="concern.x"
                          :y="concern.y"
                          :width="concern.width"
                          :height="concern.height"
                          rx="6"
                          fill="#ffffff"
                          :stroke="architectureDiagram.concernPanel.palette.stroke"
                        />
                        <text class="architecture-concern-title" :x="concern.x + concern.width / 2" :y="concern.y + 22">
                          {{ shortText(concern.title, 12) }}
                        </text>
                        <text
                          v-for="(detail, detailIndex) in componentDetailLines(concern)"
                          :key="`${concern.id}-${detailIndex}`"
                          class="architecture-detail"
                          :x="concern.x + concern.width / 2"
                          :y="concern.y + 41 + detailIndex * 14"
                        >
                          {{ shortText(detail, 12) }}
                        </text>
                      </g>
                    </g>
                  </svg>
                </div>
              </section>
            </div>
          </section>

          <section v-else-if="activeDiagram === 'flowchart'" class="flowchart-editor">
            <div class="flowchart-toolbar">
              <div>
                <strong>{{ flowchartDiagram.title }}</strong>
                <span>{{ flowchartStatsText }}</span>
              </div>
              <div class="toolbar">
                <el-button :icon="Download" :disabled="!flowchartDiagram.nodes.length" @click="downloadFlowchartSvg">SVG</el-button>
                <el-button :icon="Picture" :disabled="!flowchartDiagram.nodes.length" @click="downloadFlowchartPng">PNG</el-button>
              </div>
            </div>

            <div class="flowchart-workbench">
              <aside class="flowchart-dsl-panel">
                <div class="flowchart-dsl-header">
                  <div>
                    <strong>流程图 DSL</strong>
                    <span>使用“序号-类型-文本”描述节点，判断节点下缩进写“是-目标序号”“否-目标序号”。</span>
                  </div>
                  <div class="flowchart-dsl-actions">
                    <el-button :icon="Delete" text @click="clearFlowchartDsl">清空</el-button>
                    <el-button :icon="DocumentCopy" text @click="loadFlowchartExample">加载示例</el-button>
                    <el-button :icon="Refresh" text @click="regenerateFlowchartDsl">换一换</el-button>
                  </div>
                </div>

                <div class="flowchart-grammar">
                  类型支持：开始 / 结束 / 处理 / 判断 / 输入 / 输出 / 异常。普通节点自动连接下一节点；判断节点使用分支目标连接。
                </div>

                <el-input
                  v-model="flowchartDsl"
                  class="flowchart-dsl-input"
                  type="textarea"
                  resize="none"
                  :autosize="{ minRows: 20, maxRows: 34 }"
                  spellcheck="false"
                />

                <div class="flowchart-dsl-footer">
                  <span v-if="parsedFlowchart.errors.length">{{ parsedFlowchart.errors.length }} 条格式提示</span>
                  <span v-else>DSL 已就绪</span>
                  <el-button type="primary" :icon="MagicStick" @click="refreshFlowchartPreview">生成预览</el-button>
                </div>
              </aside>

              <section class="flowchart-preview-panel">
                <div v-if="parsedFlowchart.errors.length" class="flowchart-errors">
                  <span v-for="error in parsedFlowchart.errors.slice(0, 3)" :key="error">{{ error }}</span>
                </div>

                <div class="flowchart-preview-scroll">
                  <svg
                    class="flowchart-svg"
                    :viewBox="`0 0 ${flowchartDiagram.canvas.width} ${flowchartDiagram.canvas.height}`"
                    :style="{ width: `${flowchartDiagram.canvas.width}px`, height: `${flowchartDiagram.canvas.height}px` }"
                  >
                    <defs>
                      <marker id="flowchart-arrow-preview" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto">
                        <path d="M0,0 L0,6 L8,3 z" fill="#222222" />
                      </marker>
                    </defs>
                    <rect width="100%" height="100%" fill="#ffffff" />
                    <text class="flowchart-svg-title" :x="flowchartDiagram.canvas.width / 2" y="30">
                      {{ flowchartDiagram.title }}
                    </text>

                    <g class="flowchart-edge-layer">
                      <path
                        v-for="edge in flowchartDiagram.edges"
                        :key="edge.id"
                        class="flowchart-edge"
                        :d="edge.path"
                        marker-end="url(#flowchart-arrow-preview)"
                      />
                      <text
                        v-for="edge in flowchartLabeledEdges"
                        :key="`${edge.id}-label`"
                        class="flowchart-edge-label"
                        :x="edge.labelX"
                        :y="edge.labelY"
                      >
                        {{ edge.label }}
                      </text>
                    </g>

                    <g class="flowchart-node-layer">
                      <g v-for="node in flowchartDiagram.nodes" :key="node.id" class="flowchart-node">
                        <polygon
                          v-if="node.shape === 'diamond'"
                          class="flowchart-node-shape"
                          :points="flowchartDiamondPoints(node)"
                        />
                        <polygon
                          v-else-if="node.shape === 'parallelogram'"
                          class="flowchart-node-shape"
                          :points="flowchartParallelogramPoints(node)"
                        />
                        <rect
                          v-else
                          class="flowchart-node-shape"
                          :x="node.x"
                          :y="node.y"
                          :width="node.width"
                          :height="node.height"
                          :rx="node.shape === 'terminator' ? node.height / 2 : 3"
                        />
                        <text
                          v-for="(line, lineIndex) in node.lines"
                          :key="`${node.id}-${lineIndex}`"
                          class="flowchart-node-text"
                          :x="node.x + node.width / 2"
                          :y="flowchartTextY(node, lineIndex)"
                        >
                          {{ line }}
                        </text>
                      </g>
                    </g>
                  </svg>
                </div>
              </section>
            </div>
          </section>

          <ThreeLineTableView
            v-else-if="activeDiagram === 'three_line_table'"
            v-model:draft="threeLineDraft"
            :tables="database.tables"
            :project-name="project.projectName"
          />

          <section v-else class="custom-diagram-panel">
            <div class="custom-toolbar">
              <div>
                <strong>{{ currentGraph.title }}</strong>
                <span>{{ currentGraph.description }}</span>
              </div>
              <div class="toolbar">
                <el-button :icon="Download" :disabled="!currentGraph.nodes.length" @click="downloadCurrentSvg">SVG</el-button>
                <el-button :icon="Picture" :disabled="!currentGraph.nodes.length" @click="downloadCurrentPng">PNG</el-button>
              </div>
            </div>

            <div class="graph-scroll">
              <svg
                ref="graphSvgRef"
                class="graph-canvas"
                :viewBox="`0 0 ${currentGraph.canvas.width} ${currentGraph.canvas.height}`"
                :style="{ width: `${currentGraph.canvas.width}px`, height: `${currentGraph.canvas.height}px` }"
              >
                <defs>
                  <marker id="diagram-arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto">
                    <path d="M0,0 L0,6 L8,3 z" fill="#55708f" />
                  </marker>
                </defs>
                <rect width="100%" height="100%" fill="#ffffff" />
                <g>
                  <path
                    v-for="edge in graphEdges"
                    :key="edge.key"
                    class="diagram-edge"
                    :d="edge.path"
                    marker-end="url(#diagram-arrow)"
                  />
                  <text
                    v-for="edge in graphEdges"
                    :key="`${edge.key}-label`"
                    class="diagram-edge-label"
                    :x="edge.labelX"
                    :y="edge.labelY"
                  >
                    {{ edge.label }}
                  </text>
                </g>
                <g>
                  <g
                    v-for="node in currentGraph.nodes"
                    :key="node.id"
                    class="diagram-node"
                    :transform="`translate(${node.x}, ${node.y})`"
                  >
                    <rect :width="node.width" :height="node.height" rx="8" />
                    <text class="diagram-node-title" :x="node.width / 2" :y="node.height / 2 - 3">
                      {{ shortText(node.label, 16) }}
                    </text>
                    <text v-if="node.lane" class="diagram-node-lane" :x="node.width / 2" :y="node.height - 12">
                      {{ shortText(node.lane, 14) }}
                    </text>
                  </g>
                </g>
              </svg>
            </div>

            <el-collapse v-if="activeDiagram === 'ai' && aiDiagram.mermaid" class="diagram-code">
              <el-collapse-item title="Mermaid / 文字说明" name="mermaid">
                <el-input v-model="aiDiagram.mermaid" type="textarea" :rows="8" />
                <el-input v-model="aiDiagram.markdown" class="diagram-markdown" type="textarea" :rows="8" />
              </el-collapse-item>
            </el-collapse>
          </section>
        </main>
      </div>
    </div>
  </section>
</template>

<script setup>
import { computed, onMounted, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Delete, DocumentCopy, Download, MagicStick, Picture, Refresh } from '@element-plus/icons-vue'
import AppModuleDiagramView from '@/components/AppModuleDiagramView.vue'
import ErDiagramView from '@/components/ErDiagramView.vue'
import ThreeLineTableView from '@/components/ThreeLineTableView.vue'
import { generateDiagram, getDatabase, getProject, listProjectDiagrams, saveProjectDiagram } from '@/api/project'
import {
  ARCHITECTURE_EXAMPLE_DSL,
  buildArchitectureDiagram,
  buildArchitectureExportSvg,
  buildDefaultArchitectureDsl,
  parseArchitectureDsl
} from '@/utils/architectureDiagram'
import {
  FLOWCHART_EXAMPLE_DSL,
  buildDefaultFlowchartDsl,
  buildFlowchartDiagram,
  buildFlowchartExportSvg,
  parseFlowchartDsl
} from '@/utils/flowchartDiagram'
import { buildAppModuleGraph } from '@/utils/appModuleGraph'
import { blueprintToModuleOutline, moduleOutlineToText, parseModuleOutlineText } from '@/utils/appModuleOutline'
import { saveBlob } from '@/utils/download'

const route = useRoute()
const router = useRouter()
const projectId = computed(() => route.params.projectId)
const loading = ref(false)
const savingDiagram = ref(false)
const generatingAiDiagram = ref(false)
const activeDiagram = ref('er')
const project = reactive({})
const database = ref({ tables: [], erDiagram: {} })
const erDraft = ref({ positions: {}, relations: [], deletedEdgeIds: [] })
const threeLineDraft = ref({})
const graphSvgRef = ref(null)
const architectureSvgRef = ref(null)
const architectureDsl = ref('')
const architectureBlackWhite = ref(false)
const flowchartDsl = ref('')
const savedDiagrams = ref([])
const moduleDiagramSourceMode = ref('blueprint')
const moduleDiagramOutlineText = ref('')
const moduleDiagramLayoutOptions = ref({ density: 'standard', direction: 'horizontal', fontSize: 'standard' })
const aiDiagram = ref({ nodes: [], edges: [], title: 'AI 图表', description: '', mermaid: '', markdown: '' })
const aiForm = reactive({
  diagramType: 'flowchart',
  title: '',
  prompt: ''
})
const AI_DIAGRAM_TYPES = new Set(['use_case', 'sequence', 'activity', 'state', 'data_flow'])

const appBlueprint = computed(() => parseJson(project.appBlueprint, { roles: [], frontendMenus: [], adminMenus: [] }))
const moduleDiagramGraph = computed(() => {
  const text = moduleDiagramSourceMode.value === 'outline'
    ? moduleDiagramOutlineText.value
    : defaultModuleOutlineText()
  return buildAppModuleGraph(parseModuleOutlineText(text).outline, moduleDiagramLayoutOptions.value)
})
const parsedArchitecture = computed(() => parseArchitectureDsl(architectureDsl.value))
const architectureDiagram = computed(() => buildArchitectureDiagram(parsedArchitecture.value, {
  blackWhite: architectureBlackWhite.value,
  title: `${project.projectName || '项目'}系统架构图`
}))
const architectureStatsText = computed(() => {
  const stats = architectureDiagram.value.stats
  return `${stats.layerCount} 层 · ${stats.totalComponentCount} 组件`
})
const parsedFlowchart = computed(() => parseFlowchartDsl(flowchartDsl.value))
const flowchartDiagram = computed(() => buildFlowchartDiagram(parsedFlowchart.value, {
  title: `${project.projectName || '项目'}流程图`
}))
const flowchartStatsText = computed(() => {
  const stats = flowchartDiagram.value.stats
  return `${stats.nodeCount} 节点 · ${stats.edgeCount} 连线`
})
const flowchartLabeledEdges = computed(() => flowchartDiagram.value.edges.filter((edge) => edge.label))

const currentGraph = computed(() => {
  if (activeDiagram.value === 'ai') {
    return normalizeGraph(aiDiagram.value, 'AI 图表')
  }
  if (activeDiagram.value === 'overview') {
    return buildOverviewGraph()
  }
  return buildArchitectureGraph()
})

const graphEdges = computed(() => {
  const nodeMap = new Map(currentGraph.value.nodes.map((node) => [node.id, node]))
  return currentGraph.value.edges.map((edge, index) => {
    const source = nodeMap.get(edge.source)
    const target = nodeMap.get(edge.target)
    if (!source || !target) return null
    const sx = source.x + source.width
    const sy = source.y + source.height / 2
    const tx = target.x
    const ty = target.y + target.height / 2
    const midX = (sx + tx) / 2
    return {
      key: `${edge.source}-${edge.target}-${index}`,
      label: edge.label || '',
      labelX: midX,
      labelY: (sy + ty) / 2 - 8,
      path: `M ${sx} ${sy} C ${midX} ${sy}, ${midX} ${ty}, ${tx} ${ty}`
    }
  }).filter(Boolean)
})

function parseJson(value, fallback) {
  if (!value) return fallback
  if (typeof value === 'object') return value
  try {
    return JSON.parse(value)
  } catch (error) {
    return fallback
  }
}

function shortText(value, maxLength) {
  const text = String(value || '')
  return text.length > maxLength ? `${text.slice(0, maxLength - 1)}…` : text
}

function componentDetailLines(component) {
  return Array.isArray(component?.details) ? component.details.slice(0, 3) : []
}

function architectureArrowPath(arrow) {
  return `M ${arrow.x1} ${arrow.y1 + 4} L ${arrow.x2} ${arrow.y2 - 7}`
}

function architectureLayerSubtitleX(layer) {
  return layer.x + 14 + String(layer.title || '').length * 14 + 12
}

function flowchartDiamondPoints(node) {
  const centerX = node.x + node.width / 2
  const centerY = node.y + node.height / 2
  return `${centerX},${node.y} ${node.x + node.width},${centerY} ${centerX},${node.y + node.height} ${node.x},${centerY}`
}

function flowchartParallelogramPoints(node) {
  const offset = 14
  return `${node.x + offset},${node.y} ${node.x + node.width},${node.y} ${node.x + node.width - offset},${node.y + node.height} ${node.x},${node.y + node.height}`
}

function flowchartTextY(node, lineIndex) {
  const lines = Array.isArray(node.lines) ? node.lines : []
  return node.y + node.height / 2 - ((lines.length - 1) * 8) + lineIndex * 17
}

function normalizeGraph(source, fallbackTitle) {
  const nodes = Array.isArray(source?.nodes) ? source.nodes : []
  const positionedNodes = nodes.map((node, index) => ({
    id: String(node.id || `node_${index + 1}`).replace(/[^a-zA-Z0-9_:-]/g, '_'),
    label: node.label || node.id || `节点${index + 1}`,
    lane: node.lane || node.type || '',
    width: 158,
    height: 68,
    x: 56 + (index % 3) * 250,
    y: 76 + Math.floor(index / 3) * 132
  }))
  const rows = Math.max(1, Math.ceil(positionedNodes.length / 3))
  return {
    title: source?.title || fallbackTitle,
    description: source?.description || '可导出为 SVG 或 PNG。',
    nodes: positionedNodes,
    edges: Array.isArray(source?.edges) ? source.edges : [],
    canvas: {
      width: Math.max(860, Math.min(1200, 112 + Math.min(3, Math.max(positionedNodes.length, 1)) * 250)),
      height: Math.max(440, 96 + rows * 132)
    }
  }
}

function buildArchitectureGraph() {
  return normalizeGraph({
    title: '系统架构图',
    description: '根据项目技术栈自动生成，可作为论文系统设计章节素材。',
    nodes: [
      { id: 'browser', label: '浏览器用户', lane: '访问入口' },
      { id: 'easycode_web', label: project.frontFramework || 'Vue 前端', lane: '前台工作台' },
      { id: 'admin_ui', label: '后台管理端', lane: '管理入口' },
      { id: 'gateway', label: 'Spring Boot 接口', lane: project.backFramework || '后端服务' },
      { id: 'generator', label: '代码生成服务', lane: '业务生成' },
      { id: 'ai', label: 'AI 生成服务', lane: '图表/论文' },
      { id: 'mysql', label: 'MySQL 数据库', lane: `${database.value.tables.length || 0} 张业务表` },
      { id: 'redis', label: 'Redis 缓存', lane: '会话/任务' }
    ],
    edges: [
      { source: 'browser', target: 'easycode_web', label: '使用' },
      { source: 'browser', target: 'admin_ui', label: '管理' },
      { source: 'easycode_web', target: 'gateway', label: 'REST API' },
      { source: 'admin_ui', target: 'gateway', label: 'REST API' },
      { source: 'gateway', target: 'generator', label: '生成' },
      { source: 'gateway', target: 'ai', label: '调用' },
      { source: 'generator', target: 'mysql', label: '读写' },
      { source: 'gateway', target: 'redis', label: '缓存' }
    ]
  }, '系统架构图')
}

function buildOverviewGraph() {
  const tableNodes = database.value.tables.slice(0, 8).map((table) => ({
    id: `table_${table.tableName}`,
    label: table.tableComment || table.tableName,
    lane: table.tableName
  }))
  return normalizeGraph({
    title: '项目总览图',
    description: '汇总项目、模块、数据库和预览生成链路。',
    nodes: [
      { id: 'project', label: project.projectName || '项目草稿', lane: '需求' },
      { id: 'blueprint', label: '系统模块设计', lane: '蓝图' },
      { id: 'loop', label: '业务闭环设计', lane: '流程' },
      { id: 'database', label: '数据库设计', lane: `${database.value.tables.length || 0} 张表` },
      { id: 'preview', label: '源码预览/下载', lane: '交付' },
      ...tableNodes
    ],
    edges: [
      { source: 'project', target: 'blueprint', label: '规划' },
      { source: 'blueprint', target: 'loop', label: '闭环' },
      { source: 'loop', target: 'database', label: '落表' },
      { source: 'database', target: 'preview', label: '生成' },
      ...tableNodes.map((node) => ({ source: 'database', target: node.id, label: '包含' }))
    ]
  }, '项目总览图')
}

async function loadData() {
  if (!projectId.value) return
  loading.value = true
  try {
    const [projectResult, databaseResult, diagramsResult] = await Promise.all([
      getProject(projectId.value),
      getDatabase(projectId.value),
      listProjectDiagrams(projectId.value)
    ])
    Object.assign(project, projectResult || {})
    database.value = normalizeDatabase(databaseResult)
    savedDiagrams.value = Array.isArray(diagramsResult) ? diagramsResult : []
    applySavedDrafts()
    aiForm.title = `${project.projectName || '项目'}业务流程图`
  } catch (error) {
    ElMessage.error(error.message || '加载图表数据失败')
  } finally {
    loading.value = false
  }
}

function normalizeDatabase(value) {
  const result = value && typeof value === 'object' ? value : {}
  return {
    ...result,
    tables: Array.isArray(result.tables) ? result.tables : [],
    erDiagram: result.erDiagram && typeof result.erDiagram === 'object' ? result.erDiagram : {}
  }
}

function defaultModuleOutlineText() {
  return moduleOutlineToText(blueprintToModuleOutline(appBlueprint.value, { projectName: project.projectName }))
}

function moduleDiagramPayload() {
  const outlineText = moduleDiagramSourceMode.value === 'outline'
    ? moduleDiagramOutlineText.value
    : defaultModuleOutlineText()
  return {
    sourceMode: moduleDiagramSourceMode.value,
    outlineText,
    layoutOptions: moduleDiagramLayoutOptions.value,
    graph: moduleDiagramGraph.value,
    projectName: project.projectName
  }
}

function applySavedModuleDiagram(diagram = null) {
  const saved = diagram || savedDiagrams.value.find((item) => item.diagramType === 'module')
  const payload = parseJson(saved?.diagramJson, null)
  if (!payload || typeof payload !== 'object') {
    moduleDiagramSourceMode.value = 'blueprint'
    moduleDiagramOutlineText.value = defaultModuleOutlineText()
    moduleDiagramLayoutOptions.value = { density: 'standard', direction: 'horizontal', fontSize: 'standard' }
    return
  }
  moduleDiagramSourceMode.value = payload.sourceMode === 'outline' ? payload.sourceMode : 'blueprint'
  if (typeof payload.outlineText === 'string') {
    moduleDiagramOutlineText.value = payload.outlineText
  } else {
    moduleDiagramOutlineText.value = defaultModuleOutlineText()
  }
  moduleDiagramLayoutOptions.value = {
    density: payload.layoutOptions?.density || 'standard',
    direction: payload.layoutOptions?.direction || 'horizontal',
    fontSize: payload.layoutOptions?.fontSize || 'standard'
  }
}

function applySavedDrafts() {
  const savedEr = savedDiagrams.value.find((item) => item.diagramType === 'er')
  const erPayload = parseJson(savedEr?.diagramJson, null)
  erDraft.value = erPayload?.draft || database.value.erDiagram || { positions: {}, relations: [], deletedEdgeIds: [] }
  const savedArchitecture = savedDiagrams.value.find((item) => item.diagramType === 'architecture')
  if (savedArchitecture) {
    applySavedArchitectureDiagram(savedArchitecture)
  } else {
    architectureDsl.value = buildDefaultArchitectureDsl(project, database.value)
    architectureBlackWhite.value = false
  }
  const savedFlowchart = savedDiagrams.value.find((item) => item.diagramType === 'flowchart')
  if (savedFlowchart) {
    applySavedFlowchartDiagram(savedFlowchart)
  } else {
    flowchartDsl.value = buildDefaultFlowchartDsl(project)
  }
  const savedAi = savedDiagrams.value.find((item) => isAiDiagramType(item.diagramType))
  if (savedAi) {
    applySavedAiDiagram(savedAi)
  }
  const savedThreeLine = savedDiagrams.value.find((item) => item.diagramType === 'three_line_table')
  if (savedThreeLine) {
    applySavedThreeLineDiagram(savedThreeLine)
  } else {
    threeLineDraft.value = {}
  }
  applySavedModuleDiagram()
}

function loadSavedDiagram(diagram) {
  if (!diagram) return
  if (diagram.diagramType === 'architecture') {
    activeDiagram.value = 'architecture'
    applySavedArchitectureDiagram(diagram)
    return
  }
  if (diagram.diagramType === 'flowchart') {
    activeDiagram.value = 'flowchart'
    applySavedFlowchartDiagram(diagram)
    return
  }
  if (diagram.diagramType === 'module') {
    activeDiagram.value = 'module'
    applySavedModuleDiagram(diagram)
    return
  }
  if (isAiDiagramType(diagram.diagramType)) {
    activeDiagram.value = 'ai'
    applySavedAiDiagram(diagram)
    return
  }
  if (diagram.diagramType === 'three_line_table') {
    activeDiagram.value = 'three_line_table'
    applySavedThreeLineDiagram(diagram)
    return
  }
  activeDiagram.value = diagram.diagramType || 'er'
  if (diagram.diagramType === 'er') {
    const payload = parseJson(diagram.diagramJson, {})
    erDraft.value = payload.draft || erDraft.value
  }
}

function applySavedArchitectureDiagram(diagram) {
  const payload = parseJson(diagram.diagramJson, {})
  architectureDsl.value = typeof payload.dsl === 'string'
    ? payload.dsl
    : buildDefaultArchitectureDsl(project, database.value)
  architectureBlackWhite.value = Boolean(payload.blackWhite)
}

function applySavedFlowchartDiagram(diagram) {
  const payload = parseJson(diagram.diagramJson, {})
  flowchartDsl.value = typeof payload.dsl === 'string'
    ? payload.dsl
    : buildDefaultFlowchartDsl(project)
}

function applySavedAiDiagram(diagram) {
  const payload = parseJson(diagram.diagramJson, {})
  aiDiagram.value = {
    ...payload,
    diagramId: diagram.diagramId,
    diagramType: diagram.diagramType,
    title: diagram.title || payload.title || 'AI 图表',
    description: diagram.description || payload.description || '',
    mermaid: diagram.mermaid || payload.mermaid || '',
    markdown: diagram.markdown || payload.markdown || '',
    nodes: Array.isArray(payload.nodes) ? payload.nodes : [],
    edges: Array.isArray(payload.edges) ? payload.edges : []
  }
  aiForm.diagramType = String(diagram.diagramType || 'ai_flowchart').replace(/^ai_/, '') || 'flowchart'
  aiForm.title = aiDiagram.value.title
}

function applySavedThreeLineDiagram(diagram) {
  const payload = parseJson(diagram.diagramJson, {})
  threeLineDraft.value = {
    selectedTableIds: Array.isArray(payload.selectedTableIds) ? payload.selectedTableIds : []
  }
}

async function handleGenerateAiDiagram() {
  if (!projectId.value) return
  generatingAiDiagram.value = true
  try {
    const result = await generateDiagram(projectId.value, {
      diagramType: aiForm.diagramType,
      title: aiForm.title,
      prompt: aiForm.prompt,
      appBlueprint: project.appBlueprint,
      businessLoopPlan: project.businessLoopPlan
    })
    aiDiagram.value = result || { nodes: [], edges: [], title: aiForm.title }
    activeDiagram.value = 'ai'
    await saveCurrentDiagram()
    ElMessage.success('图表已生成并保存')
  } catch (error) {
    ElMessage.error(error.message || '生成图表失败')
  } finally {
    generatingAiDiagram.value = false
  }
}

async function saveCurrentDiagram() {
  if (!projectId.value) return
  const payload = currentDiagramPayload()
  savingDiagram.value = true
  try {
    const saved = await saveProjectDiagram(projectId.value, payload)
    mergeSavedDiagram(saved)
    if (activeDiagram.value === 'ai') {
      aiDiagram.value = {
        ...aiDiagram.value,
        diagramId: saved.diagramId,
        diagramType: saved.diagramType
      }
    }
    ElMessage.success('图表草稿已保存')
  } catch (error) {
    ElMessage.error(error.message || '保存图表失败')
  } finally {
    savingDiagram.value = false
  }
}

function currentDiagramPayload() {
  if (activeDiagram.value === 'er') {
    const existing = savedDiagrams.value.find((item) => item.diagramType === 'er')
    return {
      diagramId: existing?.diagramId,
      diagramType: 'er',
      title: 'ER 图',
      description: '数据库实体关系图草稿',
      diagramJson: JSON.stringify({ draft: erDraft.value })
    }
  }
  if (activeDiagram.value === 'module') {
    const existing = savedDiagrams.value.find((item) => item.diagramType === 'module')
    return {
      diagramId: existing?.diagramId,
      diagramType: 'module',
      title: '功能模块图',
      description: '系统功能模块图草稿',
      diagramJson: JSON.stringify(moduleDiagramPayload())
    }
  }
  if (activeDiagram.value === 'architecture') {
    const existing = savedDiagrams.value.find((item) => item.diagramType === 'architecture')
    return {
      diagramId: existing?.diagramId,
      diagramType: 'architecture',
      title: '系统架构图',
      description: 'DSL 驱动的分层系统架构图',
      diagramJson: JSON.stringify({
        dsl: architectureDsl.value,
        graph: architectureDiagram.value,
        blackWhite: architectureBlackWhite.value
      })
    }
  }
  if (activeDiagram.value === 'flowchart') {
    const existing = savedDiagrams.value.find((item) => item.diagramType === 'flowchart')
    return {
      diagramId: existing?.diagramId,
      diagramType: 'flowchart',
      title: '流程图',
      description: 'DSL 驱动的业务流程图草稿',
      diagramJson: JSON.stringify({
        dsl: flowchartDsl.value,
        graph: flowchartDiagram.value
      })
    }
  }
  if (activeDiagram.value === 'three_line_table') {
    const existing = savedDiagrams.value.find((item) => item.diagramType === 'three_line_table')
    return {
      diagramId: existing?.diagramId,
      diagramType: 'three_line_table',
      title: '三线表',
      description: '论文数据库设计三线表草稿',
      diagramJson: JSON.stringify({
        selectedTableIds: Array.isArray(threeLineDraft.value?.selectedTableIds) ? threeLineDraft.value.selectedTableIds : []
      })
    }
  }
  if (activeDiagram.value === 'ai') {
    const diagramType = normalizeAiDiagramType(aiDiagram.value.diagramType || aiForm.diagramType)
    return {
      diagramId: aiDiagram.value.diagramId,
      diagramType,
      title: aiDiagram.value.title || aiForm.title || 'AI 图表',
      description: aiDiagram.value.description || '',
      diagramJson: JSON.stringify({ ...aiDiagram.value, diagramType }),
      mermaid: aiDiagram.value.mermaid || '',
      markdown: aiDiagram.value.markdown || ''
    }
  }
  const existing = savedDiagrams.value.find((item) => item.diagramType === activeDiagram.value)
  return {
    diagramId: existing?.diagramId,
    diagramType: activeDiagram.value,
    title: currentGraph.value.title,
    description: currentGraph.value.description,
    diagramJson: JSON.stringify(currentGraph.value)
  }
}

function mergeSavedDiagram(saved) {
  if (!saved?.diagramId) return
  const index = savedDiagrams.value.findIndex((item) => item.diagramId === saved.diagramId)
  if (index >= 0) {
    savedDiagrams.value.splice(index, 1, saved)
  } else {
    savedDiagrams.value.unshift(saved)
  }
}

function isAiDiagramType(diagramType) {
  const value = String(diagramType || '')
  return value.startsWith('ai_') || AI_DIAGRAM_TYPES.has(value)
}

function normalizeAiDiagramType(diagramType) {
  const value = String(diagramType || '').replace(/^ai_/, '') || 'flowchart'
  return `ai_${value}`
}

function clearArchitectureDsl() {
  architectureDsl.value = ''
}

function loadArchitectureExample() {
  architectureDsl.value = ARCHITECTURE_EXAMPLE_DSL
  architectureBlackWhite.value = false
  ElMessage.success('示例 DSL 已加载')
}

function regenerateArchitectureDsl() {
  architectureDsl.value = buildDefaultArchitectureDsl(project, database.value)
  ElMessage.success('已根据当前项目重新生成 DSL')
}

function refreshArchitecturePreview() {
  if (!parsedArchitecture.value.layers.length && !parsedArchitecture.value.concerns.length) {
    ElMessage.warning('请先在 DSL 中添加架构层或横切关注点')
    return
  }
  ElMessage.success('预览已更新')
}

function clearFlowchartDsl() {
  flowchartDsl.value = ''
}

function loadFlowchartExample() {
  flowchartDsl.value = FLOWCHART_EXAMPLE_DSL
  ElMessage.success('示例 DSL 已加载')
}

function regenerateFlowchartDsl() {
  flowchartDsl.value = buildDefaultFlowchartDsl(project)
  ElMessage.success('已根据当前项目重新生成流程图 DSL')
}

function refreshFlowchartPreview() {
  if (!flowchartDiagram.value.nodes.length) {
    ElMessage.warning('请先在 DSL 中添加流程节点')
    return
  }
  if (parsedFlowchart.value.errors.length) {
    ElMessage.warning('DSL 还有格式提示，请修正后再使用')
    return
  }
  ElMessage.success('预览已更新')
}

function graphSvgText() {
  const svg = graphSvgRef.value
  if (!svg) return ''
  return `<?xml version="1.0" encoding="UTF-8"?>\n${svg.outerHTML}`
}

function architectureSvgText() {
  return buildArchitectureExportSvg(architectureDiagram.value, {
    blackWhite: architectureBlackWhite.value
  })
}

function downloadArchitectureSvg() {
  const text = architectureSvgText()
  if (!text) return
  saveBlob(new Blob([text], { type: 'image/svg+xml;charset=utf-8' }), `${architectureDiagram.value.title || '系统架构图'}.svg`)
}

function flowchartSvgText() {
  return buildFlowchartExportSvg(flowchartDiagram.value)
}

function downloadFlowchartSvg() {
  const text = flowchartSvgText()
  if (!text) return
  saveBlob(new Blob([text], { type: 'image/svg+xml;charset=utf-8' }), `${flowchartDiagram.value.title || '流程图'}.svg`)
}

async function downloadArchitecturePng() {
  const text = architectureSvgText()
  if (!text) return
  const blob = new Blob([text], { type: 'image/svg+xml;charset=utf-8' })
  const url = URL.createObjectURL(blob)
  const image = new Image()
  image.decoding = 'async'
  try {
    await new Promise((resolve, reject) => {
      image.onload = resolve
      image.onerror = reject
      image.src = url
    })
    const canvas = document.createElement('canvas')
    canvas.width = architectureDiagram.value.canvas.width
    canvas.height = architectureDiagram.value.canvas.height
    const context = canvas.getContext('2d')
    context.fillStyle = '#ffffff'
    context.fillRect(0, 0, canvas.width, canvas.height)
    context.drawImage(image, 0, 0)
    const pngBlob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'))
    if (pngBlob) {
      saveBlob(pngBlob, `${architectureDiagram.value.title || '系统架构图'}.png`)
    }
  } finally {
    URL.revokeObjectURL(url)
  }
}

async function downloadFlowchartPng() {
  const text = flowchartSvgText()
  if (!text) return
  const blob = new Blob([text], { type: 'image/svg+xml;charset=utf-8' })
  const url = URL.createObjectURL(blob)
  const image = new Image()
  image.decoding = 'async'
  try {
    await new Promise((resolve, reject) => {
      image.onload = resolve
      image.onerror = reject
      image.src = url
    })
    const canvas = document.createElement('canvas')
    canvas.width = flowchartDiagram.value.canvas.width
    canvas.height = flowchartDiagram.value.canvas.height
    const context = canvas.getContext('2d')
    context.fillStyle = '#ffffff'
    context.fillRect(0, 0, canvas.width, canvas.height)
    context.drawImage(image, 0, 0)
    const pngBlob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'))
    if (pngBlob) {
      saveBlob(pngBlob, `${flowchartDiagram.value.title || '流程图'}.png`)
    }
  } finally {
    URL.revokeObjectURL(url)
  }
}

function downloadCurrentSvg() {
  const text = graphSvgText()
  if (!text) return
  saveBlob(new Blob([text], { type: 'image/svg+xml;charset=utf-8' }), `${currentGraph.value.title || 'diagram'}.svg`)
}

async function downloadCurrentPng() {
  const text = graphSvgText()
  if (!text) return
  const blob = new Blob([text], { type: 'image/svg+xml;charset=utf-8' })
  const url = URL.createObjectURL(blob)
  const image = new Image()
  image.decoding = 'async'
  try {
    await new Promise((resolve, reject) => {
      image.onload = resolve
      image.onerror = reject
      image.src = url
    })
    const canvas = document.createElement('canvas')
    canvas.width = currentGraph.value.canvas.width
    canvas.height = currentGraph.value.canvas.height
    const context = canvas.getContext('2d')
    context.fillStyle = '#ffffff'
    context.fillRect(0, 0, canvas.width, canvas.height)
    context.drawImage(image, 0, 0)
    const pngBlob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'))
    if (pngBlob) {
      saveBlob(pngBlob, `${currentGraph.value.title || 'diagram'}.png`)
    }
  } finally {
    URL.revokeObjectURL(url)
  }
}

onMounted(loadData)
</script>

<style scoped lang="scss">
.diagram-center-page {
  display: grid;
  gap: 18px;
}

.diagram-layout {
  display: grid;
  grid-template-columns: 1fr;
  min-height: 620px;
}

.diagram-sidebar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: 14px 18px;
  padding: 16px 20px;
  border-bottom: 1px solid #e7ecf3;
  background: #fbfdff;
}

.diagram-kind-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  width: 100%;

  :deep(.el-radio-button) {
    flex: 0 0 auto;
  }

  :deep(.el-radio-button__inner) {
    min-width: 96px;
    border-left: var(--el-border);
    border-radius: 8px;
    text-align: center;
  }
}

.sidebar-actions,
.ai-form,
.saved-list {
  display: grid;
  gap: 10px;
}

.sidebar-actions {
  justify-self: end;
}

.sidebar-actions :deep(.el-button) {
  min-width: 150px;
}

.ai-form,
.saved-list {
  grid-column: 1 / -1;
}

.saved-list {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.saved-list strong {
  grid-column: 1 / -1;
  color: #172033;
  font-size: 14px;
}

.saved-item {
  display: grid;
  gap: 3px;
  width: 100%;
  padding: 9px 10px;
  border: 1px solid #e1e8f0;
  border-radius: 8px;
  background: #ffffff;
  color: #172033;
  cursor: pointer;
  text-align: left;

  span {
    overflow: hidden;
    font-size: 13px;
    font-weight: 700;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  small {
    color: #667085;
    font-size: 12px;
  }
}

.diagram-main {
  min-width: 0;
}

.architecture-editor {
  display: grid;
  min-height: 680px;
  background: #ffffff;
}

.architecture-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 68px;
  padding: 14px 18px;
  border-bottom: 1px solid #e7ecf3;

  strong,
  span {
    display: block;
  }

  strong {
    color: #172033;
    font-size: 16px;
  }

  span {
    margin-top: 4px;
    color: #667085;
    font-size: 13px;
  }
}

.architecture-workbench {
  display: grid;
  grid-template-columns: minmax(340px, 42%) minmax(0, 1fr);
  min-height: 612px;
}

.architecture-dsl-panel {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  border-right: 1px solid #e7ecf3;
  background: #fbfdff;
}

.architecture-dsl-header {
  display: grid;
  gap: 10px;

  strong,
  span {
    display: block;
  }

  strong {
    color: #172033;
    font-size: 15px;
  }

  span {
    margin-top: 4px;
    color: #667085;
    font-size: 12px;
    line-height: 1.5;
  }
}

.architecture-dsl-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;

  :deep(.el-button) {
    margin-left: 0;
  }
}

.architecture-grammar {
  padding: 9px 10px;
  border: 1px solid #dbeafe;
  border-radius: 6px;
  background: #eff6ff;
  color: #475569;
  font-size: 12px;
  line-height: 1.6;
}

.architecture-dsl-input {
  flex: 1;

  :deep(.el-textarea__inner) {
    min-height: 520px !important;
    border-color: #d9e0ea;
    border-radius: 6px;
    color: #172033;
    font-family: Consolas, "Courier New", "Microsoft YaHei", monospace;
    font-size: 14px;
    line-height: 1.65;
  }
}

.architecture-dsl-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;

  span {
    color: #667085;
    font-size: 12px;
  }
}

.architecture-preview-panel {
  display: grid;
  min-width: 0;
  align-content: start;
  background: #f8fafc;
}

.architecture-errors {
  display: grid;
  gap: 4px;
  padding: 10px 18px;
  border-bottom: 1px solid #fee2e2;
  background: #fff7f7;
  color: #b42318;
  font-size: 12px;
}

.architecture-preview-scroll {
  min-height: 612px;
  overflow: auto;
  padding: 24px;
}

.architecture-svg {
  display: block;
  background: #ffffff;
  border: 1px solid #d9e0ea;
  border-radius: 6px;
  font-family: "Microsoft YaHei", Arial, sans-serif;
}

.architecture-svg-title {
  fill: #0f172a;
  font-size: 18px;
  font-weight: 700;
  text-anchor: middle;
}

.architecture-layer-title {
  font-size: 14px;
  font-weight: 700;
}

.architecture-layer-subtitle {
  fill: #64748b;
  font-size: 12px;
}

.architecture-component-title,
.architecture-concern-title {
  fill: #1f2937;
  font-size: 13px;
  font-weight: 700;
  text-anchor: middle;
}

.architecture-detail {
  fill: #64748b;
  font-size: 10px;
  text-anchor: middle;
}

.architecture-arrow {
  fill: none;
  stroke: #475569;
  stroke-width: 1.5;
}

.architecture-concern-heading {
  font-size: 13px;
  font-weight: 700;
}

.flowchart-editor {
  display: grid;
  min-height: 660px;
  background: #ffffff;
}

.flowchart-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 68px;
  padding: 14px 18px;
  border-bottom: 1px solid #e7ecf3;

  strong,
  span {
    display: block;
  }

  strong {
    color: #172033;
    font-size: 16px;
  }

  span {
    margin-top: 4px;
    color: #667085;
    font-size: 13px;
  }
}

.flowchart-workbench {
  display: grid;
  grid-template-columns: minmax(340px, 38%) minmax(0, 1fr);
  min-height: 592px;
}

.flowchart-dsl-panel {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  border-right: 1px solid #e7ecf3;
  background: #fbfdff;
}

.flowchart-dsl-header {
  display: grid;
  gap: 10px;

  strong,
  span {
    display: block;
  }

  strong {
    color: #172033;
    font-size: 15px;
  }

  span {
    margin-top: 4px;
    color: #667085;
    font-size: 12px;
    line-height: 1.5;
  }
}

.flowchart-dsl-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;

  :deep(.el-button) {
    margin-left: 0;
  }
}

.flowchart-grammar {
  padding: 9px 10px;
  border: 1px solid #dbeafe;
  border-radius: 6px;
  background: #eff6ff;
  color: #475569;
  font-size: 12px;
  line-height: 1.6;
}

.flowchart-dsl-input {
  flex: 1;

  :deep(.el-textarea__inner) {
    min-height: 460px !important;
    border-color: #d9e0ea;
    border-radius: 6px;
    color: #172033;
    font-family: Consolas, "Courier New", "Microsoft YaHei", monospace;
    font-size: 14px;
    line-height: 1.65;
  }
}

.flowchart-dsl-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;

  span {
    color: #667085;
    font-size: 12px;
  }
}

.flowchart-preview-panel {
  display: grid;
  min-width: 0;
  align-content: start;
  background: #f8fafc;
}

.flowchart-errors {
  display: grid;
  gap: 4px;
  padding: 10px 18px;
  border-bottom: 1px solid #fee2e2;
  background: #fff7f7;
  color: #b42318;
  font-size: 12px;
}

.flowchart-preview-scroll {
  min-height: 592px;
  overflow: auto;
  padding: 24px;
}

.flowchart-svg {
  display: block;
  background: #ffffff;
  border: 1px solid #d9e0ea;
  border-radius: 6px;
  font-family: "Microsoft YaHei", Arial, sans-serif;
}

.flowchart-svg-title {
  fill: #111111;
  font-size: 18px;
  font-weight: 700;
  text-anchor: middle;
}

.flowchart-edge {
  fill: none;
  stroke: #222222;
  stroke-width: 1.4;
}

.flowchart-edge-label {
  fill: #475569;
  font-size: 12px;
  paint-order: stroke;
  stroke: #ffffff;
  stroke-width: 4px;
  text-anchor: middle;
}

.flowchart-node-shape {
  fill: #ffffff;
  stroke: #222222;
  stroke-width: 1.4;
}

.flowchart-node-text {
  fill: #111111;
  font-size: 14px;
  text-anchor: middle;
  dominant-baseline: middle;
}

.custom-diagram-panel {
  display: grid;
  gap: 0;
  min-height: 620px;
}

.custom-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 68px;
  padding: 14px 18px;
  border-bottom: 1px solid #e7ecf3;

  strong,
  span {
    display: block;
  }

  strong {
    color: #172033;
    font-size: 16px;
  }

  span {
    margin-top: 4px;
    color: #667085;
    font-size: 13px;
  }
}

.graph-scroll {
  min-height: 460px;
  overflow: auto;
  padding: 24px;
  background: #f8fafc;
}

.graph-canvas {
  display: block;
  background: #ffffff;
  border: 1px solid #d9e0ea;
}

.diagram-edge {
  fill: none;
  stroke: #55708f;
  stroke-width: 1.6;
}

.diagram-edge-label {
  fill: #64748b;
  font-size: 12px;
  paint-order: stroke;
  stroke: #ffffff;
  stroke-width: 4px;
  text-anchor: middle;
}

.diagram-node rect {
  fill: #ffffff;
  stroke: #2563eb;
  stroke-width: 1.4;
}

.diagram-node-title {
  fill: #172033;
  font-size: 14px;
  font-weight: 700;
  text-anchor: middle;
}

.diagram-node-lane {
  fill: #667085;
  font-size: 11px;
  text-anchor: middle;
}

.diagram-code {
  border-top: 1px solid #e7ecf3;
}

.diagram-markdown {
  margin-top: 10px;
}

@media (max-width: 980px) {
  .diagram-sidebar {
    grid-template-columns: 1fr;
  }

  .sidebar-actions {
    justify-self: stretch;
  }

  .sidebar-actions :deep(.el-button) {
    width: 100%;
  }

  .architecture-workbench,
  .flowchart-workbench {
    grid-template-columns: 1fr;
  }

  .architecture-dsl-panel,
  .flowchart-dsl-panel {
    border-right: 0;
    border-bottom: 1px solid #e7ecf3;
  }

  .architecture-toolbar,
  .flowchart-toolbar {
    align-items: flex-start;
    flex-direction: column;
  }
}
</style>
