/**
 * Shared contracts for the freight in-app customer-support chat.
 *
 * There is exactly **one conversation per customer company** — any portal user
 * of that company sees and continues the same thread, and every backoffice agent
 * works the same shared inbox (no assignment). The thread has no lifecycle: it
 * is created lazily by whichever side speaks first and stays open forever.
 * Messages are text-only for the MVP.
 *
 * Because the thread is implied by the caller's company, the portal contract is
 * addressed as a singleton (`/support/conversation`) and never passes an id.
 * Agents address threads by id, since they see every company's.
 *
 * Mirrors the notification system's contract shape (`notifications.ts`): DTO
 * interfaces with string dates for the wire, plus frozen WS event/namespace
 * constants shared by the gateway (emitter) and both web apps (subscribers).
 */

import { SUPPORT_ATTACHMENT_RESOURCE } from "../common/support-attachments";

/** Who authored a message — the customer side or a backoffice agent. */
export enum SupportAuthorRole {
  CUSTOMER = "CUSTOMER",
  AGENT = "AGENT",
}

/** `FileRecord.resource` value chat attachments are stored under. */
export { SUPPORT_ATTACHMENT_RESOURCE };

/** A file attached to a support message. */
export interface SupportAttachmentDto {
  /**
   * FileRecord id. For an authenticated download use
   * `GET /support/attachments/:id?download=1` — the generic `GET /files/:id`
   * route deliberately refuses chat attachments (it has no ownership check).
   */
  id: string;
  name: string;
  mimeType: string;
  /** Bytes. */
  size: number;
  /**
   * Short-lived signed URL for inline preview (`<img src>`), minted per response.
   * Expires — see SUPPORT_ATTACHMENT_URL_TTL_SECONDS. Clients must not persist it;
   * refetch the thread to renew.
   */
  url: string;
}

/** A single chat message on the wire. */
export interface SupportMessageDto {
  id: string;
  conversationId: string;
  authorUserId: string;
  authorRole: SupportAuthorRole;
  /** Display name of the author, resolved at send time (best-effort). */
  authorName?: string | null;
  /** Empty string for attachment-only messages. */
  body: string;
  attachments: SupportAttachmentDto[];
  createdAt: string;
}

/** A company's conversation on the wire, with denormalized last-message fields. */
export interface SupportConversationDto {
  id: string;
  companyId: string;
  companyName?: string | null;
  /** Null when an agent opened the thread — no customer created it. */
  createdByUserId?: string | null;
  lastMessageAt?: string | null;
  lastMessagePreview?: string | null;
  lastMessageAuthorRole?: SupportAuthorRole | null;
  /**
   * Unread count *for the caller's side* (messages authored by the other role
   * after the caller's read cursor). Populated on list/detail responses only —
   * WS payloads carry an unauthoritative 0, so clients must refetch, not trust it.
   */
  unreadCount: number;
  createdAt: string;
  updatedAt: string;
}

/**
 * Post a message. The portal omits the id; the thread is implied by the company.
 *
 * Attachments do not travel in this shape — a message carrying files is sent as
 * `multipart/form-data` with a `body` field plus one or more `attachments` file
 * parts, so the files are written with `resourceId = message.id` in the same
 * request. There is no staging area and therefore no orphan-file GC to run.
 */
export interface SendSupportMessageDto {
  /** Optional only when the request carries at least one attachment. */
  body?: string;
}

/** Agent opens a thread with a company that has none yet. */
export interface StartSupportConversationDto {
  companyId: string;
}

/**
 * Result of a portal send: the thread (created on the fly if this was the first
 * message) alongside the persisted message.
 */
export interface SendSupportMessageResult {
  conversation: SupportConversationDto;
  message: SupportMessageDto;
}

/** Paginated list envelope for the agent conversations list endpoint. */
export interface SupportConversationListResult {
  items: SupportConversationDto[];
  count: number;
  /** Total unread conversations for the caller's side (badge source). */
  unreadCount: number;
}

/**
 * A page of messages, walking **backwards** from newest.
 *
 * Chat pages by keyset, not by offset: an inbound message while an agent is
 * scrolled back would shift every offset by one and duplicate/skip rows across
 * pages. The cursor pins a fixed point in (createdAt, id), so concurrent inserts
 * at the head can't disturb pages already read.
 */
export interface SupportMessageListResult {
  /** Oldest-first *within the page*, so a page appends/prepends as a block. */
  items: SupportMessageDto[];
  /**
   * Opaque cursor for the next (older) page; null when the thread's start has
   * been reached. Pass back as `before`.
   */
  nextCursor: string | null;
}

/** Socket.io event names pushed server → client on the `support-chat` namespace. */
export const SUPPORT_CHAT_WS_EVENTS = {
  /** A new message was added to a conversation the socket can see. */
  MESSAGE_NEW: "support:message-new",
  /** A conversation's metadata changed (last message, or a thread was opened). */
  CONVERSATION_UPDATED: "support:conversation-updated",
} as const;

/** Socket.io namespace the support-chat gateway listens on. */
export const SUPPORT_CHAT_WS_NAMESPACE = "support-chat";

/** Payload for {@link SUPPORT_CHAT_WS_EVENTS.MESSAGE_NEW}. */
export interface SupportMessageEvent {
  conversation: SupportConversationDto;
  message: SupportMessageDto;
}
