import type { Sleeve } from "../Sleeve";
import type { ActionIdentifier } from "../../../Bladeburner/Types";
import { Player } from "@player";
import { BladeburnerActionType, BladeburnerGeneralActionName } from "@enums";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { applySleeveGains, SleeveBaseWork, SleeveWorkType } from "./Work";
import { CONSTANTS } from "../../../Constants";
import { scaleWorkStats } from "../../../Work/WorkStats";
import { loadActionIdentifier } from "../../../Bladeburner/utils/loadActionIdentifier";
import { invalidWork } from "../../../Work/InvalidWork";
import { assertObject } from "../../../utils/TypeAssertion";

interface SleeveBladeburnerWorkParams {
  actionId: ActionIdentifier & { type: BladeburnerActionType.General | BladeburnerActionType.Contract };
}

export const isSleeveBladeburnerWork = (w: SleeveBaseWork | null): w is SleeveBladeburnerWork =>
  w?.type === SleeveWorkType.BLADEBURNER;

export class SleeveBladeburnerWork extends SleeveBaseWork {
  type: SleeveWorkType.BLADEBURNER = SleeveWorkType.BLADEBURNER;
  tasksCompleted = 0;
  cyclesWorked = 0;
  actionId: ActionIdentifier & { type: BladeburnerActionType.General | BladeburnerActionType.Contract };

  constructor(params?: SleeveBladeburnerWorkParams) {
    super();
    this.actionId = params?.actionId ?? {
      type: BladeburnerActionType.General,
      name: BladeburnerGeneralActionName.FieldAnalysis,
    };
  }

  cyclesNeeded(sleeve: Sleeve): number {
    if (!Player.bladeburner) return Infinity;
    const action = Player.bladeburner.getActionObject(this.actionId);
    const timeInMs = action.getActionTime(Player.bladeburner, sleeve) * 1000;
    return timeInMs / CONSTANTS.MilliPerCycle;
  }

  process(sleeve: Sleeve, cycles: number) {
    if (!Player.bladeburner) return sleeve.stopWork();
    this.cyclesWorked += cycles;
    if (this.actionId.type === BladeburnerActionType.Contract) {
      const action = Player.bladeburner.getActionObject(this.actionId);
      if (action.count < 1) return sleeve.stopWork();
    }

    while (this.cyclesWorked >= this.cyclesNeeded(sleeve)) {
      if (this.actionId.type === BladeburnerActionType.Contract) {
        const action = Player.bladeburner.getActionObject(this.actionId);
        if (action.count < 1) return sleeve.stopWork();
      }
      const retValue = Player.bladeburner.completeAction(sleeve, this.actionId, false);
      applySleeveGains(sleeve, scaleWorkStats(retValue, sleeve.shockBonus(), false));

      this.tasksCompleted++;
      this.cyclesWorked -= this.cyclesNeeded(sleeve);
      this.resolveNextCompletion();
    }
  }

  APICopy(sleeve: Sleeve) {
    return {
      type: SleeveWorkType.BLADEBURNER as const,
      actionType: this.actionId.type,
      actionName: this.actionId.name,
      tasksCompleted: this.tasksCompleted,
      cyclesWorked: this.cyclesWorked,
      cyclesNeeded: this.cyclesNeeded(sleeve),
      nextCompletion: this.nextCompletion,
    };
  }

  /** Serialize the current object to a JSON save state. */
  toJSON(): IReviverValue {
    return Generic_toJSON("SleeveBladeburnerWork", this);
  }

  /** Initializes a BladeburnerWork object from a JSON save state. */
  static fromJSON(value: IReviverValue): SleeveBladeburnerWork {
    assertObject(value.data);
    let actionId = loadActionIdentifier(value.data?.actionId);
    if (!actionId) {
      /**
       * In pre-v2.6.1 versions, "name" and "type" of actionId are saved directly in "actionName" and "actionType", not
       * in the actionId object.
       */
      if (!value.data["actionName"]) {
        return invalidWork();
      }
      actionId = loadActionIdentifier({ name: value.data["actionName"], type: value.data["actionType"] });
      if (!actionId) {
        return invalidWork();
      }
    }
    value.data.actionId = actionId;
    return Generic_fromJSON(SleeveBladeburnerWork, value.data);
  }
}

constructorsForReviver.SleeveBladeburnerWork = SleeveBladeburnerWork;
