import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ViewEncapsulation, ElementRef, ViewChild, AfterViewInit, OnDestroy, TemplateRef } from '@angular/core';
import { CommonModule } from '@angular/common';

type InfiniteScrollDirection = 'down' | 'up' | 'both';
type InfiniteScrollThreshold = number;

export interface InfiniteScrollEvent {
  direction: 'up' | 'down';
  currentIndex: number;
  scrollPosition: number;
}

export interface InfiniteScrollConfig {
  threshold?: number; // Distance from edge to trigger load (in pixels)
  debounceTime?: number; // Debounce time for scroll events (in ms)
  disabled?: boolean; // Disable infinite scrolling
  initialItemCount?: number; // Number of items to load initially
  itemHeight?: number; // Fixed height for virtual scrolling optimization
}

@Component({
  selector: 'ui-infinite-scroll-container',
  standalone: true,
  imports: [CommonModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  template: `
    <div 
      #scrollContainer
      class="ui-infinite-scroll-container"
      [class.ui-infinite-scroll-container--direction-down]="direction === 'down'"
      [class.ui-infinite-scroll-container--direction-up]="direction === 'up'"
      [class.ui-infinite-scroll-container--direction-both]="direction === 'both'"
      [class.ui-infinite-scroll-container--loading]="loading"
      [class.ui-infinite-scroll-container--disabled]="config?.disabled"
      [class.ui-infinite-scroll-container--has-fixed-height]="config?.itemHeight"
      [attr.role]="role"
      [attr.aria-label]="ariaLabel"
      [attr.aria-busy]="loading"
      [tabindex]="tabIndex"
      (scroll)="onScroll($event)"
      (keydown)="onKeyDown($event)">
      
      @if (direction === 'up' || direction === 'both') {
        <!-- Top loading indicator -->
        <div class="ui-infinite-scroll-container__loader ui-infinite-scroll-container__loader--top">
          @if (loadingUp && loadingTemplate) {
            <ng-container [ngTemplateOutlet]="loadingTemplate"></ng-container>
          } @else if (loadingUp) {
            <div class="ui-infinite-scroll-container__default-loader">
              <span class="ui-infinite-scroll-container__spinner" aria-hidden="true"></span>
              <span>Loading more items...</span>
            </div>
          }
        </div>
      }
      
      <!-- Main content area -->
      <div class="ui-infinite-scroll-container__content">
        @if (items && items.length > 0 && itemTemplate) {
          <!-- Template-based rendering -->
          @for (item of items; track trackByFn ? trackByFn($index, item) : item; let i = $index) {
            <div 
              class="ui-infinite-scroll-container__item"
              [style.height.px]="config?.itemHeight"
              [attr.data-index]="i">
              <ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ 
                $implicit: item, 
                item: item, 
                index: i,
                isFirst: i === 0,
                isLast: i === items.length - 1
              }"></ng-container>
            </div>
          }
        } @else {
          <!-- Content projection -->
          <ng-content></ng-content>
        }
      </div>
      
      @if (direction === 'down' || direction === 'both') {
        <!-- Bottom loading indicator -->
        <div class="ui-infinite-scroll-container__loader ui-infinite-scroll-container__loader--bottom">
          @if (loadingDown && loadingTemplate) {
            <ng-container [ngTemplateOutlet]="loadingTemplate"></ng-container>
          } @else if (loadingDown) {
            <div class="ui-infinite-scroll-container__default-loader">
              <span class="ui-infinite-scroll-container__spinner" aria-hidden="true"></span>
              <span>Loading more items...</span>
            </div>
          }
        </div>
      }
      
      @if (hasMore === false && endTemplate) {
        <!-- End of content indicator -->
        <div class="ui-infinite-scroll-container__end">
          <ng-container [ngTemplateOutlet]="endTemplate"></ng-container>
        </div>
      } @else if (hasMore === false) {
        <div class="ui-infinite-scroll-container__end">
          <div class="ui-infinite-scroll-container__default-end">
            No more items to load
          </div>
        </div>
      }
      
      @if (error && errorTemplate) {
        <!-- Error state -->
        <div class="ui-infinite-scroll-container__error">
          <ng-container [ngTemplateOutlet]="errorTemplate" [ngTemplateOutletContext]="{ error: error }"></ng-container>
        </div>
      } @else if (error) {
        <div class="ui-infinite-scroll-container__error">
          <div class="ui-infinite-scroll-container__default-error">
            <span>Failed to load more items</span>
            <button 
              type="button" 
              class="ui-infinite-scroll-container__retry-button"
              (click)="retry()"
              [attr.aria-label]="'Retry loading more items'">
              Retry
            </button>
          </div>
        </div>
      }
    </div>
  `,
  styleUrl: './infinite-scroll-container.component.scss'
})
export class InfiniteScrollContainerComponent implements AfterViewInit, OnDestroy {
  @ViewChild('scrollContainer', { static: true }) scrollContainer!: ElementRef<HTMLDivElement>;

  // Basic Configuration
  @Input() direction: InfiniteScrollDirection = 'down';
  @Input() config?: InfiniteScrollConfig;
  @Input() role = 'region';
  @Input() ariaLabel = 'Infinite scroll content';
  @Input() tabIndex = 0;

  // Data Management
  @Input() items?: any[];
  @Input() itemTemplate?: TemplateRef<any>;
  @Input() trackByFn?: (index: number, item: any) => any;
  @Input() hasMore = true;

  // State Management
  @Input() loading = false;
  @Input() loadingUp = false;
  @Input() loadingDown = false;
  @Input() error?: string | Error;

  // Templates
  @Input() loadingTemplate?: TemplateRef<any>;
  @Input() errorTemplate?: TemplateRef<any>;
  @Input() endTemplate?: TemplateRef<any>;

  // Events
  @Output() scrolledToEnd = new EventEmitter<InfiniteScrollEvent>();
  @Output() scrolledToTop = new EventEmitter<InfiniteScrollEvent>();
  @Output() loadMore = new EventEmitter<InfiniteScrollEvent>();
  @Output() retryRequested = new EventEmitter<void>();
  @Output() scrolled = new EventEmitter<{ scrollTop: number; scrollHeight: number; clientHeight: number }>();

  // Internal State
  private scrollTimeout?: number;
  private isScrolling = false;
  private lastScrollTop = 0;
  private currentIndex = 0;

  private get threshold(): number {
    return this.config?.threshold ?? 200;
  }

  private get debounceTime(): number {
    return this.config?.debounceTime ?? 100;
  }

  private get isDisabled(): boolean {
    return this.config?.disabled ?? false;
  }

  ngAfterViewInit(): void {
    // Initial setup
    this.updateCurrentIndex();
  }

  ngOnDestroy(): void {
    if (this.scrollTimeout) {
      clearTimeout(this.scrollTimeout);
    }
  }

  onScroll(event: Event): void {
    if (this.isDisabled) return;

    const element = event.target as HTMLElement;
    const scrollTop = element.scrollTop;
    const scrollHeight = element.scrollHeight;
    const clientHeight = element.clientHeight;

    // Emit scroll position
    this.scrolled.emit({ scrollTop, scrollHeight, clientHeight });

    // Debounced scroll handling
    if (this.scrollTimeout) {
      clearTimeout(this.scrollTimeout);
    }

    this.scrollTimeout = window.setTimeout(() => {
      this.handleScroll(element, scrollTop, scrollHeight, clientHeight);
    }, this.debounceTime);

    this.lastScrollTop = scrollTop;
  }

  onKeyDown(event: KeyboardEvent): void {
    if (this.isDisabled) return;

    const element = this.scrollContainer.nativeElement;
    let handled = false;

    switch (event.key) {
      case 'ArrowDown':
        this.scrollBy(40);
        handled = true;
        break;
      case 'ArrowUp':
        this.scrollBy(-40);
        handled = true;
        break;
      case 'PageDown':
        this.scrollBy(element.clientHeight * 0.8);
        handled = true;
        break;
      case 'PageUp':
        this.scrollBy(-element.clientHeight * 0.8);
        handled = true;
        break;
      case 'Home':
        this.scrollToTop();
        handled = true;
        break;
      case 'End':
        this.scrollToBottom();
        handled = true;
        break;
    }

    if (handled) {
      event.preventDefault();
    }
  }

  // Public API Methods
  scrollBy(deltaY: number): void {
    const element = this.scrollContainer.nativeElement;
    element.scrollBy({
      top: deltaY,
      behavior: 'smooth'
    });
  }

  scrollToTop(): void {
    const element = this.scrollContainer.nativeElement;
    element.scrollTo({ top: 0, behavior: 'smooth' });
  }

  scrollToBottom(): void {
    const element = this.scrollContainer.nativeElement;
    element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' });
  }

  scrollToIndex(index: number): void {
    if (!this.config?.itemHeight || !this.items) return;

    const targetY = index * this.config.itemHeight;
    const element = this.scrollContainer.nativeElement;
    element.scrollTo({ top: targetY, behavior: 'smooth' });
  }

  retry(): void {
    this.retryRequested.emit();
  }

  resetScroll(): void {
    const element = this.scrollContainer.nativeElement;
    element.scrollTo({ top: 0 });
    this.currentIndex = 0;
  }

  // Private Methods
  private handleScroll(
    element: HTMLElement, 
    scrollTop: number, 
    scrollHeight: number, 
    clientHeight: number
  ): void {
    this.updateCurrentIndex();

    const scrollDirection = scrollTop > this.lastScrollTop ? 'down' : 'up';
    
    // Check for infinite scroll triggers
    if (this.shouldLoadMore('down', element)) {
      const event: InfiniteScrollEvent = {
        direction: 'down',
        currentIndex: this.currentIndex,
        scrollPosition: scrollTop
      };

      this.scrolledToEnd.emit(event);
      this.loadMore.emit(event);
    }

    if (this.shouldLoadMore('up', element)) {
      const event: InfiniteScrollEvent = {
        direction: 'up', 
        currentIndex: this.currentIndex,
        scrollPosition: scrollTop
      };

      this.scrolledToTop.emit(event);
      this.loadMore.emit(event);
    }
  }

  private shouldLoadMore(direction: 'up' | 'down', element: HTMLElement): boolean {
    if (this.isDisabled || this.loading) return false;

    if (direction === 'down') {
      if (this.direction !== 'down' && this.direction !== 'both') return false;
      if (!this.hasMore || this.loadingDown) return false;

      const scrollBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
      return scrollBottom <= this.threshold;
    } else {
      if (this.direction !== 'up' && this.direction !== 'both') return false;
      if (this.loadingUp) return false;

      return element.scrollTop <= this.threshold;
    }
  }

  private updateCurrentIndex(): void {
    if (!this.config?.itemHeight || !this.items) return;

    const element = this.scrollContainer.nativeElement;
    const scrollTop = element.scrollTop;
    this.currentIndex = Math.floor(scrollTop / this.config.itemHeight);
  }
}