import { HttpClient, HttpContext } from '@angular/common/http';
import { inject } from '@angular/core';

import {
  LOADING_MODE,
  LoadingMode
} from './global-loading/loading-mode';

const REQUEST_LOADING_MODE = Symbol('request-loading-mode');

class LoadingHttpClient {
  readonly get: HttpClient['get'];
  readonly post: HttpClient['post'];
  readonly put: HttpClient['put'];
  readonly patch: HttpClient['patch'];
  readonly delete: HttpClient['delete'];

  constructor(
    http: HttpClient,
    private readonly buildContext: (context?: HttpContext) => HttpContext
  ) {
    this.get = ((url: string, options?: object) =>
      http.get(url, this.withLoadingContext(options))) as HttpClient['get'];
    this.post = ((url: string, body: unknown, options?: object) =>
      http.post(url, body, this.withLoadingContext(options))) as HttpClient['post'];
    this.put = ((url: string, body: unknown, options?: object) =>
      http.put(url, body, this.withLoadingContext(options))) as HttpClient['put'];
    this.patch = ((url: string, body: unknown, options?: object) =>
      http.patch(url, body, this.withLoadingContext(options))) as HttpClient['patch'];
    this.delete = ((url: string, options?: object) =>
      http.delete(url, this.withLoadingContext(options))) as HttpClient['delete'];
  }

  private withLoadingContext(options?: object): object {
    const requestOptions = options as { context?: HttpContext } | undefined;

    return {
      ...requestOptions,
      context: this.buildContext(requestOptions?.context)
    };
  }
}

export abstract class BaseApiService {
  private readonly rawHttp = inject(HttpClient);
  private readonly [REQUEST_LOADING_MODE]: LoadingMode = 'global';

  protected get http(): LoadingHttpClient {
    return new LoadingHttpClient(
      this.rawHttp,
      (context) => this.loadingContext(context)
    );
  }

  withLoading(loadingMode: LoadingMode): this {
    const scopedService = Object.create(this) as this;

    Object.defineProperty(scopedService, REQUEST_LOADING_MODE, {
      configurable: false,
      enumerable: false,
      value: loadingMode,
      writable: false
    });

    return scopedService;
  }

  withCustomLoading(): this {
    return this.withLoading('custom');
  }

  withoutLoading(): this {
    return this.withLoading('none');
  }

  protected loadingContext(context: HttpContext = new HttpContext()): HttpContext {
    return context.set(LOADING_MODE, this[REQUEST_LOADING_MODE]);
  }
}
