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

import { Skeleton, SkeletonGroup, TableSkeleton } from '../components/Skeleton.js';
import { SearchInput } from '../components/SearchInput.js';
import { LoadingDots } from '../components/LoadingDots.js';

beforeEach(() => {
  cleanup();
});

describe('Skeleton', () => {
  it('is aria-hidden so screen readers skip the placeholder', () => {
    const { container } = render(<Skeleton />);
    const node = container.firstChild as HTMLElement;
    expect(node.getAttribute('aria-hidden')).toBe('true');
  });

  it('applies shape-specific classes for each of the 4 variants', () => {
    const { container: text } = render(<Skeleton shape="text" />);
    expect((text.firstChild as HTMLElement).className).toContain('rounded-full');

    const { container: circle } = render(<Skeleton shape="circle" />);
    expect((circle.firstChild as HTMLElement).className).toContain('h-10');

    const { container: card } = render(<Skeleton shape="card" />);
    expect((card.firstChild as HTMLElement).className).toContain('rounded-2xl');

    const { container: block } = render(<Skeleton shape="block" />);
    expect((block.firstChild as HTMLElement).className).toContain('min-h-20');
  });

  it('TableSkeleton renders one row per `rows`', () => {
    const { container } = render(<TableSkeleton rows={4} />);
    expect(container.querySelectorAll('[aria-hidden="true"]').length).toBe(4);
  });
});

describe('SkeletonGroup', () => {
  it('renders the fallback (not children) while loading', () => {
    render(
      <SkeletonGroup loading fallback={<span>placeholder</span>}>
        <span>real content</span>
      </SkeletonGroup>
    );
    expect(screen.getByText('placeholder')).toBeTruthy();
    expect(screen.queryByText('real content')).toBeNull();
  });

  it('renders children once loading flips false', () => {
    render(
      <SkeletonGroup loading={false} fallback={<span>placeholder</span>}>
        <span>real content</span>
      </SkeletonGroup>
    );
    expect(screen.getByText('real content')).toBeTruthy();
    expect(screen.queryByText('placeholder')).toBeNull();
  });

  it('keeps children mounted (hidden) while loading when keepContent is set', () => {
    const { container } = render(
      <SkeletonGroup loading keepContent fallback={<span>placeholder</span>}>
        <span>real content</span>
      </SkeletonGroup>
    );
    expect(container.querySelector('[data-bl-skeleton-group="loading"]')).toBeTruthy();
    expect(screen.getByText('real content')).toBeTruthy();
  });
});

describe('SearchInput', () => {
  function Harness({ clearable }: { clearable?: boolean } = {}) {
    const [v, setV] = React.useState('');
    return <SearchInput value={v} onChange={setV} clearable={clearable} ariaLabel="Search items" />;
  }

  it('exposes a searchbox role with an accessible label', () => {
    render(<Harness />);
    const input = screen.getByRole('searchbox', { name: 'Search items' });
    expect(input).toBeTruthy();
  });

  it('calls onChange with each keystroke value', () => {
    const onChange = vi.fn();
    render(<SearchInput value="" onChange={onChange} ariaLabel="Search" />);
    fireEvent.change(screen.getByRole('searchbox'), { target: { value: 'hello' } });
    expect(onChange).toHaveBeenCalledWith('hello');
  });

  it('shows a clear button only when value is non-empty and clears on click', () => {
    render(<Harness />);
    const input = screen.getByRole('searchbox');
    expect(screen.queryByTestId('bl-search-clear')).toBeNull();
    fireEvent.change(input, { target: { value: 'abc' } });
    const clear = screen.getByTestId('bl-search-clear');
    expect(clear).toBeTruthy();
    fireEvent.click(clear);
    expect((input as HTMLInputElement).value).toBe('');
  });

  it('renders a suggestions slot when provided', () => {
    render(
      <SearchInput value="a" onChange={() => {}} ariaLabel="Search" suggestions={<div>hint</div>} />
    );
    expect(screen.getByTestId('bl-search-suggestions').textContent).toContain('hint');
  });
});

describe('LoadingDots', () => {
  it('is a status region with an accessible label', () => {
    render(<LoadingDots label="Thinking" />);
    const status = screen.getByRole('status', { name: 'Thinking' });
    expect(status.getAttribute('aria-busy')).toBe('true');
  });

  it('renders exactly three animated dots', () => {
    const { container } = render(<LoadingDots />);
    const dots = container.querySelectorAll('[aria-hidden="true"]');
    expect(dots.length).toBe(3);
  });

  it('honours a custom dot colour', () => {
    const { container } = render(<LoadingDots color="rgb(255, 0, 0)" />);
    const firstDot = container.querySelector('[aria-hidden="true"]') as HTMLElement;
    expect(firstDot.style.background).toBe('rgb(255, 0, 0)');
  });
});
