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

/**
 * Comparateur de bilans: detail drawer (pure client-side, data already in
 * the DOM via data-* attributes on each cell — no server round-trip), the
 * three checklist filters' live text search + select-all/none, the level
 * chips' pressed-state toggle, dashboard favoris (save/rename/delete over
 * AJAX, load by building a URL from the stored config and navigating — no
 * AJAX for "load"), and the results area (toolbar + student cards) rebuild:
 * every filter change re-renders it via local_kid_get_comparateur_results
 * (debounced), so no "Filtrer" click or full page reload is needed.
 *
 * @module     local_kid/comparateur
 * @copyright  2026 INSP
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
import Ajax from 'core/ajax';
import Notification from 'core/notification';
import {get_strings as getStrings} from 'core/str';

const SELECTORS = {
    ROOT: '.local-kid-comparateur',
    FORM: '#kid-comparateur-form',
    RESULTS: '[data-region="kid-comparateur-results"]',
    CELL: '.kid-comparateur__cell--clickable',
    DRAWER: '#kid-comparateur-drawer',
    SCRIM: '#kid-comparateur-scrim',
    DRAWER_BODY: '#kid-comparateur-drawer-body',
    DRAWER_TITLE: '#kid-comparateur-drawer-title',
    DRAWER_CLOSE: '#kid-comparateur-drawer-close',
    SEARCH: '.kid-comparateur__search',
    OPTLIST: '.kid-comparateur__optlist',
    OPT: '.kid-comparateur__opt',
    CHECKALL: '[data-checkall]',
    CHECKNONE: '[data-checknone]',
    CHIPINPUT: '.kid-comparateur__chipinput',
    FAVSAVE: '#kid-comparateur-favsave',
    FAVNAME: '#kid-comparateur-favname',
    FAVLIST: '#kid-comparateur-favlist',
    FAVITEM: '.kid-comparateur__favitem',
    FAVLOAD: '.kid-comparateur__favload',
    FAVICON: '.kid-comparateur__favicon',
};

/**
 * @param {Element} row
 * @param {String} label
 * @param {String} value
 */
const addKv = (row, label, value) => {
    const wrap = document.createElement('div');
    wrap.className = 'kv';
    const k = document.createElement('div');
    k.className = 'k';
    k.textContent = label;
    const v = document.createElement('div');
    v.className = 'v';
    v.textContent = value;
    wrap.append(k, v);
    row.appendChild(wrap);
};

const initDrawer = (root) => {
    const drawer = root.querySelector(SELECTORS.DRAWER);
    const scrim = root.querySelector(SELECTORS.SCRIM);
    if (!drawer || !scrim) {
        return;
    }
    const body = drawer.querySelector(SELECTORS.DRAWER_BODY);
    const title = drawer.querySelector(SELECTORS.DRAWER_TITLE);

    const close = () => {
        drawer.classList.remove('show');
        scrim.classList.remove('show');
        drawer.setAttribute('aria-hidden', 'true');
        drawer.inert = true;
    };

    const stringkeys = ['comparateur_drawer_competency', 'comparateur_drawer_source',
        'comparateur_drawer_niveau', 'comparateur_drawer_comment', 'comparateur_drawer_saisie']
        .map((key) => ({key, component: 'local_kid'}));

    getStrings(stringkeys).then(([lcompetency, lsource, lniveau, lcomment, lsaisie]) => {
        root.addEventListener('click', (event) => {
            const cell = event.target.closest(SELECTORS.CELL);
            if (!cell) {
                return;
            }
            const data = cell.dataset;
            title.textContent = data.drawerStudent || '';
            body.innerHTML = '';
            addKv(body, lcompetency, data.drawerCompetency || '');
            addKv(body, lsource, `${data.drawerSource} (${data.drawerSourcetype})`);

            const niveauRow = document.createElement('div');
            niveauRow.className = 'kv';
            const k = document.createElement('div');
            k.className = 'k';
            k.textContent = lniveau;
            const v = document.createElement('div');
            v.className = 'v';
            const badge = document.createElement('span');
            badge.className = 'fr-badge kid-level-badge ' + (data.drawerNiveauclass || '');
            badge.textContent = data.drawerNiveau || '';
            v.appendChild(badge);
            niveauRow.append(k, v);
            body.appendChild(niveauRow);

            if (data.drawerComment) {
                const commentwrap = document.createElement('div');
                commentwrap.className = 'kv';
                const ck = document.createElement('div');
                ck.className = 'k';
                ck.textContent = lcomment;
                const commentbody = document.createElement('div');
                commentbody.className = 'cmt-full';
                commentbody.textContent = data.drawerComment;
                commentwrap.append(ck, commentbody);
                body.appendChild(commentwrap);
            }

            const linkrow = document.createElement('div');
            linkrow.className = 'kv';
            const link = document.createElement('a');
            link.href = data.drawerSaisieurl;
            link.className = 'kid-comparateur__srclink';
            link.textContent = lsaisie;
            linkrow.appendChild(link);
            body.appendChild(linkrow);

            drawer.classList.add('show');
            scrim.classList.add('show');
            drawer.setAttribute('aria-hidden', 'false');
            drawer.inert = false;
        });
    }).catch(Notification.exception);

    scrim.addEventListener('click', close);
    drawer.querySelector(SELECTORS.DRAWER_CLOSE)?.addEventListener('click', close);
    document.addEventListener('keydown', (event) => {
        if (event.key === 'Escape') {
            close();
        }
    });
};

/**
 * @param {Element} root
 * @param {Function} [onselectionchange] called after a bulk check-all/none,
 *   since programmatically setting .checked does not fire a native 'change'
 *   event the live-filter listener could otherwise rely on
 */
const initChecklists = (root, onselectionchange) => {
    root.querySelectorAll(SELECTORS.SEARCH).forEach((input) => {
        const target = input.dataset.filterTarget;
        const list = root.querySelector(`${SELECTORS.OPTLIST}[data-optlist="${target}"]`);
        if (!list) {
            return;
        }
        input.addEventListener('input', () => {
            const needle = input.value.trim().toLowerCase();
            list.querySelectorAll(SELECTORS.OPT).forEach((opt) => {
                opt.style.display = !needle || opt.textContent.toLowerCase().includes(needle) ? '' : 'none';
            });
        });
    });

    root.querySelectorAll(SELECTORS.CHECKALL).forEach((btn) => {
        const list = root.querySelector(`${SELECTORS.OPTLIST}[data-optlist="${btn.dataset.checkall}"]`);
        btn.addEventListener('click', () => {
            list?.querySelectorAll('input[type=checkbox]').forEach((cb) => {
                cb.checked = true;
            });
            onselectionchange?.();
        });
    });
    root.querySelectorAll(SELECTORS.CHECKNONE).forEach((btn) => {
        const list = root.querySelector(`${SELECTORS.OPTLIST}[data-optlist="${btn.dataset.checknone}"]`);
        btn.addEventListener('click', () => {
            list?.querySelectorAll('input[type=checkbox]').forEach((cb) => {
                cb.checked = false;
            });
            onselectionchange?.();
        });
    });

    root.querySelectorAll(SELECTORS.CHIPINPUT).forEach((input) => {
        input.addEventListener('change', () => {
            input.closest('.kid-fchip')?.classList.toggle('kid-fchip--pressed', input.checked);
        });
    });
};

/**
 * @param {Element} form
 * @param {String} name
 * @returns {String[]}
 */
const checkedValues = (form, name) => Array.from(form.querySelectorAll(`[name="${name}"]:checked`)).map((el) => el.value);

/**
 * @param {Element} form
 * @returns {Object}
 */
const captureConfig = (form) => ({
    promotionid: form.querySelector('[name=promotionid]')?.value || '',
    studentids: checkedValues(form, 'studentids[]'),
    sourcekeys: checkedValues(form, 'sourcekeys[]'),
    competencyids: checkedValues(form, 'competencyids[]'),
    niveaucodes: checkedValues(form, 'niveaucodes[]'),
    showcomments: form.querySelector('[name=showcomments]')?.checked ? 1 : 0,
    onlyobserved: form.querySelector('[name=onlyobserved]')?.checked ? 1 : 0,
});

/**
 * @param {Object} config as produced by captureConfig()
 * @returns {String}
 */
const buildUrlFromConfig = (config) => {
    const params = new URLSearchParams();
    params.set('tab', 'comparateur');
    if (config.promotionid) {
        params.set('promotionid', config.promotionid);
    }
    (config.studentids || []).forEach((v) => params.append('studentids[]', v));
    (config.sourcekeys || []).forEach((v) => params.append('sourcekeys[]', v));
    (config.competencyids || []).forEach((v) => params.append('competencyids[]', v));
    (config.niveaucodes || []).forEach((v) => params.append('niveaucodes[]', v));
    params.set('showcomments', config.showcomments ? '1' : '0');
    params.set('onlyobserved', config.onlyobserved ? '1' : '0');
    return M.cfg.wwwroot + '/local/kid/pages/dashboard.php?' + params.toString();
};

/**
 * Wires the filter form so the results area (toolbar + student cards)
 * rebuilds live via AJAX as soon as a checkbox/chip/switch changes —
 * debounced so a burst of rapid changes (e.g. "select all") only fires one
 * request, and stale responses from a superseded request are dropped. If
 * the results region is missing (markup mismatch) this is a no-op and the
 * form keeps working as a classic full-page GET submit.
 *
 * @param {Element} root
 * @returns {Function} scheduleRefresh, also called by initChecklists() after
 *   a programmatic bulk selection change (check-all/none don't fire a
 *   native 'change' event on their own)
 */
const initLiveFilter = (root) => {
    const form = root.querySelector(SELECTORS.FORM);
    const results = root.querySelector(SELECTORS.RESULTS);
    if (!form || !results) {
        return () => { /* Nothing to live-refresh; form falls back to a normal GET submit. */ };
    }

    let timer = null;
    let seq = 0;

    const refresh = () => {
        const mine = ++seq;
        const config = captureConfig(form);
        results.setAttribute('aria-busy', 'true');
        Ajax.call([{
            methodname: 'local_kid_get_comparateur_results',
            args: {
                promotionid: parseInt(config.promotionid, 10) || 0,
                studentids: (config.studentids || []).map((v) => parseInt(v, 10)),
                sourcekeys: config.sourcekeys || [],
                competencyids: (config.competencyids || []).map((v) => parseInt(v, 10)),
                niveaucodes: config.niveaucodes || [],
                onlyobserved: Boolean(config.onlyobserved),
                showcomments: Boolean(config.showcomments),
            },
        }])[0]
        .then((data) => {
            // Drop stale responses from a request a newer one has since superseded.
            if (mine === seq) {
                results.innerHTML = data.html;
            }
            return data;
        })
        .catch(Notification.exception)
        .always(() => {
            if (mine === seq) {
                results.removeAttribute('aria-busy');
            }
        });
    };

    const scheduleRefresh = () => {
        window.clearTimeout(timer);
        timer = window.setTimeout(refresh, 250);
    };

    form.addEventListener('change', (event) => {
        if (event.target.matches('input[type=checkbox]')) {
            scheduleRefresh();
        }
    });
    // The live refresh above already covers every filter change; keep the
    // submit button as a visible fallback, just repurposed to flush any
    // pending debounce immediately instead of reloading the page.
    form.addEventListener('submit', (event) => {
        event.preventDefault();
        window.clearTimeout(timer);
        refresh();
    });

    return scheduleRefresh;
};

const initFavoris = (root) => {
    const form = root.querySelector(SELECTORS.FORM);
    const list = root.querySelector(SELECTORS.FAVLIST);
    const saveBtn = root.querySelector(SELECTORS.FAVSAVE);
    const nameInput = root.querySelector(SELECTORS.FAVNAME);
    if (!form || !list) {
        return;
    }

    const buildItem = (id, name) => {
        const row = document.createElement('div');
        row.className = 'kid-comparateur__favitem';
        row.dataset.favoriId = id;
        const load = document.createElement('button');
        load.type = 'button';
        load.className = 'kid-comparateur__favload';
        load.textContent = name;
        const rn = document.createElement('button');
        rn.type = 'button';
        rn.className = 'kid-comparateur__favicon';
        rn.dataset.favoriAction = 'rename';
        rn.textContent = '✎';
        const del = document.createElement('button');
        del.type = 'button';
        del.className = 'kid-comparateur__favicon kid-comparateur__favicon--danger';
        del.dataset.favoriAction = 'delete';
        del.textContent = '✕';
        row.append(load, rn, del);
        return row;
    };

    const doSave = () => {
        const name = (nameInput.value || '').trim();
        if (!name) {
            nameInput.focus();
            return;
        }
        const config = captureConfig(form);
        saveBtn.setAttribute('aria-busy', 'true');
        Ajax.call([{
            methodname: 'local_kid_dashboard_favori_create',
            args: {name, config: JSON.stringify(config)},
        }])[0]
        .then((data) => {
            const item = buildItem(data.id, data.name);
            item.dataset.favoriConfig = JSON.stringify(config);
            list.appendChild(item);
            nameInput.value = '';
            return data;
        })
        .catch(Notification.exception)
        .always(() => saveBtn.removeAttribute('aria-busy'));
    };
    saveBtn?.addEventListener('click', doSave);
    // Same reasoning as the rename input: this field lives inside the main
    // filter <form>, so Enter must be prevented from submitting it.
    nameInput?.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            doSave();
        }
    });

    list.addEventListener('click', (event) => {
        const loadBtn = event.target.closest(SELECTORS.FAVLOAD);
        if (loadBtn) {
            const item = loadBtn.closest(SELECTORS.FAVITEM);
            const config = JSON.parse(item.dataset.favoriConfig || '{}');
            window.location.href = buildUrlFromConfig(config);
            return;
        }

        const actionBtn = event.target.closest(SELECTORS.FAVICON);
        if (!actionBtn) {
            return;
        }
        const item = actionBtn.closest(SELECTORS.FAVITEM);
        const id = item.dataset.favoriId;
        const originalName = item.querySelector(SELECTORS.FAVLOAD)?.textContent || '';

        if (actionBtn.dataset.favoriAction === 'rename') {
            item.innerHTML = '';
            const input = document.createElement('input');
            input.type = 'text';
            input.className = 'rename';
            input.value = originalName;
            const ok = document.createElement('button');
            ok.type = 'button';
            ok.className = 'kid-comparateur__favicon';
            ok.textContent = '✓';
            const cancel = document.createElement('button');
            cancel.type = 'button';
            cancel.className = 'kid-comparateur__favicon';
            cancel.textContent = '✕';
            item.append(input, ok, cancel);
            input.focus();
            input.select();

            const rebuild = (name) => {
                item.innerHTML = '';
                const rebuilt = buildItem(id, name);
                item.replaceWith(rebuilt);
                rebuilt.dataset.favoriConfig = item.dataset.favoriConfig;
            };
            const commit = () => {
                const newname = input.value.trim();
                if (!newname) {
                    return;
                }
                Ajax.call([{methodname: 'local_kid_dashboard_favori_rename', args: {id: parseInt(id, 10), name: newname}}])[0]
                    .then(() => rebuild(newname))
                    .catch(Notification.exception);
            };
            ok.addEventListener('click', commit);
            cancel.addEventListener('click', () => rebuild(originalName));
            input.addEventListener('keydown', (e) => {
                // The favoris panel lives inside the main filter <form> —
                // without preventDefault() Enter here would submit that
                // form (reloading the page) instead of just committing the
                // rename.
                if (e.key === 'Enter') {
                    e.preventDefault();
                    commit();
                } else if (e.key === 'Escape') {
                    e.preventDefault();
                    rebuild(originalName);
                }
            });
        } else if (actionBtn.dataset.favoriAction === 'delete') {
            item.innerHTML = '';
            const msg = document.createElement('span');
            msg.style.cssText = 'flex:1;min-width:0;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
            msg.textContent = originalName;
            const yes = document.createElement('button');
            yes.type = 'button';
            yes.className = 'kid-comparateur__favicon kid-comparateur__favicon--danger';
            yes.textContent = '✓';
            const no = document.createElement('button');
            no.type = 'button';
            no.className = 'kid-comparateur__favicon';
            no.textContent = '✕';
            item.append(msg, yes, no);
            yes.addEventListener('click', () => {
                Ajax.call([{methodname: 'local_kid_dashboard_favori_delete', args: {id: parseInt(id, 10)}}])[0]
                    .then(() => item.remove())
                    .catch(Notification.exception);
            });
            no.addEventListener('click', () => {
                const rebuilt = buildItem(id, originalName);
                rebuilt.dataset.favoriConfig = item.dataset.favoriConfig;
                item.replaceWith(rebuilt);
            });
        }
    });
};

export const init = () => {
    const root = document.querySelector(SELECTORS.ROOT);
    if (!root) {
        return;
    }
    initDrawer(root);
    const scheduleRefresh = initLiveFilter(root);
    initChecklists(root, scheduleRefresh);
    initFavoris(root);
};
