import {
  JazzSdkOptions,
  JazzSdk,
  JazzActivityEvent,
  JazzRoomEventParticipantLeft,
  JazzRoomEventParticipantJoined,
  JazzRoomEventParticipantUpdate,
  JazzRoomEventParticipants,
  JazzRoomEventLocalParticipantChanged,
  JazzRoomEventLocalParticipantId,
  JazzRoomEventDisconnecting,
  JazzRoomParticipantId,
  JazzSdkAdditionalPlugins,
  JazzRoom,
  JazzClient,
  JazzSdkPlugin,
} from '@salutejs/jazz-sdk-web';
import { Container } from 'ditox';
import { IpcRenderer } from 'electron';

/**
 * Creates Jazz SDK for electron applications.
 */
declare function createJazzSdkElectron(
  options?: JazzSdkOptions,
): Promise<JazzSdk>;

type TransportEventRegisterEndpoint = {
  type: 'registerEndpoint';
  payload: {
    endpointName: string;
  };
};
type TransportEventUnregisterEndpoint = {
  type: 'unregisterEndpoint';
  payload: {
    endpointName: string;
  };
};
type TransportEventHandsRaised = {
  type: 'handsRaised';
  payload: {
    handsRaised: JazzRoomParticipantId[];
  };
};
type ElectronVersion = {
  major: number;
  minor: number;
  patch: number;
};
type MetaInfo = Readonly<{
  electronVersion: ElectronVersion;
}>;
type TransportEventMetaIn = Readonly<{
  type: 'electron:meta:out';
  payload: MetaInfo;
}>;
type TransportEventMetaOut = Readonly<{
  type: 'electron:meta:in';
}>;
type TransportEvent = (
  | JazzActivityEvent
  | JazzRoomEventParticipantLeft
  | JazzRoomEventParticipantJoined
  | JazzRoomEventParticipantUpdate
  | JazzRoomEventParticipants
  | JazzRoomEventLocalParticipantChanged
  | JazzRoomEventLocalParticipantId
  | JazzRoomEventDisconnecting
  | TransportEventRegisterEndpoint
  | TransportEventUnregisterEndpoint
  | TransportEventUnregisterEndpoint
  | TransportEventHandsRaised
  | TransportEventMetaIn
  | TransportEventMetaOut
) &
  EventLike$1;
/**
 * experimental
 */
type TransportInvokeEvent =
  | {
      type: 'someType1';
      payload: {
        foo: 'bar';
      };
      response: {
        bar: 'foo';
      };
    }
  | {
      type: 'someType2';
      response: {
        bar: 'foo';
      };
    };
type EventLike$1 = {
  type: string;
  to?: string;
};
type EventLikeWithResponse = {
  type: string;
  payload?: unknown;
  response: unknown;
};
type EventLikeWithResponsePayload = {
  type: string;
  payload: unknown;
  response: unknown;
};
/**
 * @deprecated use elebus + multiWindowTransport MULTI_WINDOW_TRANSPORT_SERVICE_TOKEN
 */
type TransportService<
  M extends EventLike$1 = TransportEvent,
  I extends EventLikeWithResponse = TransportInvokeEvent,
> = {
  once: <
    Type extends M['type'],
    Result extends M & {
      type: Type;
    },
  >(
    key: Type,
    callback: (
      ...args: Result extends {
        payload: infer S;
      }
        ? [S]
        : []
    ) => void,
  ) => () => void;
  on: <
    Type extends M['type'],
    Result extends M & {
      type: Type;
    },
  >(
    key: Type,
    callback: (
      ...args: Result extends {
        payload: infer S;
      }
        ? [S]
        : []
    ) => void,
  ) => () => void;
  off: <
    Type extends M['type'],
    Result extends M & {
      type: Type;
    },
  >(
    key: Type,
    callback: (
      ...args: Result extends {
        payload: infer S;
      }
        ? [S]
        : []
    ) => void,
  ) => void;
  /**
   * reference to "on"
   */
  subscribe: <
    Type extends M['type'],
    Result extends M & {
      type: Type;
    },
  >(
    key: Type,
    callback: (
      ...args: Result extends {
        payload: infer S;
      }
        ? [S]
        : []
    ) => void,
  ) => () => void;
  addListener: <
    Type extends M['type'],
    Result extends M & {
      type: Type;
    },
  >(
    key: Type,
    callback: (
      ...args: Result extends {
        payload: infer S;
      }
        ? [S]
        : []
    ) => void,
  ) => void;
  /**
   * reference to "off"
   */
  removeListener: <
    Type extends M['type'],
    Result extends M & {
      type: Type;
    },
  >(
    key: Type,
    callback: (
      ...args: Result extends {
        payload: infer S;
      }
        ? [S]
        : []
    ) => void,
  ) => void;
  send: (event: M) => void;
  /**
   * experimental
   */
  __invoke: <
    Type extends I['type'],
    Payload extends I & {
      type: Type;
    },
    Response extends (I & {
      type: Type;
    })['response'],
  >(
    params: Payload extends EventLikeWithResponsePayload
      ? {
          type: Type;
          payload: Payload['payload'];
        }
      : {
          type: Type;
        },
  ) => Promise<Response>;
  withType: <
    S extends EventLike$1 | undefined | void | null,
    O extends EventLikeWithResponse | undefined | void | null = void,
  >() => S extends EventLike$1
    ? O extends EventLikeWithResponse
      ? TransportService<M | S, I | O>
      : TransportService<M | S, I>
    : O extends EventLikeWithResponse
      ? TransportService<M, I | O>
      : TransportService<M, I>;
};

type JazzSdkElectronBridge = {
  transport: TransportService;
};

type JazzSdkElectronEndpointOptions = Readonly<{
  container?: Container;
  /** Extensions for the SDK */
  plugins?: JazzSdkAdditionalPlugins;
  endpointName: string;
  bridge?: () => JazzSdkElectronBridge;
  ipcRenderer?: IpcRenderer;
}>;
type JazzSdkElectronEndpoint = Readonly<{
  container: Container;
  destroy: () => void;
}>;

/**
 * Creates Jazz SDK for browserWindows of electron.
 */
declare function createJazzSdkElectronEndpoint(
  options: JazzSdkElectronEndpointOptions,
): Promise<JazzSdkElectronEndpoint>;

declare function getJazzSdkMetaInfo(jazzSdk: JazzSdk): MetaInfo;

/**
 * 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 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;

type MultiWindowTransportWindowName = LooseAutocomplete<
  MainProcessWindowName | MainWindowName
>;
type MultiWindowTransportTransportName = string;
type MainProcessWindowName = 'main_process';
/**
 * Зарезервированное имя по умолчанию для сервиса в render процессе
 */
type MainWindowName = 'mainWindow';
type LooseAutocomplete<T extends string> = T | (string & {});
type MultiWindowTransportSendTo =
  | LooseAutocomplete<'*' | MainProcessWindowName | MainWindowName>
  | MultiWindowTransportWindowName[];
type MultiWindowTransportServiceGetEventsForTransport = {
  isReady: undefined;
  'service:register': {
    window: MultiWindowTransportWindowName;
  };
  'service:unregister': {
    window: MultiWindowTransportWindowName;
  };
  'service:initialized': {
    window: MultiWindowTransportWindowName;
  };
  'transport:register': {
    transport: MultiWindowTransportTransportName;
    window: MultiWindowTransportWindowName;
  };
  'transport:unregister': {
    transport: MultiWindowTransportTransportName;
    window: MultiWindowTransportWindowName;
  };
  'transport:event:subscribe': {
    transport: MultiWindowTransportTransportName;
    window: MultiWindowTransportWindowName;
    event: string;
  };
  'transport:event:unsubscribe': {
    transport: MultiWindowTransportTransportName;
    window: MultiWindowTransportWindowName;
    event: string;
  };
};
type MultiWindowTransportModify<
  INITIAL_TYPE extends EventLike,
  MODIFY_TYPE extends EventLike = INITIAL_TYPE,
> = {
  /**
   * Модификация отправляемых данных в другие окна
   */
  send: <
    TYPE extends keyof INITIAL_TYPE & string,
    PAYLOAD extends INITIAL_TYPE[TYPE],
  >(
    type: TYPE,
    payload: PAYLOAD,
  ) => Promise<MODIFY_TYPE>;
  /**
   * Модификация событий, полученных из других окон приложения
   */
  get: <
    TYPE extends keyof MODIFY_TYPE & string,
    PAYLOAD extends MODIFY_TYPE[TYPE],
  >(
    type: TYPE,
    payload: PAYLOAD,
  ) => Promise<INITIAL_TYPE>;
};
type MultiWindowTransportUnsubscribe = () => void;
type MultiWindowTransportRegisterOptions<
  INITIAL_TYPE extends EventLike,
  MODIFY_TYPE extends EventLike = INITIAL_TYPE,
> = {
  /**
   * под каким именем будет использоваться транспорт.
   * Если параметр не передан, то имя будет взято из транспорта.
   */
  name?: MultiWindowTransportWindowName;
  /**
   * elebus шина событий
   */
  transport: TransportRoot<INITIAL_TYPE>;
  /**
   * Модификация отправляемых и получаем событий из других окон приложения
   * Нужно, если нужно видоизменить данные. Особенно важно, если событие
   * содержит не сериализуемые/дессириализуемые данные (классы)
   */
  modify?: MultiWindowTransportModify<INITIAL_TYPE, MODIFY_TYPE>;
  /**
   * В какие окна должны пересылаться события
   * По умолчанию они отправляются во все окна подписчики
   * (см параметр name при регистрации плагина)
   *
   * @default '*'
   */
  to?: MultiWindowTransportSendTo;
};
type MultiWindowTransportRegisterInfo = {
  unsubscribe: MultiWindowTransportUnsubscribe;
};
type MultiWindowTransportService = {
  register: <INITIAL_TYPE extends EventLike, MODIFY_TYPE extends EventLike>(
    options: MultiWindowTransportRegisterOptions<INITIAL_TYPE, MODIFY_TYPE>,
  ) => MultiWindowTransportRegisterInfo;
  unregister: <EVENTS extends EventLike>(
    transport: TransportRoot<EVENTS>,
  ) => void;
  /**
   * Проинициализирован ли сервис до конца
   */
  getIsReady: () => boolean;
  /**
   * Подписка на события сервиса
   */
  on: TransportRoot<MultiWindowTransportServiceGetEventsForTransport>['on'];
  once: TransportRoot<MultiWindowTransportServiceGetEventsForTransport>['once'];
  off: TransportRoot<MultiWindowTransportServiceGetEventsForTransport>['off'];
};

type MultiWindowTransportPluginSettings = {
  /**
   * Имя сервиса. Для каждого окна оно должно быть уникальным
   * Если имя не передано, то будет использоваться значение по умолчанию
   *
   * `WARNING`: несколько окон с одним именем не должно существовать
   * Зарезервировано 2 имени:
   * `main_process` - для сервиса на уровне main процесса в electron
   * `mainWindow` - для главного окна приложения
   */
  name?: MultiWindowTransportTransportName;
  /**
   * Если у нас идет в окне изоляция и мы не можем получить доступ
   * к preload слою, то передаем в плагин ipcRender,
   * чтоб создать свой бридж
   */
  ipcRenderer?: IpcRenderer;
};

declare function getMultiWindowTransport(
  sdk: JazzRoom | JazzClient | JazzSdk,
): MultiWindowTransportService;

declare function multiWindowTransportPlugin(
  settings?: MultiWindowTransportPluginSettings,
): JazzSdkPlugin;

export {
  type JazzSdkElectronEndpoint,
  type JazzSdkElectronEndpointOptions,
  type MultiWindowTransportModify,
  type MultiWindowTransportPluginSettings,
  type MultiWindowTransportRegisterInfo,
  type MultiWindowTransportRegisterOptions,
  type MultiWindowTransportSendTo,
  type MultiWindowTransportService,
  type MultiWindowTransportUnsubscribe,
  createJazzSdkElectron,
  createJazzSdkElectronEndpoint,
  getJazzSdkMetaInfo,
  getMultiWindowTransport,
  multiWindowTransportPlugin,
};
