// @vitest-environment jsdom
import { act, cleanup, render, waitFor } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { useGradientController } from '../useGradientController';

class MockMotionValue {
  private listeners = new Set<(value: number) => void>();

  constructor(private current: number) {}

  on(event: string, handler: (value: number) => void) {
    if (event !== 'change') {
      return () => {};
    }
    this.listeners.add(handler);
    return () => {
      this.listeners.delete(handler);
    };
  }

  get() {
    return this.current;
  }

  set(value: number) {
    this.current = value;
    this.listeners.forEach((listener) => listener(value));
  }
}

vi.mock('framer-motion', () => {
  return {
    useMotionValue: (initial: number) => new MockMotionValue(initial),
    animate: (motionValue: MockMotionValue, target: number) => {
      motionValue.set(target);
      return {
        stop: () => {},
      };
    },
  };
});

describe('useGradientController', () => {
  afterEach(() => {
    cleanup();
  });

  it('updates state, uniforms, and overrides as expected', async () => {
    const updates: Array<ReturnType<typeof useGradientController>> = [];

    const Harness = ({
      onUpdate,
    }: {
      onUpdate: (value: ReturnType<typeof useGradientController>) => void;
    }) => {
      const controller = useGradientController('Static');
      onUpdate(controller);
      return null;
    };

    render(
      <Harness
        onUpdate={(controller) => {
          updates.push(controller);
        }}
      />
    );

    await waitFor(() => {
      expect(updates.length).toBeGreaterThan(0);
    });

    const latest = () => updates[updates.length - 1];

    expect(latest().state).toBe('Static');
    expect(latest().getSnapshot().flowSpeed).toBe(0);

    await act(async () => {
      latest().setUniform('flowSpeed', 0.4);
    });

    await waitFor(() => {
      expect(latest().motionValues.flowSpeed.get()).toBeCloseTo(0.4);
    });

    await act(async () => {
      latest().clearOverrides();
    });

    await waitFor(() => {
      expect(latest().motionValues.flowSpeed.get()).toBe(0);
    });

    await act(async () => {
      latest().setState('EmptyChat', { immediate: true });
    });

    await waitFor(() => {
      expect(latest().state).toBe('EmptyChat');
    });

    await act(async () => {
      latest().setUniform('vignetteRadius', 0.75, { immediate: true });
    });

    expect(latest().motionValues.vignetteRadius.get()).toBe(0.75);
    expect(Object.keys(latest().presets)).toContain('Thinking');
  });

  it('applies multiple uniforms and resolves overrides correctly', async () => {
    const updates: Array<ReturnType<typeof useGradientController>> = [];

    const Harness = ({
      onUpdate,
    }: {
      onUpdate: (value: ReturnType<typeof useGradientController>) => void;
    }) => {
      const controller = useGradientController('Thinking');
      onUpdate(controller);
      return null;
    };

    render(
      <Harness
        onUpdate={(controller) => {
          updates.push(controller);
        }}
      />
    );

    await waitFor(() => {
      expect(updates.length).toBeGreaterThan(0);
    });

    const latest = () => updates[updates.length - 1];

    expect(latest().state).toBe('Thinking');

    await act(async () => {
      latest().setUniform('colorScale', 0.3);
      latest().setUniform('flowWarp', 2);
      latest().setUniform('flowFrequency', 8);
    });

    await waitFor(() => {
      expect(latest().motionValues.colorScale.get()).toBeCloseTo(0.3);
      expect(latest().motionValues.flowWarp.get()).toBe(2);
      expect(latest().motionValues.flowFrequency.get()).toBe(8);
    });

    const snapshot = latest().getSnapshot();
    expect(snapshot.colorScale).toBeCloseTo(0.3);
    expect(snapshot.flowWarp).toBe(2);
    expect(snapshot.flowFrequency).toBe(8);
  });
});
