'use client';

import Box from '@mui/material/Box';
import dynamic from 'next/dynamic';
import * as React from 'react';
import type { Props as ApexChartProps } from 'react-apexcharts';

const ApexChart = dynamic(() => import('react-apexcharts'), { ssr: false, loading: () => null });

function toSafeNumber(value: unknown): number {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : 0;
}

function sanitizeSeries(series: ApexChartProps['series']): ApexChartProps['series'] {
  if (series == null) {
    return [];
  }

  if (!Array.isArray(series) || series.length === 0) {
    return [];
  }

  if (typeof series[0] === 'number') {
    return series.map((value) => toSafeNumber(value));
  }

  return series.map((item) => {
    if (typeof item === 'number') {
      return toSafeNumber(item);
    }

    if (item && typeof item === 'object' && 'data' in item) {
      const data = Array.isArray(item.data) ? item.data.map((value) => toSafeNumber(value)) : [];
      return { ...item, data };
    }

    return item;
  }) as ApexChartProps['series'];
}

function sanitizeOptions(options: ApexChartProps['options']): ApexChartProps['options'] {
  if (!options) {
    return {
      chart: {
        animations: { enabled: false },
        redrawOnParentResize: true,
      },
    };
  }

  const nextOptions = { ...options };

  nextOptions.chart = {
    animations: { enabled: false },
    redrawOnParentResize: true,
    ...nextOptions.chart,
  };

  if (nextOptions.xaxis?.categories) {
    nextOptions.xaxis = {
      ...nextOptions.xaxis,
      categories: nextOptions.xaxis.categories.map((category) =>
        category == null ? '' : String(category)
      ),
    };
  }

  if (Array.isArray(nextOptions.labels)) {
    nextOptions.labels = nextOptions.labels.map((label) => (label == null ? '' : String(label)));
  }

  if (Array.isArray(nextOptions.colors)) {
    nextOptions.colors = nextOptions.colors.filter(Boolean).map((color) => String(color));
  }

  return nextOptions;
}

function hasRenderableSeries(series: ApexChartProps['series']): boolean {
  if (!Array.isArray(series) || series.length === 0) {
    return false;
  }

  if (typeof series[0] === 'number') {
    return series.every((value) => Number.isFinite(toSafeNumber(value)));
  }

  return series.some(
    (item) =>
      item &&
      typeof item === 'object' &&
      'data' in item &&
      Array.isArray(item.data) &&
      item.data.length > 0 &&
      item.data.every((value) => Number.isFinite(toSafeNumber(value)))
  );
}

interface ChartErrorBoundaryProps {
  children: React.ReactNode;
  resetKey: string;
}

interface ChartErrorBoundaryState {
  hasError: boolean;
}

class ChartErrorBoundary extends React.Component<ChartErrorBoundaryProps, ChartErrorBoundaryState> {
  constructor(props: ChartErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(): ChartErrorBoundaryState {
    return { hasError: true };
  }

  componentDidUpdate(prevProps: ChartErrorBoundaryProps): void {
    if (prevProps.resetKey !== this.props.resetKey && this.state.hasError) {
      this.setState({ hasError: false });
    }
  }

  render(): React.ReactNode {
    if (this.state.hasError) {
      return null;
    }

    return this.props.children;
  }
}

export function Chart({
  series,
  options,
  type,
  height = 300,
  width: _width,
  ...rest
}: ApexChartProps): React.JSX.Element {
  const containerRef = React.useRef<HTMLDivElement>(null);
  const [mounted, setMounted] = React.useState(false);
  const [isVisible, setIsVisible] = React.useState(false);
  const [containerWidth, setContainerWidth] = React.useState(0);
  const [readyToRender, setReadyToRender] = React.useState(false);

  React.useEffect(() => {
    setMounted(true);
  }, []);

  React.useEffect(() => {
    if (!mounted || !containerRef.current) {
      return;
    }

    const element = containerRef.current;

    const intersectionObserver = new IntersectionObserver(
      ([entry]) => {
        setIsVisible(entry.isIntersecting);
      },
      { threshold: 0.01 }
    );

    const resizeObserver = new ResizeObserver((entries) => {
      const nextWidth = Math.floor(entries[0]?.contentRect.width ?? 0);
      if (nextWidth > 0) {
        setContainerWidth(nextWidth);
        setIsVisible(true);
      }
    });

    intersectionObserver.observe(element);
    resizeObserver.observe(element);

    const rect = element.getBoundingClientRect();
    if (rect.width > 0) {
      setContainerWidth(Math.floor(rect.width));
      setIsVisible(true);
    }

    return () => {
      intersectionObserver.disconnect();
      resizeObserver.disconnect();
    };
  }, [mounted]);

  React.useEffect(() => {
    if (!mounted || containerWidth > 0) {
      return;
    }

    const timeoutId = window.setTimeout(() => {
      const width = Math.floor(containerRef.current?.getBoundingClientRect().width ?? 0);
      if (width > 0) {
        setContainerWidth(width);
        setIsVisible(true);
      }
    }, 200);

    return () => {
      window.clearTimeout(timeoutId);
    };
  }, [mounted, containerWidth, type, series]);

  React.useEffect(() => {
    if (containerWidth <= 0) {
      setReadyToRender(false);
      return;
    }

    let frameId = 0;
    let timeoutId = 0;

    frameId = window.requestAnimationFrame(() => {
      timeoutId = window.setTimeout(() => {
        setReadyToRender(true);
      }, 50);
    });

    return () => {
      window.cancelAnimationFrame(frameId);
      window.clearTimeout(timeoutId);
      setReadyToRender(false);
    };
  }, [containerWidth, type, series, options]);

  const safeSeries = React.useMemo(() => sanitizeSeries(series), [series]);
  const safeOptions = React.useMemo(() => sanitizeOptions(options), [options]);
  const chartKey = React.useMemo(() => `${type}-${JSON.stringify(safeSeries)}`, [type, safeSeries]);

  const chartHeight = typeof height === 'number' ? height : 300;
  const canRender = mounted && readyToRender && hasRenderableSeries(safeSeries);

  return (
    <Box ref={containerRef} sx={{ width: '100%', minHeight: chartHeight }}>
      {canRender ? (
        <ChartErrorBoundary resetKey={chartKey}>
          <ApexChart
            {...rest}
            key={chartKey}
            type={type}
            series={safeSeries}
            options={safeOptions}
            height={chartHeight}
            width={containerWidth}
          />
        </ChartErrorBoundary>
      ) : null}
    </Box>
  );
}
