import { act, renderHook } from '@testing-library/react';

import useSpeakingState, {
  SPEAKER_AUDIO_LEVEL_THRESHOLD,
  SPEAKER_FADE_OUT_MS,
  SPEAKER_SILENCE_DEBOUNCE_MS,
  SPEAKER_WAVES_FREEZE_DELAY_MS,
} from './useSpeakingState';

const SPEAKING_LEVEL = SPEAKER_AUDIO_LEVEL_THRESHOLD;
const SILENT_LEVEL = SPEAKER_AUDIO_LEVEL_THRESHOLD - 1;

describe('useSpeakingState', () => {
  beforeEach(() => jest.useFakeTimers());
  afterEach(() => jest.useRealTimers());

  it('starts hidden with waves paused and no transition', () => {
    const { result } = renderHook(({ level }) => useSpeakingState(level), {
      initialProps: { level: 0 },
    });

    expect(result.current).toEqual({
      isVisible: false,
      isWavesAnimating: false,
      transitionMode: 'none',
    });
  });

  it('fades in when audio level reaches the speaking threshold', () => {
    const { result, rerender } = renderHook(
      ({ level }) => useSpeakingState(level),
      { initialProps: { level: 0 } },
    );

    rerender({ level: SPEAKING_LEVEL });

    expect(result.current).toEqual({
      isVisible: true,
      isWavesAnimating: true,
      transitionMode: 'fadeIn',
    });
  });

  it('does not re-trigger fadeIn while audio stays above threshold', () => {
    const { result, rerender } = renderHook(
      ({ level }) => useSpeakingState(level),
      { initialProps: { level: SPEAKING_LEVEL } },
    );

    expect(result.current.transitionMode).toBe('fadeIn');

    rerender({ level: SPEAKING_LEVEL + 10 });

    expect(result.current).toEqual({
      isVisible: true,
      isWavesAnimating: true,
      transitionMode: 'fadeIn',
    });
  });

  it('freezes waves, then fades out, then clears the transition on sustained silence', () => {
    const { result, rerender } = renderHook(
      ({ level }) => useSpeakingState(level),
      { initialProps: { level: SPEAKING_LEVEL } },
    );

    rerender({ level: SILENT_LEVEL });

    expect(result.current.isVisible).toBe(true);
    expect(result.current.isWavesAnimating).toBe(true);

    act(() => {
      jest.advanceTimersByTime(SPEAKER_WAVES_FREEZE_DELAY_MS);
    });
    expect(result.current.isVisible).toBe(true);
    expect(result.current.isWavesAnimating).toBe(false);

    act(() => {
      jest.advanceTimersByTime(SPEAKER_SILENCE_DEBOUNCE_MS);
    });
    expect(result.current.isVisible).toBe(false);
    expect(result.current.transitionMode).toBe('fadeOut');

    act(() => {
      jest.advanceTimersByTime(SPEAKER_FADE_OUT_MS);
    });
    expect(result.current).toEqual({
      isVisible: false,
      isWavesAnimating: false,
      transitionMode: 'none',
    });
  });

  it('snaps back to speaking with no transition when audio resumes during the silence debounce', () => {
    const { result, rerender } = renderHook(
      ({ level }) => useSpeakingState(level),
      { initialProps: { level: SPEAKING_LEVEL } },
    );

    rerender({ level: SILENT_LEVEL });
    act(() => {
      jest.advanceTimersByTime(SPEAKER_WAVES_FREEZE_DELAY_MS + 50);
    });
    expect(result.current.isWavesAnimating).toBe(false);

    rerender({ level: SPEAKING_LEVEL });

    expect(result.current).toEqual({
      isVisible: true,
      isWavesAnimating: true,
      transitionMode: 'none',
    });

    act(() => {
      jest.advanceTimersByTime(
        SPEAKER_SILENCE_DEBOUNCE_MS + SPEAKER_FADE_OUT_MS,
      );
    });
    expect(result.current.isVisible).toBe(true);
    expect(result.current.transitionMode).toBe('none');
  });

  it('snaps back to speaking with no transition when audio resumes during fade-out', () => {
    const { result, rerender } = renderHook(
      ({ level }) => useSpeakingState(level),
      { initialProps: { level: SPEAKING_LEVEL } },
    );

    rerender({ level: SILENT_LEVEL });
    act(() => {
      jest.advanceTimersByTime(
        SPEAKER_WAVES_FREEZE_DELAY_MS + SPEAKER_SILENCE_DEBOUNCE_MS + 50,
      );
    });
    expect(result.current.isVisible).toBe(false);
    expect(result.current.transitionMode).toBe('fadeOut');

    rerender({ level: SPEAKING_LEVEL });

    expect(result.current).toEqual({
      isVisible: true,
      isWavesAnimating: true,
      transitionMode: 'none',
    });
  });

  it('ignores silence when the speaker was never speaking', () => {
    const { result, rerender } = renderHook(
      ({ level }) => useSpeakingState(level),
      { initialProps: { level: 0 } },
    );

    rerender({ level: SILENT_LEVEL });
    act(() => {
      jest.advanceTimersByTime(
        SPEAKER_WAVES_FREEZE_DELAY_MS +
          SPEAKER_SILENCE_DEBOUNCE_MS +
          SPEAKER_FADE_OUT_MS,
      );
    });

    expect(result.current).toEqual({
      isVisible: false,
      isWavesAnimating: false,
      transitionMode: 'none',
    });
  });

  it('clears pending timers on unmount', () => {
    const { rerender, unmount } = renderHook(
      ({ level }) => useSpeakingState(level),
      { initialProps: { level: SPEAKING_LEVEL } },
    );

    rerender({ level: SILENT_LEVEL });
    unmount();

    expect(() => jest.runAllTimers()).not.toThrow();
    expect(jest.getTimerCount()).toBe(0);
  });
});
