import log from "../../log";
import type { ITextDisplayerData } from "../../main_thread/types";
import type { IRange } from "../../utils/ranges";
import { CancellationError } from "../../utils/task_canceller";
import type { ITextDisplayerInterface } from "../segment_sinks";
import type {
  IPushTextDataCoreMessage,
  IRemoveTextDataCoreMessage,
  IResetTextDisplayerCoreMessage,
  IStopTextDisplayerCoreMessage,
} from "../types";
import { CoreMessageType } from "../types";

/**
 * Implementation of an `ITextDisplayerInterface` for the Core, that can run in
 * a WebWorker (so, in a different thread than the `ITextDisplayer`).
 *
 * @class CoreTextDisplayerInterface
 */
export default class CoreTextDisplayerInterface implements ITextDisplayerInterface {
  private _contentId: string;
  private _messageSender: (msg: ICoreTextDisplayerInterfaceMessage) => void;
  public _queues: {
    pushTextData: Array<{
      resolve: (ranges: IRange[]) => void;
      reject: (err: unknown) => void;
    }>;
    remove: Array<{
      resolve: (ranges: IRange[]) => void;
      reject: (err: unknown) => void;
    }>;
  };

  /**
   * @param {string} contentId
   * @param {Object} messageSender
   */
  constructor(
    contentId: string,
    messageSender: (msg: ICoreTextDisplayerInterfaceMessage) => void,
  ) {
    this._contentId = contentId;
    this._messageSender = messageSender;
    this._queues = { pushTextData: [], remove: [] };
  }

  /**
   * @see ITextDisplayerInterface
   */
  public pushTextData(infos: ITextDisplayerData): Promise<IRange[]> {
    return new Promise((resolve, reject) => {
      this._queues.pushTextData.push({ resolve, reject });
      this._messageSender({
        type: CoreMessageType.PushTextData,
        contentId: this._contentId,
        value: infos,
      });
    });
  }

  /**
   * @see ITextDisplayerInterface
   */
  public remove(start: number, end: number): Promise<IRange[]> {
    return new Promise((resolve, reject) => {
      this._queues.remove.push({ resolve, reject });
      this._messageSender({
        type: CoreMessageType.RemoveTextData,
        contentId: this._contentId,
        value: { start, end },
      });
    });
  }

  /**
   * @see ITextDisplayerInterface
   */
  public reset(): void {
    this._messageSender({
      type: CoreMessageType.ResetTextDisplayer,
      contentId: this._contentId,
      value: null,
    });
    this._resetCurrentQueue("WorkerTextDisplayerInterface reset");
  }

  /**
   * @see ITextDisplayerInterface
   */
  public stop(reason: string | undefined): void {
    this._messageSender({
      type: CoreMessageType.StopTextDisplayer,
      contentId: this._contentId,
      value: null,
    });
    this._resetCurrentQueue(reason);
  }

  private _resetCurrentQueue(reason: string | undefined): void {
    const error = new CancellationError(
      "WorkerTextDisplayerInterface queue",
      reason ?? "reset",
    );
    this._queues.pushTextData.forEach((elt) => {
      elt.reject(error);
    });
    this._queues.pushTextData.length = 0;
    this._queues.remove.forEach((elt) => {
      elt.reject(error);
    });
    this._queues.remove.length = 0;
  }

  /**
   * @param {Array.<Object>} ranges
   */
  public onPushedTrackSuccess(ranges: IRange[]): void {
    const element = this._queues.pushTextData.shift();
    if (element === undefined) {
      log.error("text", "pushTextData success for inexistant operation");
      return;
    }
    element.resolve(ranges);
  }

  /**
   * @param {unknown} err
   */
  public onPushedTrackError(err: Error): void {
    const element = this._queues.pushTextData.shift();
    if (element === undefined) {
      log.error("text", "pushTextData error for inexistant operation");
      return;
    }
    element.reject(err);
  }

  /**
   * @param {Array.<Object>} ranges
   */
  public onRemoveSuccess(ranges: IRange[]): void {
    const element = this._queues.remove.shift();
    if (element === undefined) {
      log.error("text", "remove success for inexistant operation");
      return;
    }
    element.resolve(ranges);
  }

  /**
   * @param {unknown} err
   */
  public onRemoveError(err: Error): void {
    const element = this._queues.remove.shift();
    if (element === undefined) {
      log.error("text", "remove error for inexistant operation");
      return;
    }
    element.reject(err);
  }
}

type ICoreTextDisplayerInterfaceMessage =
  | IPushTextDataCoreMessage
  | IRemoveTextDataCoreMessage
  | IStopTextDisplayerCoreMessage
  | IResetTextDisplayerCoreMessage;
