import dayjs from 'dayjs';
import extend from 'just-extend';
import { PROFILS } from '../../../constantes/constantes';
import ActionsMetierHelper from '../../../helpers/ActionsMetierHelper';
import AGLocaleText from '../../../i18n/aggrid-locale-fr';
import i18n from '../../../i18n/i18n';

// typage -----------------------------------------------------

/**
 * Fonction qui formate la valeur obtenue à partir des paramètres.
 * @callback FnFormateurParamsSimple
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @returns {?string} valeur formatée
 */

/**
 * Fonction qui formate la valeur obtenue à partir des paramètres.
 * @callback FnFormateurParams
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @param {?string} [valeurVide]   - chaîne à afficher pour une valeur vide, optionnel
 * @param {?string} [valeurDefaut] - valeur par défaut, utilisée si la valeur en entrée n'est pas vide, optionnel
 * @returns {?string} valeur formatée
 */

/**
 * Fonction qui crée un formateur simple à partir d'un formateur de base.
 * @callback FnCreationFormateurParamsSimple
 * @param {FnFormateurParams} formateurBase
 * @returns {FnFormateurParamsSimple} formateur simple
 */

/**
 * Fonction qui un tableau de valeurs à partir des paramètres.
 * @callback FnExtracteurValeursParams
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @returns {*[]} valeurs
 */

// ------------------------------------------------------------

/**
 * Actuellement le backend renvoie tous les profils locaux (comme PCP_ACTUEL)
 * même si côté frontend l'utilisateur a choisi un autre profil global (comme FILIERE).
 * Il serait incohérent de voir l'utilisateur comme un BMOB perdant sur la vue BGCAT.
 * En attendant une modification côté backend, cette Map permet de filtrer les profils locaux.
 */
const prerequisProfilsLocaux = new Map([
    [PROFILS.BVT, PROFILS.BVT],
    [PROFILS.FILIERE, PROFILS.FILIERE],
    [PROFILS.HME, PROFILS.HME],
    [PROFILS.ITD, PROFILS.ITD],
    [PROFILS.PCP, PROFILS.PCP],
    [PROFILS.PCP_ACTUEL, PROFILS.PCP],
    [PROFILS.PCP_FUTUR, PROFILS.PCP],
]);

/** Retourne toujours false, à utiliser en singleton */
export const getFalse = () => false;

/** Retourne toujours true, à utiliser en singleton */
export const getTrue = () => true;

/**
 * Récupère l'instance d'ActionsMetierHelper à partir de params
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @returns {ActionsMetierHelper} utilitaire d'actions métier
 */
export const getActionsMetierHelper = (params) => params.context?.actionsMetierHelper;

/**
 * Récupère les données de référence à partir de params
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @returns {?import('../../../jsdoc/types').DonneesReference} données de référence
 */
export const getRefs = (params) => params.context?.donneesReference;

/**
 * Récupère les "managers" à partir de params
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @returns {?import('../../../jsdoc/types').ContexteAgGrid_Managers} managers
 */
export const getManagers = (params) => params.context?.managers;

/**
 * Récupère les "mutator" à partir de params
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @returns {?Function} mutator
 */
export const getMutator = (params) => params.context?.mutator;

/**
 * Récupère le profil global sélectionné.
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @returns {?import('../../../jsdoc/types').Profil} profil sélectionné
 */
export const getProfilGlobal = (params) => params.context?.profil;

/**
 * Renvoie les profils locaux de l'utilisateur (ceux qui sont stockés dans chaque ligne).
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @param {boolean} ecriture - true : mode écriture, false (défaut) : mode lecture
 * @returns {import('../../../jsdoc/types').Profil[]} profils locaux
 */
export const getProfilsLocaux = (params, ecriture = false) => {
    const structure = params.data?.profils;
    const profils = ecriture ? structure?.ecriture : structure?.lecture;
    if (!Array.isArray(profils) || !profils.length) {
        return [];
    }
    const profilGlobal = getProfilGlobal(params);
    return profils.filter(p => prerequisProfilsLocaux.get(p) === profilGlobal);
};

/**
 * Indique si l'utilisateur a le profil local demandé ou au moins un profil local demandé.
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @param {(import('../../../jsdoc/types').Profil|import('../../../jsdoc/types').Profil[])} profil - profil(s) à tester
 * @param {boolean} ecriture - true : mode écriture, false (défaut) : mode lecture
 * @returns {boolean}
 */
export const hasProfilLocal = (params, profil, ecriture = false) => {
    const profils = getProfilsLocaux(params, ecriture);
    return Array.isArray(profil) ? profil.some((p) => profils.includes(p)) : profils.includes(profil);
};

/**
 * Tente de lire une date à partir de la valeur et renvoie null pour une valeur vide.
 * @param {?*}      val      - tout type accepté par dayjs
 * @param {?string} [format] - format pour faciliter la lecture si besoin. optionnel
 * @returns {?import('../../../jsdoc/types').Dayjs} objet Dayjs ou null
 */
export const toDayjsOrNull = (val, format) => (val ? (val instanceof dayjs ? val : dayjs(val, format)) : null);

/**
 * Tente de lire une date à partir de la valeur et force cette date au début du jour (pas de temps).
 * @param {?*}      val                 - tout type accepté par dayjs
 * @param {?import('../../../jsdoc/types').Dayjs}  [valeurDefaut=null] - date par défaut si la valeur est vide. défaut : null
 * @param {?string} [format]            - format pour faciliter la lecture si besoin. optionnel
 * @returns {?import('../../../jsdoc/types').Dayjs} objet Dayjs au début du jour ou la valeur par défaut
 */
export const toDayjsAtStartOfDayOrDefault = (val, valeurDefaut = null, format) => toDayjsOrNull(val, format)?.startOf('day') ?? valeurDefaut;

/**
 * Tente de lire une date à partir de la valeur et force cette date au début du jour (pas de temps).
 * @param {?*}      val                 - tout type accepté par dayjs
 * @param {?Date}   [valeurDefaut=null] - date par défaut si la valeur est vide. défaut : null
 * @param {?string} [format]            - format pour faciliter la lecture si besoin. optionnel
 * @returns {?Date} date au début du jour ou la valeur par défaut
 */
export const toStartOfDayOrDefault = (val, defaultVal = null, format) => toDayjsAtStartOfDayOrDefault(val, format)?.toDate() ?? defaultVal;

/**
 * Fonction de création d'un formateur de paramètres à partir d'un formateur simple.
 * @param {FnFormateurParamsSimple} formateur - formateur simple de paramètres
 * @returns {FnFormateurParams} formateur de paramètres
 */
export const creerFormateurParams =
    (formateur) =>
    (params, valeurVide = params?.value, valeurDefaut = params?.value) =>
        !params?.value ? valeurVide : formateur(params) ?? valeurDefaut;

/**
 * Options pour l'éditeur
 * @typedef CreerColDefDonneesFormatees_OptionsEditeur
 * @property {?FnExtracteurValeursParams} extracteurValeurs
 * @property {?*} valeurVide
 *
 * Options
 * @typedef CreerColDefDonneesFormatees_Options
 * @property {FnCreationFormateurParamsSimple} [fnFormateurValeurs] - crée le formateur simple de valeurs du tableau, utilise le formateur de base
 * @property {FnCreationFormateurParamsSimple} [fnFormateurTooltip] - crée le formateur de valeurs de l'info-bulle, utilise le formateur de base
 * @property {FnCreationFormateurParamsSimple} [fnFormateurFiltre]  - crée le formateur de valeurs du filtre, utilise le formateur de base
 * @property {?CreerColDefDonneesFormatees_OptionsEditeur} [editeur={}]
 *
 * ============================================================
 *
 * Crée une définition (partielle) de colonne pour gérer une liste déroulante de données fixes.
 * @param {FnFormateurParams}                   formateurBase - formateur de base quand la valeur existe
 * @param {CreerColDefDonneesFormatees_Options} [options={}]  - options pour personnaliser l'affichage ou l'éditeur, optionnel
 * @returns {Object} définition partielle de colonne
 */
export const creerColDefDonneesFormatees = (
    formateurBase,
    { fnFormateurValeurs, fnFormateurTooltip, fnFormateurFiltre, editeur: { valeurVide, extracteurValeurs } = {} } = {}
) => {
    const _formateurValeurs =
        fnFormateurValeurs?.(formateurBase) ?? ((params) => formateurBase(params, i18n.global.donnees.vide, '[' + params.value + ']'));
    const _formateurTooltip = fnFormateurTooltip?.(formateurBase) ?? ((params) => formateurBase(params, '', '[' + params.value + ']'));
    const _formateurFiltre = fnFormateurFiltre?.(formateurBase) ?? ((params) => formateurBase(params, AGLocaleText.blanks, '[' + params.value + ']'));

    const colDef = {
        valueFormatter: _formateurValeurs,
        tooltipValueGetter: _formateurTooltip,
        filterParams: { valueFormatter: _formateurFiltre }
    };
    if (extracteurValeurs) {
        const _cellEditorParams = (params) => ({
            values: (() => {
                const valeurs = extracteurValeurs(params);
                return valeurVide !== undefined ? [valeurVide, ...valeurs] : [...valeurs];
            })(),
            useFormatter: true
        });
        extend(true, colDef, {
            cellEditor: 'agRichSelectCellEditor',
            refDataCellEditorParams: _cellEditorParams, // pour faciliter la surcharge
            cellEditorParams: _cellEditorParams
        });
    }
    return colDef;
};

/**
 * Options
 * @typedef {Object} CreerColDefDonneesRef_Options
 * @property {FnFormateurParams}               [formateurBase]      - formateur de base quand la valeur existe
 *                                                                    peut avoir des particularités si on mélange les données de référence et i18n (voir creerFormateurParams)
 *                                                                    par défaut : utilise le champ 'libelle" des données de référence
 * @property {FnCreationFormateurParamsSimple} [fnFormateurValeurs] - crée le formateur simple de valeurs du tableau, utilise le formateur de base
 * @property {FnCreationFormateurParamsSimple} [fnFormateurTooltip] - crée le formateur de valeurs de l'info-bulle, utilise le formateur de base
 * @property {FnCreationFormateurParamsSimple} [fnFormateurFiltre]  - crée le formateur de valeurs du filtre, utilise le formateur de base
 * @property {?CreerColDefDonneesFormatees_OptionsEditeur} [editeur={}]
 *
 * Fonction qui extrait une Map de données de référence ciblées à partir des paramètres.
 * @callback FnExtrateurRefsCiblees
 * @param {import('../../../jsdoc/types').AgGridParams} params
 * @returns {Map} données de référence ciblées
 *
 * ============================================================
 *
 * Crée une définition (partielle) de colonne pour gérer une liste déroulante de données de référence.
 * @param {FnExtrateurRefsCiblees}        fnExtracteurRefs - fonction qui permet de récupérer les données de référence ciblées
 *                                                           données : Map indexée par ID/code
 * @param {CreerColDefDonneesRef_Options} [options={}]     - options pour personnaliser l'affichage ou l'éditeur, optionnel
 * @returns {Object} définition partielle de colonne
 */
export const creerColDefDonneesRef = (
    fnExtracteurRefs,
    { formateurBase, fnFormateurValeurs, fnFormateurTooltip, fnFormateurFiltre, editeur: { valeurVide, extracteurValeurs } = {} } = {}
) => {
    const _formateur = formateurBase ?? creerFormateurParams((params) => fnExtracteurRefs(params)?.get(params.value)?.libelle);
    const _extracteurValeurs = extracteurValeurs ?? ((params) => fnExtracteurRefs(params)?.keys());

    return creerColDefDonneesFormatees(_formateur, {
        fnFormateurValeurs,
        fnFormateurTooltip,
        fnFormateurFiltre,
        editeur: {
            valeurVide,
            extracteurValeurs: _extracteurValeurs
        }
    });
};

/**
 * Crée une version spéciale de 'stopEditing' (fonction passée aux éditeurs de cellules) pour modifier le comportement d'un composant éditeur juste
 * avant la fin de l'édition.
 * (interface d'un composant d'éditeur : https://www.ag-grid.com/react-data-grid/component-cell-editor/#cell-editor-component-2)
 *
 * @param {*} params      paramètres passés à l'éditeur
 * @param {*} customizer  fonction de paramètre (params, editor) qui personnalise l'instance de l'éditeur actif
 */
export const customizeEditorAndStopEditing = (params, customizer = (_params, _editor) => {}) => {
    const _stopEditing = params.stopEditing;
    return (bool) => {
        const editor = params.api.getCellEditorInstances({ rowNodes: [params.node], columns: [params.column] })[0];
        if (editor && !editor.customized) {
            customizer(params, editor);
            editor.customized = true;
        }
        _stopEditing(bool);
    };
};

