/*
  Exemple pédagogique : clavier numérique aléatoire côté navigateur.

  Point important pour l'article :
  ce script améliore l'interface de saisie, mais il ne crée pas un espace secret.
  Tout ce qui est exécuté dans le navigateur peut être inspecté, modifié ou rejoué
  depuis un environnement compromis.
*/

// Longueur attendue du code.
// Elle doit rester cohérente avec la vérification PHP.
const PIN_LENGTH = 4;

// Code saisi pendant l'interaction.
// Cette valeur existe côté client : elle n'est donc pas secrète.
let currentPin = [];

// Éléments de l'interface.
// On les sélectionne une seule fois pour les réutiliser ensuite dans les fonctions.
const pad = document.querySelector(".pin-pad");
const dots = document.querySelectorAll(".dot");
const result = document.querySelector(".pin-result");
const clearLastButton = document.querySelector("#clear-last");
const clearAllButton = document.querySelector("#clear-all");

// Au chargement, on prépare le clavier et l'affichage de départ.
document.addEventListener("DOMContentLoaded", () => {
  generatePad();
  updateDisplay();
});

// Génère les touches du clavier dans un ordre aléatoire.
function generatePad() {
  const digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];

  // Le mélange change l'emplacement des chiffres.
  // Cela peut gêner certains scénarios très simples, comme une mémorisation de positions fixes.
  const shuffledDigits = shuffle(digits);

  // On vide le clavier avant de le reconstruire.
  // C'est utile après une erreur ou une réinitialisation.
  pad.innerHTML = "";

  shuffledDigits.forEach((digit) => {
    const button = document.createElement("button");

    button.type = "button";
    button.textContent = digit;

    /*
      Cette valeur est pratique pour le script : au clic, on saura quel chiffre ajouter.

      Mais elle montre aussi une limite importante :
      le chiffre reste présent dans le DOM, donc observable avec les outils du navigateur.
      L'interface masque la saisie à l'utilisateur, mais elle ne cache pas réellement
      la logique au client.
    */
    button.dataset.value = digit;

    button.addEventListener("click", handleDigitClick);

    pad.appendChild(button);
  });
}

// Mélange un tableau avec l'algorithme de Fisher-Yates.
// Nous créons une copie pour ne pas modifier le tableau d'origine.
function shuffle(values) {
  const copy = [...values];

  for (let i = copy.length - 1; i > 0; i--) {
    const randomIndex = Math.floor(Math.random() * (i + 1));

    // Échange de deux valeurs du tableau.
    [copy[i], copy[randomIndex]] = [copy[randomIndex], copy[i]];
  }

  return copy;
}

// Gère le clic sur une touche numérique.
function handleDigitClick(event) {
  // Si le code est déjà complet, on ignore les clics supplémentaires.
  if (currentPin.length >= PIN_LENGTH) {
    return;
  }

  // On lit le chiffre associé au bouton cliqué.
  const value = event.currentTarget.dataset.value;

  // On ajoute ce chiffre au code en cours.
  currentPin.push(value);

  // On efface un éventuel message précédent, puis on met à jour les pastilles.
  result.textContent = "";
  updateDisplay();

  // Quand la longueur attendue est atteinte, on envoie la saisie au serveur.
  if (currentPin.length === PIN_LENGTH) {
    sendPin();
  }
}

// Met à jour les pastilles de progression.
// Une pastille remplie signifie seulement : "un chiffre a été sélectionné ici".
function updateDisplay() {
  dots.forEach((dot, index) => {
    dot.classList.toggle("is-filled", index < currentPin.length);
  });
}

// Efface uniquement le dernier chiffre saisi.
clearLastButton.addEventListener("click", () => {
  if (currentPin.length === 0) {
    return;
  }

  currentPin.pop();
  result.textContent = "";
  updateDisplay();
});

// Réinitialise entièrement la saisie.
clearAllButton.addEventListener("click", () => {
  resetPin();
  result.textContent = "";
});

// Envoie le code au serveur pour vérification.
async function sendPin() {
  /*
    On transforme le tableau ["1", "2", "3", "4"] en chaîne "1234".
    Cette chaîne sera transmise au serveur.

    Dans une vraie application, cette requête doit impérativement passer par HTTPS.
    Le JavaScript seul ne protège pas le transport des données.
  */
  const pin = currentPin.join("");

  // On désactive temporairement les boutons pour éviter les doubles envois.
  disablePad(true);
  result.textContent = "Vérification en cours…";

  try {
    const response = await fetch("verify.php", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ pin })
    });

    // Si le serveur répond avec une erreur HTTP, on bascule vers le bloc catch.
    if (!response.ok) {
      throw new Error("Réponse serveur invalide.");
    }

    const data = await response.json();

    handleServerResponse(data);
  } catch (error) {
    /*
      Message volontairement générique.
      En production, on évite de révéler trop de détails sur l'erreur rencontrée.
    */
    result.textContent = "Erreur lors de la vérification.";
    resetPin();
  } finally {
    // On réactive l'interface, sauf si le serveur a demandé un blocage durable.
    disablePad(false);
  }
}

// Traite la réponse JSON renvoyée par verify.php.
function handleServerResponse(data) {
  result.textContent = data.message || "Réponse reçue.";

  switch (data.status) {
    case "accepted":
      /*
        Pour la démonstration, on laisse le message affiché.
        Dans un vrai parcours, on ouvrirait l'accès attendu ou on redirigerait
        vers une zone protégée, après vérification serveur.
      */
      break;

    case "refused":
      // En cas de refus, on réinitialise et on mélange à nouveau le clavier.
      resetPin();
      break;

    case "wait":
      /*
        Le serveur demande d'attendre avant une nouvelle tentative.
        Cette temporisation côté serveur est plus importante que l'interface elle-même.
      */
      resetPin();

      if (data.remaining_seconds) {
        result.textContent =
          `Veuillez patienter ${data.remaining_seconds} seconde(s) avant de réessayer.`;
      }
      break;

    case "blocked":
      /*
        Le serveur estime que trop de tentatives ont été faites.
        L'interface est désactivée côté client, mais le vrai blocage doit rester côté serveur.
      */
      resetPin();
      disablePad(true);
      break;

    case "error":
    default:
      // Réponse inattendue ou erreur de format : on revient à un état propre.
      resetPin();
      break;
  }
}

// Réinitialise le code en cours et mélange à nouveau le clavier.
function resetPin() {
  currentPin = [];
  updateDisplay();
  generatePad();
}

// Active ou désactive les touches du clavier et les boutons d'action.
function disablePad(disabled) {
  const buttons = pad.querySelectorAll("button");

  buttons.forEach((button) => {
    button.disabled = disabled;
  });

  clearLastButton.disabled = disabled;
  clearAllButton.disabled = disabled;
}