// File: src/types/observation.ts
import { Schema } from "effect";

// ─── Observation Category ───

export const ObservationCategory = Schema.Literal(
  "file-write",
  "file-read",
  "web-search",
  "http-get",
  "code-execute",
  "agent-delegate",
  "scratchpad",
  "custom",
  "error",
);
export type ObservationCategory = typeof ObservationCategory.Type;

// ─── Result Kind ───

export const ResultKind = Schema.Literal("side-effect", "data", "error");
export type ResultKind = typeof ResultKind.Type;

// ─── Trust Level (Phase 1 S2.3) ─────────────────────────────────────────────
//
// Q5 resolved (user-confirmed 2026-04-24): internal meta-tools grandfather
// to "trusted" with trustJustification: "grandfather-phase-1". CI lint will
// enforce real justifications by Phase 3.
//
// trustLevel governs how ContextCurator (S2.5) renders this observation:
//   - "trusted":   safe to render inline in the system prompt
//   - "untrusted": must go in <tool_output> blocks where any prompt-injection
//                  content from the tool's output can't escape the role boundary
//
// User-defined tools default to "untrusted" because their output could contain
// adversarial content (web-search result, file contents, etc.). Internal
// meta-tools generated by the framework are "trusted" because their output
// is framework-controlled.

export const TrustLevel = Schema.Literal("trusted", "untrusted");
export type TrustLevel = typeof TrustLevel.Type;

/**
 * Tool names whose ObservationResult is automatically marked trustLevel:
 * "trusted" by makeObservationResult. The grandfather list per Q5 — every
 * entry below is a framework-internal meta-tool whose output is
 * framework-controlled (not user/LLM/network input).
 *
 * Adding a name here is a deliberate trust assertion. Phase 3 CI lint
 * fails the build for any "trusted" entry whose justification is still
 * the placeholder "grandfather-phase-1".
 */
export const KNOWN_TRUSTED_TOOL_NAMES: ReadonlySet<string> = new Set([
  // Inline meta-tools (handled by metaToolRegistry in act.ts)
  "brief",
  "pulse",
  "activate-skill",
  // Inline meta-tools (handled directly in act.ts; not in registry)
  "final-answer",
  // ToolService-routed meta-tools that are framework-internal
  "recall",
  "find",
  "checkpoint",
  "harness-deliverable",
]);

/**
 * Standard justification stamp for the grandfather batch. Phase 3 lint
 * fails any ObservationResult that still carries this exact string.
 */
export const GRANDFATHER_TRUST_JUSTIFICATION = "grandfather-phase-1";

// ─── Observation Result ───

export const ObservationResultSchema = Schema.Struct({
  success: Schema.Boolean,
  toolName: Schema.String,
  displayText: Schema.String,
  category: ObservationCategory,
  resultKind: ResultKind,
  preserveOnCompaction: Schema.Boolean,
  delegatedToolsUsed: Schema.optional(Schema.Array(Schema.String)),
  trustLevel: TrustLevel,
  /**
   * Free-text justification when trustLevel === "trusted". Required by
   * construction-site discipline (makeObservationResult always pairs them);
   * Schema doesn't enforce conditional presence today. Phase 3 CI lint will
   * fail the build for any "trusted" entry whose justification is the
   * grandfather placeholder.
   */
  trustJustification: Schema.optional(Schema.String),
});
export type ObservationResult = typeof ObservationResultSchema.Type;

// ─── Category Mapping ───

const TOOL_CATEGORY_MAP: Record<string, ObservationCategory> = {
  "file-write": "file-write",
  "file-read": "file-read",
  "web-search": "web-search",
  "http-get": "http-get",
  "code-execute": "code-execute",
  "recall": "scratchpad",
  "spawn-agent": "agent-delegate",
};

export const categorizeToolName = (toolName: string): ObservationCategory =>
  TOOL_CATEGORY_MAP[toolName] ?? (toolName.startsWith("agent-") ? "agent-delegate" : "custom");

// ─── Result Kind Mapping ───

const SIDE_EFFECT_CATEGORIES: ReadonlySet<ObservationCategory> = new Set([
  "file-write",
  "code-execute",
  "scratchpad",
]);

export const deriveResultKind = (
  category: ObservationCategory,
  success: boolean,
): ResultKind => {
  if (!success) return "error";
  return SIDE_EFFECT_CATEGORIES.has(category) ? "side-effect" : "data";
};
