// 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.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Guards every outbound navigation from a "dirty" (grid form has unsaved
 * changes) Évaluations/Stages/CAPP fiche — shared by the three domains, one
 * module, never duplicated. `guardIfDirty()` is the single entry point:
 * amd/src/student_nav.js calls it directly (see its own doc comment) for
 * the previous/next student and the selector+"Afficher" exits (both
 * normally an in-place AJAX swap); this module binds the other two exits
 * itself — "Retour au récapitulatif" and every sidebar nav item, both plain
 * links marked [data-kid-guard-nav] in their templates — since neither is
 * ever replaced by an AJAX swap and so needs no per-navigation re-binding.
 *
 * Dirty detection reuses Moodle core's own `core_form/changechecker`
 * (field-level change tracking + the native beforeunload warning as a
 * best-effort filet on tab close/refresh) instead of re-implementing change
 * detection — this plugin never used it before. The confirmation popup
 * reuses `core/modal` (base class): its focus trap, Escape-closes,
 * backdrop-click-closes and return-focus-to-trigger are all built in, which
 * the plugin's only pre-existing modal-like pattern (the Comparateur's
 * hand-rolled drawer, amd/src/comparateur.js) does not have.
 *
 * "Enregistrer et continuer" does a real submit of the grid form (not a new
 * AJAX save endpoint): it points the form's `returnurl` hidden field at the
 * exit's destination and calls form.requestSubmit(), so the existing
 * POST -> save/detect-conflict -> redirect path in evaluations.php/
 * stages.php/capp.php (unchanged) decides where to go next.
 *
 * @module     local_kid/unsaved_guard
 * @copyright  2026 INSP
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
import Modal from 'core/modal';
import {get_strings as getStrings} from 'core/str';
import {watchForm, unWatchForm, isAnyWatchedFormDirty} from 'core_form/changechecker';

const SELECTORS = {
    REGION: '[data-region="kid-grid-student"]',
    GUARD_LINK: '[data-kid-guard-nav]',
};

let gridForm = null;

const isDirty = () => Boolean(gridForm) && isAnyWatchedFormDirty();

/**
 * @param {String} targeturl where to go if the user discards or saves first
 * @param {Function} onDiscard performs the actual navigation for "Continuer
 *   sans enregistrer" — a full page load for a plain link, or the existing
 *   AJAX fragment swap for the student-nav controls
 * @param {Element} originElement receives focus back when the popup closes
 *   without navigating ("Revenir à la fiche" / Escape / backdrop click)
 */
const openUnsavedModal = (targeturl, onDiscard, originElement) => {
    const stringkeys = ['guard_modal_title', 'guard_modal_body', 'guard_btn_save', 'guard_btn_back', 'guard_btn_discard']
        .map((key) => ({key, component: 'local_kid'}));

    getStrings(stringkeys).then(([title, body, btnsave, btnback, btndiscard]) => Modal.create({
        title,
        body: `<p>${body}</p>`,
        footer: `
            <button type="button" class="btn btn-primary" data-action="kid-save-continue">${btnsave}</button>
            <button type="button" class="btn btn-secondary" data-action="hide">${btnback}</button>
            <button type="button" class="btn btn-outline-danger" data-action="kid-discard">${btndiscard}</button>
        `,
        show: true,
        removeOnClose: true,
        returnElement: originElement,
    })).then((modal) => {
        modal.getRoot().on('click', '[data-action="kid-save-continue"]', () => {
            const returnurlfield = gridForm.querySelector('[name="returnurl"]');
            if (returnurlfield) {
                returnurlfield.value = targeturl;
            }
            modal.hide();
            gridForm.requestSubmit();
        });
        modal.getRoot().on('click', '[data-action="kid-discard"]', () => {
            modal.hide();
            onDiscard();
        });
        return modal;
    }).catch(() => {
        // Strings/modal failed to load — fail safe by letting the
        // navigation through rather than trapping the user on the page.
        onDiscard();
    });
};

/**
 * Runs onDiscard() straight away when the fiche is clean; otherwise opens
 * the confirmation popup. The one function every guarded exit calls.
 *
 * @param {String} targeturl
 * @param {Function} onDiscard
 * @param {Element} originElement
 */
export const guardIfDirty = (targeturl, onDiscard, originElement) => {
    if (!isDirty()) {
        onDiscard();
        return;
    }
    openUnsavedModal(targeturl, onDiscard, originElement);
};

/**
 * Guards every plain link explicitly marked [data-kid-guard-nav] — "Retour
 * au récapitulatif" and the sidebar's nav items — with a single delegated
 * listener.
 */
const bindGuardedLinks = () => {
    document.addEventListener('click', (e) => {
        const link = e.target.closest(SELECTORS.GUARD_LINK);
        if (!link) {
            return;
        }
        e.preventDefault();
        const url = link.getAttribute('href');
        guardIfDirty(url, () => {
            window.location.href = url;
        }, link);
    });
};

/**
 * (Re-)finds the grid form inside the given region and starts tracking it,
 * dropping the previous one first — needed both at page load and after
 * every AJAX student switch, since amd/src/student_nav.js replaces the
 * region's entire innerHTML (and therefore the form element) on each swap.
 * Exported so student_nav.js's own bindRegion() — already re-run after
 * every swap — can re-arm the guard on the fresh form in the same pass,
 * instead of this module needing its own swap-lifecycle hook.
 *
 * @param {Element} region [data-region="kid-grid-student"]
 */
export const rewatch = (region) => {
    if (gridForm) {
        unWatchForm(gridForm);
    }
    // Not a plain 'form' selector: the region also contains the
    // student-nav mini-form (the "Élève :" selector + Afficher button,
    // amd/src/student_nav.js), which comes first in document order and
    // would otherwise be picked up instead of the actual results grid.
    gridForm = region.querySelector('[data-region="kid-entry-form"] form');
    if (gridForm) {
        watchForm(gridForm);
    }
};

/**
 * Entry point, called once per page load by evaluations.php/stages.php/
 * capp.php whenever a student's grid is shown (readonly or not — a
 * hardFrozen form never fires change events, so isDirty() naturally stays
 * false and the guard is a harmless no-op there). Must run before
 * local_kid/student_nav's own init() so gridForm is set before any
 * student-nav click can call guardIfDirty() — both modules are required
 * together on every grid page, in that order (see the three controllers).
 */
export const init = () => {
    const region = document.querySelector(SELECTORS.REGION);
    if (!region) {
        return;
    }
    rewatch(region);
    bindGuardedLinks();
};
