import { describe, expect, it } from "vitest";
import { chunk, deselectStripRange, selectionAfterCheck, selectStripRange } from "./slotSelection";

const keyAt = (i: number) => `k${i}`;

describe("selectionAfterCheck", () => {
  it("добавляет ключ при обычном клике по невыбранной строке", () => {
    const next = selectionAfterCheck(new Set(), keyAt, 2, false, null);
    expect([...next]).toEqual(["k2"]);
  });

  it("снимает ключ при обычном клике по выбранной строке", () => {
    const next = selectionAfterCheck(new Set(["k2"]), keyAt, 2, false, 5);
    expect(next.has("k2")).toBe(false);
  });

  it("Shift выбирает диапазон от якоря до индекса (целевая была не выбрана)", () => {
    const next = selectionAfterCheck(new Set(), keyAt, 4, true, 1);
    expect([...next].sort()).toEqual(["k1", "k2", "k3", "k4"]);
  });

  it("Shift снимает диапазон, если целевая строка уже выбрана", () => {
    const current = new Set(["k1", "k2", "k3", "k4", "k9"]);
    const next = selectionAfterCheck(current, keyAt, 4, true, 1);
    expect([...next]).toEqual(["k9"]);
  });

  it("Shift работает при обратном порядке (якорь больше индекса)", () => {
    const next = selectionAfterCheck(new Set(), keyAt, 1, true, 4);
    expect([...next].sort()).toEqual(["k1", "k2", "k3", "k4"]);
  });

  it("Shift без якоря ведёт себя как обычное переключение", () => {
    const next = selectionAfterCheck(new Set(), keyAt, 3, true, null);
    expect([...next]).toEqual(["k3"]);
  });

  it("не мутирует исходное множество", () => {
    const current = new Set(["k2"]);
    selectionAfterCheck(current, keyAt, 5, false, null);
    expect([...current]).toEqual(["k2"]);
  });
});

describe("selectStripRange", () => {
  const members = ["a", "b", "c", "d"]; // в порядке времени

  it("выделяет членов полосы от первого к последнему включительно", () => {
    const next = selectStripRange(new Set(), members, "b", "d");
    expect([...next].sort()).toEqual(["b", "c", "d"]);
  });

  it("работает при обратном порядке кликов (последний → первый)", () => {
    const next = selectStripRange(new Set(), members, "d", "b");
    expect([...next].sort()).toEqual(["b", "c", "d"]);
  });

  it("один и тот же ключ → один член", () => {
    const next = selectStripRange(new Set(), members, "c", "c");
    expect([...next]).toEqual(["c"]);
  });

  it("сжимает выделение: повторный диапазон ближе к якорю снимает хвост полосы", () => {
    // якорь "a", сначала растянули до "d" (a..d), потом Shift+клик по "b" → остаётся a..b, c и d сняты.
    const expanded = selectStripRange(new Set(), members, "a", "d");
    expect([...expanded].sort()).toEqual(["a", "b", "c", "d"]);
    const shrunk = selectStripRange(expanded, members, "a", "b");
    expect([...shrunk].sort()).toEqual(["a", "b"]);
  });

  it("снимает хвост полосы, но не трогает выбор в других полосах", () => {
    const current = new Set(["a", "b", "c", "d", "x"]);
    const next = selectStripRange(current, members, "a", "b");
    expect([...next].sort()).toEqual(["a", "b", "x"]);
  });

  it("сохраняет уже выбранное за пределами полосы", () => {
    const next = selectStripRange(new Set(["x"]), members, "a", "b");
    expect([...next].sort()).toEqual(["a", "b", "x"]);
  });

  it("ключ вне полосы → выбор без изменений", () => {
    const next = selectStripRange(new Set(["x"]), members, "a", "zzz");
    expect([...next]).toEqual(["x"]);
  });

  it("не мутирует исходное множество", () => {
    const current = new Set(["x"]);
    selectStripRange(current, members, "a", "b");
    expect([...current]).toEqual(["x"]);
  });
});

describe("deselectStripRange", () => {
  const members = ["a", "b", "c", "d"]; // в порядке времени

  it("снимает ровно члены диапазона, остальной выбор не трогает", () => {
    const current = new Set(["a", "b", "c", "d", "x"]);
    const next = deselectStripRange(current, members, "b", "d");
    expect([...next].sort()).toEqual(["a", "x"]);
  });

  it("повторный Shift+клик по тому же концу снимает то, что выделил selectStripRange", () => {
    const selected = selectStripRange(new Set(["x"]), members, "a", "c"); // a,b,c,x
    const cleared = deselectStripRange(selected, members, "a", "c");
    expect([...cleared].sort()).toEqual(["x"]);
  });

  it("работает при обратном порядке (toKey раньше fromKey)", () => {
    const next = deselectStripRange(new Set(["a", "b", "c", "d"]), members, "d", "b");
    expect([...next].sort()).toEqual(["a"]);
  });

  it("ключ вне полосы → выбор без изменений; вход не мутируется", () => {
    const current = new Set(["a", "b"]);
    const next = deselectStripRange(current, members, "a", "zzz");
    expect([...next].sort()).toEqual(["a", "b"]);
    expect([...current].sort()).toEqual(["a", "b"]);
  });
});

describe("chunk", () => {
  it("бьёт массив на части заданного размера, последняя — остаток", () => {
    expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
  });

  it("размер >= длины — один чанк", () => {
    expect(chunk([1, 2], 10)).toEqual([[1, 2]]);
  });

  it("пустой массив — нет чанков", () => {
    expect(chunk([], 3)).toEqual([]);
  });

  it("size<=0 — единственный чанк со всем массивом", () => {
    expect(chunk([1, 2, 3], 0)).toEqual([[1, 2, 3]]);
  });
});
