/**
 * Connection State
 */
export type ConnectionState =
  | 'new'
  | 'connecting'
  | 'connected'
  | 'failed'
  | 'disconnected'
  | 'reconnecting'
  | 'not_support'
  | 'limit_exceeded';

/**
 * Cloud Mode
 * public - Public cloud
 * public_intl - Public cloud (international)
 * private - Private cloud
 */
export type CloudMode = 'public' | 'public_intl' | 'private';

/**
 * Service Mode
 * server - Dedicated acceleration mode
 * server2 - Standard acceleration mode
 */
export type SDKMode = 'server' | 'server2';

/**
 * Media Type
 */
export type MediaType = 'video' | 'audio';

/**
 * Room State
 */
export type RoomState = 'joining' | 'joined' | 'unjoined';

/**
 * Signaling State
 */
export type SignalingState =
  | 'new'
  | 'scheduling'
  | 'scheduled'
  | 'connecting'
  | 'connected'
  | 'disconnected'
  | 'closed'
  | 'reconnecting';

export const enum NetworkErrorCode {
  // Success - default response for all signaling
  SUCCESS = 0,
  // Stream not found - media service stream doesn't exist (returned by subscribe signaling)
  MEDIA_SERVER_STREAM_NOT_FOUND = 4000,
  // Parameter error - media service invalid parameters (returned by subscribe signaling)
  MEDIA_SERVER_PARAM_ERROR = 4001,
  // Track not found - media service subscribed track doesn't exist (returned by subscribe signaling)
  MEDIA_SERVER_TRACK_NOT_FOUND = 4002,
  // Invalid JSON - check signaling structure and parameters
  INVALID_JSON = 10000,
  // Invalid parameters - check signaling parameters
  INVALID_PARAMS = 10001,
  // User not joined - user hasn't joined the room
  USER_NOT_JOIN = 10002,
  // User repeated join - user logged in repeatedly
  USER_REPEAT_JOIN = 10003,
  // Source user doesn't exist - applies to signaling related to remote users (invite/offer/answer/custom data)
  SRCUSER_NOT_EXIST = 10004,
  // Invalid user signature
  INVALID_USER_SIG = 10005,
  // Invalid auth server signature - public cloud authentication failed
  INVALID_AUTH_SERVER_SIG = 10006,
  // No track exists - stream doesn't exist (returned by subscribe signaling)
  NO_TRACK_EXIST = 10007,
  // Server overload - returned by join signaling
  SERVER_OVERLOAD = 10008,
  // Media server exception
  MEDIA_SERVER_EXCEPT = 11000,
  // Unknown error
  SIGNALING_SERVER_ERROR = 20000,
}

/**
 * MQTT Connection State
 */
export type MqttConnectionState = 'new' | 'connecting' | 'connected' | 'reconnecting' | 'error' | 'closed';

/**
 * Gateway Device Permission
 */
export type PermissionState = 'master' | 'guest';

/**
 * Gateway Information
 */
export type Gateway = {
  /**
   * Field device ID. In public cloud mode, format is `${projectId}/${fieldDeviceId}`
   */
  deviceId: string;
  deviceName: string;
  deviceType: string;
  streamCount: number;
  status: string;
  /**
   * Gateway SDK version
   */
  version: string;
  /**
   * License expiration timestamp (seconds)
   */
  licenseExpire?: number;
};

/**
 * Autoplay Response Type
 */
export type OnEventAutoplayResponse = {
  type: 'autoplay';
  data: {
    /**
     * 0 - Success
     * -1 - Failed
     * 20 - MQTT client not found (public cloud only)
     * 21 - MQTT client not connected (public cloud only)
     * 22 - MQTT publish failed (public cloud only)
     * 25 - MQTT sessionId not found (public cloud only)
     */
    code: number;
    message: string;
    fieldDeviceId: string;
    srcUserId: string;
    streamId: number;
    trackId: string;
    mediaType: MediaType;
    videoWidth?: number;
    videoHeight?: number;
  };
};

/**
 * Media Stream Statistics
 */
export type WebRTCStats = {
  /** Media stream source user ID */
  srcUserId: string;
  /** Media stream track ID */
  trackId: string;
  /** Media stream ID */
  streamId: number;
  /** Media type */
  mediaType: MediaType;

  /** Current bitrate in Mbps */
  bitrate: number;
  /** Total received RTP packets (cumulative) */
  packetsReceived: number;
  /** Total lost RTP packets (cumulative) */
  packetsLost: number;
  /** Current packet loss rate in % */
  lost: number;
  /** Average packet loss rate in % */
  lostAvg: number;
  /** RTT (Round Trip Time) in ms */
  rtt: number;
  /** Average RTT in ms */
  rttAvg: number;

  /** Media codec MIME type */
  codec: string;
  /** Decoder identifier */
  decoderImplementation: string;
  /**
   * Decoder type:
   * - hardware: Hardware decoder
   * - software: Software decoder
   * - unknown: Unknown type
   */
  decoderType: 'hardware' | 'software' | 'unknown';

  /** Total NACK packets sent (cumulative) */
  nack: number;
  /** FIR count */
  firCount: number;
  /** PLI count (video only) */
  pliCount: number;

  /** Total frames received (cumulative, video only) */
  framesReceived: number;
  /** Total frames decoded (cumulative, video only) */
  framesDecoded: number;
  /** Total key frames decoded (cumulative, video only) */
  keyFramesDecoded: number;
  /** Resolution in format `${width}x${height}` (video only) */
  resolution: string;
  /** Frame rate (video only) */
  fps: number;
  /** Current decode time in ms (video only) */
  decodeTime: number;
  /** Average decode time in ms (video only) */
  decodeTimeAvg: number;
  /** Current processing delay in ms (video only) */
  processingDelay: number;
  /** Average processing delay in ms (video only) */
  processingDelayAvg: number;

  /** Current audio level (0-1, audio only) */
  audioLevel: number;

  /** Jitter in seconds */
  jitter: number;
  /** Current jitter buffer delay in ms */
  jitterBuffer: number;
  /** Average jitter buffer delay in ms */
  jitterBufferAvg: number;
  /** Current target jitter buffer delay in ms */
  jitterBufferTarget: number;
  /** Average target jitter buffer delay in ms */
  jitterBufferTargetAvg: number;

  /** Video latency in ms */
  latency: number;
  /** @deprecated Old latency algorithm, removed in v2.4.2.2+ */
  latency1?: number;
  /** @deprecated New latency algorithm, removed in v2.4.2.2+ */
  latency2?: number;
  /** Video freeze rate at 100ms */
  lag100: number;
  /** Video freeze rate at 150ms */
  lag150: number;

  /** Period delta: NACK count in current collection period */
  nackDelta: number;
  /** Period delta: FIR count in current collection period */
  firCountDelta: number;
  /** Period delta: PLI count in current collection period */
  pliCountDelta: number;
  /** Period delta: frames received in current collection period */
  framesReceivedDelta: number;
  /** Period delta: frames decoded in current collection period */
  framesDecodedDelta: number;
  /** Period delta: packets received in current collection period */
  packetsReceivedDelta: number;
  /** Period delta: packets lost in current collection period */
  packetsLostDelta: number;
  /** Stable FPS: 10s sliding window average, excluding 0 values (video only) */
  stableFps: number;
};

/**
 * Diagnosis Report
 */
export type DiagnosisReport = {
  /** Diagnosis error message, returns 'ok' if no error, otherwise error message */
  diag_error: string;
  /** Field device ID */
  device_id: string;
  /** Field device process ID */
  field_pid?: string;
  /** Field device license check, returns 'ok' if passed, otherwise error message */
  license_check?: string;
  /** Field device MQTT connection status, returns 'ok' if connected, otherwise error message */
  field_mqtt_connect?: string;
  /** Remote device MQTT connection status, returns 'ok' if connected, otherwise error message */
  remote_mqtt_connect: string;
  /** Video stream diagnosis reports */
  result: {
    /** Video stream ID */
    stream_id: number;
    /** Field device video stream diagnosis report */
    field?: {
      /** Field device service mode */
      field_mode: string;
      /** Field device signaling connection status, returns 'ok' if connected, otherwise error message */
      signal_connect: string;
      /** Field device media connection status, returns 'ok' if connected, otherwise error message */
      media_connect: string;
      /** Field device video transfer status, returns 'ok' if normal, otherwise error message */
      video_transfer: string;
      /** Field device video capture status, returns 'ok' if normal, otherwise error message */
      video_capture: string;
      /** Field device video encode status, returns 'ok' if normal, otherwise error message */
      video_encode: string;
      /** Field device RTT statistics in ms, 2-second interval */
      rtt: Array<number>;
      /** Field device packet loss rate statistics, 2-second interval */
      lost: Array<number>;
      /** Field device bitrate statistics in kbps, 2-second interval */
      bps: Array<number>;
      /** Field device frame rate statistics, 2-second interval */
      fps: Array<number>;
    };
    /** Remote device video stream diagnosis report */
    remote?: {
      /** Remote device service mode */
      remote_mode: string;
      /** Remote device signaling connection status, returns 'ok' if connected, otherwise error message */
      signal_connect: string;
      /** Remote device media connection status, returns 'ok' if connected, otherwise error message */
      media_connect: string;
      /** Remote device video transfer status, returns 'ok' if normal, otherwise error message */
      video_transfer: string;
      /** Remote device video decode status, returns 'ok' if normal, otherwise error message */
      video_decode: string;
      /** Remote device 100ms freeze rate */
      lag_k100: number;
      /** Remote device 150ms freeze rate */
      lag_k150: number;
      /** Remote device RTT statistics in ms, 2-second interval */
      rtt: Array<number>;
      /** Remote device packet loss rate statistics, 2-second interval */
      lost: Array<number>;
      /** Remote device bitrate statistics in kbps, 2-second interval */
      bps: Array<number>;
      /** Remote device frame rate statistics, 2-second interval */
      fps: Array<number>;
      /** Remote device latency statistics in ms, 2-second interval */
      latency: Array<number>;
      /** Remote device complete statistics, 2-second interval */
      webrtc_stats: Array<WebRTCStats>;
    };
  }[];
};

/**
 * WebRTC Stats Callback
 */
export type OnEventWebRTCStatsResponse = {
  type: 'webrtcStats';
  data: Array<WebRTCStats>;
};

/**
 * Gateway Stats Callback
 *
 * Each element in the stats array may contain the following fields (subject to change across gateway versions):
 *   - stream: number          // Stream ID
 *   - fps: number             // Frame rate (fps)
 *   - bps: number             // Bitrate (bps)
 *   - rtt: number             // RTT (ms)
 *   - jitter: number          // Jitter (ms)
 *   - lost: number            // Lost packet count
 *   - w: number               // Encode width (px)
 *   - h: number               // Encode height (px)
 *   - encode_cost: number     // Encode cost (ms)
 */
export type OnEventGatewayStatsResponse = {
  type: 'gatewayStats';
  data: {
    fieldDeviceId: string;
    stats: any;
  };
};

/**
 * Local Audio Track Unpublish Callback
 */
export type OnEventLocalTrackUnpublishedResponse = {
  type: 'localTrackUnpublished';
  data: {};
};

/**
 * OnEvent Response
 */
export type OnEventResponse =
  | OnEventAutoplayResponse
  | OnEventWebRTCStatsResponse
  | OnEventGatewayStatsResponse
  | OnEventLocalTrackUnpublishedResponse;

/**
 * TRROMediaObserver Interface
 */
/**
 * TRROMediaObserver callback functions
 */
export interface TRROMediaObserver {
  /**
   * @function
   *
   * @description
   * MQTT connection state callback
   *
   * @param {Object} param
   * @param {MqttConnectionState} param.state MQTT connection state
   * @param {string} [param.message] Related information
   */
  onMqttConnectionState?: ({
    state,
    message,
  }: {
    /**
     * MQTT connection state
     */
    state: MQTTConnectionState;
    /**
     * Related information
     */
    message?: string;
  }) => void;

  /**
   * @function
   *
   * @description
   * Signaling state callback
   *
   * @param {Object} param
   * @param {string} roomId Signaling room ID
   * @param {string} param.state socket.io-client built-in events, refer to: https://socket.io/docs/v2/client-api/#events
   * @param {Object} param.data Parameters for socket.io-client built-in events, refer to: https://socket.io/docs/v2/client-api/#events
   */
  onSignalingState?: ({
    roomId,
    state,
    param,
  }: {
    /**
     * Signaling room ID
     */
    roomId: string;
    /**
     * socket.io-client built-in events
     */
    state: string;
    /**
     * Parameters for socket.io-client built-in events
     */
    data?: Record<string, any>;
  }) => void;

  /**
   * @function
   *
   * @description
   * Peer connection state change callback
   *
   * CONNECTED = 0  Connection successful <br />
   * NEW = 1  New connection <br />
   * CONNECTING = 2  Connecting <br />
   * FAILED = 3  Connection failed <br />
   * DISCONNECTED = 4  Connection disconnected <br />
   * CLOSED = 5  Actively closed <br />
   * RECONNECTING = 6 Reconnecting <br />
   * NOT_SUPPORT = 255 Browser doesn't support WebRTC <br />
   * LIMIT_EXCEEDED = 256 PeerConnection count has reached browser limit
   *
   * @param {Object} param Connection state code
   * @param {string} param.deviceId Field device ID. In public cloud mode, format is `${projectId}/${fieldDeviceId}`
   * @param {number} [param.streamId] Stream ID (only available in server2 mode)
   * @param {MediaType} [param.mediaType] Stream type (only available in server2 mode)
   * @param {string} param.state Connection state information
   * @param {string} param.code Connection state code
   * @param {string} param.message Message
   * @param {Object} param.data Extended information
   */
  onPeerConnectionState?: ({
    deviceId,
    streamId,
    mediaType,
    code,
    state,
    message,
    data,
  }: {
    deviceId: string;
    streamId?: number;
    mediaType?: MediaType;
    code: number;
    state: ConnectionState;
    message?: string;
    data?: Record<string, any>;
  }) => void;

  /**
   * @function
   *
   * @description
   * Data channel state callback
   *
   * @param {Object} param
   * @param {string} param.deviceId Field device ID. In public cloud mode, format is `${projectId}/${fieldDeviceId}`
   * @param {string} param.state Data channel state: 'open' | 'close' | 'error'
   * @param {string} [param.message] Error message
   * @param {string} param.label Data channel label
   * @returns {void}
   */
  onDataChannelState?: ({
    deviceId,
    state,
    message,
    label,
  }: {
    deviceId: string;
    state: string;
    message?: string;
    label: string;
  }) => void;

  /**
   * @function
   *
   * @description
   * Field device connection callback
   *
   * @param {Object} param Callback data
   * @param {string} param.deviceId Field device ID. In public cloud mode, format is `${projectId}/${fieldDeviceId}`
   */
  onConnected?: ({ deviceId }: { deviceId: string }) => void;

  /**
   * @function
   *
   * @description
   * Field device disconnection callback
   *
   * @param {Object} param Callback data
   * @param {string} param.deviceId Field device ID. In public cloud mode, format is `${projectId}/${fieldDeviceId}`
   */
  onDisconnected?: ({ deviceId }: { deviceId: string }) => void;

  /**
   * @function
   *
   * @description
   * Video stream publish/unpublish callback
   *
   * @param {Object} param Callback data
   * @param {string} param.state Stream state 'publish' | 'unpublish'
   * @param {string} param.deviceId Field device ID. In public cloud mode, format is `${projectId}/${fieldDeviceId}`
   * @param {number} param.streamId Corresponding subscribed stream ID
   * @param {MediaType} param.mediaType 'video' | 'audio'
   */
  onTrackPublishState?: ({
    state,
    deviceId,
    streamId,
    mediaType,
  }: {
    state: 'publish' | 'unpublish';
    deviceId: string;
    streamId: number;
    mediaType: MediaType;
  }) => void;

  /**
   * @ignore
   * @function
   *
   * @description
   * Custom signaling message callback
   *
   * @param {Object} param Callback data
   * @param {string} param.deviceId Field device ID
   * @param {any} param.customData Custom message
   */
  onCustomMessage?: ({ deviceId, customData }: { deviceId: string; customData: any }) => void;

  /**
   * @function
   *
   * @description
   * DataChannel custom message callback
   *
   * @param {Object} param Callback data
   * @param {string} param.deviceId Field device ID. In public cloud mode, format is `${projectId}/${fieldDeviceId}`
   * @param {string} param.dataType Custom message type: 'string' | 'arrayBuffer'
   * @param {string|ArrayBuffer} param.data Custom message
   * @param {boolean} param.ordered Whether sent through reliable channel
   */
  onControlData?: ({
    deviceId,
    data,
    dataType,
    ordered,
  }: {
    deviceId: string;
    dataType: 'string' | 'arrayBuffer';
    data: string | ArrayBuffer;
    ordered: boolean;
  }) => void;

  /**
   * @function
   *
   * @description
   * Kickout callback
   *
   * @param {Object} param Callback data
   * @param {number} param.code Code
   * @param {string} param.reason Reason
   */
  onKick?: ({ code, reason }: { code: number; reason: string }) => void;

  /**
   * @function
   *
   * @description
   * Permission state change callback
   *
   * @param {Object} param Callback data
   * @param {string} param.deviceId Field device ID. In public cloud mode, format is `${projectId}/${fieldDeviceId}`
   * @param {string} param.userId Remote device ID
   * @param {PermissionState} param.permission Permission information
   */
  onPermissionStateChange?: (response: { deviceId: string; userId: string; permission: PermissionState }) => void;

  /**
   * @function
   *
   * @description
   * Error notification
   *
   * @param {Object} param Callback data
   * @param {number} param.code Error code
   * @param {string} param.message Error message
   */
  onError?: ({ code, message }: { code: number; message: string }) => void;

  /**
   * @function
   *
   * @description
   * Event callback
   *
   * @param {OnEventResponse} param Callback data
   * @param {string} param.type Event type: autoplay | webrtcStats | gatewayStats | localTrackUnpublished
   * @param {Object} param.data Extended information
   */
  onEvent?: (response: OnEventResponse) => void;
}

/**
 * TRROSDK initialization options.
 *
 * Top-level config including cloud mode, signaling endpoint, device credentials,
 * event observer, logger, and custom-message options. See each field below for details.
 */
export interface TRROConfig {
  /**
   * Cloud mode (default: 'private')
   */
  cloudMode?: CloudMode;
  /**
   * Signaling server IP/domain (required for private cloud)
   */
  serverIp?: string | string[];
  /**
   * Signaling server port (default: 3000/443 for private cloud)
   */
  port?: number;
  /**
   * @deprecated
   */
  targetIp?: string;
  /**
   * @deprecated
   */
  targetPort?: number;
  /**
   * Enable HTTPS (forced for public cloud)
   */
  https?: boolean;
  /**
   * MQTT configuration
   */
  mqttOptions?: {
    url?: string | string[];
    port?: number;
    forceLogin?: boolean;
    /**
     * Enable MQTT low-level logging
     *
     * - When `loggerOption.showLog` is false, logs won't be printed even if this is true
     * - When `loggerOption.reportLog` is true and this is true, MQTT logs will be reported along with other SDK logs
     *
     * @default false
     */
    enableLog?: boolean;
  };
  /**
   * Service mode (default: server2 for public cloud)
   */
  sdkMode?: SDKMode;
  /**
   * Project ID
   */
  projectId: number | string;
  /**
   * Remote device ID
   */
  remoteDeviceId: string;
  /**
   * Device password
   */
  password: string;
  /**
   * Event observer
   */
  observer?: TRROMediaObserver;
  /**
   * Logger configuration
   */
  loggerOption?: {
    showLog?: boolean;
    reportLog?: boolean;
    /**
     * Minimum log level (inclusive) to print to console. Logs below this level are not printed.
     * Order from low to high: 'DEBUG' < 'LOG' < 'INFO' < 'WARN' < 'ERROR'. Requires `showLog` to be true.
     *
     * @default 'DEBUG'
     */
    logLevel?: 'DEBUG' | 'LOG' | 'INFO' | 'WARN' | 'ERROR';
    /**
     * Minimum log level (inclusive) to report. Logs below this level are not reported.
     * Order from low to high: 'DEBUG' < 'LOG' < 'INFO' < 'WARN' < 'ERROR'. Requires `reportLog` to be true.
     *
     * @default 'DEBUG'
     */
    reportLevel?: 'DEBUG' | 'LOG' | 'INFO' | 'WARN' | 'ERROR';
    log?: Function;
    error?: Function;
    warn?: Function;
    info?: Function;
    debug?: Function;
  };
  /**
   * Custom-message (onControlData) options
   */
  customMessageOption?: {
    /**
     * Whether to unify the `data` field of the `onControlData` callback as `ArrayBuffer`.
     *
     * - `true`: `dataType` is always `'arrayBuffer'` and `data` is always an `ArrayBuffer`.
     *   Consumers can parse the payload as bytes without branching on `dataType`.
     * - `false` (default): consumers must branch on `dataType` to determine the type of `data`.
     *
     * Only affects the SCTP DataChannel path (`channelType: 'sctp'`); the MQTT path (`channelType: 'mqtt'`)
     * picks the type from the actual content and is not influenced by this flag.
     *
     * @default false
     */
    preferArrayBuffer?: boolean;
  };
}

/**
 * TRRO Class, instance must be initialized before Join
 *
 * @param {Object} params
 * @param {CloudMode} [params.cloudMode='private'] - Cloud mode, optional.<br/>public - Public cloud, private - Private cloud.<br/>Default: private.
 * @param {string | string[]} [params.serverIp] - Domain or IP of signaling dispatch server, can provide multiple addresses.<br/>Invalid in public cloud mode; Required in private cloud mode.
 * @param {number} [params.port] - Signaling service port.<br/>Invalid in public cloud mode; Default 3000/443 (with HTTPS) in private cloud.
 * @param {boolean} [params.https=false] - Enable HTTPS protocol, optional.<br/>Forced in public cloud mode; Default false in private cloud.
 * @param {Object} [params.mqttOptions] - MQTT configuration, optional.
 * @param {string | string[]} [params.mqttOptions.url] - MQTT server domain/IP, can provide multiple addresses.<br/>Usually not needed in public cloud; Defaults to serverIp in private cloud.
 * @param {number} [params.mqttOptions.port] - MQTT server port.<br/>Usually not needed in public cloud; Default 2833 in private cloud.
 * @param {boolean} [params.mqttOptions.forceLogin=false] - Force MQTT login, optional. Default false.
 * @param {string | number} params.projectId - Project ID
 * @param {string} params.remoteDeviceId - Remote device ID
 * @param {string} params.password - Remote device password
 * @param {TRROMediaObserver} [params.observer] - Event callback functions. See {@link TRROMediaObserver} for full field documentation.
 * @param {Object} [params.loggerOption] - Logger configuration. See {@link TRROConfig#loggerOption} for full field documentation.
 * @param {Object} [params.customMessageOption] - Custom-message (onControlData) options. See {@link TRROConfig#customMessageOption} for full field documentation.
 *
 * @example
 * // Minimal public cloud configuration
 * new TRROSDK({
 *  cloudMode: 'public',
 *  projectId: 'xxxxxxxx',
 *  remoteDeviceId: 'xxxxxxxx',
 *  password: '********',
 * });
 *
 * // Minimal private cloud configuration
 * new TRROSDK({
 *  serverIp: '1.1.1.1',
 *  projectId: 1111,
 *  remoteDeviceId: 'xxxxxxxx',
 *  password: '',
 * });
 */
class TRROSDK {
  constructor(params: TRROConfig);

  /**
   * @async
   * @description Initialize MQTT connection
   * @returns Promise object
   * | Name    | Type   | Description                               |
   * | ------- | ------ | ----------------------------------------- |
   * | code    | number | 0 success, 21 MQTT connect error, 26 MQTT already connected, etc. |
   * | message | string | Description and error information         |
   * @example init()
   */
  init(): Promise<{ code: number; message: string }>;

  /**
   * @description Set temporary session key for field device
   * @example setSessionPermissionToken('xxxxxxxx', '********')
   */
  setSessionPermissionToken(fieldDeviceId: string, token: string): void;
  /**
   * @async
   * @description Connect to specified field device
   * @param {string} fieldDeviceId - Field device ID
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 Already joined, 2 joining, 20 MQTT client not found, 21 MQTT connection error, 100 request error |
   * | message | string | Description and error message                     |
   * @example connect('xxxxxxxx')
   */
  connect(fieldDeviceId: string): Promise<{ code: number; message: string }>;

  /**
   * @ignore
   * @async
   * @description Set video render configuration
   * @param {Object} params
   * @param {string} params.fieldDeviceId - Field device ID
   * @param {Object[]} params.renderConfigs - Render configurations
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 2 joining, 20 MQTT client not found, 21 MQTT connection error, 100 request error |
   * | message | string | Description and error message                     |
   * @example
   * setVideoRenderConfig({
   *  fieldDeviceId: '<FIELD-DEVICE-ID>',
   *  renderConfigs: [{
   *    streamId: 0,
   *    mount: '#test-div-0',
   *    nightVision: true,
   *  }],
   * })
   */
  setVideoRenderConfig(params: {
    /**
     * Field device ID
     */
    fieldDeviceId: string;
    renderConfigs: {
      /**
       * Video stream ID
       */
      streamId: number;
      mount?: string;
      /**
       * Rotation angle, range 0-360
       *
       * @default 0
       */
      rotate?: number;
      /**
       * Zoom settings
       */
      zoom?: {
        /**
         * Zoom origin position `[x, y]`, both x and y range from `[0, 1]`
         *
         * @example
         * [0, 0]     // Top-left corner
         * [0.5, 0.5] // Center
         * [1, 1]     // Bottom-right corner
         *
         * @default
         * [0.5, 0.5]
         */
        position?: [number, number];
        /**
         * Zoom ratio, range `(0, 10]`
         *
         * @default 1
         */
        ratio?: number;
      };
      /**
       * Mirror mode
       *
       * - `horizontal` Horizontal mirror
       * - `vertical` Vertical mirror
       * - `off` Off
       *
       * @default "off"
       */
      mirror?: 'horizontal' | 'vertical' | 'off';
      /**
       * Night vision enhancement
       *
       * @default false
       */
      nightVision?: boolean;
      /**
       * Render backend controlling how `rotate` / `zoom` / `mirror` geometric transforms are implemented:
       *
       * - `auto` (default): geometric transforms prefer **CSS Transform** (zero WebGL context cost, significantly lowers WebGL OOM risk)
       * - `css`: force **CSS Transform**
       * - `webgl`: force **WebGL** shader
       *
       * **Differences between CSS and WebGL (choose per your needs)**:
       * - Arbitrary-angle `rotate`: CSS rotates the whole video element, leaving black triangular gaps at the corners; WebGL rotates sampling within a fixed frame and clamps out-of-bound regions to edge pixels (no black corners). If you rely on the "no black corner" behavior, set `renderBackend: 'webgl'` explicitly.
       * - Zoom-out (`zoom.ratio < 1`): CSS leaves black bars; WebGL stretches edge pixels to fill.
       * - Zoom-in / mirror: both behave essentially the same.
       *
       * **Note**: `nightVision` is a per-pixel enhancement with no CSS equivalent. When `nightVision: true`, WebGL is always used regardless of `renderBackend`.
       *
       * @default "auto"
       */
      renderBackend?: 'auto' | 'css' | 'webgl';
    }[];
  }): Promise<{ code: number; message: string }>;

  /**
   * @async
   * @description Subscribe to tracks (pull stream)
   * @param {Object} params
   * @param {string} params.fieldDeviceId - Field device ID
   * @param {Object[]} params.containers
   * @param {number} params.containers.streamId - Video stream ID
   * @param {string} params.containers.mount - Video stream mount point (element ID format, e.g., #test-div-0).
   *                SDK will automatically create media elements under this node for playback.
   * @param {Function} params.containers.callback - Callback with end-to-end latency and requestVideoFrameCallback metadata
   * @param {string} [params.audioContainer] - Audio stream mount point (element ID format, e.g., #test-div-0).
   *                If not provided, audio stream won't be pulled. Invalid if containers field is missing.
   * @param {boolean} [params.incremental=false] - Whether to use incremental subscription.
   *                Default false (overwrite existing subscriptions), true keeps existing subscriptions while adding/updating new ones.
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 2 joining, 3 failed, 4 streams not published, 5 empty containers, 20 MQTT client not found, 21 MQTT not connected, 100 request error |
   * | message | string | Description and error message                     |
   * @example
   * subscribe({
   *   fieldDeviceId: 'xxxxxxxx',
   *   containers: [{
   *     streamId: 0,
   *     mount: '#test-div-0',
   *     callback: ({latency, metadata}) => {
   *       console.log(latency, metadata);
   *     }
   *   },{
   *     streamId: 1,
   *     mount: '#test-div-1',
   *     callback: ({latency, metadata}) => {
   *       console.log(latency, metadata);
   *     }
   *   }],
   *   audioContainer: '#test-div-audio'
   * })
   */
  subscribe({
    fieldDeviceId,
    containers,
    audioContainer,
  }: {
    fieldDeviceId: string;
    containers: {
      streamId: number;
      mount: string;
      callback?: ({
        latency,
        latencyMax,
        gatewayTime,
        metadata,
        srcUserId,
        trackId,
      }: {
        latency: number;
        latencyMax: number;
        gatewayTime: number;
        metadata: VideoFrameCallbackMetadata & { now: DOMHighResTimeStamp };
        srcUserId: string;
        trackId: string;
      }) => void;
    }[];
    audioContainer?: string;
    incremental?: boolean;
  }): Promise<{ code: number; message: string }>;

  /**
   * @async
   * @description Unsubscribe from tracks (stop pulling stream)
   * @param {Object} params
   * @param {string} params.fieldDeviceId - Field device ID
   * @param {number[]} params.streamIds - Video stream IDs to unsubscribe
   * @param {boolean} [params.audioStream=false] - Whether to unsubscribe audio stream
   * @param {boolean} [params.sendUnpublishNotification=false] - Whether to immediately notify gateway to stop publishing
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 2 joining, 5 empty stream IDs, 20 MQTT client not found, 21 MQTT not connected, 100 request error |
   * | message | string | Description and error message                     |
   * @example unsubscribe({ fieldDeviceId: 'xxxxxxxx', streamIds: [0] })
   */
  unsubscribe({
    fieldDeviceId,
    streamIds,
    audioStream,
    sendUnpublishNotification,
  }: {
    fieldDeviceId: string;
    streamIds: number[];
    audioStream?: boolean;
    sendUnpublishNotification?: boolean;
  }): Promise<{ code: number; message: string }>;
  /**
   * @async
   * @description Publish audio stream (push stream)
   * <br/>Audio streaming requires successful video subscription first. Unsubscribing all video streams will automatically stop audio publishing via `onEvent` callback.
   * <br/>Only one audio stream per SDK instance is allowed.
   * <br/>**Requires `master` permission via `requestPermission` in server mode**.
   * @param {Object} params
   * @param {MediaStreamTrack} params.audioTrack - Audio stream track obtained via `navigator.mediaDevices.getUserMedia()`
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 2 joining, etc.           |
   * | message | string | Status description and error information        |
   * @example
   * try {
   *   // Initialize SDK instance
   *   const client = new TRROSDK({ ... });
   *   // Connect to field device
   *   await client.connect('xxxxxxxx');
   *   // Subscribe to video streams
   *   await client.subscribe({ fieldDeviceId: 'xxxxxxxx', containers: [{ streamId: 0, mount: '#test-div-0' }] });
   *   // Get local audio stream
   *   const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
   *   [audioTrack] = stream.getAudioTracks()
   *   // Publish audio stream
   *   await client.publish({ audioTrack });
   * } catch () {}
   */
  publish({ audioTrack }: { audioTrack: MediaStreamTrack }): Promise<{ code: number; message: string }>;

  /**
   * @async
   * @description Unpublish audio stream (stop pushing)
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 2 joining, etc.           |
   * | message | string | Status description and error information        |
   * @example unpublish();
   */
  unpublish(): Promise<{ code: number; message: string }>;

  /**
   * @async
   * @description Mute/unmute specific audio stream
   * @param {Object} params
   * @param {string} params.deviceId - Field device ID or remote device ID (`remoteDeviceId`)
   * @param {boolean} params.muted - true: mute, false: unmute
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 2 joining, etc.           |
   * | message | string | Status description and error information        |
   * @example muteAudio({ deviceId: 'xxxxxxxx', muted: true })
   */
  muteAudio({ deviceId, muted }: { deviceId: string; muted: boolean }): Promise<{ code: number; message: string }>;

  /**
   * @async
   * @description Disconnect from specific field device
   * @param {string} fieldDeviceId - Field device ID
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 3 failed, etc.            |
   * | message | string | Status description and error information        |
   * @example disconnect('xxxxxxxx')
   */
  disconnect(fieldDeviceId: string): Promise<{ code: number; message: string }>;

  /**
   * @async
   * @description Disconnect from all field devices
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 3 failed, etc.            |
   * | message | string | Status description and error information        |
   * @example disconnectAll()
   */
  disconnectAll(): Promise<{ code: number; message: string }>;

  /**
   * @async
   * @description Destroy all connections
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 3 failed, etc.            |
   * | message | string | Status description and error information        |
   * @example destroy()
   */
  destroy(): Promise<{ code: number; message: string }>;

  /**
   * @description Temporarily update field device encoding configuration
   * <br/>Changes persist until device reboot
   * <br/>Only modifies specified fields, others remain unchanged
   * @param {string} fieldDeviceId - Field device ID
   * @param {Object[]} params
   * @param {number} params.streamId - Video stream ID
   * @param {Object} params.encodeConfig - Encoding configuration
   * @param {number} [params.encodeConfig.encodeWidth] - Encoding width
   * @param {number} [params.encodeConfig.encodeHeight] - Encoding height
   * @param {number} [params.encodeConfig.minWidth] - Minimum width
   * @param {number} [params.encodeConfig.fps] - Target FPS
   * @param {number} [params.encodeConfig.minFps] - Minimum FPS
   * @param {number} [params.encodeConfig.bps] - Target bitrate (kbps)
   * @param {number} [params.encodeConfig.minBps] - Minimum bitrate (kbps)
   * @param {boolean} [params.encodeConfig.forceMinBps] - Enforce minimum bitrate
   * @returns
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 2 joining, etc.           |
   * | message | string | Status description and error information        |
   * @example changeFieldDeviceEncodeConfig('xxxxxxxx', [{ streamId: 0, encodeConfig: { minBps: 1000, forceMin: true } }])
   */
  changeFieldDeviceEncodeConfig(
    fieldDeviceId: string,
    params: {
      streamId: number;
      encodeConfig: {
        encodeWidth?: number;
        /** @deprecated Use encodeHeight instead */
        encodeHeigt?: number;
        encodeHeight?: number;
        minWidth?: number;
        fps?: number;
        minFps?: number;
        bps?: number;
        minBps?: number;
        forceMinBps?: boolean;
      };
    }[],
  ): { code: number; message: string };
  /**
   * @async
   * @description Request gateway permissions
   * @param {string} fieldDeviceId - Field device ID
   * @param {Object} params
   * @param {PermissionState} params.permission - 'master' for control+view permissions, 'guest' for view-only
   * @returns
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 2 joining, 20 MQTT client not found, 21 MQTT connection error, 22 MQTT publish failed, 23 request pending, 24 timeout |
   * | message | string | Status description and error information          |
   * @example requestPermission({ fieldDeviceId: 'xxx', permission: 'master' })
   */
  requestPermission(
    fieldDeviceId: string,
    params: { permission: PermissionState },
  ): { code: number; message: string; data?: { deviceId: string; userId: string; permission: PermissionState } };

  /**
   * @async
   * @description Retrieve gateway device list
   * @returns
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 20 MQTT client not found, 21 MQTT connection error, 22 request failed, 23 request pending, 24 timeout |
   * | message | string | Status description and error information          |
   * @example pullGatewayList()
   */
  pullGatewayList(): Promise<{ code: number; message: string; data?: Gateway[] }>;

  /**
   * @async
   * @description Retrieve specified gateway device information
   * @param {string} gatewayId - Gateway device ID
   * @returns
   * | Name    | Type    | Description                                      |
   * | ------- | ------- | ------------------------------------------------ |
   * | code    | number  | 0 success, 20 MQTT client not found, 21 MQTT connection error, 22 request failed, 23 request pending, 24 timeout |
   * | message | string  | Status description and error information          |
   * | data    | Gateway | Gateway device information                       |
   * @example getGatewayInfo('gateway_id_123')
   */
  getGatewayInfo(gatewayId: string): Promise<{ code: number; message: string; data?: Gateway }>;

  /**
   * @async
   * @description Adjust video bitrate
   * @param {string} fieldDeviceId - Field device ID
   * @param {Object[]} params
   * @param {number} params.streamId - Video stream ID
   * @param {number} params.videoRate - Bitrate in kbps
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 2 joining, 3 failed, 20 MQTT client not found, 21 MQTT connection error, 100 request error |
   * | message | string | Status description and error information          |
   * @example adjustVideoRate('xxxxxxxx', [{ streamId: 0, videoRate: 3000 }, { streamId: 1, videoRate: 1000 }])
   */
  adjustVideoRate(
    fieldDeviceId: string,
    params: { streamId: number; videoRate: number }[],
  ): Promise<{ code: number; message: string }>;

  /**
   * @ignore
   * @async
   * @description Reset gateway
   * @param {string} fieldDeviceId - Field device ID
   * @param {Object} params
   * @param {number[]} params.streamIds - Stream IDs to reset
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined, 2 joining, 3 failed, 20 MQTT client not found, 21 MQTT connection error, 100 request error |
   * | message | string | Status description and error information          |
   * @example reset('xxxxxxxx', { streamIds: [0] })
   */
  reset(fieldDeviceId: string, params: { streamIds: number[] }): Promise<{ code: number; message: string }>;

  /**
   * @description Send custom message via DataChannel or MQTT. **Requires 'master' permission via requestPermission**.
   * @param {string} fieldDeviceId - Field device ID
   * @param {Object} params
   * @param {string|ArrayBuffer} params.data - Custom message content
   * @param {boolean} [params.ordered=true] - Use reliable transmission channel (only effective for SCTP channel, conflicts with channelType='mqtt')
   * @param {'sctp'|'mqtt'} [params.channelType='sctp'] - Message channel type, 'sctp' for DataChannel, 'mqtt' for MQTT
   * @returns Returns Promise object
   * | Name    | Type   | Description                                      |
   * | ------- | ------ | ------------------------------------------------ |
   * | code    | number | 0 success, 1 unjoined (SCTP only), 2 joining (SCTP only), 3 failed, 20 MQTT client not found, 21 MQTT connection error, 42 no permission, 45 no subscribed streams (SCTP only), 100 request error |
   * | message | string | Status description and error information          |
   *
   * @note
   * - **SCTP Channel**: Requires `init()` → `requestPermission('master')` → `connect()` → `subscribe()` at least one video stream
   * - **MQTT Channel**: Requires `init()` → `requestPermission('master')`, no need for `connect()` and `subscribe()`
   *
   * @example
   * // Send via SCTP DataChannel (requires connect and subscribe first)
   * sendControlData('xxxxxxxx', { data: 'xxx', ordered: true })
   *
   * // Send via MQTT (only login required, no connect and subscribe needed)
   * await sdk.init(); // Login successful
   * await sdk.requestPermission('fieldDeviceId', { permission: 'master' });
   * sendControlData('xxxxxxxx', { data: 'xxx', channelType: 'mqtt' })
   */
  sendControlData(
    fieldDeviceId: string,
    params: { data: string | ArrayBuffer; ordered?: boolean; channelType?: 'sctp' | 'mqtt' },
  ): { code: number; message: string };

  /**
   * @async
   * @description Get diagnosis report. Usually need to wait 10 seconds to get the report, maximum wait time is 15 seconds.
   *
   * @param {string} fieldDeviceId - Field device ID
   *
   * @returns {Promise<DiagnosisReport>} Returns diagnosis report.
   *
   * @example
   * requestDiagnosisReport('<FIELD-DEVICE-ID>')
   */
  requestDiagnosisReport(fieldDeviceId: string): Promise<DiagnosisReport>;
}

export default TRROSDK;
