<?php

declare(strict_types=1);

namespace App\Scolarite\Domain\Service;

use App\Scolarite\Domain\Model\Evaluation\ClassStatistics;

use function array_sum;
use function count;
use function intdiv;
use function round;
use function sort;

final class AverageCalculator
{
    /**
     * Moyenne matière pondérée par les coefficients, normalisée sur /20.
     * Les notes absent/dispensé doivent être exclues en amont.
     *
     * Formule : Σ(note_sur_20 × coef) / Σ(coef)
     *
     * @param list<GradeEntry> $grades
     */
    public function calculateSubjectAverage(array $grades): ?float
    {
        if ($grades === []) {
            return null;
        }

        $sumWeighted = 0.0;
        $sumCoef = 0.0;

        foreach ($grades as $entry) {
            $normalizedValue = $entry->gradeScale->convertTo20($entry->value);
            $sumWeighted += $normalizedValue * $entry->coefficient->value;
            $sumCoef += $entry->coefficient->value;
        }

        return round($sumWeighted / $sumCoef, 2);
    }

    /**
     * Moyenne générale : moyenne arithmétique des moyennes matières.
     * Les matières sans note sont exclues en amont.
     *
     * @param list<float> $subjectAverages
     */
    public function calculateGeneralAverage(array $subjectAverages): ?float
    {
        if ($subjectAverages === []) {
            return null;
        }

        return round(array_sum($subjectAverages) / count($subjectAverages), 2);
    }

    /**
     * Statistiques de classe pour une évaluation : min, max, moyenne, médiane.
     * Les absents et dispensés doivent être exclus en amont.
     *
     * @param list<float> $values
     */
    public function calculateClassStatistics(array $values): ClassStatistics
    {
        if ($values === []) {
            return new ClassStatistics(
                average: null,
                min: null,
                max: null,
                median: null,
                gradedCount: 0,
            );
        }

        sort($values);
        $count = count($values);

        return new ClassStatistics(
            average: round(array_sum($values) / $count, 2),
            min: $values[0],
            max: $values[$count - 1],
            median: $this->calculateMedian($values),
            gradedCount: $count,
        );
    }

    /**
     * @param list<float> $sortedValues Valeurs déjà triées par ordre croissant
     */
    private function calculateMedian(array $sortedValues): float
    {
        $count = count($sortedValues);
        $middle = intdiv($count, 2);

        if ($count % 2 === 0) {
            return round(($sortedValues[$middle - 1] + $sortedValues[$middle]) / 2, 2);
        }

        return $sortedValues[$middle];
    }
}
