// src/components/onboarding/steps/enrich-step-logic.ts — décisions PURES de
// l'étape « enrichir les mots-clés » du wizard : ce que fait le clic
// « Connecter », et quel message porte chaque refus.
//
// Séparé du composant pour le motif déjà retenu par `gsc-step-logic.ts` et
// `keywords-step-logic.ts` : le module reste chargeable sous
// `--conditions=react-server` (le script `bun run test` du dépôt), donc les
// règles de l'étape sont des propriétés testables plutôt que des promesses de
// JSX.
//
// ⚠️ Mangools SEULEMENT. `ahrefs` et `semrush` figurent dans l'énumération des
// sources de mots-clés, mais AUCUN module ne les alimente : les proposer ici
// comme enrichissement par API mènerait à un bouton sans implémentation. Leurs
// fichiers CSV s'importent, eux, très bien — c'est l'étape PRÉCÉDENTE.
// SE-Ranking est câblé mais métré (crédits) et se branche dans
// Réglages → Intégrations : hors d'un parcours d'entrée qui doit rester gratuit.

/** Retour de `connectMangoolsAction`, redéclaré structurellement : le module de
 *  logique ne doit importer AUCUNE valeur d'un module `"use server"`. */
export type ConnectMangoolsOutcome =
  | { ok: true }
  | { ok: false; error: "invalid_token" | "forbidden" };

/** Clés de message que l'étape peut avoir à afficher. Le composant est le seul
 *  détenteur des traductions : la logique ne manipule que ces identifiants. */
export type EnrichErrorKey = "empty" | "invalidToken" | "forbidden" | "failed";

/**
 * Traduit le refus du serveur en clé de message.
 *
 * `forbidden` et `invalid_token` appellent deux gestes opposés : le premier dit
 * « ce n'est pas ton rôle, demande au propriétaire », le second « recopie ton
 * token ». Les confondre enverrait un membre vérifier indéfiniment un token
 * qu'il n'a de toute façon pas le droit de brancher.
 */
export function resolveEnrichErrorKey(
  error: "invalid_token" | "forbidden",
): EnrichErrorKey {
  return error === "forbidden" ? "forbidden" : "invalidToken";
}

/** Le strict nécessaire pour brancher un token, SANS la sortie d'étape :
 *  l'étape 3 réutilise ce flux telle quelle pour afficher le champ de jeton
 *  sur place, et elle n'a pas de « passer » propre au connecteur. */
export type MangoolsConnectDeps = {
  /** `connectMangoolsAction` en production, un espion dans les tests. */
  connect: (token: string) => Promise<ConnectMangoolsOutcome>;
  /** Token accepté et stocké. L'appelant NE recharge pas la page pour autant :
   *  voir la note du composant — `enrich-keywords` dérive de `volume_primary`,
   *  qu'une connexion ne remplit pas. */
  onConnected: () => void;
  onError: (message: string) => void;
  /** Messages déjà traduits, indexés par clé de décision. */
  messages: Record<EnrichErrorKey, string>;
};

export type EnrichStepHandlerDeps = MangoolsConnectDeps & {
  /** Sortie : on avance sans rien connecter. L'étape est facultative. */
  onSkip: () => void;
};

/**
 * Le clic « Connecter », isolé de toute étape.
 *
 * DEUX étapes du parcours branchent le même token — l'enrichissement (étape 4)
 * et le chemin « importer mes listes Mangools » de l'étape 3, qui affiche le
 * champ SUR PLACE plutôt que de renvoyer l'utilisateur une étape plus loin pour
 * qu'il revienne. Un second flux de connexion divergerait sur le trim, sur la
 * distinction `forbidden`/`invalid_token`, ou sur le filet ci-dessous.
 */
export function createMangoolsConnectHandler(
  deps: MangoolsConnectDeps,
): (token: string) => Promise<void> {
  return async function connect(token: string) {
    const trimmed = token.trim();
    if (!trimmed) {
      // Le serveur répondrait `invalid_token` sur une chaîne vide, ce qui se
      // lit « ton token est refusé » alors que rien n'a été saisi. On tranche
      // ici, sans aller-retour.
      deps.onError(deps.messages.empty);
      return;
    }
    try {
      // Token TRIMÉ : un copier-coller traîne presque toujours une espace ou
      // un saut de ligne, et le ping Mangools la refuserait.
      const res = await deps.connect(trimmed);
      if (!res.ok) {
        deps.onError(deps.messages[resolveEnrichErrorKey(res.error)]);
        return;
      }
      deps.onConnected();
    } catch {
      // NE PAS SUPPRIMER CE FILET en le croyant décoratif. Trois causes
      // réelles, vérifiées en source :
      //
      // 1. SESSION SANS ORGANISATION ACTIVE — le cas le plus vicieux, parce
      //    qu'il ressemble à un simple refus de rôle et n'en est pas. La gate
      //    `canManage()` de `core.ts` passe en premier, mais elle REJETTE au
      //    lieu de rendre `false` : `canManageOrgConnectors` (access.ts)
      //    appelle `auth.api.getActiveMember` sans `asResponse` ni
      //    `try/catch` ; le gestionnaire de better-auth lève
      //    `APIError(BAD_REQUEST, NO_ACTIVE_ORGANIZATION)` AVANT tout calcul
      //    de rôle, et better-call relance l'APIError telle quelle faute
      //    d'`asResponse`. Aucun `try/catch` n'entoure l'appel dans `core.ts`
      //    → la Server Action rejette. Et ce n'est pas théorique :
      //    `active-org.ts` documente qu'une session authentifiée peut naître
      //    SANS org active (inscription par lien magique en une étape) ; son
      //    auto-réparation ne couvre pas ce chemin-là.
      // 2. Panne réseau de l'appel de Server Action lui-même.
      // 3. Échec d'écriture du connecteur en base (`setConnector`).
      //
      // Dans les trois cas, /onboarding n'a AUCUNE error boundary : sans ce
      // catch, la promesse rejetterait en silence — bouton réactivé sans un
      // mot, ou page blanche sur une étape pourtant FACULTATIVE.
      //
      // Limite connue et assumée : le cas 1 affiche « la connexion a
      // échoué, réessaie » alors que réessayer n'y changera rien. C'est
      // toujours préférable au silence, et la vraie correction est côté
      // connecteur partagé (rattraper le refus dans `canManageOrgConnectors`),
      // pas ici.
      deps.onError(deps.messages.failed);
    }
  };
}

export function createEnrichStepHandlers(deps: EnrichStepHandlerDeps): {
  skip: () => void;
  connect: (token: string) => Promise<void>;
} {
  return {
    skip() {
      // Aucune écriture, aucun appel : l'étape est facultative et ne coche
      // rien. Elle réapparaîtra dans la checklist d'activation.
      deps.onSkip();
    },
    // MÊME flux que l'étape 3 : un seul chemin de connexion pour un seul geste.
    connect: createMangoolsConnectHandler(deps),
  };
}
