/**
 * Transport contracts for @shade/transfer.
 *
 * The library splits its wire concerns in two:
 *
 *  - {@link IControlChannel} — carries the FOUR control messages
 *    (stream-init, stream-finish, stream-abort, stream-resume-*) as
 *    Double-Ratchet-encrypted plaintext over the application's existing
 *    Shade transport. The `@shade/sdk` integration (M-Stream-5) provides a
 *    `ShadeControlChannel` that wraps `Shade.send`/`Shade.receive`.
 *
 *  - {@link ITransferTransport} — carries the encrypted CHUNK envelopes
 *    (wire type 0x11) directly between sender and receiver. This is where
 *    parallel-lane throughput happens; the default implementation is HTTP
 *    POST to a `transferRoute()`-mounted server, with optional WS upgrade.
 *
 * Splitting along this seam lets resume/probe/health concerns live in the
 * chunk transport without leaking into the SDK's per-message ratchet API,
 * and lets test code swap either side independently.
 */

import type {
  StreamControlMessage,
  StreamInitMessage,
} from '@shade/streams';

/** Outcome of a successful chunk POST. */
export interface ChunkAck {
  /** Last sequence number the receiver has confirmed for this lane. */
  lastSeq: number;
  /** Receiver-side observed bytes-received. Useful for sanity checking. */
  bytesReceived?: number;
}

export interface ChunkSendOptions {
  /** Caller-side request abort signal. */
  signal?: AbortSignal;
}

/**
 * Chunk transport contract.
 *
 * Implementations:
 *  - `ShadeTransferHttpTransport` — POST per chunk, signed via Ed25519.
 *  - `ShadeTransferWsTransport` — WebSocket framing (M-Stream-7).
 *  - `MemoryTransferTransport` — in-process pipe for tests.
 */
export interface ITransferTransport {
  /** Probe peer reachability. Throws on unreachable. */
  probe(peerAddress: string): Promise<void>;

  /**
   * Ship a single encoded `0x11` envelope. Returns when the receiver has
   * accepted it (and updated its persisted state, if any). Idempotent on
   * (streamId, laneId, seq) — replays return the same `ChunkAck`.
   */
  sendChunk(
    peerAddress: string,
    streamId: string,
    laneId: number,
    seq: number | bigint,
    bytes: Uint8Array,
    options?: ChunkSendOptions,
  ): Promise<ChunkAck>;

  /**
   * Ask the peer for its resume state for a given streamId. Returns
   * `null` when the peer has no record (sender then restarts from seq 0
   * across all lanes — chunks are deterministic).
   */
  fetchResumeState(
    peerAddress: string,
    streamId: string,
  ): Promise<TransferResumeState | null>;
}

/**
 * Receiver-reported resume state. `lastSeqAcked = -1` means no chunk for
 * that lane has been received.
 */
export interface TransferResumeState {
  streamId: string;
  lanes: Array<{ laneId: number; lastSeqAcked: number }>;
}

/**
 * Control-channel contract.
 *
 * Implementations layered on top of `Shade.send`/`Shade.receive` (M-Stream-5
 * `ShadeControlChannel`) or in-memory channels (tests).
 */
export interface IControlChannel {
  /** Send a plaintext control message to a peer. Returns when delivered. */
  send(peerAddress: string, message: StreamControlMessage): Promise<void>;

  /**
   * Subscribe to incoming control messages for THIS endpoint. The handler
   * runs once per incoming message; multiple subscriptions are allowed.
   * Returns an unsubscribe function.
   */
  onMessage(
    handler: (
      from: string,
      message: StreamControlMessage,
    ) => void | Promise<void>,
  ): () => void;
}

/**
 * Sender-side handle returned by `IControlChannel.send` for `stream-init`.
 *
 * Empty in v0.2.0 (the orchestrator tracks state directly), kept as a
 * forward-compatible extension point.
 */
export interface InitDispatch {
  init: StreamInitMessage;
}
