/*
 * 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';
import {MaterialSymbol} from 'material-symbols';

// 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: MaterialSymbol;

  @Input() md: string;

  @Input() ios: string;

  @Input() fill: boolean;

  @Input() weight: number;

  @Input() size: number | 'small' | 'large';

  @Input() grade: number;

  @Input() style: string;

  private mutationObserver: MutationObserver;

  iconComponent?: ComponentRef<IconComponent>;

  private 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;

    this.iconComponent.setInput('fill', this.fill === true ? 1 : this.fill === false ? 0 : this.fill);
    this.iconComponent.setInput('weight', this.weight);
    this.iconComponent.setInput('grade', this.grade);

    if (this.mode === 'ios') {
      this.iconComponent.setInput('name', this.ios || this.name);
    } else {
      this.iconComponent.setInput('name', this.md || this.name);
    }

    if (this.size) {
      const size = this.size === 'small' ? 16 : this.size === 'large' ? 24 : this.size;
      this.element.nativeElement.style.fontSize = `${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,
    );
  }
}
