import type { RpcResponse, ActionMSEnum,ActionPositionISTEnum,AdminActionItemType,AdminActionMenuItemType,AllFieldsEnum,AllItemGIKTType,ApikeyACITType,AppleIMUInput,ArgsAllActionsInput,ArgsAuthEmailInput,ArgsAuthEmailPrepareInput,ArgsAuthPhoneInput,ArgsAuthSocInput,ArgsBaseInput,ArgsChangePasswordInput,ArgsCollectionInput,ArgsCollectionRelationsInput,ArgsDataInput,ArgsDataSocialsInput,ArgsDataVizInput,ArgsDeleteDataInput,ArgsDeleteExportInput,ArgsDisplayValuesInput,ArgsEmptyDataInput,ArgsFilterInput,ArgsGeneratePaymentLinkInput,ArgsGeocodeInput,ArgsGeolocationInput,ArgsHallElementsInput,ArgsMakeExportInput,ArgsMakeStatementExportInput,ArgsMoveUpdateInput,ArgsPrepareActionInput,ArgsRemoveApiKeyInput,ArgsRunActionInput,ArgsStagesTotalInput,ArgsTCompanyBankUpdateInput,ArgsTCompanyUpdateInput,ArgsUpdateInput,AssetCIPTType,AuthEPSType,AuthEmailPrepareCFIType,AuthSocSTUType,BalanceFPType,BalancePaymentType,BoardFilterPresetFKTType,ByCoordLInput,ByStringLSInput,CollectionCDSType,CollectionMenuAMType,CollectionRelationType,CompositeTypeGHTType,DTType,DashboardBlockTypeBCLPEnum,DatumDHLTType,DatumDIType,DatumIMType,DeviceInfoMNPUInput,DirectionADEnum,DisplayValueDLOTType,EmptyDatumIMType,ErrorCDUType,FieldDescriptionItemType,FieldDescriptionType,FileAudioFileType,FileDocumentFileType,FileImageFileType,FileVideoFileType,FilterPresetFKTType,GatewayCDKTType,GeoRegionGType,GeolocationILType,GeopointType,GradientAPType,GroupIKTType,GroupMType,HallElementType,HallSelectorElementType,InitACPTType,IpResidentBELSInput,IpResidentBELSType,ItemMType,MenuAMType,MenuCGITType,MenuItemChildType,MenuItemType,MetumACFPType,MongoPointType,MongoPolygonType,NavigateOPTEnum,OpfAOPEnum,PaletteCOType,PassportDNOSInput,PassportDNOSType,PayloadUType,ProfileABDTType,ProfileAlertType,RunActionPType,ScoringESWType,SeatNRType,ShmExportType,ShmMsgAuthLinkType,ShmMsgAuthType,ShmSocialType,SortDFInput,StagesTotalMType,StatusADPREnum,StatusDEPEnum,StatusFPSEnum,StructureType,TNominalBankDetailType,TNominalCompanyType,TypeDSEnum,TypeEISWEnum,TypeIUEnum,TypeSTWZEnum,UlResidentEINRInput,UlResidentEINRType,UpdateDType,ValueMType,VizBarChartDataType,VizBarChartType,VizCardType,VizDataType,VizLineChartDataType,VizLineChartType,VizPieChartDataType,VizPieChartType,VizRFMCalulatedRangeFrequencyType,VizRFMCalulatedRangeMonetaryType,VizRFMCalulatedRangeRecencyType,VizRFMCalulatedRangesType,VizRFMDataType,VizRFMType,VizRadarChartDataType,VizRadarChartType,WarningCDUType,childGIKTType } from './types';
import { debounce, map } from 'lodash';
import { observable, toJS } from 'mobx';

            /**
 * Параметр `extensions` в RPC-запросе (`rpcClient.call`, тело: `method`, `params`, `extensions`, …)
 * — это массив имён **связанных коллекций**, которые серверу нужно **догрузить** к основному ответу
 * метода, не включая эти сущности в `result` целиком.
 *
 * **Зачем:** в схеме (см. `BaseType::extensionsStructure`) у полей-ссылок (`ID` / `IDs`) есть
 * соответствующий документ в MongoDB. Без `extensions` в `result` остаются только идентификаторы;
 * с `extensions` сервер (`ExtensionsResolver` в schema) по этим id выбирает документы из кэша/БД
 * и возвращает их **отдельно** в поле `extensions` ответа: объект «имя коллекции → массив документов».
 * Так клиент выбирает, какой граф данных подтягивать, и не платит за лишние join’ы.
 *
 * **Синтаксис строк:** `коллекция` — подгрузить на первом уровне от корневого результата;
 * `a->b` и `a->b->c` — те же коллекции, но для **вложенных** уровней: сначала грузятся `a`, затем
 * по id из полученных документов — `b`, затем `c` (см. `oneSubLevelExtensions` / `twoSubLevelExtensions`
 * в `ExtensionsResolver`). Разрешённые значения для каждого метода задаются кодогеном из схемы.
 *
 * Перед `undefined` у конкретного вызова используется `rpcClient.setDefaultExtensions` (если задано).
 * Пустой массив — дополнительные коллекции не запрашивать.
 * 
 * Далее в коде можно получить данные по примеру ExtensionStore.citiesByID(X) или ExtensionStore.citiesByIDs([X, Y, Z]) и т.д.
 */
            export const rpc = {
allActions: ( params: ArgsAllActionsInput) => {
            return rpcClient.call<ArgsAllActionsInput, AdminActionMenuItemType[] | null, null>(
               'allActions',params, []
            );
             },
apikeys: ( ) => {
            return rpcClient.call<{}, ApikeyACITType[] | null, null>(
               'apikeys',{}, []
            );
             },
audioUpload: (formData:FormData) => {
            return rpcClient.callFormData<FileAudioFileType | null>(
               'audioUpload', formData
            );
             },
authEmail: ( params: ArgsAuthEmailInput) => {
            return rpcClient.call<ArgsAuthEmailInput, string | null, null>(
               'authEmail',params, []
            );
             },
authEmailPrepare: ( params: ArgsAuthEmailPrepareInput) => {
            return rpcClient.call<ArgsAuthEmailPrepareInput, AuthEmailPrepareCFIType | null, null>(
               'authEmailPrepare',params, []
            );
             },
authPhone: ( params: ArgsAuthPhoneInput) => {
            return rpcClient.call<ArgsAuthPhoneInput, ShmMsgAuthType | null, null>(
               'authPhone',params, []
            );
             },
authSoc: ( params: ArgsAuthSocInput) => {
            return rpcClient.call<ArgsAuthSocInput, AuthSocSTUType | null, null>(
               'authSoc',params, []
            );
             },
base64: ( params: ArgsBaseInput) => {
            return rpcClient.call<ArgsBaseInput, string | null, null>(
               'base64',params, []
            );
             },
changePassword: ( params: ArgsChangePasswordInput) => {
            return rpcClient.call<ArgsChangePasswordInput, boolean | null, null>(
               'changePassword',params, []
            );
             },
collection: ( params: ArgsCollectionInput) => {
            return rpcClient.call<ArgsCollectionInput, CollectionCDSType | null, null>(
               'collection',params, []
            );
             },
collectionRelations: ( params: ArgsCollectionRelationsInput) => {
            return rpcClient.call<ArgsCollectionRelationsInput, CollectionRelationType[] | null, null>(
               'collectionRelations',params, []
            );
             },
compositeTypes: ( extensions?: Array<"hallElements" | "hallElements->hallElements" | "hallElements->hallElements->hallElements">) => {
            return rpcClient.call<{}, CompositeTypeGHTType | null, {hallElements?: HallElementType[] | null }>(
               'compositeTypes',{}, extensions
            );
             },
data: ( params: ArgsDataInput) => {
            return rpcClient.call<ArgsDataInput, DatumDHLTType | null, null>(
               'data',params, []
            );
             },
dataSocials: ( params: ArgsDataSocialsInput) => {
            return rpcClient.call<ArgsDataSocialsInput, ShmSocialType[] | null, null>(
               'dataSocials',params, []
            );
             },
dataViz: ( params: ArgsDataVizInput) => {
            return rpcClient.call<ArgsDataVizInput, VizDataType | null, null>(
               'dataViz',params, []
            );
             },
deleteData: ( params: ArgsDeleteDataInput) => {
            return rpcClient.call<ArgsDeleteDataInput, boolean | null, null>(
               'deleteData',params, []
            );
             },
deleteExport: ( params: ArgsDeleteExportInput) => {
            return rpcClient.call<ArgsDeleteExportInput, boolean | null, null>(
               'deleteExport',params, []
            );
             },
displayValues: ( params: ArgsDisplayValuesInput) => {
            return rpcClient.call<ArgsDisplayValuesInput, DisplayValueDLOTType | null, null>(
               'displayValues',params, []
            );
             },
documentUpload: (formData:FormData) => {
            return rpcClient.callFormData<FileDocumentFileType | null>(
               'documentUpload', formData
            );
             },
emptyData: ( params: ArgsEmptyDataInput) => {
            return rpcClient.call<ArgsEmptyDataInput, EmptyDatumIMType | null, null>(
               'emptyData',params, []
            );
             },
filter: ( params: ArgsFilterInput) => {
            return rpcClient.call<ArgsFilterInput, StructureType | null, null>(
               'filter',params, []
            );
             },
generatePaymentLink: ( params: ArgsGeneratePaymentLinkInput) => {
            return rpcClient.call<ArgsGeneratePaymentLinkInput, string | null, null>(
               'generatePaymentLink',params, []
            );
             },
geocode: ( params: ArgsGeocodeInput) => {
            return rpcClient.call<ArgsGeocodeInput, GeopointType[] | null, null>(
               'geocode',params, []
            );
             },
geolocation: ( params: ArgsGeolocationInput) => {
            return rpcClient.call<ArgsGeolocationInput, GeolocationILType | null, null>(
               'geolocation',params, []
            );
             },
hallElements: ( params: ArgsHallElementsInput, extensions?: Array<"hallElements" | "hallElements->hallElements" | "hallElements->hallElements->hallElements">) => {
            return rpcClient.call<ArgsHallElementsInput, HallElementType[] | null, {hallElements?: HallElementType[] | null }>(
               'hallElements',params, extensions
            );
             },
imageUpload: (formData:FormData) => {
            return rpcClient.callFormData<FileImageFileType | null>(
               'imageUpload', formData
            );
             },
init: ( ) => {
            return rpcClient.call<{}, InitACPTType | null, null>(
               'init',{}, []
            );
             },
lastBalanceOperations: ( ) => {
            return rpcClient.call<{}, BalancePaymentType[] | null, null>(
               'lastBalanceOperations',{}, []
            );
             },
listExport: ( ) => {
            return rpcClient.call<{}, ShmExportType[] | null, null>(
               'listExport',{}, []
            );
             },
makeExport: ( params: ArgsMakeExportInput) => {
            return rpcClient.call<ArgsMakeExportInput, boolean | null, null>(
               'makeExport',params, []
            );
             },
makeStatementExport: ( params: ArgsMakeStatementExportInput) => {
            return rpcClient.call<ArgsMakeStatementExportInput, boolean | null, null>(
               'makeStatementExport',params, []
            );
             },
menu: ( ) => {
            return rpcClient.call<{}, MenuAMType | null, null>(
               'menu',{}, []
            );
             },
moveUpdate: ( params: ArgsMoveUpdateInput) => {
            return rpcClient.call<ArgsMoveUpdateInput, boolean | null, null>(
               'moveUpdate',params, []
            );
             },
newApiKey: ( ) => {
            return rpcClient.call<{}, string | null, null>(
               'newApiKey',{}, []
            );
             },
prepareAction: ( params: ArgsPrepareActionInput) => {
            return rpcClient.call<ArgsPrepareActionInput, AdminActionItemType | null, null>(
               'prepareAction',params, []
            );
             },
profile: ( ) => {
            return rpcClient.call<{}, ProfileABDTType | null, null>(
               'profile',{}, []
            );
             },
removeApiKey: ( params: ArgsRemoveApiKeyInput) => {
            return rpcClient.call<ArgsRemoveApiKeyInput, boolean | null, null>(
               'removeApiKey',params, []
            );
             },
runAction: ( params: ArgsRunActionInput) => {
            return rpcClient.call<ArgsRunActionInput, RunActionPType | null, null>(
               'runAction',params, []
            );
             },
stagesTotal: ( params: ArgsStagesTotalInput) => {
            return rpcClient.call<ArgsStagesTotalInput, StagesTotalMType | null, null>(
               'stagesTotal',params, []
            );
             },
tCompanyBankSend: ( ) => {
            return rpcClient.call<{}, TNominalBankDetailType | null, null>(
               'tCompanyBankSend',{}, []
            );
             },
tCompanyBankUpdate: ( params: ArgsTCompanyBankUpdateInput) => {
            return rpcClient.call<ArgsTCompanyBankUpdateInput, TNominalBankDetailType | null, null>(
               'tCompanyBankUpdate',params, []
            );
             },
tCompanySend: ( ) => {
            return rpcClient.call<{}, TNominalCompanyType | null, null>(
               'tCompanySend',{}, []
            );
             },
tCompanyUpdate: ( params: ArgsTCompanyUpdateInput) => {
            return rpcClient.call<ArgsTCompanyUpdateInput, TNominalCompanyType | null, null>(
               'tCompanyUpdate',params, []
            );
             },
update: ( params: ArgsUpdateInput) => {
            return rpcClient.call<ArgsUpdateInput, UpdateDType | null, null>(
               'update',params, []
            );
             },
videoUpload: (formData:FormData) => {
            return rpcClient.callFormData<FileVideoFileType | null>(
               'videoUpload', formData
            );
             },
};
export const compositeTypes = rpc.compositeTypes;
export const geolocation = rpc.geolocation;
export const hallElements = rpc.hallElements;
export const imageUpload = rpc.imageUpload;
export const videoUpload = rpc.videoUpload;
export const audioUpload = rpc.audioUpload;
export const documentUpload = rpc.documentUpload;
export const authEmail = rpc.authEmail;
export const authEmailPrepare = rpc.authEmailPrepare;
export const authSoc = rpc.authSoc;
export const authPhone = rpc.authPhone;
export const profile = rpc.profile;
export const changePassword = rpc.changePassword;
export const init = rpc.init;
export const tCompanyUpdate = rpc.tCompanyUpdate;
export const tCompanySend = rpc.tCompanySend;
export const tCompanyBankUpdate = rpc.tCompanyBankUpdate;
export const tCompanyBankSend = rpc.tCompanyBankSend;
export const menu = rpc.menu;
export const dataSocials = rpc.dataSocials;
export const displayValues = rpc.displayValues;
export const collection = rpc.collection;
export const collectionRelations = rpc.collectionRelations;
export const emptyData = rpc.emptyData;
export const geocode = rpc.geocode;
export const deleteData = rpc.deleteData;
export const data = rpc.data;
export const dataViz = rpc.dataViz;
export const deleteExport = rpc.deleteExport;
export const makeExport = rpc.makeExport;
export const makeStatementExport = rpc.makeStatementExport;
export const listExport = rpc.listExport;
export const filter = rpc.filter;
export const apikeys = rpc.apikeys;
export const removeApiKey = rpc.removeApiKey;
export const newApiKey = rpc.newApiKey;
export const moveUpdate = rpc.moveUpdate;
export const base64 = rpc.base64;
export const update = rpc.update;
export const runAction = rpc.runAction;
export const allActions = rpc.allActions;
export const prepareAction = rpc.prepareAction;
export const stagesTotal = rpc.stagesTotal;
export const generatePaymentLink = rpc.generatePaymentLink;
export const lastBalanceOperations = rpc.lastBalanceOperations;

const uuidv4 = (): string => {
   return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
      const r = crypto.getRandomValues(new Uint8Array(1))[0] & 15;
      const v = c === 'x' ? r : (r & 0x3) | 0x8;
      return v.toString(16);
   });
}

const xorEncrypt = (text: string, key: string) => {
   const textBytes = new TextEncoder().encode(text);
   const keyBytes = new TextEncoder().encode(key);

   const result = new Uint8Array(textBytes.length);
   for (let i = 0; i < textBytes.length; i++) {
      result[i] = textBytes[i] ^ keyBytes[i % keyBytes.length];
   }


   return btoa(String.fromCharCode(...Array.from(result)));
}

const xorDecrypt = (encodedText: string, key: string) => {
   const binaryString = atob(encodedText);
   const textBytes = new Uint8Array(binaryString.length);
   for (let i = 0; i < binaryString.length; i++) {
      textBytes[i] = binaryString.charCodeAt(i);
   }

   const keyBytes = new TextEncoder().encode(key);
   const result = new Uint8Array(textBytes.length);

   for (let i = 0; i < textBytes.length; i++) {
      result[i] = textBytes[i] ^ keyBytes[i % keyBytes.length];
   }

   return new TextDecoder().decode(result);
}


export class rpcClient {
   private static endpoints: string[] = [];
   private static readonly ENDPOINT_INDEX_KEY = 'rpcClient_endpointIndex';
   private static currentEndpointIndex: number = 0;
   private static initialized: boolean = false;
   private static headers: Record<string, string> = {};
   private static tokenGetter: (() => string | null) | null = null;
   private static tokenGetterAsync: (() => Promise<string | null>) | null = null;
   private static unauthorizedHandler: (() => void) | null = null;
   private static networkErrorHandler: (() => void) | null = null;
   private static toastHandler: ((message: string) => void) | null = null;
   private static storage: { save: (key: string, value: string) => Promise<void>; get: (key: string) => Promise<string | null> } | null = null;

   public static getRedirectURL(to: string): string {

      let path = '/redirect?to=' + encodeURIComponent(to);

      let url = this.getCurrentEndpointDomain() + path;

      return url;

   }

   private static defaultExtensions: DefaultExtensionsType = [];

   public static setDefaultExtensions(extensions: DefaultExtensionsType): void {
      this.defaultExtensions = extensions;
   }

   private static abortControllerTimeout: number = 30000;

   public static setAbortControllerTimeout(timeout: number): void {
      this.abortControllerTimeout = timeout;
   }

   public static setStorage(storage: { save: (key: string, value: string) => Promise<void>; get: (key: string) => Promise<string | null> }): void {
      this.storage = storage;
   }

   private static useEncrypt: boolean = false;

   public static setUseEncrypt(useEncrypt: boolean): void {
      this.useEncrypt = useEncrypt;
   }

   public static setNetworkErrorHandler(handler: () => void): void {
      this.networkErrorHandler = handler;
   }

   public static async init(): Promise<void> {
      if (this.initialized) return;
      if (this.storage) {
         try {
            const savedIndex = await this.storage.get(this.ENDPOINT_INDEX_KEY);
            if (savedIndex !== null) {
               const index = parseInt(savedIndex, 10);
               if (!isNaN(index) && index >= 0 && index < this.endpoints.length) {
                  this.currentEndpointIndex = index;
               }
            }
         } catch (error) {
            console.log('⚠️ Ошибка при загрузке индекса endpoint:', error);
         }
      } else {
         this.currentEndpointIndex = 0;
      }
      this.initialized = true;
   }

   private static async saveEndpointIndex(): Promise<void> {
      if (!this.storage) return;
      try {
         await this.storage.save(this.ENDPOINT_INDEX_KEY, this.currentEndpointIndex.toString());
      } catch (error) {
         console.log('⚠️ Ошибка при сохранении индекса endpoint:', error);
      }
   }

   private static switchToNextEndpoint(): void {
      if (this.endpoints.length <= 1) return;
      this.currentEndpointIndex = (this.currentEndpointIndex + 1) % this.endpoints.length;
      this.saveEndpointIndex();
   }

   private static async waitForEndpoints(): Promise<void> {
      if (this.endpoints.length > 0) {
         await this.init();
         return;
      }

      // Ждем 1 секунду и проверяем еще раз
      await new Promise<void>((resolve) => setTimeout(resolve, 1000));

      if (this.endpoints.length === 0) {
         throw new Error('Endpoints are not set');
      }
   }

   private static getCurrentEndpoint(): string {
      if (this.endpoints.length === 0) throw new Error('Endpoints are not set');
      return this.endpoints[this.currentEndpointIndex];
   }

   public static getCurrentEndpointDomain(): string {

      let http = this.getCurrentEndpoint().split('://')[0];
      let path = this.getCurrentEndpoint().split('://')[1];

      let firstPart = path.split('/')[0];

      return `${http}://${firstPart}`;
   }

   private static isNetworkError(error: any): boolean {
      return (
         error.name === 'AbortError' ||
         error.name === 'TimeoutError' ||
         error.name === 'TypeError' ||
         error.message?.includes('Network request failed') ||
         error.message?.includes('Failed to fetch') ||
         error.message?.includes('NetworkError')
      );
   }

   public static setToken(getter: () => string | null): void {
      this.tokenGetter = getter;
   }

   public static setTokenAsync(getter: () => Promise<string | null>): void {
      this.tokenGetterAsync = getter;
   }

   public static setUnauthorizedHandler(handler: () => void): void {
      this.unauthorizedHandler = handler;
   }

   public static setToast(handler: (message: string) => void): void {
      this.toastHandler = handler;
   }

   public static setEndpoints(urls: string[]): void {
      this.endpoints = urls;
      this.currentEndpointIndex = 0;
      this.initialized = false;
   }

   public static getToken(): string | null {
      if (this.tokenGetter) return this.tokenGetter();
      return null;
   }

   public static async getTokenAsync(): Promise<string | null> {
      // Приоритет у синхронной функции
      if (this.tokenGetter) {
         return this.tokenGetter();
      }
      // Если синхронной нет, используем асинхронную
      if (this.tokenGetterAsync) {
         return await this.tokenGetterAsync();
      }
      return null;
   }

   public static setHeaders(headers: Record<string, string>): void {
      this.headers = { ...this.headers, ...headers };
   }

   private static handleError(error: any): void {
      if (error?.type === 'UNAUTHORIZED' || error?.type === 'UNAUTHENTICATED') {
         if (this.unauthorizedHandler) {
            this.unauthorizedHandler();
         }
      } else if (error?.message) {
         if (this.toastHandler) {
            this.toastHandler(error.message);
         }
      }
   }

   public static async callFormData<R>(method: string, formData: FormData): Promise<R> {
      await this.waitForEndpoints();
      const endpoint = this.getCurrentEndpoint();

      formData.append('method', method);

      let _token = await this.getTokenAsync();
      if (_token) {
         formData.append('token', _token);
      }

      formData.append('timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);

      const context = this.useEncrypt ? uuidv4() : undefined;

      if (this.useEncrypt && context) {
         formData.append('context', context);
      }


      const headers = { ...this.headers };
      delete headers['Content-Type'];

      const controller = new AbortController();
      const timeoutId = setTimeout(() => {
         controller.abort();
      }, this.abortControllerTimeout);

      return fetch(endpoint, { method: 'POST', headers, body: formData, signal: controller.signal })
         .then(res => res.json())
         .then((json: RpcResponse<R>) => {
            clearTimeout(timeoutId);

            if (this.useEncrypt && context) {

               if (json.result) {
                  json.result = JSON.parse(xorDecrypt(json.result as string, context));
               }
               if (json.extensions) {
                  json.extensions = JSON.parse(xorDecrypt(json.extensions as string, context));
               }
            }


            if (json?.error) {
               this.handleError(json.error);
               throw json.error;
            }
            this.saveEndpointIndex();
            return json.result as R;
         })
         .catch(err => {
            clearTimeout(timeoutId);
            if (this.isNetworkError(err)) {

               if (this.networkErrorHandler) {
                  this.networkErrorHandler();
               }

               this.switchToNextEndpoint();
            }
            throw err;
         });
   }

   public static async call<P, R, E>(method: string, params?: P, extensions?: Array<string>): Promise<RpcResponse<R, E>> {
      await this.waitForEndpoints();
      const endpoint = this.getCurrentEndpoint();


      const context = this.useEncrypt ? uuidv4() : undefined;


      let _extensions = extensions != undefined ? extensions : this.defaultExtensions;


      const token = await this.getTokenAsync();
      const body = {
         method, extensions: _extensions, context: context, token: token,
         params: params ? context ? xorEncrypt(JSON.stringify(params ?? {}), context) : params : undefined,
         id: Date.now().toString()
      };

      console.log('🚀🚀🚀 method: ', method, ' RPC Endpoint: ', endpoint);
      console.log('🚀🚀🚀 method: ', method, ' RPC Body: ', JSON.stringify(body));


      const controller = new AbortController();
      const timeoutId = setTimeout(() => {
         controller.abort();
      }, this.abortControllerTimeout);

      return fetch(endpoint, {
         method: 'POST', headers: {
            'Content-Type': 'application/json',
            'timezone': Intl.DateTimeFormat().resolvedOptions().timeZone,
            ...this.headers
         }, body: JSON.stringify(body),
         signal: controller.signal
      })
         .then(res => res.json())
         .then((json: RpcResponse<R>) => {
            clearTimeout(timeoutId);

            if (this.useEncrypt && context) {

               if (json.result) {
                  json.result = JSON.parse(xorDecrypt(json.result as string, context));
               }
               if (json.extensions) {
                  json.extensions = JSON.parse(xorDecrypt(json.extensions as string, context));
               }
            }

            console.log('🍪🍪🍪 method: ', method, ' RPC Response: ', JSON.stringify(json));
            if (json?.error) {
               this.handleError(json.error);
               throw json.error;
            }
            this.saveEndpointIndex();

            if (json?.extensions) {
               ExtensionsStore.addToStore(json.extensions);
            }

            return json as RpcResponse<R, E>;
         })
         .catch(err => {
            clearTimeout(timeoutId);
            if (this.isNetworkError(err)) {
               if (this.networkErrorHandler) {
                  this.networkErrorHandler();
               }
               this.switchToNextEndpoint();
            }
            console.log('🛑🛑🛑 RPC Error: ', JSON.stringify(body));
            throw err;
         });
   }
}


        

        type DefaultExtensionsType = ("hallElements")[];
        
        type ExtensionsStoreType = {hallElements:  {[key:string]: HallElementType}};
        
        type ExtensionsStoreInputType = {hallElements?:  HallElementType[]};
        
        export class ExtensionsStore {
        
          private static storage: { save: (key: string, value: string) => Promise<void>; get: (key: string) => Promise<string | null> } | null = null;


  /* 
  
  //Убрал так как объем данных может быть большим и не хватает памяти,
  //Нужно пересмотреть способ кеширования
  public static setStorage(storage: { save: (key: string, value: string) => Promise<void>; get: (key: string) => Promise<string | null> }): void {
      this.storage = storage;
   }*/

   public static async loadFromStorage(): Promise<void> {
      if (this.storage) {
         const extensionsStore = await this.storage.get("extensionsStore");
         if (extensionsStore) {
            try {
               let _extensionsStore: ExtensionsStoreType = JSON.parse(extensionsStore);

               map(_extensionsStore, (value: any, key: keyof ExtensionsStoreType) => {
                  if (value) {

                     if (this.store?.[key]) {
                        //@ts-ignore
                        this.store[key] = {
                           //@ts-ignore
                           ...this.store[key],
                           ...value
                        };

                     } else {
                        //@ts-ignore
                        this.store[key] = value;
                     }

                  }
               });

            } catch (error) {
               console.error("⚠️ Ошибка при загрузке extensionsStore из storage:", error);
            }

         }
      }
   }

            private static saveToStorage = debounce(() => {

                if (this.storage) {

                
                    this.storage.save("extensionsStore", JSON.stringify(toJS(this.store)));
                    
                
                }


            }, 1000);


             private static store: ExtensionsStoreType = observable({hallElements: {}});
                

        
        public static addToStore(value: ExtensionsStoreInputType) {
        
        if(value?.hallElements && value?.hallElements.length > 0) {
            
            for(const item of value?.hallElements) {
                 if(item?._id){
                     this.store.hallElements[item._id] = item;
                 }
            }

            }


         this.saveToStorage();
        }

        public static hallElements(): HallElementType[] {
            return Object.values(this.store.hallElements);
            }

        public static hallElementsByID(id?: string | null): HallElementType | null {
            
            
            if(!id){
                return null;
            }

            if(this.store.hallElements?.[id]){
                return this.store.hallElements[id];
            }else{
               console.info("📕 Extension hallElements not found by ID: " + id);
             }

            return null;
            }


        public static hallElementsByIDs(_ids: (string | undefined | null)[] | null | undefined): HallElementType[] {
            
            if(!_ids){
                return [];
            }

            let ids = _ids.filter(id => id !== undefined && id !== null) as string[];

            const result = [];
            for(const id of ids){
                if(this.store.hallElements[id]){
                    result.push(this.store.hallElements[id]);
                }
            }

            if(result.length === 0){
                console.info("📕 Extensions hallElements not found by IDs: " + ids.join(", "));
            }

            return result;

            }
        
          }
export const enumHelper = {
    "hallElements": {
        "type": {
            "seat": "Место",
            "sector": "Сектор",
            "table": "Стол",
            "stage": "Сцена",
            "wall": "Стена",
            "zone": "Зона"
        }
    },
    "tNominalCompanies": {
        "status": {
            "FAILED": "Отклонен",
            "SUCCESS": "Одобрен",
            "PENDING": "Ожидает проверки"
        },
        "type": {
            "IP_RESIDENT": "Индивидуальный предприниматель",
            "UL_RESIDENT": "Юридическое лицо"
        },
        "opf": {
            "АО": "Акционерное общество",
            "ООО": "Общество с ограниченной ответственностью",
            "ПАО": "Публичное акционерное общество"
        }
    },
    "tNominalBankDetails": {
        "status": {
            "draft": "Черновик",
            "pending": "Ожидает проверки",
            "approved": "Одобрен",
            "rejected": "Отклонен"
        }
    },
    "_shm_exports": {
        "type": {
            "statement": "Ведомость расчетов",
            "data": "Экспорт данные"
        },
        "status": {
            "pending": "В очереди",
            "processing": "В обработке",
            "done": "Выполнено",
            "error": "Ошибка"
        }
    }
}
export const enumHelperGet = {
  "hallElements": {
    "type": (key:string|undefined|null, defaultValue?:any)=>{ return enumHelperValue(enumHelper.hallElements.type, key, defaultValue);   },
  },
  "tNominalCompanies": {
    "status": (key:string|undefined|null, defaultValue?:any)=>{ return enumHelperValue(enumHelper.tNominalCompanies.status, key, defaultValue);   },
    "type": (key:string|undefined|null, defaultValue?:any)=>{ return enumHelperValue(enumHelper.tNominalCompanies.type, key, defaultValue);   },
    "opf": (key:string|undefined|null, defaultValue?:any)=>{ return enumHelperValue(enumHelper.tNominalCompanies.opf, key, defaultValue);   },
  },
  "tNominalBankDetails": {
    "status": (key:string|undefined|null, defaultValue?:any)=>{ return enumHelperValue(enumHelper.tNominalBankDetails.status, key, defaultValue);   },
  },
  "_shm_exports": {
    "type": (key:string|undefined|null, defaultValue?:any)=>{ return enumHelperValue(enumHelper._shm_exports.type, key, defaultValue);   },
    "status": (key:string|undefined|null, defaultValue?:any)=>{ return enumHelperValue(enumHelper._shm_exports.status, key, defaultValue);   },
  },
};
export const enumHelperValue = (
   values: { [key: string]: string },
   key: string | undefined | null,
   defaultValue: any
) => {
   if (!key) return defaultValue;
   return values[key!] ?? defaultValue;
}