const MIN_CANVAS_WIDTH = 980
const MIN_CANVAS_HEIGHT = 560
const ACTOR_X = 96
const PRIMARY_X = 360
const CHILD_X = 690
const TOP_Y = 76
const PRIMARY_GAP_Y = 118
const CHILD_GAP_Y = 58
const OVAL_WIDTH = 172
const OVAL_HEIGHT = 48

export const USE_CASE_EXAMPLE_DSL = `用户
浏览商品
  搜索商品
  查看商品详情
  筛选商品
加入购物车
  选择规格
  修改数量
直接购买
  选择规格
提交订单
  选择收货地址
  选择优惠券
支付订单
  选择支付方式
  输入支付密码
确认收货
  查看物流
评价商品
  上传图片
  打分`

function cleanText(value, fallback = '') {
  const text = String(value ?? '').trim()
  return text || fallback
}

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

function countIndent(line) {
  const match = String(line ?? '').match(/^[\t ]*/u)
  return match ? match[0].replace(/\t/gu, '  ').length : 0
}

function makeUseCaseId(index) {
  return `use_case_${index + 1}`
}

function menuTitle(menu) {
  return cleanText(menu?.name || menu?.title || menu?.code)
}

function uniqueItems(items, max = 8) {
  const seen = new Set()
  return items
    .map((item) => cleanText(item))
    .filter((item) => {
      if (!item || seen.has(item)) return false
      seen.add(item)
      return true
    })
    .slice(0, max)
}

export function buildDefaultUseCaseDsl(project = {}) {
  const blueprint = parseJson(project.appBlueprint, {})
  const frontendMenus = uniqueItems((Array.isArray(blueprint.frontendMenus) ? blueprint.frontendMenus : []).map(menuTitle), 5)
  const adminMenus = uniqueItems((Array.isArray(blueprint.adminMenus) ? blueprint.adminMenus : []).map(menuTitle), 5)

  if (!frontendMenus.length && !adminMenus.length) {
    return USE_CASE_EXAMPLE_DSL
  }

  const lines = ['用户']
  frontendMenus.forEach((name) => {
    lines.push(name)
    lines.push(`  查看${name}`)
    lines.push(`  提交${name}`)
  })
  if (adminMenus.length) {
    lines.push('后台管理')
    adminMenus.forEach((name) => {
      lines.push(`  ${name}`)
    })
  }
  return lines.join('\n')
}

export function parseUseCaseDsl(dsl = '') {
  const actors = [{ id: 'actor_1', name: '用户' }]
  const useCases = []
  const primaryUseCases = []
  const relations = []
  const warnings = []
  const lines = String(dsl || '').split(/\r?\n/u)
  let actorSet = false
  let currentPrimary = null

  lines.forEach((rawLine, index) => {
    if (!cleanText(rawLine)) return
    const lineNumber = index + 1
    const indent = countIndent(rawLine)
    const text = cleanText(rawLine)
    const actorMatch = text.match(/^角色[:：]\s*(.+)$/u)
    const arrowMatch = text.match(/^(.+?)\s*->\s*(.+)$/u)

    if (actorMatch && indent === 0) {
      actors[0].name = cleanText(actorMatch[1], '用户')
      actorSet = true
      return
    }

    if (!actorSet && indent === 0 && !arrowMatch && useCases.length === 0) {
      actors[0].name = text
      actorSet = true
      return
    }

    const rawName = arrowMatch ? cleanText(arrowMatch[2]) : text
    const optional = rawName.endsWith('?')
    const name = optional ? cleanText(rawName.slice(0, -1)) : rawName

    if (!actorSet && indent > 0) {
      warnings.push(`第 ${lineNumber} 行缺少参与者，已使用默认参与者“用户”`)
      actorSet = true
    }

    const useCase = {
      id: makeUseCaseId(useCases.length),
      name,
      optional,
      lineNumber,
      parentId: '',
      kind: indent >= 2 && currentPrimary ? 'child' : 'primary'
    }
    useCases.push(useCase)

    if (useCase.kind === 'primary') {
      currentPrimary = useCase
      primaryUseCases.push(useCase)
      relations.push({
        id: `relation_${relations.length + 1}`,
        type: 'association',
        source: actors[0].id,
        target: useCase.id,
        label: ''
      })
      return
    }

    useCase.parentId = currentPrimary.id
    relations.push({
      id: `relation_${relations.length + 1}`,
      type: optional ? 'extend' : 'include',
      source: optional ? useCase.id : currentPrimary.id,
      target: optional ? currentPrimary.id : useCase.id,
      label: optional ? '<<extend>>' : '<<include>>'
    })
  })

  if (!useCases.length) {
    warnings.push('请至少添加一个用例')
  }
  if (!actorSet) {
    warnings.push('缺少参与者，已使用默认参与者“用户”')
  }

  return {
    actors,
    useCases,
    primaryUseCases,
    relations,
    warnings,
    stats: {
      actorCount: actors.length,
      useCaseCount: useCases.length,
      primaryUseCaseCount: primaryUseCases.length,
      relationCount: relations.length
    }
  }
}

function shortText(value, max = 14) {
  const chars = Array.from(cleanText(value))
  return chars.length > max ? `${chars.slice(0, max - 1).join('')}...` : chars.join('')
}

function primaryHeight(children) {
  return Math.max(PRIMARY_GAP_Y, Math.max(1, children.length) * CHILD_GAP_Y + 24)
}

function useCaseLeft(item) {
  return { x: item.x, y: item.y + item.height / 2 }
}

function useCaseRight(item) {
  return { x: item.x + item.width, y: item.y + item.height / 2 }
}

function actorAnchor(actor) {
  return { x: actor.x + actor.width, y: actor.y + 44 }
}

function relationPath(relation, useCaseMap, actorMap) {
  const sourceUseCase = useCaseMap.get(relation.source)
  const targetUseCase = useCaseMap.get(relation.target)
  const sourceActor = actorMap.get(relation.source)

  if (sourceActor && targetUseCase) {
    const source = actorAnchor(sourceActor)
    const target = useCaseLeft(targetUseCase)
    return {
      path: `M ${source.x} ${source.y} L ${target.x} ${target.y}`,
      labelX: (source.x + target.x) / 2,
      labelY: (source.y + target.y) / 2 - 8
    }
  }

  if (sourceUseCase && targetUseCase) {
    const source = relation.type === 'extend' ? useCaseLeft(sourceUseCase) : useCaseRight(sourceUseCase)
    const target = relation.type === 'extend' ? useCaseRight(targetUseCase) : useCaseLeft(targetUseCase)
    const midX = (source.x + target.x) / 2
    return {
      path: `M ${source.x} ${source.y} L ${midX} ${source.y} L ${midX} ${target.y} L ${target.x} ${target.y}`,
      labelX: midX,
      labelY: (source.y + target.y) / 2 - 8
    }
  }

  return { path: '', labelX: 0, labelY: 0 }
}

export function buildUseCaseDiagram(parsedUseCase, options = {}) {
  const parsed = typeof parsedUseCase === 'string' ? parseUseCaseDsl(parsedUseCase) : parsedUseCase
  const sourceActors = Array.isArray(parsed?.actors) ? parsed.actors : [{ id: 'actor_1', name: '用户' }]
  const sourceUseCases = Array.isArray(parsed?.useCases) ? parsed.useCases : []
  const sourcePrimary = Array.isArray(parsed?.primaryUseCases)
    ? parsed.primaryUseCases
    : sourceUseCases.filter((item) => item.kind !== 'child')
  const childrenByParent = new Map()

  sourceUseCases.filter((item) => item.kind === 'child').forEach((item) => {
    if (!childrenByParent.has(item.parentId)) childrenByParent.set(item.parentId, [])
    childrenByParent.get(item.parentId).push(item)
  })

  let cursorY = TOP_Y
  const positioned = []
  sourcePrimary.forEach((primary) => {
    const children = childrenByParent.get(primary.id) || []
    const blockHeight = primaryHeight(children)
    const primaryY = cursorY + blockHeight / 2 - OVAL_HEIGHT / 2
    positioned.push({
      ...primary,
      x: PRIMARY_X,
      y: primaryY,
      width: OVAL_WIDTH,
      height: OVAL_HEIGHT,
      text: shortText(primary.name),
      kind: 'primary'
    })
    children.forEach((child, childIndex) => {
      const childStart = cursorY + blockHeight / 2 - ((children.length - 1) * CHILD_GAP_Y) / 2
      positioned.push({
        ...child,
        x: CHILD_X,
        y: childStart + childIndex * CHILD_GAP_Y - OVAL_HEIGHT / 2,
        width: OVAL_WIDTH,
        height: OVAL_HEIGHT,
        text: shortText(child.name),
        kind: 'child'
      })
    })
    cursorY += blockHeight
  })

  const canvasHeight = Math.max(MIN_CANVAS_HEIGHT, cursorY + 72)
  const actors = sourceActors.map((actor) => ({
    ...actor,
    x: ACTOR_X,
    y: Math.max(TOP_Y + 120, canvasHeight / 2 - 48),
    width: 82,
    height: 98
  }))
  const useCaseMap = new Map(positioned.map((item) => [item.id, item]))
  const actorMap = new Map(actors.map((item) => [item.id, item]))
  const relations = (Array.isArray(parsed?.relations) ? parsed.relations : [])
    .map((relation) => ({
      ...relation,
      ...relationPath(relation, useCaseMap, actorMap)
    }))
    .filter((relation) => relation.path)

  return {
    title: cleanText(options.title, '用例图'),
    actors,
    useCases: positioned,
    relations,
    warnings: Array.isArray(parsed?.warnings) ? parsed.warnings : [],
    stats: {
      actorCount: actors.length,
      useCaseCount: positioned.length,
      primaryUseCaseCount: sourcePrimary.length,
      relationCount: relations.length
    },
    canvas: {
      width: MIN_CANVAS_WIDTH,
      height: canvasHeight
    }
  }
}

function escapeSvg(value) {
  return String(value ?? '')
    .replace(/&/gu, '&amp;')
    .replace(/</gu, '&lt;')
    .replace(/>/gu, '&gt;')
    .replace(/"/gu, '&quot;')
    .replace(/'/gu, '&apos;')
}

function renderRelationSvg(relation) {
  const marker = relation.type === 'association' ? '' : ' marker-end="url(#use-case-arrow)"'
  const dash = relation.type === 'association' ? '' : ' stroke-dasharray="5 4"'
  const label = relation.label
    ? `<text x="${relation.labelX}" y="${relation.labelY}" text-anchor="middle" font-size="11" fill="#111111" paint-order="stroke" stroke="#ffffff" stroke-width="4">${escapeSvg(relation.label)}</text>`
    : ''
  return `<path d="${relation.path}" fill="none" stroke="#222222" stroke-width="1.3"${dash}${marker}/>${label}`
}

function actorPath(actor) {
  const cx = actor.x + actor.width / 2
  const headBottom = actor.y + 23
  const bodyBottom = actor.y + 56
  return `M ${cx} ${headBottom} L ${cx} ${bodyBottom} M ${cx - 24} ${actor.y + 36} L ${cx + 24} ${actor.y + 36} M ${cx} ${bodyBottom} L ${cx - 22} ${actor.y + 82} M ${cx} ${bodyBottom} L ${cx + 22} ${actor.y + 82}`
}

function renderActorSvg(actor) {
  const cx = actor.x + actor.width / 2
  return `<g>
    <circle cx="${cx}" cy="${actor.y + 12}" r="10" fill="#ffffff" stroke="#222222" stroke-width="1.4"/>
    <path d="${actorPath(actor)}" fill="none" stroke="#222222" stroke-width="1.4"/>
    <text x="${cx}" y="${actor.y + actor.height}" text-anchor="middle" dominant-baseline="middle" font-size="13" fill="#111111">${escapeSvg(actor.name)}</text>
  </g>`
}

function renderUseCaseSvg(item) {
  const cx = item.x + item.width / 2
  const cy = item.y + item.height / 2
  return `<g><desc>${escapeSvg(item.name)}</desc>
    <ellipse cx="${cx}" cy="${cy}" rx="${item.width / 2}" ry="${item.height / 2}" fill="#ffffff" stroke="#222222" stroke-width="1.4"/>
    <text x="${cx}" y="${cy}" text-anchor="middle" dominant-baseline="middle" font-size="13" fill="#111111">${escapeSvg(item.text)}</text>
  </g>`
}

export function buildUseCaseExportSvg(diagram = {}) {
  const width = Math.max(1, Number(diagram.canvas?.width) || MIN_CANVAS_WIDTH)
  const height = Math.max(1, Number(diagram.canvas?.height) || MIN_CANVAS_HEIGHT)
  const actors = Array.isArray(diagram.actors) ? diagram.actors : []
  const useCases = Array.isArray(diagram.useCases) ? diagram.useCases : []
  const relations = Array.isArray(diagram.relations) ? diagram.relations : []
  const title = escapeSvg(cleanText(diagram.title, '用例图'))

  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" font-family="Microsoft YaHei, Arial, sans-serif">
  <defs>
    <marker id="use-case-arrow" 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 x="${width / 2}" y="32" text-anchor="middle" font-size="18" font-weight="700" fill="#111111">${title}</text>
  ${relations.map(renderRelationSvg).join('\n  ')}
  ${actors.map(renderActorSvg).join('\n  ')}
  ${useCases.map(renderUseCaseSvg).join('\n  ')}
</svg>`
}
