import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { debounce } from "./debounce";

beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());

describe("debounce", () => {
  it("n'exécute qu'après le silence, et une seule fois", () => {
    const seen: string[] = [];
    const debounced = debounce<[string]>((_isCurrent, value) => seen.push(value), 250);

    debounced.run("j");
    vi.advanceTimersByTime(100);
    debounced.run("je");
    vi.advanceTimersByTime(100);
    debounced.run("jewelry");
    expect(seen).toEqual([]);

    vi.advanceTimersByTime(250);
    // Trois frappes, une seule requête — et c'est la dernière saisie qui part.
    expect(seen).toEqual(["jewelry"]);
  });

  it("périme une exécution dépassée par une frappe plus récente", () => {
    // Le cas qui motive le module : deux requêtes en vol dont la plus ancienne revient en
    // dernier. Sans le jeton, elle écraserait le résultat de la plus récente.
    const settled: { value: string; current: boolean }[] = [];
    const debounced = debounce<[string]>((isCurrent, value) => {
      // La réponse arrive plus tard : on interroge le jeton **à l'arrivée**, pas au départ.
      setTimeout(() => settled.push({ value, current: isCurrent() }), value === "jewel" ? 500 : 10);
    }, 250);

    debounced.run("jewel");
    vi.advanceTimersByTime(250); // « jewel » part
    debounced.run("jewelry manufacturing");
    vi.advanceTimersByTime(250); // « jewelry manufacturing » part
    vi.advanceTimersByTime(500);

    expect(settled).toEqual([
      { value: "jewelry manufacturing", current: true },
      { value: "jewel", current: false },
    ]);
  });

  it("cancel abandonne l'appel en attente", () => {
    const seen: string[] = [];
    const debounced = debounce<[string]>((_isCurrent, value) => seen.push(value), 250);
    debounced.run("jewelry");
    debounced.cancel();
    vi.advanceTimersByTime(1000);
    expect(seen).toEqual([]);
  });

  it("cancel périme aussi ce qui est déjà parti", () => {
    // Fermer le menu (Échap) ne doit pas le voir se rouvrir sur une réponse en retard.
    let current: boolean | null = null;
    const debounced = debounce<[]>((isCurrent) => {
      setTimeout(() => (current = isCurrent()), 10);
    }, 250);
    debounced.run();
    vi.advanceTimersByTime(250);
    debounced.cancel();
    vi.advanceTimersByTime(10);
    expect(current).toBe(false);
  });
});
