import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';
import ResizeObserver from 'resize-observer-polyfill';
import { capitalize, h } from 'vue';
import { Component, Inreactive, Prop, VueComponentBase, Watch } from 'vue3-component-base';

// https://echarts.apache.org/zh/api.html#events
const Events = [
  'click',
  'dblclick',
  'mousedown',
  'mouseup',
  'mouseover',
  'mouseout',
  'globalout',

  'legendselectchanged',
  'legendselected',
  'legendunselected',
  'legendscroll',
  'datazoom',
  'datarangeselected',
  'timelinechanged',
  'timelineplaychanged',
  'restore',
  'dataviewchanged',
  'magictypechanged',
  'geoselectchanged',
  'geoselected',
  'geounselected',
  'pieselectchanged',
  'pieselected',
  'pieunselected',
  'mapselectchanged',
  'mapselected',
  'mapunselected',
  'axisareaselected',
  'focusnodeadjacency',
  'unfocusnodeadjacency',
  'brush',
  'brushselected',
];

type EchartsInstance = ReturnType<typeof echarts.init>;

@Component({
  name: 'VueEcharts',
  emits: Events,
})
export class VueEcharts extends VueComponentBase {
  @Prop() readonly option: EChartsOption;
  @Prop({ default: 'default' }) readonly theme: string;
  @Prop() readonly groupId: string;
  @Prop({
    default: () => ({
      text: '努力加载中',
      color: '#c23531',
      textColor: '#489CFF',
      spinnerRadius: 6,
      lineWidth: 2,
      maskColor: 'rgba(0, 0, 0, 0.1)',
      zlevel: 0,
    }),
  }) readonly loadingOption: Record<string, any>;
  @Prop() readonly initCfg: Parameters<typeof echarts.init>[2];

  @Inreactive resizing: boolean;
  @Inreactive chart: EchartsInstance;

  $el: HTMLDivElement & { _component: VueEcharts };

  static ro: ResizeObserver;

  render() {
    return h('div', { class: 'vue-echarts' });
  }

  mounted() {
    this.refreshChart();
    this.$el._component = this;
    if (!VueEcharts.ro) {
      VueEcharts.ro = new ResizeObserver(function(this: void, entries: ResizeObserverEntry[]) {
        entries.forEach(entry => {
          const that = (entry.target as HTMLDivElement & { _component: VueEcharts })._component;
          if (entry.contentRect.width && entry.contentRect.height && that.chart && !that.resizing) {
            that.resizing = true;
            requestAnimationFrame(() => {
              if (that.chart) that.chart.resize(entry.contentRect);
              that.resizing = false;
            });
          }
        });
      });
    }

    VueEcharts.ro.observe(this.$el);
  }

  beforeUnmount() {
    if (this.chart) {
      this.chart.dispose();
      this.chart = undefined;
    }
    VueEcharts.ro?.unobserve(this.$el);
  }

  @Watch('option')
  refreshOption() {
    if (!this.chart) return;
    if (this.option && Object.keys(this.option).some(x => /^[a-z]/.test(x))) {
      this.chart.setOption(this.option, true);
      if (this.$el.clientHeight) this.chart.resize();
      this.chart.hideLoading();
    } else {
      this.chart.showLoading('default', this.loadingOption);
    }
  }

  @Watch('theme')
  refreshChart() {
    if (this.chart) {
      this.chart.dispose();
      this.chart = undefined;
    }

    const chart = echarts.init(this.$el, this.theme, this.initCfg);
    chart.group = this.groupId;

    this.chart = chart;

    this.refreshOption();

    Events.forEach(x => {
      const eventName = 'on' + capitalize(x);
      if (typeof this.$.vnode.props[eventName] === 'function') {
        chart.on(x, this.$emit.bind(this, x));
      }
    });
  }

  setOption(...args: Parameters<EchartsInstance['setOption']>) {
    return this.chart.setOption(...args);
  }

  dispatchAction(...args: Parameters<EchartsInstance['dispatchAction']>) {
    return this.chart.dispatchAction(...args);
  }
}