/**
 * The wire contract between the browser, the API relay and the Piggy agent.
 *
 * Like `piggy-context`, this crosses process boundaries and is validated
 * `.strict()` at two of them, so it lives here once and every hop derives from
 * it. Unlike `piggy-context`, it also has to survive a harness swap: the events
 * below are PIG's own vocabulary, deliberately NOT Prime Agent's. The agent
 * runtime emits `message_update` / `tool_execution_start` / `turn_end` and a
 * dozen more; the chat server narrows that to the eight cases the product
 * actually renders. Keeping the translation on the server means a harness
 * upgrade is a server change, not a client one.
 *
 * Two things here are new to the agent era and worth stating plainly:
 *
 *   approval — a write tool in `confirm` mode does not perform its mutation. It
 *              returns a description of what it WOULD do and yields an
 *              `approval_required` event; the write happens only when the user
 *              answers. The model is told the write is pending, not done, so it
 *              cannot report success it has not achieved.
 *   model    — which model answered is part of the record. It varies per turn
 *              now that the user can choose, so it rides on the events rather
 *              than being read from configuration.
 */

/**
 * How far Piggy may act without being asked again.
 *
 * `read_only` keeps the pre-agent behaviour: no write tool is even offered to
 * the model, which is a stronger guarantee than offering one and refusing it.
 */
export const PIGGY_MODES = ['read_only', 'confirm', 'auto'] as const;
export type PiggyMode = (typeof PIGGY_MODES)[number];

/** Writes that always need a human, whatever the mode. */
export const PIGGY_ALWAYS_CONFIRM_KINDS = [
  'contract',
  'commitment',
  'allocation',
  'compliance',
] as const;
export type PiggyGuardedKind = (typeof PIGGY_ALWAYS_CONFIRM_KINDS)[number];

/**
 * A model the user may choose, as the UI needs it.
 *
 * `costPerMTokIn`/`Out` are US dollars per million tokens — NOT cents. This is
 * the one money field in PIG that is not an integer of cents, because that is
 * the unit every provider publishes and converting it here would invite the
 * same 100x error the units rule exists to prevent. The field names say so.
 */
export interface PiggyModelOption {
  /** Provider-qualified id, e.g. `nvidia/nemotron-3-nano-30b-a3b`. */
  id: string;
  label: string;
  /** Short note on when to reach for it, shown under the label. */
  hint?: string;
  costPerMTokIn: number;
  costPerMTokOut: number;
  contextWindow: number;
  /** True when the model supports a reasoning budget. */
  reasoning: boolean;
  /** The default the deployment ships with, when no user preference is stored. */
  isDefault?: boolean;
}

/** A change Piggy proposes but has not made. */
export interface PiggyProposedChange {
  /** Stable within a turn; the client answers with it. */
  id: string;
  /** The tool that proposed it, e.g. `pig_log_activity`. */
  tool: string;
  kind: string;
  /** One line, in the user's language: "Log a call on Northwind Robotics". */
  summary: string;
  /** Field-level detail for the diff card. Values are already display-formatted. */
  fields: { label: string; value: string; previous?: string }[];
  /** Set when the change targets an existing record the user can open. */
  record?: { type: string; id: string; label?: string };
  /** True when the mode would have applied this automatically but policy forbade it. */
  forcedConfirm?: boolean;
}

export type PiggyApprovalDecision = 'apply' | 'reject';

/**
 * Events the chat server streams as NDJSON.
 *
 * `content_delta` and `reasoning_delta` are unchanged from the pre-agent
 * protocol so the transcript renderer did not have to be rewritten around the
 * harness swap.
 */
export type PiggyChatEvent =
  | { type: 'meta'; model: string; mode: PiggyMode; conversationId: string }
  | { type: 'reasoning_delta'; delta: string }
  | { type: 'content_delta'; delta: string }
  | { type: 'tool_call'; id: string; name: string; arguments: unknown }
  | {
      type: 'tool_result';
      id: string;
      name: string;
      ok: boolean;
      result?: unknown;
      error?: string;
    }
  /** A write is waiting on the user. The turn stays open until it is answered. */
  | { type: 'approval_required'; change: PiggyProposedChange }
  /** The outcome of an answered approval, so the transcript can settle the card. */
  | {
      type: 'approval_resolved';
      changeId: string;
      decision: PiggyApprovalDecision;
      ok: boolean;
      error?: string;
    }
  | {
      type: 'done';
      inputTokens: number | null;
      outputTokens: number | null;
      /** Whole US cents spent on this turn, when the provider reported usage. */
      costMicroCents: number | null;
      /** `length` when the answer was cut short by the token budget. */
      finishReason?: string;
    }
  | { type: 'error'; message: string; code?: string; retryAfterSeconds?: number };

export type PiggyChatEventType = PiggyChatEvent['type'];

/** A stored conversation, as the workspace sidebar lists them. */
export interface PiggyConversationSummary {
  id: string;
  title: string;
  updatedAt: string;
  messageCount: number;
  /** Present while a turn is still streaming. */
  running?: boolean;
}

export function isGuardedKind(kind: string): kind is PiggyGuardedKind {
  return (PIGGY_ALWAYS_CONFIRM_KINDS as readonly string[]).includes(kind);
}

/**
 * Whether a proposed change may be applied without asking.
 *
 * Stated as one function, used by the agent runtime AND asserted by the tests,
 * so "auto mode still stops at a contract" cannot drift into being true in one
 * place and false in another.
 */
export function requiresApproval(mode: PiggyMode, kind: string): boolean {
  if (mode === 'read_only') return true;
  if (mode === 'confirm') return true;
  return isGuardedKind(kind);
}
