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

import { getGame } from "../helpers";

export default function evaluateCheck(
    dice: number[],
    checkTargetNumber: number,
    {
        maximumCoupResult = 1,
        minimumFumbleResult = 20,
        canFumble = true,
    }: { maximumCoupResult?: number; minimumFumbleResult?: number; canFumble?: boolean } = {},
): SubCheckResult[] {
    const diceWithSubChecks = assignSubChecksToDice(dice, checkTargetNumber, {
        maximumCoupResult: maximumCoupResult,
    });
    return evaluateDiceWithSubChecks(diceWithSubChecks, {
        maximumCoupResult: maximumCoupResult,
        minimumFumbleResult: minimumFumbleResult,
        canFumble: canFumble,
    });
}

interface DieWithSubCheck {
    result: number;
    checkTargetNumber: number;
}

function assignSubChecksToDice(
    dice: number[],
    checkTargetNumber: number,
    {
        maximumCoupResult = 1,
    }: {
        maximumCoupResult?: number;
    } = {},
): DieWithSubCheck[] {
    const requiredNumberOfDice = getRequiredNumberOfDice(checkTargetNumber);

    if (dice.length !== requiredNumberOfDice || requiredNumberOfDice < 1) {
        throw new Error(getGame().i18n.localize("DS4.ErrorInvalidNumberOfDice"));
    }

    const checkTargetNumberForLastSubCheck = checkTargetNumber - 20 * (requiredNumberOfDice - 1);

    const indexOfSmallestNonCoup = findIndexOfSmallestNonCoup(dice, maximumCoupResult);
    const indexOfFirstCoup = dice.findIndex((die) => die <= maximumCoupResult);
    const indexForLastSubCheck = shouldUseCoupForLastSubCheck(
        indexOfSmallestNonCoup,
        indexOfFirstCoup,
        dice,
        checkTargetNumberForLastSubCheck,
    )
        ? indexOfFirstCoup
        : indexOfSmallestNonCoup;

    return dice.map((die, index) => ({
        result: die,
        checkTargetNumber: index === indexForLastSubCheck ? checkTargetNumberForLastSubCheck : 20,
    }));
}

function findIndexOfSmallestNonCoup(dice: number[], maximumCoupResult: number): number {
    return dice
        .map((die, index) => [die, index])
        .filter((indexedDie) => indexedDie[0] > maximumCoupResult)
        .reduce(
            (smallestIndexedDie, indexedDie) =>
                indexedDie[0] < smallestIndexedDie[0] ? indexedDie : smallestIndexedDie,
            [Infinity, -1],
        )[1];
}

function shouldUseCoupForLastSubCheck(
    indexOfSmallestNonCoup: number,
    indexOfFirstCoup: number,
    dice: number[],
    checkTargetNumberForLastSubCheck: number,
) {
    return (
        indexOfFirstCoup !== -1 &&
        (indexOfSmallestNonCoup === -1 ||
            (dice[indexOfSmallestNonCoup] > checkTargetNumberForLastSubCheck &&
                dice[indexOfSmallestNonCoup] + checkTargetNumberForLastSubCheck > 20))
    );
}

interface SubCheckResult extends DieWithSubCheck, DiceTerm.Result {}

function evaluateDiceWithSubChecks(
    results: DieWithSubCheck[],
    {
        maximumCoupResult,
        minimumFumbleResult,
        canFumble,
    }: { maximumCoupResult: number; minimumFumbleResult: number; canFumble: boolean },
): SubCheckResult[] {
    return results.map((dieWithSubCheck, index) => {
        const result: SubCheckResult = {
            ...dieWithSubCheck,
            active: dieWithSubCheck.result <= dieWithSubCheck.checkTargetNumber,
            discarded: dieWithSubCheck.result > dieWithSubCheck.checkTargetNumber,
        };
        if (result.result <= maximumCoupResult) {
            result.success = true;
            result.count = result.checkTargetNumber;
            result.active = true;
            result.discarded = false;
        }
        if (index === 0 && canFumble && result.result >= minimumFumbleResult) result.failure = true;
        return result;
    });
}

export function getRequiredNumberOfDice(checkTargetNumber: number): number {
    return Math.max(Math.ceil(checkTargetNumber / 20), 1);
}