import {
  ChangeDetectionStrategy,
  Component,
  PLATFORM_ID,
  WritableSignal,
  computed,
  effect,
  inject,
  input,
  output,
  signal,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Component({
  selector: 'app-main-carousel',
  templateUrl: './main-carousel.component.html',
  styleUrl: './main-carousel.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MainCarouselComponent {
  private readonly platformId = inject(PLATFORM_ID);
  private readonly requestedIndex = signal(0);
  private readonly paused = signal(false);
  private readonly loadedIndexes = signal<ReadonlySet<number>>(new Set());
  private readonly settledIndexes = signal<ReadonlySet<number>>(new Set());
  private firstImageReadyEmitted = false;

  readonly images = input.required<readonly string[]>();
  readonly imageAlts = input<readonly string[]>([]);
  readonly autoSlideInterval = input(5000);
  readonly ariaLabel = input('Imágenes destacadas');
  readonly firstImageReady = output<void>();

  protected readonly activeIndex = computed(() => {
    const imageCount = this.images().length;

    return imageCount ? this.requestedIndex() % imageCount : 0;
  });
  protected readonly hasMultipleImages = computed(() => this.images().length > 1);
  protected readonly sequentialLoadIndex = computed(() => {
    const settledIndexes = this.settledIndexes();

    for (let index = 0; index < this.images().length; index += 1) {
      if (!settledIndexes.has(index)) {
        return index;
      }
    }

    return -1;
  });

  constructor() {
    effect(() => {
      this.images();
      this.requestedIndex.set(0);
      this.loadedIndexes.set(new Set());
      this.settledIndexes.set(new Set());
      this.firstImageReadyEmitted = false;
    });

    effect(() => {
      const imageCount = this.images().length;
      const interval = Math.max(this.autoSlideInterval(), 1000);

      if (!isPlatformBrowser(this.platformId) || imageCount < 2) {
        return;
      }

      const timer = window.setInterval(() => {
        if (!this.paused()) {
          this.requestedIndex.update((index) => {
            const nextIndex = (index + 1) % imageCount;

            return this.loadedIndexes().has(nextIndex) ? nextIndex : index;
          });
        }
      }, interval);

      return () => window.clearInterval(timer);
    });
  }

  protected selectImage(index: number): void {
    if (index >= 0 && index < this.images().length) {
      this.requestedIndex.set(index);
    }
  }

  protected setPaused(paused: boolean): void {
    this.paused.set(paused);
  }

  protected imageSource(image: string, index: number): string | null {
    const shouldLoad =
      this.loadedIndexes().has(index) ||
      index === this.sequentialLoadIndex() ||
      index === this.activeIndex();

    return shouldLoad ? image : null;
  }

  protected handleImageLoad(index: number): void {
    this.updateIndexSet(this.loadedIndexes, index, true);
    this.updateIndexSet(this.settledIndexes, index, true);
    this.notifyFirstImageReady(index);
  }

  protected handleImageError(index: number): void {
    this.updateIndexSet(this.loadedIndexes, index, false);
    this.updateIndexSet(this.settledIndexes, index, true);
    this.notifyFirstImageReady(index);
  }

  protected handleKeydown(event: KeyboardEvent): void {
    if (!this.hasMultipleImages()) {
      return;
    }

    if (event.key === 'ArrowLeft') {
      event.preventDefault();
      this.requestedIndex.update(
        (index) => (index - 1 + this.images().length) % this.images().length,
      );
    }

    if (event.key === 'ArrowRight') {
      event.preventDefault();
      this.requestedIndex.update((index) => (index + 1) % this.images().length);
    }
  }

  protected imageAlt(index: number): string {
    return this.imageAlts()[index] || `Imagen destacada ${index + 1}`;
  }

  private updateIndexSet(
    target: WritableSignal<ReadonlySet<number>>,
    index: number,
    present: boolean,
  ): void {
    target.update((current) => {
      const updated = new Set(current);
      present ? updated.add(index) : updated.delete(index);

      return updated;
    });
  }

  private notifyFirstImageReady(index: number): void {
    if (index !== 0 || this.firstImageReadyEmitted) {
      return;
    }

    this.firstImageReadyEmitted = true;
    this.firstImageReady.emit();
  }
}
