import { describe, it, expect } from 'bun:test';
import { createReactiveState } from './reactive-state';

describe('createReactiveState', () => {
  it('should call callback on property change with new and old state', async () => {
    const initial = { count: 0 };
    let called = false;
    let newState: any, oldState: any;
    const [state, subscribe] = createReactiveState(initial);
    subscribe((n, o) => {
      called = true;
      newState = n;
      oldState = o;
    });
    state.count = 1;
    await new Promise((resolve) => setTimeout(resolve, 0)); // wait for microtask
    expect(called).toBe(true);
    expect(newState.count).toBe(1);
    expect(oldState.count).toBe(0);
  });

  it('should maintain reactivity when assigned to another variable', async () => {
    const initial = { count: 0 };
    let called = false;
    const [state, subscribe] = createReactiveState(initial);
    subscribe((n, o) => {
      called = true;
    });
    const newState = state;
    newState.count = 1;
    await new Promise((resolve) => setTimeout(resolve, 0)); // wait for microtask
    expect(called).toBe(true);
    expect(newState.count).toBe(1);
  });

  it('should be reactive when new properties are added', async () => {
    const initial = { count: 0 };
    let called = 0;
    const [state, subscribe] = createReactiveState(initial);
    subscribe((n, o) => {
      called++;
    });
    state.newproperty = 'hello';
    await new Promise((resolve) => setTimeout(resolve, 0)); // wait for microtask
    state.secondProperty = 'world';
    await new Promise((resolve) => setTimeout(resolve, 0)); // wait for microtask
    expect(called).toBe(2);
    expect(state.count).toBe(0);
    expect(state.newproperty).toBe('hello');
    expect(state.secondProperty).toBe('world');
  });

  it('should batch multiple changes into one callback call', async () => {
    const initial = { a: 1, b: 2 };
    let callCount = 0;
    let finalNewState: any;
    const [state, subscribe] = createReactiveState(initial);
    subscribe((n) => {
      callCount++;
      finalNewState = n;
    });
    state.a = 10;
    state.b = 20;
    await new Promise((resolve) => setTimeout(resolve, 0));
    expect(callCount).toBe(1);
    expect(finalNewState.a).toBe(10);
    expect(finalNewState.b).toBe(20);
  });

  it('should support nested object changes', async () => {
    const initial = { nested: { value: 1 } };
    let called = false;
    let newState: any;
    const [state, subscribe] = createReactiveState(initial);
    subscribe((n) => {
      called = true;
      newState = n;
    });
    state.nested.value = 2;
    await new Promise((resolve) => setTimeout(resolve, 0));
    expect(called).toBe(true);
    expect(newState.nested.value).toBe(2);
  });

  it('should allow unsubscribing from callbacks', async () => {
    const initial = { count: 0 };
    let callCount = 0;
    const [state, subscribe] = createReactiveState(initial);
    const unsubscribe = subscribe(() => {
      callCount++;
    });
    state.count = 1;
    await new Promise((resolve) => setTimeout(resolve, 0));
    expect(callCount).toBe(1);
    unsubscribe();
    state.count = 2;
    await new Promise((resolve) => setTimeout(resolve, 0));
    expect(callCount).toBe(1); // should not increase
  });

  it('should support multiple subscribers', async () => {
    const initial = { count: 0 };
    let callCount1 = 0,
      callCount2 = 0;
    const [state, subscribe] = createReactiveState(initial);
    const unsubscribe1 = subscribe(() => callCount1++);
    const unsubscribe2 = subscribe(() => callCount2++);
    state.count = 1;
    await new Promise((resolve) => setTimeout(resolve, 0));
    expect(callCount1).toBe(1);
    expect(callCount2).toBe(1);
    unsubscribe1();
    state.count = 2;
    await new Promise((resolve) => setTimeout(resolve, 0));
    expect(callCount1).toBe(1);
    expect(callCount2).toBe(2);
  });
});
