// SPDX-FileCopyrightText: 2021 Johannes Loher
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
//
// SPDX-License-Identifier: MIT

import { getGame } from "../utils/utils";
import { evaluateCheck, getRequiredNumberOfDice } from "./check-evaluation";

/**
 * Implements DS4 Checks as an emulated "dice throw".
 *
 * @example
 * - Roll a check against a Check Target Number (CTN) of 18: `/r dsv18`
 * - Roll a check with multiple dice against a CTN of 34: `/r dsv34`
 * - Roll a check with a racial ability that makes `2` a coup and `19` a fumble: `/r dsv19c2:19`
 * - Roll a check with a racial ability that makes `5` a coup and default fumble: `/r dsv19c5`
 */
export class DS4Check extends DiceTerm {
  constructor({ modifiers = [], results = [], options } = {}) {
    super({
      faces: 20,
      results,
      modifiers,
      options,
    });

    // Parse and store check target number
    const checkTargetNumberModifier = this.modifiers.filter((m) => m[0] === "v")[0];
    const ctnRgx = new RegExp("v([0-9]+)?");
    const ctnMatch = checkTargetNumberModifier?.match(ctnRgx);
    if (ctnMatch) {
      const [parseCheckTargetNumber] = ctnMatch.slice(1);
      this.checkTargetNumber = parseCheckTargetNumber
        ? parseInt(parseCheckTargetNumber)
        : DS4Check.DEFAULT_CHECK_TARGET_NUMBER;
    }

    this.number = getRequiredNumberOfDice(this.checkTargetNumber);

    // Parse and store maximumCoupResult and minimumFumbleResult
    const coupFumbleModifier = this.modifiers.filter((m) => m[0] === "c")[0];
    const cfmRgx = new RegExp("c([0-9]+)?(:([0-9]+))?");
    const cfmMatch = coupFumbleModifier?.match(cfmRgx);
    if (cfmMatch) {
      const parseMaximumCoupResult = cfmMatch[1];
      const parseMinimumFumbleResult = cfmMatch[3];
      this.maximumCoupResult = parseMaximumCoupResult
        ? parseInt(parseMaximumCoupResult)
        : DS4Check.DEFAULT_MAXIMUM_COUP_RESULT;
      this.minimumFumbleResult = parseMinimumFumbleResult
        ? parseInt(parseMinimumFumbleResult)
        : DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT;
      if (this.minimumFumbleResult <= this.maximumCoupResult)
        throw new SyntaxError(getGame().i18n.localize("DS4.ErrorDiceCoupFumbleOverlap"));
    }

    // Parse and store no fumble
    const noFumbleModifier = this.modifiers.filter((m) => m[0] === "n")[0];
    if (noFumbleModifier) {
      this.canFumble = false;
    }

    if (this.results.length > 0) {
      this.evaluateResults();
    }
  }

  /** @type {boolean | null} */
  coup = null;
  /** @type {boolean | null} */
  fumble = null;
  canFumble = true;
  checkTargetNumber = DS4Check.DEFAULT_CHECK_TARGET_NUMBER;
  minimumFumbleResult = DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT;
  maximumCoupResult = DS4Check.DEFAULT_MAXIMUM_COUP_RESULT;

  /** @override */
  get expression() {
    return `ds${this.modifiers.join("")}`;
  }

  /** @override */
  get total() {
    if (this.fumble) return 0;
    return super.total;
  }

  /** @override */
  _evaluateSync({ minimize = false, maximize = false } = {}) {
    super._evaluateSync({ minimize, maximize });
    this.evaluateResults();
    return this;
  }

  /** @override */
  roll({ minimize = false, maximize = false } = {}) {
    // Swap minimize / maximize because in DS4, the best possible roll is a 1 and the worst possible roll is a 20
    return super.roll({ minimize: maximize, maximize: minimize });
  }

  /**
   * Perform additional evaluation after the individual results have been evaluated.
   * @protected
   */
  evaluateResults() {
    const dice = this.results.map((die) => die.result);
    const results = evaluateCheck(dice, this.checkTargetNumber, {
      maximumCoupResult: this.maximumCoupResult,
      minimumFumbleResult: this.minimumFumbleResult,
      canFumble: this.canFumble,
    });
    this.results = results;
    this.coup = results[0]?.success ?? false;
    this.fumble = results[0]?.failure ?? false;
  }

  /**
   * @remarks "min" and "max" are filtered out because they are irrelevant for
   * {@link DS4Check}s and only result in some dice rolls being highlighted
   * incorrectly.
   * @override
   */
  getResultCSS(result) {
    return super.getResultCSS(result).filter((cssClass) => cssClass !== "min" && cssClass !== "max");
  }

  static DEFAULT_CHECK_TARGET_NUMBER = 10;
  static DEFAULT_MAXIMUM_COUP_RESULT = 1;
  static DEFAULT_MINIMUM_FUMBLE_RESULT = 20;
  static DENOMINATION = "s";
  static MODIFIERS = {
    c: () => undefined, // Modifier is consumed in constructor for maximumCoupResult / minimumFumbleResult
    v: () => undefined, // Modifier is consumed in constructor for checkTargetNumber
    n: () => undefined, // Modifier is consumed in constructor for canFumble
  };
}
