//! 数据模型定义 — 与数据库表对应的 Rust 结构体

use df_ai_core::model::{deserialize_model_configs, ModelConfig};
use df_types::types::{IdeaStatus, LinkType, NodeType, ProjectStatus, TaskStatus};
use serde::{Deserialize, Serialize};

// ============================================================
// 想法相关模型
// ============================================================

/// 想法记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IdeaRecord {
    pub id: String,
    pub title: String,
    pub description: String,
    pub status: IdeaStatus,
    pub priority: i32,
    pub score: Option<f64>,
    pub tags: Option<String>, // JSON 数组
    pub source: Option<String>,
    pub promoted_to: Option<String>,   // 晋升后的 project_id
    pub ai_analysis: Option<String>,   // AI 分析结果 JSON
    pub scores: Option<String>,        // 多维评分 JSON (feasibility/impact/urgency/overall)
    pub related_ids: Option<String>,   // 关联灵感 id JSON 数组(同 tags 模式,为关联关系 UI 打底)
    pub created_at: String,
    pub updated_at: String,
}

/// 灵感评估历史记录(追加型审计表 idea_evaluations,V22)
///
/// 每次灵感 AI 评估产生一行快照,version 单调递增。
/// 替代覆写 ideas.ai_analysis / ideas.scores:保留评估历史可追溯。
/// ai_analysis/scores 为 JSON 字符串,score 为综合评分,evaluated_by 标评估发起者。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IdeaEvaluationRecord {
    pub id: String,
    pub idea_id: String,
    pub version: i64,
    pub ai_analysis: Option<String>,
    pub scores: Option<String>,
    pub score: Option<f64>,
    pub evaluated_by: Option<String>,
    pub evaluated_at: String,
}

// ============================================================
// 项目相关模型
// ============================================================

/// 项目记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectRecord {
    pub id: String,
    pub name: String,
    pub description: String,
    pub status: ProjectStatus,
    pub idea_id: Option<String>,
    /// 绑定的本地代码目录(绝对路径,可空=未绑定,第二步导入历史项目时复用)
    pub path: Option<String>,
    /// 技术栈 JSON 数组字符串(如 ["rust","vue","tauri"],由探测填充,可空)
    pub stack: Option<String>,
    pub created_at: String,
    pub updated_at: String,
}

// ============================================================
// 任务相关模型
// ============================================================

/// 任务记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskRecord {
    pub id: String,
    pub project_id: String,
    pub title: String,
    pub description: String,
    pub status: TaskStatus,
    pub priority: i32,
    pub branch_name: Option<String>,
    pub assignee: Option<String>,
    pub workflow_def_id: Option<String>, // 关联的工作流定义 ID
    pub base_branch: Option<String>,     // 基础分支
    /// review 退回累计轮数(in_review→in_progress / testing→in_review 时 +1,
    /// 由 advance_task 原子写入,见 df-nodes::task_advance_node)。
    /// #[serde(default)] 兼容旧前端无该字段的 JSON(老任务记录默认 0)。
    #[serde(default)]
    pub review_rounds: i32,
    /// 任务产出 JSON 字符串(AiNode 自审闭环,决策 a:task 中心,产出跟 task 走)。
    ///
    /// 三方读写约定:ai_execute 写产出 / ai_self_review 读产出做自审 / human_review 展示产出对象。
    /// 经通用 update_field 写白名单(非 status 那种状态机收口字段),TEXT NULL 向后兼容。
    /// #[serde(skip_serializing_if)] 无产出不序列化(省前端字段空值),#[serde(default)] 兼容旧 JSON。
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub output_json: Option<String>,
    /// 关联灵感 ID(1对1 单向,任务→灵感)。
    /// 复用 projects.idea_id 模式(REFERENCES ideas(id) 外键),可空(任务可不关联灵感)。
    /// 老任务无 idea_id → None。#[serde(default)] 兼容旧前端 JSON(无该字段时为 None)。
    #[serde(default)]
    pub idea_id: Option<String>,
    /// 管理维度池标记(知识图谱 Phase 1,V29 列)。与 status(执行维度)正交。
    /// 取值 backlog(需求池)/todo(待办池)/decision(待决策池)/active(执行中)/done(已完成)。
    /// DEFAULT 'todo' 保证老任务迁移后取值确定(非 NULL),TaskRecord 字段为 String(非 Option)。
    /// #[serde(default = "default_queue")] 兼容旧前端 JSON(无该字段时为 "todo")。
    #[serde(default = "default_queue")]
    pub queue: String,
    /// 父任务 ID(知识图谱 Phase 1,V29 列,父任务纵向关联)。
    /// NULL = 叶子任务(走状态机 advance_task);非空 = 子任务(限制 1 级嵌套,无孙任务,
    /// 由 IPC 层校验不进 DB 约束)。父任务=容器模型,status 由子任务聚合计算(不走状态机)。
    /// TEXT NULL 向后兼容,TaskRecord 字段为 Option<String>(老任务无 parent → None)。
    /// #[serde(default)] 兼容旧前端 JSON(无该字段时为 None)。
    #[serde(default)]
    pub parent_id: Option<String>,
    /// 结构化需求规格 JSON 字符串(知识图谱 Phase 1,V29 列)。
    /// 结构 { background, acceptance_criteria[], scope[], technical_design, custom_fields }。
    /// AI 从对话提取填充,执行中用 acceptance_criteria 自检。NULL = 无结构化规格(纯文本 description)。
    /// TEXT NULL 向后兼容,TaskRecord 字段为 Option<String>。
    /// #[serde(default, skip_serializing_if = "Option::is_none")] 兼容旧 JSON + 无值不序列化。
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub content_json: Option<String>,
    pub created_at: String,
    pub updated_at: String,
}

/// 任务横向关联记录(task_links 表,知识图谱 Phase 1 V29,对标设计 §2.2)。
///
/// 表达任务间依赖/阻塞/弱关联关系,AI 拓扑排序编排调度的基础。独立表方案替代 JSON 列
/// (Idea related_ids 的缺陷:无类型区分/单向/无法高效反向查询)。
///
/// - `source_id` / `target_id`:关联两端任务 id(FK tasks.id)。软删除语义:Task 软删不
///   级联删 link(对标设计 §2.2 边界「软删除保留」——恢复后关系还在),故无 ON DELETE。
/// - `link_type`:关联类型白名单(应用层校验,非 DB 约束):
///   - `depends_on`:source 依赖 target(target 完成后 source 才能开始)→ 拓扑排序调度
///   - `blocks`:source 阻塞 target(source 不完成则 target 无法推进)→ 依赖的反向声明
///   - `relates_to`:弱关联,无执行约束 → 上下文提示
/// - `remark`:可选备注。
/// 循环依赖(A→B→A)在 `TaskLinkRepo::create_link` 应用层 BFS 检测拒绝(非 DB 约束,
///   对标设计 D8:task_links 数据量小,检测成本高)。跨项目依赖允许。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskLinkRecord {
    pub id: String,
    pub source_id: String,
    pub target_id: String,
    pub link_type: LinkType,
    pub remark: Option<String>,
    pub created_at: String,
}

/// 项目事件流记录(project_events 表,知识图谱 Phase 2 V30,追加型审计表)。
///
/// 一张表承载跨实体(idea/task/workflow/knowledge/module/service/project)的全部事件,
/// 是 AI 精准检索项目事件流的基础(回答"上周做了什么 / 这个任务为何 blocked / 决策何时做出")。
/// 对标设计 docs/02-架构设计/专项设计/项目知识图谱与任务队列系统-2026-06-26.md §2.4。
///
/// **追加型审计**(对标 KnowledgeEventRecord / IdeaEvaluationRecord):只 INSERT 不 UPDATE/
/// DELETE,历史不改可追溯。无 updated_at 字段,无通用 update/delete 路径。
///
/// - `project_id`:所属项目(FK projects.id)。
/// - `event_type`:事件类型白名单(idea_created/task_advanced/decision_made 等,应用层校验)。
/// - `entity_type` / `entity_id`:事件指向的实体(可空——纯决策日志等无明确实体)。entity_type
///   白名单应用层校验(idea/project/task/workflow/knowledge/module/service)。
/// - `from_state` / `to_state`:状态变化前后(仅状态变化类事件有值;created/referenced 类为 None)。
/// - `context_json`:事件附加上下文(JSON 字符串,因 event_type 而异)。
/// - `source`:ai / human / system(AI Working 溯源——区分 AI 自主操作还是人操作)。
/// - `conversation_id`:触发事件的对应对话(AI Working 溯源链:事件→对话→决策,可空)。
///
/// **与 knowledge_events 关系**(设计 D9):knowledge_events 保持不变(知识库专属),knowledge
/// 相关事件同时写入两者,双写在埋点层(commands/IPC hook)实现。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectEventRecord {
    pub id: String,
    pub project_id: String,
    pub event_type: String,
    pub entity_type: Option<String>,
    pub entity_id: Option<String>,
    pub from_state: Option<String>,
    pub to_state: Option<String>,
    pub context_json: Option<String>,
    pub source: Option<String>,
    pub conversation_id: Option<String>,
    pub created_at: String,
}

/// 项目基础设施配置记录(project_services 表,知识图谱 Phase 3 V31)。
///
/// 记录项目依赖的基础设施(数据库/缓存/MQ/API 等),为 AI 执行任务时提供基础设施上下文:
/// "这项目用了什么数据库、Redis 在哪、有没有 MQ"。对标设计 docs/02-架构设计/专项设计/
/// 项目知识图谱与任务队列系统-2026-06-26.md §2.3。
///
/// - `project_id`:所属项目(FK projects.id)。
/// - `name`:服务名(如"主数据库"/"缓存"/"消息队列",人/AI 可读标识)。
/// - `service_type`:基础设施类型白名单(mysql/postgresql/sqlite/redis/mongodb/mq/api/other,
///   应用层校验,不进 DB 约束)。
/// - `endpoint`:连接地址(localhost:3306 / URL),可空(部分服务无固定 endpoint,如 serverless API)。
/// - `config_json`:类型相关配置 JSON 字符串(如连接池参数/MQ topic 列表),可空。
/// - `environment`:环境标识(development/staging/production),同一服务可在不同环境各存一行。
/// - `remark`:备注说明,可空。
///
/// ⚠️ **不存敏感凭证(D10)**:本记录仅存连接信息,密码/密钥/Token 走环境变量/外部密钥管理,
/// 不进 config_json。内容审查在 ProjectServiceRepo insert/update_full 时检测敏感字段拒绝。
///
/// **元数据非运维工具**:不做连接池/健康检查,仅记录"项目用了什么基础设施"这一事实。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectServiceRecord {
    pub id: String,
    pub project_id: String,
    pub name: String,
    pub service_type: String,
    pub endpoint: Option<String>,
    pub config_json: Option<String>,
    pub environment: String,
    pub remark: Option<String>,
    pub created_at: String,
    pub updated_at: String,
}

/// 分支记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BranchRecord {
    pub id: String,
    pub project_id: String,
    pub task_id: Option<String>,
    pub name: String,
    pub base: String,
    pub status: String,
    pub created_at: String,
    pub updated_at: String,
    pub merged_at: Option<String>,
}

/// 发布记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReleaseRecord {
    pub id: String,
    pub project_id: String,
    pub version: String,
    pub status: String,
    pub task_ids: String, // JSON 数组
    pub changelog: Option<String>,
    pub created_at: String,
    pub released_at: Option<String>,
}

// ============================================================
// 工作流相关模型
// ============================================================

/// 工作流执行记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkflowRecord {
    pub id: String,
    pub name: String,
    pub dag_json: String, // DAG 的 JSON 序列化
    pub status: String,
    pub triggered_by: Option<String>,
    pub project_id: Option<String>, // 关联项目 ID
    pub task_id: Option<String>,    // 关联任务 ID
    pub created_at: String,
    pub completed_at: Option<String>,
}

/// 节点执行记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeExecutionRecord {
    pub id: String,
    pub workflow_id: String,
    pub node_id: String,
    pub node_type: NodeType,
    pub status: String,
    pub input_json: Option<String>,
    pub output_json: Option<String>,
    pub error_message: Option<String>,
    pub started_at: Option<String>,
    pub completed_at: Option<String>,
}

// ============================================================
// AI 相关模型 (V3)
// ============================================================

/// AI 提供商配置记录
///
/// 自定义 Debug: api_key 脱敏为 `"sk-****"`, model_configs / config 脱敏为 `"***"`。
#[derive(Clone, Serialize, Deserialize)]
pub struct AiProviderRecord {
    pub id: String,
    pub name: String,
    pub provider_type: String,
    pub api_key: String,
    pub base_url: String,
    pub default_model: String,
    pub models: Option<String>,       // JSON array of model names
    /// 模型能力配置数组(4 维度 + 路由控制)。
    /// DB TEXT 列存 JSON 字符串,from_row 经 deserialize_model_configs 解析。
    /// 向后兼容:老库 NULL/空/老字符串数组 → 空 Vec 或转默认 ModelConfig。default_model 保留过渡。
    #[serde(default, deserialize_with = "deserialize_model_configs")]
    pub model_configs: Vec<ModelConfig>,
    pub is_default: bool,
    pub config: Option<String>,       // JSON extra config
    pub created_at: String,
    pub updated_at: String,
    /// provider 是否进入负载均衡池。false = 仅作为配置存在,不参与主链路由/选池。
    /// 老库迁移默认 1(单 provider 路径零变化)。
    #[serde(default = "default_enabled")]
    pub enabled: bool,
    /// provider 在负载均衡池中的选择权重(0-100)。高权重优先被选为主;
    /// 同权重时退化近似轮询。老库迁移默认 50。
    #[serde(default = "default_weight")]
    pub weight: u32,
}

impl std::fmt::Debug for AiProviderRecord {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AiProviderRecord")
            .field("id", &self.id)
            .field("name", &self.name)
            .field("provider_type", &self.provider_type)
            .field("api_key", &"sk-****")
            .field("base_url", &self.base_url)
            .field("default_model", &self.default_model)
            .field("models", &self.models)
            .field("model_configs", &"***")
            .field("is_default", &self.is_default)
            .field("config", &"***")
            .field("created_at", &self.created_at)
            .field("updated_at", &self.updated_at)
            .field("enabled", &self.enabled)
            .field("weight", &self.weight)
            .finish()
    }
}

/// AiProviderRecord.enabled 的 serde 默认(true)。老库/缺字段 JSON → enabled。
fn default_enabled() -> bool {
    true
}

/// AiProviderRecord.weight 的 serde 默认(50)。老库/缺字段 JSON → 50。
fn default_weight() -> u32 {
    50
}

/// TaskRecord.queue 的 serde 默认("todo",知识图谱 Phase 1 V29)。老库/缺字段 JSON → "todo"。
/// 对齐 DB 列 DEFAULT 'todo' 语义,保证字段始终非空 String。
fn default_queue() -> String {
    "todo".to_string()
}

/// AI 对话记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiConversationRecord {
    pub id: String,
    pub title: Option<String>,
    pub messages: String,             // JSON array of ChatMessage
    pub provider_id: Option<String>,
    pub model: Option<String>,
    pub models: Option<String>,             // 用过的所有 model(JSON 数组字符串,去重)
    pub archived: bool,                 // 是否归档(侧栏折叠展示)
    pub pinned: bool,                   // 是否置顶(排序置前;UX-17)
    pub prompt_tokens: Option<i64>,         // 输入 token 累计(流式 usage 落库)
    pub completion_tokens: Option<i64>,     // 输出 token 累计(流式 usage 落库)
    pub pinned_goals: Option<String>,       // 对话目标钉扎持久化(JSON GoalEntry 数组 [{text,status}],兼容旧 ["text"] 格式,默认'[]')
    pub pending_approvals: Option<String>,  // 挂起审批快照(JSON 对象,以 tool_call_id 为键,默认'{}')
    pub created_at: String,
    pub updated_at: String,
}

/// AI 工具执行审计记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiToolExecutionRecord {
    pub id: String,
    pub conversation_id: Option<String>,
    /// 消息级溯源:工具调用所属的 ChatMessage.id。
    /// NULL = 老库行 / 消息级溯源未启用期的记录 / 无法关联的调用。
    /// 升级后,audit 写入从 ContextManager 取当前 assistant 消息 id 填入。
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message_id: Option<String>,
    pub tool_call_id: String,
    pub tool_name: String,
    pub arguments: String,
    pub result: Option<String>,
    pub status: String,               // pending/approved/rejected/executing/completed/failed
    pub risk_level: String,           // low/medium/high
    pub requested_at: String,
    pub executed_at: Option<String>,
    pub decided_by: Option<String>,   // human/auto
}

/// 消息记录(ai_messages 表,消息拆分存储)。
///
/// 每条 ChatMessage 一行,替代 `ai_conversations.messages` 的整对话 JSON 列存。
/// 主键 `id` = ChatMessage.id(构造时 ULID 风格生成),(conversation_id, seq) UNIQUE
/// 保证对话内序号唯一。本结构仅承载持久化映射,P0 阶段 ContextManager 仍走旧 JSON 列,
/// 切读策略待用户决策(P2)。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiMessageRecord {
    /// 消息全局唯一 ID(= ChatMessage.id),主键
    pub id: String,
    /// 关联对话 ID
    pub conversation_id: String,
    /// 对话内序号(排序用,从 0 起)
    pub seq: i64,
    /// 消息角色:system/user/assistant/tool
    pub role: String,
    /// 文本内容(parts 的 Text 片与 content 一致)
    pub content: String,
    /// 多模态 parts JSON(可空)
    pub parts: Option<String>,
    /// 工具调用 ID(role=Tool 时必填)
    pub tool_call_id: Option<String>,
    /// AI 发起的工具调用 JSON(可空)
    pub tool_calls: Option<String>,
    /// 生成该消息的 model(仅 assistant)
    pub model: Option<String>,
    /// 消息状态:active/compressed/archived_segment/truncated(默认 active)
    pub status: String,
    /// DeepSeek thinking 推理内容(可空)
    pub reasoning_content: Option<String>,
    /// 消息创建时间(Unix 毫秒,可空)
    pub timestamp: Option<i64>,
    /// 落库时间字符串(迁移期 fallback 到对话 created_at)
    pub created_at: String,
    /// 本轮 LLM 调用输入 token 用量(仅 assistant,消息级 token 持久化)。
    /// 老/NULL → ChatMessage.prompt_tokens=None(向前兼容)。
    pub prompt_tokens: Option<u32>,
    /// 本轮 LLM 调用输出 token 用量(仅 assistant,消息级 token 持久化)。
    pub completion_tokens: Option<u32>,
}

// ============================================================
// 知识库模型 (V7)
// ============================================================

/// 知识条目记录(经验沉淀基本单元,共享记忆层)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgeRecord {
    pub id: String,
    pub kind: String,                  // KnowledgeKind snake_case(7 种)
    pub title: String,
    pub content: String,
    pub tags: Option<String>,          // JSON 数组字符串
    pub status: String,                // candidate|pending_review|published|archived
    pub confidence: Option<String>,    // high|medium|low(AI 提炼自评,可空)
    pub reuse_count: i32,              // 唯一客观排序信号
    pub verified: bool,                // 发布审核时一次性人工标
    pub source_project: Option<String>, // 来源项目(仅溯源不过滤)
    pub source_ref: Option<String>,    // 来源实体引用(如 conv:{id})
    pub reasoning: Option<String>,     // AI 提炼判断依据("为何值得沉淀"),手动录入为 None
    /// 嵌入生成状态(V23):None/pending=未生成,done=成功,failed=失败可重试。
    ///
    /// P1 修复(嵌入失败无标记):spawn_embedding_for_knowledge 此前 fire-and-forget,
    /// provider 临时不可用 → 仅 warn,该条永久无向量索引无人感知。
    /// 现写入 done/failed,failed 可由 knowledge_retry_embedding 触发补偿重试。
    /// `#[serde(default)]` 兼容老前端/老 JSON(未带该字段反序列化为 None)。
    #[serde(default)]
    pub embedding_status: Option<String>,
    pub created_at: String,
    pub updated_at: String,
}

/// 知识生命线事件记录(追加型审计:产生/审核/引用/归档)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgeEventRecord {
    pub id: String,
    pub knowledge_id: String,
    pub event_type: String,            // created | extracted | status_changed | referenced | archived
    pub source_ref: Option<String>,    // 触发来源: conv:{id} / manual / system
    pub context_json: Option<String>,  // JSON: 因 event_type 而异
    pub timestamp: String,
}

/// 工程记录(project_modules 表,项目多工程,每个工程独立代码仓库)
///
/// 一个项目可含多个工程(Monorepo 多仓库 / 微服务 / 前后端分离)。
/// 每个工程有独立的目录(`path`)、Git 地址(`git_url`)、技术栈(`stack`)。
/// Git 状态(分支/改动/提交)是实时派生的(查 git 命令),不存表。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectModuleRecord {
    pub id: String,
    pub project_id: String,
    pub name: String,
    pub path: String,
    pub git_url: Option<String>,
    pub stack: Option<String>,          // 技术栈 JSON 字符串
    pub auto_detected: bool,
    pub sort_order: i32,
    pub created_at: String,
    pub updated_at: String,
}

/// 工程依赖关系记录(V35 module_dependencies 表)。
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ModuleDependencyRecord {
    pub id: String,
    pub project_id: String,
    pub from_module_id: String,
    pub to_module_id: String,
    /// 依赖类型:library / api / mq / shared / custom
    pub dep_type: String,
    /// 可选标签(自定义描述)
    pub label: Option<String>,
    pub created_at: String,
}

// ============================================================
// 多 Agent 并行执行模型 (V36)
// ============================================================

/// Plan 记录(一次用户消息触发的并行执行计划)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanRecord {
    pub id: String,
    pub conversation_id: String,
    /// 触发 Plan 的用户消息 id
    pub user_message_id: Option<String>,
    /// planning / executing / merging / done / error
    pub status: String,
    pub subtask_count: i64,
    pub created_at: String,
    pub completed_at: Option<String>,
}

/// SubTask 记录(Plan 拆解出的子任务)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubTaskRecord {
    pub id: String,
    pub plan_id: String,
    /// 分配的人设 id(coder/reviewer/architect/tester/analyst)
    pub persona_id: Option<String>,
    /// 子任务意图描述
    pub intent: String,
    /// pending / running / done / error
    pub status: String,
    /// DAG 层级(0 起)
    pub layer: i64,
    /// 依赖的 SubTask id 列表(JSON 数组字符串)
    pub deps: Option<String>,
    /// Git worktree 分支名(NULL=非 Git 工程降级串行)
    pub branch: Option<String>,
    pub created_at: String,
    pub completed_at: Option<String>,
}

/// 冲突记录(合并时检测到的文件/语义冲突)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConflictRecord {
    pub id: String,
    pub plan_id: String,
    pub file_path: String,
    /// file(同文件路径) / semantic(编译失败)
    pub conflict_type: String,
    pub subtask_a: Option<String>,
    pub subtask_b: Option<String>,
    pub diff_a: Option<String>,
    pub diff_b: Option<String>,
    /// pending / a / b / merged / manual
    pub resolution: String,
    /// reviewer / user
    pub resolved_by: Option<String>,
    pub created_at: String,
    pub resolved_at: Option<String>,
}
