import { Injectable, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { BehaviorSubject, Observable } from 'rxjs';

export interface FontTransitionConfig {
  duration: number; // in milliseconds
  easing: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | string;
  stagger: number; // delay between elements in milliseconds
  fadeEffect: boolean;
  scaleEffect: boolean;
}

@Injectable({
  providedIn: 'root'
})
export class FontTransitionService {
  private isTransitioning$ = new BehaviorSubject<boolean>(false);
  private transitionConfig: FontTransitionConfig = {
    duration: 300,
    easing: 'ease-in-out',
    stagger: 50,
    fadeEffect: true,
    scaleEffect: false
  };

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

  /**
   * Configure transition settings
   */
  configure(config: Partial<FontTransitionConfig>): void {
    this.transitionConfig = { ...this.transitionConfig, ...config };
  }

  /**
   * Get current transition state
   */
  isTransitioning(): Observable<boolean> {
    return this.isTransitioning$.asObservable();
  }

  /**
   * Apply smooth font transition with animations
   */
  applyFontTransition(
    newFontVariables: { [key: string]: string },
    customConfig?: Partial<FontTransitionConfig>
  ): Observable<void> {
    return new Observable(observer => {
      const config = customConfig ? { ...this.transitionConfig, ...customConfig } : this.transitionConfig;
      
      this.isTransitioning$.next(true);
      
      // Get all text elements
      const textElements = this.getTextElements();
      
      // Apply transition styles
      this.prepareTransition(textElements, config);
      
      // Start the transition
      requestAnimationFrame(() => {
        this.executeTransition(textElements, newFontVariables, config).then(() => {
          this.isTransitioning$.next(false);
          observer.next();
          observer.complete();
        }).catch(error => {
          this.isTransitioning$.next(false);
          observer.error(error);
        });
      });
    });
  }

  /**
   * Apply font changes with smooth fade transition
   */
  applyWithFade(newFontVariables: { [key: string]: string }): Observable<void> {
    return this.applyFontTransition(newFontVariables, {
      fadeEffect: true,
      scaleEffect: false,
      duration: 400,
      easing: 'ease-in-out'
    });
  }

  /**
   * Apply font changes with scale effect
   */
  applyWithScale(newFontVariables: { [key: string]: string }): Observable<void> {
    return this.applyFontTransition(newFontVariables, {
      fadeEffect: false,
      scaleEffect: true,
      duration: 500,
      easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
    });
  }

  /**
   * Apply instant font changes without animation
   */
  applyInstant(newFontVariables: { [key: string]: string }): void {
    const root = this.document.documentElement;
    Object.entries(newFontVariables).forEach(([property, value]) => {
      root.style.setProperty(property, value);
    });
  }

  private getTextElements(): Element[] {
    // Get all elements that likely contain text
    const selectors = [
      'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
      'p', 'span', 'div', 'a', 'button',
      'label', 'input', 'textarea', 'select',
      '.text', '[class*="text"]', '[class*="font"]'
    ];
    
    const elements: Element[] = [];
    selectors.forEach(selector => {
      const found = Array.from(this.document.querySelectorAll(selector));
      elements.push(...found);
    });
    
    // Remove duplicates and filter out elements without text content
    return Array.from(new Set(elements)).filter(el => {
      const text = el.textContent?.trim();
      return text && text.length > 0;
    });
  }

  private prepareTransition(elements: Element[], config: FontTransitionConfig): void {
    elements.forEach((element, index) => {
      const htmlElement = element as HTMLElement;
      
      // Store original styles
      const originalTransition = htmlElement.style.transition;
      htmlElement.setAttribute('data-original-transition', originalTransition);
      
      // Apply transition styles
      const transitions: string[] = [];
      
      if (config.fadeEffect) {
        transitions.push(`opacity ${config.duration}ms ${config.easing}`);
      }
      
      if (config.scaleEffect) {
        transitions.push(`transform ${config.duration}ms ${config.easing}`);
      }
      
      // Always transition font properties
      transitions.push(
        `font-family ${config.duration}ms ${config.easing}`,
        `font-weight ${config.duration}ms ${config.easing}`,
        `font-size ${config.duration}ms ${config.easing}`
      );
      
      htmlElement.style.transition = transitions.join(', ');
      
      // Add stagger delay
      if (config.stagger > 0) {
        htmlElement.style.transitionDelay = `${index * config.stagger}ms`;
      }
    });
  }

  private async executeTransition(
    elements: Element[],
    newFontVariables: { [key: string]: string },
    config: FontTransitionConfig
  ): Promise<void> {
    // Phase 1: Prepare for transition (fade out / scale down)
    if (config.fadeEffect || config.scaleEffect) {
      elements.forEach(element => {
        const htmlElement = element as HTMLElement;
        
        if (config.fadeEffect) {
          htmlElement.style.opacity = '0.3';
        }
        
        if (config.scaleEffect) {
          htmlElement.style.transform = 'scale(0.98)';
        }
      });
      
      // Wait for fade/scale animation
      await this.delay(config.duration / 2);
    }
    
    // Phase 2: Apply new font variables
    const root = this.document.documentElement;
    Object.entries(newFontVariables).forEach(([property, value]) => {
      root.style.setProperty(property, value);
    });
    
    // Small delay to ensure font variables are applied
    await this.delay(10);
    
    // Phase 3: Restore visibility (fade in / scale up)
    if (config.fadeEffect || config.scaleEffect) {
      elements.forEach(element => {
        const htmlElement = element as HTMLElement;
        
        if (config.fadeEffect) {
          htmlElement.style.opacity = '1';
        }
        
        if (config.scaleEffect) {
          htmlElement.style.transform = 'scale(1)';
        }
      });
    }
    
    // Phase 4: Wait for transition to complete and cleanup
    const maxDelay = elements.length * config.stagger;
    const totalDuration = config.duration + maxDelay + 50; // Add buffer
    
    await this.delay(totalDuration);
    
    // Cleanup: Restore original transition styles
    elements.forEach(element => {
      const htmlElement = element as HTMLElement;
      const originalTransition = htmlElement.getAttribute('data-original-transition') || '';
      
      htmlElement.style.transition = originalTransition;
      htmlElement.style.transitionDelay = '';
      htmlElement.style.opacity = '';
      htmlElement.style.transform = '';
      htmlElement.removeAttribute('data-original-transition');
    });
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  /**
   * Create a custom transition keyframe animation
   */
  createCustomTransition(keyframes: Keyframe[], options: KeyframeAnimationOptions): void {
    const textElements = this.getTextElements();
    
    textElements.forEach((element, index) => {
      const animation = element.animate(keyframes, {
        ...options,
        delay: (options.delay || 0) + (index * 25) // Stagger effect
      });
      
      animation.addEventListener('finish', () => {
        animation.cancel();
      });
    });
  }

  /**
   * Typewriter effect for font changes
   */
  applyTypewriterTransition(newFontVariables: { [key: string]: string }): Observable<void> {
    return new Observable(observer => {
      const textElements = this.getTextElements();
      let completedElements = 0;
      
      this.isTransitioning$.next(true);
      
      // Apply new fonts immediately but keep text invisible
      const root = this.document.documentElement;
      Object.entries(newFontVariables).forEach(([property, value]) => {
        root.style.setProperty(property, value);
      });
      
      textElements.forEach((element, index) => {
        const htmlElement = element as HTMLElement;
        const text = htmlElement.textContent || '';
        const originalText = text;
        
        // Clear text and start typewriter effect
        htmlElement.textContent = '';
        
        let charIndex = 0;
        const typeInterval = setInterval(() => {
          if (charIndex < originalText.length) {
            htmlElement.textContent = originalText.substring(0, charIndex + 1);
            charIndex++;
          } else {
            clearInterval(typeInterval);
            completedElements++;
            
            if (completedElements === textElements.length) {
              this.isTransitioning$.next(false);
              observer.next();
              observer.complete();
            }
          }
        }, 20 + (index * 5)); // Stagger the typing speed
      });
    });
  }
}