import type { MobileStep, StepValue } from "../../src/types/intervention";
import {
  buildPhases,
  computeVisibleStepIds,
  evaluateVisibilityRule,
  getBlockingStepIds,
  getCommentRequirement,
  getStepMetadata,
  isAutoCalc,
  isCerfaLike,
  isDevisListStep,
  selectDevisForStep,
  isDisplayOnly,
  isEmptyStepValue,
  isGateStep,
  isProblemValue,
  normalizeText,
  resolveDevisStepText,
  uuidv4,
} from "../../src/shared/workflowUtils";

const makeStep = (overrides: Partial<MobileStep> = {}): MobileStep => ({
  id: "s1",
  intervention_id: 1,
  order_index: 0,
  type: "text",
  label: "",
  required: false,
  status: "todo",
  value: null,
  version: 1,
  updated_at: "2026-01-01",
  ...overrides,
});

describe("workflowUtils", () => {
  describe("getStepMetadata", () => {
    it("retourne la metadata quand presente", () => {
      const step = makeStep({ metadata: { phase: "Phase 1", is_extension: true } });
      expect(getStepMetadata(step)).toEqual({ phase: "Phase 1", is_extension: true });
    });

    it("retourne un objet vide quand metadata absent", () => {
      expect(getStepMetadata(makeStep())).toEqual({});
    });
  });

  describe("uuidv4", () => {
    afterEach(() => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const g = globalThis as any;
      if (g.__originalCrypto !== undefined) {
        g.crypto = g.__originalCrypto;
        delete g.__originalCrypto;
      }
    });

    it("utilise crypto.randomUUID quand disponible", () => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const g = globalThis as any;
      g.__originalCrypto = g.crypto;
      g.crypto = { randomUUID: () => "fixed-uuid-from-crypto" };
      expect(uuidv4()).toBe("fixed-uuid-from-crypto");
    });

    it("fallback vers expo-crypto.randomUUID quand crypto.randomUUID absent", () => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const g = globalThis as any;
      g.__originalCrypto = g.crypto;
      g.crypto = {};
      expect(uuidv4()).toBe("expo-crypto-uuid");
    });

    it("fallback vers expo-crypto.randomUUID quand crypto absent totalement", () => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const g = globalThis as any;
      g.__originalCrypto = g.crypto;
      g.crypto = undefined;
      expect(uuidv4()).toBe("expo-crypto-uuid");
    });
  });

  describe("normalizeText", () => {
    it("retourne chaine vide pour null/undefined", () => {
      expect(normalizeText(null)).toBe("");
      expect(normalizeText(undefined)).toBe("");
    });

    it("supprime accents, trim, lowercase", () => {
      expect(normalizeText("  Problème  ")).toBe("probleme");
      expect(normalizeText("DÉFAUT")).toBe("defaut");
    });

    it("stringifie les non-string", () => {
      expect(normalizeText(42)).toBe("42");
      expect(normalizeText(true)).toBe("true");
    });
  });

  describe("isProblemValue", () => {
    it("retourne false pour valeur vide", () => {
      expect(isProblemValue("")).toBe(false);
      expect(isProblemValue(null)).toBe(false);
      expect(isProblemValue(undefined)).toBe(false);
    });

    it("detecte chaque keyword", () => {
      expect(isProblemValue("probleme")).toBe(true);
      expect(isProblemValue("Problème detecté")).toBe(true);
      expect(isProblemValue("panne moteur")).toBe(true);
      expect(isProblemValue("KO")).toBe(true);
      expect(isProblemValue("defaut mineur")).toBe(true);
      expect(isProblemValue("Non conforme")).toBe(true);
      expect(isProblemValue("A revoir")).toBe(true);
    });

    it("retourne false pour valeurs OK", () => {
      expect(isProblemValue("ok")).toBe(false);
      expect(isProblemValue("conforme")).toBe(false);
      expect(isProblemValue("rien a signaler")).toBe(false);
    });
  });

  describe("isGateStep", () => {
    it("detecte un gate boolean commencer", () => {
      const step = makeStep({
        label: "Commencer l'intervention ?",
        metadata: { input_kind: "boolean" },
      });
      expect(isGateStep(step)).toBe(true);
    });

    it("detecte un gate choice commencer", () => {
      const step = makeStep({
        label: "COMMENCER ?",
        metadata: { input_kind: "choice" },
      });
      expect(isGateStep(step)).toBe(true);
    });

    it("retourne false si input_kind autre", () => {
      const step = makeStep({
        label: "Commencer",
        metadata: { input_kind: "text" },
      });
      expect(isGateStep(step)).toBe(false);
    });

    it("retourne false si label sans commencer", () => {
      const step = makeStep({
        label: "Mesure initiale",
        metadata: { input_kind: "boolean" },
      });
      expect(isGateStep(step)).toBe(false);
    });
  });

  describe("buildPhases", () => {
    it("retourne [] pour entree vide", () => {
      expect(buildPhases([])).toEqual([]);
    });

    it("filtre les gate steps", () => {
      const steps = [
        makeStep({
          id: "g",
          label: "Commencer ?",
          metadata: { input_kind: "boolean" },
        }),
        makeStep({ id: "a", metadata: { phase: "P1" }, order_index: 1 }),
      ];
      const phases = buildPhases(steps);
      expect(phases).toHaveLength(1);
      expect(phases[0].steps).toHaveLength(1);
      expect(phases[0].steps[0].id).toBe("a");
    });

    it("trie par order_index et groupe par phase", () => {
      const steps = [
        makeStep({ id: "b", order_index: 2, metadata: { phase: "P1" } }),
        makeStep({ id: "a", order_index: 1, metadata: { phase: "P1" } }),
        makeStep({ id: "c", order_index: 3, metadata: { phase: "P2" } }),
      ];
      const phases = buildPhases(steps);
      expect(phases.map((p) => p.name)).toEqual(["P1", "P2"]);
      expect(phases[0].steps.map((s) => s.id)).toEqual(["a", "b"]);
      expect(phases[1].steps.map((s) => s.id)).toEqual(["c"]);
    });

    it("utilise — comme phase par defaut quand aucune phase precedente", () => {
      const steps = [makeStep({ id: "a" })];
      const phases = buildPhases(steps);
      expect(phases[0].name).toBe("—");
    });

    it("continue la phase precedente quand phase absente sur un step suivant", () => {
      const steps = [
        makeStep({ id: "a", order_index: 1, metadata: { phase: "P1" } }),
        makeStep({ id: "b", order_index: 2 }),
      ];
      const phases = buildPhases(steps);
      expect(phases).toHaveLength(1);
      expect(phases[0].name).toBe("P1");
      expect(phases[0].steps.map((s) => s.id)).toEqual(["a", "b"]);
    });
  });

  describe("computeVisibleStepIds", () => {
    it("etape non-extension toujours visible", () => {
      const phase = { name: "P", steps: [makeStep({ id: "a" })] };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("a")).toBe(true);
    });

    it("extension visible quand parent choice = probleme", () => {
      const phase = {
        name: "P",
        steps: [
          makeStep({ id: "p", metadata: { input_kind: "choice" }, value: "Probleme" }),
          makeStep({ id: "e", metadata: { is_extension: true } }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("p")).toBe(true);
      expect(visible.has("e")).toBe(true);
    });

    it("extension cachee quand parent choice = ok", () => {
      const phase = {
        name: "P",
        steps: [
          makeStep({ id: "p", metadata: { input_kind: "choice" }, value: "OK" }),
          makeStep({ id: "e", metadata: { is_extension: true } }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("e")).toBe(false);
    });

    it("utilise draft value plutot que value existante", () => {
      const phase = {
        name: "P",
        steps: [
          makeStep({ id: "p", metadata: { input_kind: "choice" }, value: "OK" }),
          makeStep({ id: "e", metadata: { is_extension: true } }),
        ],
      };
      const visible = computeVisibleStepIds(phase, { p: "probleme" });
      expect(visible.has("e")).toBe(true);
    });

    it("etape text coupe la chaine d'extensions", () => {
      const phase = {
        name: "P",
        steps: [
          makeStep({ id: "p", metadata: { input_kind: "choice" }, value: "probleme" }),
          makeStep({ id: "t", metadata: { input_kind: "text" } }),
          makeStep({ id: "e", metadata: { is_extension: true } }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("t")).toBe(true);
      expect(visible.has("e")).toBe(false);
    });

    it("extension-parent-probleme devient nouveau parent (imbrication)", () => {
      const phase = {
        name: "P",
        steps: [
          makeStep({ id: "p", metadata: { input_kind: "choice" }, value: "probleme" }),
          makeStep({
            id: "e1",
            metadata: { is_extension: true, input_kind: "choice" },
            value: "probleme",
          }),
          makeStep({ id: "e2", metadata: { is_extension: true } }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("e2")).toBe(true);
    });

    it("extension-parent-ok ne rompt pas la chaine (sœur reste visible)", () => {
      const phase = {
        name: "P",
        steps: [
          makeStep({ id: "p", metadata: { input_kind: "choice" }, value: "probleme" }),
          makeStep({
            id: "e1",
            metadata: { is_extension: true, input_kind: "choice" },
            value: "OK",
          }),
          makeStep({ id: "e2", metadata: { is_extension: true } }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("e1")).toBe(true);
      expect(visible.has("e2")).toBe(true);
    });

    it("extension-parent cachee coupe la cascade des extensions suivantes", () => {
      const phase = {
        name: "P",
        steps: [
          makeStep({ id: "p", metadata: { input_kind: "choice" }, value: "probleme" }),
          // Extension parent-type mais cachee par sa regle (parent hors liste).
          makeStep({
            id: "e1",
            metadata: { is_extension: true, input_kind: "choice" },
            constraints: { visible_if_in_parent: ["autre"] },
          }),
          // Sans la coupe de cascade, e2 resterait visible via le fallback
          // isProblemValue("probleme") du parent original.
          makeStep({ id: "e2", metadata: { is_extension: true } }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("e1")).toBe(false);
      expect(visible.has("e2")).toBe(false);
    });

    it("extension ignoree si aucun parent actif avant", () => {
      const phase = {
        name: "P",
        steps: [
          makeStep({ id: "t", metadata: { input_kind: "text" } }),
          makeStep({ id: "e", metadata: { is_extension: true } }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("e")).toBe(false);
    });

    // BUG 2 : "etat machine a l'arrivee" deploie ses enfants via une primitive
    // explicite visible_if_in_parent (refonte coolcare 2026-04-24), pas via les
    // mots-cles isProblemValue. Plusieurs valeurs neutres doivent deployer.
    it("extension visible via visible_if_in_parent meme sans mot-cle probleme", () => {
      const phase = {
        name: "P2",
        steps: [
          makeStep({ id: "etat", metadata: { input_kind: "choice" }, value: "A l'arret" }),
          makeStep({
            id: "ext",
            metadata: { is_extension: true },
            constraints: { visible_if_in_parent: ["A l'arret", "En marche degradee"] },
          }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("ext")).toBe(true);
    });

    it("extension cachee si la valeur parent n'est pas dans visible_if_in_parent", () => {
      const phase = {
        name: "P2",
        steps: [
          makeStep({ id: "etat", metadata: { input_kind: "choice" }, value: "En marche normale" }),
          makeStep({
            id: "ext",
            metadata: { is_extension: true },
            constraints: { visible_if_in_parent: ["A l'arret", "En marche degradee"] },
          }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("ext")).toBe(false);
    });

    it("visible_if_not_parent : visible pour toute valeur sauf l'exclue", () => {
      const phase = {
        name: "P2",
        steps: [
          makeStep({ id: "etat", metadata: { input_kind: "choice" }, value: "En marche degradee" }),
          makeStep({
            id: "ext",
            metadata: { is_extension: true },
            constraints: { visible_if_not_parent: "En marche normale" },
          }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("ext")).toBe(true);
    });

    it("primitive explicite l'emporte sur le fallback isProblemValue", () => {
      // Parent = "panne" (isProblemValue=true) mais la regle exige "RAS" :
      // la regle gagne -> extension cachee.
      const phase = {
        name: "P2",
        steps: [
          makeStep({ id: "etat", metadata: { input_kind: "choice" }, value: "panne" }),
          makeStep({
            id: "ext",
            metadata: { is_extension: true },
            constraints: { visible_if_in_parent: ["RAS"] },
          }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("ext")).toBe(false);
    });

    // Regression handoff #168 : huile et acide sont 2 etapes NON-extension
    // mutuellement exclusives ; le serveur en masque une via is_visible=false
    // (resolveMachineTestStepVisibility). Depuis que les endpoints renvoient
    // aussi les etapes masquees (offline-first), le mobile recevait les deux ->
    // il doit respecter le flag serveur, sinon il affiche prelevement ET acide.
    it("non-extension masquee serveur (is_visible=false) reste cachee", () => {
      const phase = {
        name: "P2",
        steps: [
          makeStep({ id: "huile", label: "NUMERO PRELEVEMENT HUILE", is_visible: true }),
          makeStep({
            id: "acide",
            type: "photo",
            label: "Si acide test // Photo",
            is_visible: false,
          }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("huile")).toBe(true);
      expect(visible.has("acide")).toBe(false);
    });

    // Le cas inverse (machine sous contrat acide) doit cacher l'huile.
    it("non-extension masquee serveur : cas inverse acide visible / huile cachee", () => {
      const phase = {
        name: "P2",
        steps: [
          makeStep({ id: "huile", label: "NUMERO PRELEVEMENT HUILE", is_visible: false }),
          makeStep({ id: "acide", type: "photo", label: "Si acide test // Photo", is_visible: true }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("huile")).toBe(false);
      expect(visible.has("acide")).toBe(true);
    });

    // Une non-extension masquee qui est aussi parent potentiel coupe la chaine :
    // ses extensions ne doivent pas s'accrocher a un parent invisible.
    it("non-extension masquee serveur coupe la chaine d'extensions", () => {
      const phase = {
        name: "P",
        steps: [
          makeStep({ id: "p", metadata: { input_kind: "choice" }, value: "probleme", is_visible: false }),
          makeStep({ id: "e", metadata: { is_extension: true } }),
        ],
      };
      const visible = computeVisibleStepIds(phase, {});
      expect(visible.has("p")).toBe(false);
      expect(visible.has("e")).toBe(false);
    });
  });

  describe("evaluateVisibilityRule", () => {
    it("retourne null sans primitive _parent (fallback positionnel)", () => {
      expect(evaluateVisibilityRule(null, "x")).toBeNull();
      expect(evaluateVisibilityRule({ unit: "bar" }, "x")).toBeNull();
    });

    it("visible_if_in_parent : appartenance stricte a la liste", () => {
      expect(evaluateVisibilityRule({ visible_if_in_parent: ["A", "B"] }, "B")).toBe(true);
      expect(evaluateVisibilityRule({ visible_if_in_parent: ["A", "B"] }, "C")).toBe(false);
    });

    it("visible_if_not_parent : visible si different de la valeur exclue", () => {
      expect(evaluateVisibilityRule({ visible_if_not_parent: "X" }, "Y")).toBe(true);
      expect(evaluateVisibilityRule({ visible_if_not_parent: "X" }, "X")).toBe(false);
    });

    it("parent vide ('' ou '-') -> cache (garde-fou avant saisie)", () => {
      expect(evaluateVisibilityRule({ visible_if_not_parent: "X" }, "")).toBe(false);
      expect(evaluateVisibilityRule({ visible_if_in_parent: ["A"] }, "-")).toBe(false);
    });
  });

  describe("getBlockingStepIds", () => {
    const phase = (steps: MobileStep[]) => ({ name: "P", steps });

    it("ignore les steps non visibles", () => {
      const steps = [makeStep({ id: "a", required: true })];
      const res = getBlockingStepIds(phase(steps), {}, new Set());
      expect(res).toEqual([]);
    });

    it("ignore les steps non required", () => {
      const steps = [makeStep({ id: "a", required: false })];
      const res = getBlockingStepIds(phase(steps), {}, new Set(["a"]));
      expect(res).toEqual([]);
    });

    it("ignore les steps auto-calc", () => {
      const steps = [
        makeStep({
          id: "a",
          required: true,
          metadata: { phase: "Calculs automatiques" },
        }),
      ];
      const res = getBlockingStepIds(phase(steps), {}, new Set(["a"]));
      expect(res).toEqual([]);
    });

    it("ignore les steps display-only", () => {
      const steps = [
        makeStep({
          id: "a",
          required: true,
          metadata: { input_kind: "display" },
        }),
      ];
      const res = getBlockingStepIds(phase(steps), {}, new Set(["a"]));
      expect(res).toEqual([]);
    });

    it("bloque les required vides", () => {
      const steps = [makeStep({ id: "a", required: true, value: null })];
      const res = getBlockingStepIds(phase(steps), {}, new Set(["a"]));
      expect(res).toEqual(["a"]);
    });

    it("debloque si draft value fournie", () => {
      const steps = [makeStep({ id: "a", required: true, value: null })];
      const res = getBlockingStepIds(phase(steps), { a: "rempli" }, new Set(["a"]));
      expect(res).toEqual([]);
    });
  });

  describe("isEmptyStepValue", () => {
    it("null et undefined sont vides", () => {
      expect(isEmptyStepValue(null)).toBe(true);
      expect(isEmptyStepValue(undefined as unknown as StepValue)).toBe(true);
    });

    it("string vide ou espaces", () => {
      expect(isEmptyStepValue("")).toBe(true);
      expect(isEmptyStepValue("   ")).toBe(true);
    });

    it("tableau vide", () => {
      expect(isEmptyStepValue([])).toBe(true);
    });

    it("signature non signee", () => {
      expect(
        isEmptyStepValue({ signed: false, signed_by: "technicien", uri: "" })
      ).toBe(true);
    });

    it("valeurs valides ne sont pas vides", () => {
      expect(isEmptyStepValue("texte")).toBe(false);
      expect(isEmptyStepValue(0)).toBe(false);
      expect(isEmptyStepValue(false)).toBe(false);
      expect(isEmptyStepValue(["x"])).toBe(false);
      expect(
        isEmptyStepValue({ signed: true, signed_by: "client", uri: "f" })
      ).toBe(false);
    });
  });

  describe("isAutoCalc", () => {
    it("detecte phase 'Calculs automatiques' (pluriel)", () => {
      expect(
        isAutoCalc(makeStep({ metadata: { phase: "Calculs automatiques" } }))
      ).toBe(true);
    });

    it("detecte phase 'Calcul automatique' (singulier)", () => {
      expect(
        isAutoCalc(makeStep({ metadata: { phase: "Calcul automatique" } }))
      ).toBe(true);
    });

    it("detecte action_label 'calcul automatique'", () => {
      expect(
        isAutoCalc(
          makeStep({ metadata: { action_label: "Calcul automatique du truc" } })
        )
      ).toBe(true);
    });

    it("detecte les formules t_condensation / t_ligne_* / t_evaporation", () => {
      expect(
        isAutoCalc(makeStep({ metadata: { action_label: "formule t_condensation = x" } }))
      ).toBe(true);
      expect(
        isAutoCalc(makeStep({ metadata: { action_label: "t_ligne_aspiration" } }))
      ).toBe(true);
      expect(
        isAutoCalc(makeStep({ metadata: { action_label: "t_ligne_liquide" } }))
      ).toBe(true);
      expect(
        isAutoCalc(makeStep({ metadata: { action_label: "t_evaporation" } }))
      ).toBe(true);
    });

    it("detecte le pattern 'nbre heure >'", () => {
      expect(
        isAutoCalc(
          makeStep({
            metadata: { action_label: "Si nbre heure > 32000 h, duree depassee" },
          })
        )
      ).toBe(true);
    });

    it("retourne false sans match", () => {
      expect(isAutoCalc(makeStep())).toBe(false);
      expect(
        isAutoCalc(
          makeStep({ metadata: { phase: "Phase 1", action_label: "simple" } })
        )
      ).toBe(false);
    });
  });

  describe("isCerfaLike", () => {
    it("detecte 'cerfa'", () => {
      expect(isCerfaLike(makeStep({ label: "Formulaire CERFA XYZ" }))).toBe(true);
    });

    it("detecte 'prelevement huile'", () => {
      expect(
        isCerfaLike(makeStep({ label: "Prelevement huile annuel" }))
      ).toBe(true);
    });

    it("retourne false sinon", () => {
      expect(isCerfaLike(makeStep({ label: "Autre prerequis" }))).toBe(false);
      expect(isCerfaLike(makeStep({ label: "Huile seule" }))).toBe(false);
    });
  });

  describe("isDisplayOnly", () => {
    it("detecte display/section/info", () => {
      expect(isDisplayOnly(makeStep({ metadata: { input_kind: "display" } }))).toBe(true);
      expect(isDisplayOnly(makeStep({ metadata: { input_kind: "section" } }))).toBe(true);
      expect(isDisplayOnly(makeStep({ metadata: { input_kind: "info" } }))).toBe(true);
    });

    it("retourne false pour les autres input_kind", () => {
      expect(isDisplayOnly(makeStep({ metadata: { input_kind: "text" } }))).toBe(false);
      expect(isDisplayOnly(makeStep())).toBe(false);
    });

    // BUG 1 : prerequis CERFA / prelevement huile de phase 1 -> le serveur pose
    // display_only=true (applyPhase1CerfaInfoOverride). Le mobile doit le
    // respecter et les rendre en info, meme si input_kind=prerequisite.
    it("respecte le flag serveur display_only sur un prerequisite", () => {
      const step = makeStep({
        label: "N° CERFA",
        metadata: { input_kind: "prerequisite", display_only: true },
      });
      expect(isDisplayOnly(step)).toBe(true);
    });

    it("prerequisite cerfa sans display_only n'est pas display-only", () => {
      const step = makeStep({
        label: "N° CERFA",
        metadata: { input_kind: "prerequisite" },
      });
      expect(isDisplayOnly(step)).toBe(false);
    });
  });

  describe("isDevisListStep", () => {
    it("detecte les labels LISTE DEVIS EN ATTENTE / LISTE DERNIER DEVIS (insensible casse/espaces)", () => {
      expect(isDevisListStep(makeStep({ label: "LISTE DEVIS EN ATTENTE" }))).toBe(true);
      expect(isDevisListStep(makeStep({ label: "liste   dernier devis" }))).toBe(true);
    });
    it("retourne false pour un autre label", () => {
      expect(isDevisListStep(makeStep({ label: "Relevé pression HP" }))).toBe(false);
    });
  });

  describe("resolveDevisStepText", () => {
    const devis = [
      { id: 12, numero_devis: "DV-2026-012", statut: "accepte" },
      { id: 11, numero_devis: "DV-2026-011", statut: "en_attente" },
      { id: 10, numero_devis: null, statut: "en_attente" },
    ];

    it("retourne null si le label ne matche aucun pattern", () => {
      expect(resolveDevisStepText("Relevé pression", devis)).toBeNull();
    });

    it("LISTE DEVIS EN ATTENTE : ne garde que les en_attente, joint par ' | '", () => {
      expect(resolveDevisStepText("LISTE DEVIS EN ATTENTE", devis)).toBe(
        "#11 – DV-2026-011 (en_attente) | #10 – Sans numéro (en_attente)"
      );
    });

    it("LISTE DEVIS EN ATTENTE : message si aucun en_attente", () => {
      expect(
        resolveDevisStepText("LISTE DEVIS EN ATTENTE", [
          { id: 1, numero_devis: "DV-1", statut: "accepte" },
        ])
      ).toBe("Aucun devis en attente.");
    });

    it("LISTE DERNIER DEVIS : prend data[0] (le plus recent)", () => {
      expect(resolveDevisStepText("LISTE DERNIER DEVIS", devis)).toBe(
        "#12 – DV-2026-012 (accepte)"
      );
    });

    it("LISTE DERNIER DEVIS : message si liste vide", () => {
      expect(resolveDevisStepText("LISTE DERNIER DEVIS", [])).toBe("Aucun devis disponible.");
    });

    it("LISTE DERNIER DEVIS : fallbacks 'Sans numéro' et '-' sur champs manquants", () => {
      expect(resolveDevisStepText("LISTE DERNIER DEVIS", [{ id: 9 }])).toBe(
        "#9 – Sans numéro (-)"
      );
    });
  });

  describe("selectDevisForStep", () => {
    const devis = [
      { id: 12, numero_devis: "DV-2026-012", statut: "accepte" },
      { id: 11, numero_devis: "DV-2026-011", statut: "en_attente" },
      { id: 10, numero_devis: null, statut: "en_attente" },
    ];

    it("retourne null si le label n'est pas une liste devis", () => {
      expect(selectDevisForStep("Relevé pression", devis)).toBeNull();
    });

    it("EN ATTENTE : ne garde que les devis en_attente (miroir du texte)", () => {
      expect(selectDevisForStep("LISTE DEVIS EN ATTENTE", devis)).toEqual([
        { id: 11, numero_devis: "DV-2026-011", statut: "en_attente" },
        { id: 10, numero_devis: null, statut: "en_attente" },
      ]);
    });

    it("EN ATTENTE : tableau vide si aucun en_attente (pas null)", () => {
      expect(
        selectDevisForStep("LISTE DEVIS EN ATTENTE", [
          { id: 1, numero_devis: "DV-1", statut: "accepte" },
        ])
      ).toEqual([]);
    });

    it("DERNIER : ne garde que data[0]", () => {
      expect(selectDevisForStep("LISTE DERNIER DEVIS", devis)).toEqual([
        { id: 12, numero_devis: "DV-2026-012", statut: "accepte" },
      ]);
    });

    it("DERNIER : tableau vide si liste vide", () => {
      expect(selectDevisForStep("LISTE DERNIER DEVIS", [])).toEqual([]);
    });
  });

  describe("getCommentRequirement", () => {
    it("requires_comment + probleme → visible + obligatoire", () => {
      const step = makeStep({ metadata: { requires_comment: true } });
      const res = getCommentRequirement(step, "probleme");
      expect(res.visible).toBe(true);
      expect(res.required).toBe(true);
      expect(res.placeholder).toMatch(/obligatoire/);
    });

    it("requires_comment + ok → visible + optionnel", () => {
      const step = makeStep({ metadata: { requires_comment: true } });
      const res = getCommentRequirement(step, "ok");
      expect(res.visible).toBe(true);
      expect(res.required).toBe(false);
      expect(res.placeholder).toMatch(/Probleme\/Panne/);
    });

    it("extension + choice + probleme → visible + obligatoire", () => {
      const step = makeStep({
        metadata: { is_extension: true, input_kind: "choice" },
      });
      const res = getCommentRequirement(step, "panne");
      expect(res.visible).toBe(true);
      expect(res.required).toBe(true);
    });

    it("extension + choice + ok → non visible", () => {
      const step = makeStep({
        metadata: { is_extension: true, input_kind: "choice" },
      });
      const res = getCommentRequirement(step, "ok");
      expect(res.visible).toBe(false);
      expect(res.required).toBe(false);
      expect(res.placeholder).toBe("");
    });

    it("step quelconque → non visible", () => {
      const step = makeStep();
      const res = getCommentRequirement(step, "ok");
      expect(res.visible).toBe(false);
    });
  });
});
