/**
 * Minimal subset of the standard WebRTC interfaces that
 * `@shade/transport-webrtc` depends on.
 *
 * Bun does not yet expose `RTCPeerConnection` natively, so the package
 * accepts a factory rather than reaching for `globalThis`. Browsers can
 * use {@link nativeRtcFactory}; tests use {@link MemoryRtcFactory} from
 * `./memory-rtc.js`; Node-class environments can adapt
 * `node-datachannel`/`wrtc` behind the same shape.
 *
 * The shape is intentionally narrower than the spec — only the event names,
 * methods, and properties Shade actually uses are required. Everything is
 * declared in terms of plain DOM event listeners (`addEventListener`) so
 * that adapter authors can implement it without pulling lib.dom typings
 * into their own packages.
 */

/** A subset of `RTCConfiguration` that Shade understands. */
export interface ShadeRtcConfig {
  /** ICE servers (STUN + TURN). When omitted, defaults to public STUN. */
  iceServers?: ShadeIceServer[];
  /**
   * Force `'relay'` to mandate TURN-relay (useful for tests / paranoid
   * deployments where a direct path must never be tried).
   */
  iceTransportPolicy?: 'all' | 'relay';
  /** Bundle policy passed through verbatim. */
  bundlePolicy?: 'balanced' | 'max-compat' | 'max-bundle';
}

export interface ShadeIceServer {
  urls: string | string[];
  username?: string;
  credential?: string;
}

/** Default public STUN servers (Google's). Used when `iceServers` is omitted. */
export const DEFAULT_STUN_SERVERS: ShadeIceServer[] = [
  { urls: 'stun:stun.l.google.com:19302' },
  { urls: 'stun:stun1.l.google.com:19302' },
];

/** Standard `RTCSdpType` subset. */
export type ShadeSdpType = 'offer' | 'answer';

export interface ShadeSessionDescription {
  type: ShadeSdpType;
  sdp: string;
}

/** ICE candidate as serialized over the signaling channel. */
export interface ShadeIceCandidate {
  candidate: string;
  sdpMid: string | null;
  sdpMLineIndex: number | null;
  /** Optional usernameFragment, mirrored from `RTCIceCandidate`. */
  usernameFragment?: string | null;
}

/** State the manager exposes to the rest of the SDK. */
export type ShadeRtcConnectionState =
  | 'new'
  | 'connecting'
  | 'connected'
  | 'disconnected'
  | 'failed'
  | 'closed';

// ─── Adapter contracts ──────────────────────────────────────

/**
 * Factory injected at construction time. Returns a peer connection
 * configured with the supplied {@link ShadeRtcConfig}.
 */
export interface IRtcFactory {
  createPeerConnection(config: ShadeRtcConfig): IPeerConnection;
}

/**
 * The narrow `RTCPeerConnection` shape Shade depends on. Adapters MUST
 * implement these methods + emit the listed events.
 *
 * Events:
 *   - `'icecandidate'`         — `{ candidate: ShadeIceCandidate | null }`
 *   - `'datachannel'`          — `{ channel: IDataChannel }` (callee side)
 *   - `'connectionstatechange'`
 *   - `'iceconnectionstatechange'`
 */
export interface IPeerConnection {
  readonly connectionState: ShadeRtcConnectionState | string;
  readonly iceConnectionState: string;

  createDataChannel(
    label: string,
    init?: { ordered?: boolean; maxRetransmits?: number; maxPacketLifeTime?: number },
  ): IDataChannel;

  createOffer(): Promise<ShadeSessionDescription>;
  createAnswer(): Promise<ShadeSessionDescription>;
  setLocalDescription(desc: ShadeSessionDescription): Promise<void>;
  setRemoteDescription(desc: ShadeSessionDescription): Promise<void>;
  addIceCandidate(candidate: ShadeIceCandidate | null): Promise<void>;

  close(): void;

  addEventListener(event: 'icecandidate', cb: (ev: { candidate: ShadeIceCandidate | null }) => void): void;
  addEventListener(event: 'datachannel', cb: (ev: { channel: IDataChannel }) => void): void;
  addEventListener(event: 'connectionstatechange', cb: () => void): void;
  addEventListener(event: 'iceconnectionstatechange', cb: () => void): void;
  removeEventListener(event: string, cb: (...args: unknown[]) => void): void;
}

/**
 * The narrow `RTCDataChannel` shape Shade depends on. Binary-only — Shade
 * never sends text frames over a transfer DC.
 */
export interface IDataChannel {
  readonly readyState: 'connecting' | 'open' | 'closing' | 'closed';
  readonly label: string;

  /** Default `'arraybuffer'`. Adapters MUST coerce so `message.data` is an `ArrayBuffer`. */
  binaryType: 'arraybuffer' | 'blob';

  /** Buffered amount in bytes. Used for pacing. */
  readonly bufferedAmount: number;

  send(data: ArrayBuffer | Uint8Array): void;
  close(): void;

  addEventListener(event: 'open', cb: () => void): void;
  addEventListener(event: 'close', cb: () => void): void;
  addEventListener(event: 'error', cb: (ev: { error?: unknown }) => void): void;
  addEventListener(event: 'message', cb: (ev: { data: ArrayBuffer }) => void): void;
  removeEventListener(event: string, cb: (...args: unknown[]) => void): void;
}

// ─── Signaling envelope kinds (over Shade.send) ─────────────

export type WebRtcSignalingKind =
  | 'shade.webrtc-offer/v1'
  | 'shade.webrtc-answer/v1'
  | 'shade.webrtc-ice/v1'
  | 'shade.webrtc-bye/v1';

export interface WebRtcOfferMessage {
  kind: 'shade.webrtc-offer/v1';
  /** Caller-generated session id; both peers tag every signaling message with this. */
  sessionId: string;
  sdp: string;
}

export interface WebRtcAnswerMessage {
  kind: 'shade.webrtc-answer/v1';
  sessionId: string;
  sdp: string;
}

export interface WebRtcIceMessage {
  kind: 'shade.webrtc-ice/v1';
  sessionId: string;
  candidate: ShadeIceCandidate | null;
}

export interface WebRtcByeMessage {
  kind: 'shade.webrtc-bye/v1';
  sessionId: string;
  reason?: string;
}

export type WebRtcSignalingMessage =
  | WebRtcOfferMessage
  | WebRtcAnswerMessage
  | WebRtcIceMessage
  | WebRtcByeMessage;

export function isWebRtcSignalingMessage(value: unknown): value is WebRtcSignalingMessage {
  if (typeof value !== 'object' || value === null) return false;
  const kind = (value as { kind?: unknown }).kind;
  return typeof kind === 'string' && kind.startsWith('shade.webrtc-');
}

export function parseWebRtcSignaling(plaintext: string): WebRtcSignalingMessage | null {
  try {
    const parsed = JSON.parse(plaintext) as unknown;
    return isWebRtcSignalingMessage(parsed) ? parsed : null;
  } catch {
    return null;
  }
}

export function encodeWebRtcSignaling(msg: WebRtcSignalingMessage): string {
  return JSON.stringify(msg);
}
