// useAcpStream wraps the ACP WebSocket per spec §10.3:
//   - connect + auto-reconnect once with exponential backoff (1s/3s, then give up)
//   - ?since=lastEventID resumption
//   - send session/prompt / session/cancel (generates client-<uuid> ID)
//   - accumulate events into a ref array
//   - expose sessionStatus + connection-state status
import { ref, onScopeDispose, type Ref } from 'vue'
import { openSessionWS, acpApi, type AcpEvent, type PermissionRequest } from '@/api/acp'
import { useAuthStore } from '@/stores/auth'

export type StreamStatus = 'idle' | 'connecting' | 'streaming' | 'closed' | 'error'
export type SessionStatus = 'starting' | 'running' | 'crashed' | 'exited'

export interface UseAcpStreamReturn {
  status: Ref<StreamStatus>
  sessionStatus: Ref<SessionStatus>
  events: Ref<AcpEvent[]>
  pendingPermissions: Ref<PermissionRequest[]>
  errorMsg: Ref<string | null>
  prompt: (text: string, agentSessionID: string) => boolean
  cancel: (agentSessionID: string) => boolean
  reconnect: () => void
  close: () => void
}

// 把控制帧（ID=0）的 payload 归一为对象，无论 WS 传来字符串还是已解析对象。
function decodePayload(raw: unknown): Record<string, unknown> {
  if (typeof raw === 'string') {
    try {
      return JSON.parse(raw) as Record<string, unknown>
    } catch {
      return {}
    }
  }
  if (raw && typeof raw === 'object') return raw as Record<string, unknown>
  return {}
}

interface ServerStateEvent {
  kind: 'state'
  status: SessionStatus
}

interface SessionTerminatedPayload {
  status?: 'crashed' | 'exited'
}

export function useAcpStream(sessionId: string): UseAcpStreamReturn {
  const status = ref<StreamStatus>('idle')
  const sessionStatus = ref<SessionStatus>('starting')
  const events = ref<AcpEvent[]>([])
  const pendingPermissions = ref<PermissionRequest[]>([])
  const errorMsg = ref<string | null>(null)
  const lastEventID = ref(0)

  function upsertPermission(p: PermissionRequest) {
    if (!pendingPermissions.value.some((x) => x.id === p.id)) {
      pendingPermissions.value.push(p)
    }
  }
  function removePermission(reqID: string) {
    pendingPermissions.value = pendingPermissions.value.filter((x) => x.id !== reqID)
  }

  let ws: WebSocket | null = null
  let retried = 0
  let manualClose = false
  let reconnectTimer: ReturnType<typeof setTimeout> | null = null

  const auth = useAuthStore()

  function connect() {
    if (ws || manualClose) return
    status.value = 'connecting'
    const token = auth.token ?? ''
    const sock = openSessionWS(sessionId, lastEventID.value, token)
    ws = sock

    sock.onopen = () => {
      status.value = 'streaming'
      retried = 0
      // 控制帧（permission_*）不落 acp_events，重连不补发；连上后主动拉取当前待审。
      acpApi.sessions
        .permissions(sessionId)
        .then((list) => {
          for (const p of list) upsertPermission(p)
        })
        .catch(() => {})
    }

    sock.onmessage = (ev: MessageEvent) => {
      let parsed: unknown
      try {
        parsed = JSON.parse(ev.data as string)
      } catch {
        return
      }
      if (!parsed || typeof parsed !== 'object') return
      const obj = parsed as Record<string, unknown>

      // 权限请求/决议控制帧（不进 events 流，单独维护 pendingPermissions）。
      if (obj.kind === 'permission_request') {
        const p = decodePayload(obj.payload)
        upsertPermission({
          id: String(p.request_id ?? ''),
          session_id: String(p.session_id ?? ''),
          tool_name: String(p.tool_name ?? ''),
          tool_call: p.tool_call,
          options: Array.isArray(p.options)
            ? (p.options as { id: string; name?: string }[])
            : [],
          status: String(p.status ?? 'pending'),
          created_at: String(p.created_at ?? ''),
        })
        return
      }
      if (obj.kind === 'permission_resolved') {
        const p = decodePayload(obj.payload)
        removePermission(String(p.request_id ?? ''))
        return
      }

      // 客户端错误帧（bad json / 无权限 / 方法不允许 / 转发失败）——单独提示，不进 events 流。
      if (obj.kind === 'client_error') {
        const msg = typeof obj.message === 'string' ? obj.message : 'client error'
        errorMsg.value = msg
        return
      }

      // Initial state event from server
      if (obj.kind === 'state') {
        const s = (parsed as ServerStateEvent).status
        if (s) sessionStatus.value = s
        return
      }

      // Final session_terminated event
      if (obj.kind === 'session_terminated') {
        let payloadObj: SessionTerminatedPayload = {}
        const rawPayload = obj.payload
        if (typeof rawPayload === 'string') {
          try {
            payloadObj = JSON.parse(rawPayload) as SessionTerminatedPayload
          } catch {
            payloadObj = {}
          }
        } else if (rawPayload && typeof rawPayload === 'object') {
          payloadObj = rawPayload as SessionTerminatedPayload
        }
        sessionStatus.value = payloadObj.status === 'crashed' ? 'crashed' : 'exited'
        events.value.push(parsed as AcpEvent)
        return
      }

      // Regular event envelope
      const ev2 = parsed as AcpEvent
      if (typeof ev2.id === 'number' && ev2.id > lastEventID.value) {
        lastEventID.value = ev2.id
      }
      events.value.push(ev2)
    }

    sock.onerror = () => {
      status.value = 'error'
    }

    sock.onclose = () => {
      ws = null
      if (manualClose) {
        status.value = 'closed'
        return
      }
      if (retried < 2) {
        const delay = retried === 0 ? 1000 : 3000
        retried += 1
        reconnectTimer = setTimeout(connect, delay)
      } else {
        status.value = 'closed'
      }
    }
  }

  function send(method: string, params: Record<string, unknown>) {
    if (!ws || ws.readyState !== WebSocket.OPEN) return false
    const uuid = crypto.randomUUID?.() ?? Math.random().toString(36).slice(2)
    const id = 'client-' + uuid
    const msg = { jsonrpc: '2.0', id, method, params }
    ws.send(JSON.stringify(msg))
    return true
  }

  function prompt(text: string, agentSessionID: string) {
    return send('session/prompt', {
      sessionId: agentSessionID,
      prompt: [{ type: 'text', text }],
    })
  }

  function cancel(agentSessionID: string) {
    return send('session/cancel', { sessionId: agentSessionID })
  }

  function reconnect() {
    if (ws) ws.close()
    retried = 0
    manualClose = false
    connect()
  }

  function close() {
    manualClose = true
    if (reconnectTimer) {
      clearTimeout(reconnectTimer)
      reconnectTimer = null
    }
    if (ws) ws.close()
    status.value = 'closed'
  }

  connect()
  onScopeDispose(close)

  return { status, sessionStatus, events, pendingPermissions, errorMsg, prompt, cancel, reconnect, close }
}
