import { describe, it, expect, beforeEach } from 'vitest';
import { cleanup, fireEvent, render, screen } from '@testing-library/react';

import { AudioWaveform } from '../AudioWaveform.js';
import { ImageGenStream } from '../ImageGenStream.js';
import { VideoPlayer } from '../VideoPlayer.js';

beforeEach(() => cleanup());

describe('AudioWaveform', () => {
  it('renders a canvas with the configured size', () => {
    render(<AudioWaveform peaks={[0.1, 0.5, 0.9, 0.4]} width={320} height={64} />);
    const root = screen.getByTestId('bl-audio-waveform');
    const canvas = root.querySelector('canvas');
    expect(canvas).not.toBeNull();
    expect(canvas?.style.width).toBe('320px');
    expect(canvas?.style.height).toBe('64px');
  });

  it('invokes onSeek with the clicked position', () => {
    let seeked: number | null = null;
    render(
      <AudioWaveform
        peaks={[0.1, 0.5, 0.9]}
        width={200}
        height={40}
        onSeek={(p) => {
          seeked = p;
        }}
      />,
    );
    const canvas = screen.getByTestId('bl-audio-waveform').querySelector('canvas')!;
    // Stub bounding rect to 200 px wide for the click math.
    canvas.getBoundingClientRect = () =>
      ({
        x: 0,
        y: 0,
        left: 0,
        top: 0,
        right: 200,
        bottom: 40,
        width: 200,
        height: 40,
        toJSON: () => '',
      }) as DOMRect;
    fireEvent.click(canvas, { clientX: 100, clientY: 20 });
    expect(seeked).toBeCloseTo(0.5, 5);
  });

  it('renders a placeholder waveform when neither peaks nor audioUrl are supplied', () => {
    render(<AudioWaveform bars={16} width={120} height={40} />);
    expect(screen.getByTestId('bl-audio-waveform')).toBeDefined();
  });

  it('clamps bars=0 to a safe minimum (no division-by-zero on canvas)', () => {
    // Should not throw, should render a canvas.
    expect(() => render(<AudioWaveform peaks={[0.5]} bars={0} />)).not.toThrow();
    const canvas = screen.getByTestId('bl-audio-waveform').querySelector('canvas');
    expect(canvas).not.toBeNull();
  });
});

describe('ImageGenStream', () => {
  it('idle status renders the idle placeholder', () => {
    render(<ImageGenStream status="idle" />);
    expect(screen.getByTestId('bl-image-gen-stream-idle')).toBeDefined();
  });

  it('streaming status renders the progress strip + image', () => {
    render(
      <ImageGenStream
        status="streaming"
        snapshot={{ progress: 0.4, partialUrl: 'data:image/gif;base64,R0lGODlh', label: '20/50' }}
      />,
    );
    expect(screen.getByTestId('bl-image-gen-stream-progress')).toBeDefined();
    expect(screen.getByTestId('bl-image-gen-stream-img')).toBeDefined();
    expect(screen.getByTestId('bl-image-gen-stream').getAttribute('data-status')).toBe('streaming');
  });

  it('complete status drops the blur', () => {
    render(<ImageGenStream status="complete" finalUrl="data:image/gif;base64,R0lGODlh" />);
    const img = screen.getByTestId('bl-image-gen-stream-img') as HTMLImageElement;
    expect(img.style.filter).toBe('none');
  });

  it('error status renders the error placeholder', () => {
    render(<ImageGenStream status="error" errorMessage="boom" />);
    const e = screen.getByTestId('bl-image-gen-stream-error');
    expect(e.textContent).toMatch(/boom/);
  });

  it('streaming with no partialUrl renders a placeholder instead of an empty <img>', () => {
    render(<ImageGenStream status="streaming" snapshot={{ progress: 0.1 }} />);
    expect(screen.queryByTestId('bl-image-gen-stream-img')).toBeNull();
    // The waiting placeholder uses the same testid as the idle one (Placeholder helper).
    expect(screen.getByTestId('bl-image-gen-stream-idle').textContent).toMatch(/waiting/i);
    // Progress strip still renders.
    expect(screen.getByTestId('bl-image-gen-stream-progress')).toBeDefined();
  });
});

describe('VideoPlayer', () => {
  it('renders the native <video> element with the src', () => {
    render(<VideoPlayer src="https://example.com/demo.mp4" />);
    const v = screen.getByTestId('bl-video-player-video') as HTMLVideoElement;
    expect(v.src).toMatch(/demo\.mp4$/);
  });

  it('renders chapter buttons that match the chapter list', () => {
    render(
      <VideoPlayer
        src="x.mp4"
        chapters={[
          { id: 'intro', label: 'Intro', startSeconds: 0 },
          { id: 'demo', label: 'Demo', startSeconds: 65 },
          { id: 'qa', label: 'Q&A', startSeconds: 360 },
        ]}
      />,
    );
    expect(screen.getAllByTestId('bl-video-player-chapter')).toHaveLength(3);
  });

  it('renders the caption rail when captions are supplied', () => {
    render(
      <VideoPlayer
        src="x.mp4"
        captions={[
          { id: 'c1', start: 0, end: 5, text: 'Hi there.' },
        ]}
      />,
    );
    expect(screen.getByTestId('bl-video-player-caption')).toBeDefined();
  });
});
