// src/lib/onboarding/wizard-gate.ts — porte d'entrée du wizard.
//
// Décide si l'utilisateur doit être envoyé au wizard.
// Pur, sans dépendance serveur : la lecture de l'état (drapeaux dérivés, cookie,
// rôle, session) vit dans le layout (app), pas ici. C'est ce qui rend chaque
// échappatoire testable sans monter une requête.

/**
 * Cookie de report, posé UNIQUEMENT côté serveur par la route
 * `/onboarding/defer`, en `httpOnly`, pour la durée de la session (pas de
 * `max-age` ni d'`expires` : il meurt à la fermeture du navigateur). Le report
 * ne survit donc jamais à la session, conformément à l'intention produit —
 * « plus tard » ne veut pas dire « plus jamais ».
 *
 * ⚠️ Il n'a AUCUN producteur ni lecteur client : `document.cookie` ne peut pas
 * écraser un cookie `httpOnly`, et l'écriture échouerait **en silence** — le
 * bouton paraîtrait fonctionner et l'utilisateur reviendrait au wizard à la
 * navigation suivante. Toute sortie du parcours navigue vers la route.
 */
export const DEFER_COOKIE = "onboarding-deferred";

/**
 * Valeur posée par la route de report (`/onboarding/defer`) et attendue par le
 * gate. Exportée pour que producteur et consommateur ne soient pas couplés par
 * un littéral : changer la valeur ici ne doit jamais pouvoir désarmer le gate
 * en silence.
 */
export const DEFER_COOKIE_VALUE = "1";

/**
 * Où atterrit un report sans destination explicite : le premier écran qui
 * montre un plan de travail constitué.
 */
export const DEFER_DEFAULT_DESTINATION = "/keywords/planning";

/**
 * Destination d'un report, validée.
 *
 * La route `/onboarding/defer` accepte un paramètre `to` pour que les chemins
 * « je ferai ça ailleurs » (importer depuis le réservoir, changer de forfait)
 * déposent l'utilisateur sur la bonne surface avec le report déjà posé. Ce
 * paramètre vient de l'URL : non validé, c'est une redirection ouverte —
 * `/onboarding/defer?to=https://evil.test` deviendrait un lien d'hameçonnage
 * portant notre domaine, et il pose au passage un cookie de session.
 *
 * On n'accepte donc QUE des chemins internes, et tout refus retombe en silence
 * sur la destination par défaut : c'est une porte de sortie, elle ne doit
 * jamais afficher d'erreur ni se refermer.
 */
export function resolveDeferDestination(to: string | null | undefined): string {
  if (typeof to !== "string" || to.length === 0) {
    return DEFER_DEFAULT_DESTINATION;
  }
  // Doit être un chemin absolu du site. Un `to` relatif ("keywords") serait
  // résolu contre /onboarding/defer, pas contre la racine.
  if (!to.startsWith("/")) return DEFER_DEFAULT_DESTINATION;
  // `//evil.test` est une URL protocole-relatif : interne en apparence,
  // EXTERNE en fait. `/\evil.test` l'est aussi — les navigateurs normalisent
  // l'antislash en slash.
  if (to.startsWith("//") || to.startsWith("/\\")) {
    return DEFER_DEFAULT_DESTINATION;
  }
  if (to.includes("://")) return DEFER_DEFAULT_DESTINATION;
  // Retours à la ligne et autres caractères de contrôle : injection d'en-tête
  // côté serveur, et découpage silencieux de l'URL côté navigateur.
  // biome-ignore lint/suspicious/noControlCharactersInRegex: c'est précisément ce qu'on refuse
  if (/[\u0000-\u001f\u007f]/.test(to)) return DEFER_DEFAULT_DESTINATION;
  return to;
}

export type WizardGateInput = {
  /** requiredComplete(flags) — dérivé de la base. */
  requiredDone: boolean;
  /** L'utilisateur a cliqué « je le ferai plus tard » (cookie de session). */
  deferred: boolean;
  /** owner/admin. Un membre ne peut pas créer de site : le wizard n'a rien à lui proposer. */
  canCreateSite: boolean;
  /**
   * Un super-admin agit sous l'identité d'un client. Le détourner vers le
   * wizard DE CE CLIENT l'enfermerait sur un écran dont les écritures sont
   * volontairement neutralisées (voir `isImpersonating` dans
   * src/lib/onboarding/activation/actions.ts) : il n'aurait aucun moyen de le
   * terminer. La popup username du layout (app) saute déjà pour la même raison.
   */
  isImpersonating: boolean;
};

/**
 * Vrai UNIQUEMENT quand le wizard a quelque chose à proposer ET que
 * l'utilisateur n'a pas d'échappatoire légitime. Chaque `return false` est une
 * porte de sortie : elles sont indépendantes, une seule suffit.
 */
export function shouldEnterWizard(input: WizardGateInput): boolean {
  if (input.isImpersonating) return false;
  if (!input.canCreateSite) return false;
  if (input.deferred) return false;
  return !input.requiredDone;
}
