// src/components/onboarding/steps/target-step-logic.ts — décisions PURES de
// l'étape « planifier un premier mot-clé » : ce que fait le clic, et ce qui se
// passe quand l'écriture échoue.
//
// Séparé du composant pour le motif déjà retenu par `gsc-step-logic.ts`,
// `keywords-step-logic.ts` et `enrich-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 testables sans DOM ni clic.

import type { TargetStatus } from "@/lib/keywords/targets";
import type { FirstTarget } from "@/lib/onboarding/first-target";

/**
 * Signature de `setKeywordTargetAction`, redéclarée structurellement : le
 * module de logique ne doit importer AUCUNE valeur d'un module `"use server"`.
 */
export type SetKeywordTarget = (
  keywordIdValue: string,
  country: string,
  status: TargetStatus | null,
) => Promise<void>;

export type TargetStepHandlerDeps = {
  /** `setKeywordTargetAction` en production, un espion dans les tests. */
  setTarget: SetKeywordTarget;
  /**
   * Écriture confirmée. L'appelant peut quitter le parcours : `target-country`
   * vient réellement de passer à vrai côté serveur. Jamais appelé sur un échec
   * — une navigation prématurée ramènerait sur la même étape en détruisant au
   * passage le message d'erreur.
   */
  onDone: () => void;
  onError: (message: string) => void;
  /** Message déjà traduit — la logique ne manipule pas de traductions. */
  failedMessage: string;
};

export function createTargetStepHandlers(deps: TargetStepHandlerDeps): {
  plan: (target: FirstTarget | null) => Promise<void>;
} {
  return {
    async plan(target: FirstTarget | null) {
      // Sans candidat il n'y a rien à écrire. Le bouton est déjà désactivé dans
      // ce cas ; la garde couvre l'appel programmatique (touche Entrée).
      if (!target) return;
      try {
        // SEUL chemin du dépôt qui pose À LA FOIS le `keyword_target` au statut
        // `planned` ET le stage `planning` : le ciblage en masse
        // (`bulkSetKeywordTargetsAction`) et le bouton « → Planning » de la
        // recherche n'en écrivent qu'un des deux, ce qui produit un mot-clé
        // planifié absent de la Chambre de Briefing — ou l'inverse.
        await deps.setTarget(target.keywordId, target.country, "planned");
      } catch {
        // `setKeywordTargetAction` LÈVE (et ne retourne pas) : la garde
        // `assertKeywordInActiveOrg` rejette sur une session sans organisation
        // active — cas non théorique, `active-org.ts` documente qu'une
        // inscription par lien magique peut en produire une — et toute panne DB
        // remonte telle quelle. Or /onboarding n'a AUCUNE error boundary : sans
        // ce catch, la promesse rejetterait en silence, bouton réactivé sans un
        // mot, sur la DERNIÈRE étape du parcours.
        deps.onError(deps.failedMessage);
        return;
      }
      deps.onDone();
    },
  };
}
