import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';

export interface FontLoadingState {
  fontName: string;
  status: 'idle' | 'loading' | 'loaded' | 'error' | 'timeout';
  progress: number; // 0-100
  error?: string;
  loadTime?: number;
  url?: string;
}

export interface GlobalLoadingState {
  isLoading: boolean;
  totalFonts: number;
  loadedFonts: number;
  failedFonts: number;
  progress: number; // 0-100
  currentlyLoading: string[];
  errors: { [fontName: string]: string };
}

@Injectable({
  providedIn: 'root'
})
export class FontLoadingStateService {
  private fontStates = new Map<string, BehaviorSubject<FontLoadingState>>();
  private globalState$ = new BehaviorSubject<GlobalLoadingState>({
    isLoading: false,
    totalFonts: 0,
    loadedFonts: 0,
    failedFonts: 0,
    progress: 0,
    currentlyLoading: [],
    errors: {}
  });

  /**
   * Get loading state for a specific font
   */
  getFontState(fontName: string): Observable<FontLoadingState> {
    if (!this.fontStates.has(fontName)) {
      this.fontStates.set(fontName, new BehaviorSubject<FontLoadingState>({
        fontName,
        status: 'idle',
        progress: 0
      }));
    }
    
    return this.fontStates.get(fontName)!.asObservable();
  }

  /**
   * Get global loading state
   */
  getGlobalState(): Observable<GlobalLoadingState> {
    return this.globalState$.asObservable();
  }

  /**
   * Get all font states
   */
  getAllFontStates(): Observable<FontLoadingState[]> {
    if (this.fontStates.size === 0) {
      return new BehaviorSubject<FontLoadingState[]>([]).asObservable();
    }

    const stateObservables = Array.from(this.fontStates.values());
    return combineLatest(stateObservables);
  }

  /**
   * Update font loading state
   */
  updateFontState(fontName: string, update: Partial<FontLoadingState>): void {
    const currentSubject = this.fontStates.get(fontName);
    if (!currentSubject) {
      this.fontStates.set(fontName, new BehaviorSubject<FontLoadingState>({
        fontName,
        status: 'idle',
        progress: 0,
        ...update
      }));
    } else {
      const current = currentSubject.value;
      currentSubject.next({ ...current, ...update });
    }

    this.updateGlobalState();
  }

  /**
   * Start font loading
   */
  startLoading(fontName: string, url?: string): void {
    this.updateFontState(fontName, {
      status: 'loading',
      progress: 0,
      url,
      error: undefined,
      loadTime: undefined
    });
  }

  /**
   * Update loading progress
   */
  updateProgress(fontName: string, progress: number): void {
    this.updateFontState(fontName, {
      progress: Math.max(0, Math.min(100, progress))
    });
  }

  /**
   * Mark font as loaded
   */
  markLoaded(fontName: string, loadTime: number): void {
    this.updateFontState(fontName, {
      status: 'loaded',
      progress: 100,
      loadTime
    });
  }

  /**
   * Mark font as failed
   */
  markFailed(fontName: string, error: string): void {
    this.updateFontState(fontName, {
      status: 'error',
      progress: 0,
      error
    });
  }

  /**
   * Mark font as timed out
   */
  markTimeout(fontName: string): void {
    this.updateFontState(fontName, {
      status: 'timeout',
      progress: 0,
      error: 'Font loading timed out'
    });
  }

  /**
   * Reset font state
   */
  resetFontState(fontName: string): void {
    this.updateFontState(fontName, {
      status: 'idle',
      progress: 0,
      error: undefined,
      loadTime: undefined
    });
  }

  /**
   * Reset all font states
   */
  resetAllStates(): void {
    this.fontStates.forEach((subject, fontName) => {
      subject.next({
        fontName,
        status: 'idle',
        progress: 0
      });
    });
    
    this.updateGlobalState();
  }

  /**
   * Remove font state
   */
  removeFontState(fontName: string): void {
    const subject = this.fontStates.get(fontName);
    if (subject) {
      subject.complete();
      this.fontStates.delete(fontName);
    }
    
    this.updateGlobalState();
  }

  /**
   * Get loading statistics
   */
  getLoadingStats(): Observable<{
    totalRequests: number;
    successRate: number;
    averageLoadTime: number;
    fastestLoad: number;
    slowestLoad: number;
  }> {
    return this.getAllFontStates().pipe(
      map(states => {
        const completedStates = states.filter(s => s.status === 'loaded' || s.status === 'error');
        const loadedStates = states.filter(s => s.status === 'loaded');
        const loadTimes = loadedStates
          .map(s => s.loadTime)
          .filter(time => time !== undefined) as number[];

        return {
          totalRequests: states.length,
          successRate: completedStates.length > 0 ? (loadedStates.length / completedStates.length) * 100 : 0,
          averageLoadTime: loadTimes.length > 0 ? loadTimes.reduce((a, b) => a + b, 0) / loadTimes.length : 0,
          fastestLoad: loadTimes.length > 0 ? Math.min(...loadTimes) : 0,
          slowestLoad: loadTimes.length > 0 ? Math.max(...loadTimes) : 0
        };
      })
    );
  }

  /**
   * Check if any fonts are currently loading
   */
  isAnyFontLoading(): Observable<boolean> {
    return this.getGlobalState().pipe(
      map(state => state.isLoading)
    );
  }

  /**
   * Get fonts by status
   */
  getFontsByStatus(status: FontLoadingState['status']): Observable<FontLoadingState[]> {
    return this.getAllFontStates().pipe(
      map(states => states.filter(state => state.status === status))
    );
  }

  /**
   * Wait for all fonts to finish loading (success or failure)
   */
  waitForAllFonts(): Observable<FontLoadingState[]> {
    return this.getAllFontStates().pipe(
      map(states => {
        const allFinished = states.every(state => 
          state.status === 'loaded' || 
          state.status === 'error' || 
          state.status === 'timeout' ||
          state.status === 'idle'
        );
        
        if (allFinished) {
          return states;
        } else {
          throw new Error('Fonts still loading'); // This will cause the observable to not emit yet
        }
      })
    );
  }

  private updateGlobalState(): void {
    const states = Array.from(this.fontStates.values()).map(subject => subject.value);
    
    const totalFonts = states.length;
    const loadingFonts = states.filter(s => s.status === 'loading');
    const loadedFonts = states.filter(s => s.status === 'loaded');
    const failedFonts = states.filter(s => s.status === 'error' || s.status === 'timeout');
    const currentlyLoading = loadingFonts.map(s => s.fontName);
    
    const errors: { [fontName: string]: string } = {};
    states.forEach(state => {
      if (state.error) {
        errors[state.fontName] = state.error;
      }
    });

    const completedFonts = loadedFonts.length + failedFonts.length;
    const progress = totalFonts > 0 ? (completedFonts / totalFonts) * 100 : 0;

    this.globalState$.next({
      isLoading: loadingFonts.length > 0,
      totalFonts,
      loadedFonts: loadedFonts.length,
      failedFonts: failedFonts.length,
      progress,
      currentlyLoading,
      errors
    });
  }
}