<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

/**
 * Builds the DTO for the CAPP session "livret" PDF (page de garde +
 * Bilan général optionnel + une fiche par élève sélectionné) —
 * pages/capp_rapport.php's only job is to call build() and hand the result
 * to pdf_service::build_capp_rapport(). Distinct from fiche_builder (one
 * élève, tous domaines) : ce builder couvre un seul domaine (CAPP) mais
 * potentiellement toute une cohorte en un seul document.
 *
 * @package    local_kid
 */

namespace local_kid\local;

defined('MOODLE_INTERNAL') || die();

class capp_rapport_builder {

    /**
     * Mode is derived here from the selection, never trusted from the
     * caller: cohorte (Bilan général inclus) s'applique quand aucune
     * sélection n'est donnée (Surface A : export de toute la session) ou
     * que la sélection couvre exactement tous les dossiers de la session
     * (Surface B : "tout sélectionner") ; tout sous-ensemble strict est
     * individuel (pas de Bilan général).
     *
     * @param int[] $requestedstudentids raw selection from the caller, may
     *        be empty.
     * @return array{mode:string, studentids:int[]} authoritative selection,
     *         checked against bulk_action_service::get_entries() exactly
     *         comme toute autre action groupée de cet écran — un id hors de
     *         cet ensemble est silencieusement écarté, jamais fait confiance
     *         tel quel.
     */
    public static function resolve_selection(int $sessionid, array $requestedstudentids): array {
        $entries = bulk_action_service::get_entries('capp', $sessionid);
        $allstudentids = array_keys($entries);

        if (empty($requestedstudentids)) {
            return ['mode' => 'cohorte', 'studentids' => $allstudentids];
        }

        $selected = array_values(array_intersect(array_map('intval', $requestedstudentids), $allstudentids));
        if (empty($selected)) {
            throw new \moodle_exception('error_no_active_students', 'local_kid');
        }

        $mode = count($selected) === count($allstudentids) ? 'cohorte' : 'individuel';
        return ['mode' => $mode, 'studentids' => $selected];
    }

    /**
     * The Bilan général count table alone (competency x niveau-bucket
     * counts across every entry in the session), with none of the
     * per-student fiche building build() does for the full livret — used
     * by the "Bilan général" tab of the CAPP récapitulatif screen
     * (capp_grid_page) to show on-screen exactly the same table as the
     * PDF's, computed via the same build_bilan() so the two can never
     * drift apart.
     */
    public static function build_bilan_table(int $sessionid): \stdClass {
        $session = capp_repository::get_session($sessionid);
        if (!$session) {
            throw new \moodle_exception('invalidrecord', 'error');
        }

        $competencies = competency_repository::get_active_for_promotion((int) $session->promotionid);

        $niveaux = [];
        foreach (niveau_repository::get_all() as $niveau) {
            $niveaux[$niveau->id] = $niveau;
        }

        $entries = capp_repository::get_entries_for_session($sessionid);
        $currentresultsbyentryid = [];
        foreach ($entries as $entry) {
            $currentresultsbyentryid[$entry->id] = capp_repository::get_results((int) $entry->id);
        }

        return self::build_bilan($competencies, $niveaux, $currentresultsbyentryid, count($entries));
    }

    /**
     * @param int[] $requestedstudentids see resolve_selection().
     */
    public static function build(int $sessionid, array $requestedstudentids): \stdClass {
        global $DB;

        $session = capp_repository::get_session($sessionid);
        if (!$session) {
            throw new \moodle_exception('invalidrecord', 'error');
        }
        $promotion = promotion_repository::get((int) $session->promotionid);
        $previoussession = capp_repository::get_previous_session($sessionid);

        $selection = self::resolve_selection($sessionid, $requestedstudentids);
        $studentidset = array_flip($selection['studentids']);

        $competencies = competency_repository::get_active_for_promotion((int) $session->promotionid);

        $niveaux = [];
        foreach (niveau_repository::get_all() as $niveau) {
            $niveaux[$niveau->id] = $niveau;
        }

        $entries = capp_repository::get_entries_for_session($sessionid);

        $previousentriesbystudentid = [];
        if ($previoussession) {
            foreach (capp_repository::get_entries_for_session((int) $previoussession->id) as $preventry) {
                $previousentriesbystudentid[(int) $preventry->studentid] = $preventry;
            }
        }

        // Bulk user fetch (avoids one query per student for fullname()).
        $userids = array_values(array_unique(array_map(fn($e) => (int) $e->userid, $entries)));
        $users = $userids ? $DB->get_records_list('user', 'id', $userids) : [];

        $students = [];
        $currentresultsbyentryid = [];
        foreach ($entries as $entry) {
            if (!isset($studentidset[(int) $entry->studentid])) {
                continue;
            }
            $currentresults = capp_repository::get_results((int) $entry->id);
            $currentresultsbyentryid[$entry->id] = $currentresults;

            $previousresults = [];
            $preventry = $previousentriesbystudentid[(int) $entry->studentid] ?? null;
            if ($preventry) {
                $previousresults = capp_repository::get_results((int) $preventry->id);
            }

            $user = $users[$entry->userid] ?? (object) ['firstname' => $entry->firstname, 'lastname' => $entry->lastname];
            $students[] = self::build_student_fiche($entry, $user, $competencies, $niveaux, $currentresults, $previousresults);
        }

        $bilan = null;
        if ($selection['mode'] === 'cohorte') {
            $bilan = self::build_bilan($competencies, $niveaux, $currentresultsbyentryid, count($students));
        }

        return (object) [
            'promotion' => (object) ['name' => $promotion ? format_string($promotion->name) : null],
            'session' => (object) ['code' => format_string($session->code), 'libelle' => format_string($session->libelle)],
            'previoussessioncode' => $previoussession ? format_string($previoussession->code) : null,
            'mode' => $selection['mode'],
            'generatedat' => time(),
            'bilan' => $bilan,
            'students' => $students,
        ];
    }

    private static function build_student_fiche(
        \stdClass $entry,
        \stdClass $user,
        array $competencies,
        array $niveaux,
        array $currentresults,
        array $previousresults
    ): \stdClass {
        $cells = [];
        foreach ($competencies as $competency) {
            $currentniveau = self::resolve_niveau($currentresults[$competency->id] ?? null, $niveaux);
            $previousniveau = self::resolve_niveau($previousresults[$competency->id] ?? null, $niveaux);
            $cells[] = (object) [
                'code' => $competency->codecourt,
                'libelle' => competency_repository::label($competency),
                'niveaucapp1' => $previousniveau->libelle ?? null,
                'niveaucode1' => $previousniveau->code ?? null,
                'niveaucapp2' => $currentniveau->libelle ?? null,
                'niveaucode2' => $currentniveau->code ?? null,
            ];
        }

        return (object) [
            'displayname' => strtoupper(trim($user->lastname)) . ' ' . trim($user->firstname),
            'codeena' => codeena_resolver::resolve((int) $entry->userid),
            'competences' => $cells,
            'appreciationglobale' => $entry->appreciationglobale,
        ];
    }

    /**
     * @param \stdClass[] $results keyed by competencyid (niveauid/commentaire).
     */
    private static function resolve_niveau(?\stdClass $result, array $niveaux): ?\stdClass {
        $niveauid = $result->niveauid ?? null;
        return ($niveauid && isset($niveaux[$niveauid])) ? $niveaux[$niveauid] : null;
    }

    /**
     * @param array<int, \stdClass[]> $currentresultsbyentryid entryid =>
     *        (competencyid => result), scoped to exactly the entries this
     *        Bilan covers (always the whole session in cohorte mode).
     */
    private static function build_bilan(array $competencies, array $niveaux, array $currentresultsbyentryid, int $studentcount): \stdClass {
        $rows = [];
        foreach ($competencies as $competency) {
            $counts = ['nonobserve' => 0, 'nonacquis' => 0, 'encours' => 0, 'acquisdla' => 0];
            foreach ($currentresultsbyentryid as $results) {
                $niveau = self::resolve_niveau($results[$competency->id] ?? null, $niveaux);
                $bucket = niveau_repository::display_bucket($niveau->code ?? null);
                $counts[$bucket]++;
            }
            $rows[] = (object) array_merge(['libelle' => competency_repository::label($competency)], $counts);
        }
        return (object) ['studentcount' => $studentcount, 'rows' => $rows];
    }
}
