/**
 * AI Chat composable(WS 版,miniapp 专用)
 *
 * 与桌面端 useAiSend/useAiEvents 的差异:
 * - 桌面用 Tauri invoke(发命令)/ listen(收事件),miniapp 用 WS 收发(relay 透传)
 * - 桌面有 useAiApproval/useAiStream/useAiConversations 等细粒度拆分,
 *   miniapp MVP 合一(状态量小,后续可拆)
 *
 * 参考(桌面端逻辑结构,WS 替代 Tauri):
 * - src/composables/ai/useAiEvents.ts:699 handleEvent(事件分派逻辑)
 * - src/composables/ai/useAiSend.ts(sendMessage 流程)
 *
 * 状态:
 * - messages: 消息气泡列表(本地渲染,非真相源 —— 真相源在桌面端 session.messages)
 * - currentText: 流式累积文本(对齐桌面 state.currentText,AiTextDelta 增量拼)
 * - generating: 生成中(对齐桌面 state.generatingConvs,单 conv 简化为 bool)
 * - conversations: 会话列表(命令 device 同步,本地缓存)
 * - activeConversationId: 当前会话(对齐桌面 state.activeConversationId)
 * - wsStatus: WS 连接状态
 * - tokenUsage: 最近回复 token 用量(可选展示)
 *
 * 方法:
 * - send(text): 发消息(send_message Command → relay → device invoke ai_chat_send)
 * - stop(): 停止生成(stop Command → device invoke ai_chat_stop)
 * - regenerate(): 重发最后一条 user 消息(重新 send_message)
 * - switchConversation(id): 切换会话(switch Command → device 切 active)
 * - approve(toolCallId, approved): 审批工具调用(approve Command → device invoke ai_approve)
 * - authorizeDir(toolCallId, decision): 路径授权(authorize_dir Command → device invoke ai_authorize_dir)
 * - continueLoop(convId): 继续循环(continue_loop Command → device invoke ai_continue_loop)
 * - stopLoop(convId): 停止循环(stop_loop Command → device invoke ai_stop_loop)
 */

import { ref, reactive, computed } from 'vue'
import { wsClient } from '@/api/ws'
import type { WsStatus } from '@/api/ws'
import type {
  AiChatEvent,
  ChatMessage,
  Conversation,
  AiToolCallInfo,
  TokenUsage,
} from '@/types/events'
import type { BroadcastMessage, MiniCommand, ControlMessage } from '@/types/relay'

/** 简易唯一 id 生成(miniapp 无 crypto.randomUUID 兼容性顾虑,时间戳+随机即可) */
function genId(prefix: string): string {
  return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 10000)}`
}

/**
 * 全局单例状态(miniapp 进程内一份,多页面共享)
 *
 * 与桌面端差异:桌面 state 在 src/stores/ai.ts(pinia-like 单例),miniapp 用模块级
 * reactive(uni-app 无 pinia 依赖,模块单例即可)。
 */
const messages = reactive<ChatMessage[]>([])
const currentText = ref<string>('')
const generating = ref<boolean>(false)
const agentRound = ref<number>(0)
const conversations = reactive<Conversation[]>([])
const activeConversationId = ref<string | null>(null)
const wsStatus = ref<WsStatus>('disconnected')
const wsStatusDetail = ref<string>('')
const tokenUsage = ref<TokenUsage | null>(null)
const pendingApprovals = reactive<AiToolCallInfo[]>([])

/** 已注册事件监听标志(防多次订阅 wsClient) */
let subscribed = false

/** 从工具调用 id 在消息列表中查找(对齐桌面 findToolCall,useAiEvents.ts:408) */
function findToolCall(id: string): AiToolCallInfo | null {
  for (let i = messages.length - 1; i >= 0; i--) {
    const m = messages[i]
    if (m.toolCalls) {
      for (const tc of m.toolCalls) {
        if (tc.id === id) return tc
      }
    }
  }
  return null
}

/** 流式文本回填到最后一条 assistant 气泡(对齐桌面 flushCurrentText,useAiEvents.ts:186) */
function flushCurrentText(): void {
  if (!currentText.value) return
  for (let i = messages.length - 1; i >= 0; i--) {
    const m = messages[i]
    if (m.role !== 'assistant') break
    if (!m.isError) {
      m.content = currentText.value
      break
    }
  }
}

/**
 * AiChatEvent 处理(对齐桌面 useAiEvents.ts handleEvent,按域简化合并)
 *
 * miniapp MVP 简化:
 * - 仅处理核心渲染相关事件(TextDelta/AgentRound/Completed/Error/Tool/Approval)
 * - 看门狗/超时/审批计时器等桌面机制未移植(后续批)
 * - 路由判断:无 conversation_id 或等于 active 时处理,其他忽略
 */
export function handleEvent(event: AiChatEvent): void {
  const convId = event.conversation_id ?? null

  // 首次收到事件同步当前对话 id(对齐桌面 useAiEvents.ts:705)
  if (convId && !activeConversationId.value) {
    activeConversationId.value = convId
  }

  // 路由:非当前会话事件忽略(miniapp MVP 单会话视图,不追踪多 conv 生成态)
  const isCurrent = !convId || convId === activeConversationId.value
  if (!isCurrent) return

  switch (event.type) {
    case 'AiTextDelta':
      currentText.value += event.delta
      break

    case 'AiAgentRound': {
      // 新一轮:flush 当前文本到上一条 assistant,新建空 assistant 占位
      flushCurrentText()
      currentText.value = ''
      messages.push({
        id: genId('ai'),
        role: 'assistant',
        content: '',
        timestamp: Date.now(),
      })
      if (event.round > 0) agentRound.value = event.round
      break
    }

    case 'AiHeartbeat':
      // 静默期报活,miniapp 仅维持连接(无 watchdog 需重置)
      break

    case 'AiStreamRetry': {
      // 重试提示:复用错误气泡或新建
      const last = messages[messages.length - 1]
      const text = `重试中(${event.attempt}/${event.max_attempts})...`
      if (last && last.isError) {
        last.content = text
      } else {
        messages.push({
          id: genId('retry'),
          role: 'assistant',
          content: text,
          isError: true,
          timestamp: Date.now(),
        })
      }
      break
    }

    case 'AiMaxRoundsReached':
      // 达 max 暂停:miniapp 展示提示气泡(不实现继续/停止按钮,用户回桌面处理)
      messages.push({
        id: genId('maxrounds'),
        role: 'system',
        content: '⚠ 已达最大轮次,请在桌面端处理(继续/停止)',
        timestamp: Date.now(),
      })
      break

    case 'AiToolCallStarted': {
      const info: AiToolCallInfo = {
        id: event.id,
        name: event.name,
        args: event.args,
        status: 'running',
      }
      if (!findToolCall(event.id)) {
        const last = messages[messages.length - 1]
        if (last && last.role === 'assistant') {
          last.toolCalls = last.toolCalls || []
          last.toolCalls.push(info)
        }
      }
      break
    }

    case 'AiToolCallCompleted': {
      const tc = findToolCall(event.id)
      if (tc) {
        tc.status = 'completed'
        tc.result = event.result
      }
      // 移除对应挂起审批(若有)
      const idx = pendingApprovals.findIndex((p) => p.id === event.id)
      if (idx >= 0) pendingApprovals.splice(idx, 1)
      break
    }

    case 'AiToolAutoApproved':
      // 会话级信任自动放行,miniapp 仅 console(不做 toast UI)
      console.log(`[auto-approved] ${event.tool} @ ${event.dir}`)
      break

    case 'AiApprovalRequired': {
      const info: AiToolCallInfo = {
        id: event.id,
        name: event.name,
        args: event.args,
        status: 'pending_approval',
        kind: 'risk',
        reason: event.reason,
        diff: event.diff ?? undefined,
      }
      pendingApprovals.push(info)
      const tc = findToolCall(event.id)
      if (tc) {
        tc.status = 'pending_approval'
        tc.reason = event.reason
        if (event.diff) tc.diff = event.diff
      }
      break
    }

    case 'AiApprovalResult': {
      if (!event.approved) {
        const tc = findToolCall(event.id)
        if (tc) tc.status = 'rejected'
      }
      const idx = pendingApprovals.findIndex((p) => p.id === event.id)
      if (idx >= 0) pendingApprovals.splice(idx, 1)
      break
    }

    case 'AiDirAuthRequired':
      // 路径授权:miniapp 仅提示(不实现授权 UI,用户回桌面处理)
      messages.push({
        id: genId('dirauth'),
        role: 'system',
        content: `⚠ 路径授权请求:${event.tool} → ${event.dir}(请在桌面端处理)`,
        timestamp: Date.now(),
      })
      break

    case 'AiCompleted': {
      flushCurrentText()
      currentText.value = ''
      generating.value = false
      agentRound.value = 0
      // token 用量(可选展示)
      tokenUsage.value = {
        prompt: event.prompt_tokens,
        completion: event.completion_tokens,
        total: event.total_tokens,
      }
      // 不完整标记(网络中断保文)
      if (event.incomplete) {
        messages.push({
          id: genId('incomplete'),
          role: 'assistant',
          content: '⚠ 响应因网络中断不完整',
          timestamp: Date.now(),
        })
      }
      break
    }

    case 'AiError': {
      flushCurrentText()
      currentText.value = ''
      generating.value = false
      agentRound.value = 0
      pendingApprovals.splice(0, pendingApprovals.length)
      messages.push({
        id: genId('err'),
        role: 'assistant',
        content: friendlyError(event.error),
        isError: true,
        timestamp: Date.now(),
      })
      break
    }

    case 'AiHelpRequired':
      // 断路器熔断求助:miniapp 展示求助卡(用户在桌面选 option,miniapp 不渲染选项按钮)
      flushCurrentText()
      currentText.value = ''
      generating.value = false
      messages.push({
        id: genId('help'),
        role: 'system',
        content: `🆘 AI 求助:${event.reason}\n已试:${event.context}\n建议:${event.options.join(' / ')}(请在桌面端选择)`,
        timestamp: Date.now(),
      })
      break

    case 'AiContextCleared':
    case 'AiCompressing':
      // 上下文分段/压缩中,miniapp 无需特殊处理(状态在桌面端)
      break

    case 'AiCompressed':
      // 压缩完成:在消息首位插入摘要提示
      messages.unshift({
        id: genId('compressed'),
        role: 'system',
        content: `📋 上下文已压缩(摘要:${event.summary.slice(0, 100)}...)`,
        timestamp: Date.now(),
      })
      break

    case 'AiConvStateChanged':
      // 统一状态机:generating/stopping → generating=true;idle/error → false
      if (event.conv_state === 'generating' || event.conv_state === 'stopping') {
        generating.value = true
      } else if (event.conv_state === 'idle' || event.conv_state === 'error') {
        generating.value = false
      }
      // compressed 是派生态,保持当前 generating(压缩完回原态)
      break
  }
}

/** 错误转用户友好提示(对齐桌面 friendlyError,useAiEvents.ts:177) */
export function friendlyError(raw: string): string {
  if (/404|not\s*found/i.test(raw)) return '未找到(可能对话已删除)'
  if (/401|403|unauthorized|api[_\s-]?key/i.test(raw)) return '鉴权失败,请检查 API Key 配置'
  if (/timeout|超时/i.test(raw)) return '请求超时'
  if (/network|connection|ECONN|网络|连接/i.test(raw)) return '网络错误,请检查连接'
  return raw
}

/** 注册 wsClient 订阅(幂等,App.vue onLaunch 调用一次) */
function subscribeWs(): void {
  if (subscribed) return
  subscribed = true
  wsClient.subscribe({
    onEvent: (msg: BroadcastMessage) => {
      // payload 是 AiChatEvent JSON,透传给 handleEvent
      try {
        const event = msg.payload as AiChatEvent
        if (event && typeof event === 'object' && 'type' in event) {
          handleEvent(event)
        }
      } catch (e) {
        console.warn('[useAiChat] 事件 payload 解析失败', e)
      }
    },
    onControl: (msg: ControlMessage) => {
      // 心跳响应等,miniapp 仅 console(不维持在线状态 UI)
      console.log('[useAiChat] control', msg)
    },
    onStatus: (status: WsStatus, detail?: string) => {
      wsStatus.value = status
      wsStatusDetail.value = detail ?? ''
    },
  })
}

/** 发 send_message Command(relay → device invoke ai_chat_send) */
function send(text: string): void {
  const trimmed = text.trim()
  if (!trimmed) return
  if (generating.value) {
    console.warn('[useAiChat] 生成中,忽略新发送')
    return
  }

  // 本地先 push user 气泡(乐观渲染,对齐桌面 sendMessage)
  messages.push({
    id: genId('user'),
    role: 'user',
    content: trimmed,
    timestamp: Date.now(),
  })

  // 新建 assistant 占位气泡(等待流式增量填充)
  messages.push({
    id: genId('ai'),
    role: 'assistant',
    content: '',
    timestamp: Date.now(),
  })
  currentText.value = ''
  generating.value = true

  const cmd: MiniCommand = {
    cmd: 'send_message',
    args: {
      message: trimmed,
      conversation_id: activeConversationId.value,
    },
  }
  if (!wsClient.send(cmd)) {
    // 发送失败:在 assistant 占位气泡显错误
    flushCurrentText()
    messages.push({
      id: genId('err'),
      role: 'assistant',
      content: '未连接桌面端,请检查网络/配置',
      isError: true,
      timestamp: Date.now(),
    })
    generating.value = false
  }
}

/** 停止生成(relay → device invoke ai_chat_stop) */
function stop(): void {
  if (!generating.value) return
  const cmd: MiniCommand = {
    cmd: 'stop',
    args: {
      conversation_id: activeConversationId.value,
    },
  }
  wsClient.send(cmd)
}

/** 重发最后一条 user 消息(重新 send_message) */
function regenerate(): void {
  // 找最后一条 user 消息
  let lastUserText = ''
  for (let i = messages.length - 1; i >= 0; i--) {
    if (messages[i].role === 'user') {
      lastUserText = messages[i].content
      break
    }
  }
  if (!lastUserText) return
  // 移除末尾 assistant 占位/错误气泡(若仍是空的)
  const last = messages[messages.length - 1]
  if (last && last.role === 'assistant' && !last.content) {
    messages.pop()
  }
  send(lastUserText)
}

/**
 * 审批工具调用(approve Command → relay → device invoke ai_approve)
 *
 * Phase3 §2.2 路由表:{cmd:"approve", args:{tool_call_id, approved}}
 * 对应事件:AiApprovalRequired(挂起审批列表 pendingApprovals)。
 */
function approve(toolCallId: string, approved: boolean): void {
  if (!toolCallId) return
  const cmd: MiniCommand = {
    cmd: 'approve',
    args: {
      tool_call_id: toolCallId,
      approved,
    },
  }
  wsClient.send(cmd)
  // 乐观从挂起列表移除(对齐 AiApprovalResult 事件处理,避免 UI 卡 pending)
  const idx = pendingApprovals.findIndex((p) => p.id === toolCallId)
  if (idx >= 0) pendingApprovals.splice(idx, 1)
}

/**
 * 路径授权(authorize_dir Command → relay → device invoke ai_authorize_dir)
 *
 * Phase3 §2.2 路由表:{cmd:"authorize_dir", args:{tool_call_id, decision}}
 * 对应事件:AiDirAuthRequired(决策:'once'=本次 / 'always'=永久 / 'deny'=拒绝)。
 */
function authorizeDir(toolCallId: string, decision: 'once' | 'always' | 'deny'): void {
  if (!toolCallId) return
  const cmd: MiniCommand = {
    cmd: 'authorize_dir',
    args: {
      tool_call_id: toolCallId,
      decision,
    },
  }
  wsClient.send(cmd)
}

/**
 * 继续循环(continue_loop Command → relay → device invoke ai_continue_loop)
 *
 * Phase3 §2.2 路由表:{cmd:"continue_loop", args:{conversation_id}}
 * 对应场景:AiMaxRoundsReached 达最大轮次暂停后,用户选「继续」再跑一轮。
 */
function continueLoop(conversationId?: string | null): void {
  const convId = conversationId ?? activeConversationId.value
  if (!convId) {
    console.warn('[useAiChat] continue_loop 缺 conversation_id')
    return
  }
  const cmd: MiniCommand = {
    cmd: 'continue_loop',
    args: { conversation_id: convId },
  }
  wsClient.send(cmd)
}

/**
 * 停止循环(stop_loop Command → relay → device invoke ai_stop_loop)
 *
 * Phase3 §2.2 路由表:{cmd:"stop_loop", args:{conversation_id}}
 * 对应场景:AiMaxRoundsReached 达最大轮次暂停后,用户选「停止」彻底终止。
 * 与 stop 区别:stop 是中断流式生成,stop_loop 是停止多轮 agent 循环。
 */
function stopLoop(conversationId?: string | null): void {
  const convId = conversationId ?? activeConversationId.value
  if (!convId) {
    console.warn('[useAiChat] stop_loop 缺 conversation_id')
    return
  }
  const cmd: MiniCommand = {
    cmd: 'stop_loop',
    args: { conversation_id: convId },
  }
  wsClient.send(cmd)
}

/** 切换会话(switch Command → device 切 active_conversation_id) */
function switchConversation(convId: string): void {
  activeConversationId.value = convId
  // 清空当前视图(新会话消息从 device 同步或留空)
  messages.splice(0, messages.length)
  currentText.value = ''
  generating.value = false

  const cmd: MiniCommand = {
    cmd: 'switch_conversation',
    args: { conversation_id: convId },
  }
  wsClient.send(cmd)
}

/** 新建会话(本地清空 + 切到 null,下次 send 时 device 创建新 conv) */
function newConversation(): void {
  activeConversationId.value = null
  messages.splice(0, messages.length)
  currentText.value = ''
  generating.value = false
  pendingApprovals.splice(0, pendingApprovals.length)
}

/**
 * useAiChat 单例 hook
 *
 * 多次调用返回同一状态(模块级 reactive 单例)。
 */
export function useAiChat() {
  subscribeWs()

  const isWsConnected = computed(() => wsStatus.value === 'connected')

  return {
    // 状态
    messages,
    currentText,
    generating,
    agentRound,
    conversations,
    activeConversationId,
    wsStatus,
    wsStatusDetail,
    isWsConnected,
    tokenUsage,
    pendingApprovals,
    // 方法
    connect: () => wsClient.connect(),
    disconnect: () => wsClient.disconnect(),
    resumeIfDisconnected: () => wsClient.resumeIfDisconnected(),
    send,
    stop,
    regenerate,
    switchConversation,
    newConversation,
    approve,
    authorizeDir,
    continueLoop,
    stopLoop,
    handleEvent,
    friendlyError,
  }
}
