import { Module, ModuleDeclaration, Container } from 'ditox';
import { JazzRoom, JazzClient, JazzSdk } from '@salutejs/jazz-sdk-web';

type AnyObject = Record<string, any>;

type LogLevel$1 = 'debug' | 'info' | 'warn' | 'error';
type LogMetadata = Readonly<Record<string, string>>;
type LogEvent$1 = Readonly<{
  timestamp: number;
  level: LogLevel$1;
  tag: string;
  messages: ReadonlyArray<unknown>;
  meta?: LogMetadata;
}>;

/**
 * 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 LogLevel = LogLevel$1;
type LogEvent = LogEvent$1;

type Version = {
  major: number;
  minor: number;
  patch: number;
  preReleaseLabel?: string;
  preReleaseType?: string;
  preReleaseIncrement?: number;
};

/**
 * Jazz SDK Plugin provides additional features.
 */
type JazzSdkPlugin<T extends Module<AnyObject> = Module<AnyObject>> = Promise<
  () => {
    key: string | undefined;
    module: JazzSdkModuleDeclaration<T>;
  }
>;
type JazzSdkModuleDeclaration<
  T extends JazzSdkModule<AnyObject> = JazzSdkModule<AnyObject>,
> = ModuleDeclaration<T>;
type JazzSdkModule<T extends AnyObject = AnyObject> = Module<T>;
type JazzSdkAdditionalPlugins = ReadonlyArray<JazzSdkPlugin>;

type JazzSdkElectronMainOptions = {
  container?: Container;
  plugins?: JazzSdkAdditionalPlugins;
};
type JazzSdkElectronMain = {
  container: Container;
  destroy: () => void;
};

declare const createJazzSdkElectronMain: (
  options?: JazzSdkElectronMainOptions,
) => Promise<JazzSdkElectronMain>;

declare function multiWindowTransportPlugin(): JazzSdkPlugin;

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 MultiWindowTransportBridgeServiceHandler = (
  event: MultiWindowTransportServiceGetEvents,
) => void;
type MultiWindowTransportBridgeTransportHandler = (
  event: MultiWindowTransportServiceTransportsEvents,
) => void;
type MultiWindowTransportBridge = {
  /**
   * подписываемся на события сервиса из main процесса
   */
  onServiceEvents: (
    callback: MultiWindowTransportBridgeServiceHandler,
  ) => MultiWindowTransportUnsubscribe;
  /**
   * подписываемся на события шин из main процесса
   */
  onTransportsEvents: (
    callback: MultiWindowTransportBridgeTransportHandler,
  ) => MultiWindowTransportUnsubscribe;
  /**
   * отправляем события сервиса в main процесс
   */
  sendServiceEvent: (event: MultiWindowTransportServiceSendEvents) => void;
  /**
   * отправляем события шин в main процесс
   */
  sendTransportEvent: (
    event: MultiWindowTransportServiceTransportsEvents,
  ) => void;
};
/**
 * События сервиса, которые отправляются в main процесс
 */
type MultiWindowTransportServiceSendEvents =
  | {
      type: 'service:register';
      payload: {
        name: MultiWindowTransportWindowName;
      };
    }
  | {
      type: 'service:unregister';
      payload: {
        name: MultiWindowTransportWindowName;
      };
    }
  | {
      type: 'transport:register';
      payload: {
        transport: MultiWindowTransportTransportName;
        to: MultiWindowTransportSendTo;
      };
    }
  | {
      type: 'transport:unregister';
      payload: {
        transport: MultiWindowTransportTransportName;
      };
    }
  | {
      type: 'transport:event:subscribe';
      payload: {
        transport: MultiWindowTransportTransportName;
        event: string;
      };
    }
  | {
      type: 'transport:event:unsubscribe';
      payload: {
        transport: MultiWindowTransportTransportName;
        event: string;
      };
    };
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;
  };
};
/**
 * события сервиса, которые прилетают из main процесса в окна
 */
type MultiWindowTransportServiceGetEvents =
  | {
      type: 'service:register';
      payload: {
        window: MultiWindowTransportWindowName;
      };
    }
  | {
      type: 'service:unregister';
      payload: {
        window: MultiWindowTransportWindowName;
      };
    }
  | {
      type: 'service:initialized';
      payload: {
        window: MultiWindowTransportWindowName;
      };
    }
  | {
      type: 'transport:register';
      payload: {
        transport: MultiWindowTransportTransportName;
        window: MultiWindowTransportWindowName;
      };
    }
  | {
      type: 'transport:unregister';
      payload: {
        transport: MultiWindowTransportTransportName;
        window: MultiWindowTransportWindowName;
      };
    }
  | {
      type: 'transport:event:subscribe';
      payload: {
        transport: MultiWindowTransportTransportName;
        window: MultiWindowTransportWindowName;
        event: string;
      };
    }
  | {
      type: 'transport:event:unsubscribe';
      payload: {
        transport: MultiWindowTransportTransportName;
        window: MultiWindowTransportWindowName;
        event: string;
      };
    };
type MultiWindowTransportServiceTransportsEvents = {
  /**
   * имя шины для которой стрельнуло событие
   */
  transport: MultiWindowTransportTransportName;
  event: string;
  payload: any;
  to?: MultiWindowTransportSendTo;
};
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'];
};

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

type ElectronVersion = Version;

type SdkMainProcessPreinitSetting = {
  setting: string;
  value?: string;
};
declare function getPreinitElectronFlags(): SdkMainProcessPreinitSetting[];

export {
  type ElectronVersion,
  type JazzSdkElectronMain,
  type JazzSdkElectronMainOptions,
  type JazzSdkPlugin,
  type LogEvent,
  type LogLevel,
  type MainProcessWindowName,
  type MainWindowName,
  type MultiWindowTransportBridge,
  type MultiWindowTransportBridgeServiceHandler,
  type MultiWindowTransportBridgeTransportHandler,
  type MultiWindowTransportModify,
  type MultiWindowTransportRegisterInfo,
  type MultiWindowTransportRegisterOptions,
  type MultiWindowTransportSendTo,
  type MultiWindowTransportService,
  type MultiWindowTransportServiceGetEvents,
  type MultiWindowTransportServiceGetEventsForTransport,
  type MultiWindowTransportServiceSendEvents,
  type MultiWindowTransportServiceTransportsEvents,
  type MultiWindowTransportTransportName,
  type MultiWindowTransportUnsubscribe,
  type MultiWindowTransportWindowName,
  type SdkMainProcessPreinitSetting,
  createJazzSdkElectronMain,
  getMultiWindowTransport,
  getPreinitElectronFlags,
  multiWindowTransportPlugin,
};
