/**
 * remote/event-name-map — legacy ↔ canonical AG-UI event type mapping.
 *
 * The Vauban SDK has emitted dotted-lowercase event types (`run.start`,
 * `assistant.delta`, `tool.call.start`, etc.) since 2.11.0. The AG-UI
 * canonical vocabulary (Linux Foundation, late 2025) defines those same
 * lifecycle events as UPPER_SNAKE_CASE (`RUN_STARTED`, `TEXT_MESSAGE_CONTENT`,
 * `TOOL_CALL_START`, etc.). This file is the SSOT of the bidirectional
 * mapping ; the hub uses it to translate type names at the emit boundary
 * when `eventNaming: "canonical"` is requested.
 *
 * Two Vauban extensions go beyond AG-UI (`tool.intent`, `instruction.injected`,
 * `hitl.request`, `hitl.resolved`) ; we project them onto the closest AG-UI
 * `CUSTOM`-prefixed name with a Vauban suffix so the conformance suite's
 * vocabulary check still passes. The `name` field of a `CUSTOM` event would
 * normally carry the Vauban-specific identifier ; here we lift the Vauban
 * semantic into the type itself for round-tripping fidelity, then declare
 * the projection as `CUSTOM_*` so the AG-UI vocabulary recognises it as a
 * customisation extension.
 *
 * **2.27.0 deprecation policy**
 *   - 2.27.0 : both names accepted on input ; default emit = legacy ; opt-in
 *     `eventNaming: "canonical"` flips emission to AG-UI canonical.
 *   - 2.28.0 : default emit flips to canonical ; legacy still accepted on input.
 *   - 2.29.0 : legacy aliases removed from documentation, deprecation warning
 *     on any legacy-mode emit.
 *   - 3.0.0  : legacy event type strings removed entirely.
 *
 * @public @since 2.27.0
 */

/**
 * Authoritative legacy → canonical map. Every key is a Vauban dotted-lowercase
 * event type string ; every value is the equivalent AG-UI canonical name.
 *
 * Gaps (events that have no direct AG-UI equivalent) are projected onto
 * `CUSTOM_*` so the conformance suite's closed-vocabulary check still passes
 * while Vauban semantic survives round-tripping.
 */
export const LEGACY_TO_CANONICAL: Record<string, string> = {
  // ─── Lifecycle (direct AG-UI mapping) ───────────────────────────────────────
  "run.start": "RUN_STARTED",
  "run.step": "STEP_STARTED",
  "run.finished": "RUN_FINISHED",

  // ─── Text messages (direct AG-UI mapping) ───────────────────────────────────
  "assistant.delta": "TEXT_MESSAGE_CONTENT",
  "assistant.message": "TEXT_MESSAGE_END",

  // ─── Tool calls (direct AG-UI mapping ; Vauban veto-window has no AG-UI peer) ─
  "tool.call.start": "TOOL_CALL_START",
  "tool.call.end": "TOOL_CALL_END",

  // ─── State (direct AG-UI mapping) ───────────────────────────────────────────
  state: "STATE_SNAPSHOT",

  // ─── Vauban extensions (projected onto AG-UI CUSTOM_* keeping closed vocab) ─
  // Veto window — Vauban's transparency-before-action lever, no AG-UI peer.
  "tool.intent": "CUSTOM_TOOL_INTENT",
  // HITL request / resolve — AG-UI handles HITL via RUN_FINISHED(interrupt) ;
  // Vauban exposes the lifecycle directly so a remote client can render UI.
  "hitl.request": "CUSTOM_HITL_REQUEST",
  "hitl.resolved": "CUSTOM_HITL_RESOLVED",
  // Mid-run steering instruction injection ; no AG-UI equivalent.
  "instruction.injected": "CUSTOM_INSTRUCTION_INJECTED",
};

/**
 * Inverted map — every canonical name maps back to its legacy alias. Useful
 * when downstream consumers configured for legacy mode receive a canonical
 * event off the wire and need to normalise it for legacy-aware handlers.
 */
export const CANONICAL_TO_LEGACY: Record<string, string> = Object.fromEntries(
  Object.entries(LEGACY_TO_CANONICAL).map(([legacy, canonical]) => [
    canonical,
    legacy,
  ])
);

/**
 * Every event type name (legacy OR canonical) that the SDK recognises.
 * Used by `looksLikeSessionEvent` for the permissive type guard.
 */
export const ALL_KNOWN_EVENT_TYPES: ReadonlySet<string> = new Set([
  ...Object.keys(LEGACY_TO_CANONICAL),
  ...Object.values(LEGACY_TO_CANONICAL),
]);

/** Event naming convention chosen by the hub at construction time. */
export type EventNamingMode = "legacy" | "canonical";

/**
 * Normalise an event `type` string to the requested naming mode.
 *
 * Given a type that is already in the target convention, return it
 * unchanged. Given a type in the OTHER convention, translate it. Given
 * an unknown string (not in either convention), return it unchanged
 * so an unknown CUSTOM event still flows through verbatim.
 */
export function normalizeEventType(
  type: string,
  mode: EventNamingMode
): string {
  if (mode === "canonical") {
    // Already canonical → unchanged.
    if (CANONICAL_TO_LEGACY[type] !== undefined) return type;
    // Legacy → translate.
    if (LEGACY_TO_CANONICAL[type] !== undefined) {
      return LEGACY_TO_CANONICAL[type];
    }
    return type;
  }
  // mode === "legacy"
  // Already legacy → unchanged.
  if (LEGACY_TO_CANONICAL[type] !== undefined) return type;
  // Canonical → translate.
  if (CANONICAL_TO_LEGACY[type] !== undefined) {
    return CANONICAL_TO_LEGACY[type];
  }
  return type;
}

/**
 * Return true if the input `type` string is a known Vauban or canonical
 * AG-UI event type. The boundary is intentionally permissive ; consumers
 * that need stricter validation should `validateEvent` against the AG-UI
 * spec separately.
 */
export function isKnownEventType(type: string): boolean {
  return ALL_KNOWN_EVENT_TYPES.has(type);
}
