/**
 * Decopilot SSE Event Types
 *
 * Canonical type definitions for thread statuses and decopilot SSE events.
 * Shared between server (emitter) and client (consumer) for full type safety.
 */

// ============================================================================
// Thread Status
// ============================================================================

/** Persisted thread statuses (written to DB). */
export const THREAD_STATUSES = [
  "in_progress",
  "requires_action",
  "failed",
  "completed",
] as const;
export type ThreadStatus = (typeof THREAD_STATUSES)[number];

/**
 * Display statuses include "expired" — a virtual status computed at read time
 * for threads stuck in "in_progress" beyond a timeout threshold.
 * Never persisted to DB, but appears in API responses and UI.
 */
export const THREAD_DISPLAY_STATUSES = [...THREAD_STATUSES, "expired"] as const;
export type ThreadDisplayStatus = (typeof THREAD_DISPLAY_STATUSES)[number];

// ============================================================================
// SSE Event Type Constants
// ============================================================================

export const DECOPILOT_EVENTS = {
  STEP: "decopilot.step",
  FINISH: "decopilot.finish",
  THREAD_STATUS: "decopilot.thread.status",
} as const;

export type DecopilotEventType =
  (typeof DECOPILOT_EVENTS)[keyof typeof DECOPILOT_EVENTS];

export const ALL_DECOPILOT_EVENT_TYPES: DecopilotEventType[] =
  Object.values(DECOPILOT_EVENTS);

// ============================================================================
// Event Payloads (discriminated union on `type`)
// ============================================================================

interface BaseDecopilotEvent {
  id: string;
  source: "decopilot";
  /** Thread ID this event relates to */
  subject: string;
  time: string;
}

export interface DecopilotStepEvent extends BaseDecopilotEvent {
  type: typeof DECOPILOT_EVENTS.STEP;
  data: { stepCount: number };
}

export interface DecopilotFinishEvent extends BaseDecopilotEvent {
  type: typeof DECOPILOT_EVENTS.FINISH;
  data: { status: ThreadStatus };
}

export interface DecopilotThreadStatusEvent extends BaseDecopilotEvent {
  type: typeof DECOPILOT_EVENTS.THREAD_STATUS;
  data: {
    status: ThreadStatus;
    /** The user message this turn executes — present on RUN_STARTED-driven
     *  "in_progress" events so the queue tray can flip exactly this item
     *  into the chat body at dispatch, without a fetch. */
    message_id?: string;
    virtual_mcp_id?: string;
    /** User who created the thread; needed to populate filter-complete cache rows on the client. */
    created_by?: string;
    /** Automation trigger id; null for human-initiated, omitted when unknown. */
    trigger_id?: string | null;
    /** Thread title at emit time. Absent if caller didn't load the row. */
    title?: string;
    /** Branch this thread is pinned to (null when unpinned). Absent if caller didn't load the row. */
    branch?: string | null;
    /** Harness the thread is pinned to — null until its first run pins it.
     *  The chat client picks its live-update mechanism off this (a batch
     *  harness has no per-thread stream to follow), so a row the client only
     *  ever sees through these events must carry it. Absent if the caller
     *  didn't load the row. */
    harness_id?: string | null;
    /** Thread creation timestamp. Absent if caller didn't load the row. */
    created_at?: string;
    /** Last update timestamp; useful for the client to sort/dedupe. Absent if caller didn't load the row. */
    updated_at?: string;
    /** Free-form thread metadata snapshot. The chat UI keys off
     *  metadata.kind to switch between agent-thread and tool_call_run
     *  renderings (avatar, message-renderer), so the workflow that
     *  spawns those threads must include it on the first event or the
     *  row renders with the wrong icon until a refetch. */
    metadata?: Record<string, unknown>;
  };
}

export type DecopilotSSEEvent =
  | DecopilotStepEvent
  | DecopilotFinishEvent
  | DecopilotThreadStatusEvent;

/** Map from event type string → typed payload (useful for generic handlers) */
export interface DecopilotEventMap {
  [DECOPILOT_EVENTS.STEP]: DecopilotStepEvent;
  [DECOPILOT_EVENTS.FINISH]: DecopilotFinishEvent;
  [DECOPILOT_EVENTS.THREAD_STATUS]: DecopilotThreadStatusEvent;
}

// ============================================================================
// Server-side Factories (create typed events for SSEHub.emit)
// ============================================================================

export function createDecopilotStepEvent(
  taskId: string,
  stepCount: number,
): DecopilotStepEvent {
  return {
    id: crypto.randomUUID(),
    type: DECOPILOT_EVENTS.STEP,
    source: "decopilot",
    subject: taskId,
    data: { stepCount },
    time: new Date().toISOString(),
  };
}

export function createDecopilotFinishEvent(
  taskId: string,
  status: ThreadStatus,
): DecopilotFinishEvent {
  return {
    id: crypto.randomUUID(),
    type: DECOPILOT_EVENTS.FINISH,
    source: "decopilot",
    subject: taskId,
    data: { status },
    time: new Date().toISOString(),
  };
}

export function createDecopilotThreadStatusEvent(
  taskId: string,
  status: ThreadStatus,
  opts?: {
    virtualMcpId?: string;
    createdBy?: string;
    triggerId?: string | null;
    title?: string;
    branch?: string | null;
    harnessId?: string | null;
    createdAt?: string;
    updatedAt?: string;
    metadata?: Record<string, unknown>;
    messageId?: string;
  },
): DecopilotThreadStatusEvent {
  return {
    id: crypto.randomUUID(),
    type: DECOPILOT_EVENTS.THREAD_STATUS,
    source: "decopilot",
    subject: taskId,
    data: {
      status,
      ...(opts?.messageId !== undefined && { message_id: opts.messageId }),
      ...(opts?.virtualMcpId !== undefined && {
        virtual_mcp_id: opts.virtualMcpId,
      }),
      ...(opts?.createdBy !== undefined && { created_by: opts.createdBy }),
      ...(opts?.triggerId !== undefined && { trigger_id: opts.triggerId }),
      ...(opts?.title !== undefined && { title: opts.title }),
      ...(opts?.branch !== undefined && { branch: opts.branch }),
      ...(opts?.harnessId !== undefined && { harness_id: opts.harnessId }),
      ...(opts?.createdAt !== undefined && { created_at: opts.createdAt }),
      ...(opts?.updatedAt !== undefined && { updated_at: opts.updatedAt }),
      ...(opts?.metadata !== undefined && { metadata: opts.metadata }),
    },
    time: new Date().toISOString(),
  };
}
