//! LLM Provider 数据结构定义（纯类型，零 IO）
//!
//! 从 provider.rs 拆分的核心数据类型层：请求/响应/消息/工具/Token 等
//! `#[derive]` struct 与 enum + 一个内联时间戳辅助函数。所有 impl 块与
//! `LlmProvider` trait 留在 provider.rs，本文件只承载类型定义。
//!
//! 经 `provider.rs` 的 `pub use crate::types::*` re-export，外部路径
//! `df_ai_core::provider::*` / `df_ai::provider::*` 保持不变（编译期验证）。

use std::pin::Pin;
use std::sync::atomic::{AtomicU64, Ordering};

use futures::Stream;
use serde::{Deserialize, Serialize};

// ============================================================
// 核心数据结构
// ============================================================

/// LLM 调用请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletionRequest {
    /// 模型名称
    pub model: String,
    /// 提示消息列表
    pub messages: Vec<ChatMessage>,
    /// 温度（0.0 ~ 2.0）
    pub temperature: Option<f32>,
    /// 最大生成 token 数
    pub max_tokens: Option<u32>,
    /// 是否流式输出
    pub stream: bool,
    /// 可调用的工具定义
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tools: Option<Vec<ToolDefinition>>,
    /// 工具调用策略: "auto" | "none" | {"type":"function","name":"xxx"}
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_choice: Option<serde_json::Value>,
    /// DeepSeek thinking 模式：需把上一轮 response 的 reasoning_content 回传
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reasoning_content: Option<String>,
}

/// 多模态消息内容片。Text 片为字符串；Image 片可走 url 或 base64（二选一，base64 非空时 url 忽略）。
///
/// ContentPart 作为 `ChatMessage.parts` 的元素类型。
/// 设计上 `content: String`（纯文本主载荷）保持不变，多模态片挂在 `parts`：
/// 这样未接入多模态的调用方（audit/title/commands/knowledge_inject 等读 content 当字符串）
/// 零回归，避免一次性改全仓。provider 转换层在 `has_image()` 为真时把 parts 透传给
/// vision 端点，否则只用 content 文本（保持纯文本端点兼容）。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentPart {
    /// 文本片
    Text { text: String },
    /// 图片片。
    /// - url：http(s) 可达 URL（provider 直接转发，不读字节）。
    /// - base64：data URI 之外的纯 base64 字符串 + media_type（provider 内嵌转发）。
    ///   base64 非空时 url 忽略，便于前端上行无网络回拉的本地粘贴图。
    Image {
        url: Option<String>,
        base64: Option<String>,
        /// base64 模式必填（image/png | image/jpeg | image/webp | image/gif）；url 模式可空。
        media_type: Option<String>,
        /// 可选 alt（vision 模型/降级文本时用）
        #[serde(skip_serializing_if = "Option::is_none")]
        alt: Option<String>,
    },
}

/// 聊天消息
///
/// ⚠️ **迁移耦合点**:下方各字段名(`role`/`content`/`parts`/`tool_call_id`/
/// `tool_calls`/`model`/`status`/`reasoning_content`/`timestamp`)被
/// `df-storage/src/migrations.rs` 的 `migrate_v21` 硬编码提取(裸 JSON 反序列化,
/// 因 df-storage 不依赖 df-ai-core)。**改字段名必须同步更新迁移函数**,否则老库
/// 迁移漏数据。详见消息拆分存储设计 §4.2.3 迁移耦合点。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
    /// 消息全局唯一 ID。用于消息级溯源(source_ref / audit message_id / idea source)。
    /// 构造时由 `new_message_id()` 生成;老 JSON 反序列化为 None(向前兼容)。
    /// 消息拆分存储后,此 ID 即 `ai_messages.id` 列主键。
    /// 临时/派生消息(如 title 摘要)可显式赋 None(不溯源不落库)。
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    pub role: MessageRole,
    pub content: String,
    /// 多模态内容片。
    ///
    /// `None`/空 → 纯文本消息（绝大多数场景，content 即全部载荷）。
    /// `Some(含 Image 片)` → 多模态消息，provider 在 `has_image()` 为真时把 parts
    /// 连同 content（作为前置 Text 片）一起转成 OpenAI/Anthropic 的 content blocks。
    /// content 字段始终保留人类可读文本（文本消息的 Text 片内容与 content 一致），
    /// 保证 audit/title/export 等读 content 当字符串的调用方零回归。
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub parts: Option<Vec<ContentPart>>,
    /// 工具调用 ID（role=Tool 时必填）
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_call_id: Option<String>,
    /// AI 发起的工具调用列表（role=Assistant 时可能有）
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<Vec<ToolCall>>,
    /// 生成该消息的 model（仅 assistant 消息有，消息级 model 追溯）
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub model: Option<String>,
    /// 消息状态（UX-09 编辑重生成）：None 正常可见；
    /// "truncated" 软删（编辑某条 user 消息后其后续消息标记，保留 DB 可追溯但不进 LLM 上下文、前端视图过滤）
    /// 默认 None（向前兼容老 JSON 反序列化）。落库随 messages JSON 序列化，无需独立列。
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub status: Option<MessageStatus>,
    /// DeepSeek thinking 模式的推理内容（多轮需回传）
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reasoning_content: Option<String>,
    /// 消息创建时间(Unix 毫秒)。仅前端时间戳展示用,序列化进 messages JSON 持久化;
    /// provider 请求映射不读此字段(构造器打戳→映射忽略,不进 LLM 请求),老数据反序列化为 None。
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub timestamp: Option<i64>,
    /// 本轮 LLM 调用的输入 token 用量(仅 assistant 消息,消息级 token 持久化)。
    /// 双轨:消息级(本字段,新)+ 会话级累计(ai_conversations.prompt_tokens,旧,保留)。
    /// 用于 reload/压缩/切会话后历史 assistant 消息 token 显示;老 JSON 反序列化为 None(向前兼容)。
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub prompt_tokens: Option<u32>,
    /// 本轮 LLM 调用的输出 token 用量(仅 assistant 消息,消息级 token 持久化)。
    /// 语义同 prompt_tokens;provider 流式 usage 缺失时(GLM 等)可能为 0。
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub completion_tokens: Option<u32>,
}

/// 当前 Unix 毫秒(ChatMessage 打戳用;df-ai-core 不依赖 df-types,内联避免新增依赖)。
pub(crate) fn now_millis_i64() -> i64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_millis() as i64)
        .unwrap_or(0)
}

/// ChatMessage.id 生成器:全局唯一 + 单调递增 + 零依赖。
///
/// 格式 `msg_{ts_ms}_{counter}` —— 时间戳(毫秒)+ 进程内 AtomicU64 计数器双保险。
/// 决策:不用 ULID crate(workspace 无此依赖,df-ai-core 也不依赖 df-types::new_id),
/// 沿用 `now_millis_i64` 内联模式;AtomicU64 保证并发安全(多线程构造消息 ID 不冲突),
/// 计数器即便时间戳回拨(系统时钟调整)也单调。可读性好(含时间戳,便于排查)。
pub(crate) fn new_message_id() -> String {
    static COUNTER: AtomicU64 = AtomicU64::new(0);
    let ts = now_millis_i64();
    let n = COUNTER.fetch_add(1, Ordering::Relaxed);
    format!("msg_{}_{n}", ts)
}

/// 消息角色
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MessageRole {
    System,
    User,
    Assistant,
    Tool,
}

/// 消息状态枚举（IPC 边界序列化为小写 snake_case 字符串）
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum MessageStatus {
    /// 正常可见
    Active,
    /// 软删（编辑某条 user 消息后其后续消息标记）
    Truncated,
    /// 已压缩
    Compressed,
    /// 已归档段
    ArchivedSegment,
}

impl MessageStatus {
    /// DB 存储用的小写 snake_case 字符串
    pub fn as_db_str(&self) -> &'static str {
        match self {
            MessageStatus::Active => "active",
            MessageStatus::Truncated => "truncated",
            MessageStatus::Compressed => "compressed",
            MessageStatus::ArchivedSegment => "archived_segment",
        }
    }

    /// 从 DB 字符串解析
    pub fn from_db_str(s: &str) -> Option<Self> {
        Some(match s {
            "active" => MessageStatus::Active,
            "truncated" => MessageStatus::Truncated,
            "compressed" => MessageStatus::Compressed,
            "archived_segment" => MessageStatus::ArchivedSegment,
            _ => return None,
        })
    }
}

/// ChatMessage 结构体
/// 工具类型（IPC 边界透明序列化为字符串，如 "function"）
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct ToolType(String);

impl ToolType {
    /// 构造新工具类型
    pub fn new(s: impl Into<String>) -> Self {
        Self(s.into())
    }
}

impl std::fmt::Display for ToolType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<String> for ToolType {
    fn from(s: String) -> Self {
        Self(s)
    }
}

impl From<&str> for ToolType {
    fn from(s: &str) -> Self {
        Self(s.to_owned())
    }
}

impl PartialEq<&str> for ToolType {
    fn eq(&self, other: &&str) -> bool {
        self.0 == *other
    }
}

impl PartialEq<str> for ToolType {
    fn eq(&self, other: &str) -> bool {
        self.0 == other
    }
}

/// 工具定义
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDefinition {
    #[serde(rename = "type")]
    pub tool_type: ToolType,
    pub function: ToolFunction,
}

/// 函数定义
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolFunction {
    pub name: String,
    pub description: String,
    pub parameters: serde_json::Value,
}

/// 工具调用（AI 发起）
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
    pub id: String,
    #[serde(rename = "type")]
    pub call_type: ToolType,
    pub function: ToolCallFunction,
}

/// 工具调用函数部分
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallFunction {
    pub name: String,
    pub arguments: String,
}

/// LLM 调用响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletionResponse {
    /// 生成的文本
    pub text: String,
    /// 使用的模型
    pub model: String,
    /// 消耗的 token 数
    pub usage: TokenUsage,
    /// AI 发起的工具调用（如有）
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<Vec<ToolCall>>,
    /// DeepSeek thinking 模式响应中的推理内容（需回传到下一轮请求）
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reasoning_content: Option<String>,
}

/// Token 用量
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TokenUsage {
    pub prompt_tokens: u32,
    pub completion_tokens: u32,
    pub total_tokens: u32,
}

/// 流式输出的 chunk
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamChunk {
    /// 增量文本
    pub delta: String,
    /// 是否结束
    pub finished: bool,
    /// 工具调用增量（如有）
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<Vec<ToolCallDelta>>,
    /// Token 用量（流末 chunk 携带，由 provider 解析自 SSE usage 事件）
    #[serde(skip_serializing_if = "Option::is_none")]
    pub usage: Option<TokenUsage>,
    /// provider 流式错误事件（如 Anthropic SSE `type=="error"`）。
    /// 非空表示流中途出错，不应视为正常完成（finished 路径），由 stream_llm 转 AiError。
    #[serde(skip)]
    pub error: Option<String>,
    /// DeepSeek thinking 模式推理内容增量
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reasoning_content: Option<String>,
}

/// 工具调用增量（流式中的片段）
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallDelta {
    /// 索引
    pub index: u32,
    /// 工具调用 ID（仅第一个 chunk 有）
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    /// 函数名片段
    #[serde(skip_serializing_if = "Option::is_none")]
    pub function_name: Option<String>,
    /// 函数参数片段
    #[serde(skip_serializing_if = "Option::is_none")]
    pub function_arguments: Option<String>,
}

/// 异步流类型别名
pub type StreamResult = Pin<Box<dyn Stream<Item = anyhow::Result<StreamChunk>> + Send>>;
