/**
 * Direct unit coverage for the playback-progress pub/sub — focused on the
 * delta-short-circuit behaviour that keeps idle CPU bounded. The end-to-end
 * "Tauri event → emit → subscriber" path is covered by
 * `playerStore.progress.test.ts`.
 */
import { afterEach, describe, expect, it, vi } from 'vitest';
import {
  _resetPlaybackProgressForTest,
  emitPlaybackProgress,
  getPlaybackProgressSnapshot,
  subscribePlaybackProgress,
} from './playbackProgress';

afterEach(() => {
  _resetPlaybackProgressForTest();
});

describe('getPlaybackProgressSnapshot', () => {
  it('starts at zero', () => {
    const snap = getPlaybackProgressSnapshot();
    expect(snap).toEqual({ currentTime: 0, progress: 0, buffered: 0, buffering: false });
  });

  it('returns the latest snapshot after an emit', () => {
    emitPlaybackProgress({ currentTime: 42, progress: 0.5, buffered: 0.7 });
    expect(getPlaybackProgressSnapshot()).toEqual({
      currentTime: 42, progress: 0.5, buffered: 0.7, buffering: false,
    });
  });
});

describe('subscribePlaybackProgress', () => {
  it('fires the listener with (next, prev) on a meaningful change', () => {
    const cb = vi.fn();
    subscribePlaybackProgress(cb);
    emitPlaybackProgress({ currentTime: 1, progress: 0.1, buffered: 0.2 });
    expect(cb).toHaveBeenCalledTimes(1);
    expect(cb.mock.calls[0][0]).toEqual({
      currentTime: 1, progress: 0.1, buffered: 0.2, buffering: false,
    });
    expect(cb.mock.calls[0][1]).toEqual({ currentTime: 0, progress: 0, buffered: 0, buffering: false });
  });

  it('returns an unsubscribe that detaches the listener', () => {
    const cb = vi.fn();
    const unsub = subscribePlaybackProgress(cb);
    unsub();
    emitPlaybackProgress({ currentTime: 1, progress: 0.1, buffered: 0.2 });
    expect(cb).not.toHaveBeenCalled();
  });

  it('fans out to multiple listeners', () => {
    const a = vi.fn();
    const b = vi.fn();
    subscribePlaybackProgress(a);
    subscribePlaybackProgress(b);
    emitPlaybackProgress({ currentTime: 1, progress: 0.1, buffered: 0.2 });
    expect(a).toHaveBeenCalledTimes(1);
    expect(b).toHaveBeenCalledTimes(1);
  });
});

describe('emitPlaybackProgress delta short-circuit', () => {
  it('skips emits whose deltas are below thresholds (currentTime <0.005, progress/buffered <0.0002)', () => {
    const cb = vi.fn();
    subscribePlaybackProgress(cb);
    emitPlaybackProgress({ currentTime: 1.0, progress: 0.5, buffered: 0.6 });
    expect(cb).toHaveBeenCalledTimes(1);
    // All deltas below the cut-off — must be suppressed.
    emitPlaybackProgress({ currentTime: 1.001, progress: 0.50005, buffered: 0.60005 });
    expect(cb).toHaveBeenCalledTimes(1);
    // currentTime delta crosses the 0.005 threshold — fires.
    emitPlaybackProgress({ currentTime: 1.01, progress: 0.50005, buffered: 0.60005 });
    expect(cb).toHaveBeenCalledTimes(2);
  });

  it('fires when only progress crosses its threshold', () => {
    const cb = vi.fn();
    subscribePlaybackProgress(cb);
    emitPlaybackProgress({ currentTime: 1.0, progress: 0.5, buffered: 0.6 });
    cb.mockClear();
    emitPlaybackProgress({ currentTime: 1.0, progress: 0.5005, buffered: 0.6 });
    expect(cb).toHaveBeenCalledTimes(1);
  });

  it('does not advance the snapshot when an emit is suppressed', () => {
    emitPlaybackProgress({ currentTime: 1.0, progress: 0.5, buffered: 0.6 });
    emitPlaybackProgress({ currentTime: 1.001, progress: 0.5, buffered: 0.6 });
    expect(getPlaybackProgressSnapshot().currentTime).toBe(1.0);
  });
});
