<template>
  <div class="app-module-diagram">
    <div class="module-toolbar">
      <div class="module-summary">
        <span>节点：{{ graph.nodes.length }}</span>
        <span>连线：{{ graph.edges.length }}</span>
      </div>
      <div class="module-controls">
        <el-radio-group :model-value="sourceMode" size="small" @change="setSourceMode">
          <el-radio-button value="blueprint">自动蓝图</el-radio-button>
          <el-radio-button value="outline">缩进结构</el-radio-button>
        </el-radio-group>
        <el-radio-group :model-value="normalizedLayoutOptions.density" size="small" @change="(value) => updateLayoutOption('density', value)">
          <el-radio-button value="compact">紧凑</el-radio-button>
          <el-radio-button value="standard">标准</el-radio-button>
          <el-radio-button value="relaxed">宽松</el-radio-button>
        </el-radio-group>
        <el-radio-group :model-value="normalizedLayoutOptions.direction" size="small" @change="(value) => updateLayoutOption('direction', value)">
          <el-radio-button value="horizontal">横向</el-radio-button>
          <el-radio-button value="vertical">竖向</el-radio-button>
        </el-radio-group>
        <el-select
          :model-value="normalizedLayoutOptions.fontSize"
          size="small"
          class="module-font-select"
          @change="(value) => updateLayoutOption('fontSize', value)"
        >
          <el-option label="小" value="small" />
          <el-option label="标准" value="standard" />
          <el-option label="大" value="large" />
        </el-select>
        <el-button :icon="Download" @click="exportSvg">SVG</el-button>
        <el-button :icon="Picture" @click="exportPng">PNG</el-button>
      </div>
    </div>

    <div v-if="editable" class="module-editor-pane">
      <el-input
        :model-value="activeOutlineText"
        type="textarea"
        :rows="12"
        spellcheck="false"
        @input="updateOutlineText"
      />
      <div v-if="outlineWarnings.length" class="module-warning-list">
        <el-tag
          v-for="warning in outlineWarnings"
          :key="`${warning.code}-${warning.line}`"
          type="warning"
          effect="plain"
        >
          第 {{ warning.line || 1 }} 行：{{ warning.message }}
        </el-tag>
      </div>
    </div>

    <el-empty v-if="!graph.nodes.length" description="生成系统模块设计后可查看系统功能模块图" />
    <div v-else class="module-scroll">
      <svg ref="svgRef" class="module-canvas" :viewBox="`0 0 ${canvas.width} ${canvas.height}`" :style="canvasStyle">
        <g class="edge-layer">
          <path
            v-for="edge in graph.edges"
            :key="edge.id"
            class="tree-edge"
            :d="edge.path"
            fill="none"
            stroke="#222222"
            stroke-width="1.2"
            vector-effect="non-scaling-stroke"
          />
        </g>

        <g class="node-layer">
          <g
            v-for="node in graph.nodes"
            :key="node.id"
            class="module-node"
            :class="[node.kind, node.lane]"
            :transform="`translate(${node.x}, ${node.y})`"
          >
            <rect class="module-node-box" :width="node.width" :height="node.height" fill="#ffffff" stroke="#222222" stroke-width="1" />
            <text
              v-if="node.vertical"
              class="module-label"
              :x="node.width / 2"
              :y="verticalTextY(node)"
              :font-size="node.fontSize || 16"
            >
              <tspan
                v-for="(char, charIndex) in verticalChars(node.title)"
                :key="`${node.id}-${charIndex}`"
                :x="node.width / 2"
                :dy="charIndex === 0 ? 0 : 18"
              >
                {{ char }}
              </tspan>
            </text>
            <text
              v-else
              class="node-title"
              :x="node.width / 2"
              :y="node.height / 2"
              :font-size="node.fontSize || (node.kind === 'root' ? 17 : 16)"
            >
              {{ shortText(node.title, 18) }}
            </text>
          </g>
        </g>

        <text class="figure-caption" :x="canvas.width / 2" :y="canvas.height - 24">{{ graph.caption }}</text>
      </svg>
    </div>
  </div>
</template>

<script setup>
import { computed, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Download, Picture } from '@element-plus/icons-vue'
import { buildAppModuleExportSvg } from '@/utils/appModuleExport'
import { buildAppModuleGraph } from '@/utils/appModuleGraph'
import {
  blueprintToModuleOutline,
  moduleOutlineToText,
  parseModuleOutlineText
} from '@/utils/appModuleOutline'
import { saveBlob } from '@/utils/download'

const DEFAULT_LAYOUT_OPTIONS = {
  density: 'standard',
  direction: 'horizontal',
  fontSize: 'standard'
}

const props = defineProps({
  blueprint: {
    type: Object,
    default: () => ({ roles: [], frontendMenus: [], adminMenus: [] })
  },
  projectName: {
    type: String,
    default: ''
  },
  sourceMode: {
    type: String,
    default: 'blueprint'
  },
  outlineText: {
    type: String,
    default: ''
  },
  layoutOptions: {
    type: Object,
    default: () => ({ density: 'standard', direction: 'horizontal', fontSize: 'standard' })
  },
  editable: {
    type: Boolean,
    default: false
  }
})

const emit = defineEmits(['update:sourceMode', 'update:outlineText', 'update:layoutOptions', 'graph-change'])

const svgRef = ref(null)
const normalizedLayoutOptions = computed(() => ({
  density: props.layoutOptions?.density || DEFAULT_LAYOUT_OPTIONS.density,
  direction: props.layoutOptions?.direction || DEFAULT_LAYOUT_OPTIONS.direction,
  fontSize: props.layoutOptions?.fontSize || DEFAULT_LAYOUT_OPTIONS.fontSize
}))
const blueprintOutline = computed(() => blueprintToModuleOutline(props.blueprint, { projectName: props.projectName }))
const blueprintOutlineText = computed(() => moduleOutlineToText(blueprintOutline.value))
const activeOutlineText = computed(() => props.sourceMode === 'outline' ? props.outlineText : blueprintOutlineText.value)
const parsedOutline = computed(() => parseModuleOutlineText(activeOutlineText.value))
const outlineWarnings = computed(() => parsedOutline.value.warnings || [])
const graph = computed(() => buildAppModuleGraph(parsedOutline.value.outline, normalizedLayoutOptions.value))

const canvas = computed(() => graph.value.canvas || { width: 760, height: 420 })

const canvasStyle = computed(() => ({
  width: `${canvas.value.width}px`,
  height: `${canvas.value.height}px`
}))

watch(graph, (value) => {
  emit('graph-change', value)
}, { immediate: true })

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

function verticalChars(value) {
  const chars = Array.from(String(value ?? '').trim())
  if (chars.length <= 7) return chars
  return [...chars.slice(0, 6), '…']
}

function verticalTextY(node) {
  const count = verticalChars(node.title).length
  return Math.max(22, (node.height - (count - 1) * 18) / 2 + 5)
}

function setSourceMode(value) {
  const nextMode = value === 'outline' ? 'outline' : 'blueprint'
  emit('update:sourceMode', nextMode)
  if (nextMode === 'outline' && !props.outlineText) {
    emit('update:outlineText', blueprintOutlineText.value)
  }
}

function updateOutlineText(value) {
  if (props.sourceMode !== 'outline') {
    emit('update:sourceMode', 'outline')
  }
  emit('update:outlineText', value)
}

function updateLayoutOption(key, value) {
  emit('update:layoutOptions', {
    ...normalizedLayoutOptions.value,
    [key]: value
  })
}

function exportSvg() {
  const svgText = buildAppModuleExportSvg(graph.value)
  saveBlob(new Blob([svgText], { type: 'image/svg+xml;charset=utf-8' }), 'system-module-diagram.svg')
}

async function exportPng() {
  if (!svgRef.value) return

  const svgText = buildAppModuleExportSvg(graph.value)
  const svgBlob = new Blob([svgText], { type: 'image/svg+xml;charset=utf-8' })
  const url = URL.createObjectURL(svgBlob)
  const image = new Image()
  image.decoding = 'async'

  try {
    await new Promise((resolve, reject) => {
      image.onload = resolve
      image.onerror = reject
      image.src = url
    })
    const canvasElement = document.createElement('canvas')
    canvasElement.width = canvas.value.width
    canvasElement.height = canvas.value.height
    const context = canvasElement.getContext('2d')
    if (!context) throw new Error('Canvas context unavailable')
    context.fillStyle = '#ffffff'
    context.fillRect(0, 0, canvasElement.width, canvasElement.height)
    context.drawImage(image, 0, 0)
    const blob = await new Promise((resolve) => canvasElement.toBlob(resolve, 'image/png'))
    if (blob) {
      saveBlob(blob, 'system-module-diagram.png')
    }
  } catch (error) {
    ElMessage.warning('导出图片失败，请稍后重试')
  } finally {
    URL.revokeObjectURL(url)
  }
}
</script>

<style scoped lang="scss">
.app-module-diagram {
  background: #ffffff;
  min-height: 420px;
}

.module-toolbar {
  align-items: center;
  background: #ffffff;
  border-bottom: 1px solid #e5e7eb;
  display: flex;
  gap: 16px;
  justify-content: space-between;
  padding: 12px 18px;
}

.module-summary {
  color: #4b5563;
  display: flex;
  flex-wrap: wrap;
  font-size: 13px;
  gap: 12px;
}

.module-controls {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
}

.module-font-select {
  width: 92px;
}

.module-editor-pane {
  background: #fbfdff;
  border-bottom: 1px solid #e5e7eb;
  display: grid;
  gap: 10px;
  padding: 16px 18px;
}

.module-warning-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.module-scroll {
  background: #ffffff;
  min-height: 420px;
  overflow: auto;
  padding: 24px;
}

.module-canvas {
  background: #ffffff;
  display: block;
  font-family: SimSun, "宋体", "Microsoft YaHei", Arial, sans-serif;
}

.tree-edge {
  fill: none;
  shape-rendering: crispEdges;
  stroke: #222222;
  stroke-width: 1.2;
}

.module-node-box {
  fill: #ffffff;
  shape-rendering: crispEdges;
  stroke: #222222;
  stroke-width: 1;
}

.node-title {
  dominant-baseline: middle;
  fill: #111111;
  font-size: 16px;
  font-weight: 600;
  text-anchor: middle;
}

.module-node.root .node-title {
  font-size: 17px;
}

.module-label {
  fill: #111111;
  font-size: 16px;
  font-weight: 600;
  text-anchor: middle;
}

.figure-caption {
  fill: #111111;
  font-size: 14px;
  text-anchor: middle;
}

@media (max-width: 860px) {
  .module-toolbar {
    align-items: flex-start;
    flex-direction: column;
  }

  .module-controls {
    justify-content: flex-start;
  }
}
</style>
