import { describe, expect, it, vi } from "vitest";
import { runChunked } from "./runChunked";

describe("runChunked", () => {
  it("бьёт набор на чанки и шлёт их по очереди", async () => {
    const items = Array.from({ length: 450 }, (_, i) => i);
    const sizes: number[] = [];
    const fn = vi.fn(async (part: number[]) => {
      sizes.push(part.length);
      return part.length; // снятие: число снятых в чанке
    });

    const results = await runChunked(items, 200, fn);

    expect(fn).toHaveBeenCalledTimes(3);
    expect(sizes).toEqual([200, 200, 50]);
    expect(results).toEqual([200, 200, 50]);
    // суммирование на стороне вызова
    expect(results.reduce((a, b) => a + b, 0)).toBe(450);
  });

  it("сообщает прогресс (обработано/всего) после каждого чанка", async () => {
    const items = Array.from({ length: 450 }, (_, i) => i);
    const progress: Array<[number, number]> = [];

    await runChunked(items, 200, async () => 0, (done, total) => progress.push([done, total]));

    expect(progress).toEqual([
      [200, 450],
      [400, 450],
      [450, 450],
    ]);
  });

  it("пустой набор — не зовёт fn и возвращает пусто", async () => {
    const fn = vi.fn(async () => 0);
    const results = await runChunked([], 200, fn);
    expect(fn).not.toHaveBeenCalled();
    expect(results).toEqual([]);
  });

  it("ошибка чанка пробрасывается наружу", async () => {
    const items = [1, 2, 3];
    await expect(
      runChunked(items, 2, async (part) => {
        if (part.includes(3)) throw new Error("boom");
        return part.length;
      }),
    ).rejects.toThrow("boom");
  });

  it("аккумулирует списки (бронь) с сохранением порядка", async () => {
    const items = [1, 2, 3, 4, 5];
    const results = await runChunked(items, 2, async (part) => part.map((n) => n * 10));
    expect(results.flat()).toEqual([10, 20, 30, 40, 50]);
  });
});
