import { identity, isEqual, sortBy } from 'lodash';
import { useEffect, useMemo, useRef, useState } from 'react';
import * as React from 'react';

import {
  AbsoluteTimeRange,
  applyFieldOverrides,
  createFieldConfigRegistry,
  DashboardCursorSync,
  DataFrame,
  EventBus,
  FieldColorModeId,
  FieldConfigSource,
  getFrameDisplayName,
  LoadingState,
  SplitOpen,
  ThresholdsConfig,
  TimeRange,
} from '@grafana/data';
import { PanelRenderer } from '@grafana/runtime';
import {
  GraphDrawStyle,
  GraphThresholdsStyleConfig,
  LegendDisplayMode,
  SortOrder,
  TimeZone,
  TooltipDisplayMode,
  VizLegendOptions,
} from '@grafana/schema';
import { PanelContext, PanelContextProvider, SeriesVisibilityChangeMode, useTheme2 } from '@grafana/ui';
import { GraphFieldConfig } from 'app/plugins/panel/graph/types';
import { defaultGraphConfig, getGraphFieldConfig } from 'app/plugins/panel/timeseries/config';
import { Options as TimeSeriesOptions } from 'app/plugins/panel/timeseries/panelcfg.gen';
import { ExploreGraphStyle } from 'app/types';

import { seriesVisibilityConfigFactory } from '../../dashboard/dashgrid/SeriesVisibilityConfigFactory';
import { useExploreDataLinkPostProcessor } from '../hooks/useExploreDataLinkPostProcessor';

import { applyGraphStyle, applyThresholdsConfig } from './exploreGraphStyleUtils';
import { useStructureRev } from './useStructureRev';

interface Props {
  data: DataFrame[];
  height: number;
  width: number;
  timeRange: TimeRange;
  timeZone: TimeZone;
  loadingState: LoadingState;
  annotations?: DataFrame[];
  onHiddenSeriesChanged?: (hiddenSeries: string[]) => void;
  tooltipDisplayMode?: TooltipDisplayMode;
  splitOpenFn: SplitOpen;
  onChangeTime: (timeRange: AbsoluteTimeRange) => void;
  graphStyle: ExploreGraphStyle;
  anchorToZero?: boolean;
  yAxisMaximum?: number;
  thresholdsConfig?: ThresholdsConfig;
  thresholdsStyle?: GraphThresholdsStyleConfig;
  eventBus: EventBus;
  vizLegendOverrides?: Partial<VizLegendOptions>;
  toggleLegendRef?: React.MutableRefObject<(name: string, mode: SeriesVisibilityChangeMode) => void>;
}

export function ExploreGraph({
  data,
  height,
  width,
  timeZone,
  timeRange,
  onChangeTime,
  loadingState,
  annotations,
  onHiddenSeriesChanged,
  splitOpenFn,
  graphStyle,
  tooltipDisplayMode = TooltipDisplayMode.Single,
  anchorToZero = false,
  yAxisMaximum,
  thresholdsConfig,
  thresholdsStyle,
  eventBus,
  vizLegendOverrides,
  toggleLegendRef,
}: Props) {
  const theme = useTheme2();

  const fieldConfigRegistry = useMemo(
    () => createFieldConfigRegistry(getGraphFieldConfig(defaultGraphConfig), 'Explore'),
    []
  );

  const [fieldConfig, setFieldConfig] = useState<FieldConfigSource<GraphFieldConfig>>({
    defaults: {
      min: anchorToZero ? 0 : undefined,
      max: yAxisMaximum || undefined,
      unit: 'short',
      color: {
        mode: FieldColorModeId.PaletteClassic,
      },
      custom: {
        drawStyle: GraphDrawStyle.Line,
        fillOpacity: 0,
        pointSize: 5,
      },
    },
    overrides: [],
  });

  const styledFieldConfig = useMemo(() => {
    const withGraphStyle = applyGraphStyle(fieldConfig, graphStyle, yAxisMaximum);
    return applyThresholdsConfig(withGraphStyle, thresholdsStyle, thresholdsConfig);
  }, [fieldConfig, graphStyle, yAxisMaximum, thresholdsConfig, thresholdsStyle]);

  const dataLinkPostProcessor = useExploreDataLinkPostProcessor(splitOpenFn, timeRange);

  const dataWithConfig = useMemo(() => {
    return applyFieldOverrides({
      fieldConfig: styledFieldConfig,
      data,
      timeZone,
      replaceVariables: (value) => value, // We don't need proper replace here as it is only used in getLinks and we use getFieldLinks
      theme,
      fieldConfigRegistry,
      dataLinkPostProcessor,
    });
  }, [fieldConfigRegistry, data, timeZone, theme, styledFieldConfig, dataLinkPostProcessor]);

  const annotationsWithConfig = useMemo(() => {
    return applyFieldOverrides({
      fieldConfig: {
        defaults: {},
        overrides: [],
      },
      data: annotations,
      timeZone,
      replaceVariables: (value) => value,
      theme,
      dataLinkPostProcessor,
    });
  }, [annotations, timeZone, theme, dataLinkPostProcessor]);

  const structureRev = useStructureRev(dataWithConfig);

  const onHiddenSeriesChangedRef = useRef(onHiddenSeriesChanged);
  const previousHiddenFrames = useRef<string[] | undefined>(undefined);

  useEffect(() => {
    if (onHiddenSeriesChangedRef.current) {
      const hiddenFrames: string[] = [];
      dataWithConfig.forEach((frame) => {
        const allFieldsHidden = frame.fields.map((field) => field.config?.custom?.hideFrom?.viz).every(identity);
        if (allFieldsHidden) {
          hiddenFrames.push(getFrameDisplayName(frame));
        }
      });
      if (
        previousHiddenFrames.current === undefined ||
        !isEqual(sortBy(hiddenFrames), sortBy(previousHiddenFrames.current))
      ) {
        previousHiddenFrames.current = hiddenFrames;
        onHiddenSeriesChangedRef.current(hiddenFrames);
      }
    }
  }, [dataWithConfig]);

  const panelContext: PanelContext = {
    eventsScope: 'explore',
    eventBus,
    // TODO: Re-enable DashboardCursorSync.Crosshair when #81505 is fixed
    sync: () => DashboardCursorSync.Off,
    onToggleSeriesVisibility(label: string, mode: SeriesVisibilityChangeMode) {
      setFieldConfig(seriesVisibilityConfigFactory(label, mode, fieldConfig, data));
    },
    dataLinkPostProcessor,
  };

  function toggleLegend(name: string, mode: SeriesVisibilityChangeMode) {
    setFieldConfig(seriesVisibilityConfigFactory(name, mode, fieldConfig, data));
  }

  if (toggleLegendRef) {
    toggleLegendRef.current = toggleLegend;
  }

  const panelOptions: TimeSeriesOptions = useMemo(
    () => ({
      tooltip: { mode: tooltipDisplayMode, sort: SortOrder.None },
      legend: {
        displayMode: LegendDisplayMode.List,
        showLegend: true,
        placement: 'bottom',
        calcs: [],
        ...vizLegendOverrides,
      },
    }),
    [tooltipDisplayMode, vizLegendOverrides]
  );

  return (
    <PanelContextProvider value={panelContext}>
      <PanelRenderer
        data={{
          series: dataWithConfig,
          timeRange,
          state: loadingState,
          annotations: annotationsWithConfig,
          structureRev,
        }}
        pluginId="timeseries"
        title=""
        width={width}
        height={height}
        onChangeTimeRange={onChangeTime}
        timeZone={timeZone}
        options={panelOptions}
      />
    </PanelContextProvider>
  );
}
