import {Action} from "../../internal/Action.js";
import {TimelinePosition} from "../../qualifiers/video/TimelinePosition.js";
import {BlendModeQualifier} from "../../qualifiers/blendMode/BlendModeQualifier.js";
import {PositionQualifier} from "../../qualifiers/position/PositionQualifier.js";
import {FlagQualifier} from "../../qualifiers/flag/FlagQualifier.js";
import {Position} from "../../qualifiers/position.js";
import {BaseSource} from "../../qualifiers/source/BaseSource.js";
import {BlendModeType} from "../../types/types.js";
import {Qualifier} from "../../internal/qualifier/Qualifier.js";
import {IActionModel} from "../../internal/models/IActionModel.js";
import {IOverlayActionModel} from "../../internal/models/IOverlayActionModel.js";
import {createSourceFromModel} from "../../internal/models/createSourceFromModel.js";
import {ImageSource} from "../../qualifiers/source/sourceTypes/ImageSource.js";
import {ITransformationFromJson} from "../../internal/models/IHasFromJson.js";
import {createPositionFromModel} from "../../internal/models/createPositionFromModel.js";
import {createTimelinePositionFromModel} from "../../internal/models/createTimelinePositionFromModel.js";
import {ISourceModel} from "../../internal/models/ISourceModel.js";
import {ITimelinePositionModel} from "../../internal/models/ITimelinePositionModel.js";
import {IPositionModel} from "../../internal/models/IPositionModel.js";
import {ACTION_TYPE_TO_BLEND_MODE_MAP} from "../../internal/internalConstants.js";


/**
 * @extends SDK.Action
 * @memberOf SDK
 * @description
 * A generic Layer action that can add a Video, Text or Image layer.<br>
 * This class can represent an overlay or an underlay.
 */
class LayerAction extends Action {
  private source: BaseSource;
  private _position:PositionQualifier;
  private _blendMode: BlendModeQualifier | BlendModeType;
  private _timelinePosition: TimelinePosition;
  protected _actionModel: IOverlayActionModel;
  layerType: 'u' | 'l';

  /**
   * @description Creates a LayerAction to be used with overlays and underlays
   * @param {ImageSource | TextSource | VideoSource} layerSource The Source used for the layer, use the builders provided {@link Qualifiers.Source|here}
   */
  constructor(layerSource: BaseSource) {
    super();
    this.source = layerSource;

    this._actionModel = {
      actionType: 'overlay',
      source: layerSource.toJson() as ISourceModel
    };
  }

  /**
   * @description Sets the layerType to 'u' (underlay) or 'l' (overlay).
   * @param {'u' | 'l'} type
   * @return {this}
   */
  setLayerType(type: 'u' | 'l'): this {
    this.layerType = type;
    this._actionModel.actionType = type === 'u' ? 'underlay' : 'overlay';

    return this;
  }

  /**
   * @description Sets the timeline position of the video layer
   * @param {Qualifiers.TimelinePosition} timelinePosition
   * @return {this}
   */
  timeline(timelinePosition: TimelinePosition): this {
    this._timelinePosition = timelinePosition;
    this._actionModel.timelinePosition = timelinePosition.toJson() as unknown as ITimelinePositionModel;

    return this;
  }

  /**
   * @description Sets the position of the layer
   * @param {Qualifiers.Position} position
   * @return {this}
   */
  position(position: Position): this {
    this._position = position;
    this._actionModel.position = position.toJson() as unknown as IPositionModel;

    return this;
  }

  /**
   * @description Specifies how to blend the image overlay with the base overlay
   * @param {Qualifiers.BlendMode|BlendModeType} blendMode
   * @return {this}
   */
  blendMode(blendMode: BlendModeType|BlendModeQualifier): this {
    this._blendMode = blendMode;

    const [mode, level] = `${blendMode}`.replace('e_', '').split(":");
    if (mode === 'anti_removal') {
      this._actionModel.blendMode = level ? {blendModeType: 'antiRemoval', level: level} : {blendModeType: 'antiRemoval'};
    }else {
      this._actionModel.blendMode = {blendModeType: mode};
    }
    return this;
  }

  /**
   * @private
   * @description
   * Closes a layer (layers are built in three stages -> /Open/Transform/Close).
   * @return {SDK.Action}
   */
  private closeLayer():Action {
    const bit = new Action().addFlag(new FlagQualifier('layer_apply'));

    this._position?.qualifiers.forEach((qualifier) => {
      bit.addQualifier(qualifier);
    });

    // Flags are stored separately from qualifiers, we need to add those as well
    this._position?.flags.forEach((flag) => {
      bit.addFlag(flag);
    });

    if(typeof this._blendMode === "string"){
      bit.addQualifier(new Qualifier('e', this._blendMode));
    }else{
      this._blendMode?.qualifiers.forEach((qualifier) => {
        bit.addQualifier(qualifier);
      });
    }
    this._timelinePosition?.qualifiers.forEach((qualifier) => {
      bit.addQualifier(qualifier);
    });

    return bit;
  }

  /**
   * @private
   * @description
   * Opens a layer (layers are built in three stages -> /Open/Transform/Close).
   * @return string
   */
  private openLayer(): string {
    return `${this.source.getOpenSourceString(this.layerType)}`;
  }


  /**
   * @description
   * Serializes the Layer to a string
   * @return {string}
   */
  toString():string{
    return [
      this.openLayer(),
      this.source.getTransformation() && this.source.getTransformation().toString(),
      this.closeLayer()
    ].filter((a) => a).join('/');
  }

  static fromJson(actionModel: IActionModel, transformationFromJson: ITransformationFromJson): LayerAction {
    const {source, actionType, position, timelinePosition, blendMode} = (actionModel as IOverlayActionModel);
    const sourceInstance = createSourceFromModel(source, transformationFromJson) as ImageSource;

    // We are using this() to allow inheriting classes to use super.fromJson.apply(this, [actionModel])
    // This allows the inheriting classes to determine the class to be created
    const result = new this(sourceInstance);
    const layerType = actionType === 'overlay' ? 'l' : 'u';
    result.setLayerType(layerType);

    if (position) {
      result.position(createPositionFromModel(position));
    }

    if (timelinePosition) {
      result.timeline(createTimelinePositionFromModel(timelinePosition));
    }

    if (blendMode) {
      const blendModeType = ACTION_TYPE_TO_BLEND_MODE_MAP[blendMode.blendModeType] || blendMode.blendModeType;
      if(blendMode?.level) {
        result.blendMode(new BlendModeQualifier(blendModeType, blendMode.level as number));
      }else{
        result.blendMode(new BlendModeQualifier(blendModeType));
      }
    }

    return result;
  }
}

export {LayerAction};