'use strict';

import {HandlePromise} from '../../../../helpers/helpers';
import type ProcessContext from './processContext';

/**
 * Abstract class to implement long-running async processes. Each of `start`, `run`, `stop` stage methods are called
 * after each other waiting for previous stage method completed
 */
abstract class AsyncProcess<Context extends ProcessContext = any> {

  private _context: Context;

  /**
   * Constructs instance
   * @param context process context. Inherited processes **MUST** specify the process context as the first parameter in
   * their constructors
   */
  constructor(context: Context) {
    this._context = context;
  }

  /**
   * Returns process context
   * @returns process context
   */
  get context(): Context {
    return this._context;
  }

  /**
   * Called with shared dependencies, specified in scheduler options
   */
  inject(...dependencies: any[]): void {}

  /**
   * Initializes process with arguments specified when scheduled
   */
  initialize(...args: any[]): void {}

  /**
   * Setups the process
   * @param stopPromise stop promise to check if the process should be stopped
   * @returns promise resolving when started or rejecting if failed to start
   * @throws if failed to start
   */
  abstract start(stopPromise: HandlePromise<void>): Promise<void>;

  /**
   * Runs the process after `start` call resolved successfuly if not stopped. This run is a long-running call, which
   * intended to be running until `stopPromise` completes. If completes with an error, the process will be failovered.
   * If resolves earlier than `stopPromise` resolved, the process is treated as stopped unexpectedly and will failover
   * @param stopPromise stop promise to check if the process should be stopped
   * @returns promise resolving when completed successfully or rejecing if failed to run
   */
  abstract run(stopPromise: HandlePromise<void>): Promise<void>;

  /**
   * Stops the process to release resources after `run` completed. Can be called also when `run` was not called due to
   * `start` error or stopped state after `start`, i.e. will always be called eventually
   * @returns promise resolving when completed
   */
  abstract stop(): Promise<void>;
}

namespace AsyncProcess {

  /** Util type retrieving process arguments from its constructor */
  export type Arguments<Process> = Process extends AsyncProcess ? Parameters<Process['initialize']> : any;

  /** Infers process dependencies */
  export type Dependencies<Process> = Process extends AsyncProcess ? Parameters<Process['inject']> : any;
}

export default AsyncProcess;
