/*
 * Copyright (C) 2022 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */

import {
  ComponentRef,
  Directive,
  ElementRef,
  Host,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  Optional,
  Self,
  ViewContainerRef,
} from '@angular/core';
import {IconComponent} from './icon.component';
import {IonIcon} from '@ionic/angular';

// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};
const noopProperty = {
  set: noop,
  get: noop,
};

@Directive({
  selector: 'ion-icon',
})
export class IonIconDirective implements OnInit, OnDestroy, OnChanges {
  @Input() name: string;

  @Input() md: string;

  @Input() ios: string;

  @Input() fill = false;

  @Input() weight: number;

  @Input() size: number;

  @Input() grade: number;

  @Input() style: string;

  private mutationObserver: MutationObserver;

  iconComponent?: ComponentRef<IconComponent>;

  private static get mode(): 'md' | 'ios' {
    return document.querySelector(':root')?.getAttribute('mode') as 'md' | 'ios';
  }

  constructor(
    private element: ElementRef,
    private viewContainerRef: ViewContainerRef,
    @Host() @Self() @Optional() private ionIcon: IonIcon,
  ) {}

  ngOnInit() {
    this.iconComponent = this.viewContainerRef.createComponent(IconComponent, {});

    this.element.nativeElement.insertBefore(
      this.iconComponent.location.nativeElement,
      this.element.nativeElement.firstChild,
    );

    this.mutationObserver = new MutationObserver(() => {
      const inner = this.element.nativeElement.shadowRoot.querySelector('.icon-inner');
      if (!inner) return;

      inner.insertBefore(document.createElement('slot'), inner.firstChild);
    });
    this.mutationObserver.observe(this.element.nativeElement.shadowRoot, {
      childList: true,
    });

    this.ngOnChanges();
    // this will effectively completely disable the ion-icon component
    for (const name of ['src', 'name', 'icon', 'md', 'ios']) {
      this.disableProperty(name);
    }
  }

  ngOnDestroy() {
    this.mutationObserver.disconnect();
  }

  ngOnChanges() {
    if (!this.iconComponent) return;
    for (const key of ['name', 'weight', 'fill', 'size', 'grade'] as Array<
      keyof IconComponent & keyof IonIconDirective
    >) {
      // @ts-expect-error type mismatch
      this.iconComponent.instance[key] = this[key];
    }

    for (const mode of ['md', 'ios'] as Array<'md' | 'ios'>) {
      if (this[mode] && IonIconDirective.mode === mode) {
        this.iconComponent.instance.name = this[mode];
      }
    }

    if (this.size) {
      this.element.nativeElement.style.cssText = `font-size: ${this.size}px;`;
    }
    if (this.style) {
      this.element.nativeElement.style.cssText += this.style;
    }
  }

  disableProperty(name: string) {
    Object.defineProperty(
      Object.getPrototypeOf((this.ionIcon as unknown as {el: HTMLElement}).el),
      name,
      noopProperty,
    );
  }
}
