/**
 *
 * Ensemble des fonctions liées à la gestion des assiduités
 * Créé par : HARTMANN Matthias (Iziram)
 *
 */

/**
 * <== OUTILS ==>
 */

/**
 * 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,
});

/**
 * 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) {
  const response = fetch(path);
  response
    .then((response) => {
      if (response.ok) {
        response.json().then((data) => {
          success(data, "success");
        });
      } 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 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) {
  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 {
      if (response.status == 404) {
        response.json().then((data) => {
          if (errors) errors(data);
        });
      } else {
        throw new Error("Network response was not ok.");
      }
    }
  } catch (error) {
    console.error(error);
    if (errors) errors(error);
  }

  return response;
}

/**
 * Récupère les étudiants en fonction des groupes sélectionnés
 * @param {Array} groupIds - Les identifiants des groupes pour lesquels récupérer les étudiants.
 * @returns {Promise<Object>} Un objet contenant les étudiants, indexés par leur identifiant.
 */
async function recupEtuds(groupIds) {
  const etuds = new Map();
  if (groupIds == null || groupIds.length == 0) return etuds;

  // Créer un tableau de promesses pour chaque requête GET asynchrone.
  let requests = groupIds.map((groupId) =>
    fetch(`../../api/group/${groupId}/etudiants`)
      .then((response) => {
        if (!response.ok) {
          throw new Error("Network response was not ok");
        }
        return response.json();
      })
      .then((data) => {
        data.forEach((etud) => {
          etuds.set(etud.id, etud);
        });
      })
      .catch((error) =>
        console.error(
          "There has been a problem with your fetch operation:",
          error
        )
      )
  );

  // Attendre que toutes les promesses dans le tableau `requests` soient résolues.
  await Promise.all(requests);

  return etuds;
}
/**
 * Récupère l'assiduité des étudiants pour une date donnée
 * @param {Map} etuds
 * @param {Date} date
 */
async function recupAssiduites(etuds, date) {
  const etudIds = [...etuds.keys()].join(",");

  const date_debut = date.add(-1, "days").format("YYYY-MM-DDTHH:mm");
  const date_fin = date.add(2, "days").format("YYYY-MM-DDTHH:mm");

  url =
    `../../api/assiduites/group/query?date_debut=${date_debut}` +
    `&date_fin=${date_fin}&etudids=${etudIds}&with_justifs`;

  await fetch(url)
    .then((res) => {
      if (!res.ok) {
        throw new Error("Network response was not ok");
      }
      return res.json();
    })
    .then((data) => {
      Object.keys(data).forEach((etudid) => {
        const etud = etuds.get(Number(etudid));
        const assiduites = data[etudid];
        etud.assiduites = assiduites;
      });
    })
    .catch((error) =>
      console.error(
        "There has been a problem with your fetch operation:",
        error
      )
    );
}

/**
 * Génération ligne étudiante
 */
function creerLigneEtudiant(etud, index) {
  let currentAssiduite = {
    etat: "",
    type: "creation",
    assiduite_id: -1,
    date_debut: null,
    date_fin: null,
  };

  /**Retourne une liste d'assiduité en conflit avec la période actuelle
   * @param {Array} assiduites - Les assiduités de l'étudiant
   * @returns {Array} Les assiduités en conflit
   */
  function recupConflitsAssiduites(assiduites) {
    const period = getPeriodAsDate();

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

      return (
        period.deb.isBefore(interval.fin) && period.fin.isAfter(interval.deb)
      );
    });
  }
  // Pas de conflit en readonly
  const conflits = readOnly ? [] : recupConflitsAssiduites(etud.assiduites);

  // Si il y a des conflits, on prend le premier pour l'afficher
  // si les dates de début et de fin sont les mêmes, c'est une édition
  // sinon c'est un conflit
  if (conflits.length > 0) {
    currentAssiduite = conflits[0];

    const conflitsPeriode = {
      deb: new Date(Date.removeUTC(currentAssiduite.date_debut)),
      fin: new Date(Date.removeUTC(currentAssiduite.date_fin)),
    };
    const period = getPeriodAsDate();
    currentAssiduite.type =
      period.deb.isSame(conflitsPeriode.deb) &&
      period.fin.isSame(conflitsPeriode.fin)
        ? "edition"
        : "conflit";
  }

  // Création de la ligne étudiante en DOM
  /* exemple de ligne étudiante
   <div class="etud_row" id="etud_row_497">
      <div class="index">1</div>
      <div class="name_field"><img src="../../api/etudiant/etudid/497/photo?size=small" alt="Baudin Joseph" class="pdp"><a
              class="name_set" href="bilan_etud?etudid=497">
              <h4 class="nom">Baudin</h4>
              <h5 class="prenom">Joseph</h5>
          </a></div>
      <div class="assiduites_bar">
          <div id="prevDateAssi" class="vide"></div>
          <div class="mini-timeline"><span class="mini_tick" style="left: 47.5%;">13h</span>
              <div class="mini-timeline-block creneau" style="left: 20%; width: 17.5%;"></div>
          </div>
      </div>
      <fieldset class="btns_field single" etudid="497" type="creation" assiduite_id="-1">
        <input
          type="checkbox"
          value="present"
          name="btn_assiduites_1"
          id="rbtn_present"
          class="rbtn present"
          title="present"
        >
        <input
          type="checkbox"
          value="retard"
          name="btn_assiduites_1"
          id="rbtn_retard"
          class="rbtn retard"
          title="retard"
        >
        <input
          type="checkbox"
          value="absent"
          name="btn_assiduites_1"
          id="rbtn_absent"
          class="rbtn absent"
          title="absent"
        >
      </fieldset>
  </div>
   */
  const ligneEtud = document.createElement("div");
  ligneEtud.classList.add("etud_row");
  if (Object.keys(etudsDefDem).includes(etud.id)) {
    ligneEtud.classList.add(etudsDefDem[etud.id] == "D" ? "dem" : "def");
  }
  ligneEtud.id = `etud_row_${etud.id}`;

  if (currentAssiduite.type === "conflit" && !readOnly)
    ligneEtud.classList.add("conflit");

  // div index avec l'index
  const indexDiv = document.createElement("div");
  indexDiv.classList.add("index");
  indexDiv.id = `etudid-${etud.id}`;

  indexDiv.textContent = index;

  ligneEtud.appendChild(indexDiv);

  // div name_field

  const nameField = document.createElement("div");
  nameField.classList.add("name_field");

  const pdp = document.createElement("img");
  pdp.src = `../../api/etudiant/etudid/${etud.id}/photo?size=small`;
  pdp.alt = `${etud.nom} ${etud.prenom}`;
  pdp.classList.add("pdp");
  nameField.appendChild(pdp);

  const nameSet = document.createElement("a");
  nameSet.classList.add("name_set");
  nameSet.id = `etudid-${etud.id}`;
  nameSet.href = `bilan_etud?etudid=${etud.id}`;

  const nom = document.createElement("h4");
  nom.classList.add("nom");
  nom.textContent = etud.nom;

  const prenom = document.createElement("h5");
  prenom.classList.add("prenom");
  prenom.textContent = etud.prenom;

  nameSet.appendChild(nom);
  nameSet.appendChild(prenom);

  nameField.appendChild(nameSet);
  ligneEtud.appendChild(nameField);

  // div assiduites_bar

  const assiduitesBar = document.createElement("div");
  assiduitesBar.classList.add("assiduites_bar");

  const prevDateAssi = document.createElement("div");
  prevDateAssi.id = "prevDateAssi";

  function recupDerniereAssiduite(assiduites) {
    const period = {
      deb: $("#date").datepicker("getDate").add(-1, "days"),
      fin: $("#date").datepicker("getDate"),
    };

    const lastAssiduite = assiduites
      .filter((assi) => {
        const interval = {
          deb: new Date(Date.removeUTC(assi.date_debut)),
          fin: new Date(Date.removeUTC(assi.date_fin)),
        };

        return (
          period.deb.isBefore(interval.fin) && period.fin.isAfter(interval.deb)
        );
      })
      .sort((a, b) => {
        return (
          new Date(Date.removeUTC(b.date_debut)) -
          new Date(Date.removeUTC(a.date_debut))
        );
      })
      .pop();

    return lastAssiduite ?? null;
  }

  const lastAssiduite = recupDerniereAssiduite(etud.assiduites);
  prevDateAssi.classList.add(lastAssiduite?.etat.toLowerCase() ?? "vide");
  setupAssiduiteBubble(prevDateAssi, lastAssiduite);
  assiduitesBar.appendChild(prevDateAssi);

  // div minitimeline
  assiduitesBar.appendChild(createMiniTimeline(etud.assiduites));
  ligneEtud.appendChild(assiduitesBar);

  // fieldset btns_field single
  const btnsField = document.createElement("fieldset");
  btnsField.classList.add("btns_field", "single");
  btnsField.setAttribute("etudid", etud.id);
  btnsField.setAttribute("type", currentAssiduite.type);
  btnsField.setAttribute("assiduite_id", currentAssiduite.assiduite_id);

  // Création des boutons d'assiduités
  if (readOnly) {
  } else if (currentAssiduite.type != "conflit") {
    const etats = ["retard", "absent"];

    if (!window.nonPresent) {
      etats.splice(0, 0, "present");
    }

    etats.forEach((abs) => {
      const btn = document.createElement("input");
      btn.type = "checkbox";
      btn.value = abs;
      btn.name = `btn_assiduites_${index}`;
      btn.id = `rbtn_${abs}`;
      btn.classList.add("rbtn", abs);
      btn.title = abs;

      btn.checked = abs === currentAssiduite?.etat.toLowerCase();

      // Une seule checkbox à la fois
      btn.addEventListener("click", () => {
        Array.from(btn.parentElement.children).forEach((chbox) => {
          if (chbox.checked && chbox.value !== btn.value) {
            chbox.checked = false;
          }
        });
      });

      // Action au clic
      btn.addEventListener("click", (e) => {
        actionAssiduite(
          etud,
          btn.value,
          currentAssiduite.type,
          currentAssiduite.type == "edition" ? currentAssiduite : null
        );
        e.preventDefault();
      });

      btnsField.appendChild(btn);
    });
  } else {
    const btn = document.createElement("input");
    btn.type = "checkbox";
    btn.value = "conflit";
    btn.name = `btn_assiduites_${index}`;
    btn.id = `rbtn_conflit`;
    btn.classList.add("rbtn", "conflit");
    btn.title = "conflit";

    // TODO : Ouvrir solveur

    const solveur = new ConflitResolver(etud.assiduites, getPeriodAsDate(), {
      deb: $("#date").datepicker("getDate"),
      fin: $("#date").datepicker("getDate").add(1, "days"),
    });

    const update = () => {
      MiseAJourLigneEtud(etud);
    };

    solveur.callbacks = {
      delete: update,
      edit: update,
      split: update,
    };

    btn.addEventListener("click", () => {
      solveur.open();
      btn.checked = false;
    });

    btnsField.appendChild(btn);
  }

  ligneEtud.appendChild(btnsField);

  // Attache les infos de l'étudiant (bulle etud_info)
  try {
    attach_etud_info(nameSet);
    attach_etud_info(indexDiv);
  } catch {}

  return ligneEtud;
}

/**
 * Génération de toutes les lignes étudiantes
 */

async function creerTousLesEtudiants(etuds) {
  const etudsDiv = document.querySelector(".etud_holder");
  etudsDiv.innerHTML = "";
  const moduleImplId = readOnly ? null : $("#moduleimpl_select").val();
  const inscriptions = await getInscriptionModule(moduleImplId);
  // on trie les étudiants par ordre alphabétique
  // et on garde ceux qui sont inscrits au module
  // puis pour chaque étudiant on crée une ligne
  [...etuds.values()]
    .sort((a, b) => {
      return a.sort_key > b.sort_key ? 1 : -1;
    })
    .filter((etud) => {
      return inscriptions == null || inscriptions.includes(etud.id);
    })
    .forEach((etud, index) => {
      etudsDiv.appendChild(creerLigneEtudiant(etud, index + 1));
    });
  // Récupère l'offset timezone serveur pour la date sélectionnée
  const date_iso = getSelectedDateIso();
  try {
    const res = await fetch(`../../api/assiduite/date_time_offset/${date_iso}`);
    if (!res.ok) {
      throw new Error("Network response was not ok");
    }
    const text = await res.text();
    SERVER_TIMEZONE_OFFSET = text;
  } catch (error) {
    console.error('Error:', error);
  }
}

/**
 * Récupère une version lisible du moduleimpl
 * @param {Object} assiduite
 * @returns {String}
 */
async function getModuleImpl(assiduite) {
  if (assiduite == null) return "Module non spécifié";
  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"
        ? "Autre module (pas dans la liste)"
        : assiduite.external_data.module;
    } else {
      return "Module non spécifié";
    }
  }

  if (id in moduleimpls) {
    return moduleimpls[id];
  }
  const url_api = `../../api/moduleimpl/${id}`;

  return await fetch(url_api)
    .then((res) => {
      if (!res.ok) {
        throw new Error("Network response was not ok");
      }
      return res.json();
    })
    .then((data) => {
      moduleimpls[id] = `${data.module.code} ${data.module.abbrev || ""}`;
      return moduleimpls[id];
    })
    .catch((_) => {
      moduleimpls[id] = "Pas de module";
      return moduleimpls[id];
    });
}
/**
 * Renvoie le moduleimpl_id de l'assiduité
 * ou l'external_data.module si le moduleimpl_id n'est pas défini
 * "" si aucun module n'est défini
 * @param {Object} assiduite
 * @returns {String}
 */
function getModuleImplId(assiduite) {
  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.toLowerCase();
    } else {
      return "";
    }
  } else {
    return id + "";
  }
}
/**
 * Récupère les etudid de tous les étudiants inscrits au module
 * @param {String} moduleimpl_id
 * @returns {Array}
 */
async function getInscriptionModule(moduleimpl_id) {
  if ([null, "", "autre"].includes(moduleimpl_id)) return null;
  if (!inscriptionsModules.has(moduleimpl_id)) {
    const path = `../../api/moduleimpl/${moduleimpl_id}/inscriptions`;
    await fetch(path)
      .then((res) => {
        if (!res.ok) {
          throw new Error("Network response was not ok");
        }
        return res.json();
      })
      .then((data) => {
        inscriptionsModules.set(
          moduleimpl_id,
          data.map((i) => i.etudid)
        );
      })
      .catch((_) => {
        inscriptionsModules.set(moduleimpl_id, []);
      });
  }

  return inscriptionsModules.get(moduleimpl_id);
}
// Mise à jour de la ligne étudiant
async function MiseAJourLigneEtud(etud) {
  //Récupérer ses assiduités
  function RecupAssiduitesEtudiant(etudid) {
    const date = $("#date").datepicker("getDate");
    const date_debut = date.add(-1, "days").format("YYYY-MM-DDTHH:mm");
    const date_fin = date.add(2, "days").format("YYYY-MM-DDTHH:mm");
    url =
      `../../api/assiduites/${etudid}/query?date_debut=${date_debut}` +
      `&date_fin=${date_fin}&with_justifs`;

    return fetch(url)
      .then((res) => {
        if (!res.ok) {
          throw new Error("Network response was not ok");
        }
        return res.json();
      })
      .then((data) => {
        etud.assiduites = data;
      })
      .catch((error) => {
        console.error(
          "There has been a problem with your fetch operation:",
          error
        );
      });
  }

  await RecupAssiduitesEtudiant(etud.id);
  // Une fois les assiduités récupérées, on met à jour la ligne étudiant
  // on replace l'ancienne ligne par la nouvellement générée

  const etudRow = document.getElementById(`etud_row_${etud.id}`);
  if (etudRow == null) return;

  const ligneEtud = creerLigneEtudiant(
    etud,
    document.querySelector(`#etud_row_${etud.id}`).querySelector(".index")
      .textContent
  );

  etudRow.replaceWith(ligneEtud);
}

// Action appelée lors d'un clic sur un bouton d'assiduité
// Création, édition ou suppression d'une assiduité
async function actionAssiduite(etud, etat, type, assiduite = null) {
  const modimpl_id = $("#moduleimpl_select").val();
  if (assiduite && assiduite.etat.toLowerCase() === etat) type = "suppression";

  const { deb, fin } = getPeriodAsISO(); // chaines sans timezone pour l'API
  // génération d'un objet assiduité basique qui sera complété
  let assiduiteObjet = assiduite ?? {
    date_debut: deb,
    date_fin: fin,
    etudid: etud.id,
  };

  assiduiteObjet.etat = etat;
  assiduiteObjet.moduleimpl_id = modimpl_id;
  // En fonction du type d'action on appelle la bonne route
  // avec les bonnes valeurs
  if (type === "creation") {
    await async_post(
      `../../api/assiduite/${etud.id}/create`,
      [assiduiteObjet],
      (data) => {
        if (data.success.length > 0) {
          MiseAJourLigneEtud(etud);
          envoiToastEtudiant(etat, etud);
        } else {
          console.error(data.errors["0"].message);
          erreurModuleImpl(data.errors["0"].message);
        }
      },
      (error) => {
        console.error("Erreur lors de la création de l'assiduité", error);
      }
    );
  } else if (type === "edition") {
    await async_post(
      `../../api/assiduite/${assiduite.assiduite_id}/edit`,
      {
        etat: assiduiteObjet.etat,
        moduleimpl_id: assiduiteObjet.moduleimpl_id,
      },
      (data) => {
        MiseAJourLigneEtud(etud);
        envoiToastEtudiant(etat, etud);
      },
      (error) => {
        console.error("Erreur lors de la modification de l'assiduité", error);
      }
    );
  } else if (type === "suppression") {
    await async_post(
      `../../api/assiduite/delete`,
      [assiduite.assiduite_id],
      (data) => {
        if (data.success.length > 0) {
          MiseAJourLigneEtud(etud);
          envoiToastEtudiant("remove", etud);
        } else {
          console.error(data.errors["0"].message);
          erreurModuleImpl(data.errors["0"].message);
        }
      },
      (error) => {
        console.error("Erreur lors de la suppression de l'assiduité", error);
      }
    );
  }
}
// Fonction pour afficher un message d'erreur si le module n'est pas renseigné
// ou si l'étudiant n'est pas inscrit au module.
// On donne le message d'erreur d'une requête api et cela affiche le message correspondant
function erreurModuleImpl(message) {
  if (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 (
    message == "L'étudiant n'est pas inscrit au module" ||
    message == "param 'moduleimpl_id': etud non inscrit"
  ) {
    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);
  }
  if (
    message == "La date de début n'est pas un jour travaillé"
  ) {
      const HTML = `
      <p>Attention, la date de début n'est pas un jour travaillé.</p>
      `;

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

      openAlertModal("Date de début", content);
  }
}
// Fonction pour ajouter en lot une assiduité à tous les étudiants
// Fonctionne uniquement pour créer ou supprimer des assiduités
// Pas d'édition possible
function mettreToutLeMonde(etat, el = null) {
  const lignesEtuds = [...document.querySelectorAll("fieldset.btns_field")];

  const { deb, fin } = getPeriodAsDate(true); // tz server
  const period_iso = getPeriodAsISO(); // chaines sans timezone pour l'API
  const deb_iso = period_iso.deb;
  const fin_iso = period_iso.fin;
  const assiduiteObjet = {
    date_debut: deb_iso,
    date_fin: fin_iso,
    etat: etat,
    moduleimpl_id: $("#moduleimpl_select").val(),
  };

  if (el != null) el.checked = false;

  // Suppression des assiduités
  if (etat == "vide") {
    if (!confirm("Effacer tout les évènements correspondant à cette plage ?")) {
      return; // annulation
    }
    // On récupère les lignes avec une seule assiduité
    let assiduites_id = lignesEtuds
      .filter((e) => e.getAttribute("type") == "edition")
      .map((e) => Number(e.getAttribute("assiduite_id")));

    // On récupère les assiduités conflictuelles mais qui sont comprises
    // dans la plage de suppression
    const unDeleted = {};
    lignesEtuds
      .filter((e) => e.getAttribute("type") == "conflit")
      .forEach((e) => {
        const etud = etuds.get(Number(e.getAttribute("etudid")));
        // On récupère les assiduités couvertes par la plage de suppression
        etud.assiduites.forEach((a) => {
          const date_debut = new Date(a.date_debut);
          const date_fin = new Date(a.date_fin);
          // On prend en compte uniquement les assiduités conflictuelles
          // (qui intersectent la plage de suppression)
          if (
            Date.intersect(
              { deb: deb, fin: fin }, // la plage, en Date avec timezone serveur
              { deb: date_debut, fin: date_fin } // dates de l'assiduité avec leur timezone
            )
          ) {
            // Si l'assiduité est couverte par la plage de suppression
            // On l'ajoute à la liste des assiduités à supprimer.
            if (
              date_debut.isBetween(deb, fin, "[]") &&
              date_fin.isBetween(deb, fin, "[]")
            ) {
              assiduites_id.push(a.assiduite_id);
            }
            // Sinon on ajoute l'étudiant à la liste des étudiants non gérés
            else {
              unDeleted[a.etudid] = true;
            }
          }
        });
      });

    afficheLoader();

    async_post(
      `../../api/assiduite/delete`,
      assiduites_id,
      async (data) => {
        retirerLoader();
        if (data.errors.length == 0) {
          await recupAssiduites(etuds, $("#date").datepicker("getDate"));
          creerTousLesEtudiants(etuds);
        } else {
          console.error(data.errors);
        }
        envoiToastTous("remove", assiduites_id.length);
        if (Object.keys(unDeleted).length == 0) return;
        // CAS : des assiduités d'étudiants n'ont pas pu être supprimés
        let unDeletedEtuds = `
        <ul>
        ${Object.keys(unDeleted)
          .map((etudid) => {
            const etud = etuds.get(Number(etudid));
            return `<li>${etud.civilite}. ${etud.nom.toUpperCase()} ${
              etud.prenom
            }</li>`;
          })
          .join("")}
        </ul>
        `;

        let html = `
        <p>Les assiduités des étudiants suivants n'ont pas été supprimées car elles ne sont pas incluses dans la plage de suppression :</p>
        ${unDeletedEtuds}
        `;
        const div = document.createElement("div");
        div.innerHTML = html;
        openAlertModal("Assiduité non supprimée", div);
      },
      (error) => {
        console.error("Erreur lors de la suppression de l'assiduité", error);
      }
    );

    return;
  }

  // Création
  const assiduitesACreer = lignesEtuds
    .filter((e) => e.getAttribute("type") == "creation")
    .map((e) => Number(e.getAttribute("etudid")));
  // création

  const promiseCreate = async_post(
    `../../api/assiduites/create`,
    assiduitesACreer.map((etudid) => {
      return { ...assiduiteObjet, etudid };
    }),
    async (data) => {
      if (data.errors.length > 0) {
        console.error(data.errors);
      }
    },
    (error) => {
      console.error("Erreur lors de la création de l'assiduité", error);
    }
  );

  // Affiche un loader
  afficheLoader();

  Promise.all([promiseCreate]).then(async () => {
    retirerLoader();
    await recupAssiduites(etuds, $("#date").datepicker("getDate"));
    creerTousLesEtudiants(etuds);
    envoiToastTous(etat, assiduitesACreer.length);
  });
}

// Affichage d'un loader (animation jeu pong)
function afficheLoader() {
  const loaderDiv = document.createElement("div");
  loaderDiv.id = "loader";
  const span = document.createElement("span");
  span.textContent = "Chargement en cours";
  loaderDiv.appendChild(span);
  const loader = document.createElement("div");
  loader.classList.add("loader");
  loaderDiv.appendChild(loader);
  document.body.appendChild(loaderDiv);
}
// Retrait du loader (animation jeu pong)
function retirerLoader() {
  document.getElementById("loader").remove();
}

// Simplification de l'envoie de toast pour un étudiant
// affiche le nom, le prénom et l'état de l'assiduité avec une couleur spécifique
function envoiToastEtudiant(etat, etud) {
  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 = `${etud.nom.toUpperCase()} ${etud.prenom.capitalize()}`;
  const span = document.createElement("span");
  span.innerHTML = etatAffiche.replace("%etud%", nom_prenom);

  pushToast(generateToast(span, getToastColorFromEtat(etat.toUpperCase()), 5));
}
// Fonction pour simplifier l'envoie de toast avec le bouton "mettre tout le monde"
// On donne un etat et un compte et cela affichera le message associé.
// ex : 12 assiduités ont été supprimées
// ex : 15 étudiants ont été mis Absent.
function envoiToastTous(etat, count) {
  const span = document.createElement("span");
  let etatAffiche = etat;
  switch (etat) {
    case "remove":
      if (count > 0) {
        span.innerHTML = `${count} assiduités ont été supprimées.`;
      } else {
        span.innerHTML = `Aucune assiduité n'a été supprimée.`;
      }
      break;
    case "retard":
      etatAffiche = "En retard";
    default:
      if (count > 0) {
        span.innerHTML = `${count} étudiants ont été mis <u><strong>${etatAffiche
          .capitalize()
          .trim()}</strong></u>`;
      } else {
        span.innerHTML = `Aucun étudiant n'a été mis <u><strong>${etatAffiche
          .capitalize()
          .trim()}</strong></u>`;
      }
      break;
  }

  pushToast(generateToast(span, getToastColorFromEtat(etat.toUpperCase()), 5));
}
// Permet de savoir si un jour est travaillé ou pas
// jour : Date
// nonWorkdays : Array[str] => ["mar", "sam", "dim"]
function estJourTravail(jour, nonWorkdays) {
  const d = Intl.DateTimeFormat("fr-FR", {
    timeZone: SCO_TIMEZONE,
    weekday: "short",
  })
    .format(jour)
    .replace(".", "");
  return !nonWorkdays.includes(d);
}

// Renvoie le dernier jour travaillé disponible.
// par défaut va en arrière (dans le passé)
// si anti == False => va dans le futur
function retourJourTravail(date, anti = true) {
  const jourMiliSecondes = 86400000; // 24 * 3600 * 1000 | H * s * ms
  let jour = date;
  let compte = 0;

  while (!estJourTravail(jour, nonWorkDays) && compte++ < 7) {
    let temps = anti
      ? jour - jourMiliSecondes
      : jour.valueOf() + jourMiliSecondes;
    jour = new Date(temps);
  }
  return jour;
}
// Vérifie si la date courante est travaillée
// Si ce n'est pas le cas, on change la date pour le dernier jour travaillé (passé)
// et on affiche une alerte
// (utilise le datepicker #date)
function dateCouranteEstTravaillee() {
  const date = $("#date").datepicker("getDate");

  if (!estJourTravail(date, nonWorkDays)) {
    // récupération du jour travaillé le plus proche
    const nouvelleDate = retourJourTravail(date);
    $("#date").datepicker("setDate", nouvelleDate);
    // Création du message d'alerte
    let msg = "Le jour sélectionné";
    if (new Date().format("YYYY-MM-DD") == date.format("YYYY-MM-DD")) {
      msg = "Aujourd'hui";
    }
    const att = document.createTextNode(
      `${msg} (${Date.toFRA(
        date.format("YYYY-MM-DD")
      )}) 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é : ${Date.toFRA(
          nouvelleDate.format("YYYY-MM-DD")
        )}.`
      )
    );
    // Affichage de l'alerte
    openAlertModal("Attention", div, "", "#eec660");

    return false;
  }
  return true;
}
// Fonction pour passer au jour suivant
// anti : bool => si true, on va dans le passé
function jourSuivant(anti = false) {
  let date = $("#date").datepicker("getDate");

  date = anti ? date.add(-1, "days") : date.add(1, "days");

  const nouvelleDate = retourJourTravail(date, anti);

  $("#date").datepicker("setDate", nouvelleDate);
  creerTousLesEtudiants(etuds);
}

/**
 * Ajout de la visualisation des assiduités de la mini timeline
 * @param {HTMLElement} el l'élément survollé
 * @param {Assiduité} assiduite l'assiduité représentée par l'élément
 */
function setupAssiduiteBubble(el, assiduite) {
  function formatDateModal(dateStr) {
    const date = new Date(Date.removeUTC(dateStr));
    return date.format("DD/MM/Y HH:mm");
  }

  if (!assiduite) return;

  const bubble = document.createElement("div");
  bubble.className = "assiduite-bubble";
  bubble.classList.add(assiduite.etat.toLowerCase());

  // Ajout d'un lien pour plus d'informations
  const infos = document.createElement("a");
  infos.className = "";
  infos.textContent = `ℹ️`;
  infos.title = "Détails / Modifier";
  infos.target = "_blank";
  infos.href = `edit_assiduite_etud/${assiduite.assiduite_id}`;

  const actionsDiv = document.createElement("div");
  actionsDiv.className = "assiduite-actions";
  actionsDiv.appendChild(infos);
  bubble.appendChild(actionsDiv);

  const stateDiv = document.createElement("div");
  stateDiv.className = "assiduite-state";
  stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
  bubble.appendChild(stateDiv);

  const idDiv = document.createElement("div");
  idDiv.className = "assiduite-id";
  getModuleImpl(assiduite).then((modImpl) => {
    idDiv.textContent = `${modImpl}`;
  });
  bubble.appendChild(idDiv);

  // Affichage des dates
  // si les jours sont les mêmes, on affiche "jour hh:mm - hh:mm"
  // sinon on affiche "jour hh:mm - jour hh:mm"
  const periodDiv = document.createElement("div");
  periodDiv.className = "assiduite-period";
  const dateDeb = new Date(Date.removeUTC(assiduite.date_debut));
  const dateFin = new Date(Date.removeUTC(assiduite.date_fin));
  if (dateDeb.isSame(dateFin, "day")) {
    const jour = dateDeb.format("DD/MM/YYYY");
    const deb = dateDeb.format("HH:mm");
    const fin = dateFin.format("HH:mm");
    periodDiv.textContent = `${jour} de ${deb} à ${fin}`;
  } else {
    const jourDeb = dateDeb.format("DD/MM/YYYY");
    const jourFin = dateFin.format("DD/MM/YYYY");
    periodDiv.textContent = `du ${jourDeb} au ${jourFin}`;
  }

  bubble.appendChild(periodDiv);

  const motifDiv = document.createElement("div");
  motifDiv.className = "assiduite-why";
  const motif = ["", null, undefined].includes(assiduite.desc)
    ? "Non spécifié"
    : assiduite.desc.capitalize();
  motifDiv.textContent = `Motif: ${motif}`;
  bubble.appendChild(motifDiv);

  const userIdDiv = document.createElement("div");
  userIdDiv.className = "assiduite-user_id";
  userIdDiv.textContent = `saisie le ${formatDateModal(
    assiduite.entry_date,
    " à "
  )}`;

  if (assiduite.user_id != null) {
    userIdDiv.textContent += `\npar ${assiduite.user_nom_complet}`;
  }
  bubble.appendChild(userIdDiv);

  el.appendChild(bubble);
}

/**
 * Permet d'afficher ou non les photos des étudiants
 * @param {boolean} checked
 */
function afficherPDP(checked) {
  if (checked) {
    gtrcontent.setAttribute("data-pdp", "true");
  } else {
    gtrcontent.removeAttribute("data-pdp");
  }

  // On sauvegarde le choix dans le localStorage
  localStorage.setItem("scodoc-etud-pdp", `${checked}`);
  pdp.checked = checked;
}