import { Signal, Atom, Scope } from 'nrgy';
import {
  EventError,
  EventOk,
  SyncResult,
  HttpClientFetchError,
  HttpClientResponseError,
  JazzSdkPlugin,
  JazzRoomExtender,
  JazzRoom,
  JazzClientExtender,
  JazzClient,
  Extender,
  MakeExtender,
  QueryAtom,
  ConfigFlags,
  Query,
  JazzRoomParticipantId,
  LocalAudioOutputDevice,
  JazzSdk,
  MediaType,
  LogLevel,
  LogEvent,
  ResultSuccess,
  ResultFailure,
  EventBus,
  DeepUnion,
  MeetingHistory,
  Timestamp,
  JazzRoomParticipantRole,
  CollectMetricsHandler,
  CollectMetricsMetric,
  CollectMetricsMeta,
  CollectMetricsMetricData,
  CollectMetricsMetaActions,
  ResponseExtender,
} from '@salutejs/jazz-sdk-web';
import { Controller } from 'nrgy/mvc';
import { Observable } from 'rxjs';
import { Controller as Controller$1 } from 'rx-effects';
import { toQuery, toAction } from 'nrgy/rx-effects';
import { observe } from 'nrgy/rxjs';
import * as ditox from 'ditox';
import { ModuleDeclaration } from 'ditox';

type ServerRecordingEventStarted = {
  type: 'serverRecordStarted';
};
type ServerRecordingEventStopped = {
  type: 'serverRecordStopped';
  payload: {
    reason?: 'DURATION_LIMIT_EXCEEDED' | 'STOP_MANUALLY';
  };
};
type ServerRecordingEventUploaded = {
  type: 'serverRecordUploaded';
};
type ServerRecordingEventStartError = {
  type: 'serverRecordStartError';
  payload?: {
    error: 'forbidden' | 'alreadyStarted' | 'storageCapacityExceeded';
  };
};
type ServerRecordingEventStopError = {
  type: 'serverRecordStopError';
  payload?: {
    error: 'forbidden' | 'notStarted';
  };
};
type ServerRecordingEventUploadError = {
  type: 'serverRecordUploadedError';
};
type ServerRecordingEventError = {
  type: 'serverRecordError';
};
type ServerRecordingEventAutoStartError = {
  type: 'serverRecordAutoStartError';
  payload?: {
    error: 'internalError' | 'hasNoPermission' | 'storageSizeExceeded';
  };
};
type ServerRecordingEvents =
  | ServerRecordingEventStarted
  | ServerRecordingEventStopped
  | ServerRecordingEventUploaded
  | ServerRecordingEventStartError
  | ServerRecordingEventStopError
  | ServerRecordingEventUploadError
  | ServerRecordingEventError
  | ServerRecordingEventAutoStartError;
type RoomServerRecordingStatus =
  | 'inactive'
  | 'processStarting'
  | 'recording'
  | 'processStopping';
type RoomServerRecordingSupportedStatus =
  | 'supported'
  | 'pending'
  | 'unsupported';
type BaseServerRecordingRoomService = {
  events: Signal<ServerRecordingEvents>;
  /**
   * Starts server recording
   * @throws {ServerRecordingError} When recording fails to start with codes:
   * - 'FORBIDDEN'
   * - 'VIDEO_RECORD_ALREADY_STARTED'
   * - 'VIDEO_RECORD_STORAGE_CAPACITY_EXCEEDED'
   * - 'VIDEO_RECORD_START_ERROR'
   */
  startServerRecording: () => Promise<void>;
  /**
   * Stops server recording
   * @throws {ServerRecordingError} When recording fails to stop with codes:
   * - 'FORBIDDEN'
   * - 'VIDEO_RECORD_STOP_ERROR'
   * - 'VIDEO_RECORD_NOT_STARTED'
   */
  stopServerRecording: () => Promise<void>;
};
type ServerRecordingRoomService = BaseServerRecordingRoomService & {
  serverSupportedStatus: Atom<RoomServerRecordingSupportedStatus>;
  isReady: Atom<boolean>;
  status: Atom<RoomServerRecordingStatus>;
  canManageServerRecordList: Atom<boolean>;
  canManageServerRecord: Atom<boolean>;
};
declare class ServerRecordingError extends Error {
  readonly code: string;
  readonly error?: string;
  constructor(code: string, message?: string);
}

type ServerRecordingStartErrorEvent = EventError<
  | 'FORBIDDEN'
  | 'VIDEO_RECORD_ALREADY_STARTED'
  | 'VIDEO_RECORD_STORAGE_CAPACITY_EXCEEDED'
  | 'VIDEO_RECORD_START_ERROR'
>;
type ServerRecordingStopErrorEvent = EventError<
  'FORBIDDEN' | 'VIDEO_RECORD_STOP_ERROR' | 'VIDEO_RECORD_NOT_STARTED'
>;
type ServerRecordingMessages =
  | EventOk<'video-record-started'>
  | EventOk<'video-record-uploaded'>
  | EventOk<
      'video-record-stopped', // запись остановлена
      {
        reason: 'STOP_MANUALLY' | 'DURATION_LIMIT_EXCEEDED';
      }
    >
  | EventError<
      'START_ERROR' | 'AUTOSTART_ERROR' | 'RECORD_ERROR' | 'UPLOAD_ERROR', // ошибка загрузки записи
      unknown,
      'video-record-error'
    >;

type GetVideoStorageResponse = {
  usedMb: number;
  availableMb?: number;
  capacityMb: number;
};
type StartVideoRecordErrors =
  | {
      type: 'storageQuotaExceeded';
    }
  | {
      type: 'clientError';
    };
type StartVideoRecordResponse = SyncResult<
  undefined,
  | HttpClientFetchError
  | HttpClientResponseError<StartVideoRecordErrors, void, void>
>;
type GetVideoFindRequest = Readonly<{
  pageNumber?: number;
  pageSize: number;
}>;
type GetVideoFindResponse = {
  totalPages: number;
  records: VideoRecord[];
  pageNumber?: number;
  pageSize?: number;
};
type VideoRecord = {
  id: number;
  title: string;
  createdAt: string;
  durationSeconds: number;
  publicId?: string;
  sizeMb?: number;
  publicAccess?: boolean;
  watchUrl?: string | null;
  downloadUrl?: string;
  permissions: {
    canTogglePublicAccess: boolean;
  };
  status: 'UPLOADED' | 'STOPPED';
};
type GetVideoResponse = VideoRecord;
type RoomRecordingServiceClient = Readonly<{
  getVideoStorage: () => Promise<GetVideoStorageResponse>;
  startVideoRecord: (roomId: string) => Promise<StartVideoRecordResponse>;
  stopVideoRecord: (roomId: string) => Promise<void>;
  getVideoFind: (request: GetVideoFindRequest) => Promise<GetVideoFindResponse>;
  getVideo: (recordId: string) => Promise<GetVideoResponse>;
  updateVideoTitle: (recordId: number, title: string) => Promise<void>;
  updateVideoPublicAccess: (
    recordId: number,
    publicAccess: boolean,
  ) => Promise<void>;
  deleteVideo: (recordId: number) => Promise<void>;
  getDownloadLink: (recordPublicId: string) => Promise<string>;
  getS3DownloadLink: (url: string) => Promise<string | undefined>;
}>;

declare function serverRecordingPlugin(): JazzSdkPlugin;

declare const getServerRecording: <R extends JazzRoomExtender[]>(
  jazzRoom: JazzRoom<R>,
) => ServerRecordingRoomService;

declare const getServerRecordingClient: <
  R extends JazzRoomExtender[],
  C extends JazzClientExtender[],
>(
  jazzRoom: JazzRoom<R> | JazzClient<C>,
) => RoomRecordingServiceClient;

type ServerRecordingCreateRoomParams = {
  serverVideoRecordAutoStartEnabled: boolean;
};
declare const withServerRecordingCreateRoom: (
  params: ServerRecordingCreateRoomParams,
) => Extender;

type ServerRecordingJazzRoom = MakeExtender<
  JazzRoomExtender,
  {
    permissions: {
      canStartServerVideoRecord?: boolean;
    };
  }
>;
type ServerRecordingJazzClient = MakeExtender<
  JazzClientExtender,
  {
    userInfo: {
      features: {
        serverVideoRecordAvailable?: boolean;
      };
    };
  }
>;

type AudioLevelService = {
  addMediaStream: (stream: MediaStream) => AudioLevel;
  removeMediaStream: (stream: MediaStream) => void;
};
type AudioLevel = {
  /**
   * @example 50
   * @description the min value is 0 and the max value is 100
   */
  level: QueryAtom<number>;
  isActive: QueryAtom<boolean>;
};

type AudioMixerEventStopAudio = Readonly<{
  type: 'stopAudio';
}>;
type AudioMixerEventStoppedAudio = Readonly<{
  type: 'stoppedAudio';
}>;
type AudioMixerEventStartAudio = Readonly<{
  type: 'startAudio';
}>;
type AudioMixerEventStartedAudio = Readonly<{
  type: 'startedAudio';
}>;
type AudioMixerEventAddMediaStream = Readonly<{
  type: 'addMediaStream';
  payload: {
    mediaStream: MediaStream;
    audioElement: HTMLAudioElement;
  };
}>;
type AudioMixerEventRemoveMediaStream = Readonly<{
  type: 'removeMediaStream';
  payload: {
    mediaStream: MediaStream;
    audioElement: HTMLAudioElement;
  };
}>;
type AudioMixerEventGainChanged = Readonly<{
  type: 'gainChanged';
  payload: {
    value: number;
  };
}>;
type AudioMixerEvent =
  | AudioMixerEventStopAudio
  | AudioMixerEventStoppedAudio
  | AudioMixerEventStartAudio
  | AudioMixerEventStartedAudio
  | AudioMixerEventAddMediaStream
  | AudioMixerEventRemoveMediaStream
  | AudioMixerEventGainChanged;
type AudioMixinNode = Controller<{
  destinationNode: MediaStreamAudioDestinationNode;
  addStream: (stream: MediaStream) => void;
  removeStream: (stream: MediaStream) => void;
}>;
type AudioMixer = Controller<{
  isSuspended: Atom<boolean>;
  isReady: Atom<boolean>;
  /**
   * Creating all the elements for audio capture and playback of external audio streams,
   * after that, loopback is immediately launched, if it is enabled.
   * Until this function works, the audio will not be played.
   */
  startAudio: () => Promise<void>;
  /**
   * Stops loopback if it was enabled and then clears all audio capture and playback elements.
   */
  stopAudio: () => Promise<void>;
  addMediaStream: (mediaStreams: MediaStream) => void;
  removeMediaStream: (mediaStreams: MediaStream) => void;
  removeAllMediaStreams: () => void;
  outputGain: Atom<number>;
  setOutputGain: (volume: number) => void;
  setOutputDevice: (deviceId: string) => Promise<void>;
  soundCheck: () => void;
  setRemoteInputMixer: (mixer: InputAudioMixerFactory | undefined) => void;
  getAudioContext: () => AudioContext;
  createMixinNode: () => Promise<AudioMixinNode>;
  setAudioSourcesPlayback: (enabled: boolean) => void;
  event$: Observable<AudioMixerEvent>;
}>;
type AudioEffectFactory<T extends Controller> = (
  audioContext: AudioContext,
) => T;
type AudioSource = Readonly<{
  sourceNode: Atom<MediaStreamAudioSourceNode>;
  scope: Scope;
  element: HTMLAudioElement;
  streamId: string;
}>;
type InputAudioMixer = Controller<{
  output: AudioNode;
  connect: (source: AudioSource) => Controller;
}>;
type InputAudioMixerFactory = AudioEffectFactory<InputAudioMixer>;

type AudioMixerFlags = ConfigFlags<{
  sampleRate?: number;
  latencyHint: string;
}>;

type AudioTrack = {
  participantId: JazzRoomParticipantId;
  mediaStream: MediaStream;
};
type AudioMixerParticipantsManager = Controller$1<{
  $mutedParticipants: Query<ReadonlySet<JazzRoomParticipantId>>;
  addTracks: (tracks: AudioTrack[]) => void;
  removeTracks: (tracks: AudioTrack[]) => void;
  removeAllTracks: () => void;
  muteParticipants: (
    isMuted: boolean,
    ids: Array<JazzRoomParticipantId>,
  ) => void;
}>;

type AudioOutputMixer = {
  /**
   * @deprecated use isSuspended
   */
  $isSuspended: Query<boolean>;
  isSuspended: Atom<boolean>;
  event$: Observable<AudioMixerEvent>;
  startAudio: () => Promise<void>;
  stopAudio: () => Promise<void>;
  setOutputDevice: (audioOutput: LocalAudioOutputDevice) => Promise<void>;
  outputGain: QueryAtom<number>;
  setOutputGain: (volume: number) => void;
  addMediaStream: (mediaStream: MediaStream) => void;
  removeMediaStream: (mediaStream: MediaStream) => void;
  muteParticipants: (
    isMuted: boolean,
    ids: Array<JazzRoomParticipantId>,
  ) => void;
};

type AudioOutputMixerPluginOptions = Partial<{
  flags?: Partial<AudioMixerFlags>;
  audioLevelAnimationInterval?: number | undefined;
  checkLoadFirstAudioPackage?: boolean;
}>;
type AudioOutputMixerContext = {
  audioMixer: AudioMixer;
  audioOutputMixer: AudioOutputMixer;
  audioMixerParticipantsManager: AudioMixerParticipantsManager;
  audioLevel: AudioLevelService;
};

/**
 * Позволяет локально управлять звуком участников конференций
 */
declare function audioOutputMixerPlugin(
  options?: AudioOutputMixerPluginOptions,
): JazzSdkPlugin;

declare const AUDIO_GAIN_DEFAULT = 1;
declare const MIN_AUDIO_GAIN_VALUE = 0;
declare const MAX_AUDIO_GAIN_VALUE = 10;

/**
 * @example
 * ```js
 * const { getAudioOutputMixer } from '@salutejs/jazz-sdk-web-plugins';
 * const audioOutputMixer = getAudioOutputMixer(sdk);
 * const initOutputGain = audioOutputMixer.outputGain.get();
 * ```
 * @example
 * ```js
 * const { getAudioOutputMixer } from '@salutejs/jazz-sdk-web-plugins';
 * const audioOutputMixer = getAudioOutputMixer(sdk);
 * audioOutputMixer.setOutputGain(5);
 * ```
 * @example
 * ```js
 * const { getAudioOutputMixer } from '@salutejs/jazz-sdk-web-plugins';
 * const audioOutputMixer = getAudioOutputMixer(sdk);
 * const unsubscribe = handleEvent(
 *   audioOutputMixer.event$,
 *   'gainChanged',
 *   ({ payload }) => {
 *     setOutputGain(payload.value);
 *   },
 * );
 * ```
 */
declare function getAudioOutputMixer(sdk: JazzSdk): AudioOutputMixer;

type RoomAudioMixerEvent = Readonly<
  | {
      type: 'muteParticipantsChanged';
      payload: {
        isMuted: boolean;
        participantIds: JazzRoomParticipantId[];
      };
    }
  | {
      type: 'muteAllParticipantsChanged';
      payload: {
        isMuted: boolean;
      };
    }
>;
type AudioOutputMixerManager = {
  mutedParticipants: QueryAtom<ReadonlySet<JazzRoomParticipantId>>;
  isMutedAll: QueryAtom<boolean>;
  muteParticipants: (
    isMuted: boolean,
    participantIds: JazzRoomParticipantId[] | JazzRoomParticipantId,
  ) => void;
  muteAllParticipants: (isMutedAll: boolean) => void;
  event$: Observable<RoomAudioMixerEvent>;
};

/**
 * @example
 * ```js
 * const { getAudioOutputMixerManager } from '@salutejs/jazz-sdk-web-plugins'
 * const audioOutputMixerManger = getAudioOutputMixerManager(room)
 * audioOutputMixerManger.mutedParticipants.get().has(participantId)
 * ```
 * @example
 * ```js
 * const { getAudioOutputMixerManager } from '@salutejs/jazz-sdk-web-plugins'
 * const audioOutputMixerManger = getAudioOutputMixerManager(room)
 *
 * const unsubscribe = handleEvent(
 *  audioOutputMixerManger.event$,
 *  'muteParticipantsChanged',
 *  ({ payload }) => {
 *    if (payload.participantIds.some((id) => participantId === id)) {
 *      setIsMuted(payload.isMuted);
 *    }
 *  },
 * );
 * ```
 * @example
 * ```js
 * const { getAudioOutputMixerManager } from '@salutejs/jazz-sdk-web-plugins'
 * const audioOutputMixerManger = getAudioOutputMixerManager(room)
 * audioOutputMixerManger.muteParticipants(!isMuted, [participantId]);
 * ```
 */
declare function getAudioOutputMixerManager(
  room: JazzRoom,
): AudioOutputMixerManager;

/**
 * Event name for subscribe to all events in transport
 */
declare type AllEventTypes = '*';

declare interface BaseEventBusReadonly<EVENTS extends EventLike>
  extends BaseEventBusSubscriber<EVENTS> {
  name?: string;
}

declare interface BaseEventBusSubscriber<EVENTS extends EventLike> {
  /**
   * Method for subscribing to bus events.
   * In addition to events of the type, you can also specify the * event,
   * which will allow you to subscribe to all bus events.
   * The method returns a function for unsubscribing the callback
   * (this can also be done via the off or removeEventListener methods).
   *
   * If the onSubscribe lifecycle method is passed,
   * it will be called when this event is sent.
   *
   * If the transport was destroyed, this method will do nothing.
   *
   * @example
   * ```ts
   * type Events = { event: string };
   * const eventBus = createBaseEventBus<Events>();
   *
   * const unsubscriber = eventBus.on('event', (event, payload) => console.log(payload));
   * unsubscriber();
   *
   * eventBus.send('event', 'test');
   * ```
   */
  on<EVENT extends string & keyof EVENTS>(
    event: EVENT,
    callback: (event: EVENT, payload: EVENTS[EVENT]) => void,
  ): Unsubscriber;
  /**
   * unsubscribe from an event.
   * If there are no subscribers left for the event, we remove it from the map.
   *
   * If the onUnsubscribe lifecycle callback is passed,
   * it will be called each time this function is called.
   *
   * If the transport was destroyed, the method does not work.
   *
   * @example
   * ```ts
   * type Events = { event: string };
   * const eventBus = createBaseEventBus<Events>();
   *
   * function handler(type: string, payload: string): void {}
   *
   * eventBus.on('event', handler);
   * eventBus.off('event', handler);
   * ```
   */
  off<EVENT extends string & keyof EVENTS>(
    event: EVENT,
    callback: (event: EVENT, payload: EVENTS[EVENT]) => void,
  ): void;
}

declare interface BaseTransportNodeReadonly {
  name?: string;
  __isRoot: Readonly<false>;
  /**
   * A property indicating that a class has been destroyed.
   * Once resolved, all methods in it stop working and the data is cleared.
   */
  isDestroyed: boolean;
  /**
   * Method to get the root node object referenced by the node.
   */
  getTransports: () => TransportRootNodes;
}

declare interface BaseTransportRoot extends DestroyedNode {
  name?: string;
  __isRoot: Readonly<true>;
}

/**
 * A node that has a cleanup mechanism. After the method chchchch is executed,
 * the node becomes inactive because event subscriptions and message sending stop functioning.
 */
declare interface DestroyedNode {
  /**
   * whether the transport is destroyed.
   * If the transport is destroyed,
   * then subscriptions and event sending do not work, and the subscriber list is destroyed.
   * Also, all dependent nodes are automatically unsubscribed from the destroyed node.
   */
  isDestroyed: boolean;
  destroy(): void;
}

declare type EventLike = Record<string, unknown>;

declare type Namespace = string;

declare interface SubscribeNodeSubscribers<EVENTS extends EventLike> {
  on<
    EVENTS_KEYS extends keyof EVENTS,
    TYPE extends string,
    NAMESPACES extends UtilsTypeFilterTypesWithNamespaces<
      string & EVENTS_KEYS,
      TYPE
    >,
    EVENT_TYPE extends
      | `${NAMESPACES}:${AllEventTypes}`
      | AllEventTypes
      | (string & EVENTS_KEYS),
    NEW_NAMESPACE extends UtilsTypeFilterTypesWithNamespaces<EVENT_TYPE, TYPE>,
    CALLBACK_EVENTS extends EVENT_TYPE extends AllEventTypes
      ? string & EVENTS_KEYS
      : EVENT_TYPE extends `${NAMESPACES}:${AllEventTypes}`
        ? UtilsTypeRemoveNamespaceFromType<string & EVENTS_KEYS, NEW_NAMESPACE>
        : EVENT_TYPE,
    CALLBACK_PARAMS extends {
      [TYPE in CALLBACK_EVENTS]: [event: TYPE, payload: EVENTS[TYPE]];
    },
  >(
    event: EVENT_TYPE,
    callback: (...args: CALLBACK_PARAMS[CALLBACK_EVENTS]) => void,
  ): Unsubscriber;
  once<
    EVENTS_KEYS extends keyof EVENTS,
    TYPE extends string,
    NAMESPACES extends UtilsTypeFilterTypesWithNamespaces<
      string & EVENTS_KEYS,
      TYPE
    >,
    EVENT_TYPE extends
      | `${NAMESPACES}:${AllEventTypes}`
      | AllEventTypes
      | (string & EVENTS_KEYS),
    NEW_NAMESPACE extends UtilsTypeFilterTypesWithNamespaces<EVENT_TYPE, TYPE>,
    CALLBACK_EVENTS extends EVENT_TYPE extends AllEventTypes
      ? string & EVENTS_KEYS
      : EVENT_TYPE extends `${NAMESPACES}:${AllEventTypes}`
        ? UtilsTypeRemoveNamespaceFromType<string & EVENTS_KEYS, NEW_NAMESPACE>
        : EVENT_TYPE,
    CALLBACK_PARAMS extends {
      [TYPE in CALLBACK_EVENTS]: [event: TYPE, payload: EVENTS[TYPE]];
    },
  >(
    event: EVENT_TYPE,
    callback: (...args: CALLBACK_PARAMS[CALLBACK_EVENTS]) => void,
  ): Unsubscriber;
  off<
    EVENTS_KEYS extends keyof EVENTS,
    TYPE extends string,
    NAMESPACES extends UtilsTypeFilterTypesWithNamespaces<
      string & EVENTS_KEYS,
      TYPE
    >,
    EVENT_TYPE extends
      | `${NAMESPACES}:${AllEventTypes}`
      | AllEventTypes
      | (string & EVENTS_KEYS),
  >(
    type: EVENT_TYPE,
    callback: (...args: any[]) => void,
  ): void;
}

declare interface SubscribeReadonlyNode<EVENTS extends EventLike>
  extends SubscribeReadonlyNodeExtends<EVENTS> {}

declare type SubscribeReadonlyNodeExtends<EVENTS extends EventLike> =
  BaseTransportNodeReadonly & SubscribeNodeSubscribers<EVENTS>;

declare type TransportLifecycleEvents<EVENTS extends EventLike> = {
  /**
   * The transport was cleared. After that,
   * it stops functioning and all data in it is cleared.
   */
  destroy: undefined;
  /**
   * Subscribed to some event.
   * The object indicates what event was subscribed to and whether it is the first.
   */
  subscribe: {
    event: string & keyof EVENTS;
    mode: 'on' | 'once';
    subscriber: Parameters<TransportRootSubscribers<EVENTS>['on']>[1];
    subscribersCount: number;
  };
  /**
   * Unsubscribed from some event.
   * The object indicates what event was unsubscribed from and whether there are more subscribers.
   */
  unsubscribe: {
    event: string & keyof EVENTS;
    mode: 'on' | 'once';
    subscriber: Parameters<TransportRootSubscribers<EVENTS>['off']>[1];
    subscribersCount: number;
  };
};

declare interface TransportReadonlyNode<EVENTS extends EventLike>
  extends TransportReadonlyNodeBase<EVENTS> {
  lifecycle: TransportRoot<EVENTS>['lifecycle'];
}

declare type TransportReadonlyNodeBase<EVENTS extends EventLike> =
  TransportRootSubscribers<EVENTS> & BaseTransportNodeReadonly;

declare interface TransportRoot<EVENTS extends EventLike>
  extends TransportRootBase<EVENTS> {
  /**
   * Sync mode sending events
   *
   * @default false
   */
  sync?: Readonly<boolean>;
  /**
   * Method for sending an event to listeners.
   * If the transport was destroyed,
   * or no one is subscribed to this event, the method will do nothing.
   *
   * If there are subscribers to *,
   * they will listen to all events that were forwarded.
   *
   * The method works in 2 modes: synchronous and asynchronous (asynchronous mode is enabled by default).
   * To change this, you need to pass the 3rd argument.
   *
   * @example
   * ```ts
   * type Events = { event: string, event_empty: undefined };
   * const transport = createTransport<Events>();
   *
   * transport.on('event', (event, payload) => console.log(payload));
   * transport.on('event_empty', (event, payload) => console.log(payload));
   * transport.on('*', (event, payload) => console.log(payload));
   *
   * transport.send('event', 'test');
   * transport.send('event_empty');
   * transport.send('event_empty', undefined);
   * ```
   */
  send<
    TYPE extends string & keyof EVENTS,
    PARAMETERS extends EVENTS[TYPE] extends undefined
      ? (payload?: EVENTS[TYPE]) => void
      : (payload: EVENTS[TYPE]) => void,
  >(
    type: TYPE,
    ...other: Parameters<PARAMETERS>
  ): void;
  /**
   * Method for getting a node that has only subscription interfaces (on/once/off).
   * Recommended for use in public API services to hide methods
   * for direct control of transport state from the outside.
   */
  asReadonly(): TransportReadonlyNode<EVENTS>;
}

declare type TransportRootBase<EVENTS extends EventLike> =
  TransportRootSubscribers<EVENTS> &
    BaseTransportRoot & {
      /**
       * Transport lifecycle event bus. You can subscribe to 3 events:
       * 1) destroy - the transport was cleared. After that, it stops functioning and all data in it is cleared.
       * 2) subscribe - subscribed to some event. The object indicates what event was subscribed to and whether it is the first.
       * 3) unsubscribe - unsubscribed from some event. The object indicates what event was unsubscribed from and whether there are more subscribers.
       *
       * When the main transport is destroyed, the lifecycle event bus also dies.
       *
       * @example
       * ```ts
       * const transport = createTransport<Events>();
       *
       * transport.lifecycle.on('destroy', () => console.log('transport is destroy'));
       * transport.lifecycle.on('subscribe', ({ event, isFirstSubscribe }) => console.log(`subscribe to event ${event} isFirst=${isFirstSubscribe}`));
       * transport.lifecycle.on('unubscribe', ({ event, isHasSubscribers }) => console.log(`unsubscribe from event ${event} isHasSubscribers=${isHasSubscribers}`));
       *
       * const unsubscriber1 = transport.on('event1', () => {}) // subscribe to event event1 isFirst=true
       * const unsubscriber2 = transport.on('event1', () => {}) // subscribe to event event1 isFirst=false
       * const unsubscriber3 = transport.on('event2', () => {}) // subscribe to event event2 isFirst=true
       *
       * unsubscriber3() // unsubscribe from event event2 isHasSubscribers=false
       * unsubscriber2() // unsubscribe from event event1 isHasSubscribers=true
       * unsubscriber1() // unsubscribe from event event1 isHasSubscribers=false
       *
       * transport.destroy(); // transport is destroy
       * ```
       */
      lifecycle: Readonly<
        BaseEventBusReadonly<TransportLifecycleEvents<EVENTS>>
      >;
    };

/**
 * List of nodes the node is subscribed to.
 */
declare type TransportRootNodes = Record<Namespace, Array<TransportRoot<any>>>;

declare interface TransportRootSubscribers<EVENTS extends EventLike> {
  /**
   * Method for subscribing to bus events.
   * In addition to events of the type, you can also specify the * event,
   * which will allow you to subscribe to all bus events.
   * The method returns a function for unsubscribing the callback
   * (this can also be done via the off or removeEventListener methods).
   *
   * If the onSubscribe lifecycle method is passed,
   * it will be called when this event is sent.
   *
   * If the transport was destroyed, this method will do nothing.
   *
   * @example
   * ```ts
   * type Events = { event: string };
   * const transport = createTransport<Events>();
   *
   * transport.on('event', (event, payload) => console.log(payload));
   * const unsubscriber = transport.on('*', (event, payload) => console.log(payload));
   * unsubscriber();
   *
   * transport.send('event', 'test');
   * ```
   */
  on<
    EVENT_TYPE extends string & (keyof EVENTS | AllEventTypes),
    EVENT extends EVENT_TYPE extends AllEventTypes
      ? string & keyof EVENTS
      : EVENT_TYPE,
    CB extends {
      [TYPE in EVENT]: [TYPE, EVENTS[TYPE]];
    },
  >(
    event: EVENT_TYPE,
    callback: (...args: CB[EVENT]) => void,
  ): Unsubscriber;
  /**
   * A method for one-time subscription to bus events.
   * In addition to events of the type, you can also specify an event *,
   * which will allow you to subscribe to all bus events.
   * The method returns a function for unsubscribing the callback
   * (this can also be done via the off or removeEventListener methods).
   *
   * If the onSubscribe lifecycle method is passed,
   * it will be called when this event is sent.
   *
   * If the transport was destroyed, this method will do nothing.
   *
   * @example
   * ```ts
   * type Events = { event: string };
   * const transport = createTransport<Events>();
   *
   * transport.once('event', (event, payload) => console.log(payload));
   * const unsubscriber = transport.once('*', (event, payload) => console.log(payload));
   * unsubscriber();
   *
   * transport.send('event', 'test');
   * transport.send('event', 'test'); // not call subscribers
   * ```
   */
  once<
    EVENT_TYPE extends string & (keyof EVENTS | AllEventTypes),
    EVENT extends EVENT_TYPE extends AllEventTypes
      ? string & keyof EVENTS
      : EVENT_TYPE,
    CB extends {
      [TYPE in EVENT]: [TYPE, EVENTS[TYPE]];
    },
  >(
    event: EVENT_TYPE,
    callback: (...args: CB[EVENT]) => void,
  ): Unsubscriber;
  /**
   * unsubscribe from an event.
   * If there are no subscribers left for the event, we remove it from the map.
   *
   * If the onUnsubscribe lifecycle callback is passed,
   * it will be called each time this function is called.
   *
   * If the transport was destroyed, the method does not work.
   *
   * @example
   * ```ts
   * type Events = { event: string };
   * const transport = createTransport<Events>();
   *
   * function handler(type: string, payload: string): void {}
   *
   * transport.on('event', handler);
   * transport.off('event', handler);
   * ```
   */
  off<EVENT_TYPE extends string & (keyof EVENTS | AllEventTypes)>(
    event: EVENT_TYPE,
    callback: (...args: any[]) => void,
  ): void;
}

/**
 * unsubscribe function to unsubscribe from an event.
 */
declare type Unsubscriber = () => void;

/**
 * Utility type for getting namespace from event name (max size 5 namespaces)
 *
 * @example
 *
 * UtilsTypeFilterTypesWithNamespaces<'namespace1:event', 'event'> // 'namespace1'
 * UtilsTypeFilterTypesWithNamespaces<'namespace1:namespace2:event', 'event'> // 'namespace1:namespace2'
 * UtilsTypeFilterTypesWithNamespaces<'namespace1:namespace2:namespace3:event', 'event'> // 'namespace1:namespace2:namespace3'
 */
declare type UtilsTypeFilterTypesWithNamespaces<
  STR extends string,
  TYPE extends string,
> = STR extends `${infer NAMESPACE_1}:${infer NAMESPACE_2}:${infer NAMESPACE_3}:${infer NAMESPACE_4}:${infer NAMESPACE_5}:${TYPE}`
  ? `${NAMESPACE_1}:${NAMESPACE_2}:${NAMESPACE_3}:${NAMESPACE_4}:${NAMESPACE_5}`
  : STR extends `${infer NAMESPACE_1}:${infer NAMESPACE_2}:${infer NAMESPACE_3}:${infer NAMESPACE_4}:${TYPE}`
    ? `${NAMESPACE_1}:${NAMESPACE_2}:${NAMESPACE_3}:${NAMESPACE_4}`
    : STR extends `${infer NAMESPACE_1}:${infer NAMESPACE_2}:${infer NAMESPACE_3}:${TYPE}`
      ? `${NAMESPACE_1}:${NAMESPACE_2}:${NAMESPACE_3}`
      : STR extends `${infer NAMESPACE_1}:${infer NAMESPACE_2}:${TYPE}`
        ? `${NAMESPACE_1}:${NAMESPACE_2}`
        : STR extends `${infer NAMESPACE}:${TYPE}`
          ? `${NAMESPACE}`
          : never;

/**
 * Utility type of extraction from event name type without namespace
 *
 * @example
 * UtilsTypeRemoveNamespaceFromType<'namespace:event', 'namespace'> // 'event'
 */
declare type UtilsTypeRemoveNamespaceFromType<
  NAMESPACED_TYPE extends string,
  NAMESPACE extends string,
> = NAMESPACED_TYPE extends `${NAMESPACE}:${infer TYPE}` ? TYPE : never;

type VideoElementPoolSettingsVideoSource = Exclude<MediaType, 'audio'>;
type VideoElementPoolSettingsPausedSources = {
  [key in VideoElementPoolSettingsVideoSource]?: boolean;
};
type VideoElementPoolSettingsEvents = {
  pausedAllVideoChange: {
    isPaused: boolean;
  };
  pauseVideSourcesChange: {
    pausedVideoSources: VideoElementPoolSettingsPausedSources;
  };
};
/**
 * Сервис для работы с общими настройками
 * возможности запускать видео элементы
 *
 * Применяется как глобально, так и для
 * videoElementPoolForRoom
 */
type VideoElementPoolSettingsService = {
  events: TransportReadonlyNode<VideoElementPoolSettingsEvents>;
  isPausedAllVideo: Atom<boolean>;
  getIsPausedAllVideo: () => boolean;
  /**
   * Остановка или запуск видео элементов.
   */
  setPauseAllVideo: (isPaused: boolean) => void;
  pausedVideoSources: Atom<VideoElementPoolSettingsPausedSources>;
  getPausedVideoSources: () => VideoElementPoolSettingsPausedSources;
  /**
   * Остановка или запуск видео элементов нужного типа.
   * Влияет на видео элементы в videoElementPoolForRoom
   */
  playVideoSources: (
    sources:
      | VideoElementPoolSettingsVideoSource
      | VideoElementPoolSettingsVideoSource[],
  ) => void;
  pauseVideoSources: (
    sources:
      | VideoElementPoolSettingsVideoSource
      | VideoElementPoolSettingsVideoSource[],
  ) => void;
};

type VideoElementPoolStreamSize = {
  width: number;
  height: number;
};

type VideoElementPoolGlobalEvents = VideoElementPoolSettingsEvents;
type VideoElementPoolGlobalElementEvents = {
  pausedChange: {
    isPaused: boolean;
  };
  /**
   * стрим загружен в видео элемент
   * и готов для отображения на странице
   */
  isLoadingStreamChange: {
    isLoading: boolean;
  };
  streamUpdate: {
    stream: MediaStream | null | undefined;
  };
};
type VideoElementPoolGlobalElementForStream = {
  videoElement: HTMLVideoElement;
  /**
   * Находится ли элемент на паузе.
   * Элемент может быть приостановлен, если:
   * 1) stream с активным MediaStreamTrack не установлен
   * 2) Элемент не отображается, если включена настройка `stopElementsForStreamIfNotInteractionEnabled`.
   * 3) Все видеоролики остановлены (см метод `changePauseAllVideo`)
   * и включена настройка `allVideoPauseForStreamElementsEnabled`.
   */
  isPaused: Atom<boolean>;
  /**
   * Зависит от того, загружен ли поток в элемент video.
   * Когда значение свойства становится равным true, элемент будет готов к отображению видео.
   *
   * При обновление стрима в элементе значение сбрасывается и по новой ждет,
   * когда произойдет загрузка
   */
  isLoadedStream: Atom<boolean>;
  /**
   * mediaStream, который сейчас используется
   * для всех видео элементов
   */
  stream: Atom<MediaStream | null | undefined>;
  /**
   * размер видео стрима. Если стрима нет или видео замьючено,
   * то вычисления не происходит и значения высоты и ширины
   * равны 0
   */
  streamSize: Atom<VideoElementPoolStreamSize>;
  on: TransportReadonlyNode<VideoElementPoolGlobalElementEvents>['on'];
  once: TransportReadonlyNode<VideoElementPoolGlobalElementEvents>['once'];
  off: TransportReadonlyNode<VideoElementPoolGlobalElementEvents>['off'];
  /**
   * метод для обновления MediaStream в элементе
   */
  setStream: (stream: MediaStream | null | undefined) => void;
  /**
   * метод для ручного запуска видео элемента
   * не запуститься, если это запрещено политиками (см свойство isPaused)
   */
  play: () => void;
  /**
   * метод для ручной остановки видео элемента
   */
  pause: () => void;
  /**
   * метод для высвобождения видео элемента
   * и выполнения отписки всех методов
   *
   * Если видео элемент больше не нужен, то обязателен к выполнению
   */
  release: () => Promise<void>;
};
type VideoElementPoolGlobalSettings = {
  stream?: MediaStream | null | undefined;
  videoElementProps?: Partial<HTMLVideoElement>;
  /**
   * необходимо ли вычислять размер видео стрима
   * Будет работать, если:
   * 1) фича включена (флаг streamSizeCheckEnabled)
   * 2) видео элемент виден
   * 3) есть видео стрим с живым видео треком
   * 4) видео элемент не стоит на паузе
   *
   * @default false
   */
  watchStreamSize?: boolean;
  /**
   * необходимо ли отслеживать, что элемент виден на странице
   *
   * @default VideoElementPoolSettings['stopElementsForStreamIfNotInteractionEnabled']
   */
  watchIsVisible?: boolean;
};
/**
 * Сервис для работы с видео элементами вне конференций
 */
type VideoElementPoolGlobalService = {
  on: TransportReadonlyNode<VideoElementPoolGlobalEvents>['on'];
  once: TransportReadonlyNode<VideoElementPoolGlobalEvents>['once'];
  off: TransportReadonlyNode<VideoElementPoolGlobalEvents>['off'];
  isPausedAllVideo: VideoElementPoolSettingsService['isPausedAllVideo'];
  getIsPausedAllVideo: VideoElementPoolSettingsService['getIsPausedAllVideo'];
  /**
   * Остановка или запуск всех видео элементов в приложение.
   * Распространяется на все videoElementPoolForRoom.
   * Все видео элементы ставятся на паузу и из них выгружаются стримы
   * При обратном запуске работа видео элементов нормализуется
   *
   * Имеет более высокий приоритет над настройкой для всех videoElementPoolForRoom
   * т.е. если если глобально видео остановлены, а в videoElementPoolForRoom
   * запущено, то все видео будут остановлены
   *
   * Влияет на свойство isPaused у каждого элемента
   */
  setPauseAllVideo: VideoElementPoolSettingsService['setPauseAllVideo'];
  pausedVideoSources: VideoElementPoolSettingsService['pausedVideoSources'];
  getPausedVideoSources: VideoElementPoolSettingsService['getPausedVideoSources'];
  /**
   * Остановка или запуск всех видео данного videoSource в приложение.
   * Распространяется на все videoElementPoolForRoom.
   * Все видео элементы данного типа ставятся на паузу и из них выгружаются стримы
   * При обратном запуске работа видео элементов нормализуется
   *
   * Имеет более высокий приоритет над настройкой для всех videoElementPoolForRoom
   * т.е. если если глобально видео остановлены, а в videoElementPoolForRoom
   * запущено, то все видео будут остановлены
   *
   * Влияет на свойство isPaused у каждого элемента
   */
  playVideoSources: VideoElementPoolSettingsService['playVideoSources'];
  pauseVideoSources: VideoElementPoolSettingsService['pauseVideoSources'];
  /**
   * Метод получения видео элемента для ручного подставления в него стрима
   * и отслеживания его состояния
   */
  getVideoElementForStream: (
    settings?: VideoElementPoolGlobalSettings,
  ) => VideoElementPoolGlobalElementForStream;
};

type VideoSizeSettings = {
  width: number;
  height: number;
};
type Source = Exclude<MediaType, 'audio'>;
type RequestVideoElement = {
  /**
   * тип используемого видео
   */
  source: Source;
  /**
   * необходимо ли следить за размером видео элемента
   * по умолчанию считается, что параметр включен
   *
   * Если параметр `true`, то если элемент виден, то
   * к нему подключается resizeObserver. Как только элемент
   * перестает быть видим - происходим отписка.
   * Каждый раз, когда меняется размер отправляется запрос
   * в плагин displayEndpoints
   *
   * Если элемент имеет статичные размеры и они точно не изменятся
   * то можно выключить это свойство для экономии ресурсов
   *
   * А также если нужно управлять размером запрашиваемого видео
   * в ручном режиме через `setUsageVideoSize`
   *
   * @default true
   */
  watchResize?: boolean;
  /**
   * игнорирование глобального занижения
   * запрашиваемого качества
   *
   * @default false
   */
  ignoreMaxVideoSize?: boolean;
};
type DisplayEndpointsEvents = {
  setMaxRequestVideoSizeAllVideos: {
    settings: VideoSizeSettings;
  };
  clearMaxRequestVideoSizeAllVideos: undefined;
};
/**
 * Сервис для взаимодействия с реальными displayEndpoints
 *
 * В зону ответственности входит отслеживание видимости
 * и размеров видео элементов, а также запущены они или нет
 * и в зависимости от этих знаний дергает нужные ручки
 *
 * Элемент будет зарегистрирован в реальных displayEndpoints
 * только если он виден на странице и не стоит на паузе
 */
type DisplayEndpointsService = {
  events: TransportReadonlyNode<DisplayEndpointsEvents>;
  registerElement: (
    participantId: JazzRoomParticipantId,
    request: RequestVideoElement,
    element: HTMLVideoElement,
    isMountedInDOMPromise: Promise<void>,
  ) => void;
  unregisterElement: (element: HTMLVideoElement) => void;
  /**
   * Метод для ручной активации элемента. Используется если у элемента нет стрима,
   * напр. с подписками в Next.
   */
  activateElement: (element: HTMLVideoElement) => void;
  deactivateElement: (element: HTMLVideoElement) => void;
  /**
   * Метод отписки подписчикоков на resize и intersection
   */
  unsubscribeWatchObserver: (element: HTMLVideoElement) => void;
  /**
   * метод отправки в displayEndpoints plugin
   * обновленных размеров, при этом эти значения не сохраняются
   * и если передан watResize=true, то при обновление размера
   * видео элемента эти значения будут затерты
   */
  setUsageVideoSize: (
    element: HTMLVideoElement,
    settings?: VideoSizeSettings,
  ) => void;
  /**
   * жестко устанавливает размер видео (если он виден)
   * и отключает resizeObserver для этого элемента
   *
   * если передать undefined данная политика отключается
   */
  setUsageVideoSizeWithSaving: (
    element: HTMLVideoElement,
    settings: VideoSizeSettings | undefined,
  ) => void;
  maxVideoSizeAllVideos: Atom<VideoSizeSettings | undefined>;
  getMaxVideoSizeAllVideos: () => VideoSizeSettings | undefined;
  /**
   * Метод установки верхней планки всех запрашиваемых видео
   * Нужно для случаев, когда нужно ограничить качество всех видео на странице
   *
   * Для игнорирования этой настройки можно передать параметр `ignoreMaxVideoSize=true`
   * при регистрации видео элемента
   */
  setMaxVideoSizeAllVideos: (videoSize: VideoSizeSettings | undefined) => void;
  setWatchResize: (element: HTMLVideoElement, isWatch: boolean) => void;
  clearAllTimers: () => void;
};

type RoomVideoElementsVideoEvents = {
  /**
   * событие любого обновления стрима в комнтае
   * то есть (addTrack, removeTrack, trackMuteChange and etc.)
   *
   * потенциально есть возможность,
   * что трек будет unmute, но стрим будет пустой
   */
  trackUpdated: {
    stream: MediaStream | null | undefined;
    isMuted: boolean;
    isPaused: boolean;
  };
  /**
   * событие остановки видео элемента
   * см `videoElementPoolForRoom.setPauseAllVideo`
   * и  `videoElementPoolForRoom.playVideoSources`.
   */
  elementsPausedChanged: {
    stream: MediaStream | null | undefined;
    isMuted: boolean;
    isPaused: boolean;
  };
};

type VideoElementPoolForRoomEvents = VideoElementPoolSettingsEvents &
  DisplayEndpointsEvents;
type VideoElementPoolForRoomVideoSource = Exclude<MediaType, 'audio'>;
type VideoElementPoolForRoomVideoSizeSettings = {
  width: number;
  height: number;
};
type VideoElementPoolForRoomElementEvents = RoomVideoElementsVideoEvents;
type VideoElementPoolForRoomElement = {
  /**
   * HTMLVideoElement для встройки на страницу
   * механиками videoElementPool в него будет
   * подставляться актуальный mediaStream
   * а также он будет запускаться и останавливаться
   */
  videoElement: HTMLVideoElement;
  /**
   * замьючен ли videoSource
   */
  isMuted: Atom<boolean>;
  /**
   * стоит ли данный videoSource на паузе
   * по дефолту состояние повторяет isMuted
   *
   * но при размьюченном видео может быть false, если:
   * 1) все видео элементы стоят на паузе
   * см `setPauseAllVideo`
   * данное поведение можно игнорировать
   * с помощью настройки `ignoreAllVideoPause`
   *
   * 2) данный videoSource отключен
   * см `playVideoSources`
   * данное поведение можно игнорировать
   * с помощью настройки `ignoreSourcePause`
   */
  isPaused: Atom<boolean>;
  /**
   * mediaStream, который сейчас используется
   * для всех видео элементов
   */
  stream: Atom<MediaStream | null | undefined>;
  /**
   * размер видео стрима. Если стрима нет или видео замьючено,
   * то вычисления не происходит и значения высоты и ширины
   * равны 0
   */
  streamSize: Atom<VideoElementPoolStreamSize>;
  /**
   * videoSource. Используется в качестве атома
   * для удобства взаимодействия и сохранения
   * единого стиля данных
   */
  source: Atom<VideoElementPoolForRoomVideoSource>;
  on: TransportReadonlyNode<VideoElementPoolForRoomElementEvents>['on'];
  once: TransportReadonlyNode<VideoElementPoolForRoomElementEvents>['once'];
  off: TransportReadonlyNode<VideoElementPoolForRoomElementEvents>['off'];
  /**
   * установка размера запрашиваемого видео
   * при этом происходит остановка отслеживания размера видео элемента
   *
   * для отмены этого поведения нужно передать undefined
   * или это произойдет автоматически≤ если высота или ширина будут равны 0
   */
  setVideoSize: (
    settings: VideoElementPoolForRoomVideoSizeSettings | undefined,
  ) => void;
  /**
   * динамический запуск или остановка отслеживания размера видео элемента
   * отслеживание работает только если видео элемент виден на странице
   * и он не остановлен (см свойство `isPaused`)
   */
  setIsWatchResize: (isWatch: boolean) => void;
  /**
   * не рекомендуется к использованию
   *
   * Ручной запуск видео элемента
   *
   * Метод работает только если данный videoSource
   * не стоит на паузе и все видео не стоят на паузе
   * или если есть соответсвующие настройки игнорирования
   */
  play: () => void;
  /**
   * не рекомендуется к использованию
   *
   * ручная остановка видео элемента
   */
  pause: () => void;
  release: () => void;
};
type VideoElementPoolForRoomRequestVideoElement = {
  source: VideoElementPoolForRoomVideoSource;
  /**
   * игнорирование остановки всех видео элементов
   * см `setPauseAllVideo`
   *
   * @default false
   */
  ignoreAllVideoPause?: boolean;
  /**
   * игнорирование остановки конкретного типа видео
   * см `pauseVideoSources`
   *
   * @default false
   */
  ignoreVideoSourcePause?: boolean;
  /**
   * игнорирование верхней планки запрашиваемого видео
   * см `setMaxVideoSizeAllVideos`
   *
   * @default false
   */
  ignoreMaxVideoSize?: boolean;
  /**
   * нужно ли отслеживать изменение размера видео элемента
   * Отслеживание работает только если видео элемент виден
   * и не стоит на паузе
   *
   * @default true
   */
  watchResize?: boolean;
  /**
   * HTMLVideoElement props, которые можно установить сразу
   * при инициализации видео элемента
   */
  videoElementProps?: Partial<HTMLVideoElement>;
  /**
   * Нужно ли дожидаться, когда видео будет смонтировано в DOM
   * @default true
   */
  waitForMountingInDOM?: boolean;
};
/**
 * Сервис для работы с видео элементами в комнате
 */
type VideoElementPoolForRoomService = {
  on: SubscribeReadonlyNode<VideoElementPoolForRoomEvents>['on'];
  once: SubscribeReadonlyNode<VideoElementPoolForRoomEvents>['once'];
  off: SubscribeReadonlyNode<VideoElementPoolForRoomEvents>['off'];
  isPausedAllVideo: VideoElementPoolSettingsService['isPausedAllVideo'];
  getIsPausedAllVideo: VideoElementPoolSettingsService['getIsPausedAllVideo'];
  /**
   * Остановка или запуск всех видео элементов в комнате.
   * Все видео элементы ставятся на паузу и из них выгружаются стримы
   * При обратном запуске работа видео элементов нормализуется
   *
   * Не касается элементов с настройкой `ignoreAllVideoPause`
   * Имеет более низкий приоритет над настройкой для всего videoElementPool
   * т.е. если если глобально видео остановлены, а в videoElementPoolForRoom
   * запущено, то все видео будут остановлены
   *
   * Влияет на свойство isPaused у каждого элемента
   *
   * Если стрим у видео элемента остановлен (isMute), то запуск его не коснется
   */
  setPauseAllVideo: VideoElementPoolSettingsService['setPauseAllVideo'];
  pausedVideoSources: VideoElementPoolSettingsService['pausedVideoSources'];
  getPausedVideoSources: VideoElementPoolSettingsService['getPausedVideoSources'];
  /**
   * Остановка или запуск всех видео данного videoSource в комнате.
   * Все видео элементы данного типа ставятся на паузу и из них выгружаются стримы
   * При обратном запуске работа видео элементов нормализуется
   *
   * Не касается элементов с настройкой `ignoreVideoSourcePause`
   * Имеет более низкий приоритет над настройкой для всего videoElementPool
   * т.е. если если глобально видео остановлены, а в videoElementPoolForRoom
   * запущено, то все видео будут остановлены
   *
   * Влияет на свойство isPaused у каждого элемента
   *
   * Если стрим у видео элемента остановлен (isMute), то запуск его не коснется
   */
  pauseVideoSources: VideoElementPoolSettingsService['pauseVideoSources'];
  playVideoSources: VideoElementPoolSettingsService['playVideoSources'];
  maxVideoSizeAllVideos: DisplayEndpointsService['maxVideoSizeAllVideos'];
  getMaxVideoSizeAllVideos: DisplayEndpointsService['getMaxVideoSizeAllVideos'];
  /**
   * Метод установки верхней планки всех запрашиваемых видео
   * Нужно для случаев, когда нужно ограничить качество всех видео в комнате
   *
   * Для игнорирования этой настройки можно передать параметр `ignoreMaxVideoSize`
   * при получение видео элемента
   */
  setMaxVideoSizeAllVideos: DisplayEndpointsService['setMaxVideoSizeAllVideos'];
  /**
   * метод получения видео элемента в videoElementPool
   * со всеми методами взаимодействия и отслеживания его состояния
   *
   * Для конфигурации поведения используйте настройки плагина
   */
  getVideoElement: (
    participantId: JazzRoomParticipantId,
    request: VideoElementPoolForRoomRequestVideoElement,
  ) => VideoElementPoolForRoomElement;
  /**
   * release all elements
   * not recommended
   */
  release: () => void;
};

type VideoElementPoolFlags = ConfigFlags<{
  /**
   * If the video in the conference could not start immediately,
   * we try to lower and raise it back.
   * This helps in cases where packet loss has occurred.
   *
   * @default true
   */
  videoLossEnabled: boolean;
  /**
   * @default 5_000 ms
   */
  videoLossTimeout: number;
  /**
   * Allow the function of stopping all video elements
   * created outside the conference for a separate video stream.
   *
   * @default false
   */
  allVideoPauseForStreamElementsEnabled: boolean;
  /**
   * When stopping all videos in videoElementPool, do not disable local video elements.
   *
   * @default true
   */
  ignoreLocalVideoForPauseAllVideoEnabled: boolean;
  /**
   * When stopping a video type in videoElementPool, do not disable the local video element.
   *
   * @default true
   */
  ignoreLocalVideoForPauseVideoSourceEnabled: boolean;
  /**
   * Allow the video of an element created using getVideoElementForStream
   * to stop if the element is out of sight
   *
   * @default true
   */
  stopElementsForStreamIfNotInteractionEnabled: boolean;
  /**
   * Delayed release of the element.
   * It is necessary that when re-rendering the application,
   * the deletion and creation of an element with all registrations does not occur.
   * If the item is "released" and requested back faster than the release occurs,
   * then it will be reused
   *
   * @default 2_000 ms
   */
  delayReleaseVideoElement: number;
  /**
   * If the video element is not visible, then an unsubscription from displayEndpoints
   * will be delayed to avoid "flashing video" during re-renders and other cases
   * when the element disappears and appears for a short time.
   *
   * If rendering occurs, then the minimum time value between
   * the flag delayReleaseVideoElement and delayStopRequestVideoIfNotInteraction is taken.
   *
   * In order for the unsubscription to occur instantly, you need to specify the value 0.
   *
   * @default 10_000 ms
   */
  delayStopRequestVideoIfNotInteraction: number;
  /**
   * If you need to track the size of the video stream.
   * The `getVideoElement` method returns the streamSize property,
   * in which once every N seconds (regulated by the `streamSizeCheckInterval` flag)
   * the value of the video stream size will be calculated and placed.
   *
   * @default false
   */
  streamSizeCheckEnabled: boolean;
  /**
   * The interval for checking the size of the video stream.
   * If you specify 0, it will be equivalent to passing the `streamSizeCheckEnabled` parameter false.
   *
   * @default 1_000 ms
   */
  streamSizeCheckInterval: number;
  /**
   * Enabling the feature of limiting the number of requested videos in high quality.
   * If a video in full HD is requested for more than the `maxQualitySubscribersLimit` flag,
   * its quality is lowered to 0 size (unsunscribe).
   * When the queue is released, their quality is restored to the originally requested one.
   *
   * @default true
   */
  maxQualitySubscribersLimitEnabled: boolean;
  /**
   * The maximum number of videos requested in full HD quality.
   * It works when the maxQualitySubscribersLimitEnabled flag is enabled.
   * If more videos are requested, their quality is lowered to 0 size (unsunscribe).
   *
   * @default 16
   */
  maxQualitySubscribersLimit: number;
  /**
   * the mode of operation of the plugin
   *
   * if you select `scoped`, then each room will have its own isolated version
   *
   * if `singleton` is selected, only 1 instance will be created,
   * which will be used for all rooms and all video elements will fall into it.
   * It may be useful if the plugin is used in child windows of the electron application.
   *
   * It is `recommended` to use the `scoped` mode
   * @default scoped
   */
  mode: 'scoped' | 'singleton';
}>;

type VideoElementPoolSettings = VideoElementPoolFlags;
type VideoElementPoolPluginOptions = Partial<VideoElementPoolSettings>;

/**
 * пару ручек для приведения новых форматов данных
 * в старые интерфейсы
 */
declare const atomToRxEffectsQuery: typeof toQuery;
declare const signalToRxEffectsAction: typeof toAction;
declare const atomToRxObserve: typeof observe;
declare const signalToRxObserve: typeof observe;
type Unsubscribe = () => void;
declare function subscribeAtom<T>(
  source: Atom<T>,
  callback: (payload: T) => void,
): Unsubscribe;

declare function getAppVideoElementPool(
  sdk: JazzSdk,
): VideoElementPoolGlobalService;

declare function getVideoElementPoolForRoom<R extends JazzRoomExtender[]>(
  room: JazzRoom<R>,
): VideoElementPoolForRoomService;

/**
 * It allows you to integrate videos into the page through the video element,
 * as well as track the status of tracks.
 */
declare function videoElementPoolPlugin(
  options?: VideoElementPoolPluginOptions,
): JazzSdkPlugin;

declare function moderatorsPlugin(): JazzSdkPlugin;

type ModeratorsCreateRoomParams = Readonly<{
  moderatorEmails: string[];
}>;
declare const withModeratorsCreateRoom: (
  params: ModeratorsCreateRoomParams,
) => Extender;

type SubscriberEvent<T extends EventLike> = {
  on: TransportRoot<T>['on'];
  once: TransportRoot<T>['once'];
  off: TransportRoot<T>['off'];
};

type ModeratorsRoomEvents = {
  assignModeratorRolePending: void;
  assignModeratorRoleSuccess: void;
  assignModeratorRoleFailure: {
    code: 'FORBIDDEN';
    message: string | undefined;
  };
  revokeModeratorRolePending: void;
  revokeModeratorRoleSuccess: void;
  revokeModeratorRoleFailure: {
    code: 'FORBIDDEN';
    message: string | undefined;
  };
  participantLosesModeratorRole: void;
  participantGainsModeratorRole: void;
};
type ModeratorsRoomManagementType = {
  assignModeratorRoleStatus: Atom<'idle' | 'pending'>;
  revokeModeratorRoleStatus: Atom<'idle' | 'pending'>;
  assignModeratorRole: (participantId: string) => Promise<void>;
  revokeModeratorRole: (participantId: string) => Promise<void>;
};
type ModeratorsPluginRoomServiceType = ModeratorsRoomManagementType &
  SubscriberEvent<ModeratorsRoomEvents>;

declare function getModeratorsPluginRoomService<R extends JazzRoomExtender[]>(
  room: JazzRoom<R>,
): ModeratorsPluginRoomServiceType;

type ModeratorsJazzRoom = MakeExtender<
  JazzRoomExtender,
  {
    permissions: {
      canViewModerators?: boolean;
      canUpdateRoleMemberToModerator?: boolean;
      canUpdateRoleModeratorToMember?: boolean;
    };
  }
>;
type ModeratorsJazzClient = MakeExtender<
  JazzClientExtender,
  {
    userInfo: {
      features: {
        moderatorsAvailable?: boolean;
      };
    };
  }
>;

type LogsPluginOptions = Readonly<{
  /**
   * @default 'info'
   */
  logLevel?: LogLevel;
  /**
   * @default true
   */
  isEnableStdout?: boolean;
  subscribe?: (event: LogEvent) => void;
}>;
/**
 * Позволяет подписаться на события логов в JazzSDK
 */
declare function logsPlugin(options?: LogsPluginOptions): JazzSdkPlugin;

interface WatermarkRoomPermissionUpdateEvent {
  type: 'permissionUpdated';
  payload: {
    canEdit: boolean;
  };
}
interface WatermarkRoomEnableUpdateEvent {
  type: 'enableUpdated';
  payload:
    | {
        isEnabled: true;
        text: string;
      }
    | {
        isEnabled: false;
      };
}
type WatermarkRoomEvent =
  | WatermarkRoomEnableUpdateEvent
  | WatermarkRoomPermissionUpdateEvent;
type WatermarkRoomService = {
  text: Atom<string | undefined>;
  canEdit: Atom<boolean>;
  isEnabled: Atom<boolean>;
  setEnabled: (isEnabled: boolean) => Promise<void>;
  event$: Observable<WatermarkRoomEvent>;
};

declare function getWatermarkService<R extends JazzRoomExtender[]>(
  room: JazzRoom<R>,
): WatermarkRoomService;

type WatermarkCreateRoomParams = {
  watermarkEnabled: boolean;
};
declare const withWatermarksCreateRoom: (
  params: WatermarkCreateRoomParams,
) => Extender;

declare function watermarksPlugin(): JazzSdkPlugin;

type WatermarksJazzRoom = MakeExtender<
  JazzRoomExtender,
  {
    permissions: {
      canManageWatermark?: boolean;
    };
  }
>;
type WatermarksJazzClient = MakeExtender<
  JazzClientExtender,
  {
    userInfo: {
      features: {
        watermarkAvailable?: boolean;
      };
    };
  }
>;

declare function pollsPlugin(): JazzSdkPlugin;

type AsyncData<P, R = void, E = void> = {
  Payload: P;
  Response: R;
  Error: E;
};
type AsyncMethod<T extends AsyncData<unknown, unknown, unknown>> = (
  payload: T['Payload'],
) => Promise<ResultSuccess<T['Response']> | ResultFailure<T['Error']>>;

type PollStatus = 'NONE' | 'STARTED' | 'STOPPED' | 'PUBLISHED' | 'FINISHED';
type PollRole = 'NONE' | 'OWNER' | 'USER';
type PollQuestionType = 'SINGLE_CHOICE' | 'MULTIPLE_CHOICE';
type PollQuestion = {
  text: string;
  type: PollQuestionType;
  options: Array<{
    text: string;
    numberOfVote: number;
  }>;
};

type PollStateData = {
  pollId: string;
  status: 'STARTED' | 'STOPPED' | 'PUBLISHED' | 'FINISHED';
  title: string;
  numberOfVote: number;
  questions: PollQuestion[];
};
type PollStartedData = {
  pollId: string;
  title: string;
  questions: PollQuestion[];
};
type PollStoppedData = {
  pollId: string;
};
type PollPublishedData = {
  pollId: string;
  title: string;
  numberOfVote: number;
  questions: PollQuestion[];
};
type PollFinishedData = {
  pollId: string;
};

type PollModelType = {
  state: {
    id: Atom<string>;
    status: Atom<PollStatus>;
    role: Atom<PollRole>;
    title: Atom<string>;
    numberOfVote: Atom<number>;
    questions: Atom<PollQuestion[]>;
    isOwner: Atom<boolean>;
    isUser: Atom<boolean>;
    isStarted: Atom<boolean>;
  };
  actions: {
    validateId: (
      pollId: string,
    ) => 'no-poll-started' | 'pollId-is-not-equal' | 'is-valid';
    applyStateData: (data: PollStateData) => void;
    applyStartedData: (data: PollStartedData) => void;
    applyStoppedData: (data: PollStoppedData) => void;
    applyPublishedData: (data: PollPublishedData) => void;
    applyFinishedData: (data: PollFinishedData) => void;
    clear: () => void;
    startPoll: () => void;
    stopPoll: () => void;
    publishPoll: () => void;
    finishPoll: () => void;
  };
};

type RequestFailedError = {
  type: 'request-failed';
};
type PollIdIsNotEqualError = {
  type: 'pollId-is-not-equal';
};
type NoPollStartedError = {
  type: 'no-poll-started';
};
type PollAlreadyStartedError = {
  type: 'poll-already-started';
};
type YouIsNotPollOwnerError = {
  type: 'you-is-not-poll-owner';
};
type StartPollData = AsyncData<
  {
    pollTemplateId: string;
  },
  void,
  RequestFailedError | PollAlreadyStartedError
>;
type StopPollData = AsyncData<
  void,
  void,
  | RequestFailedError
  | NoPollStartedError
  | PollIdIsNotEqualError
  | YouIsNotPollOwnerError
>;
type PublishPollData = AsyncData<
  void,
  void,
  | RequestFailedError
  | NoPollStartedError
  | PollIdIsNotEqualError
  | YouIsNotPollOwnerError
>;
type FinishPollData = AsyncData<
  void,
  void,
  RequestFailedError | NoPollStartedError | PollIdIsNotEqualError
>;
type SendPollAnswerData = AsyncData<
  {
    questions: Array<{
      options: number[];
    }>;
  },
  void,
  RequestFailedError | NoPollStartedError | PollIdIsNotEqualError
>;
type CheckPollAnswerData = AsyncData<
  void,
  {
    answered: boolean;
  },
  RequestFailedError | NoPollStartedError | PollIdIsNotEqualError
>;
type PollsPluginRoomEvents = {
  ownerPollState: {
    pollId: string;
    role: 'OWNER';
    status: PollStatus;
    title: string;
    numberOfVote: number;
    questions: PollQuestion[];
  };
  userPollStarted: {
    pollId: string;
    role: 'USER';
    status: 'STARTED';
    title: string;
    questions: PollQuestion[];
  };
  userPollFinished: {
    pollId: string;
    role: 'USER';
    status: 'FINISHED';
  };
  userPollPublished: {
    pollId: string;
    role: 'USER';
    status: 'PUBLISHED';
    title: string;
    numberOfVote: number;
    questions: PollQuestion[];
  };
  userPollStopped: {
    pollId: string;
    role: 'USER';
    status: 'STOPPED';
  };
  startPollPending: void;
  startPollFailure: StartPollData['Error'];
  startPollSuccess: StartPollData['Response'];
  stopPollPending: void;
  stopPollFailure: StopPollData['Error'];
  stopPollSuccess: StopPollData['Response'];
  publishPollPending: void;
  publishPollFailure: PublishPollData['Error'];
  publishPollSuccess: PublishPollData['Response'];
  finishPollPending: void;
  finishPollFailure: FinishPollData['Error'];
  finishPollSuccess: FinishPollData['Response'];
  sendPollAnswerPending: void;
  sendPollAnswerFailure: SendPollAnswerData['Error'];
  sendPollAnswerSuccess: SendPollAnswerData['Response'];
  checkPollAnswerPending: void;
  checkPollAnswerFailure: CheckPollAnswerData['Error'];
  checkPollAnswerSuccess: CheckPollAnswerData['Response'];
};

type PollsPluginRoomServiceType = {
  pollState: PollModelType['state'];
  startPoll: AsyncMethod<StartPollData>;
  stopPoll: AsyncMethod<StopPollData>;
  publishPoll: AsyncMethod<PublishPollData>;
  finishPoll: AsyncMethod<FinishPollData>;
  sendPollAnswer: AsyncMethod<SendPollAnswerData>;
  checkPollAnswer: AsyncMethod<CheckPollAnswerData>;
} & SubscriberEvent<PollsPluginRoomEvents>;

declare function getPollsPluginRoomService<R extends JazzRoomExtender[]>(
  room: JazzRoom<R>,
): PollsPluginRoomServiceType;

type PollTemplate = {
  title: string;
  questions: Array<{
    question: string;
    type: 'SINGLE_CHOICE' | 'MULTIPLE_CHOICE';
    options: string[];
  }>;
};
type PollTemplateListItem = {
  id: string;
  title: string;
  questionsCount: number;
  lastUsedTimestamp: string | null;
};
type PollsPluginClientServiceType = {
  getPollTemplateList: () => Promise<{
    templates: PollTemplateListItem[];
  }>;
  getPollTemplateById: (pollTemplateId: string) => Promise<PollTemplate>;
  createPollTemplate: (data: PollTemplate) => Promise<{
    id: string;
  }>;
  updatePollTemplate: (
    pollTemplateId: string,
    data: PollTemplate,
  ) => Promise<void>;
  removePollTemplate: (pollTemplateId: string) => Promise<void>;
};

declare function getPollsPluginClientService<
  R extends JazzRoomExtender[],
  C extends JazzClientExtender[],
>(jazzRoom: JazzRoom<R> | JazzClient<C>): PollsPluginClientServiceType;

type PollsJazzRoom = MakeExtender<
  JazzRoomExtender,
  {
    permissions: {
      canStartPoll?: boolean;
      canManagePoll?: boolean;
    };
  }
>;
type PollsJazzClient = MakeExtender<
  JazzClientExtender,
  {
    userInfo: {
      features: {
        pollAvailable?: boolean;
      };
    };
  }
>;

/**
 * Copy of types from "lib-jitsi-meet"
 */
type JitsiLocalTrackEffect = {
  isEnabled: (track: JitsiLocalTrack) => boolean;
  isMuted?: () => boolean;
  setMuted?: (muted: boolean) => void;
  startEffect: (stream: MediaStream) => MediaStream | null;
  stopEffect: () => void;
  key?: string;
  order?: 'start' | 'end';
};
type JitsiLocalTrack = {
  isAudioTrack: () => boolean;
};

type MfxProviderName = string;
type MfxProvider = {
  initialize: (options: { eventBus: EventBus<MfxEvents> }) => void;
  isAvailable: () => boolean;
  createEffect: () => Promise<MfxProcessor>;
  providerName: MfxProviderName;
};
type MfxEvents =
  | {
      type: 'providerLoading';
      value: boolean;
    }
  | {
      type: 'lowPerformance';
      cause: string;
    };
interface MfxProcessor extends JitsiLocalTrackEffect {
  onStartEffect: Signal<{
    input: MediaStream;
    output: MediaStream;
  }>;
  onStopEffect: Signal<void>;
}

type MfxAudioProvider = MfxProvider & {
  setEffects: (effects: MfxAudioEffects) => void | boolean;
  supportedEffects: MfxAudioSupportedEffects;
};
type MfxAudioEffects = {
  normalization?: boolean;
  denoiser?: 'normal' | 'advanced';
};
type MfxAudioSupportedEffects = {
  denoiser: boolean;
  advancedDenoiser: boolean;
  normalization: boolean;
};
type MfxAudioEffectService = {
  setMode: (mode: MfxAudioPluginModes) => boolean;
  setMediaStream: (stream: MediaStream | undefined) => void;
  state: Atom<MfxAudioProviderState>;
  event$: Observable<MfxEvents>;
};
type MfxAudioPluginService = {
  service: MfxAudioEffectService;
};
type MfxAudioPluginModes =
  | 'normalization'
  | 'denoiser'
  | 'advancedDenoiser'
  | undefined;
type MfxAudioProviderState =
  | {
      status: 'empty';
    }
  | {
      status: 'ready';
      current: MfxProvider;
    }
  | {
      status: 'loading';
      current?: MfxProvider;
      willSupportEffects: MfxAudioSupportedEffects;
    }
  | {
      status: 'error';
      error: string;
    };

declare function mfxAudioPlugin(options: MfxAudioPluginOptions): JazzSdkPlugin;
type MfxAudioPluginOptions = {
  provider: ModuleDeclaration<MfxAudioProvider>;
  mode?: MfxAudioPluginModes;
  autoSubscribeTrack?: boolean;
  lowerModeOnLowPerformance?: boolean;
};

declare function getMfxAudioEffectService(sdk: JazzSdk): MfxAudioEffectService;

declare function summarizationPlugin(): JazzSdkPlugin;

type ElebusEventTriplet<T extends EventLike> = {
  on: TransportRoot<T>['on'];
  once: TransportRoot<T>['once'];
  off: TransportRoot<T>['off'];
};
type SummarizationJazzClient = MakeExtender<
  JazzClientExtender,
  {
    userInfo: {
      features: {
        summarizationAvailable?: boolean;
      };
    };
  }
>;

type CommonFindSummarizationResponse = {
  id: string;
  createdAt: string;
  positive: boolean;
  canRecalculate: boolean;
};
type SummarizationContent = {
  topics: Array<{
    summary: string;
    duration?: number;
  }>;
  blocks: Array<{
    summary: string;
  }>;
  actionPoints: Array<{
    assignee: string | undefined | null;
    text: string;
  }>;
};
type FindSummarizationResponse = (
  | {
      status: 'FINISH';
      card: SummarizationContent;
    }
  | {
      status: 'PENDING' | 'PROCESSING' | 'ERROR';
    }
) &
  CommonFindSummarizationResponse;
type FindSummarization =
  | FindSummarizationResponse
  | {
      status: 'EMPTY';
    };
type SummarizationError =
  | {
      status: number;
    }
  | Error;
type RecalculateSummarizationRequest = {
  summarizationId: string;
};
type RecalculateSummarizationResponse = SyncResult<
  FindSummarization,
  HttpClientFetchError | SummarizationError
>;
type RateSummarizationRequest = {
  summarizationId: string;
  positive: boolean;
};
type RateSummarizationResponse = SyncResult<boolean, HttpClientFetchError>;
type SummarizationClientServiceAvailableEventPayload = {
  isAvailable: boolean;
};
type SummarizationClientServiceAvailableEvent = {
  summarizationAvailableUpdated: SummarizationClientServiceAvailableEventPayload;
};
type SummarizationClientService = Readonly<
  {
    recalculate: (
      request: RecalculateSummarizationRequest,
    ) => Promise<RecalculateSummarizationResponse>;
    rate: (
      request: RateSummarizationRequest,
    ) => Promise<RateSummarizationResponse>;
    isSummarizationAvailable: Atom<boolean>;
    destroy: () => void;
  } & ElebusEventTriplet<SummarizationClientServiceAvailableEvent>
>;

type SummarizationRoomEnableUpdateEventPayload = {
  isEnabled: boolean;
};
type SummarizationRoomEnableUpdateEvent = {
  summarizationEnableUpdated: SummarizationRoomEnableUpdateEventPayload;
};
type SummarizationRoomService = {
  isEnabled: Atom<boolean>;
} & ElebusEventTriplet<SummarizationRoomEnableUpdateEvent>;

declare const withSummarizationCreateRoom: (params: {
  enabled: boolean;
}) => Extender;
type SummarizationGetMeetingHistoryDetailsResponseExt = Readonly<{
  summarizationEnabled: boolean;
  summarization?: FindSummarizationResponse;
}>;
type SummarizationGetMeetingHistoryDetailsResponse = DeepUnion<
  MeetingHistory,
  SummarizationGetMeetingHistoryDetailsResponseExt
>;
declare const withSummarizationGetMeetingHistoryDetails: () => Extender<SummarizationGetMeetingHistoryDetailsResponse>;

declare function getSummarizationRoomService<R extends JazzRoomExtender[]>(
  room: JazzRoom<R>,
): SummarizationRoomService;

type GetTranscriptionPayload = {
  pageKey: number | null;
  limit: number;
};
type Message = {
  id: string;
  participantId: string;
  participantName: string;
  createdAt: number;
  text: string;
};
type TranscriptionForbiddenError = {
  code: 'FORBIDDEN';
  message: string;
};
type TranscriptionAlreadyStartedError = {
  code: 'TRANSCRIPTION_ALREADY_STARTED';
  message: string;
};
type TranscriptionNotStartedError = {
  code: 'TRANSCRIPTION_NOT_STARTED';
  message: string;
};
type StartTranscriptionError =
  | TranscriptionAlreadyStartedError
  | TranscriptionForbiddenError;
type StopTranscriptionError =
  | TranscriptionNotStartedError
  | TranscriptionForbiddenError;
type GetTranscriptionError = TranscriptionForbiddenError;

type TranscriptionEvents = {
  transcriptionMessage: {
    message: Message;
  };
  transcriptionMessages: {
    messages: Message[];
    pageKey: string | undefined;
  };
  startTranscriptionPending: void;
  startTranscriptionSuccess: void;
  startTranscriptionFailure: {
    code: 'FORBIDDEN' | 'TRANSCRIPTION_ALREADY_STARTED';
    message: string | undefined;
  };
  stopTranscriptionPending: void;
  stopTranscriptionSuccess: void;
  stopTranscriptionFailure: {
    code: 'FORBIDDEN' | 'TRANSCRIPTION_NOT_STARTED';
    message: string | undefined;
  };
  getTranscriptionPending: void;
  getTranscriptionSuccess: {
    pageKey: string | undefined;
  };
  getTranscriptionFailure: {
    code: 'FORBIDDEN';
    message: string | undefined;
  };
  permissionsUpdate: {
    canManageAsr: boolean | undefined;
    canViewAsr: boolean | undefined;
  };
  isActiveAsr: boolean;
};
type TranscriptionMeetingActions = {
  startTranscription: () => Promise<void>;
  stopTranscription: () => Promise<void>;
  getTranscription: (payload: GetTranscriptionPayload) => Promise<void>;
};
type TranscriptionMeetingState = {
  startTranscriptionStatus: Atom<'idle' | 'pending'>;
  stopTranscriptionStatus: Atom<'idle' | 'pending'>;
  getTranscriptionStatus: Atom<'idle' | 'pending'>;
  isActiveAsr: Atom<boolean>;
  canViewAsr: Atom<boolean>;
  canManageAsr: Atom<boolean>;
};
type TranscriptionPluginServiceType = TranscriptionMeetingActions &
  TranscriptionMeetingState &
  SubscriberEvent<TranscriptionEvents>;
type TranscriptionPluginServiceExternalType = TranscriptionMeetingActions &
  SubscriberEvent<TranscriptionEvents> &
  Omit<
    TranscriptionMeetingState,
    | 'startTranscriptionStatus'
    | 'stopTranscriptionStatus'
    | 'getTranscriptionStatus'
  >;

type TranscriptionErrorsMap = {
  start: StartTranscriptionError['code'];
  stop: StopTranscriptionError['code'];
  get: GetTranscriptionError['code'];
};
declare class TranscriptionError<
  T extends keyof TranscriptionErrorsMap,
> extends Error {
  readonly error?: string;
  readonly code: TranscriptionErrorsMap[T];
  constructor(code: TranscriptionErrorsMap[T], message?: string);
}
declare class TranscriptionStartError extends TranscriptionError<'start'> {
  constructor(code: StartTranscriptionError['code'], message?: string);
}
declare class TranscriptionStopError extends TranscriptionError<'stop'> {
  constructor(code: StopTranscriptionError['code'], message?: string);
}
declare class TranscriptionGetError extends TranscriptionError<'get'> {
  constructor(code: GetTranscriptionError['code'], message?: string);
}

declare function transcriptionPlugin(): JazzSdkPlugin;

declare function getTranscriptionRoomService<R extends JazzRoomExtender[]>(
  room: JazzRoom<R>,
): TranscriptionPluginServiceExternalType;

type TranscriptionCreateRoomParams = {
  transcriptionAutoStartEnabled: boolean;
};
declare const withTranscriptionCreateRoom: (
  params: TranscriptionCreateRoomParams,
) => Extender;

type TranscriptionJazzRoom = MakeExtender<
  JazzRoomExtender,
  {
    permissions: {
      canManageAsr?: boolean;
      canViewAsr?: boolean;
      canStartTranscription?: boolean;
      canViewTranscription?: boolean;
    };
    permissionRequests: {
      canViewTranscription?: boolean;
    };
  }
>;
type TranscriptionJazzClient = MakeExtender<
  JazzClientExtender,
  {
    userInfo: {
      features: {
        asrAvailable?: boolean;
        transcriptionAvailable?: boolean;
      };
    };
  }
>;

type MessageBody = {
  id: string;
  timestamp: Timestamp;
  text: string;
};
type ChatMessageStatus =
  | 'MESSAGE_DELIVERED'
  | 'MESSAGE_IN_PROGRESS'
  | 'MESSAGE_NOT_DELIVERED'
  | 'MESSAGE_TIMEOUT';
type MessageStatusEvent = {
  type: 'messageStatus';
  payload: {
    id: string;
    status: ChatMessageStatus;
  };
};
type ChatMessage = MessageBody & {
  participantId: JazzRoomParticipantId;
  participantName?: string;
};
type ChatRolePermissionsUpdateEvent = {
  type: 'rolePermissionsUpdated';
  payload: Partial<Record<JazzRoomParticipantRole, Partial<ChatPermissions>>>;
};
type ChatRolePermissionsEvent = {
  type: 'rolePermissions';
  payload: Partial<Record<JazzRoomParticipantRole, ChatPermissions>>;
};
type ChatPermissionsEvent = {
  type: 'permissions';
  payload: ChatPermissions;
};
type ChatPermissionsUpdateEvent = {
  type: 'permissionsUpdated';
  payload: Partial<ChatPermissions>;
};
type MessageReceivedChatEvent = {
  type: 'messageReceived';
  payload: ChatMessage;
};
type MessageSentChatEvent = {
  type: 'messageSent';
  payload: ChatMessage;
};
type ChatEvents =
  | MessageStatusEvent
  | MessageReceivedChatEvent
  | MessageSentChatEvent
  | ChatPermissionsEvent
  | ChatPermissionsUpdateEvent
  | ChatRolePermissionsEvent
  | ChatRolePermissionsUpdateEvent;
type ChatPermissions = {
  canSendMessage: boolean;
};
type ChatRoomPermissions = {
  canSendMessage: Atom<boolean>;
};
type ChatRolePermissions = Partial<
  Record<JazzRoomParticipantRole, ChatPermissions>
>;
type ChatRoomService = {
  event$: Observable<ChatEvents>;
  /**
   * permissions of local participant
   */
  permissions: ChatRoomPermissions;
  /**
   * permissions by role
   */
  rolePermissions: Atom<ChatRolePermissions | undefined>;
  changePermission: (
    permissions: Partial<ChatPermissions>,
    role?: JazzRoomParticipantRole,
  ) => Promise<void>;
  sendMessage: (text: string, id?: string) => Promise<void>;
  getMessages: () => Promise<ChatMessage[]>;
};

declare function getChatService<C extends JazzRoomExtender[]>(
  room: JazzRoom<C>,
): ChatRoomService;

declare function chatPlugin(): JazzSdkPlugin;

type ChatJazzRoom = MakeExtender<
  JazzRoomExtender,
  {
    permissions: {
      canSendMessage?: boolean;
    };
    permissionRequests: {
      canSendMessage?: boolean;
    };
  }
>;
type ChatJazzClient = MakeExtender<
  JazzClientExtender,
  {
    userInfo: {
      features: {
        chatAvailable?: boolean;
      };
    };
  }
>;

type CollectMetricsFlags = ConfigFlags<{
  /**
   * На какой эндпоинт нужно отправлять статистику
   *
   * @default '/logs/client-metrics/'
   */
  endpoint: string;
  /**
   * Раз в какой время нужно отправлять статистику
   * Если значение передано 0, то отправка будет
   * происходить по достижению лимита (см настройку bufferSize)
   *
   * @default 5_000 ms
   */
  sendInterval: number;
  /**
   * Размер буфера. Когда число событий его превысит - они
   * автоматически будут отправлены
   *
   * @default 100_000
   */
  bufferSize: number;
  /**
   * Нужно ли преобразовать массив метрик в строку,
   * где каждая метрика идет с новой строки.
   * Если настройка false - массив событий будет
   * просто преобразован через JSON.stringify
   *
   * @default false
   */
  serializedMetrics: boolean;
}>;

type CollectMetricsPluginSubscribeHandler = CollectMetricsHandler;
type CollectMetricsPluginMetric = CollectMetricsMetric;
type CollectMetricsPluginMetricMeta = CollectMetricsMeta;
type CollectMetricsPluginMetricData = CollectMetricsMetricData;
type CollectMetricsPluginOptions = {
  /**
   * backend на который будут отправляться данные.
   * Если адрес не указан, то он будет браться из JazzClient
   */
  backend?: string;
  /**
   * Нужно ли отправлять метрики на бэкенд
   *
   * @default false
   */
  isSendMetrics?: boolean;
  subscribe?: CollectMetricsPluginSubscribeHandler;
} & Partial<CollectMetricsFlags>;

declare function getCollectMetricsCustomMetaService<
  R extends JazzRoomExtender[],
  C extends JazzClientExtender[],
>(sdk: JazzSdk | JazzClient<C> | JazzRoom<R>): CollectMetricsMetaActions;

/**
 * Plugin for send metrics
 */
declare function collectMetricsPlugin(
  options?: CollectMetricsPluginOptions,
): JazzSdkPlugin;

declare function webinarsPlugin(): JazzSdkPlugin;

type WebinarPermissions = {
  canManageWebinarStream: boolean;
  canChangeServerVideoRecordLayout: boolean;
  canChangeWebinarStreamLayout: boolean;
  canStopMeeting: boolean;
};
type ConnectorLayout = 'SINGLE' | 'LIST' | 'GRID';
type WebinarsPluginRoomApiEvents = {
  joinResponse: {
    title: string;
    participant: {
      permissions: WebinarPermissions;
    };
    webinar: {
      scheduledAt: number;
      viewerPassword: string;
      viewerUrl: string;
      description: string;
    };
  };
  webinarStreamStopped: unknown;
  webinarStreamStopResponse: unknown;
  webinarStreamStartResponse: unknown;
  webinarViewerCount: {
    count: number;
  };
  webinarStreamStarted: {
    startedAt: number;
    url: string;
  };
  webinarStreamError: unknown;
  webinarStreamNotStarted: unknown;
  permissionsUpdated: WebinarPermissions;
  videoRecordLayout: {
    layout: ConnectorLayout;
  };
  videoRecordLayoutResponse: {
    layout: ConnectorLayout;
  };
  webinarStreamLayout: {
    layout: ConnectorLayout;
  };
  webinarStreamLayoutResponse: {
    layout: ConnectorLayout;
  };
  webinarStreamAlreadyStarted: unknown;
  webinarRoomViewersCapacityExceeded: {
    maxCapacity: number;
  };
};
type WebinarsPluginRoomApiControllerType = {
  onStart: () => Promise<SyncResult<void>>;
  onStop: () => Promise<SyncResult<void>>;
  onChangeLayout: (layout: ConnectorLayout) => Promise<SyncResult<void>>;
} & SubscriberEvent<WebinarsPluginRoomApiEvents>;
type WebinarsPluginRoomServiceType = WebinarsPluginRoomApiControllerType;

declare function getWebinarsPluginRoomService<R extends JazzRoomExtender[]>(
  room: JazzRoom<R>,
): WebinarsPluginRoomServiceType;

type Retranslation = Readonly<{
  /**
   * @description url ретрансляции
   */
  url: string;
  /**
   * @description ключ ретрансляции
   */
  key: string;
}>;
type CreateRoomWebinarRequest = Readonly<{
  title: string;
  description?: string;
  guestEnabled?: boolean;
  lobbyEnabled?: boolean;
  scheduledAt?: number;
  retranslations?: Array<Retranslation>;
}>;
type CreateRoomWebinarResponse = {
  roomId: string;
  password: string;
  domain: string;
  url: string;
  isRussianCACertificateRequired: boolean;
  viewerUrl: string;
  viewerPassword: string;
};
type WebinarsPluginClientService = {
  createRoom: <Ext extends Extender[]>(
    request: CreateRoomWebinarRequest,
    ...extenders: Ext
  ) => Promise<ResponseExtender<CreateRoomWebinarResponse, Ext>>;
};

declare const WEBINARS_PLUGIN_CLIENT_MODULE: ditox.ModuleDeclaration<WebinarsPluginClientService>;

type WebinarsJazzRoom = MakeExtender<
  JazzRoomExtender,
  {
    permissions: {
      canManageWebinarStream?: boolean;
    };
    error: {
      type: 'ROOM_VIEWERS_CAPACITY_EXCEEDED';
      message: string;
      params: {
        maxViewerParticipantCount: number;
      };
    };
  }
>;
type WebinarsJazzClient = MakeExtender<
  JazzClientExtender,
  {
    userInfo: {
      features: {
        maxWebinarViewersCapacity?: number;
        maxWebinarSpeakersCapacity?: number;
        webinarAvailable?: boolean;
      };
    };
  }
>;

type SupportedVideoEffects = {
  autoWhiteBalance: boolean;
  autoGammaCorrection: boolean;
  autoColorCorrection: boolean;
  faceBeautification: boolean;
  smartFocus: boolean;
  virtualBackgroundSegmentation: boolean;
  greenScreenSegmentation: boolean;
};

type VideoEffectsService = {
  initialize: () => Promise<void>;
  setMediaStream: (stream: MediaStream | undefined) => void;
  setEffects: (effects: Partial<VideoEffects>) => void;
  supportedEffects: Atom<Partial<SupportedVideoEffects>>;
  status: Atom<VideoEffectsServiceStatus>;
  isActive: Atom<boolean>;
} & SubscriberEvent<VideoEffectsEvents>;
type VideoEffectsServiceStatus =
  | {
      type: 'empty';
    }
  | {
      type: 'loading';
    }
  | {
      type: 'error';
      error: string;
    }
  | {
      type: 'ready';
    };
type VideoEffectsEvents = {
  setMediaStream: {
    id: string | undefined;
  };
  startEffect: void;
  stopEffect: void;
  setSettings: {
    settings: VideoEffects;
  };
};
type VideoEffects = {
  backgroundReplacement: ImageBitmap | 'blur' | 'disabled';
  autoWhiteBalance: boolean;
  autoGammaCorrection: boolean;
  autoColorCorrection: boolean;
  faceBeautification: boolean;
  smartFocus: boolean;
};

/**
 * Returns the instance of VideoEffectsService.
 */

declare function getVideoEffectsService<
  R extends JazzRoomExtender[],
  C extends JazzClientExtender[],
>(sdk: JazzSdk | JazzRoom<R> | JazzClient<C>): VideoEffectsService;

declare function videoEffectsPlugin(): JazzSdkPlugin;

export {
  AUDIO_GAIN_DEFAULT,
  type AudioMixerEvent,
  type AudioMixerEventAddMediaStream,
  type AudioMixerEventGainChanged,
  type AudioMixerEventRemoveMediaStream,
  type AudioMixerEventStartAudio,
  type AudioMixerEventStartedAudio,
  type AudioMixerEventStopAudio,
  type AudioMixerEventStoppedAudio,
  type AudioOutputMixer,
  type AudioOutputMixerContext,
  type AudioOutputMixerManager,
  type ChatEvents,
  type ChatJazzClient,
  type ChatJazzRoom,
  type ChatMessage,
  type ChatMessageStatus,
  type ChatPermissionsUpdateEvent,
  type ChatRoomService,
  type CollectMetricsPluginMetric,
  type CollectMetricsPluginMetricData,
  type CollectMetricsPluginMetricMeta,
  type CollectMetricsPluginOptions,
  type CollectMetricsPluginSubscribeHandler,
  type CreateRoomWebinarRequest,
  type CreateRoomWebinarResponse,
  type FindSummarization,
  type GetTranscriptionError,
  MAX_AUDIO_GAIN_VALUE,
  MIN_AUDIO_GAIN_VALUE,
  type MessageReceivedChatEvent,
  type MessageSentChatEvent,
  type MessageStatusEvent,
  type MfxAudioEffectService,
  type MfxAudioPluginModes,
  type MfxAudioPluginService,
  type MfxAudioProvider,
  type MfxAudioProviderState,
  type ModeratorsJazzClient,
  type ModeratorsJazzRoom,
  type ModeratorsPluginRoomServiceType,
  type PollQuestion,
  type PollQuestionType,
  type PollRole,
  type PollStatus,
  type PollTemplate,
  type PollTemplateListItem,
  type PollsJazzClient,
  type PollsJazzRoom,
  type PollsPluginClientServiceType,
  type PollsPluginRoomServiceType,
  type RateSummarizationRequest,
  type RateSummarizationResponse,
  type RecalculateSummarizationRequest,
  type RecalculateSummarizationResponse,
  type Retranslation,
  type RoomRecordingServiceClient,
  type RoomServerRecordingStatus,
  type RoomServerRecordingSupportedStatus,
  ServerRecordingError,
  type ServerRecordingEventAutoStartError,
  type ServerRecordingEventError,
  type ServerRecordingEventStartError,
  type ServerRecordingEventStarted,
  type ServerRecordingEventStopError,
  type ServerRecordingEventStopped,
  type ServerRecordingEventUploadError,
  type ServerRecordingEventUploaded,
  type ServerRecordingEvents,
  type ServerRecordingJazzClient,
  type ServerRecordingJazzRoom,
  type ServerRecordingMessages,
  type ServerRecordingRoomService,
  type ServerRecordingStartErrorEvent,
  type ServerRecordingStopErrorEvent,
  type StartTranscriptionError,
  type StopTranscriptionError,
  type SummarizationClientService,
  type SummarizationClientServiceAvailableEventPayload,
  type SummarizationError,
  type SummarizationJazzClient,
  type SummarizationRoomEnableUpdateEventPayload,
  type SummarizationRoomService,
  type TranscriptionAlreadyStartedError,
  type TranscriptionForbiddenError,
  TranscriptionGetError,
  type TranscriptionJazzClient,
  type TranscriptionJazzRoom,
  type TranscriptionNotStartedError,
  type TranscriptionPluginServiceType,
  TranscriptionStartError,
  TranscriptionStopError,
  type VideoEffects,
  type VideoEffectsService,
  type VideoEffectsServiceStatus,
  type VideoElementPoolForRoomElement,
  type VideoElementPoolForRoomElementEvents,
  type VideoElementPoolForRoomEvents,
  type VideoElementPoolForRoomRequestVideoElement,
  type VideoElementPoolForRoomService,
  type VideoElementPoolForRoomVideoSizeSettings,
  type VideoElementPoolForRoomVideoSource,
  type VideoElementPoolGlobalElementEvents,
  type VideoElementPoolGlobalElementForStream,
  type VideoElementPoolGlobalEvents,
  type VideoElementPoolGlobalService,
  type VideoElementPoolGlobalSettings,
  type VideoElementPoolPluginOptions,
  type VideoElementPoolSettings,
  type VideoElementPoolSettingsPausedSources,
  type VideoElementPoolSettingsVideoSource,
  type VideoElementPoolStreamSize,
  WEBINARS_PLUGIN_CLIENT_MODULE,
  type WatermarkRoomEnableUpdateEvent,
  type WatermarkRoomEvent,
  type WatermarkRoomPermissionUpdateEvent,
  type WatermarkRoomService,
  type WatermarksJazzClient,
  type WatermarksJazzRoom,
  type WebinarsJazzClient,
  type WebinarsJazzRoom,
  type WebinarsPluginClientService,
  atomToRxEffectsQuery,
  atomToRxObserve,
  audioOutputMixerPlugin,
  chatPlugin,
  collectMetricsPlugin,
  getAppVideoElementPool,
  getAudioOutputMixer,
  getAudioOutputMixerManager,
  getChatService,
  getCollectMetricsCustomMetaService,
  getMfxAudioEffectService,
  getModeratorsPluginRoomService,
  getPollsPluginClientService,
  getPollsPluginRoomService,
  getServerRecording,
  getServerRecordingClient,
  getSummarizationRoomService,
  getTranscriptionRoomService,
  getVideoEffectsService,
  getVideoElementPoolForRoom,
  getWatermarkService,
  getWebinarsPluginRoomService,
  logsPlugin,
  mfxAudioPlugin,
  moderatorsPlugin,
  pollsPlugin,
  serverRecordingPlugin,
  signalToRxEffectsAction,
  signalToRxObserve,
  subscribeAtom,
  summarizationPlugin,
  transcriptionPlugin,
  videoEffectsPlugin,
  videoElementPoolPlugin,
  watermarksPlugin,
  webinarsPlugin,
  withModeratorsCreateRoom,
  withServerRecordingCreateRoom,
  withSummarizationCreateRoom,
  withSummarizationGetMeetingHistoryDetails,
  withTranscriptionCreateRoom,
  withWatermarksCreateRoom,
};
