class FlashListPerformanceTracker {
  private screenLoadTime: number;

  private itemsVisibleStartTime: number;

  private itemsVisibleDuration: number;

  private scrollEvents: {
    startTime: number;
    endTime: number;
    duration: number;
    itemsFetched: number;
    scrollDistance: number;
  }[] = [];

  private currentScrollEvent: unknown = null;

  private fetchStartTime: number;

  private fetchEvents: {
    startTime: number;
    endTime: number;
    duration: number;
    itemsCount: number;
  }[] = [];

  private persistentMetrics = {
    lastFetchTime: null as number | null,
    lastFetchTimestamp: 0,
    avgFetchTime: 0,
    fetchCount: 0,
  };

  private lastFetchItemCount = 0;

  private totalItemsDisplayed = 0;

  constructor() {
    this.reset();
  }

  reset(): void {
    // Timestamps
    this.screenLoadTime = Date.now();
    this.itemsVisibleStartTime = 0;
    // Calculated durations (in ms)
    this.itemsVisibleDuration = 0;

    this.scrollEvents = [];
    this.currentScrollEvent = null;
    this.fetchStartTime = 0;
    this.fetchEvents = [];

    this.lastFetchItemCount = 0;
    this.totalItemsDisplayed = 0;
  }

  markItemsVisible(): void {
    if ( this.itemsVisibleStartTime === 0 ) {
      this.itemsVisibleStartTime = Date.now();
      this.itemsVisibleDuration = this.itemsVisibleStartTime - this.screenLoadTime;
    }
  }

  beginScrollEvent( y: number ): void {
    // Only one scroll event can be tracked at a time
    if ( !this.currentScrollEvent ) {
      this.currentScrollEvent = {
        startTime: Date.now(),
        endTime: 0,
        duration: 0,
        itemsFetched: 0,
        startY: y,
        endY: y,
        scrollDistance: 0,
      };
    }
  }

  endScrollEvent( y: number ): void {
    if ( this.currentScrollEvent ) {
      const now = Date.now();
      this.currentScrollEvent.endTime = now;
      this.currentScrollEvent.duration = now - this.currentScrollEvent.startTime;
      this.currentScrollEvent.endY = y;
      this.currentScrollEvent.scrollDistance = Math.abs( y - this.currentScrollEvent.startY );

      this.scrollEvents.push( this.currentScrollEvent );
      this.currentScrollEvent = null;
    }
  }

  beginDataFetch(): void {
    this.fetchStartTime = Date.now();
  }

  endDataFetch( itemsCount: number ): void {
    if ( this.fetchStartTime > 0 ) {
      const endTime = Date.now();
      const fetchDuration = Date.now() - this.fetchStartTime;
      this.fetchEvents.push( {
        startTime: this.fetchStartTime,
        endTime,
        duration: fetchDuration,
        itemsCount,
      } );

      this.persistentMetrics.lastFetchTime = fetchDuration;
      this.persistentMetrics.lastFetchTimestamp = endTime;

      this.persistentMetrics.fetchCount += 1;
      this.persistentMetrics.avgFetchTime
        = ( ( this.persistentMetrics.avgFetchTime
          * ( this.persistentMetrics.fetchCount - 1 ) ) + fetchDuration )
        / this.persistentMetrics.fetchCount;

      this.lastFetchItemCount = itemsCount;

      this.totalItemsDisplayed += itemsCount;
    }
  }

  getLastScrollMetrics() {
    if ( this.scrollEvents.length === 0 ) {
      return null;
    }
    return this.scrollEvents[this.scrollEvents.length - 1];
  }

  getAverageFetchTime() {
    const currentSessionAvg = this.fetchEvents.length > 0
      ? Math.round( this.fetchEvents.reduce( ( sum, event ) => sum + event.duration, 0 )
      / this.fetchEvents.length )
      : 0;

    return Math.max( currentSessionAvg, Math.round( this.persistentMetrics.avgFetchTime ) );
  }

  getLastFetchTime(): number | null {
    if ( this.fetchEvents.length > 0 ) {
      return this.fetchEvents[this.fetchEvents.length - 1].duration;
    }

    return this.persistentMetrics.lastFetchTime;
  }

  getSummary(): {
    listReadyTime: number;
    itemsVisibleTime: number;
    scrollEvents: number;
    avgScrollDuration: number;
    avgFetchTime: number;
    lastFetchTime: number | null;
    } {
    const avg = this.scrollEvents.length > 0
      ? this.scrollEvents.reduce( ( sum, event ) => sum + event.duration, 0 )
        / this.scrollEvents.length
      : 0;

    return {
      itemsVisibleTime: this.itemsVisibleDuration,
      scrollEvents: this.scrollEvents.length,
      avgScrollDuration: Math.round( avg ),
      avgFetchTime: this.getAverageFetchTime(),
      lastFetchTime: this.getLastFetchTime(),
      totalFetches: this.fetchEvents.length
        + this.persistentMetrics.fetchCount - this.fetchEvents.length,
      lastFetchItemCount: this.lastFetchItemCount,
      totalItemsDisplayed: this.totalItemsDisplayed,
    };
  }
}

const flashListTracker = new FlashListPerformanceTracker();
export default flashListTracker;
