function cleanId(value, fallback) {
  const raw = String(value || '').trim()
  const safe = raw
    .replace(/[^\w\u4e00-\u9fa5-]+/g, '_')
    .replace(/^_+|_+$/g, '')
  return safe || fallback
}

function asArray(value) {
  return Array.isArray(value) ? value : []
}

function firstJsonObject(text) {
  const s = String(text || '').trim()
  const fenced = s.match(/```(?:json)?\s*([\s\S]*?)```/i)
  const candidate = fenced ? fenced[1].trim() : s
  try { return JSON.parse(candidate) } catch {}

  const start = candidate.indexOf('{')
  if (start < 0) throw new Error('大模型没有返回 JSON 对象')

  let depth = 0
  let inString = false
  let escaped = false
  for (let i = start; i < candidate.length; i++) {
    const ch = candidate[i]
    if (inString) {
      if (escaped) {
        escaped = false
      } else if (ch === '\\') {
        escaped = true
      } else if (ch === '"') {
        inString = false
      }
      continue
    }
    if (ch === '"') {
      inString = true
    } else if (ch === '{') {
      depth += 1
    } else if (ch === '}') {
      depth -= 1
      if (depth === 0) return JSON.parse(candidate.slice(start, i + 1))
    }
  }

  throw new Error('大模型返回的 JSON 不完整')
}

export function parseFlowchartJson(text) {
  const parsed = firstJsonObject(text)
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error('流程图结果必须是 JSON 对象')
  }
  return parsed
}

export function normalizeFlowchartSpec(spec) {
  const sourceNodes = asArray(spec?.nodes)
  const lanesByActor = new Map()

  const nodes = sourceNodes.map((node, index) => {
    const id = cleanId(node?.id, `node_${index + 1}`)
    const actor = String(node?.actor || node?.owner || node?.lane || '未指定').trim() || '未指定'
    if (!lanesByActor.has(actor)) {
      lanesByActor.set(actor, {
        id: cleanId(`lane_${actor}`, `lane_${lanesByActor.size + 1}`),
        name: actor,
      })
    }
    const lane = lanesByActor.get(actor)
    return {
      id,
      label: String(node?.label || node?.name || `活动 ${index + 1}`).trim(),
      type: String(node?.type || 'activity').trim(),
      actor,
      laneId: lane.id,
      context: String(node?.context || node?.description || '').trim(),
      inputs: asArray(node?.inputs).map(String),
      outputs: asArray(node?.outputs).map(String),
      x: Number.isFinite(Number(node?.x)) ? Number(node.x) : 8 + index * 18,
      y: Number.isFinite(Number(node?.y)) ? Number(node.y) : 18 + (lanesByActor.size - 1) * 18,
    }
  }).map(node => ({
    ...node,
    x: Math.max(2, Math.min(86, node.x)),
    y: Math.max(8, Math.min(82, node.y)),
  }))

  const nodeIds = new Set(nodes.map(n => n.id))
  const edges = asArray(spec?.edges || spec?.links).map((edge, index) => {
    const source = cleanId(edge?.source || edge?.from, '')
    const target = cleanId(edge?.target || edge?.to, '')
    if (!nodeIds.has(source) || !nodeIds.has(target) || source === target) return null
    return {
      id: cleanId(edge?.id, `edge_${index + 1}`),
      source,
      target,
      label: String(edge?.label || edge?.condition || '').trim(),
    }
  }).filter(Boolean)

  return {
    title: String(spec?.title || 'AI 生成流程图').trim(),
    summary: String(spec?.summary || spec?.description || '').trim(),
    lanes: asArray(spec?.lanes).length
      ? asArray(spec.lanes).map((lane, index) => ({
          id: cleanId(lane?.id, `lane_${index + 1}`),
          name: String(lane?.name || lane?.label || `泳道 ${index + 1}`).trim(),
        }))
      : Array.from(lanesByActor.values()),
    nodes,
    edges,
    notes: asArray(spec?.notes).map(String),
  }
}
