<?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.

namespace local_kid\local;

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

/**
 * Operational competency reference table, one row set per promotion.
 *
 * No physical delete while a competency is referenced anywhere (is_in_use())
 * — deleting it would orphan real evaluation/stage/CAPP data.
 */
class competency_repository {

    public static function get_for_promotion(int $promotionid): array {
        global $DB;
        return $DB->get_records('local_kid_competency', ['promotionid' => $promotionid], 'sortorder ASC');
    }

    public static function get_active_for_promotion(int $promotionid): array {
        global $DB;
        return $DB->get_records('local_kid_competency', ['promotionid' => $promotionid, 'active' => 1], 'sortorder ASC');
    }

    public static function get(int $id): ?\stdClass {
        global $DB;
        return $DB->get_record('local_kid_competency', ['id' => $id], '*', IGNORE_MISSING) ?: null;
    }

    /**
     * Single source of truth for a competency's human-readable label —
     * always the full libelle from the referentiel, never the codecourt
     * (C01, C02...) which is only a short machine-generated identifier.
     * Every screen, fiche DOCX/PDF, and export must call this instead of
     * reading ->libelle/->codecourt directly, so the short code can't leak
     * back into a "name" position by accident.
     *
     * codeadesi is a separate, unrelated technical identifier (ADESI
     * export/Moodle linkage) and is never part of this label.
     */
    public static function label(\stdClass $competency, bool $withcode = false): string {
        $label = format_string($competency->libelle);
        if ($withcode && !empty($competency->codecourt)) {
            $label .= ' (' . $competency->codecourt . ')';
        }
        return $label;
    }

    public static function create(int $promotionid, string $libelle, string $codeadesi, int $userid): int {
        global $DB;
        $now = time();
        $maxsort = (int) $DB->get_field_sql(
            'SELECT COALESCE(MAX(sortorder), 0) FROM {local_kid_competency} WHERE promotionid = ?', [$promotionid]);
        $codecourt = self::generate_next_codecourt($promotionid);
        return $DB->insert_record('local_kid_competency', (object) [
            'promotionid' => $promotionid,
            'coreid' => null,
            'codecourt' => $codecourt,
            'libelle' => $libelle,
            'codeadesi' => $codeadesi,
            'sortorder' => $maxsort + 1,
            'active' => 1,
            'timecreated' => $now,
            'timemodified' => $now,
            'usermodified' => $userid,
        ]);
    }

    public static function update(int $id, string $libelle, string $codeadesi, bool $active, int $userid): void {
        global $DB;
        $DB->update_record('local_kid_competency', (object) [
            'id' => $id,
            'libelle' => $libelle,
            'codeadesi' => $codeadesi,
            'active' => $active ? 1 : 0,
            'timemodified' => time(),
            'usermodified' => $userid,
        ]);
    }

    /**
     * True if the competency is referenced by any domain table — the guard
     * that blocks deletion (there is no deletion route for a referenced
     * competency; callers should just prevent showing a delete action, this
     * is the check they use to decide).
     */
    public static function is_in_use(int $id): bool {
        global $DB;
        foreach (['local_kid_eval_competency', 'local_kid_stage_competency',
                  'local_kid_eval_result', 'local_kid_stage_result', 'local_kid_capp_result'] as $table) {
            if ($DB->record_exists($table, ['competencyid' => $id])) {
                return true;
            }
        }
        return false;
    }

    public static function delete(int $id): bool {
        global $DB;
        if (self::is_in_use($id)) {
            return false;
        }
        $DB->delete_records('local_kid_competency', ['id' => $id]);
        return true;
    }

    /**
     * codecourt is always machine-generated by position (C01, C02, ...),
     * never copied from a Moodle idnumber — those can be full ADESI codes
     * (up to 26 chars) that would overflow the char(20) column. Derived from
     * the highest existing numeric suffix (not a row COUNT), so it stays
     * collision-free with the unique (promotionid, codecourt) index even
     * after a competency has been deleted.
     */
    private static function generate_next_codecourt(int $promotionid): string {
        global $DB;
        $existing = $DB->get_fieldset_select('local_kid_competency', 'codecourt', 'promotionid = ?', [$promotionid]);
        $max = 0;
        foreach ($existing as $codecourt) {
            if (preg_match('/^C(\d+)$/', $codecourt, $matches)) {
                $max = max($max, (int) $matches[1]);
            }
        }
        return 'C' . str_pad((string) ($max + 1), 2, '0', STR_PAD_LEFT);
    }

    /**
     * Syncs from a real core_competency framework. codeadesi is seeded from
     * the Moodle competency's idnumber ONLY at creation — never overwritten
     * on a later resync, so a manual admin correction is never clobbered.
     * Upserts by coreid, so existing ratings on already-synced competencies
     * stay attached to the same local_kid_competency row.
     *
     * @return array{added:int, updated:int}
     */
    public static function sync_from_framework(int $promotionid, int $frameworkid, int $userid): array {
        global $DB;
        require_once($GLOBALS['CFG']->dirroot . '/competency/classes/api.php');

        $competencies = \core_competency\api::list_competencies(['competencyframeworkid' => $frameworkid], 'sortorder');
        $existing = $DB->get_records('local_kid_competency', ['promotionid' => $promotionid], '', 'coreid, id');

        $added = 0;
        $updated = 0;
        foreach ($competencies as $competency) {
            $coreid = (int) $competency->get('id');
            $shortname = format_string($competency->get('shortname'));
            $idnumber = $competency->get('idnumber');

            if (isset($existing[$coreid])) {
                $DB->update_record('local_kid_competency', (object) [
                    'id' => $existing[$coreid]->id,
                    'libelle' => $shortname,
                    'timemodified' => time(),
                    'usermodified' => $userid,
                ]);
                $updated++;
            } else {
                $now = time();
                $DB->insert_record('local_kid_competency', (object) [
                    'promotionid' => $promotionid,
                    'coreid' => $coreid,
                    'codecourt' => self::generate_next_codecourt($promotionid),
                    'libelle' => $shortname,
                    'codeadesi' => $idnumber,
                    'sortorder' => (int) $competency->get('sortorder'),
                    'active' => 1,
                    'timecreated' => $now,
                    'timemodified' => $now,
                    'usermodified' => $userid,
                ]);
                $added++;
            }
        }

        $DB->update_record('local_kid_promotion', (object) [
            'id' => $promotionid,
            'competencyframeworkid' => $frameworkid,
            'timemodified' => time(),
            'usermodified' => $userid,
        ]);

        return ['added' => $added, 'updated' => $updated];
    }

    /**
     * @return array id => name, every core_competency framework Moodle has.
     */
    public static function get_all_frameworks(): array {
        require_once($GLOBALS['CFG']->dirroot . '/competency/classes/api.php');
        $frameworks = \core_competency\api::list_frameworks(
            'shortname', 'ASC', 0, 0, \context_system::instance(), 'children');
        $out = [];
        foreach ($frameworks as $framework) {
            $out[(int) $framework->get('id')] = format_string($framework->get('shortname'));
        }
        return $out;
    }
}
