<template>
  <section class="risk-rule-flow-figure" aria-label="风险规则流程说明" :style="accentStyle">
    <div class="risk-rule-flow-content">
      <aside class="risk-rule-flow-explainer">
        <div class="risk-rule-section-title risk-rule-flow-copy-head">
          <strong>流程解释</strong>
          <span>{{ flowModel.severityLabel }}</span>
        </div>

        <ol class="risk-rule-flow-steps" aria-label="文字流程说明">
          <li v-for="(item, index) in flowSteps" :key="item.title">
            <span class="risk-rule-step-index">{{ index + 1 }}</span>
            <div>
              <strong>{{ item.title }}</strong>
              <p>{{ item.text }}</p>
              <div v-if="item.fields?.length" class="risk-rule-field-list">
                <span v-for="field in item.fields" :key="field">{{ field }}</span>
              </div>
            </div>
          </li>
        </ol>

        <div class="risk-rule-flow-branches" aria-label="是或否分支">
          <div v-for="item in flowBranches" :key="item.answer" class="risk-rule-flow-branch">
            <span class="risk-rule-branch-answer" :class="`answer-${item.tone}`">
              {{ item.answer }}
            </span>
            <div>
              <strong>{{ item.title }}</strong>
              <p>{{ item.text }}</p>
            </div>
          </div>
        </div>
      </aside>

      <div class="risk-rule-flow-visual">
        <div class="risk-rule-section-title risk-rule-flow-visual-title">
          <strong>流程图</strong>
        </div>
        <img
          v-if="src"
          class="risk-rule-flow-image"
          :src="src"
          alt="风险规则流程说明"
          draggable="false"
        />
        <div
          v-else
          class="risk-rule-flow-svg-viewport"
        >
          <div
            class="risk-rule-flow-svg-canvas"
            v-html="displaySvg"
          ></div>
        </div>
      </div>
    </div>
  </section>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({
  svg: { type: String, default: '' },
  src: { type: String, default: '' },
  flow: { type: Object, default: () => ({}) },
  fields: { type: Array, default: () => [] },
  severity: { type: String, default: 'medium' },
  severityLabel: { type: String, default: '中风险' }
})

const FONT = "-apple-system, BlinkMacSystemFont, 'SF Pro Text', 'PingFang SC', 'Segoe UI', Arial, sans-serif"
const TEXT = '#0d0d0d'
const MUTED = '#6e6e80'
const NEUTRAL_LINE = '#cbd5e1'
const NEUTRAL_BORDER = '#e2e8f0'
const PALETTES = {
  low: {
    accent: '#2563eb',
    accentDark: '#1d4ed8',
    border: '#bfdbfe',
    surface: '#eff6ff'
  },
  medium: {
    accent: '#f97316',
    accentDark: '#c2410c',
    border: '#fed7aa',
    surface: '#fff7ed'
  },
  high: {
    accent: '#dc2626',
    accentDark: '#b91c1c',
    border: '#fecaca',
    surface: '#fef2f2'
  },
  critical: {
    accent: '#991b1b',
    accentDark: '#7f1d1d',
    border: '#fca5a5',
    surface: '#fff1f2'
  }
}

const DRAWIO_PALETTES = {
  neutral: { fill: '#ffffff', stroke: '#e2e8f0' },
  blue: { fill: '#ffffff', stroke: '#e2e8f0' },
  yellow: { fill: '#ffffff', stroke: '#e2e8f0' },
  green: { fill: '#ffffff', stroke: '#e2e8f0' },
  low: { fill: '#eff6ff', stroke: '#bfdbfe' },
  medium: { fill: '#fff7ed', stroke: '#fed7aa' },
  high: { fill: '#fef2f2', stroke: '#fecaca' },
  critical: { fill: '#fff1f2', stroke: '#fca5a5' }
}

function normalizeText(value, fallback = '') {
  return String(value || fallback || '').trim()
}

function escapeSvg(value) {
  return normalizeText(value)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
}

function isSafeSvg(value) {
  const text = normalizeText(value)
  if (!text.startsWith('<svg') || !text.endsWith('</svg>')) {
    return false
  }
  return !/(<script|<foreignObject|<iframe|<object|<embed|\son\w+=|javascript:)/i.test(text)
}

function isCurrentDisplaySvg(value) {
  return (
    isSafeSvg(value) &&
    value.includes('data-risk-flow-style="review-node-only"') &&
    value.includes('data-risk-flow-detail="logic-v2"')
  )
}

function resolvePalette(severity) {
  return PALETTES[normalizeText(severity).toLowerCase()] || PALETTES.medium
}

function formatFieldDisplay(item) {
  const key = normalizeText(item?.key)
  const label = normalizeText(item?.label || key)
  if (label && key && label !== key) {
    return `${label}[${key}]`
  }
  return label || key
}

function wrapText(value, width, maxLines) {
  const text = normalizeText(value)
  if (!text) {
    return ['']
  }
  const lines = []
  for (let index = 0; index < text.length; index += width) {
    lines.push(text.slice(index, index + width))
  }
  if (lines.length > maxLines) {
    return [...lines.slice(0, maxLines - 1), `${lines[maxLines - 1].slice(0, width - 1)}…`]
  }
  return lines
}

function textLines(lines, x, y, anchor = 'middle', color = MUTED, fontSize = 13) {
  return lines
    .map(
      (line, index) =>
        `<text x="${x}" y="${y + index * (fontSize + 5)}" text-anchor="${anchor}" fill="${color}" font-family="${FONT}" font-size="${fontSize}" font-weight="400">${escapeSvg(line)}</text>`
    )
    .join('')
}

function truncateText(value, length) {
  const text = normalizeText(value)
  return text.length <= length ? text : `${text.slice(0, Math.max(0, length - 1))}…`
}

function node(title, body, x, y, width, height, type = 'blue') {
  const palette = DRAWIO_PALETTES[type] || DRAWIO_PALETTES.blue
  return `<g class="drawio-node">
    <rect x="${x}" y="${y}" width="${width}" height="${height}" rx="6" ry="6" fill="${palette.fill}" stroke="${palette.stroke}" stroke-width="1.2"/>
    <text x="${x + width / 2}" y="${y + 24}" text-anchor="middle" fill="#0f172a" font-family="${FONT}" font-size="13" font-weight="600">${escapeSvg(title)}</text>
    ${textLines(wrapText(body, width <= 126 ? 10 : 11, 1), x + width / 2, y + 43, 'middle', '#475569', 11)}
  </g>`
}

function diamond(title, body, x, y, width, height) {
  const cx = x + width / 2
  const cy = y + height / 2
  const points = `${cx},${y} ${x + width},${cy} ${cx},${y + height} ${x},${cy}`
  const palette = DRAWIO_PALETTES.yellow
  return `<g class="drawio-node">
    <polygon points="${points}" fill="${palette.fill}" stroke="${palette.stroke}" stroke-width="1.2"/>
    <text x="${cx}" y="${cy - 8}" text-anchor="middle" fill="#0f172a" font-family="${FONT}" font-size="12.5" font-weight="600">${escapeSvg(title)}</text>
    ${textLines(wrapText(body, 8, 2), cx, cy + 12, 'middle', '#475569', 10.2)}
  </g>`
}

function note(body) {
  return `<g class="drawio-node note">
    <rect x="214" y="218" width="290" height="36" rx="3" ry="3" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.2" stroke-dasharray="3,3" filter="url(#shadow)"/>
    <text x="226" y="240" fill="#64748b" font-family="${FONT}" font-size="10.5" font-weight="700">BASIS</text>
    ${textLines(wrapText(body, 22, 1), 272, 240, 'start', '#334155', 10.5)}
  </g>`
}

function panel(title, rows, x, y, width, height) {
  const visibleRows = (Array.isArray(rows) ? rows : [])
    .filter(Boolean)
    .slice(0, 4)
    .map((row) => truncateText(row, 34))
  const renderedRows = visibleRows.length ? visibleRows : ['读取规则字段并归一化为判断事实']
  return `<g class="drawio-node panel-node">
    <rect x="${x}" y="${y}" width="${width}" height="${height}" rx="6" ry="6" fill="#ffffff" stroke="#e2e8f0" stroke-width="1.2"/>
    <text x="${x + 16}" y="${y + 26}" fill="#0f172a" font-family="${FONT}" font-size="13" font-weight="700">${escapeSvg(title)}</text>
    ${renderedRows
      .map(
        (row, index) =>
          `<text x="${x + 16}" y="${y + 48 + index * 18}" fill="#334155" font-family="${FONT}" font-size="11" font-weight="400">${escapeSvg(row)}</text>`
      )
      .join('')}
  </g>`
}

const palette = computed(() => resolvePalette(props.severity))

const accentStyle = computed(() => ({
  '--risk-flow-accent': palette.value.accent,
  '--risk-flow-accent-dark': palette.value.accentDark,
  '--risk-flow-border': palette.value.border,
  '--risk-flow-surface': palette.value.surface
}))

const fieldDisplays = computed(() =>
  (Array.isArray(props.fields) ? props.fields : []).map(formatFieldDisplay).filter(Boolean)
)

const fieldSummary = computed(() => {
  const fields = fieldDisplays.value
  if (!fields.length) {
    return '规则字段'
  }
  if (fields.length <= 4) {
    return fields.join('、')
  }
  return `${fields.slice(0, 4).join('、')} 等 ${fields.length} 项字段`
})

const flowModel = computed(() => {
  const severityLabel = normalizeText(props.severityLabel, '中风险')
  return {
    severityLabel,
    start: normalizeText(props.flow?.start, '业务单据提交'),
    evidence: normalizeText(props.flow?.evidence, '读取规则字段'),
    decision: normalizeText(props.flow?.decision, '判断是否命中风险'),
    basis: normalizeText(props.flow?.basis || props.flow?.decision, '根据规则字段判断是否命中风险'),
    facts: Array.isArray(props.flow?.facts) ? props.flow.facts.map(normalizeText).filter(Boolean) : [],
    conditions: Array.isArray(props.flow?.conditions)
      ? props.flow.conditions.map(normalizeText).filter(Boolean)
      : [],
    hitLogic: normalizeText(props.flow?.hitLogic || props.flow?.formula),
    pass: normalizeText(props.flow?.pass, '未命中风险，继续流转'),
    fail: normalizeText(props.flow?.fail, `命中${severityLabel}，进入人工复核`)
  }
})

const flowSteps = computed(() => [
  {
    title: '业务输入',
    text: flowModel.value.start
  },
  {
    title: '字段取数',
    text: `读取规则所需字段，并将字段证据送入判断节点。字段：${fieldSummary.value}`,
    fields: flowModel.value.facts.length ? flowModel.value.facts : fieldDisplays.value
  },
  {
    title: '判断依据',
    text: flowModel.value.basis || flowModel.value.decision,
    fields: flowModel.value.conditions
  }
])

const flowBranches = computed(() => [
  {
    answer: '否',
    tone: 'pass',
    title: '不命中风险',
    text: flowModel.value.pass
  },
  {
    answer: '是',
    tone: 'risk',
    title: `命中${flowModel.value.severityLabel}`,
    text: flowModel.value.fail
  }
])

const displaySvg = computed(() => {
  const providedSvg = normalizeText(props.svg)
  if (isCurrentDisplaySvg(providedSvg)) {
    return providedSvg
  }

  const flow = flowModel.value
  const severity = props.severity
  const facts = flow.facts.length ? flow.facts : fieldDisplays.value.slice(0, 4)
  const conditions = flow.conditions.length ? flow.conditions : [flow.basis || flow.decision]
  const hitLogic = flow.hitLogic || flow.basis || flow.decision

  return `<svg xmlns="http://www.w3.org/2000/svg" width="860" height="360" viewBox="0 0 860 360" data-risk-flow-style="review-node-only" data-risk-flow-detail="logic-v2" role="img" aria-label="风险规则流程说明">
  <defs>
    <marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
      <path d="M 0 0 L 8 3 L 0 6 Z" fill="#cbd5e1"/>
    </marker>
    <marker id="arrow-risk" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
      <path d="M 0 0 L 8 3 L 0 6 Z" fill="${palette.value.accent}"/>
    </marker>
  </defs>
  <rect width="860" height="360" fill="#ffffff"/>
  <rect x="18" y="18" width="824" height="324" rx="8" fill="none" stroke="#e2e8f0" stroke-width="1" stroke-dasharray="4,3"/>
  
  <text x="34" y="43" fill="#94a3b8" font-family="${FONT}" font-size="10.5" font-weight="700">RULE FLOW</text>
  
  ${node('业务输入', flow.start, 38, 142, 120, 62, 'neutral')}
  ${panel('字段事实', facts, 196, 64, 240, 128)}
  ${panel('判断条件', conditions, 196, 216, 382, 104)}
  ${diamond('命中逻辑', hitLogic, 494, 80, 122, 122)}
  ${node('继续流转', flow.pass, 688, 76, 122, 60, 'neutral')}
  ${node('进入复核', flow.fail, 688, 226, 122, 68, severity)}
  
  <path d="M 158 173 H 176 V 128 H 196" fill="none" stroke="#cbd5e1" stroke-width="1.45" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow)"/>
  <line x1="316" y1="192" x2="316" y2="216" stroke="#cbd5e1" stroke-width="1.45" stroke-linecap="round" marker-end="url(#arrow)"/>
  <path d="M 436 128 H 466 V 141 H 494" fill="none" stroke="#cbd5e1" stroke-width="1.45" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow)"/>
  <line x1="555" y1="216" x2="555" y2="202" stroke="#cbd5e1" stroke-width="1.35" stroke-linecap="round" marker-end="url(#arrow)"/>
  <path d="M 616 125 H 648 V 106 H 688" fill="none" stroke="#cbd5e1" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow)"/>
  <text x="651" y="119" text-anchor="middle" fill="#64748b" font-family="${FONT}" font-size="10.5" font-weight="500">否</text>
  <path d="M 616 166 H 648 V 260 H 688" fill="none" stroke="${palette.value.accent}" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow-risk)"/>
  <text x="651" y="214" text-anchor="middle" fill="${palette.value.accentDark}" font-family="${FONT}" font-size="10.5" font-weight="700">是</text>
</svg>`
})
</script>

<style scoped>
.risk-rule-flow-figure {
  width: 100%;
  min-height: 0;
  overflow: hidden;
  border: none;
  background: transparent;
  padding: 0;
  cursor: default;
}

.risk-rule-flow-content {
  width: 100%;
  display: grid;
  grid-template-columns: minmax(260px, 0.78fr) minmax(0, 1.22fr);
  gap: 16px;
  align-items: start;
}

.risk-rule-flow-explainer {
  min-width: 0;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-right: 16px;
  border-right: 1px solid #e2e8f0;
}

.risk-rule-section-title {
  min-height: 24px;
  display: flex;
  align-items: center;
  width: 100%;
  line-height: 1.4;
}

.risk-rule-flow-copy-head {
  justify-content: space-between;
  gap: 10px;
}

.risk-rule-section-title strong {
  color: #0f172a;
  font-size: 14px;
  font-weight: 850;
  line-height: 1.4;
}

.risk-rule-flow-copy-head span {
  color: var(--risk-flow-accent-dark);
  font-size: 12px;
  font-weight: 800;
}

.risk-rule-flow-steps {
  display: grid;
  gap: 11px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.risk-rule-flow-steps li,
.risk-rule-flow-branch {
  min-width: 0;
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr);
  gap: 8px;
  align-items: start;
}

.risk-rule-step-index,
.risk-rule-branch-answer {
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 800;
}

.risk-rule-step-index,
.answer-pass {
  background: #f8fafc;
  color: #64748b;
  border: 1px solid #e2e8f0;
}

.answer-risk {
  background: var(--risk-flow-surface);
  color: var(--risk-flow-accent-dark);
  border: 1px solid var(--risk-flow-border);
}

.risk-rule-flow-steps strong,
.risk-rule-flow-branch strong {
  display: block;
  color: #111827;
  font-size: 12.5px;
  font-weight: 800;
  line-height: 1.35;
}

.risk-rule-flow-explainer p {
  margin: 4px 0 0;
  color: #475569;
  font-size: 12px;
  line-height: 1.55;
  overflow-wrap: anywhere;
}

.risk-rule-field-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.risk-rule-field-list span {
  max-width: 100%;
  padding: 3px 7px;
  border: 1px solid #e2e8f0;
  border-radius: 999px;
  background: #f8fafc;
  color: #334155;
  font-size: 11px;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.risk-rule-flow-branches {
  display: grid;
  gap: 10px;
  padding-top: 12px;
  border-top: 1px solid #e2e8f0;
}

.risk-rule-flow-visual {
  min-width: 0;
  display: grid;
  gap: 8px;
  place-items: center;
  overflow: hidden;
}

.risk-rule-flow-visual-title {
  justify-self: start;
  justify-content: flex-start;
}

.risk-rule-flow-svg-viewport {
  position: relative;
  width: 100%;
  height: 360px;
  overflow: hidden;
  border-radius: 6px;
  background: #ffffff;
  border: 1px solid #e2e8f0;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
}

.risk-rule-flow-svg-canvas {
  width: 860px;
  height: 360px;
  flex-shrink: 0;
}

.risk-rule-flow-svg-canvas :deep(svg) {
  width: 100%;
  height: 100%;
  display: block;
}

.diagram-zoom-controls {
  position: absolute;
  bottom: 12px;
  right: 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(8px);
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 4px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
  z-index: 10;
}

.zoom-btn {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: #475569;
  cursor: pointer;
  transition: all 0.2s ease;
}

.zoom-btn:hover {
  background: #f1f5f9;
  color: #0f172a;
}

.zoom-btn i {
  font-size: 16px;
}

.risk-rule-flow-image {
  width: min(860px, 100%);
  display: block;
  pointer-events: none;
  user-select: none;
  height: auto;
  object-fit: contain;
}

@media (max-width: 980px) {
  .risk-rule-flow-content {
    grid-template-columns: 1fr;
  }

  .risk-rule-flow-explainer {
    padding-right: 0;
    padding-bottom: 12px;
    border-right: 0;
    border-bottom: 1px solid #e2e8f0;
  }
}

@media (max-width: 760px) {
  .risk-rule-flow-figure {
    padding: 10px;
  }
}
</style>
