import {
  ChangeDetectionStrategy,
  Component,
  ViewEncapsulation,
  input,
  computed,
  signal,
  viewChild,
  ElementRef,
  AfterViewInit,
  OnDestroy,
} from '@angular/core';
import { IconButtonComponent } from '@isa/ui/buttons';
import { provideIcons } from '@ng-icons/core';
import { isaActionChevronLeft, isaActionChevronRight } from '@isa/icons';

@Component({
  selector: 'ui-carousel',
  exportAs: 'uiCarousel',
  imports: [IconButtonComponent],
  templateUrl: './carousel.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [provideIcons({ isaActionChevronLeft, isaActionChevronRight })],
  host: {
    '[class]': '["ui-carousel", arrowAutoHideClass()]',
    '[tabindex]': '0',
    '(keydown.ArrowLeft)': 'navigateToPrevious($event)',
    '(keydown.ArrowRight)': 'navigateToNext($event)',
  },
})
export class CarouselComponent implements AfterViewInit, OnDestroy {
  #resizeObserver = new ResizeObserver(() => {
    this.updateBounds();
    this.updateNavigationState();
  });

  // Input signals
  gap = input<string>('1rem');
  arrowAutoHide = input<boolean>(true);
  disabled = input<boolean>(false);

  // View child for container
  container = viewChild.required<ElementRef<HTMLDivElement>>('container');

  // Transform position in pixels
  private currentTranslateX = signal(0);

  // Touch/drag state
  readonly isDragging = signal(false);
  private dragStartX = 0;
  private dragStartTranslateX = 0;
  private containerWidth = 0;
  private contentWidth = 0;

  // Computed transform style (use translate3d for hardware acceleration)
  transformX = computed(
    () => `translate3d(${this.currentTranslateX()}px, 0, 0)`,
  );

  // Internal state signals
  canScrollLeft = signal(false);
  canScrollRight = signal(false);

  // Computed signals
  arrowAutoHideClass = computed(() =>
    this.arrowAutoHide() ? 'ui-carousel--auto-hide' : '',
  );

  ngAfterViewInit(): void {
    const containerEl = this.container()?.nativeElement;
    this.updateBounds();
    this.updateNavigationState();

    containerEl.addEventListener('touchstart', this.onTouchStart, {
      passive: true,
    });
    containerEl.addEventListener('touchmove', this.onTouchMove, {
      passive: false,
    });
    containerEl.addEventListener('touchend', this.onTouchEnd);
    containerEl.addEventListener('mousedown', this.onMouseDown);
    document.addEventListener('mousemove', this.onMouseMove);
    document.addEventListener('mouseup', this.onMouseUp);
    containerEl.addEventListener('mouseleave', this.onMouseLeave);

    this.#resizeObserver.observe(containerEl);
  }

  ngOnDestroy(): void {
    const containerEl = this.container()?.nativeElement;
    containerEl.removeEventListener('touchstart', this.onTouchStart);
    containerEl.removeEventListener('touchmove', this.onTouchMove);
    containerEl.removeEventListener('touchend', this.onTouchEnd);
    containerEl.removeEventListener('mousedown', this.onMouseDown);
    document.removeEventListener('mousemove', this.onMouseMove);
    document.removeEventListener('mouseup', this.onMouseUp);
    containerEl.removeEventListener('mouseleave', this.onMouseLeave);
    this.#resizeObserver.disconnect();
  }

  /**
   * Update container and content width bounds
   */
  private updateBounds(): void {
    const containerEl = this.container()?.nativeElement;
    if (!containerEl) return;

    // Container width = visible viewport (parent element)
    const parentEl = containerEl.parentElement;
    if (!parentEl) return;
    this.containerWidth = parentEl.offsetWidth;

    // Content width = total width of all children (the container itself)
    this.contentWidth = containerEl.offsetWidth;
  }

  /**
   * Update navigation state (can navigate left/right)
   */
  private updateNavigationState(): void {
    const translate = this.currentTranslateX();
    const maxTranslate = Math.min(0, this.containerWidth - this.contentWidth);

    // Can navigate left if not at start (translateX < 0)
    this.canScrollLeft.set(translate < 0);

    // Can navigate right if not at end
    this.canScrollRight.set(translate > maxTranslate);
  }

  /**
   * Calculate the navigation amount based on fully visible items
   */
  private calculateNavigationAmount(): number {
    const containerEl = this.container()?.nativeElement;
    if (!containerEl) return 0;

    const parentEl = containerEl.parentElement;
    if (!parentEl) return 0;

    const children = Array.from(containerEl.children) as HTMLElement[];
    if (children.length === 0) return this.containerWidth;

    // Use parent rect (visible viewport) not container rect (full content)
    const viewportRect = parentEl.getBoundingClientRect();
    const viewportLeft = viewportRect.left;
    const viewportRight = viewportRect.right;

    // Count fully visible items
    let firstFullyVisibleItem: HTMLElement | null = null;
    let fullyVisibleCount = 0;

    for (const child of children) {
      const childRect = child.getBoundingClientRect();
      const isFullyVisible =
        childRect.left >= viewportLeft - 1 &&
        childRect.right <= viewportRight + 1;

      if (isFullyVisible) {
        if (!firstFullyVisibleItem) {
          firstFullyVisibleItem = child as HTMLElement;
        }
        fullyVisibleCount++;
      }
    }

    // Use fully visible items width if available
    if (fullyVisibleCount >= 1 && firstFullyVisibleItem) {
      const nextSibling =
        firstFullyVisibleItem.nextElementSibling as HTMLElement;
      if (nextSibling) {
        const firstRect = firstFullyVisibleItem.getBoundingClientRect();
        const nextRect = nextSibling.getBoundingClientRect();
        const itemWidthWithGap = nextRect.left - firstRect.left;
        return itemWidthWithGap * fullyVisibleCount;
      }
      return firstFullyVisibleItem.getBoundingClientRect().width;
    }

    // Fallback: use viewport width
    return this.containerWidth;
  }

  /**
   * Set transform position with bounds checking
   */
  private setTranslateX(value: number): void {
    const maxTranslate = Math.min(0, this.containerWidth - this.contentWidth);
    const boundedValue = Math.max(maxTranslate, Math.min(0, value));

    this.currentTranslateX.set(boundedValue);
    this.updateNavigationState();
  }

  /**
   * Navigate to the previous set of items
   */
  navigateToPrevious(event?: Event): void {
    if (this.disabled()) return;
    event?.preventDefault();
    const amount = this.calculateNavigationAmount();
    this.setTranslateX(this.currentTranslateX() + amount);
  }

  /**
   * Navigate to the next set of items
   */
  navigateToNext(event?: Event): void {
    if (this.disabled()) return;
    event?.preventDefault();
    const amount = this.calculateNavigationAmount();
    this.setTranslateX(this.currentTranslateX() - amount);
  }

  /**
   * Navigate to start
   */
  navigateToStart(): void {
    if (this.disabled()) return;
    this.setTranslateX(0);
  }

  // Touch event handlers
  private onTouchStart = (e: TouchEvent): void => {
    if (this.disabled()) return;
    this.isDragging.set(true);
    this.dragStartX = e.touches[0].clientX;
    this.dragStartTranslateX = this.currentTranslateX();
  };

  private onTouchMove = (e: TouchEvent): void => {
    if (this.disabled() || !this.isDragging()) return;

    e.preventDefault();
    const currentX = e.touches[0].clientX;
    const deltaX = currentX - this.dragStartX;

    // Direct DOM manipulation for smooth dragging (no signal updates)
    const maxTranslate = Math.min(0, this.containerWidth - this.contentWidth);
    const newValue = this.dragStartTranslateX + deltaX;
    const boundedValue = Math.max(maxTranslate, Math.min(0, newValue));

    const containerEl = this.container()?.nativeElement;
    if (containerEl) {
      containerEl.style.transform = `translate3d(${boundedValue}px, 0, 0)`;
    }
  };

  private onTouchEnd = (): void => {
    if (this.disabled()) return;
    this.isDragging.set(false);

    // Update signal with final position
    const containerEl = this.container()?.nativeElement;
    if (containerEl) {
      const transform = containerEl.style.transform;
      const match = transform.match(/translate3d\(([-\d.]+)px/);
      if (match) {
        this.currentTranslateX.set(parseFloat(match[1]));
        this.updateNavigationState();
      }
    }
  };

  // Mouse event handlers (for desktop drag)
  private onMouseDown = (e: MouseEvent): void => {
    if (this.disabled()) return;
    // Prevent dragging on buttons
    if ((e.target as HTMLElement).closest('button')) return;

    this.isDragging.set(true);
    this.dragStartX = e.clientX;
    this.dragStartTranslateX = this.currentTranslateX();
  };

  private onMouseMove = (e: MouseEvent): void => {
    if (!this.isDragging()) return;

    const currentX = e.clientX;
    const deltaX = currentX - this.dragStartX;

    // Direct DOM manipulation for smooth dragging (no signal updates)
    const maxTranslate = Math.min(0, this.containerWidth - this.contentWidth);
    const newValue = this.dragStartTranslateX + deltaX;
    const boundedValue = Math.max(maxTranslate, Math.min(0, newValue));

    const containerEl = this.container()?.nativeElement;
    if (containerEl) {
      containerEl.style.transform = `translate3d(${boundedValue}px, 0, 0)`;
    }
  };

  private onMouseUp = (): void => {
    if (this.isDragging()) {
      this.isDragging.set(false);

      // Update signal with final position
      const containerEl = this.container()?.nativeElement;
      if (containerEl) {
        const transform = containerEl.style.transform;
        const match = transform.match(/translate3d\(([-\d.]+)px/);
        if (match) {
          this.currentTranslateX.set(parseFloat(match[1]));
          this.updateNavigationState();
        }
      }
    }
  };

  private onMouseLeave = (): void => {
    if (this.isDragging()) {
      this.isDragging.set(false);

      // Update signal with final position
      const containerEl = this.container()?.nativeElement;
      if (containerEl) {
        const transform = containerEl.style.transform;
        const match = transform.match(/translate3d\(([-\d.]+)px/);
        if (match) {
          this.currentTranslateX.set(parseFloat(match[1]));
          this.updateNavigationState();
        }
      }
    }
  };
}
