import { useLayoutEffect, useMemo, useState } from 'react';
import { type ChartProps } from 'react-chartjs-2';

import BarChart from '@design-system/Charts/BarChart';
import Title from '@design-system/Title';
import { Box, CircularProgress, Stack } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import {
  Chart,
  type ChartEvent,
  type LegendElement,
  type LegendItem,
} from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation';

import { type EnpsChartProps } from './types';
import { generateNullAnnotations } from './utils';

Chart.register(annotationPlugin);

const CHART_HEIGHT = 360;
const AXIS_WIDTH = 35;

const EnpsChart = ({
  data,
  titleSlot,
  differenceData,
  headerSlot,
  navigationSlot,
  loading,
  isPreviousData = false,
  nullDataMessage = 'No data',
  filteredTooltipPrefix = 'Filtered',
  totalTooltipPrefix = 'Total',
  enpsTooltipPrefix = 'eNPS',
  emptyStateSlot,
}: EnpsChartProps) => {
  const theme = useTheme();
  const [footerEl, setFooterEl] = useState<HTMLDivElement | null>(null);
  const [footerWidth, setFooterWidth] = useState(0);

  useLayoutEffect(() => {
    if (!footerEl) return;

    const observer = new ResizeObserver(entries => {
      setFooterWidth((entries[0].target as HTMLElement).scrollWidth);
    });

    observer.observe(footerEl);
    return () => observer.disconnect();
  }, [footerEl]);

  const annotations = useMemo(
    () => generateNullAnnotations(data, theme, nullDataMessage),
    [data, theme, nullDataMessage],
  );

  const hasDifferenceData = !!differenceData && !!differenceData.length;

  const handleLegendClick = (
    _event: ChartEvent,
    legendItem: LegendItem,
    legend: LegendElement<'bar'>,
  ) => {
    const clickedIndex = legendItem.datasetIndex ?? 0;
    const chart = legend.chart;
    const datasetCount = chart.data.datasets.length;

    chart.setDatasetVisibility(
      clickedIndex,
      !chart.isDatasetVisible(clickedIndex),
    );

    const allHidden = Array.from(
      { length: datasetCount },
      (_, index) => index,
    ).every(i => !chart.isDatasetVisible(i));

    if (allHidden) {
      for (let i = 0; i < datasetCount; i++) {
        if (i !== clickedIndex) chart.setDatasetVisibility(i, true);
      }
    }

    chart.update();
  };

  const chartOptions: ChartProps<'bar'>['options'] = {
    animation: {
      duration: 0,
    },
    layout: {
      padding: {
        top: 10,
        bottom: 10,
      },
    },
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
      legend: {
        display: hasDifferenceData,
        position: 'top',
        align: 'start',
        onClick: handleLegendClick,
        labels: {
          color: theme.palette.new.text.neutral.default,
        },
      },
      tooltip: {
        displayColors: false,
        yAlign: 'bottom',
        backgroundColor: theme.palette.new.background.elements.inverted,
        bodyColor: theme.palette.new.text.neutral.inverted,
        filter: tooltipItem => data[tooltipItem.dataIndex].value !== null,
        callbacks: {
          title: () => '',
          label: ctx => {
            const datasetIndex = ctx.datasetIndex;
            const dataIndex = ctx.dataIndex;
            const originalValue =
              datasetIndex === 0
                ? data[dataIndex].value
                : (differenceData?.[dataIndex].value ?? ctx.parsed.y);

            if (originalValue === null) return nullDataMessage;

            if (!differenceData || differenceData.length === 0)
              return `${enpsTooltipPrefix}: ${originalValue}`;

            return `${datasetIndex === 0 ? filteredTooltipPrefix : totalTooltipPrefix}: ${originalValue}`;
          },
        },
      },
      annotation: {
        annotations,
      },
    },
    scales: {
      x: {
        display: false,
      },
      y: {
        beginAtZero: true,
        display: true,
        min: -100,
        max: 100,
        ticks: {
          stepSize: 50,
          display: false,
        },
        border: {
          dash: [4, 4],
        },
        grid: {
          display: true,
          drawTicks: false,
          color: ctx =>
            ctx.tick.value === 0
              ? theme.palette.new.text.neutral.default
              : theme.palette.new.border.neutral.divider,
        },
      },
    },
  };

  const shadowChartOptions: ChartProps<'bar'>['options'] = {
    animation: {
      duration: 0,
    },
    layout: {
      padding: {
        top: hasDifferenceData ? 42 : 0,
      },
    },
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
      legend: { display: false },
    },
    scales: {
      x: {
        display: false,
      },
      y: {
        beginAtZero: true,
        afterFit: ctx => {
          ctx.width = AXIS_WIDTH;
        },
        min: -100,
        max: 100,
        ticks: {
          stepSize: 50,
          color: theme.palette.new.text.neutral.lighter,
        },
        border: {
          dash: [4, 4],
        },
        grid: {
          color: ctx =>
            ctx.tick.value === 0
              ? theme.palette.new.text.neutral.default
              : theme.palette.new.border.neutral.divider,
        },
      },
    },
  };

  const datasets = useMemo(() => {
    const finalDatasets: ChartProps<'bar'>['data']['datasets'] = [
      {
        label: hasDifferenceData ? filteredTooltipPrefix : totalTooltipPrefix,
        data: data.map(item => {
          if (item.value === null) return 0;
          if (item.value === 0) return 1;
          return item.value;
        }),
        backgroundColor: theme.palette.new.graphics.brand[500],
        borderRadius: 4,
        borderSkipped: 'start',
      },
    ];

    if (differenceData) {
      finalDatasets.push({
        label: totalTooltipPrefix,
        data: differenceData.map((item, index) => {
          if (data[index].value === null) return 0;
          if (item.value === null) return 0;
          if (item.value === 0) return 1;
          return item.value;
        }),
        backgroundColor: theme.palette.new.graphics.info[600],
        borderRadius: 4,
        borderSkipped: 'start',
      });
    }

    return finalDatasets;
  }, [data, differenceData, theme, filteredTooltipPrefix, totalTooltipPrefix]);

  const shadowDataset = useMemo(() => {
    const finalDatasets: ChartProps<'bar'>['data']['datasets'] = [
      {
        data: data.map(item => item.value),
      },
    ];

    if (differenceData) {
      finalDatasets.push({
        data: differenceData.map(item => item.value),
      });
    }

    return finalDatasets;
  }, [data, differenceData]);

  const chartData: ChartProps<'bar'>['data'] = {
    labels: data.map(item => item.label),
    datasets,
  };

  const shadowChartData: ChartProps<'bar'>['data'] = {
    labels: data.map(item => item.label),
    datasets: shadowDataset,
  };

  return (
    <Stack
      sx={{
        width: '100%',
        p: 2,
        gap: 2,
        border: `1px solid ${theme.palette.new.border.neutral.default}`,
        backgroundColor: theme.palette.new.background.layout.tertiary,
        borderRadius: 2,
      }}
    >
      {titleSlot && (
        <Title
          title={titleSlot}
          variant="L"
        />
      )}
      {headerSlot}
      {loading && (
        <Stack
          sx={{
            height: CHART_HEIGHT,
            justifyContent: 'center',
            alignItems: 'center',
          }}
        >
          <CircularProgress />
        </Stack>
      )}
      {!loading && data.length === 0 && emptyStateSlot}
      {!loading && data.length > 0 && (
        <Stack
          sx={{
            height: '100%',
            flexDirection: 'row',
            width: '100%',
            alignItems: 'flex-start',
            overflowX: 'auto',
            backgroundColor: theme.palette.new.background.layout.tertiary,
            opacity: isPreviousData ? 0.5 : 1,
            transition: theme.transitions.create(['opacity'], {
              duration: theme.transitions.duration.short,
            }),
          }}
        >
          {/* Axis purpose chart */}
          <Box
            sx={{
              position: 'sticky',
              left: 0,
              top: 0,
              backgroundColor: theme.palette.new.background.layout.tertiary,
              zIndex: 1,
              height: CHART_HEIGHT,
              width: AXIS_WIDTH,
            }}
          >
            <BarChart
              type="bar"
              data={shadowChartData}
              options={shadowChartOptions}
            />
          </Box>
          <Stack
            sx={{
              width: '100%',
            }}
          >
            <Box
              sx={{
                height: CHART_HEIGHT,
                width: footerWidth || undefined,
              }}
            >
              <BarChart
                type="bar"
                data={chartData}
                options={chartOptions}
                plugins={[annotationPlugin]}
              />
            </Box>
            <Box
              ref={setFooterEl}
              sx={{ py: 1.5 }}
            >
              {navigationSlot}
            </Box>
          </Stack>
        </Stack>
      )}
    </Stack>
  );
};

export default EnpsChart;
