import { Component, signal, TemplateRef, viewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { CarouselComponent, CarouselItemContext } from './carousel.component';

interface TestItem {
  id: number;
  label: string;
}

@Component({
  imports: [CarouselComponent],
  template: `
    <ng-template #itemTemplate let-item let-index="index">
      <article [attr.data-index]="index">{{ item.label }}</article>
    </ng-template>

    <app-carousel
      [items]="items"
      [itemTemplate]="itemTemplate"
      [scrollStep]="120"
      [gap]="16"
      [circular]="circular()"
      ariaLabel="Productos destacados"
    />
  `,
})
class TestHostComponent {
  readonly circular = signal(false);

  readonly items: TestItem[] = [
    { id: 1, label: 'Primero' },
    { id: 2, label: 'Segundo' },
    { id: 3, label: 'Tercero' },
  ];

  readonly carousel = viewChild.required(CarouselComponent<TestItem>);
  readonly itemTemplate =
    viewChild.required<TemplateRef<CarouselItemContext<TestItem>>>('itemTemplate');
}

describe('CarouselComponent', () => {
  let fixture: ComponentFixture<TestHostComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [TestHostComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(TestHostComponent);
    fixture.detectChanges();
  });

  it('renderiza el template provisto para cada elemento', () => {
    const articles = fixture.nativeElement.querySelectorAll('article');

    expect(articles).toHaveLength(3);
    expect(articles[0].textContent.trim()).toBe('Primero');
    expect(articles[1].getAttribute('data-index')).toBe('1');
  });

  it('expone una región accesible y controles con etiquetas', () => {
    const region = fixture.nativeElement.querySelector('[role="region"]');
    const buttons = fixture.nativeElement.querySelectorAll('button');

    expect(region.getAttribute('aria-label')).toBe('Productos destacados');
    expect(buttons[0].getAttribute('aria-label')).toBe('Mostrar elementos anteriores');
    expect(buttons[1].getAttribute('aria-label')).toBe('Mostrar elementos siguientes');
  });

  it('desplaza el viewport en ambas direcciones', () => {
    const viewport = fixture.nativeElement.querySelector('.carousel__viewport') as HTMLElement;
    const scrollBy = vi.fn();
    viewport.scrollBy = scrollBy;

    fixture.componentInstance.carousel().next();
    fixture.componentInstance.carousel().previous();

    expect(scrollBy).toHaveBeenNthCalledWith(1, {
      left: 120,
      behavior: 'smooth',
    });
    expect(scrollBy).toHaveBeenNthCalledWith(2, {
      left: -120,
      behavior: 'smooth',
    });
  });

  it('permite navegar con las flechas del teclado', () => {
    const viewport = fixture.nativeElement.querySelector('.carousel__viewport') as HTMLElement;
    const scrollBy = vi.fn();
    viewport.scrollBy = scrollBy;

    viewport.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
    viewport.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }));

    expect(scrollBy).toHaveBeenCalledTimes(2);
  });

  it('usa todo el ancho y distribuye el espacio sobrante entre elementos completos', () => {
    const host = fixture.nativeElement.querySelector('app-carousel') as HTMLElement;
    const firstItem = fixture.nativeElement.querySelector('.carousel__item') as HTMLElement;

    Object.defineProperty(host, 'clientWidth', {
      configurable: true,
      value: 650,
    });
    firstItem.getBoundingClientRect = vi.fn(
      () =>
        ({
          width: 200,
        }) as DOMRect,
    );

    fixture.componentInstance.carousel().ngAfterViewInit();
    fixture.detectChanges();

    const frame = fixture.nativeElement.querySelector('.carousel__frame') as HTMLElement;
    const items = fixture.nativeElement.querySelectorAll('.carousel__item');

    expect(frame.style.width).toBe('');
    expect(items[0].style.width).toBe('269px');
    expect(items[1].style.width).toBe('269px');
  });

  it('mantiene preparadas las páginas adyacentes y rota al terminar la transición', () => {
    fixture.componentInstance.circular.set(true);
    fixture.detectChanges();

    const host = fixture.nativeElement.querySelector('app-carousel') as HTMLElement;
    const firstItem = fixture.nativeElement.querySelector('.carousel__item') as HTMLElement;

    Object.defineProperty(host, 'clientWidth', {
      configurable: true,
      value: 366,
    });
    firstItem.getBoundingClientRect = vi.fn(
      () =>
        ({
          width: 100,
        }) as DOMRect,
    );

    fixture.componentInstance.carousel().ngAfterViewInit();
    fixture.detectChanges();

    const pages = fixture.nativeElement.querySelectorAll('.carousel__page');
    expect(pages).toHaveLength(3);
    expect(pages[0].querySelectorAll('.carousel__item')).toHaveLength(2);
    expect(pages[0].textContent).toContain('Tercero');
    expect(pages[0].textContent).toContain('Primero');
    expect(pages[1].textContent).toContain('Primero');
    expect(pages[2].textContent).toContain('Tercero');

    fixture.componentInstance.carousel().next();
    fixture.detectChanges();

    const track = fixture.nativeElement.querySelector('.carousel__track') as HTMLElement;
    expect(track.classList).toContain('carousel__track--next');

    track.dispatchEvent(new TransitionEvent('transitionend', { propertyName: 'transform' }));
    fixture.detectChanges();

    const recenteredPages = fixture.nativeElement.querySelectorAll('.carousel__page');
    expect(track.classList).not.toContain('carousel__track--next');
    expect(recenteredPages[1].textContent).toContain('Tercero');
  });
});
