/** A media/file part attached to a message (contentRef points at object storage). */
export interface Attachment {
  contentRef: string;
  mimeType: string;
  sizeBytes: number;
  kind: 'image' | 'video' | 'audio' | 'file';
}

/** A generic annotation aggregate on a message (e.g. type "reaction", value = emoji). */
export interface AnnotationGroup {
  type: string;
  value: string;
  users: string[]; // usernames who applied it
}

/** Wire shapes the kernel emits over socket / returns over REST (align with service). */
export interface Message {
  id: string;
  threadId: string;
  senderActorId: string;
  senderId: string; // sender's email/username — reliable "is this mine?" check
  senderName: string;
  content: string;
  contentRef?: string;
  attachment?: Attachment;
  parentInteractionId?: string;
  annotations?: AnnotationGroup[];
  traceId?: string;
  createdAt: string;
}

export interface OpenThreadResult {
  threadId: string;
  status: string;
  history: Message[];
}

export interface ReceiptEvent {
  interactionId: string;
  actorId: string;
  kind: 'READ' | 'DELIVERED';
}

export interface TypingEvent {
  threadId: string;
  userId: string;
}

/** Broadcast when someone toggles an annotation — carries the refreshed user list. */
export interface AnnotationEvent {
  threadId: string;
  interactionId: string;
  type: string;
  value: string;
  op: 'add' | 'remove';
  users: string[];
  userId: string;
}

export interface MessageEvents {
  message: (m: Message) => void;
  receipt: (e: ReceiptEvent) => void;
  typing: (e: TypingEvent) => void;
  annotation: (e: AnnotationEvent) => void;
}

/** A discoverable thread from GET /v1/threads/discover — includes ones you have NOT joined. */
export interface DiscoveredThread {
  threadId: string;
  subject: string | null;
  metadata: Record<string, unknown> | null;
  participantCount: number;
  joined: boolean;
}

/** A "my threads" entry from GET /v1/threads (server-authoritative). */
export interface ThreadSummary {
  threadId: string;
  subject: string | null;
  membership?: string; // 'dm' | 'group' (opaque app attribute)
  /** The thread's opaque, app-supplied attribute bag — echoed verbatim; the kernel never interprets it. */
  metadata?: Record<string, unknown> | null;
  participants: string[]; // member usernames
  participantCount: number;
  unread: number;
  muted?: boolean;
  lastMessage?: string;
  lastAt?: string;
}

/** A personally-saved message with its thread context (GET /v1/threads/my-annotations?type=save). */
export interface SavedItem {
  message: Message;
  threadId: string;
  threadSubject: string | null;
}

/** Result of the dev IdP login (POST /v1/dev/login). A real IdP issues the same claims. */
export interface LoginResult {
  token: string;
  userId: string;
}

export type InboxState = 'OPEN' | 'SNOOZED' | 'DONE' | 'ARCHIVED' | 'CANCELLED' | 'STALE';

export interface InboxItem {
  id: string;
  ownerActorId: string;
  kind: string;
  state: InboxState;
  title: string;
  summary?: string | null;
  priority: string;
  threadId?: string | null;
  sourceInteractionId?: string | null;
  traceId?: string | null;
  createdAt: string;
  updatedAt: string;
}

export type TicketState =
  | 'NEW'
  | 'OPEN'
  | 'PENDING_CUSTOMER'
  | 'PENDING_INTERNAL'
  | 'RESOLVED'
  | 'CLOSED'
  | 'CANCELLED';

export interface Ticket {
  id: string;
  subject: string;
  state: TicketState;
  priority: string;
  queueId?: string | null;
  requesterActorId?: string | null;
  assignedActorId?: string | null;
  createdAt: string;
  updatedAt: string;
  /** Opaque, app-supplied attribute bag on the ticket — echoed verbatim; the kernel never interprets it. */
  metadata?: Record<string, unknown> | null;
  threadLinks?: Array<{ threadId: string; relationKind: string }>;
}

export interface CallbackRequest {
  id: string;
  status: string;
  preferChannel: string;
}

export interface RouteBinding {
  id: string;
  originChannelType: string;
  originRef?: string | null;
  destinationChannelType: string;
  destinationRef?: string | null;
  restrictionProfile?: string | null;
  mode: string;
  outputFormat: string;
  requiresReview: boolean;
  enabled: boolean;
}

export interface RouteDecision {
  id: string;
  interactionId: string;
  routeBindingId: string;
  decisionState: string;
  reasonCodes: string[];
  previewPayload: { text?: string; destinationRef?: string | null; format?: string } | unknown;
  outputFormat: string;
  executed: boolean;
  routeBinding?: RouteBinding;
}

export interface AiClaim {
  id: string;
  claimType: string;
  claimJson: Record<string, unknown> | unknown;
  confidence?: number | null;
  validationStatus: string;
}

export interface AiEvidenceLink {
  id: string;
  sourceType: string;
  sourceId: string;
  spanRef?: string | null;
  relevanceScore?: number | null;
}

export interface AiModelRun {
  provider: string;
  model: string;
  modelVersion: string;
  promptVersion: string;
  tokensIn: number;
  tokensOut: number;
  costUnits: number;
  latencyMs: number;
}

export interface AiArtifact {
  id: string;
  jobId: string;
  artifactType: string;
  status: string;
  confidence?: number | null;
  contentRef: unknown;
  explanationRef?: string | null;
  abstentionReason?: string | null;
  createdAt: string;
  claims?: AiClaim[];
  evidence?: AiEvidenceLink[];
  modelRun?: AiModelRun;
  job?: { id: string; jobType: string; interactionId?: string | null; status: string };
}

export interface AiJobResult {
  job: { id: string; jobType: string; status: string; abstentionReason?: string | null };
  artifact: AiArtifact | null;
}

export interface MeetingParticipant {
  id: string;
  actorRefId?: string | null;
  role: string;
  attendanceStatus: string;
  recordingConsent: string;
  visibility: string;
}

export interface Meeting {
  id: string;
  meetingType: string;
  status: string;
  title: string;
  organizerActorId: string;
  startAt?: string | null;
  endAt?: string | null;
  timezone: string;
  calendarEventId?: string | null;
  participants?: MeetingParticipant[];
}

export interface MeetingActionItem {
  id: string;
  meetingId: string;
  actionItemId: string;
  status: string;
  actionItem?: { id: string; title: string; status: string; ownerActorId?: string | null };
}

/** Minimal socket surface so the facade can be unit-tested with a fake. */
export interface SocketLike {
  on(event: string, handler: (...args: unknown[]) => void): unknown;
  off(event: string, handler: (...args: unknown[]) => void): unknown;
  emit(event: string, ...args: unknown[]): unknown;
  emitWithAck(event: string, ...args: unknown[]): Promise<unknown>;
  connect(): unknown;
  disconnect(): unknown;
  /** True while the underlying transport is connected (socket.io exposes this). */
  connected?: boolean;
  /** Per-call ack timeout (socket.io). Optional so fakes can omit it. */
  timeout?(ms: number): { emitWithAck(event: string, ...args: unknown[]): Promise<unknown> };
}
