import { Injectable } from '@angular/core';
import { Icon, IconAlias, IconConfig } from './interfaces';
import { IconLoader } from './loader';
import { Observable, Subject, isObservable } from 'rxjs';

@Injectable()
export class IconRegistry {
  private _icons = new Map<string, Icon>();
  private _aliases = new Map<string, string>();
  private _fallback: string;
  private _viewBox: string;

  updated = new Subject<void>();

  private _initComplete = false;

  constructor(private _iconLoader: IconLoader) {
    this._loadIcons();
  }

  private async _loadIcons(): Promise<void> {
    const load = this._iconLoader.getIcons();

    if (load instanceof Promise) {
      const config = await load;
      this._init(config);
    } else if (isObservable(load)) {
      load.subscribe((config) => {
        this._init(config);
      });
    } else {
      this._init(load);
    }
  }

  private _init(config: IconConfig): void {
    this.register(...config.icons);
    this.alias(...config.aliases);
    this.setViewBox(config.viewBox);
    this.setFallback(config.fallback);

    this._initComplete = true;

    this.updated.next();
  }

  register(...icons: Icon[]): IconRegistry {
    icons?.forEach((icon) => {
      this._icons.set(icon.name, icon);
    });

    return this;
  }

  setViewBox(viewBox: string): void {
    this._viewBox = viewBox;
  }

  alias(...aliases: IconAlias[]): IconRegistry {
    aliases?.forEach((alias) => {
      this._aliases.set(alias.alias, alias.name);
    });

    return this;
  }

  setFallback(name: string): void {
    this._fallback = name;
  }

  get(name: string): Icon | undefined {
    const alias = this._aliases.get(name);
    let iconName = name;
    if (alias) {
      iconName = alias;
    }

    let icon = this._icons.get(iconName);

    if (!icon && this._initComplete) {
      if (alias) {
        console.warn(`Not found: Icon with name ${name} (${iconName})`);
      } else {
        console.warn(`Unable to find icon: '${name}'`);
      }
    }

    if (!icon && this._fallback) {
      icon = this._icons.get(this._fallback);
    }

    return { ...icon, viewBox: icon?.viewBox || this._viewBox };
  }

  get$(name: string): Observable<Icon | undefined> {
    return new Observable<Icon | undefined>((subscriber) => {
      let icon = this.get(name);
      subscriber.next(icon);
      subscriber.complete();

      const sub = this.updated.subscribe(() => {
        icon = this.get(name);
        subscriber.next(icon);
        subscriber.complete();
      });

      return () => sub.unsubscribe();
    });
  }
}
