import useCoolColors from '@/hooks/use-cool-colors';
import { Chart } from '@gpustack/core-ui/charts';
import { formatLargeNumber } from '@gpustack/core-ui/utils';
import { Empty, Spin, theme } from 'antd';
import _ from 'lodash';
import React, {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState
} from 'react';

export interface BarSeriesItem {
  name: string;
  data: any[];
  color?: string;
  stack?: string | false;
}

export interface BarChartProps {
  seriesData: BarSeriesItem[];
  xAxisData: string[];
  height: number | string;
  width?: number | string;
  loading?: boolean;
  legendData?: { name: string; icon?: string }[];
  labelFormatter?: (val: any, index?: number) => string;
  tooltipValueFormatter?: (val: any) => string;
  title?: string;
  grid?: {
    left?: number | string;
    right?: number | string;
    top?: number | string;
    bottom?: number | string;
    containLabel?: boolean;
  };
  legendIsolate?: boolean;
}

const BarChart: React.FC<BarChartProps> = (props) => {
  const {
    seriesData,
    xAxisData,
    height,
    width,
    legendData,
    loading,
    labelFormatter,
    tooltipValueFormatter,
    title,
    grid,
    legendIsolate
  } = props;
  const { token } = theme.useToken();
  const chartRef = useRef<{ chart: any } | null>(null);
  const generateCoolColors = useCoolColors();

  // ECharts reads the DOM width at init time; with a "100%" width it can pick
  // up a stale/tiny value while the flex child's layout is still resolving,
  // rendering every bar squeezed at the left edge until its internal (throttled)
  // ResizeObserver corrects it ~100ms later — a visible blue-sliver flash. We
  // measure the container ourselves via a callback ref (which fires during
  // commit, before paint) and feed ECharts an explicit pixel width, so the very
  // first render is already correct. No gating, so the chart mounts with no
  // extra delay; the ResizeObserver keeps the width in sync afterwards.
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
  const [measuredWidth, setMeasuredWidth] = useState(0);
  const measureRef = useCallback((node: HTMLDivElement | null) => {
    resizeObserverRef.current?.disconnect();
    if (!node) return;
    setMeasuredWidth(node.clientWidth);
    resizeObserverRef.current = new ResizeObserver(() => {
      setMeasuredWidth(node.clientWidth);
    });
    resizeObserverRef.current.observe(node);
  }, []);
  useEffect(() => () => resizeObserverRef.current?.disconnect(), []);

  const dynamicColors = useMemo(
    () => generateCoolColors(seriesData.length),
    [seriesData.length, generateCoolColors]
  );

  const options = useMemo(() => {
    // Group series by stack name to compute top-of-stack per date
    const stackGroups = new Map<string, BarSeriesItem[]>();
    seriesData.forEach((s) => {
      if (s.stack === false || !s.stack) return;
      const list = stackGroups.get(s.stack) ?? [];
      list.push(s);
      stackGroups.set(s.stack, list);
    });

    // topMap[`${stack}::${dateIdx}`] = index within stackGroups list of the
    // topmost visible (value > 0) series at that date
    const topMap = new Map<string, number>();
    stackGroups.forEach((stackSeries, stackKey) => {
      const dateCount = Math.max(...stackSeries.map((s) => s.data.length), 0);
      for (let dateIdx = 0; dateIdx < dateCount; dateIdx++) {
        for (let i = stackSeries.length - 1; i >= 0; i--) {
          const dp: any = stackSeries[i].data[dateIdx];
          const value = dp && typeof dp === 'object' ? dp.value : dp;
          if ((value ?? 0) > 0) {
            topMap.set(`${stackKey}::${dateIdx}`, i);
            break;
          }
        }
      }
    });

    const series = _.map(seriesData, (item: BarSeriesItem, index: number) => {
      const { stack, color, data, ...rest } = item;
      const resolvedColor = color || dynamicColors[index];
      const stackKey = stack === false || !stack ? null : (stack as string);
      const stackList = stackKey ? stackGroups.get(stackKey) : null;
      const indexInStack = stackList ? stackList.indexOf(item) : -1;

      const processedData =
        stackKey && stackList
          ? data.map((dp: any, dateIdx: number) => {
              if (dp == null) return dp;
              const isTop =
                topMap.get(`${stackKey}::${dateIdx}`) === indexInStack;
              const base = typeof dp === 'object' ? dp : { value: dp };
              return {
                ...base,
                itemStyle: {
                  borderRadius: isTop ? [2, 2, 0, 0] : 0
                }
              };
            })
          : data;

      return {
        ...rest,
        data: processedData,
        type: 'bar',
        barMaxWidth: 20,
        // Keep a small floor only — a large barMinWidth would force wide bars
        // when there are many categories (e.g. hourly buckets), squeezing out
        // the category gap so bars look fused. A low floor lets barCategoryGap
        // win, so even dense hourly views keep visible gaps.
        barMinWidth: 2,
        barGap: '30%',
        barCategoryGap: '50%',
        ...(stack === false || stack === undefined ? {} : { stack }),
        itemStyle: {
          color: resolvedColor,
          borderRadius: [2, 2, 0, 0]
        }
      };
    });

    return {
      title: {
        show: true,
        left: 'center',
        textStyle: { fontSize: 12, color: token.colorText },
        text: title || ''
      },
      grid: {
        left: 0,
        right: 0,
        top: title ? 30 : 10,
        bottom: 8,
        containLabel: true,
        ...grid
      },
      tooltip: {
        trigger: 'axis',
        backgroundColor: token.colorBgElevated,
        borderColor: 'transparent',
        formatter: (params: any) => {
          let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
          let visibleItemCount = 0;

          params.forEach((item: any) => {
            const raw = item.data?.value ?? item.value;
            const value = tooltipValueFormatter
              ? tooltipValueFormatter(raw)
              : raw;

            if (value === null || value === undefined) return;

            visibleItemCount += 1;
            const stackLabel = item.data?.stackLabel;
            const baseName = item.data?.tooltipName ?? item.seriesName;
            const displayName = stackLabel
              ? `${baseName} (${stackLabel})`
              : baseName;
            result += `<span class="tooltip-item">
              <span class="tooltip-item-name">
                <span class="tooltip-item-dot" style="border-radius:2px;background-color:${item.color};"></span>
                <span class="tooltip-item-title">${displayName}</span>:
              </span>
              <span class="tooltip-value">${value}</span>
            </span>`;
          });

          const wrapperClassName =
            visibleItemCount >= 12
              ? 'tooltip-wrapper tooltip-grid'
              : 'tooltip-wrapper';
          return `<div class="${wrapperClassName}">${result}</div>`;
        }
      },
      legend: {
        type: 'scroll',
        itemWidth: 8,
        itemHeight: 8,
        itemGap: 12,
        textStyle: { color: token.colorTextTertiary },
        pageTextStyle: { color: token.colorTextTertiary },
        pageIconColor: token.colorTextTertiary,
        pageIconInactiveColor: token.colorTextDisabled,
        bottom: 0,
        data: legendData,
        show: !!legendData?.length
      },
      xAxis: {
        type: 'category',
        axisTick: {
          show: true,
          lineStyle: { color: token.colorSplit }
        },
        axisLabel: {
          color: token.colorTextTertiary,
          fontSize: 12,
          formatter: labelFormatter
        },
        axisLine: { show: false },
        data: xAxisData
      },
      yAxis: {
        type: 'value',
        nameTextStyle: { padding: [0, 0, 0, -20] },
        splitLine: {
          show: true,
          lineStyle: { type: 'dashed', color: token.colorBorder }
        },
        axisLabel: {
          color: token.colorTextTertiary,
          fontSize: 12,
          formatter: formatLargeNumber
        },
        axisTick: { show: false }
      },
      animation: false,
      series
    };
  }, [
    seriesData,
    xAxisData,
    dynamicColors,
    labelFormatter,
    tooltipValueFormatter,
    legendData,
    title,
    token
  ]);

  useEffect(() => {
    if (!legendIsolate) return;
    const chart = chartRef.current?.chart;
    if (!chart) return;

    const handler = (params: any) => {
      const clicked = params.name;
      const allNames = Object.keys(params.selected || {});
      if (!allNames.length) return;

      const isClickedSelected = !!params.selected[clicked];
      const allOthersHidden = allNames
        .filter((n) => n !== clicked)
        .every((n) => !params.selected[n]);

      let newSelected: Record<string, boolean>;
      if (!isClickedSelected && allOthersHidden) {
        // clicking the only-visible one → restore all
        newSelected = Object.fromEntries(allNames.map((n) => [n, true]));
      } else {
        // isolate clicked
        newSelected = Object.fromEntries(
          allNames.map((n) => [n, n === clicked])
        );
      }

      chart.setOption({ legend: { selected: newSelected } });
    };

    chart.on('legendselectchanged', handler);
    return () => {
      chart.off('legendselectchanged', handler);
    };
  }, [legendIsolate, seriesData, legendData]);

  if (!seriesData.length) {
    return (
      <div
        style={{
          width: width || '100%',
          height,
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center'
        }}
      >
        {loading ? (
          <Spin size="middle" />
        ) : (
          <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
        )}
      </div>
    );
  }

  return (
    <div
      ref={measureRef}
      style={{ width: width || '100%', height, position: 'relative' }}
    >
      <Chart
        ref={chartRef as any}
        options={options as any}
        height={height}
        width={measuredWidth || width || '100%'}
      />
      {loading && (
        <div
          style={{
            position: 'absolute',
            inset: 20,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            background: 'var(--ant-color-bg-container)',
            opacity: 0.6,
            pointerEvents: 'none'
          }}
        >
          <Spin size="middle" />
        </div>
      )}
    </div>
  );
};

export default BarChart;
