this.ApiClient = class {
  constructor() {
    this._baseURL = `${window.location.origin}/Synergy`;
    this._requestInterceptors = [];
    this._responseInterceptors = [];
  }

  async request({ url, method = 'GET', body, headers = {}, defaultUser = false }) {
    let finalHeaders = this._normalizeHeaders({
      authorization: defaultUser ? this._buildDefaultAuthHeader() : this._buildAuthHeader(),
      ...headers,
    });

    let finalBody = body;

    const contentType = this._getContentType(finalHeaders);

    if (body != null) {
      // FormData
      if (body instanceof FormData) {
        delete finalHeaders['content-type'];
      }

      // JSON
      else if (contentType === 'application/json') {
        finalBody = JSON.stringify(body);
      }

      // x-www-form-urlencoded
      else if (contentType === 'application/x-www-form-urlencoded') {
        finalBody = new URLSearchParams(body).toString();
      }

      // авто JSON
      else if (!contentType && this._isJsonLike(body)) {
        finalHeaders['content-type'] = 'application/json';
        finalBody = JSON.stringify(body);
      }
    }

    let config = {
      url: `${this._baseURL}/${url}`,
      method,
      headers: finalHeaders,
      body: ['GET', 'HEAD'].includes(method) ? undefined : finalBody,
    };

    // request interceptors
    for (const interceptor of this._requestInterceptors) {
      config = (await interceptor(config)) || config;
    }

    let response;
    try {
      response = await fetch(config.url, config);
    } catch (err) {
      throw this._handleNetworkError(err);
    }

    const parsedData = await this._parseResponse(response);

    let result = {
      ok: response.ok,
      status: response.status,
      headers: response.headers,
      data: parsedData,
    };

    // response interceptors
    for (const interceptor of this._responseInterceptors) {
      result = (await interceptor(result)) || result;
    }

    if (!response.ok) {
    	console.error({config, msg: result});
      throw this._handleHttpError(result);
    }

    return result.data;
  }

  addRequestInterceptor(fn) {
    this._requestInterceptors.push(fn);
  }

  addResponseInterceptor(fn) {
    this._responseInterceptors.push(fn);
  }

  _buildAuthHeader() {
		const {login, password, token} = AS.OPTIONS;

    if (token) {
      return `Bearer ${token}`;
    } else {
    	const base64 = btoa(
    		new TextEncoder().encode(`${login}:${password}`)
    		.reduce((str, byte) => str + String.fromCharCode(byte), '')
    		);
      return `Basic ${base64}`;
    }
  }

  _buildDefaultAuthHeader() {
    const {defaultUser, defaultUserPassword} = Cons.getCurrentApp();

    const base64 = btoa(
      new TextEncoder().encode(`${defaultUser}:${defaultUserPassword}`)
      .reduce((str, byte) => str + String.fromCharCode(byte), '')
      );
    return `Basic ${base64}`;
  }

  _normalizeHeaders(headers = {}) {
    const result = {};
    for (const key in headers) {
      result[key.toLowerCase()] = headers[key];
    }
    return result;
  }

  _getContentType(headers = {}) {
    const ct = headers['content-type'];
    if (!ct) return null;
    return ct.split(';')[0].trim().toLowerCase();
  }

  _isJsonLike(body) {
    return (
      typeof body === 'object' &&
      !(body instanceof FormData) &&
      !(body instanceof Blob) &&
      !(body instanceof ArrayBuffer)
    );
  }

  async _parseResponse(response) {
    const contentType = response.headers.get('content-type') || '';

    try {
      if (contentType.includes('application/json')) {
        return await response.json();
      }

      if (contentType.includes('text/')) {
        return await response.text();
      }

      return await response.blob();
    } catch {
      return null;
    }
  }

  _handleNetworkError(err) {
    return new Error(`Network error: ${err.message}`);
  }

  _handleHttpError(res) {
    return new Error(`HTTP error: ${res.status} - ${JSON.stringify(res.data)}`);
  }
}