import { useEffect, useLayoutEffect, useRef, useCallback } from 'react';
import * as echarts from 'echarts';

type UseEChartsOpts = {
  option: echarts.EChartsCoreOption;
  theme?: string;
  renderer?: 'canvas' | 'svg';
  onEvents?: Record<string, (params: any, chart: echarts.ECharts) => void>;
  shouldUpdate?: (
    prev: echarts.EChartsCoreOption,
    next: echarts.EChartsCoreOption,
  ) => boolean;
};

function hasSize(el: HTMLElement): boolean {
  return el.clientWidth > 0 && el.clientHeight > 0;
}

export function useECharts<T extends HTMLDivElement>({
  option,
  theme,
  renderer = 'canvas',
  onEvents,
  shouldUpdate,
}: UseEChartsOpts) {
  const domRef = useRef<T | null>(null);
  const chartRef = useRef<echarts.ECharts | null>(null);
  const lastOptionRef = useRef<echarts.EChartsCoreOption | null>(null);
  const roRef = useRef<ResizeObserver | null>(null);
  const pendingOptionRef = useRef<echarts.EChartsCoreOption | null>(null);
  const mountedRef = useRef(true);

  const applyOption = useCallback(
    (chart: echarts.ECharts, opt: echarts.EChartsCoreOption) => {
      const need =
        !lastOptionRef.current ||
        (shouldUpdate ? shouldUpdate(lastOptionRef.current, opt) : true);
      if (need) {
        chart.setOption(opt, { notMerge: false, lazyUpdate: true });
        lastOptionRef.current = opt;
      }
    },
    [shouldUpdate],
  );

  const initChart = useCallback(() => {
    if (!domRef.current) return;
    if (!hasSize(domRef.current)) return;

    const existing = echarts.getInstanceByDom(domRef.current as any);
    if (existing) {
      chartRef.current = existing;
      if (pendingOptionRef.current) {
        applyOption(existing, pendingOptionRef.current);
        pendingOptionRef.current = null;
      }
      return;
    }

    const chart = echarts.init(domRef.current, theme, { renderer });
    chartRef.current = chart;

    if (onEvents) {
      Object.entries(onEvents).forEach(([evt, handler]) => {
        chart.on(evt as any, (p: any) => handler(p, chart));
      });
    }

    if (pendingOptionRef.current) {
      applyOption(chart, pendingOptionRef.current);
      pendingOptionRef.current = null;
    }
  }, [theme, renderer, onEvents, applyOption]);

  useLayoutEffect(() => {
    mountedRef.current = true;
    if (chartRef.current) {
      chartRef.current.dispose();
      chartRef.current = null;
    }

    initChart();

    if (!chartRef.current) {
      const timers = [
        window.setTimeout(() => {
          if (mountedRef.current) initChart();
        }, 50),
        window.setTimeout(() => {
          if (mountedRef.current) initChart();
        }, 200),
        window.setTimeout(() => {
          if (mountedRef.current) initChart();
        }, 500),
      ];
      return () => {
        mountedRef.current = false;
        timers.forEach(clearTimeout);
        if (chartRef.current) {
          chartRef.current.dispose();
          chartRef.current = null;
        }
      };
    }

    return () => {
      mountedRef.current = false;
      if (chartRef.current) {
        chartRef.current.dispose();
        chartRef.current = null;
      }
    };
  }, [initChart]);

  useEffect(() => {
    if (!domRef.current) return;

    roRef.current = new ResizeObserver(() => {
      requestAnimationFrame(() => {
        if (!chartRef.current) {
          initChart();
        } else {
          chartRef.current.resize();
        }
      });
    });
    roRef.current.observe(domRef.current);

    return () => {
      roRef.current?.disconnect();
      roRef.current = null;
    };
  }, [initChart]);

  useEffect(() => {
    if (!chartRef.current) {
      pendingOptionRef.current = option;
      return;
    }
    applyOption(chartRef.current, option);
  }, [option, applyOption]);

  return { domRef, chartRef };
}
