import { Injectable, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { BehaviorSubject, Observable, from, of, forkJoin } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { FontDefinition, FontTheme, FontManagerConfig, FontLoadingOptions } from './types/font-types';
import { GOOGLE_FONTS_PRESET, FONT_COMBINATIONS } from './presets/google-fonts-preset';

@Injectable({
  providedIn: 'root'
})
export class UiFontManagerService {
  private loadedFonts = new Set<string>();
  private activeTheme$ = new BehaviorSubject<string>('default');
  private customPresets = new Map<string, FontDefinition>();
  
  private config: FontManagerConfig = {
    defaultTheme: 'default',
    preloadFonts: false,
    fallbackTimeout: 3000,
    cacheEnabled: true
  };

  constructor(@Inject(DOCUMENT) private document: Document) {}

  /**
   * Configure the font manager
   */
  configure(config: Partial<FontManagerConfig>): void {
    this.config = { ...this.config, ...config };
  }

  /**
   * Get all available Google Fonts presets
   */
  getGoogleFontsPresets(): { [key: string]: FontDefinition } {
    return GOOGLE_FONTS_PRESET;
  }

  /**
   * Get popular font combinations
   */
  getFontCombinations(): typeof FONT_COMBINATIONS {
    return FONT_COMBINATIONS;
  }

  /**
   * Add a custom font preset
   */
  addCustomFont(name: string, definition: FontDefinition): void {
    this.customPresets.set(name, definition);
  }

  /**
   * Get all available fonts (Google Fonts + Custom)
   */
  getAllFonts(): { [key: string]: FontDefinition } {
    return {
      ...GOOGLE_FONTS_PRESET,
      ...Object.fromEntries(this.customPresets)
    };
  }

  /**
   * Load a single font dynamically
   */
  loadFont(fontName: string, options?: FontLoadingOptions): Observable<boolean> {
    const font = this.getAllFonts()[fontName];
    if (!font) {
      console.warn(`Font "${fontName}" not found in presets`);
      return of(false);
    }

    if (this.loadedFonts.has(fontName)) {
      return of(true);
    }

    return this.loadFontFromDefinition(font, options).pipe(
      map(() => {
        this.loadedFonts.add(fontName);
        return true;
      }),
      catchError(error => {
        console.error(`Failed to load font "${fontName}":`, error);
        return of(false);
      })
    );
  }

  /**
   * Load multiple fonts
   */
  loadFonts(fontNames: string[], options?: FontLoadingOptions): Observable<boolean[]> {
    const loadObservables = fontNames.map(name => this.loadFont(name, options));
    return forkJoin(loadObservables);
  }

  /**
   * Apply a font theme
   */
  applyTheme(theme: FontTheme): Observable<boolean> {
    const fontsToLoad = Object.values(theme.fonts);
    const fontNames = fontsToLoad.map(font => font.family);

    return this.loadFonts(fontNames).pipe(
      map(results => {
        if (results.every(success => success)) {
          this.updateCSSVariables(theme);
          this.activeTheme$.next(theme.name);
          return true;
        }
        return false;
      })
    );
  }

  /**
   * Get current active theme
   */
  getActiveTheme(): Observable<string> {
    return this.activeTheme$.asObservable();
  }

  /**
   * Create a theme from font combination
   */
  createThemeFromCombination(name: string, combinationName: string): FontTheme | null {
    const combination = FONT_COMBINATIONS[combinationName as keyof typeof FONT_COMBINATIONS];
    if (!combination) return null;

    const allFonts = this.getAllFonts();
    
    return {
      name,
      fonts: {
        sans: allFonts[combination.sans],
        serif: allFonts[combination.serif],
        mono: allFonts[combination.mono],
        display: allFonts[combination.display]
      }
    };
  }

  /**
   * Check if font is loaded
   */
  isFontLoaded(fontName: string): boolean {
    return this.loadedFonts.has(fontName);
  }

  /**
   * Preload fonts for performance
   */
  preloadFonts(fontNames: string[]): void {
    if (!this.config.preloadFonts) return;

    fontNames.forEach(fontName => {
      const font = this.getAllFonts()[fontName];
      if (font?.url) {
        const link = this.document.createElement('link');
        link.rel = 'preload';
        link.href = font.url;
        link.as = 'style';
        this.document.head.appendChild(link);
      }
    });
  }

  private loadFontFromDefinition(font: FontDefinition, options?: FontLoadingOptions): Observable<void> {
    return new Observable(observer => {
      if (!font.url) {
        // Assume system font or already available
        observer.next();
        observer.complete();
        return;
      }

      // Check if already loaded
      const existingLink = this.document.querySelector(`link[href="${font.url}"]`);
      if (existingLink) {
        observer.next();
        observer.complete();
        return;
      }

      // Create and load font
      const link = this.document.createElement('link');
      link.rel = 'stylesheet';
      link.href = this.buildFontURL(font, options);
      
      link.onload = () => {
        observer.next();
        observer.complete();
      };

      link.onerror = (error) => {
        observer.error(error);
      };

      this.document.head.appendChild(link);

      // Fallback timeout
      setTimeout(() => {
        if (!observer.closed) {
          observer.error(new Error('Font loading timeout'));
        }
      }, this.config.fallbackTimeout);
    });
  }

  private buildFontURL(font: FontDefinition, options?: FontLoadingOptions): string {
    if (!font.url) return '';
    
    let url = font.url;
    
    // Add display parameter for Google Fonts
    if (url.includes('fonts.googleapis.com') && options?.display) {
      const separator = url.includes('?') ? '&' : '?';
      url += `${separator}display=${options.display}`;
    }
    
    return url;
  }

  private updateCSSVariables(theme: FontTheme): void {
    const root = this.document.documentElement;
    
    // Update CSS custom properties
    root.style.setProperty('--font-family-sans', this.buildFontStack(theme.fonts.sans));
    root.style.setProperty('--font-family-serif', this.buildFontStack(theme.fonts.serif));
    root.style.setProperty('--font-family-mono', this.buildFontStack(theme.fonts.mono));
    root.style.setProperty('--font-family-display', this.buildFontStack(theme.fonts.display));
  }

  private buildFontStack(font: FontDefinition): string {
    return [font.family, ...font.fallbacks].map(f => f.includes(' ') ? `"${f}"` : f).join(', ');
  }
}
