// Package contract 是 Gateway / Dispatcher / MCP 之间的共享契约：
// Task 数据结构与 NATS subject 命名约定。
package contract

import "encoding/json"

// NATS subject / stream 约定（与 README、各服务 config 保持一致）。
const (
	StreamTasks     = "SUNDYNIX_TASKS"   // JetStream stream 名
	SubjectTasks    = "sundynix.tasks"   // 任务发布主题前缀；实际为 sundynix.tasks.<id>
	SubjectTasksAll = "sundynix.tasks.>" // stream 捕获的通配
	SubjectStream   = "sundynix.streams" // Token 回流前缀；实际 sundynix.streams.<id>
	ConsumerDurable = "dispatchers"      // Dispatcher 持久消费者（队列组负载均衡）

	// HeaderStreamEnd 是 Token 流的结束信号（core NATS 消息头）。
	// 置为 "1" 的消息体为空，表示该 task 的 Token 流结束。
	HeaderStreamEnd = "X-Stream-End"

	// MCP 工具调用约定（第 4 层 Dispatcher → 第 5 层 MCP Tools）。
	// 用 core NATS request-reply：同步拿结果，队列组内负载均衡。
	SubjectToolsGo    = "sundynix.tools.go"   // Go I/O 型工具前缀；实际 sundynix.tools.go.<tool>
	SubjectToolsGoAll = "sundynix.tools.go.>" // mcp-go 通配订阅
	SubjectToolsPy    = "sundynix.tools.py"   // Python 算法型工具前缀；实际 sundynix.tools.py.<tool>
	SubjectToolsPyAll = "sundynix.tools.py.>" // mcp-py 通配订阅
	QueueToolsGo      = "mcp-go-workers"      // mcp-go 队列组（多副本负载均衡）
	QueueToolsPy      = "mcp-py-workers"      // mcp-py 队列组

	// QueueGateway 是网关侧事件订阅/配置应答的队列组：多网关副本下，每条
	// eval/usage/status 事件与每个 config 请求只由组内一个副本处理，避免重复落库/重复应答（HA）。
	QueueGateway = "gateway-workers"

	// 服务探活：dispatcher 既无 HTTP 端点也不挂工具，单独用一个 core NATS
	// request-reply 心跳主题让控制面（管理端「服务状态」）能判定它在不在线。
	SubjectHealthDispatcher = "sundynix.health.dispatcher"

	// 任务生命周期状态回写：dispatcher 开跑/跑完/出错经此主题广播，网关订阅落 PG 并推 UI。
	// core NATS pub-sub（状态是幂等覆盖，丢一条由下一条纠正，无需持久化）。
	// 注意：必须在 sundynix.tasks.> 之外，否则会被任务流捕获成"幽灵任务"自我放大。
	SubjectTaskStatus = "sundynix.status.task"

	// 人工审批（HITL）决定回传前缀：实际 sundynix.approval.<task_id>。
	// UI 点批准/拒绝 → 网关 nc.Publish 到此。阻塞模型经 core NATS WaitApproval 收；
	// 中断/恢复模型经下方 JetStream 流持久捕获、由 dispatcher 持久消费者驱动 resume（抗离线）。
	SubjectApproval    = "sundynix.approval"
	SubjectApprovalAll = "sundynix.approval.>" // 审批决定流捕获的通配
	StreamApprovals    = "SUNDYNIX_APPROVALS"  // 审批决定 JetStream 流（持久，决定不因 dispatcher 离线而丢）
	ConsumerApprovals  = "approval-resumers"   // 审批决定持久消费者（队列组：多副本下每条决定只一个副本处理 resume）

	// BucketCheckpoints 是 HITL 持久化中断的 JetStream KV 桶名：存 compose 图 checkpoint
	// （键=task_id）与 resume 记录（键=pending:task_id），dispatcher 重启后可据此恢复在途审批。
	BucketCheckpoints = "SUNDYNIX_CHECKPOINTS"

	// 自动化评测结果回写：dispatcher 评完经此广播，网关订阅落 PG 并供 UI 查询。core NATS pub-sub。
	SubjectEval = "sundynix.eval.task"

	// Token 用量回写：dispatcher 任务收尾经此广播本轮 token 用量，网关订阅累加到用户日预算并供计费。core NATS pub-sub。
	SubjectUsage = "sundynix.usage.task"
)

// UsageEvent 是一次任务的 token 用量（dispatcher 收尾经 SubjectUsage 回流给网关累计/计费）。
// dispatcher 不持有计价，只报原始 token + 模型名；真钱成本由网关据 Pricing 折算。
type UsageEvent struct {
	TaskID    string `json:"task_id"`
	UserID    string `json:"user_id,omitempty"`
	TenantID  string `json:"tenant_id,omitempty"` // 租户标识（按租户计量 / 计费）
	Model     string `json:"model,omitempty"`
	PromptTok int    `json:"prompt_tok"` // 输入 token（估算）
	CompTok   int    `json:"comp_tok"`   // 输出 token（估算）
	TotalTok  int    `json:"total_tok"`  // 合计
	Exceeded  bool   `json:"exceeded"`   // 是否触顶单任务预算被中止
	TS        int64  `json:"ts"`         // unix 毫秒
}

// 评测质量分级（据综合分 + 忠实度阈值，闭环门控/告警用）。
const (
	EvalOK   = "ok"   // 综合 ≥ 0.75 且无忠实度风险
	EvalWarn = "warn" // 0.5 ≤ 综合 < 0.75，或忠实度偏低
	EvalPoor = "poor" // 综合 < 0.5
)

// EvalEvent 是一次自动化评测的结果（经 SubjectEval 回流给网关落库）。
type EvalEvent struct {
	TaskID    string   `json:"task_id"`
	Overall   float64  `json:"overall"`             // 综合分 [0,1]
	Rule      float64  `json:"rule"`                // 规则分
	LLM       float64  `json:"llm"`                 // LLM 质量分
	Faithful  float64  `json:"faithful"`            // RAG 忠实度分（0=无来源未评）
	Level     string   `json:"level"`               // ok / warn / poor（纠偏后的终值）
	Flags     []string `json:"flags,omitempty"`     // 命中问题（规则 + 未被来源支持）
	Reason    string   `json:"reason,omitempty"`    // 评语
	Sources   int      `json:"sources,omitempty"`   // 检索来源数
	Corrected bool     `json:"corrected,omitempty"` // 是否经低分自动纠偏重生成后采纳（恒温器闭环）
	TS        int64    `json:"ts"`                  // unix 毫秒
}

// 任务生命周期状态机：submitted（网关建任务）→ running（dispatcher 开跑）
// → done / failed / timeout（dispatcher 收尾）。
// HITL：执行到审批节点 → waiting（等人工决定）→ 批准回 running / 拒绝→rejected。
const (
	TaskSubmitted = "submitted"
	TaskRunning   = "running"
	TaskDone      = "done"
	TaskFailed    = "failed"
	TaskTimeout   = "timeout"
	TaskWaiting   = "waiting"  // 等待人工审批
	TaskRejected  = "rejected" // 人工拒绝（或审批超时，安全默认拒绝）
)

// TaskStatusEvent 是一次任务状态流转事件（经 SubjectTaskStatus 回流给网关）。
type TaskStatusEvent struct {
	TaskID string `json:"task_id"`
	Status string `json:"status"`           // running / done / failed / timeout / waiting / rejected
	Detail string `json:"detail,omitempty"` // 失败原因 / 审批摘要等
	TS     int64  `json:"ts"`               // unix 毫秒
}

// ApprovalSubject 返回某任务的人工审批决定回传主题。
func ApprovalSubject(id string) string { return SubjectApproval + "." + id }

// ApprovalDecision 是一次人工审批的结果（UI → 网关 → dispatcher）。
type ApprovalDecision struct {
	TaskID   string `json:"task_id"`
	Node     string `json:"node,omitempty"` // 审批节点 id（可空：按 task 维度兜底匹配）
	Approved bool   `json:"approved"`       // true=批准放行，false=拒绝中止
	Note     string `json:"note,omitempty"` // 审批人备注
	By       string `json:"by,omitempty"`   // 审批人（用户 id）
	TS       int64  `json:"ts"`             // unix 毫秒
}

const (
	// MetaUserID 是 Task.Meta 中承载已登录用户标识的键（用于偏好记忆召回）。
	MetaUserID = "user_id"
	// MetaTenantID 是 Task.Meta 中承载租户标识的键（用于用量按租户计量 / 计费）。
	MetaTenantID = "tenant_id"
	// MetaSessionID 是 Task.Meta 中承载会话标识的键（用于短期多轮历史）。
	MetaSessionID = "session_id"
	// MetaSafetyCheck 是输入护栏「灰区升级」标志：网关 Tier1（归一化+正则）判为疑似但不确定时置 true，
	// Dispatcher 执行前据此调 LLM jailbreak 分类器（Tier2）裁决。明确干净/明确恶意的输入不带此标志，不付 LLM 成本。
	MetaSafetyCheck = "safety_check"
	// MetaTokenBudget 是单任务 token 预算上限（数字）：网关按用户/套餐下发，Dispatcher 据此封顶单任务用量，
	// 触顶即中止（防失控成本）。缺省用 Dispatcher 的 env TASK_TOKEN_BUDGET。
	MetaTokenBudget = "token_budget"

	// 配置控制面按 kind 寻址：sundynix.config.<kind>.get / .updated。
	// Gateway 持有配置，消费方(Dispatcher/mcp-go)经 NATS 取用/订阅变更。
	ConfigKindChat      = "chat"      // 对话模型（Dispatcher 用）
	ConfigKindEmbedding = "embedding" // 向量模型（mcp-go RAG 用）

	// 报告生成：Task.Meta[MetaIntent]==IntentReport 时，Dispatcher 走专用多步编排
	// （规划大纲 → 各章节并行检索+撰写 → 汇聚 → 渲染 Word），而非通用对话图。
	MetaIntent   = "intent"
	IntentReport = "report"
	MetaTopic    = "topic" // 报告主题
	MetaKB       = "kb"    // 报告依据的知识库（可空，则不挂检索）
)

// ConfigGetSubject / ConfigUpdatedSubject 返回某类配置的 request / 广播主题。
func ConfigGetSubject(kind string) string     { return "sundynix.config." + kind + ".get" }
func ConfigUpdatedSubject(kind string) string { return "sundynix.config." + kind + ".updated" }

// SubjectExec 是执行可视化事件的回流前缀；实际 sundynix.exec.<task_id>。
// 与 Token 流（sundynix.streams.<id>）分流：Token 是零拷贝字节，Exec 是结构化节点事件。
const SubjectExec = "sundynix.exec"

// ExecSubject 返回某任务的执行事件回流主题。
func ExecSubject(id string) string { return SubjectExec + "." + id }

// ExecEvent 是一次任务执行中某节点/阶段的生命周期事件（经 sundynix.exec.<id> 回流给 UI，
// 用于"运行·观测"的实时轨迹：节点点亮、工具调用入参/产出、各阶段耗时）。
type ExecEvent struct {
	Seq    int    `json:"seq"`              // 任务内自增序号（保序）
	TS     int64  `json:"ts"`               // unix 毫秒
	Node   string `json:"node"`             // 稳定节点 id：init / tool:wiki_search / prompt / model / plan / section:0 / render
	Kind   string `json:"kind"`             // 归类着色：memory|tool|prompt|model|plan|section|render|system
	Phase  string `json:"phase"`            // start|end|error|info
	Label  string `json:"label"`            // 人读标题
	Detail string `json:"detail,omitempty"` // 入参/产出/计数预览
	MS     int64  `json:"ms,omitempty"`     // end 事件的耗时（毫秒）
}

// TripleView 是回流给 UI 的一条知识三元组（主体-关系-客体），用于实时展示抽取过程与图谱。
type TripleView struct {
	S string `json:"s"`
	P string `json:"p"`
	O string `json:"o"`
}

// IngestEvent 是入库流水线的实时进度事件（经 sundynix.streams.<job_id> 回流给 UI）。
type IngestEvent struct {
	Stage   string       `json:"stage"`             // 解析/解析完成/切块/向量化/写Milvus/写Bleve/抽实体/写Neo4j/完成/失败
	Msg     string       `json:"msg,omitempty"`     // 文案
	Done    int          `json:"done,omitempty"`    // 进度（如已向量化块数）
	Total   int          `json:"total,omitempty"`   // 总数
	Chunks  []string     `json:"chunks,omitempty"`  // 切块预览（切块阶段发一次）
	Preview string       `json:"preview,omitempty"` // 解析阶段：解析出的文本片段预览
	Triples []TripleView `json:"triples,omitempty"` // 抽实体阶段：LLM 抽出的知识三元组（实时浮现 + 喂图谱）
	Error   string       `json:"error,omitempty"`
}

// ModelConfig 是一个模型后端的连接配置（provider 抽象，chat 与 embedding 同形）。
// 开发期指向第三方在线 API（OpenAI 兼容）；生产期可换自部署或其它在线模型。
type ModelConfig struct {
	Provider string `json:"provider"` // openai-compatible / vllm / ...
	BaseURL  string `json:"base_url"` // 如 https://api.deepseek.com
	APIKey   string `json:"api_key,omitempty"`
	Model    string `json:"model"` // 如 deepseek-chat / text-embedding-v3
	// Fallbacks 是主模型调用失败/超时时按序切换的备用模型（仅 chat 用，骑在主配置里一并下发）。
	// 网关把"其它 enabled chat 模型"填进来；dispatcher 据此把模型包成 failover 链。
	Fallbacks []ModelConfig `json:"fallbacks,omitempty"`
}

// Ready 报告该配置是否足以发起真实推理。
func (m *ModelConfig) Ready() bool {
	return m != nil && m.BaseURL != "" && m.Model != ""
}

// Task 是 DSL 解析组装后的可调度任务，在 NATS 上以 JSON 传输。
type Task struct {
	ID    string          `json:"id"`
	Graph json.RawMessage `json:"graph"` // React Flow 导出的 Agent 编排图
	Meta  map[string]any  `json:"meta,omitempty"`
}

// TaskSubject 返回某任务的发布主题。
func TaskSubject(id string) string { return SubjectTasks + "." + id }

// StreamSubject 返回某任务的 Token 回流主题。
func StreamSubject(id string) string { return SubjectStream + "." + id }

// ToolSubjectGo / ToolSubjectPy 返回某工具的调用主题。
func ToolSubjectGo(tool string) string { return SubjectToolsGo + "." + tool }
func ToolSubjectPy(tool string) string { return SubjectToolsPy + "." + tool }

// ToolCall 是 Dispatcher 对一个 MCP 工具的调用请求（NATS request 体）。
type ToolCall struct {
	Tool   string         `json:"tool"`              // 工具名，如 wiki_search
	Args   map[string]any `json:"args,omitempty"`    // 工具参数
	TaskID string         `json:"task_id,omitempty"` // 触发该调用的任务（便于追踪）
}

// ToolResult 是 MCP 工具的应答（NATS reply 体）。
type ToolResult struct {
	OK      bool   `json:"ok"`
	Content string `json:"content,omitempty"` // 工具产出（如检索结果文本）
	Error   string `json:"error,omitempty"`   // 非空表示工具内部出错
}

// Marshal / Unmarshal 便捷方法。
func (t *Task) Marshal() ([]byte, error) { return json.Marshal(t) }
func Unmarshal(b []byte) (*Task, error) {
	var t Task
	if err := json.Unmarshal(b, &t); err != nil {
		return nil, err
	}
	return &t, nil
}
