/**
 * Il credito è mezza giornata d'aula: 4 ore fino a 30 discenti.
 * Stessa formula del backend (`CreditService::costFor`,
 * `config('easyform.credit_hours_per_credit')` e
 * `config('easyform.credit_participants_band')`):
 * crediti = ceil(ore / ore_per_credito) × ceil(discenti / fascia_discenti).
 */
export const DEFAULT_HOURS_PER_CREDIT = 4;
export const DEFAULT_PARTICIPANTS_BAND = 30;

/** Le due costanti del listino, come arrivano dalle props condivise Inertia. */
export type CreditRule = {
    hoursPerCredit?: number;
    participantsBand?: number;
};

/** Il minimo che serve a `recommendPlan` per ragionare su un pacchetto. */
export type CreditPackage = {
    name: string;
    slug: string;
    credits: number;
    price_cents: number;
};

function positive(value: number | undefined, fallback: number): number {
    return typeof value === 'number' && Number.isFinite(value) && value > 0
        ? value
        : fallback;
}

/** Costo in crediti di un'aula da `participants` discenti per `hours` ore. */
/** Above this many top-ups a month the calculator stops quoting and points to a custom contract. */
export const MAX_TOPUPS_PER_MONTH = 3;

export function creditCost(
    participants: number,
    hours: number,
    { hoursPerCredit, participantsBand }: CreditRule = {},
): number {
    const perCredit = positive(hoursPerCredit, DEFAULT_HOURS_PER_CREDIT);
    const band = positive(participantsBand, DEFAULT_PARTICIPANTS_BAND);

    if (
        !Number.isFinite(participants) ||
        !Number.isFinite(hours) ||
        participants <= 0 ||
        hours <= 0
    ) {
        return 0;
    }

    return Math.ceil(hours / perCredit) * Math.ceil(participants / band);
}

/** Ore d'aula comprate da N crediti: il rovescio della formula, per la vetrina. */
export function classroomHours(
    credits: number,
    hoursPerCredit: number = DEFAULT_HOURS_PER_CREDIT,
): number {
    if (!Number.isFinite(credits) || credits <= 0) {
        return 0;
    }

    return credits * positive(hoursPerCredit, DEFAULT_HOURS_PER_CREDIT);
}

/**
 * Legge un campo numerico di un form, restituendo 0 per vuoto o non numerico.
 *
 * Il valore è arrotondato per eccesso: corsi, ore e discenti sono unità intere, e il listino
 * arrotonda già per eccesso ovunque. Gli input `type="number"` hanno `step` 1 ma il browser
 * accetta comunque un "2,5" digitato a mano, e senza questo finirebbe in "2.5 crediti".
 */
export function parseCount(value: string | number | null | undefined): number {
    const parsed = typeof value === 'number' ? value : Number(value);

    return Number.isFinite(parsed) && parsed > 0 ? Math.ceil(parsed) : 0;
}

/** "1 credito" / "40 crediti". Tollera valori mancanti dal backend. */
export function creditsLabel(amount: number | null | undefined): string {
    const value = Number.isFinite(amount) ? (amount as number) : 0;

    return value === 1 ? '1 credito' : `${value} crediti`;
}

/** "1 ora d'aula" / "40 ore d'aula". */
export function classroomHoursLabel(hours: number): string {
    const value = Number.isFinite(hours) && hours > 0 ? hours : 0;

    return value === 1 ? "1 ora d'aula" : `${value} ore d'aula`;
}

/** Il piano (più eventuali ricariche) che copre il fabbisogno mensile. */
export type PlanRecommendation<T extends CreditPackage = CreditPackage> = {
    plan: T;
    /** Ricarica scelta per coprire lo scoperto, `null` se il piano basta. */
    topup: T | null;
    /** Quante ricariche servono ogni mese. */
    topupCount: number;
    /** Crediti coperti dalla combinazione: può restare sotto al fabbisogno. */
    creditsCovered: number;
    /** Il piano da solo non basta e nessuna ricarica lo colma. */
    short: boolean;
    /** Spesa mensile in centesimi, piano più ricariche. */
    monthlyCents: number;
    /** Spesa per corso in centesimi, `null` se i corsi al mese sono zero. */
    perCourseCents: number | null;
};

function usable<T extends CreditPackage>(packages: readonly T[]): T[] {
    return packages.filter(
        (pkg) =>
            Number.isFinite(pkg.credits) &&
            pkg.credits > 0 &&
            Number.isFinite(pkg.price_cents) &&
            pkg.price_cents >= 0,
    );
}

/**
 * Il piano più economico che copre `creditsNeeded`. Se nessuno basta, il piano
 * più capiente più le ricariche che colmano lo scoperto, nella combinazione
 * che costa meno: è la domanda vera del visitatore ("quanto pago al mese"),
 * non quanti crediti consuma.
 */
export function recommendPlan<T extends CreditPackage>(
    creditsNeeded: number,
    plans: readonly T[],
    topups: readonly T[] = [],
    coursesPerMonth = 0,
): PlanRecommendation<T> | null {
    const candidates = usable(plans);

    if (candidates.length === 0) {
        return null;
    }

    const need =
        Number.isFinite(creditsNeeded) && creditsNeeded > 0 ? creditsNeeded : 0;

    const withCost = (
        plan: T,
        topup: T | null,
        topupCount: number,
    ): PlanRecommendation<T> => {
        const monthlyCents =
            plan.price_cents + topupCount * (topup?.price_cents ?? 0);
        const creditsCovered =
            plan.credits + topupCount * (topup?.credits ?? 0);

        return {
            plan,
            topup: topupCount > 0 ? topup : null,
            topupCount,
            creditsCovered,
            short: creditsCovered < need,
            monthlyCents,
            perCourseCents:
                coursesPerMonth > 0
                    ? Math.round(monthlyCents / coursesPerMonth)
                    : null,
        };
    };

    const covering = candidates
        .filter((plan) => plan.credits >= need)
        .sort((a, b) => a.price_cents - b.price_cents);

    if (covering.length > 0) {
        return withCost(covering[0], null, 0);
    }

    const largest = candidates.reduce((best, plan) =>
        plan.credits > best.credits ? plan : best,
    );
    const missing = need - largest.credits;

    let best: PlanRecommendation<T> | null = null;

    for (const topup of usable(topups)) {
        const count = Math.ceil(missing / topup.credits);

        // Beyond a handful of top-ups a month the customer needs a custom contract, not a
        // quote: leave the recommendation short so the page shows the warning instead.
        if (count > MAX_TOPUPS_PER_MONTH) {
            continue;
        }

        const option = withCost(largest, topup, count);

        if (best === null || option.monthlyCents < best.monthlyCents) {
            best = option;
        }
    }

    return best ?? withCost(largest, null, 0);
}
