import { describe, expect, test } from "bun:test";
import { batch, computed, effect, resource, signal, watch } from "../src/index.ts";

describe("reactive primitives", () => {
  test("signals skip no-op updates", () => {
    const value = signal(1);
    const seen: number[] = [];
    value.subscribe((next) => seen.push(next));
    value.set(1);
    value.set(2);
    expect(seen).toEqual([2]);
  });

  test("batch coalesces notifications", () => {
    const value = signal(0);
    const seen: number[] = [];
    value.subscribe((next) => seen.push(next));
    batch(() => {
      value.set(1);
      value.set(2);
      value.set(3);
    });
    expect(seen).toEqual([3]);
  });

  test("computed values and effects track dependencies", () => {
    const count = signal(2);
    const doubled = computed(() => count.get() * 2);
    const seen: number[] = [];
    const dispose = effect(() => {
      seen.push(doubled.get());
    });
    count.set(3);
    dispose();
    count.set(4);
    expect(seen).toEqual([4, 6]);
  });

  test("effects ignore accidental non-function return values", () => {
    const value = signal(0);
    const seen: number[] = [];
    const dispose = effect(() => seen.push(value.get()) as unknown as void);
    value.set(1);
    dispose();
    expect(seen).toEqual([0, 1]);
  });
});

test("watch reports undefined as the previous value for an immediate run", () => {
  const value = signal(1);
  const seen: Array<[number, number | undefined]> = [];
  const stop = watch(
    () => value.get(),
    (next, previous) => void seen.push([next, previous]),
    { immediate: true },
  );
  value.set(2);
  stop();

  expect(seen).toEqual([
    [1, undefined],
    [2, 1],
  ]);
});

test("aborting a pending resource does not dereference a cleared controller", async () => {
  let resolve!: (value: string) => void;
  const pending = new Promise<string>((done) => {
    resolve = done;
  });
  const value = resource(() => pending);
  const run = value.run();
  value.abort("cancelled");
  resolve("late");

  await expect(run).resolves.toBeUndefined();
  expect(value.status.get()).toBe("idle");
  expect(value.data.get()).toBeUndefined();
});
