/**
 * Common types for the @shade/transport-bridge package.
 *
 * The bridge layer abstracts "how do I receive ciphertext envelopes" so that
 * an application can stay transport-agnostic. Three implementations ship in
 * v0.1.0: WS, SSE, long-poll. A `FallbackBridgeTransport` wraps them in
 * priority order so a client blocked from WebSockets by a strict proxy
 * automatically slides down to SSE, then long-poll.
 *
 * Every bridge surfaces the same {@link IncomingMessage} shape — application
 * code never branches on transport.
 */

/**
 * A single transport-level incoming message.
 *
 * `from` is the relay-known sender fingerprint (8-byte hex of the sender's
 * Ed25519 signing key, computed by the inbox server at PUT time). It is
 * empty for legacy blobs that were stored before the inbox started tracking
 * sender provenance, and for non-inbox bridges that have no upstream sender
 * notion. The authoritative sender identity always lives inside the Double
 * Ratchet envelope and is recovered post-decrypt — `from` is a hint, not a
 * trust anchor.
 */
export interface IncomingMessage {
  from: string;
  bytes: Uint8Array;
  receivedAt: number;
  /** Relay-assigned message id (sha256 of ciphertext). Useful for ack/dedup. */
  msgId?: string;
  /**
   * Absolute expiry (ms since epoch) reported by the relay. Surfaced for
   * symmetry with `Inbox.handleBlob` so that
   * `Inbox.acceptBridgeFrame(msg)` can be wired directly without the
   * caller having to invent a TTL. Optional — pre-V4.8.3 relays / non-
   * inbox bridges don't populate it.
   */
  expiresAt?: number;
}

/** Subscriber callback. Bridges MAY invoke it concurrently. */
export type IncomingMessageHandler = (msg: IncomingMessage) => void | Promise<void>;

export interface BridgeConnectOptions {
  onMessage: IncomingMessageHandler;
  /** Caller-supplied error handler; defaults to console.warn. */
  onError?: (err: Error) => void;
}

/**
 * Common contract for every bridge transport.
 *
 * Implementations are expected to:
 *   - block in `connect` until they have either established a session OR
 *     surfaced a hard failure as a thrown error;
 *   - keep delivering messages until `disconnect` is called;
 *   - reconnect transparently on transient drops while bumping the cursor
 *     to avoid duplicate delivery (Last-Event-ID for SSE, `since` cursor for
 *     long-poll, etc.).
 *
 * Bridges are *single-use*: call `connect` once, then `disconnect`. Calling
 * `connect` twice on the same instance must throw.
 */
export interface BridgeTransport {
  connect(options: BridgeConnectOptions): Promise<void>;
  disconnect(): Promise<void>;
  /** Resolves to a stable identifier ("ws" | "sse" | "long-poll" | …). */
  readonly kind: string;
}

/**
 * Wire-format for a single message inside an SSE event, long-poll batch
 * entry, or WS frame. The bridge protocol is JSON-over-the-wire with
 * base64-encoded ciphertext — the same shape regardless of transport.
 */
export interface BridgeWireMessage {
  msgId: string;
  /** base64 ciphertext */
  ciphertext: string;
  receivedAt: number;
  expiresAt?: number;
  /** Sender fingerprint hint (may be empty). */
  from?: string;
}

export function decodeWireMessage(wire: BridgeWireMessage): IncomingMessage {
  const bytes = base64ToBytes(wire.ciphertext);
  const msg: IncomingMessage = {
    from: wire.from ?? '',
    bytes,
    receivedAt: wire.receivedAt,
  };
  if (wire.msgId !== undefined) msg.msgId = wire.msgId;
  if (wire.expiresAt !== undefined) msg.expiresAt = wire.expiresAt;
  return msg;
}

function base64ToBytes(s: string): Uint8Array {
  const bin = atob(s);
  const out = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
  return out;
}
