/* global React */
// ─── FloodsFlow — les blocs de la phase « Connexion API » ─────────────────────────────
//
// Deux blocs qui travaillent ensemble :
//
//   GAUCHE  « Comptes à brancher »   la liste de ce qui reste, avec l'etat de chaque lien
//   DROITE  « Authentification API » le lien du compte choisi, et la marche a suivre
//
// Ils se parlent par un petit magasin partage : cocher un compte a gauche remplit la droite.
// Deux composants React sans parent commun ne peuvent pas se passer d'etat autrement -- ils
// sont poses independamment dans le layout par le Phase Builder.
//
// LE MOT « BUNDLE » N'APPARAIT NULLE PART. Le VA voit « l'API » ; le fournisseur est un
// detail d'implementation qui n'a pas a circuler.
//
// CE QUI EST ENREGISTRE, ET OU :
//   window.CATALOGUE_BLOCS_EXTERNES  -> le Phase Builder les propose (lu a son chargement)
//   window.BLOCS_EXTERNES            -> device_view les affiche (lu a chaque rendu)

const FF_ETAPES = [
  "changer l'IP",
  "créer un conteneur Safari",
  "envoyer le lien de connexion sur le téléphone",
  "saisir les identifiants du compte",
  "autoriser l'accès API sur le compte Instagram",
  "supprimer le conteneur",
  "changer l'IP",
];

// ── le magasin partagé ────────────────────────────────────────────────────────────────
// Volontairement minuscule : un compte choisi, des abonnés. La liste des comptes est
// rechargée par chaque bloc — c'est le backend qui fait autorité, pas une copie locale qui
// pourrait diverger entre les deux blocs.
const FloodsStore = {
  choisi: null,
  _abonnes: [],
  sub(fn) {
    this._abonnes.push(fn);
    return () => { this._abonnes = this._abonnes.filter((f) => f !== fn); };
  },
  _prevenir() { this._abonnes.slice().forEach((f) => { try { f(); } catch (e) {} }); },
  choisir(compte) { this.choisi = compte; this._prevenir(); },
  rafraichir() { this._prevenir(); },
};

function useFloods() {
  const [, forcer] = React.useReducer((x) => x + 1, 0);
  React.useEffect(() => FloodsStore.sub(forcer), []);
  return FloodsStore;
}

const ffBase = () => (window.Backend && window.Backend.base) || "";

async function ffAppel(chemin, methode, corps) {
  const r = await fetch(ffBase() + chemin, {
    method: methode || "GET",
    headers: { "content-type": "application/json" },
    body: corps === undefined ? undefined : JSON.stringify(corps),
    cache: "no-store",
  });
  return r.json().catch(() => ({ error: "réponse illisible du backend" }));
}

const ffCarte = {
  background: "var(--surface)", border: "1px solid var(--border)", borderRadius: 12,
  overflow: "hidden",
};
const ffEntete = {
  display: "flex", alignItems: "center", gap: 8, padding: "9px 12px",
  background: "var(--surface-2, rgba(255,255,255,.03))", fontSize: 11.5, fontWeight: 700,
  letterSpacing: ".06em", textTransform: "uppercase", color: "var(--muted)",
};
const ffBouton = (accent) => ({
  display: "inline-flex", alignItems: "center", gap: 6, padding: "6px 11px", borderRadius: 8,
  fontSize: 12.5, fontWeight: 600, cursor: "pointer", font: "inherit",
  background: accent ? "var(--accent)" : "var(--surface-2, rgba(255,255,255,.05))",
  color: accent ? "#06281c" : "var(--text)",
  border: "1px solid " + (accent ? "var(--accent)" : "var(--border)"),
});

function ffCopier(texte, surAvis) {
  try {
    navigator.clipboard.writeText(texte);
    surAvis && surAvis("copié");
  } catch (e) { surAvis && surAvis("copie impossible"); }
}

// Ouvrir un lien HORS de l'application. Sans ce garde-fou, un clic sur un pseudo ouvrirait
// Instagram DANS la fenetre Electron et remplacerait l'application.
//
// L'ORDRE COMPTE. `Backend.openUrl` fait un `webbrowser.open` COTE SERVEUR : la page s'ouvre sur
// la machine qui heberge le backend. Pour un VA a distance, c'etait donc l'ecran du proprietaire
// qui s'allumait, et le VA ne voyait jamais rien -- et le repli ne se declenchait pas, puisque
// l'appel REUSSISSAIT (au mauvais endroit). `plOuvrirLien` tranche justement ce cas : chemin
// serveur seulement si la page est servie par la machine ou l'on est. Il passe donc en premier.
function ffOuvrir(url) {
  try {
    if (window.plOuvrirLien) { window.plOuvrirLien(url); return; }
    if (window.Backend && window.Backend.openUrl) {
      window.Backend.openUrl(url).catch(function () { window.open(url, "_blank", "noopener"); });
      return;
    }
  } catch (e) {}
  window.open(url, "_blank", "noopener,noreferrer");
}

// Les categories du parc ou l'on range un compte mort ou a verifier. On les RESOUT depuis
// le parc au lieu de les ecrire en dur : elles portent des emojis, elles sont renommees, et
// un nom fige enverrait les comptes dans une categorie inexistante sans rien dire.
async function ffCategories() {
  try {
    const d = await ffAppel("/api/floodsflow/concepts");
    const noms = (d.categories || []).map((c) => c.nom);
    return {
      ban: noms.find((n) => /ban/i.test(n)) || "",
      verif: noms.find((n) => /v[eé]rif/i.test(n)) || "",
    };
  } catch (e) { return { ban: "", verif: "" }; }
}

// Ranger un compte, et le sortir de la liste de travail s'il est mort.
// Le magasin des comptes est cote navigateur (AccountCreationStore) : c'est lui qui detient
// la categorie et qui pousse au backend. On passe par lui, pas par une route parallele qui
// divergerait.
function ffRanger(username, categorie, archiver, statut) {
  const S = window.AccountCreationStore;
  if (!S || !S.list || !S.update) return { ok: false, error: "stockage de comptes indisponible" };
  const a = (S.list() || []).find((x) => String(x.username || "").trim() === username);
  if (!a) return { ok: false, error: "compte introuvable dans le stockage" };
  // On pose AUSSI le champ `problem`, deja utilise par le Stockage de comptes pour afficher
  // 🚫 ou 🔍 en face du compte. Ne changer que la categorie aurait cree deux verites : le
  // compte range comme banni, mais sans la marque que le reste de l'application regarde.
  S.update(a.id, {
    category: categorie,
    problem: { status: statut, motif: "constaté depuis la phase Connexion API",
               by: "FloodsFlow", at: new Date().toISOString() },
  });
  // L'archivage LIBERE l'iPhone attribue et consigne le passage dans l'historique. On ne le
  // fait que pour un compte mort : un compte a verifier est recuperable, lui retirer son
  // telephone ferait perdre le contexte de travail du VA.
  if (archiver && S.archive) S.archive([a.id]);
  return { ok: true };
}

// ══ GAUCHE — les comptes qui restent à brancher ═══════════════════════════════════════
function BlocAbrancher() {
  const store = useFloods();
  const [comptes, setComptes] = React.useState(null);
  const [erreur, setErreur] = React.useState("");
  const [cats, setCats] = React.useState({ ban: "", verif: "" });
  React.useEffect(() => { ffCategories().then(setCats); }, []);

  const ranger = (ev, c, cat, archiver, mot) => {   // archiver=true <=> c'est un ban
    ev.stopPropagation();                        // ne pas selectionner le compte au passage
    if (!cat) { setErreur("aucune catégorie « " + mot + " » dans le parc"); return; }
    // L'archivage libere l'iPhone : on demande confirmation. C'est defaisable depuis le
    // Stockage de comptes, mais un clic par erreur rendrait un telephone au pot commun.
    if (archiver && !window.confirm(
      "Marquer « " + c.username + " » comme banni ?\n\n"
      + "Il passe dans « " + cat + " », quitte la liste de travail et LIBÈRE son iPhone.\n"
      + "Son historique est conservé ; l'opération se défait depuis le Stockage de comptes.")) return;
    const r = ffRanger(c.username, cat, archiver, archiver ? "ban" : "verif");
    if (!r.ok) { setErreur(r.error); return; }
    if (store.choisi && store.choisi.username === c.username) FloodsStore.choisir(null);
    FloodsStore.rafraichir();
  };

  const charger = React.useCallback(async () => {
    const d = await ffAppel("/api/floodsflow/a-brancher");
    if (d.error) { setErreur(d.error); setComptes([]); return; }
    setErreur(""); setComptes(d.comptes || []);
  }, []);
  React.useEffect(() => { charger(); }, [charger]);
  React.useEffect(() => FloodsStore.sub(charger), [charger]);

  const ton = (c) => (c.etat_lien === "VALIDE" ? "var(--accent)"
    : c.etat_lien === "PERIME" ? "var(--danger, #ff5c6c)" : "var(--faint)");
  const dit = (c) => (c.etat_lien === "VALIDE" ? c.heures_restantes + " h"
    : c.etat_lien === "PERIME" ? "périmé" : "pas de lien");

  return (
    <div style={ffCarte}>
      <div style={ffEntete}>
        <span style={{ flex: 1 }}>Comptes à brancher</span>
        {comptes && <span style={{ fontWeight: 500, textTransform: "none", letterSpacing: 0 }}>
          {comptes.length}</span>}
        <button onClick={charger} style={{ ...ffBouton(), padding: "3px 8px", fontSize: 11 }}>⟳</button>
      </div>

      {erreur ? (
        <div style={{ padding: 12, fontSize: 12.5, color: "var(--danger, #ff5c6c)" }}>⚠ {erreur}</div>
      ) : comptes === null ? (
        <div style={{ padding: 12, fontSize: 12.5, color: "var(--faint)" }}>chargement…</div>
      ) : !comptes.length ? (
        <div style={{ padding: 14, fontSize: 12.5, color: "var(--faint)" }}>
          Tout est branché. Les comptes apparaissent ici dès qu'un concept les couvre,
          dans FloodsFlow.
        </div>
      ) : comptes.map((c) => {
        const actif = store.choisi && store.choisi.username === c.username;
        return (
          <div key={c.username} onClick={() => FloodsStore.choisir(c)}
            style={{ display: "flex", alignItems: "center", gap: 9, padding: "8px 12px",
              borderTop: "1px solid var(--border)", cursor: "pointer", fontSize: 13,
              background: actif ? "rgba(34,224,122,.10)" : "transparent" }}>
            <span style={{ width: 14, height: 14, borderRadius: 4, flexShrink: 0, fontSize: 10,
              lineHeight: "13px", textAlign: "center", fontWeight: 900, color: "#06281c",
              border: "1px solid " + (actif ? "var(--accent)" : "var(--border)"),
              background: actif ? "var(--accent)" : "transparent" }}>{actif ? "✓" : ""}</span>
            {/* Le pseudo ouvre le profil Instagram : c'est le seul moyen de savoir si le
                compte tient encore avant d'y passer dix minutes. */}
            <a href={"https://instagram.com/" + encodeURIComponent(c.username)}
              onClick={(ev) => { ev.preventDefault(); ev.stopPropagation();
                ffOuvrir("https://instagram.com/" + encodeURIComponent(c.username)); }}
              title={"Ouvrir instagram.com/" + c.username + " pour vérifier qu'il est encore actif"}
              style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis",
                whiteSpace: "nowrap", color: "var(--text)", textDecoration: "none",
                borderBottom: "1px dotted var(--border)" }}>
              {c.username}
            </a>
            <span style={{ fontSize: 11, color: ton(c) }}>{dit(c)}</span>
            <button onClick={(ev) => ranger(ev, c, cats.verif, false, "vérif")}
              title="Le compte demande une vérification : le ranger sans lui retirer son iPhone"
              style={{ ...ffBouton(), padding: "2px 7px", fontSize: 10.5 }}>vérif</button>
            <button onClick={(ev) => ranger(ev, c, cats.ban, true, "ban")}
              title="Le compte est banni : le ranger, le sortir de la liste et libérer son iPhone"
              style={{ ...ffBouton(), padding: "2px 7px", fontSize: 10.5,
                borderColor: "rgba(255,92,108,.3)", color: "var(--danger, #ff5c6c)" }}>ban</button>
          </div>
        );
      })}
    </div>
  );
}

// ══ DROITE — le lien du compte choisi, et la marche à suivre ══════════════════════════
function BlocAuthAPI({ device }) {
  const store = useFloods();
  const c = store.choisi;
  const [faites, setFaites] = React.useState({});
  const [avis, setAvis] = React.useState("");
  const [occupe, setOccupe] = React.useState(false);

  // ── LE CODE 2FA, CALCULE ET RAFRAICHI ───────────────────────────────────────────────
  // Le stockage detient une CLE ; l'autorisation attend six chiffres valables trente
  // secondes. On les demande a /api/totp (pyotp, cote backend) et on les renouvelle : un
  // code affiche une fois serait perime avant que le VA l'ait tape.
  const [code2fa, setCode2fa] = React.useState("");
  const [reste, setReste] = React.useState(0);
  React.useEffect(() => {
    if (!c || !c.totp_key || !window.Backend || !window.Backend.totp) { setCode2fa(""); return; }
    let vivant = true;
    const tirer = async () => {
      const d = await window.Backend.totp(c.totp_key);
      if (!vivant) return;
      setCode2fa(d && d.ok ? d.token : "");
      setReste(d && d.ok ? d.remaining : 0);
    };
    tirer();
    const t = setInterval(() => {
      setReste((r) => {
        if (r <= 1) { tirer(); return 30; }
        return r - 1;
      });
    }, 1000);
    return () => { vivant = false; clearInterval(t); };
  }, [c && c.username, c && c.totp_key]);

  // La checklist repart de zéro à chaque compte : garder les cases d'un compte précédent
  // ferait valider un compte sur lequel on n'a rien fait.
  React.useEffect(() => { setFaites({}); setAvis(""); }, [c && c.username]);

  const toutes = FF_ETAPES.every((_, i) => faites[i]);

  // ECRIRE SUR LE TELEPHONE. C'est le mecanisme deja utilise par le bloc « Saisie de texte » :
  // le texte part dans le champ actif de l'iPhone. Le VA doit donc avoir place le curseur au
  // bon endroit -- on le dit dans l'avis plutot que de laisser croire a un echec.
  const envoyer = async (texte, quoi) => {
    if (!device || !window.Backend || !window.Backend.action || occupe) return;
    setOccupe(true);
    try {
      await window.Backend.action(device.udid, "inputText", { text: texte });
      setAvis((quoi === "lien" ? "lien" : quoi) + " envoyé — vérifie qu'il est bien arrivé dans le champ actif");
    } catch (e) {
      setAvis("envoi impossible : " + (e && e.message ? e.message : e));
    }
    setOccupe(false);
  };

  const terminer = async (annuler) => {
    if (!c || occupe) return;
    setOccupe(true);
    const d = await ffAppel("/api/floodsflow/valider", "POST",
      { username: c.username, annuler: !!annuler });
    setOccupe(false);
    if (!d.ok) { setAvis(d.error || "échec"); return; }
    // Le compte quitte la liste : on la recharge et on libère la sélection, sinon le bloc
    // continue d'afficher un compte qui n'est plus à faire.
    FloodsStore.choisir(null);
    FloodsStore.rafraichir();
  };

  if (!c) {
    return (
      <div style={ffCarte}>
        <div style={ffEntete}>Authentification API</div>
        <div style={{ padding: 14, fontSize: 12.5, color: "var(--faint)" }}>
          Choisis un compte dans la liste de gauche : son lien et la marche à suivre
          s'affichent ici.
        </div>
      </div>
    );
  }

  // Chaque champ est copiable ET envoyable : l'autorisation demande le pseudo, le mot de
  // passe, puis le code 2FA. Les trois se tapent sur le telephone, donc les trois ont leur
  // bouton — sinon le VA recopie a la main un TOTP qui expire en trente secondes.
  const champ = (l, v, quoi, mention) => v ? (
    <div style={{ display: "flex", alignItems: "center", gap: 7, padding: "5px 12px", fontSize: 12.5 }}>
      <span style={{ width: 82, color: "var(--faint)", flexShrink: 0 }}>{l}</span>
      <span className="mono" style={{ flex: 1, minWidth: 0, overflow: "hidden",
        textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{v}</span>
      {mention && <span style={{ fontSize: 11, color: "var(--faint)", flexShrink: 0 }}>{mention}</span>}
      <button onClick={() => ffCopier(v, setAvis)}
        style={{ ...ffBouton(), padding: "3px 8px", fontSize: 11 }}>copier</button>
      <button onClick={() => envoyer(v, quoi)} disabled={!device || occupe}
        title={device ? "Écrit cette valeur dans le champ actif du téléphone" : "Aucun téléphone"}
        style={{ ...ffBouton(), padding: "3px 8px", fontSize: 11,
          opacity: device ? 1 : .45 }}>envoyer</button>
    </div>
  ) : null;

  return (
    <div style={ffCarte}>
      <div style={ffEntete}>
        <span style={{ flex: 1 }}>Authentification API</span>
        <span style={{ fontWeight: 500, textTransform: "none", letterSpacing: 0,
          color: "var(--text)" }}>{c.username}</span>
      </div>

      {/* Le lien d'abord : c'est ce qu'on vient chercher. */}
      <div style={{ padding: "10px 12px", borderTop: "1px solid var(--border)" }}>
        {c.etat_lien === "VALIDE" ? (
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
            <span style={{ fontSize: 12, color: "var(--accent)" }}>
              lien valide encore {c.heures_restantes} h
            </span>
            <span style={{ flex: 1 }} />
            <button onClick={() => ffCopier(c.lien, setAvis)}
              style={{ ...ffBouton(), padding: "4px 9px", fontSize: 11.5 }}>copier</button>
            {/* Envoyer bat copier : le VA n'a pas a coller a la main sur le telephone, et
                un lien d'authentification tape de travers ne mene nulle part. */}
            <button onClick={() => envoyer(c.lien, "lien")} disabled={!device || occupe}
              title={device ? "Écrit le lien dans le champ actif du téléphone"
                : "Aucun téléphone : ce bloc doit être posé dans une phase d'iPhone"}
              style={{ ...ffBouton(!!device) }}>
              {occupe ? "…" : "Envoyer sur le téléphone"}
            </button>
          </div>
        ) : (
          <div style={{ fontSize: 12.5, color: "var(--busy, #f0a020)" }}>
            {c.etat_lien === "PERIME"
              ? "Lien périmé — régénère les liens depuis FloodsFlow avant de continuer."
              : "Aucun lien pour ce compte — génère les liens depuis FloodsFlow."}
          </div>
        )}
      </div>

      {/* Les identifiants, pour le conteneur. */}
      <div style={{ borderTop: "1px solid var(--border)", padding: "5px 0" }}>
        {champ("username", c.username, "pseudo")}
        {champ("mot de passe", c.password, "mot de passe")}
        {/* Le code 2FA est demande PENDANT l'autorisation. On affiche les SIX CHIFFRES, pas
            la cle : c'est le code que le formulaire attend, et il ne vaut que trente
            secondes. Le compte a rebours evite de coller un code deja expire. */}
        {c.totp_key ? champ("code 2FA", code2fa || "…", "code 2FA",
          reste ? reste + " s" : "") : (
          <div style={{ padding: "4px 12px", fontSize: 11.5, color: "var(--busy, #f0a020)" }}>
            aucune clé 2FA enregistrée pour ce compte — l'autorisation la demandera quand même
          </div>
        )}
      </div>

      {/* La marche à suivre. Les deux rotations d'IP encadrent le reste : c'est ce qui
          empêche deux comptes d'être reliés par une IP commune. */}
      <div style={{ borderTop: "1px solid var(--border)" }}>
        {FF_ETAPES.map((e, i) => (
          <div key={i} onClick={() => setFaites((f) => Object.assign({}, f, { [i]: !f[i] }))}
            style={{ display: "flex", alignItems: "center", gap: 9, padding: "6px 12px",
              fontSize: 12.5, cursor: "pointer",
              color: faites[i] ? "var(--faint)" : "var(--text)",
              textDecoration: faites[i] ? "line-through" : "none" }}>
            <span style={{ width: 15, height: 15, borderRadius: 4, flexShrink: 0, fontSize: 10,
              lineHeight: "14px", textAlign: "center", fontWeight: 900, color: "#06281c",
              border: "1px solid " + (faites[i] ? "var(--accent)" : "var(--border)"),
              background: faites[i] ? "var(--accent)" : "transparent" }}>{faites[i] ? "✓" : ""}</span>
            <span style={{ color: "var(--faint)", width: 12 }}>{i + 1}</span>
            <span style={{ flex: 1 }}>{e}</span>
          </div>
        ))}
      </div>

      {avis && (
        <div style={{ padding: "6px 12px", fontSize: 12, color: "var(--faint)" }}>{avis}</div>
      )}

      <div style={{ display: "flex", gap: 8, padding: "10px 12px",
        borderTop: "1px solid var(--border)" }}>
        {/* « Validé » reste cliquable même si tout n'est pas coché : une checklist qui
            bloque pousse à cocher sans faire. On dit ce qui manque, on n'interdit pas. */}
        <button onClick={() => terminer(false)} disabled={occupe}
          style={{ ...ffBouton(toutes), flex: 1, justifyContent: "center" }}>
          {occupe ? "…" : toutes ? "Validé" : "Valider quand même"}
        </button>
        <button onClick={() => { FloodsStore.choisir(null); }} style={ffBouton()}>
          Passer
        </button>
      </div>
      {!toutes && (
        <div style={{ padding: "0 12px 10px", fontSize: 11.5, color: "var(--busy, #f0a020)" }}>
          {FF_ETAPES.length - FF_ETAPES.filter((_, i) => faites[i]).length} étape(s) pas encore cochée(s).
        </div>
      )}
    </div>
  );
}

// ── enregistrement ────────────────────────────────────────────────────────────────────
// Le catalogue est lu au CHARGEMENT du Phase Builder : ce fichier doit donc être chargé
// avant lui dans index.html. Les composants, eux, sont lus à chaque rendu.
window.CATALOGUE_BLOCS_EXTERNES = (window.CATALOGUE_BLOCS_EXTERNES || []).concat([
  { id: "floods_a_brancher", emoji: "🔌", label: "Comptes à brancher (API)", col: "left",
    desc: "Les comptes en attente d'authentification API. En cocher un remplit le bloc Authentification API." },
  { id: "floods_auth_api", emoji: "✨", label: "Authentification API", col: "right",
    desc: "Lien de connexion du compte choisi, ses identifiants, et la marche à suivre à cocher." },
]);
window.BLOCS_EXTERNES = Object.assign(window.BLOCS_EXTERNES || {}, {
  floods_a_brancher: BlocAbrancher,
  floods_auth_api: BlocAuthAPI,
});
Object.assign(window, { BlocAbrancher, BlocAuthAPI, FloodsStore, FF_ETAPES });
