// <=== CONSTANTS and GLOBALS ===>

let url;

function getUrl() {
  if (!url) {
    url = SCO_URL.substring(0, SCO_URL.lastIndexOf("/"));
  }
  return url;
}

//Les valeurs par défaut de la timeline (8h -> 18h)
let currentValues = [8.0, 10.0];

//Objet stockant les étudiants et les assiduités
let etuds = {};
let assiduites = {};
let justificatifs = {};

// Variable qui définit si le processus d'action de masse est lancé
let currentMassAction = false;
let currentMassActionEtat = undefined;

/**
 * Ajout d'une fonction `capitalize` sur tous les strings
 * alice.capitalize() -> Alice
 */
Object.defineProperty(String.prototype, "capitalize", {
  value: function () {
    return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
  },
  enumerable: false,
});

const DatePrecisions = [
  "year",
  "month",
  "day",
  "hour",
  "minute",
  "second",
  "millisecond",
];

// <<== Outils ==>>
Object.defineProperty(Array.prototype, "reversed", {
  value: function () {
    return [...this].map(this.pop, this);
  },
  enumerable: false,
});

/**
 * Ajout des évents sur les boutons d'assiduité
 * @param {Document | HTMLFieldSetElement} parent par défaut le document, un field sinon
 */
function setupCheckBox(parent = document) {
  const checkboxes = Array.from(parent.querySelectorAll(".rbtn"));
  checkboxes.forEach((box) => {
    box.addEventListener("click", (event) => {
      if (!uniqueCheckBox(box)) {
        event.preventDefault();
      }
      if (!box.parentElement.classList.contains("mass")) {
        assiduiteAction(box);
      }
    });
  });
}

/**
 * Validation préalable puis désactivation des chammps :
 * - Groupe
 * - Module impl
 * - Date
 */
function validateSelectors(btn) {
  const action = () => {
    const group_ids = getGroupIds();

    etuds = {};
    group_ids.forEach((group_id) => {
      sync_get(
        getUrl() + `/api/group/${group_id}/etudiants`,
        (data, status) => {
          if (status === "success") {
            data.forEach((etud) => {
              if (!(etud.id in etuds)) {
                etuds[etud.id] = etud;
              }
            });
          }
        }
      );
    });

    if (getModuleImplId() == null && window.forceModule && !readOnly) {
      const HTML = `
      <p>Attention, le module doit obligatoirement être renseigné.</p>
      <p>Cela vient de la configuration du semestre ou plus largement du département.</p>
      <p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
       `;

      const content = document.createElement("div");
      content.innerHTML = HTML;

      openAlertModal("Sélection du module", content);
      return;
    }

    getAssiduitesFromEtuds(true);

    document.querySelector(".selectors").disabled = true;
    $("#tl_date").datepicker("option", "disabled", true);
    generateMassAssiduites();
    generateAllEtudRow();
    btn.remove();
    onlyAbs();
  };

  if (!verifyDateInSemester()) {
    const HTML = `
    <p>Attention, la date sélectionnée n'est pas comprise dans le semestre.</p>
    <p>Cette page permet l'affichage et la modification des assiduités uniquement pour le semestre sélectionné.</p>
    <p>Vous n'aurez donc pas accès aux assiduités.</p>
    <p>Appuyer sur "Valider" uniquement si vous souhaitez poursuivre sans modifier la date.</p>
     `;

    const content = document.createElement("div");
    content.innerHTML = HTML;

    openPromptModal("Vérification de la date", content, action);
    return;
  }

  action();
}

function onlyAbs() {
  if (getDate() > Date.now()) {
    document
      .querySelectorAll(".rbtn.present, .rbtn.retard")
      .forEach((el) => el.remove());
  }
}

/**
 * Limite le nombre de checkbox marquée
 * Vérifie aussi si le cliqué est fait sur des assiduités conflictuelles
 * @param {HTMLInputElement} box la checkbox utilisée
 * @returns {boolean} Faux si il y a un conflit d'assiduité, Vrai sinon
 */
function uniqueCheckBox(box) {
  const type = box.parentElement.getAttribute("type") === "conflit";
  if (!type) {
    const checkboxs = Array.from(box.parentElement.children);

    checkboxs.forEach((chbox) => {
      if (chbox.checked && chbox.value !== box.value) {
        chbox.checked = false;
      }
    });
    return true;
  }

  return false;
}

/**
 * Fait une requête GET de façon synchrone
 * @param {String} path adresse distante
 * @param {CallableFunction} success fonction à effectuer en cas de succès
 * @param {CallableFunction} errors fonction à effectuer en cas d'échec
 */
function sync_get(path, success, errors) {
  //TODO Optimiser : rendre asynchrone + sans jquery
  console.log("sync_get " + path);
  $.ajax({
    async: false,
    type: "GET",
    url: path,
    success: success,
    error: errors,
  });
}
/**
 * Fait une requête GET de façon asynchrone
 * @param {String} path adresse distante
 * @param {CallableFunction} success fonction à effectuer en cas de succès
 * @param {CallableFunction} errors fonction à effectuer en cas d'échec
 */
async function async_get(path, success, errors) {
  console.log("async_get " + path);
  let response;
  try {
    response = await fetch(path);
    if (response.ok) {
      const data = await response.json();
      success(data);
    } else {
      throw new Error("Network response was not ok.");
    }
  } catch (error) {
    console.error(error);
    if (errors) errors(error);
  }

  return response;
}

/**
 * Fait une requête POST de façon synchrone
 * @param {String} path adresse distante
 * @param {object} data données à envoyer (objet js)
 * @param {CallableFunction} success fonction à effectuer en cas de succès
 * @param {CallableFunction} errors fonction à effectuer en cas d'échec
 */
function sync_post(path, data, success, errors) {
  //TODO Optimiser : rendre asynchrone + sans jquery
  console.log("sync_post " + path);
  $.ajax({
    async: false,
    type: "POST",
    url: path,
    data: JSON.stringify(data),
    success: success,
    error: errors,
  });
}
/**
 * Fait une requête POST de façon asynchrone
 * @param {String} path adresse distante
 * @param {object} data données à envoyer (objet js)
 * @param {CallableFunction} success fonction à effectuer en cas de succès
 * @param {CallableFunction} errors fonction à effectuer en cas d'échec
 */
async function async_post(path, data, success, errors) {
  console.log("async_post " + path);
  let response;
  try {
    response = await fetch(path, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    });

    if (response.ok) {
      const responseData = await response.json();
      success(responseData);
    } else {
      throw new Error("Network response was not ok.");
    }
  } catch (error) {
    console.error(error);
    if (errors) errors(error);
  }

  return response;
}

// <<== Gestion des actions de masse ==>>
const massActionQueue = new Map();

/**
 * Cette fonction remet à zero la gestion des actions de masse
 */
function resetMassActionQueue() {
  massActionQueue.set("supprimer", []);
  massActionQueue.set("editer", []);
  massActionQueue.set("creer", []);
}

/**
 * Fonction pour alimenter la queue des actions de masse
 * @param {String} type Le type de queue ("creer", "supprimer", "editer")
 * @param {*} obj L'objet qui sera utilisé par les API
 */
function addToMassActionQueue(type, obj) {
  massActionQueue.get(type)?.push(obj);
}

/**
 * Fonction pour exécuter les actions de masse
 */
function executeMassActionQueue() {
  if (!currentMassAction) return;

  //Récupération des queues
  const toCreate = massActionQueue.get("creer");
  const toEdit = massActionQueue.get("editer");
  const toDelete = massActionQueue.get("supprimer");

  //Fonction qui créé les assidutiés de la queue "creer"
  const create = () => {
    /**
     * Création du template de l'assiduité
     *
     * {
     *   date_debut: #debut_timeline,
     *   date_fin: #fin_timeline,
     *   moduleimpl_id ?: <>
     * }
     */
    const tlTimes = getTimeLineTimes();
    let assiduite = {
      date_debut: tlTimes.deb.toFakeIso(),
      date_fin: tlTimes.fin.toFakeIso(),
    };

    assiduite = setModuleImplId(assiduite);
    if (!hasModuleImpl(assiduite) && window.forceModule) {
      const html = `
    <h3>Aucun module n'a été spécifié</h3>
    `;
      const div = document.createElement("div");
      div.innerHTML = html;
      openAlertModal("Erreur Module", div);
      return 0;
    }

    const createQueue = []; //liste des assiduités qui seront créées.

    /**
     * Pour chaque état de la queue 'creer' on génère une
     * assiduitée précise depuis le template
     */
    toCreate.forEach((obj) => {
      const curAssiduite = structuredClone(assiduite);
      curAssiduite.etudid = obj.etudid;
      curAssiduite.etat = obj.etat;

      createQueue.push(curAssiduite);
    });

    /**
     * On envoie les données à l'API
     */
    const path = getUrl() + `/api/assiduites/create`;
    sync_post(
      path,
      createQueue,
      (data, status) => {
        //success
      },
      (data, status) => {
        //error
        console.error(data, status);
        errorAlert();
      }
    );
    return createQueue.length;
  };

  //Fonction qui modifie les assiduités de la queue 'edition'
  const edit = () => {
    //On ajoute le moduleimpl (s'il existe) aux assiduités à modifier
    const editQueue = toEdit.map((assiduite) => {
      assiduite = setModuleImplId(assiduite);
      return assiduite;
    });

    if (getModuleImplId() == null && window.forceModule) {
      const html = `
    <h3>Aucun module n'a été spécifié</h3>
    `;
      const div = document.createElement("div");
      div.innerHTML = html;
      openAlertModal("Erreur Module", div);
      return 0;
    }

    const path = getUrl() + `/api/assiduites/edit`;
    sync_post(
      path,
      editQueue,
      (data, status) => {
        //success
      },
      (data, status) => {
        //error
        console.error(data, status);
        errorAlert();
      }
    );
    return editQueue.length;
  };

  //Fonction qui supprime les assiduités de la queue 'supprimer'
  const supprimer = () => {
    const path = getUrl() + `/api/assiduite/delete`;
    sync_post(
      path,
      toDelete,
      (data, status) => {
        //success
      },
      (data, status) => {
        //error
        console.error(data, status);
        errorAlert();
      }
    );
    return toDelete.length;
  };

  //On exécute les fonctions de queue
  let count = 0;
  if (currentMassActionEtat == "remove") {
    count += supprimer();
    const span = document.createElement("span");
    if (count > 0) {
      span.innerHTML = `${count} assiduités ont été supprimées.`;
    } else {
      span.innerHTML = `Aucune assiduité n'a été supprimée.`;
    }
    pushToast(
      generateToast(
        span,
        getToastColorFromEtat(currentMassActionEtat.toUpperCase()),
        5
      )
    );
  } else {
    count += create();
    count += edit();
    const etat =
      currentMassActionEtat.toUpperCase() == "RETARD"
        ? "En retard"
        : currentMassActionEtat;
    const span = document.createElement("span");
    if (count > 0) {
      span.innerHTML = `${count} étudiants ont été mis <u><strong>${etat
        .capitalize()
        .trim()}</strong></u>`;
    } else {
      span.innerHTML = `Aucun étudiant n'a été mis <u><strong>${etat
        .capitalize()
        .trim()}</strong></u>`;
    }
    pushToast(
      generateToast(
        span,
        getToastColorFromEtat(currentMassActionEtat.toUpperCase()),
        5
      )
    );
  }
  //On récupère les assiduités puis on regénère les lignes d'étudiants
  getAssiduitesFromEtuds(true);
  generateAllEtudRow();
}
/**
 * Processus de peuplement des queues
 * puis d'exécution
 */
function massAction() {
  //On récupère tous les boutons d'assiduités
  const fields = Array.from(document.querySelectorAll(".btns_field.single"));
  //On récupère l'état de l'action de masse
  currentMassActionEtat = getAssiduiteValue(
    document.querySelector(".btns_field.mass")
  );

  //On remet à 0 les queues
  resetMassActionQueue();

  //on met à vrai la variable pour la suite
  currentMassAction = true;

  //On affiche le "loader" le temps du processus
  showLoader();

  //On timeout 0 pour le mettre à la fin de l'event queue de JS
  setTimeout(() => {
    const conflicts = [];
    /**
     * Pour chaque étudiant :
     * On vérifie s'il y a un conflit -> on place l'étudiant dans l'array conflicts
     * Sinon -> on fait comme si l'utilisateur cliquait sur le bouton d'assiduité
     */
    fields.forEach((field) => {
      if (field.getAttribute("type") != "conflit") {
        if (currentMassActionEtat != "remove") {
          field.querySelector(`.rbtn.${currentMassActionEtat}`).click();
        } else {
          field.querySelector(".rbtn.absent").click();
        }
      } else {
        const etudid = field.getAttribute("etudid");
        conflicts.push(etuds[parseInt(etudid)]);
      }
    });

    //on exécute les queues puis on cache le loader
    executeMassActionQueue();
    hideLoader();

    //Fin du processus, on remet à false
    currentMassAction = false;
    currentMassActionEtat = undefined;

    //On remet à zero les boutons d'assiduité de masse
    const boxes = Array.from(
      document.querySelector(".btns_field.mass").querySelectorAll(".rbtn")
    );
    boxes.forEach((box) => {
      box.checked = false;
    });

    //Si il y a des conflits d'assiduité, on affiche la liste dans une alert
    if (conflicts.length > 0) {
      const div = document.createElement("div");
      const sub = document.createElement("p");
      sub.textContent =
        "L'assiduité des étudiants suivant n'a pas pu être modifiée";
      div.appendChild(sub);
      const ul = document.createElement("ul");
      conflicts.forEach((etu) => {
        const li = document.createElement("li");
        li.textContent = `${etu.nom} ${etu.prenom.capitalize()}`;
        ul.appendChild(li);
      });
      div.appendChild(ul);
      openAlertModal("Conflits d'assiduités", div, "");
    }
  }, 0);
}

/**
 * On génère les boutons d'assiduités de masse
 * puis on ajoute les événements associés
 */
function generateMassAssiduites() {
  const content = document.getElementById("content");

  const mass = document.createElement("div");
  mass.className = "mass-selection";
  mass.innerHTML = `
    <span>Mettre tout le monde :</span>
    <fieldset class="btns_field mass">
        <input type="checkbox" value="present" name="mass_btn_assiduites" id="mass_rbtn_present"
            class="rbtn present">
        <input type="checkbox" value="retard" name="mass_btn_assiduites" id="mass_rbtn_retard" class="rbtn retard">
        <input type="checkbox" value="absent" name="mass_btn_assiduites" id="mass_rbtn_absent" class="rbtn absent">
        <input type="checkbox" value="remove" name="mass_btn_assiduites" id="mass_rbtn_aucun" class="rbtn aucun">
    </fieldset>`;

  content.insertBefore(mass, content.querySelector(".etud_holder"));

  const mass_btn = Array.from(mass.querySelectorAll(".rbtn"));
  mass_btn.forEach((btn) => {
    btn.addEventListener("click", () => {
      massAction();
    });
  });

  if (!verifyDateInSemester() || readOnly) {
    content.querySelector(".btns_field.mass").setAttribute("disabled", "true");
  }
}

/**
 * Affichage du loader
 */
function showLoader() {
  document.getElementById("loaderContainer").style.display = "block";
}
/**
 * Dissimulation du loader
 */
function hideLoader() {
  document.getElementById("loaderContainer").style.display = "none";
}

// <<== Gestion du temps ==>>

/**
 * Transforme un temps numérique en string
 * 8.75 -> 08h45
 * @param {number} time Le temps (float)
 * @returns {string} le temps (string)
 */
function toTime(time) {
  let heure = Math.floor(time);
  let minutes = Math.round((time - heure) * 60);
  if (minutes < 10) {
    minutes = `0${minutes}`;
  }
  if (heure < 10) {
    heure = `0${heure}`;
  }
  return `${heure}h${minutes}`;
}
/**
 * Transforme une date iso en une date lisible:
 * new Date('2023-03-03') -> "vendredi 3 mars 2023"
 * @param {Date} date
 * @param {object} styles
 * @returns
 */
function formatDate(date, styles = { dateStyle: "full" }) {
  return new Intl.DateTimeFormat("fr-FR", {
    ...{ timeZone: SCO_TIMEZONE },
    ...styles,
  }).format(date);
}

/**
 * Met à jour la date visible sur la page en la formatant
 */
function updateDate() {
  const dateInput = document.querySelector("#tl_date");
  let date = $(dateInput).datepicker("getDate");
  if (date == null) {
    date = new Date(Date.fromFRA(dateInput.value));
  }
  const intlOptions = {
    dateStyle: "full",
    timeZone: SCO_TIMEZONE,
  };
  let dateStr = "";

  if (!isNonWorkDay(date, nonWorkDays)) {
    dateStr = formatDate(date, intlOptions).capitalize();
  } else {
    // On se rend au dernier jour travaillé disponible
    const lastWorkDay = getNearestWorkDay(date);
    const att = document.createTextNode(
      `Le jour sélectionné (${formatDate(
        date,
        intlOptions
      )}) n'est pas un jour travaillé.`
    );
    const div = document.createElement("div");
    div.appendChild(att);
    div.appendChild(document.createElement("br"));
    div.appendChild(
      document.createTextNode(
        `Le dernier jour travaillé disponible a été sélectionné : ${formatDate(
          lastWorkDay,
          intlOptions
        )}.`
      )
    );
    openAlertModal("Attention", div, "", "#eec660");

    /* BUG TODO MATHIAS
    $(dateInput).datepicker("setDate", date_fra); // XXX ??? non définie
    dateInput.value = date_fra;
    */
    date = lastWorkDay;

    dateStr = formatDate(lastWorkDay, {
      dateStyle: "full",
      timeZone: SCO_TIMEZONE,
    }).capitalize();
  }

  document.querySelector("#datestr").textContent = dateStr;
  return true;
}

function getNearestWorkDay(date) {
  const aDay = 86400000; // 24 * 3600 * 1000 | H * s * ms
  let day = date;
  let count = 0;
  while (isNonWorkDay(day, nonWorkDays) && count++ < 7) {
    day = new Date(day - aDay);
  }
  return day;
}

function verifyDateInSemester() {
  const date = getDate();

  const periodSemester = getFormSemestreDates();
  return date.isBetween(periodSemester.deb, periodSemester.fin, "[]");
}

/**
 * Ajoute la possibilité d'ouvrir le calendrier
 * lorsqu'on clique sur la date
 */
function setupDate(onchange = null) {
  const datestr = document.querySelector("#datestr");
  const input = document.querySelector("#tl_date");

  datestr.addEventListener("click", () => {
    if (!document.querySelector(".selectors").disabled) {
      try {
        document.querySelector(".infos .ui-datepicker-trigger").click();
      } catch {}
    }
  });

  if (onchange != null) {
    $(input).change(onchange);
  }
}

/**
 * GetAssiduitesOnDateChange
 * (Utilisé uniquement avec étudiant unique)
 */

function getAssiduitesOnDateChange() {
  if (!isSingleEtud()) return;
  actualizeEtud(etudid);
}
/**
 * Transforme une date iso en date intelligible
 * @param {String} str date iso
 * @param {String} separator le séparateur de la date intelligible (01/01/2000 {separtor} 10:00)
 * @returns {String} la date intelligible
 */
function formatDateModal(str, separator = " ") {
  return new Date(str).format("DD/MM/Y HH:mm").replace(" ", separator);
}

/**
 * Vérifie si la date sélectionnée n'est pas un jour non travaillé
 * Renvoie Vrai si le jour est non travaillé
 */
function isNonWorkDay(day, nonWorkdays) {
  const d = Intl.DateTimeFormat("fr-FR", {
    timeZone: SCO_TIMEZONE,
    weekday: "short",
  })
    .format(day)
    .replace(".", "");
  return nonWorkdays.indexOf(d) != -1;
}

/**
 * Fonction qui vérifie si une période est dans un interval
 * Objet période / interval
 * {
 *      deb: Date,
 *      fin: Date,
 * }
 * @param {object} period
 * @param {object} interval
 * @returns {boolean} Vrai si la période est dans l'interval
 */
function hasTimeConflict(period, interval) {
  return period.deb.isBefore(interval.fin) && period.fin.isAfter(interval.deb);
}

/**
 * On récupère la période de la timeline
 * @returns {deb : Date, fin: Date)}
 */
function getTimeLineTimes() {
  //getPeriodValues() -> retourne la position de la timeline [a,b] avec a et b des number
  let values = getPeriodValues();
  //On récupère la date
  const dateiso = getDate().format("YYYY-MM-DD");

  //On génère des objets temps
  values = values.map((el) => {
    el = toTime(el).replace("h", ":");
    el = `${dateiso}T${el}`;
    return new Date(el);
  });

  return { deb: values[0], fin: values[1] };
}

/**
 * Vérification de l'égalité entre un conflit et la période de la timeline
 * @param {object} conflict
 * @returns {boolean} Renvoie Vrai si la période de la timeline est égal au conflit
 */
function isConflictSameAsPeriod(conflict, period = undefined) {
  const tlTimes = period == undefined ? getTimeLineTimes() : period;
  const clTimes = {
    deb: new Date(Date.removeUTC(conflict.date_debut)),
    fin: new Date(Date.removeUTC(conflict.date_fin)),
  };
  return tlTimes.deb.isSame(clTimes.deb) && tlTimes.fin.isSame(clTimes.fin);
}

/**
 * Retourne un objet Date de la date sélectionnée
 * @returns {Date} la date sélectionnée
 */
function getDate() {
  const date =
    $("#tl_date").datepicker("getDate") ??
    new Date(Date.fromFRA(document.querySelector("#tl_date").value));
  return date.startOf("day");
}

/**
 * Retourne un objet date représentant le jour suivant
 * @returns {Date} le jour suivant
 */
function getNextDate() {
  const date = getDate();
  return date.clone().add(1, "days");
}
/**
 * Retourne un objet date représentant le jour précédent
 * @returns {Date} le jour précédent
 */
function getPrevDate() {
  const date = getDate();
  return date.clone().add(-1, "days");
}

/**
 * Transformation d'un objet Date en chaîne ISO
 * @param {Date} date
 * @returns {string} la date iso avec le timezone
 */

/**
 * Transforme un temps numérique en une date
 * @param {number} nb
 * @returns {Date} Une date formée du temps donné et de la date courante
 */
function numberTimeToDate(nb) {
  time = toTime(nb).replace("h", ":");
  date = getDate().format("YYYY-MM-DD");

  datetime = `${date}T${time}`;

  return new Date(datetime);
}

// <<== Gestion des assiduités ==>>

/**
 * Récupère les assiduités des étudiants
 * en fonction de :
 *   - du semestre
 *   - de la date courant et du jour précédent.
 * @param {boolean} clear vidage de l'objet "assiduites" ou non
 * @returns {object} l'objet Assiduités {<etudid:str> : [<assiduite>,]}
 */
function getAssiduitesFromEtuds(clear, deb, fin) {
  const etudIds = Object.keys(etuds).join(",");

  const date_debut = deb ? deb : getPrevDate().toFakeIso();
  const date_fin = fin ? fin : getNextDate().toFakeIso();

  if (clear) {
    assiduites = {};
  }

  const url_api =
    getUrl() +
    `/api/assiduites/group/query?date_debut=${date_debut}&date_fin=${date_fin}&etudids=${etudIds}`;
  sync_get(url_api, (data, status) => {
    if (status === "success") {
      const dataKeys = Object.keys(data);
      dataKeys.forEach((key) => {
        if (clear || !(key in assiduites)) {
          assiduites[key] = data[key];
        } else {
          assiduites[key] = assiduites[key].concat(data[key]);
        }
        let assi_ids = [];
        assiduites[key] = assiduites[key].reversed().filter((value) => {
          if (assi_ids.indexOf(value.assiduite_id) == -1) {
            assi_ids.push(value.assiduite_id);
            return true;
          }

          return false;
        });
      });
    }
  });
  return assiduites;
}

/**
 * Création d'une assiduité pour un étudiant
 * @param {String} etat l'état de l'étudiant
 * @param {Number | String} etudid l'identifiant de l'étudiant
 *
 * TODO : Rendre asynchrone
 */
function createAssiduite(etat, etudid) {
  const tlTimes = getTimeLineTimes();
  let assiduite = {
    date_debut: tlTimes.deb.toFakeIso(),
    date_fin: tlTimes.fin.toFakeIso(),
    etat: etat,
  };

  assiduite = setModuleImplId(assiduite);

  if (!hasModuleImpl(assiduite) && window.forceModule) {
    const html = `
  <h3>Aucun module n'a été spécifié</h3>
  `;
    const div = document.createElement("div");
    div.innerHTML = html;
    openAlertModal("Erreur Module", div);
    return false;
  }

  const path = getUrl() + `/api/assiduite/${etudid}/create`;

  let with_errors = false;

  sync_post(
    path,
    [assiduite],
    (data, status) => {
      //success
      if (data.success.length > 0) {
        let obj = data.success["0"].message.assiduite_id;
      }
      if (data.errors.length > 0) {
        console.error(data.errors["0"].message);
        if (data.errors["0"].message == "Module non renseigné") {
          const HTML = `
          <p>Attention, le module doit obligatoirement être renseigné.</p>
          <p>Cela vient de la configuration du semestre ou plus largement du département.</p>
          <p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
          `;

          const content = document.createElement("div");
          content.innerHTML = HTML;

          openAlertModal("Sélection du module", content);
        }
        if (
          data.errors["0"].message == "L'étudiant n'est pas inscrit au module"
        ) {
          const HTML = `
          <p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
          <p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
          `;

          const content = document.createElement("div");
          content.innerHTML = HTML;

          openAlertModal("Sélection du module", content);
        }
        with_errors = true;
      }
    },
    (data, status) => {
      //error
      console.error(data, status);
      errorAlert();
      with_errors = true;
    }
  );
  return !with_errors;
}
/**
 * Création d'une assiduité pour un étudiant
 * @param {String} etat l'état de l'étudiant
 * @param {Number | String} etudid l'identifiant de l'étudiant
 *
 * TODO : Rendre asynchrone
 */
function createAssiduiteComplete(assiduite, etudid) {
  if (!hasModuleImpl(assiduite) && window.forceModule) {
    const html = `
  <h3>Aucun module n'a été spécifié</h3>
  `;
    const div = document.createElement("div");
    div.innerHTML = html;
    openAlertModal("Erreur Module", div);
    return false;
  }

  const path = getUrl() + `/api/assiduite/${etudid}/create`;

  let with_errors = false;

  sync_post(
    path,
    [assiduite],
    (data, status) => {
      //success
      if (data.success.length > 0) {
        let obj = data.success["0"].message.assiduite_id;
      }
      if (data.errors.length > 0) {
        console.error(data.errors["0"].message);
        if (data.errors["0"].message == "Module non renseigné") {
          const HTML = `
          <p>Attention, le module doit obligatoirement être renseigné.</p>
          <p>Voir configuration du semestre ou du département.</p>
          `;

          const content = document.createElement("div");
          content.innerHTML = HTML;

          openAlertModal("Sélection du module", content);
        }
        if (
          data.errors["0"].message == "L'étudiant n'est pas inscrit au module"
        ) {
          const HTML = `
          <p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
          `;

          const content = document.createElement("div");
          content.innerHTML = HTML;

          openAlertModal("Sélection du module", content);
        }
        if (
          data.errors["0"].message ==
          "Duplication: la période rentre en conflit avec une plage enregistrée"
        ) {
          const HTML = `
          <p>L'assiduité n'a pas pu être enregistrée car un autre évènement
          existe sur la période sélectionnée</p>
          `;

          const content = document.createElement("div");
          content.innerHTML = HTML;

          openAlertModal("Période conflictuelle", content);
        }
        with_errors = true;
      }
    },
    (data, status) => {
      //error
      console.error(data, status);
      errorAlert();
      with_errors = true;
    }
  );
  return !with_errors;
}

/**
 * Suppression d'une assiduité
 * @param {String | Number} assiduite_id l'identifiant de l'assiduité
 * TODO : Rendre asynchrone
 */
function deleteAssiduite(assiduite_id) {
  const path = getUrl() + `/api/assiduite/delete`;
  sync_post(
    path,
    [assiduite_id],
    (data, status) => {
      //success
      if (data.success.length > 0) {
        let obj = data.success["0"].message.assiduite_id;
      }
    },
    (data, status) => {
      //error
      console.error(data, status);
      errorAlert();
    }
  );
  return true;
}

function hasModuleImpl(assiduite) {
  if (assiduite.moduleimpl_id != null) return true;
  return (
    assiduite.hasOwnProperty("external_data") &&
    assiduite.external_data != null &&
    assiduite.external_data.hasOwnProperty("module")
  );
}

/**
 *
 * @param {String | Number} assiduite_id l'identifiant d'une assiduité
 * @param {String} etat l'état à modifier
 * @returns {boolean} si l'édition a fonctionné
 * TODO : Rendre asynchrone
 */
function editAssiduite(assiduite_id, etat, assi) {
  if (assi.length != 1 || !assi[0].hasOwnProperty("assiduite_id")) {
    const html = `
  <h3>Aucune assiduité n'a pû être éditée</h3>
  `;
    const div = document.createElement("div");
    div.innerHTML = html;
    openAlertModal("Erreur", div);
    return;
  }
  let assiduite = {
    etat: etat,
    external_data: assi ? assi.external_data : null,
  };

  assiduite = setModuleImplId(assiduite);
  if (!hasModuleImpl(assiduite) && window.forceModule) {
    const html = `
  <h3>Aucun module n'a été spécifié</h3>
  `;
    const div = document.createElement("div");
    div.innerHTML = html;
    openAlertModal("Erreur Module", div);
    return;
  }
  const path = getUrl() + `/api/assiduite/${assiduite_id}/edit`;
  let bool = false;
  sync_post(
    path,
    assiduite,
    (data, status) => {
      bool = true;
    },
    (data, status) => {
      //error
      console.error(data, status);
      try {
        errorJson = data.responseJSON;
        if (errorJson.message == "param 'moduleimpl_id': etud non inscrit") {
          const html = `
          <h3>L'étudiant n'est pas inscrit à ce module</h3>
          `;
          const div = document.createElement("div");
          div.innerHTML = html;
          openAlertModal("Erreur Module", div);
          return;
        }
        if (
          errorJson.message ==
          "param 'moduleimpl_id' : le moduleimpl_id ne peut pas être nul"
        ) {
          const html = `
          <h3>Un module doit être spécifié</h3>
          `;
          const div = document.createElement("div");
          div.innerHTML = html;
          openAlertModal("Erreur Module", div);
          return;
        }
      } catch (e) {
        console.error(e);
        //errorAlert();
      }
    }
  );

  return bool;
}

/**
 * Récupération des assiduités conflictuelles avec la période de la timeline
 * @param {String | Number} etudid identifiant de l'étudiant
 * @returns {Array[Assiduité]} un tableau d'assiduité
 */
function getAssiduitesConflict(etudid, periode) {
  const etudAssiduites = assiduites[etudid];
  if (!etudAssiduites) {
    return [];
  }
  if (!periode) {
    periode = getTimeLineTimes();
  }

  return etudAssiduites.filter((assi) => {
    const interval = {
      deb: new Date(Date.removeUTC(assi.date_debut)),
      fin: new Date(Date.removeUTC(assi.date_fin)),
    };
    const test = hasTimeConflict(periode, interval);
    return test;
  });
}

/**
 * Récupération de la dernière assiduité du jour précédent
 * @param {String | Number} etudid l'identifiant de l'étudiant
 * @returns {Assiduité} la dernière assiduité du jour précédent
 */
function getLastAssiduiteOfPrevDate(etudid) {
  const etudAssiduites = assiduites[etudid];
  if (!etudAssiduites) {
    return "";
  }
  const period = {
    deb: getPrevDate(),
    fin: getDate(),
  };
  const prevAssiduites = etudAssiduites
    .filter((assi) => {
      const interval = {
        deb: new Date(Date.removeUTC(assi.date_debut)),
        fin: new Date(Date.removeUTC(assi.date_fin)),
      };

      return hasTimeConflict(period, interval);
    })
    .sort((a, b) => {
      const a_fin = new Date(Date.removeUTC(a.date_fin));
      const b_fin = new Date(Date.removeUTC(b.date_fin));
      return b_fin < a_fin;
    });

  if (prevAssiduites.length < 1) {
    return null;
  }

  return prevAssiduites.pop();
}

/**
 * Récupération de l'état appointé
 * @param {HTMLFieldSetElement} field le conteneur des boutons d'assiduité d'une ligne étudiant
 * @returns {String} l'état appointé : ('present','absent','retard', 'remove')
 *
 * état = 'remove' si le clic désélectionne une assiduité appointée
 */
function getAssiduiteValue(field) {
  const checkboxs = Array.from(field.children);
  let value = "remove";
  checkboxs.forEach((chbox) => {
    if (chbox.checked) {
      value = chbox.value;
    }
  });

  return value;
}

/**
 * Mise à jour des assiduités d'un étudiant
 * @param {String | Number} etudid identifiant de l'étudiant
 */
function actualizeEtudAssiduite(etudid) {
  const date_debut = getPrevDate().toFakeIso();
  const date_fin = getNextDate().toFakeIso();

  const url_api =
    getUrl() +
    `/api/assiduites/${etudid}/query?date_debut=${date_debut}&date_fin=${date_fin}`;
  sync_get(url_api, (data, status) => {
    if (status === "success") {
      assiduites[etudid] = data;
    }
  });
}

function getAllAssiduitesFromEtud(
  etudid,
  action,
  order = false,
  justifs = false,
  courant = false
) {
  const url_api =
    getUrl() +
    `/api/assiduites/${etudid}${
      order
        ? "/query?order%°"
            .replace("%", justifs ? "&with_justifs" : "")
            .replace("°", courant ? "&courant" : "")
        : ""
    }`;
  //TODO Utiliser async_get au lieu de jquery
  $.ajax({
    async: true,
    type: "GET",
    url: url_api,
    success: (data, status) => {
      if (status === "success") {
        assiduites[etudid] = data;
        action(data);
      }
    },
    error: () => {},
  });
}

/**
 * Déclenchement d'une action après appuie sur un bouton d'assiduité
 * @param {HTMLInputElement} element Bouton d'assiduité appuyé
 */
function assiduiteAction(element) {
  const field = element.parentElement;

  const type = field.getAttribute("type");
  const etudid = parseInt(field.getAttribute("etudid"));
  const assiduite_id = parseInt(field.getAttribute("assiduite_id"));
  const etat = getAssiduiteValue(field);

  // Cas de l'action de masse -> peuplement des queues
  if (currentMassAction) {
    if (currentMassActionEtat != "remove") {
      switch (type) {
        case "création":
          addToMassActionQueue("creer", { etat: etat, etudid: etudid });
          break;
        case "édition":
          if (etat != "remove") {
            addToMassActionQueue("editer", {
              etat: etat,
              assiduite_id: assiduite_id,
            });
          }
          break;
      }
    } else if (type == "édition") {
      addToMassActionQueue("supprimer", assiduite_id);
    }
  } else {
    // Cas normal -> mise à jour en base
    let done = false;
    switch (type) {
      case "création":
        done = createAssiduite(etat, etudid);
        break;
      case "édition":
        if (etat === "remove") {
          done = deleteAssiduite(assiduite_id);
        } else {
          done = editAssiduite(
            assiduite_id,
            etat,
            assiduites[etudid].filter((a) => a.assiduite_id == assiduite_id)
          );
        }
        break;
      case "conflit":
        const conflitResolver = new ConflitResolver(
          assiduites[etudid],
          getTimeLineTimes(),
          {
            deb: getDate(),
            fin: getNextDate(),
          }
        );
        const update = (assi) => {
          actualizeEtud(assi.etudid);
        };
        conflitResolver.callbacks = {
          delete: update,
          edit: update,
          split: update,
        };

        conflitResolver.open();
        return;
    }

    if (type != "conflit" && done) {
      let etatAffiche;

      switch (etat.toUpperCase()) {
        case "PRESENT":
          etatAffiche =
            "%etud% a été noté(e) <u><strong>présent(e)</strong></u>";
          break;
        case "RETARD":
          etatAffiche =
            "%etud% a été noté(e) <u><strong>en retard</strong></u>";
          break;
        case "ABSENT":
          etatAffiche =
            "%etud% a été noté(e) <u><strong>absent(e)</strong></u>";
          break;
        case "REMOVE":
          etatAffiche = "L'assiduité de %etud% a été retirée.";
      }

      const nom_prenom = `${etuds[etudid].nom.toUpperCase()} ${etuds[
        etudid
      ].prenom.capitalize()}`;
      const span = document.createElement("span");
      span.innerHTML = etatAffiche.replace("%etud%", nom_prenom);

      pushToast(
        generateToast(span, getToastColorFromEtat(etat.toUpperCase()), 5)
      );
    }

    actualizeEtud(etudid, !isSingleEtud);
  }
}

// <<== Gestion de l'affichage des barres étudiant ==>>

/**
 * Génère l'HTML lié à la barre d'un étudiant
 * @param {Etudiant} etud représentation objet d'un étudiant
 * @param {Number} index l'index de l'étudiant dans la liste
 * @param {AssiduitéMod} assiduite Objet représentant l'état de l'étudiant pour la période de la timeline
 * @returns {String} l'HTML généré
 */
function generateEtudRow(
  etud,
  index,
  assiduite = {
    etatAssiduite: "",
    type: "création",
    id: -1,
    date_debut: null,
    date_fin: null,
    prevAssiduites: "",
  }
) {
  // Génération des boutons du choix de l'assiduité
  let assi = "";
  ["present", "retard", "absent"].forEach((abs) => {
    if (abs.toLowerCase() === assiduite.etatAssiduite.toLowerCase()) {
      assi += `<input checked type="checkbox" value="${abs}" name="btn_assiduites_${index}" id="rbtn_${abs}" class="rbtn ${abs}" title="${abs}">`;
    } else {
      assi += `<input type="checkbox" value="${abs}" name="btn_assiduites_${index}" id="rbtn_${abs}" class="rbtn ${abs}" title="${abs}">`;
    }
  });
  const conflit = assiduite.type == "conflit" ? "conflit" : "";
  const pdp_url = `${getUrl()}/api/etudiant/etudid/${etud.id}/photo?size=small`;

  let defdem = "";

  try {
    if (etud.id in etudsDefDem) {
      defdem = etudsDefDem[etud.id] == "D" ? "dem" : "def";
    }
  } catch (_) {}

  const HTML = `<div class="etud_row ${conflit} ${defdem}" id="etud_row_${
    etud.id
  }">

    <div class="index">${index}</div>
    <div class="name_field">

        <img class="pdp" src="${pdp_url}">

        <a class="name_set" href="bilan_etud?etudid=${etud.id}">
            <h4 class="nom">${etud.nom}</h4>
            <h5 class="prenom">${etud.prenom}</h5>
        </a>

    </div>
    <div class="assiduites_bar">
        <div id="prevDateAssi" class="${assiduite.prevAssiduites?.etat?.toLowerCase()}">
        </div>
    </div>
    <fieldset class="btns_field single" etudid="${etud.id}" assiduite_id="${
    assiduite.id
  }" type="${assiduite.type}">

        ${assi}

    </fieldset>


  </div>`;

  return HTML;
}

/**
 * Insertion de la ligne étudiant
 * @param {Etudiant} etud l'objet représentant un étudiant
 * @param {Number} index le n° de l'étudiant dans la liste des étudiants
 * @param {boolean} output ajout automatique dans la page ou non (default : Non)
 * @returns {String} HTML si output sinon rien
 */
function insertEtudRow(etud, index, output = false) {
  const etudHolder = document.querySelector(".etud_holder");
  const conflict = getAssiduitesConflict(etud.id);
  const prevAssiduite = getLastAssiduiteOfPrevDate(etud.id);
  let assiduite = {
    etatAssiduite: "",
    type: "création",
    id: -1,
    date_debut: null,
    date_fin: null,
    prevAssiduites: prevAssiduite,
  };
  if (conflict.length > 0) {
    assiduite.etatAssiduite = conflict[0].etat;

    assiduite.id = conflict[0].assiduite_id;
    assiduite.date_debut = Date.removeUTC(conflict[0].date_debut);
    assiduite.date_fin = Date.removeUTC(conflict[0].date_fin);
    if (isConflictSameAsPeriod(conflict[0])) {
      assiduite.type = "édition";
    } else {
      assiduite.type = "conflit";
    }
  }
  let row = generateEtudRow(etud, index, assiduite);

  if (output) {
    return row;
  }
  etudHolder.insertAdjacentHTML("beforeend", row);

  row = document.getElementById(`etud_row_${etud.id}`);
  const prev = row.querySelector("#prevDateAssi");
  setupAssiduiteBuble(prev, prevAssiduite);
  const bar = row.querySelector(".assiduites_bar");

  bar.appendChild(createMiniTimeline(assiduites[etud.id]));

  if (!verifyDateInSemester() || readOnly) {
    row.querySelector(".btns_field.single").setAttribute("disabled", "true");
  }
}

/**
 * Mise à jour d'une ligne étudiant
 * @param {String | Number} etudid l'identifiant de l'étudiant
 */
function actualizeEtud(etudid) {
  actualizeEtudAssiduite(etudid);
  //Actualize row
  const etudHolder = document.querySelector(".etud_holder");
  const ancient_row = document.getElementById(`etud_row_${etudid}`);

  let new_row = document.createElement("div");
  new_row.innerHTML = insertEtudRow(
    etuds[etudid],
    ancient_row.querySelector(".index").textContent,
    true
  );
  setupCheckBox(new_row.firstElementChild);
  const bar = new_row.firstElementChild.querySelector(".assiduites_bar");
  bar.appendChild(createMiniTimeline(assiduites[etudid]));
  const prev = new_row.firstElementChild.querySelector("#prevDateAssi");
  if (isSingleEtud()) {
    prev.classList.add("single");
  }
  setupAssiduiteBuble(prev, getLastAssiduiteOfPrevDate(etudid));
  etudHolder.replaceChild(new_row.firstElementChild, ancient_row);
}

/**
 * Génération de toutes les lignes étudiant
 */
function generateAllEtudRow() {
  if (isSingleEtud()) {
    try {
      actualizeEtud(etudid);
    } catch (ignored) {}
    return;
  }

  if (!document.querySelector(".selectors")?.disabled) {
    return;
  }

  document.querySelector(".etud_holder").innerHTML = "";
  etuds_ids = Object.keys(etuds).sort((a, b) =>
    etuds[a].nom > etuds[b].nom ? 1 : etuds[b].nom > etuds[a].nom ? -1 : 0
  );

  let mod = getModuleImplId();
  etuds_ids = etuds_ids.filter((i) => {
    return checkInscriptionModule(mod, i);
  });

  for (let i = 0; i < etuds_ids.length; i++) {
    const etud = etuds[etuds_ids[i]];
    insertEtudRow(etud, i + 1);
  }

  setupCheckBox();
}

// <== Gestion du modal de conflit ==>

// <<== Gestion de la récupération d'informations ==>>

/**
 * Récupération des ids des groupes
 * @returns la liste des ids des groupes
 */
function getGroupIds() {
  const btns = document.querySelector(".multiselect-container.dropdown-menu");

  const groups = Array.from(btns.querySelectorAll(".active")).map((el) => {
    return el.querySelector("input").value;
  });

  return groups;
}

/**
 * Récupération du moduleimpl_id
 * @returns {String} l'identifiant ou null si inéxistant
 */
function getModuleImplId() {
  const val = document.querySelector("#moduleimpl_select")?.value;
  return ["", undefined, null].includes(val) ? null : val;
}

function setModuleImplId(assiduite, module = null) {
  const moduleimpl = module == null ? getModuleImplId() : module;
  if (moduleimpl === "autre") {
    if (
      assiduite.hasOwnProperty("external_data") &&
      assiduite.external_data != null
    ) {
      if (assiduite.external_data.hasOwnProperty("module")) {
        assiduite.external_data.module = "Autre";
      } else {
        assiduite["external_data"] = { module: "Autre" };
      }
    } else {
      assiduite["external_data"] = { module: "Autre" };
    }
    assiduite.moduleimpl_id = null;
  } else {
    assiduite["moduleimpl_id"] = moduleimpl;
    if (
      assiduite.hasOwnProperty("external_data") &&
      assiduite.external_data != null
    ) {
      if (assiduite.external_data.hasOwnProperty("module")) {
        delete assiduite.external_data.module;
      }
    }
  }
  return assiduite;
}

/**
 * Récupération de l'id du formsemestre
 * @returns {String} l'identifiant du formsemestre
 */
function getFormSemestreId() {
  return document.querySelector(".formsemestre_id").textContent;
}

/**
 * Récupère la période du semestre
 * @returns {object} période {deb,fin}
 */
function getFormSemestreDates() {
  const dateDeb = document.getElementById(
    "formsemestre_date_debut"
  ).textContent;
  const dateFin = document.getElementById("formsemestre_date_fin").textContent;

  return {
    deb: new Date(dateDeb),
    fin: new Date(dateFin),
  };
}

/**
 * Récupère un objet étudiant à partir de son id
 * @param {Number} etudid
 */
function getSingleEtud(etudid) {
  sync_get(getUrl() + `/api/etudiant/etudid/${etudid}`, (data) => {
    etuds[etudid] = data;
  });
}

function isSingleEtud() {
  return location.href.includes("ajout_assiduite_etud");
}

function getCurrentAssiduiteModuleImplId() {
  const currentAssiduites = getAssiduitesConflict(etudid);
  if (currentAssiduites.length > 0) {
    let mod = currentAssiduites[0].moduleimpl_id;
    if (
      mod == null &&
      currentAssiduites[0].hasOwnProperty("external_data") &&
      currentAssiduites[0].external_data != null &&
      currentAssiduites[0].external_data.hasOwnProperty("module")
    ) {
      mod = currentAssiduites[0].external_data.module;
    }
    return mod == null ? "" : mod;
  }
  return "";
}

function getCurrentAssiduite(etudid) {
  const field = document.querySelector(
    `fieldset.btns_field.single[etudid='${etudid}']`
  );

  if (!field) return null;

  const assiduite_id = parseInt(field.getAttribute("assiduite_id"));
  const type = field.getAttribute("type");

  if (type == "édition") {
    let assi = null;
    assiduites[etudid].forEach((a) => {
      if (a.assiduite_id === assiduite_id) {
        assi = a;
      }
    });
    return assi;
  } else {
    return null;
  }
}

// <<== Gestion de la justification ==>>

function getJustificatifFromPeriod(date, etudid, update) {
  $.ajax({
    async: true,
    type: "GET",
    url:
      getUrl() +
      `/api/justificatifs/${etudid}/query?date_debut=${date.deb
        .add(1, "seconds")
        .toFakeIso()}&date_fin=${date.fin.add(-1, "seconds").toFakeIso()}`,
    success: (data) => {
      update(data);
    },
    error: () => {},
  });
}

function updateJustifyBtn() {
  if (isSingleEtud()) {
    const assi = getCurrentAssiduite(etudid);

    const just = assi ? !assi.est_just : false;
    const btn = document.getElementById("justif-rapide");
    if (!just) {
      btn.setAttribute("disabled", "true");
    } else {
      btn.removeAttribute("disabled");
    }
  }
}

function fastJustify(assiduite) {
  if (assiduite.etat == "PRESENT") {
    openAlertModal(
      "Attention",
      document.createTextNode("Une présence ne peut être justifiée.")
    );
    return;
  }

  const period = {
    deb: new Date(Date.removeUTC(assiduite.date_debut)),
    fin: new Date(Date.removeUTC(assiduite.date_fin)),
  };
  const action = (justifs) => {
    //créer un nouveau justificatif
    // Afficher prompt -> demander raison et état

    const success = () => {
      const raison = document.getElementById("promptText").value;
      const etat = document.getElementById("promptSelect").value;

      //créer justificatif

      const justif = {
        date_debut: new Date(Date.removeUTC(assiduite.date_debut)).toFakeIso(),
        date_fin: new Date(Date.removeUTC(assiduite.date_fin)).toFakeIso(),
        raison: raison,
        etat: etat,
      };

      createJustificatif(justif);

      generateAllEtudRow();
      try {
        loadAll();
      } catch {}
    };

    const content = document.createElement("fieldset");

    const htmlPrompt = `<legend>Entrez l'état du justificatif :</legend>
    <select name="promptSelect" id="promptSelect" required>
      <option value="valide">Valide</option>
      <option value="attente">En Attente de validation</option>
      <option value="non_valide">Non Valide</option>
      <option value="modifie">Modifié</option>
  </select>
  <legend>Raison:</legend>
  <textarea type="text" placeholder="Explication du justificatif (non obligatoire)" id="promptText" style="width:100%;"></textarea>
  `;

    content.innerHTML = htmlPrompt;

    openPromptModal(
      "Nouveau justificatif (Rapide)",
      content,
      success,
      () => {},
      "var(--color-primary)"
    );
  };
  if (assiduite.etudid) {
    getJustificatifFromPeriod(period, assiduite.etudid, action);
  }
}

function justifyAssiduite(assiduite_id, justified) {
  const assiduite = {
    est_just: justified,
  };
  const path = getUrl() + `/api/assiduite/${assiduite_id}/edit`;
  let bool = false;
  sync_post(
    path,
    assiduite,
    (data, status) => {
      bool = true;
    },
    (data, status) => {
      //error
      console.error(data, status);
      errorAlert();
    }
  );

  return bool;
}

function createJustificatif(justif, success = () => {}) {
  const path = getUrl() + `/api/justificatif/${etudid}/create`;
  sync_post(path, [justif], success, (data, status) => {
    //error
    console.error(data, status);
    errorAlert();
  });
}

function getAllJustificatifsFromEtud(
  etudid,
  action,
  order = false,
  courant = false
) {
  const url_api =
    getUrl() +
    `/api/justificatifs/${etudid}${
      order ? "/query?order°".replace("°", courant ? "&courant" : "") : ""
    }`;

  //TODO Utiliser async_get au lieu de jquery
  $.ajax({
    async: true,
    type: "GET",
    url: url_api,
    success: (data, status) => {
      if (status === "success") {
        action(data);
      }
    },
    error: () => {},
  });
}

function deleteJustificatif(justif_id) {
  const path = getUrl() + `/api/justificatif/delete`;
  sync_post(
    path,
    [justif_id],
    (data, status) => {
      //success
      if (data.success.length > 0) {
      }
    },
    (data, status) => {
      //error
      console.error(data, status);
      errorAlert();
    }
  );
}

function errorAlert() {
  const html = `
  <h4>Il peut s'agir d'un problème de droits, ou d'une modification survenue sur le serveur.</h4>
  <p>Si le problème persiste, demandez de l'aide sur le Discord d'assistance de ScoDoc</p>

  `;
  const div = document.createElement("div");
  div.innerHTML = html;
  openAlertModal("Une erreur s'est produite", div);
}

const moduleimpls = {};

function getModuleImpl(assiduite) {
  if (assiduite == null) return "Pas de module";
  const id = assiduite.moduleimpl_id;

  if (id == null || id == undefined) {
    if (
      assiduite.hasOwnProperty("external_data") &&
      assiduite.external_data != null &&
      assiduite.external_data.hasOwnProperty("module")
    ) {
      return assiduite.external_data.module == "Autre"
        ? "Tout module"
        : assiduite.external_data.module;
    } else {
      return "Pas de module";
    }
  }

  if (id in moduleimpls) {
    return moduleimpls[id];
  }
  const url_api = getUrl() + `/api/moduleimpl/${id}`;
  sync_get(
    url_api,
    (data) => {
      moduleimpls[id] = `${data.module.code} ${data.module.abbrev}`;
    },
    (data) => {
      moduleimpls[id] = "Pas de module";
    }
  );

  return moduleimpls[id];
}

// le nom de l'utilisateur à afficher
function getUser(obj) {
  if (
    obj.hasOwnProperty("external_data") &&
    obj.external_data != null &&
    obj.external_data.hasOwnProperty("enseignant")
  ) {
    return obj.external_data.enseignant;
  }

  return obj.user_nom_complet || obj.user_id;
}

const inscriptionsModule = {};

function checkInscriptionModule(moduleimpl_id, etudid) {
  if ([null, "", "autre"].indexOf(moduleimpl_id) !== -1) return true;
  if (!inscriptionsModule.hasOwnProperty(moduleimpl_id)) {
    const path = getUrl() + `/api/moduleimpl/${moduleimpl_id}/inscriptions`;
    sync_get(
      path,
      (data, status) => {
        inscriptionsModule[moduleimpl_id] = data;
      },
      (data, status) => {
        //error
        console.error(data, status);
        errorAlert();
      }
    );
  }
  const etudsInscrits = inscriptionsModule[moduleimpl_id].map((i) => i.etudid);
  return etudsInscrits.indexOf(Number(etudid)) !== -1;
}