import type { Logger } from '@learnitive/logger';
import type { Service, ServiceContext } from '@learnitive/poor-mans-di';
import type {
  BaseError,
  BaseServiceCommonOptions,
  Store,
} from '@learnitive/types';
export type BaseServiceContext = {
  ctx: BaseServiceCommonOptions;
  serviceContext: ServiceContext;
};

export abstract class BaseService implements Service<BaseServiceCommonOptions> {
  hookPostInstantiate?() {}

  // called after hookPostInstantiate and  asynchronously after all dependencies have mounted
  abstract hookMount(): void | Promise<void>;

  abortSignal: AbortSignal;
  logger: Logger;

  get aborted() {
    return this.abortSignal.aborted;
  }
  get mounted() {
    return this._mounted;
  }

  private _mountPromise: Promise<void> | undefined;

  private _mounted = false;

  get mountPromise() {
    return this._mountPromise ?? Promise.resolve();
  }

  protected get store(): Store {
    return this.__context.ctx.store;
  }

  constructor(
    public name: string,
    private __context: BaseServiceContext,
    private __dependencies: Record<string, Service<any>> | null,
  ) {
    this.logger = __context.ctx.logger.child(this.name);
    this.logger.debug('Creating service');
    this.abortSignal = __context.serviceContext.abortSignal;

    this.abortSignal.addEventListener('abort', () => {
      this.logger.debug('Aborting service');
      this._mounted = false;
    });
  }

  addCleanup(...callbacks: Array<() => void>): void {
    for (const cb of callbacks) {
      this.abortSignal.addEventListener(
        'abort',
        () => {
          cb();
        },
        {
          once: true,
        },
      );
    }
  }

  postInstantiate() {
    this.hookPostInstantiate?.();
  }

  mount() {
    if (this._mountPromise) {
      return this._mountPromise;
    }

    const dep = this.__dependencies ?? {};

    this._mountPromise = Promise.all(
      Object.values(dep)
        .map((dep) => dep.mount?.())
        .filter((x) => !!x),
    ).then(async () => {
      this.logger.debug('Mounting service');
      await this.hookMount();
      if (this.aborted) {
        return;
      }
      this._mounted = true;
    });

    return this._mountPromise;
  }

  protected emitAppError = (error: BaseError): void => {
    this.logger.debug('Emitting app error');
    queueMicrotask(() => {
      this.__context.ctx.emitAppError(error);
    });
  };
}
