/**
 * 消费 /api/skill-run/stream 的 SSE 流
 *
 * 支持的事件：
 *   { t: 'session', sessionId }      —— 会话开始时一次
 *   { t: 'delta',   v }              —— LLM 文字增量
 *   { t: 'tool_call', name, args }   —— LLM 调起工具
 *   { t: 'tool_result', name, result } —— 工具执行结果
 *   { t: 'files', sessionId, files } —— 本次会话生成的文件列表
 *   { t: 'limit_reached', message }   —— 工具调用达到上限，前端可提示继续
 *   { t: 'heartbeat', ... }          —— 长任务心跳
 *   { t: 'resume', ... }             —— 断线重连后的事件回放信息
 *   { t: 'done', ... }               —— 会话完成
 *   { t: 'error', v }                —— 错误
 *   data: [DONE]                     —— 结束
 */
export async function consumeSkillRunStream(res, handlers = {}) {
  const reader = res.body.getReader()
  const decoder = new TextDecoder()
  let buffer = ''
  let isDone = false

  try {
    while (!isDone) {
      const { value, done } = await reader.read()
      if (done) break

      buffer += decoder.decode(value, { stream: true })
      const parts = buffer.split('\n\n')
      buffer = parts.pop() || ''

      for (const part of parts) {
        const lines = part.split(/\r?\n/).map(line => line.trim()).filter(Boolean)
        if (!lines.length || lines.every(line => line.startsWith(':'))) continue
        const idLine = lines.find(line => line.startsWith('id:'))
        const eventId = idLine ? Number(idLine.slice(3).trim()) : undefined
        const dataLines = lines
          .filter(line => line.startsWith('data:'))
          .map(line => line.slice(5).trim())
        if (!dataLines.length) continue
        const payload = dataLines.join('\n').trim()
        if (payload === '[DONE]') { isDone = true; break }

        let obj
        try { obj = JSON.parse(payload) } catch { continue }
        if (!obj || typeof obj !== 'object') continue
        if (Number.isFinite(eventId) && obj.eventId == null) obj.eventId = eventId
        handlers.onEvent?.(obj)

        switch (obj.t) {
          case 'session':
            handlers.onSession?.(obj.sessionId)
            break
          case 'delta':
            if (obj.v) handlers.onDelta?.(obj.v)
            break
          case 'status':
            handlers.onStatus?.(obj)
            break
          case 'reasoning_summary':
            handlers.onReasoningSummary?.(obj)
            break
          case 'warning':
          case 'warn':
            handlers.onWarning?.(obj)
            break
          case 'artifact':
            handlers.onArtifact?.(obj)
            break
          case 'provider_session':
            handlers.onProviderSession?.(obj)
            break
          case 'tool_call':
            handlers.onToolCall?.(obj.name, obj.args)
            break
          case 'tool_result':
            handlers.onToolResult?.(obj.name, obj.result)
            break
          case 'files':
            handlers.onFiles?.(obj.sessionId, obj.files || [])
            break
          case 'limit_reached':
            handlers.onLimitReached?.(obj.message || '工具调用已达上限')
            break
          case 'heartbeat':
            handlers.onHeartbeat?.(obj)
            break
          case 'resume':
            handlers.onResume?.(obj)
            break
          case 'done':
            handlers.onDone?.(obj)
            isDone = true
            break
          case 'error':
            handlers.onError?.(obj.v || 'AI 服务错误')
            isDone = true
            break
          default:
            break
        }
        if (isDone) break
      }
    }
  } finally {
    try { reader.cancel().catch(() => {}) } catch {}
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, Math.max(0, ms || 0)))
}

export async function runSkillRunStreamWithReconnect({
  apiBase,
  body,
  authHeaders = {},
  handlers = {},
  maxReconnects = 3,
  retryDelayMs = 1000,
  signal,
} = {}) {
  if (!apiBase) throw new Error('缺少 apiBase')
  const baseBody = body && typeof body === 'object' ? body : {}
  let sessionId = baseBody.sessionId || ''
  let lastEventId = Number(baseBody.resumeFromEventId || baseBody.afterEventId || 0)
  let reconnects = 0

  while (true) {
    let streamErr = null
    let completed = false
    const requestBody = {
      ...baseBody,
      ...(sessionId ? { sessionId } : {}),
      ...(reconnects > 0 ? { resume: true, resumeFromEventId: lastEventId } : {}),
    }

    try {
      const res = await fetch(`${String(apiBase).replace(/\/$/, '')}/skill-run/stream`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHeaders },
        body: JSON.stringify(requestBody),
        signal,
      })

      if (!res.ok) {
        const errText = await res.text().catch(() => '')
        let errMsg = errText
        try { errMsg = JSON.parse(errText).error || errText } catch {}
        throw new Error(errMsg || `HTTP ${res.status}`)
      }

      await consumeSkillRunStream(res, {
        ...handlers,
        onEvent: (evt) => {
          if (Number.isFinite(evt.eventId)) lastEventId = Math.max(lastEventId, evt.eventId)
          if (evt.sessionId) sessionId = evt.sessionId
          handlers.onEvent?.(evt)
        },
        onSession: (sid) => {
          if (sid) sessionId = sid
          handlers.onSession?.(sid)
        },
        onResume: (evt) => {
          if (evt?.sessionId) sessionId = evt.sessionId
          handlers.onResume?.(evt)
        },
        onDone: (evt) => {
          completed = true
          handlers.onDone?.(evt)
        },
        onError: (msg) => {
          streamErr = msg || 'AI 服务错误'
          handlers.onError?.(streamErr)
        },
      })

      if (streamErr) {
        const err = new Error(streamErr)
        err.noReconnect = true
        throw err
      }
      return { sessionId, lastEventId, reconnects, completed }
    } catch (err) {
      if (err?.name === 'AbortError') throw err
      if (err?.noReconnect) throw err
      if (!sessionId || reconnects >= maxReconnects) throw err
      reconnects += 1
      handlers.onReconnect?.({
        sessionId,
        lastEventId,
        attempt: reconnects,
        error: err?.message || String(err),
      })
      await sleep(retryDelayMs)
    }
  }
}
