import {
  Directive,
  ElementRef,
  EventEmitter,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  Output,
  SimpleChanges,
} from '@angular/core';
import { EChartsOption } from 'echarts';
import { filter, interval, map, Observable, Subscription } from 'rxjs';
import * as echarts from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import waldenTheme from './walden.theme.json';
import westerosTheme from './westeros.theme.json';

const CUSTOM_THEME_TOKEN = 'customTheme';

@Directive({
  selector: '[dksEcharts]',
  standalone: false,
})
export class EchartsDirective implements OnInit, OnDestroy, OnChanges {
  @Input() options?: EChartsOption;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  @Input() extentions: any[] = [];
  @Input() isResizable = true;
  @Input() defaultWidth = 400;
  @Input() defaultHeight = 400;
  @Input() periodicityInMilliseconds = 2000;
  @Input() theme: Record<string, unknown> | string = '';

  @Output() chartInit = new EventEmitter<echarts.ECharts>();

  protected echartsInstance: echarts.ECharts | undefined;

  protected subscription: Subscription | undefined;

  constructor(protected readonly _el: ElementRef<HTMLElement>) {}

  ngOnInit(): void {
    echarts.use([...this.extentions, CanvasRenderer]);
    echarts.registerTheme(CUSTOM_THEME_TOKEN, westerosTheme.theme);
    this.echartsInstance = echarts.init(
      this._el.nativeElement,
      CUSTOM_THEME_TOKEN,
      {
        width:
          this._el.nativeElement.clientWidth === 0
            ? this.defaultWidth
            : undefined,
        height:
          this._el.nativeElement.clientHeight === 0
            ? this.defaultHeight
            : undefined,
      }
    );
    this.chartInit.emit(this.echartsInstance);
    this.setParams();
    if (this.isResizable) {
      this.addResizableFunctionality();
    }
  }

  ngOnDestroy(): void {
    if (this.subscription != null) this.subscription.unsubscribe();
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['options'] && !changes['options'].firstChange) {
      this.setParams();
    }

    if (changes['isResizable'] && !changes['isResizable'].firstChange) {
      if (this.isResizable) {
        this.addResizableFunctionality();
      } else {
        if (this.subscription != null) this.subscription.unsubscribe();
      }
    }
  }

  protected addResizableFunctionality() {
    if (this.subscription != null) this.subscription.unsubscribe();
    this.subscription = this.getWidthSensor(
      this._el.nativeElement,
      this.periodicityInMilliseconds
    ).subscribe(() => {
      if (this.echartsInstance != null) {
        this.echartsInstance.resize();
      }
    });
  }

  protected setParams() {
    if (this.echartsInstance != null && this.options != null) {
      this.echartsInstance.setOption(this.options, true);
    }
  }

  private getWidthSensor(
    element: HTMLElement,
    periodicityInMilliseconds: number = 1000
  ): Observable<number> {
    let oldWidth = element.clientWidth;
    return interval(periodicityInMilliseconds).pipe(
      filter(() => {
        if (Math.abs(oldWidth - element.clientWidth) > 2) {
          oldWidth = element.clientWidth;
          return true;
        }
        return false;
      }),
      map(() => element.clientWidth)
    );
  }
}
