import type {
  Attachment,
  BulkAddResult,
  ChannelSummary,
  Conversation,
  CreateChannelInput,
  Membership,
  Message,
  MessageEvent,
  Person,
  SendOpts,
  Unsubscribe,
} from './types';

/**
 * The one seam of this SDK. Hosts implement this; the SDK renders it.
 *
 * Lifted from lynkeduppro-crm's MessengerData/ThreadData, which already survived
 * two implementations (live data-door + mock) — the minimum real evidence that a
 * seam is genuine rather than imagined.
 *
 * Optional methods degrade gracefully: the UI hides the reaction picker when
 * `react` is absent, and the attach button when `upload` is absent. That is how one
 * component set serves both a full CRM messenger and a stripped-down widget with no
 * `mode` prop.
 */
export interface MessagingAdapter {
  listConversations(): Promise<Conversation[]>;

  openThread(p: { participantIds: string[]; membership?: Membership; subject?: string }): Promise<{ threadId: string }>;

  history(threadId: string): Promise<Message[]>;

  send(threadId: string, content: string, opts?: SendOpts): Promise<Message>;

  /** Returns an unsubscribe fn. Implementations MUST be idempotent on repeat unsubscribe. */
  subscribe(threadId: string, cb: (e: MessageEvent) => void): Unsubscribe;

  sendTyping(threadId: string): void;

  markRead(threadId: string, messageId: string): Promise<void>;

  /** Report which thread is in the foreground (null when none/blurred) so the backend can suppress
   *  push notifications for a thread you're actively viewing. Absent => presence isn't tracked. */
  setFocus?(threadId: string | null): void;

  /** Subscribe to activity across ALL of the caller's threads (not just the open one) — fires for
   *  every incoming message. Drives live unread + in-app notifications. Absent => no cross-thread
   *  awareness. Returns an unsubscribe fn. */
  subscribeActivity?(cb: (e: { threadId: string; message: Message }) => void): Unsubscribe;

  /**
   * The current user's actor id, or null if not yet known.
   *
   * MUST NOT be inferred from message history. The CRM's bug was exactly that:
   * scanning for a sent message meant every message read as not-yours until you
   * had spoken. Adapters derive this from auth/session.
   */
  currentActorId(): string | null;

  /** Absent => the UI hides reactions entirely. */
  react?(threadId: string, messageId: string, emoji: string): Promise<void>;

  /** Absent => the UI hides attachments. Storage/auth/limits are the host's concern. */
  upload?(file: File): Promise<Attachment>;

  /** Absent => treated as always connected (e.g. a pure-REST adapter). */
  isConnected?(): boolean;

  /** A thread's members (id + display name), for @mention autocomplete + highlighting.
   *  Absent => the composer offers no autocomplete (you can still type @text). */
  listMembers?(threadId: string): Promise<Person[]>;

  /** The people you can start a conversation with (org directory). Drives the "New message"
   *  people picker. Absent => the UI hides DM/group creation (you can still open channels). */
  directory?(): Promise<Person[]>;

  // ── Channels (optional capability) ──────────────────────────────
  // A channel is just a third membership beyond dm/group: a discoverable, joinable room.
  // Implement all four to enable the channels UI; absent => the UI hides channels entirely.

  /** Discoverable channels in the caller's scope, each flagged `joined`. */
  browseChannels?(): Promise<ChannelSummary[]>;

  /** Create a channel; the creator joins as admin. Returns the new thread id. */
  createChannel?(input: CreateChannelInput): Promise<{ threadId: string }>;

  /** Join a (public) channel by id. */
  joinChannel?(threadId: string): Promise<void>;

  /** Leave a channel by id. */
  leaveChannel?(threadId: string): Promise<void>;

  // ── Group / channel administration (optional) ───────────────────
  // Enable the settings panel for groups + channels. Absent methods hide their affordance;
  // the server (OPA) still enforces who may actually add/remove/rename (admin-only).

  /** Add a person to a group or private channel. */
  addMember?(threadId: string, userId: string): Promise<void>;

  /** Add many people in one call — powers "add everyone from another channel". The host is expected
   *  to enforce who may add (and who may read the source roster) server-side.
   *  Absent => the import-from-a-channel affordance is hidden; single add still works. */
  addMembers?(threadId: string, userIds: string[]): Promise<BulkAddResult>;

  /** Remove a person from a group or channel. */
  removeMember?(threadId: string, userId: string): Promise<void>;

  /** Rename a group or channel. */
  renameConversation?(threadId: string, subject: string): Promise<void>;
}
