import type { ZodSchema } from 'zod';
import type { SafetyRule } from './core/safety-gate.service';

/**
 * AiCall — harness 核心契约。
 *
 * 每个 AiCall 是一次 LLM 调用的"配方":model + prompt + schema + safety rules + fallback。
 * AiCallRunner 拿到 call + input,统一跑 cache → invoke → safety → record 全流程。
 *
 * 设计原则(对照 Anthropic "Building Effective Agents"):
 *   1. 单任务:一个 call 只做一件事,不在 prompt 里塞多任务
 *   2. 输出强约束:必须 Zod schema(走 generateObject 走 JSON mode)
 *   3. 输入最小化:input 只装这个 call 真正用到的字段(不要把全量 patient/persona 塞进来)
 *   4. 版本化:promptVersion 改了就 bump,audit/eval 才能归因
 *   5. 可降级:fallback 是模板兜底,LLM 失败 / safety 拒收时走这条路
 *
 * kind 是分类(粗,做仪表盘);callKey 是身份(细,做缓存/eval 索引)。
 */
export interface AiCall<TInput, TOutput> {
  /** 粗分类,落 AgentInvocation.kind */
  readonly kind: 'script' | 'summary' | 'persona_feature' | 'judge' | 'ranking' | 'plan_analysis';

  /** 细身份,落 AgentInvocation.callKey,全 codebase 唯一 */
  readonly callKey: string;

  /** prompt 版本(改 prompt 必须 bump),格式 "{callKey}@{date}-{letter}" */
  readonly promptVersion: string;

  /** 默认模型 id(可被调用方覆盖,如 eval 时切 v4-flash 跑大批量) */
  readonly defaultModelId: string;

  /** 输出 schema(必须),走 generateObject 强约束 */
  readonly outputSchema: ZodSchema<TOutput>;

  /** 构建 prompt:返回 system + prompt 两段;system 是稳定指令,prompt 是 input 装配 */
  buildPrompt(input: TInput): { system: string; prompt: string };

  /** safety rules(可选;不挂等于没校验) */
  readonly safetyRules?: ReadonlyArray<SafetyRule<TOutput>>;

  /** 降级 fallback(可选;LLM 失败/safety 拒收时用) */
  fallback?(input: TInput, reason: string): TOutput;
}

/**
 * 一次 AI 调用的执行上下文(runner 注入)
 */
export interface AiCallContext {
  hostId: string;
  tenantId: string;
  workflowRunId: string;          // 一次业务行为的 run id;同 plan 多次 AI 调用共享
  parentInvocationId?: string;
  linkedPatientId?: string;
  linkedPersonaId?: string;
  linkedPlanId?: string;
  /** 跑 eval 时不走 production 路径,标记此次调用模式 */
  evalMode?: 'production' | 'golden_set' | 'shadow' | 'manual';
  goldenSetCaseId?: string;
  /** 强制跳过缓存(重新生成场景) */
  bustCache?: boolean;
  /** 覆盖默认模型(eval 切 flash 跑大批量;production 通常不传) */
  modelIdOverride?: string;
  /** 取消信号(SSE 客户端断连时 abort)→ 透传给 generateObject/streamObject 取消在途 LLM 请求,
   *  避免用户关页面/点停止后,深度档仍把 3-4 次调用跑完烧钱。abort 时不走兜底、不落 PlanScript。 */
  signal?: AbortSignal;
}

export interface AiCallResult<TOutput> {
  output: TOutput;
  invocationId: string;
  cacheHit: boolean;
  /** 'agent' 正常出;'template_fallback' 走了 fallback */
  source: 'agent' | 'template_fallback';
  fallbackReason?: string;
  promptTokens: number;
  completionTokens: number;
  costYuan: number;
}
