import { css, cx } from '@emotion/css';
import { useBooleanFlagValue } from '@openfeature/react-sdk';
import { capitalize, groupBy } from 'lodash';
import * as React from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { usePrevious, useUnmount } from 'react-use';

import {
  type AbsoluteTimeRange,
  compareArrayValues,
  CoreApp,
  type DataFrame,
  DataHoverClearEvent,
  DataHoverEvent,
  type DataQueryResponse,
  type EventBus,
  type ExploreLogsPanelState,
  type ExplorePanelsState,
  type FieldConfigSource,
  type GrafanaTheme2,
  LoadingState,
  type LogLevel,
  type LogRowContextOptions,
  type LogRowModel,
  LogsDedupDescription,
  LogsDedupStrategy,
  type LogsMetaItem,
  LogsSortOrder,
  type PanelData,
  rangeUtil,
  type RawTimeRange,
  serializeStateToUrlParam,
  shallowCompare,
  type SplitOpen,
  store,
  type TimeRange,
  type TimeZone,
  toUtc,
  urlUtil,
} from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { config, reportInteraction } from '@grafana/runtime';
import { type DataQuery, DataTopic, type TableSortByFieldState } from '@grafana/schema';
import {
  Button,
  InlineField,
  InlineFieldRow,
  InlineSwitch,
  PanelChrome,
  type PopoverContent,
  RadioButtonGroup,
  SeriesVisibilityChangeMode,
  type Themeable2,
  withTheme2,
} from '@grafana/ui';
import { createAndCopyShortLink, getLogsPermalinkRange } from 'app/core/utils/shortLinks';
import { ControlledLogRows } from 'app/features/logs/components/ControlledLogRows';
import { InfiniteScroll } from 'app/features/logs/components/InfiniteScroll';
import { LogRows } from 'app/features/logs/components/LogRows';
import { LogRowContextModal } from 'app/features/logs/components/log-context/LogRowContextModal';
import { LogLineContext } from 'app/features/logs/components/panel/LogLineContext';
import { LogList, type LogListOptions } from 'app/features/logs/components/panel/LogList';
import { isDedupStrategy, isLogsSortOrder } from 'app/features/logs/components/panel/LogListContext';
import { dedupLogRows, LogLevelColor } from 'app/features/logs/logsModel';
import { getLogLevelFromKey, getLogLevelInfo } from 'app/features/logs/utils';
import { LokiQueryDirection } from 'app/plugins/datasource/loki/dataquery.gen';
import { isLokiQuery } from 'app/plugins/datasource/loki/queryUtils';
import { type GetFieldLinksFn } from 'app/plugins/panel/logs/types';
import { type Options } from 'app/plugins/panel/logstable/options/types';
import { type BuildLinkToLogLine } from 'app/plugins/panel/logstable/types';
import { getState } from 'app/store/store';
import { type ExploreItemState } from 'app/types/explore';
import { useDispatch } from 'app/types/store';

import { getDefaultFieldSelectorWidth } from '../../logs/components/fieldSelector/FieldSelector';
import {
  contentOutlineTrackPinAdded,
  contentOutlineTrackPinClicked,
  contentOutlineTrackPinLimitReached,
  contentOutlineTrackPinRemoved,
  contentOutlineTrackUnpinClicked,
} from '../ContentOutline/ContentOutlineAnalyticEvents';
import { useContentOutlineContext } from '../ContentOutline/ContentOutlineContext';
import { getUrlStateFromPaneState } from '../hooks/useStateSync/external.utils';
import { changePanelState } from '../state/explorePane';
import { changeQueries, runQueries } from '../state/query';

import { ExploreLogsTable } from './ExploreLogsTable';
import { LogsFeedback } from './LogsFeedback';
import { LogsMetaRow } from './LogsMetaRow';
import LogsNavigation from './LogsNavigation';
import { getLogsTableHeight, LogsTableWrap } from './LogsTableWrap';
import { LogsVolumePanelList } from './LogsVolumePanelList';
import { type LogsVisualisationType } from './constants';
import { LOGS_TABLE_SETTING_KEYS, SETTING_KEY_ROOT, SETTINGS_KEYS, visualisationTypeKey } from './utils/logs';
import { getDefaultDisplayedFieldsFromExploreState } from './utils/table/columnsMigration';
import { getDefaultTableSortBy } from './utils/table/logsTable';
import { getExploreBaseUrl } from './utils/url';

interface Props extends Themeable2 {
  width: number;
  splitOpen: SplitOpen;
  logRows: LogRowModel[];
  logsMeta?: LogsMetaItem[];
  logsSeries?: DataFrame[];
  logsQueries?: DataQuery[];
  visibleRange?: AbsoluteTimeRange;
  theme: GrafanaTheme2;
  loading: boolean;
  loadingState: LoadingState;
  absoluteRange: AbsoluteTimeRange;
  timeZone: TimeZone;
  scanning?: boolean;
  scanRange?: RawTimeRange;
  exploreId: string;
  datasourceType?: string;
  logsVolumeEnabled: boolean;
  logsVolumeData: DataQueryResponse | undefined;
  onSetLogsVolumeEnabled: (enabled: boolean) => void;
  loadLogsVolumeData: () => void;
  showContextToggle?: (row: LogRowModel) => boolean;
  onChangeTime: (range: AbsoluteTimeRange) => void;
  onClickFilterLabel?: (key: string, value: string, frame?: DataFrame) => void;
  onClickFilterOutLabel?: (key: string, value: string, frame?: DataFrame) => void;
  onStartScanning?: () => void;
  onStopScanning?: () => void;
  getRowContext?: (row: LogRowModel, origRow: LogRowModel, options: LogRowContextOptions) => Promise<DataQueryResponse>;
  getRowContextQuery?: (
    row: LogRowModel,
    options?: LogRowContextOptions,
    cacheFilters?: boolean
  ) => Promise<DataQuery | null>;
  getLogRowContextUi?: (row: LogRowModel, runContextQuery?: () => void) => React.ReactNode;
  getFieldLinks: GetFieldLinksFn;
  eventBus: EventBus;
  panelState?: ExplorePanelsState;
  isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
  logsFrames: DataFrame[] | undefined;
  range: TimeRange;
  onClickFilterString?: (value: string, refId?: string) => void;
  onClickFilterOutString?: (value: string, refId?: string) => void;
  loadMoreLogs?(range: AbsoluteTimeRange): void;
  onPinLineCallback?: () => void;
}

// we need to define the order of these explicitly
const DEDUP_OPTIONS = [
  LogsDedupStrategy.none,
  LogsDedupStrategy.exact,
  LogsDedupStrategy.numbers,
  LogsDedupStrategy.signature,
];

const getDefaultVisualisationType = (): LogsVisualisationType => {
  const visualisationType = store.get(visualisationTypeKey);
  if (visualisationType === 'table') {
    return 'table';
  }
  if (visualisationType === 'logs') {
    return 'logs';
  }
  return 'logs';
};

const PINNED_LOGS_LIMIT = 10;
const PINNED_LOGS_TITLE = 'Pinned log';
const PINNED_LOGS_MESSAGE = 'Pin to content outline';
const PINNED_LOGS_PANELID = 'Logs';

const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
  const {
    width,
    splitOpen,
    logRows,
    logsMeta,
    logsVolumeEnabled,
    logsVolumeData,
    loadLogsVolumeData,
    loading = false,
    onClickFilterLabel,
    onClickFilterOutLabel,
    timeZone,
    scanning,
    scanRange,
    showContextToggle,
    absoluteRange,
    onChangeTime,
    getFieldLinks,
    theme,
    logsQueries,
    exploreId,
    getRowContext,
    getLogRowContextUi,
    getRowContextQuery,
    loadMoreLogs,
    panelState,
    eventBus,
    onPinLineCallback,
  } = props;
  const [showLabels, setShowLabels] = useState<boolean>(store.getBool(SETTINGS_KEYS.showLabels, false));
  const [showTime, setShowTime] = useState<boolean>(store.getBool(SETTINGS_KEYS.showTime, true));
  const [wrapLogMessage, setWrapLogMessage] = useState<boolean>(store.getBool(SETTINGS_KEYS.wrapLogMessage, true));
  const [prettifyLogMessage, setPrettifyLogMessage] = useState<boolean>(
    store.getBool(SETTINGS_KEYS.prettifyLogMessage, false)
  );
  const [dedupStrategy, setDedupStrategy] = useState<LogsDedupStrategy>(LogsDedupStrategy.none);
  const [logsSortOrder, setLogsSortOrder] = useState<LogsSortOrder>(
    panelState?.logs?.sortOrder ?? store.get(SETTINGS_KEYS.logsSortOrder) ?? LogsSortOrder.Descending
  );
  const tableFrameIndex: number = useMemo(() => {
    const frameIndex = props.logsFrames?.findIndex((f) => f.refId === props.panelState?.logs?.refId);
    if (frameIndex !== undefined && frameIndex !== -1) {
      return frameIndex;
    }
    return 0;
  }, [props.logsFrames, props.panelState?.logs?.refId]);

  const tableSortByDefaultStringFromStorage: string = store.get(LOGS_TABLE_SETTING_KEYS.sortBy);
  const [tableSortBy, setTableSortBy] = useState<TableSortByFieldState[]>(
    getDefaultTableSortBy(tableSortByDefaultStringFromStorage, props?.logsFrames?.[tableFrameIndex], logsSortOrder)
  );

  const fieldSelectorWidthFromStorage = store.get(LOGS_TABLE_SETTING_KEYS.fieldSelectorWidth);
  const [fieldSelectorWidth, setFieldSelectorWidth] = useState(
    fieldSelectorWidthFromStorage ? parseInt(fieldSelectorWidthFromStorage, 10) : getDefaultFieldSelectorWidth()
  );
  const [isFlipping, setIsFlipping] = useState<boolean>(false);
  const [defaultDisplayedFields, setDefaultDisplayedFields] = useState<string[]>([]);
  const [contextOpen, setContextOpen] = useState<boolean>(false);
  const [contextRow, setContextRow] = useState<LogRowModel | undefined>(undefined);
  const [pinLineButtonTooltipTitle, setPinLineButtonTooltipTitle] = useState<PopoverContent>(PINNED_LOGS_MESSAGE);
  const [visualisationType, setVisualisationType] = useState<LogsVisualisationType>(
    panelState?.logs?.visualisationType ?? getDefaultVisualisationType()
  );
  const logsContainerRef = useRef<HTMLDivElement | null>(null);
  const dispatch = useDispatch();
  const previousLoading = usePrevious(loading);

  const logsVolumeEventBus = eventBus.newScopedBus('logsvolume', { onlyLocal: false });
  const { register, unregister, outlineItems, updateItem } = useContentOutlineContext() ?? {};
  const flipOrderTimer = useRef<number | undefined>(undefined);
  const cancelFlippingTimer = useRef<number | undefined>(undefined);
  const toggleLegendRef = useRef<(name: string | undefined, mode: SeriesVisibilityChangeMode) => void>(() => {});
  const topLogsRef = useRef<HTMLDivElement>(null);
  const [filterLevels, setFilterLevels] = useState<LogLevel[] | undefined>(undefined);
  const newLogsPanelEnabled = useBooleanFlagValue('newLogsPanel', true);
  const newLogContextEnabled = useBooleanFlagValue('newLogContext', false);
  const logsPanelControlsEnabled = useBooleanFlagValue('logsPanelControls', true);

  const tableHeight = getLogsTableHeight();
  const setWrapperLineWrapStyles = wrapLogMessage || visualisationType === 'table';
  const styles = getStyles(theme, setWrapperLineWrapStyles, tableHeight);
  const hasData = logRows && logRows.length > 0;
  const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';

  const updatePanelState = useCallback(
    (logsPanelState: Partial<ExploreLogsPanelState>) => {
      const state: ExploreItemState | undefined = getState().explore.panes[exploreId];
      if (state?.panelsState) {
        dispatch(
          changePanelState(exploreId, 'logs', {
            ...state.panelsState.logs,
            columns: logsPanelState.columns ?? panelState?.logs?.columns,
            visualisationType: logsPanelState.visualisationType ?? visualisationType,
            labelFieldName: logsPanelState.labelFieldName,
            refId: logsPanelState.refId ?? panelState?.logs?.refId,
            displayedFields: logsPanelState.displayedFields ?? panelState?.logs?.displayedFields,
            tableSortBy: logsPanelState.tableSortBy ?? panelState?.logs?.tableSortBy,
            tableSortDir: logsPanelState.tableSortDir ?? panelState?.logs?.tableSortDir,
          })
        );
      }
    },
    [
      dispatch,
      exploreId,
      panelState?.logs?.columns,
      panelState?.logs?.displayedFields,
      panelState?.logs?.refId,
      panelState?.logs?.tableSortBy,
      panelState?.logs?.tableSortDir,
      visualisationType,
    ]
  );

  const [displayedFields, setDisplayedFields] = useState<string[]>(() =>
    getDefaultDisplayedFieldsFromExploreState(
      panelState?.logs,
      updatePanelState,
      config.featureToggles.logsTablePanelNG ?? false // Use the old feature toggles on initial load, newer useBooleanFlagValue hooks can use default of first render
    )
  );

  // Get pinned log lines
  const logsParent = outlineItems?.find((item) => item.panelId === PINNED_LOGS_PANELID && item.level === 'root');
  const pinnedLogs = useMemo(
    () =>
      logsParent?.children
        ?.filter((outlines) => outlines.title === PINNED_LOGS_TITLE)
        .map((pinnedLogs) => pinnedLogs.id),
    [logsParent?.children]
  );

  const getPinnedLogsCount = useCallback(() => {
    const logsParent = outlineItems?.find((item) => item.panelId === PINNED_LOGS_PANELID && item.level === 'root');
    return logsParent?.children?.filter((child) => child.title === PINNED_LOGS_TITLE).length ?? 0;
  }, [outlineItems]);

  const handleSetTableSortBy = useCallback((sortBy: TableSortByFieldState[]) => {
    store.set(LOGS_TABLE_SETTING_KEYS.sortBy, JSON.stringify(sortBy));
    setTableSortBy(sortBy);
  }, []);

  useEffect(() => {
    if (getPinnedLogsCount() === PINNED_LOGS_LIMIT) {
      setPinLineButtonTooltipTitle(
        <span style={{ display: 'flex', textAlign: 'center' }}>
          ❗️
          <Trans i18nKey="explore.logs.maximum-pinned-logs">
            Maximum of {{ PINNED_LOGS_LIMIT }} pinned logs reached. Unpin a log to add another.
          </Trans>
        </span>
      );
    } else {
      setPinLineButtonTooltipTitle(PINNED_LOGS_MESSAGE);
    }
  }, [outlineItems, getPinnedLogsCount]);

  useEffect(() => {
    if (loading && !previousLoading && panelState?.logs?.id) {
      // loading stopped, so we need to remove any permalinked log lines
      delete panelState.logs.id;

      dispatch(
        changePanelState(exploreId, 'logs', {
          ...panelState,
        })
      );
    }
  }, [dispatch, exploreId, loading, panelState, previousLoading]);

  useEffect(() => {
    // Initialize URL sort order
    if (!panelState?.logs?.sortOrder) {
      dispatch(
        changePanelState(exploreId, 'logs', {
          ...panelState,
          sortOrder: logsSortOrder,
        })
      );
    }
  }, [dispatch, exploreId, logsSortOrder, panelState]);

  useEffect(() => {
    const visualisationType = panelState?.logs?.visualisationType ?? getDefaultVisualisationType();
    setVisualisationType(visualisationType);

    store.set(visualisationTypeKey, visualisationType);
  }, [panelState?.logs?.visualisationType]);

  useUnmount(() => {
    if (flipOrderTimer) {
      window.clearTimeout(flipOrderTimer.current);
    }
    if (cancelFlippingTimer) {
      window.clearTimeout(cancelFlippingTimer.current);
    }
  });

  useUnmount(() => {
    // If we're unmounting logs (e.g. switching to another datasource), we need to remove the logs specific panel state, otherwise it will persist in the explore url
    dispatch(
      changePanelState(exploreId, 'logs', {
        ...panelState?.logs,
        columns: undefined,
        visualisationType: visualisationType,
        labelFieldName: undefined,
        refId: undefined,
        displayedFields: undefined,
        sortOrder: undefined,
        tableSortBy: undefined,
        tableSortDir: undefined,
      })
    );
  });

  useEffect(() => {
    if (!shallowCompare(displayedFields, panelState?.logs?.displayedFields ?? [])) {
      updatePanelState({
        ...panelState?.logs,
        displayedFields,
      });
    }
  }, [displayedFields, panelState?.logs, updatePanelState]);

  // actions
  const onLogRowHover = useCallback(
    (row?: LogRowModel) => {
      if (!row) {
        props.eventBus.publish(new DataHoverClearEvent());
      } else {
        props.eventBus.publish(
          new DataHoverEvent({
            point: {
              time: row.timeEpochMs,
            },
          })
        );
      }
    },
    [props.eventBus]
  );

  const scrollIntoView = useCallback((element: HTMLElement) => {
    if (logsContainerRef.current) {
      topLogsRef.current?.scrollIntoView?.();
      logsContainerRef.current.scroll({
        behavior: 'smooth',
        top: logsContainerRef.current.scrollTop + element.getBoundingClientRect().top - window.innerHeight / 2,
      });
    }

    return;
  }, []);

  const sortOrderChanged = useCallback(
    (newSortOrder: LogsSortOrder) => {
      if (!logsQueries) {
        return;
      }
      let hasLokiQueries = false;
      const newQueries = logsQueries.map((query) => {
        if (query.datasource?.type !== 'loki' || !isLokiQuery(query)) {
          return query;
        }
        if (query.direction === LokiQueryDirection.Scan) {
          // Don't override Scan. When the direction is Scan it means that the user specifically assigned this direction to the query.
          return query;
        }
        hasLokiQueries = true;
        const newDirection =
          newSortOrder === LogsSortOrder.Ascending ? LokiQueryDirection.Forward : LokiQueryDirection.Backward;
        if (newDirection !== query.direction) {
          query.direction = newDirection;
        }
        return query;
      });

      if (hasLokiQueries) {
        dispatch(changeQueries({ exploreId, queries: newQueries }));
        dispatch(runQueries({ exploreId }));
      }
      dispatch(
        changePanelState(exploreId, 'logs', {
          ...panelState?.logs,
          sortOrder: newSortOrder,
        })
      );
    },
    [dispatch, exploreId, logsQueries, panelState?.logs]
  );

  const onChangeLogsSortOrder = useCallback(
    (newSortOrder: LogsSortOrder) => {
      setIsFlipping(true);
      // we are using setTimeout here to make sure that disabled button is rendered before the rendering of reordered logs
      flipOrderTimer.current = window.setTimeout(() => {
        store.set(SETTINGS_KEYS.logsSortOrder, newSortOrder);
        sortOrderChanged(newSortOrder);
        setLogsSortOrder(newSortOrder);
      }, 0);
      cancelFlippingTimer.current = window.setTimeout(() => setIsFlipping(false), 1000);
    },
    [sortOrderChanged]
  );

  const onChangeVisualisation = useCallback(
    (visualisation: LogsVisualisationType) => {
      setVisualisationType(visualisation);
      const payload = {
        ...panelState?.logs,
        visualisationType: visualisation,
      };
      updatePanelState(payload);

      reportInteraction('grafana_explore_logs_visualisation_changed', {
        newVisualizationType: visualisation,
        datasourceType: props.datasourceType ?? 'unknown',
      });
    },
    [panelState?.logs, props.datasourceType, updatePanelState]
  );

  const onChangeDedup = useCallback(
    (dedupStrategy: LogsDedupStrategy) => {
      reportInteraction('grafana_explore_logs_deduplication_clicked', {
        deduplicationType: dedupStrategy,
        datasourceType: props.datasourceType,
      });
      setDedupStrategy(dedupStrategy);
    },
    [props.datasourceType]
  );

  const onChangeLabels = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
    const { target } = event;
    if (target) {
      const showLabels = target.checked;

      setShowLabels(showLabels);
      store.set(SETTINGS_KEYS.showLabels, showLabels);
    }
  }, []);

  const onChangeShowTime = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
    const { target } = event;
    if (target) {
      const showTime = target.checked;

      setShowTime(showTime);
      store.set(SETTINGS_KEYS.showTime, showTime);
    }
  }, []);

  const onChangeWrapLogMessage = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
    const { target } = event;
    if (target) {
      const wrapLogMessage = target.checked;

      setWrapLogMessage(wrapLogMessage);
      store.set(SETTINGS_KEYS.wrapLogMessage, wrapLogMessage);
    }
  }, []);

  const onChangePrettifyLogMessage = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
    const { target } = event;
    if (target) {
      const prettifyLogMessage = target.checked;

      setPrettifyLogMessage(prettifyLogMessage);
      store.set(SETTINGS_KEYS.prettifyLogMessage, prettifyLogMessage);
    }
  }, []);

  const onToggleLogsVolumeCollapse = useCallback(
    (collapsed: boolean) => {
      props.onSetLogsVolumeEnabled(!collapsed);
      reportInteraction('grafana_explore_logs_histogram_toggle_clicked', {
        datasourceType: props.datasourceType,
        type: !collapsed ? 'open' : 'close',
      });
    },
    [props]
  );

  const onClickScan = useCallback(
    (event: React.SyntheticEvent) => {
      event.preventDefault();
      if (props.onStartScanning) {
        props.onStartScanning();
        reportInteraction('grafana_explore_logs_scanning_button_clicked', {
          type: 'start',
          datasourceType: props.datasourceType,
        });
      }
    },
    [props]
  );

  const onClickStopScan = useCallback(
    (event: React.SyntheticEvent) => {
      event.preventDefault();
      if (props.onStopScanning) {
        props.onStopScanning();
      }
    },
    [props]
  );

  const showField = useCallback(
    (key: string) => {
      const index = displayedFields.indexOf(key);

      if (index === -1) {
        const updatedDisplayedFields = displayedFields.concat(key);
        setDisplayedFields(updatedDisplayedFields);
      }
    },
    [displayedFields]
  );

  const hideField = useCallback(
    (key: string) => {
      const index = displayedFields.indexOf(key);
      if (index > -1) {
        const updatedDisplayedFields = displayedFields.filter((k) => key !== k);
        setDisplayedFields(updatedDisplayedFields);
      }
    },
    [displayedFields]
  );

  const clearDisplayedFields = useCallback(() => {
    setDisplayedFields([]);
  }, []);

  const onCloseCallbackRef = useRef<() => void>(() => {});

  let onCloseContext = useCallback(() => {
    setContextOpen(false);
    setContextRow(undefined);
    if (!newLogContextEnabled) {
      reportInteraction('grafana_explore_logs_log_context_closed', {
        datasourceType: contextRow?.datasourceType,
        logRowUid: contextRow?.uid,
      });
    }
    onCloseCallbackRef?.current();
  }, [contextRow?.datasourceType, contextRow?.uid, newLogContextEnabled, onCloseCallbackRef]);

  const onOpenContext = useCallback(
    (row: LogRowModel, onClose: () => void) => {
      // we are setting the `contextOpen` open state and passing it down to the `LogRow` in order to highlight the row when a LogContext is open
      setContextOpen(true);
      setContextRow(row);
      if (!newLogContextEnabled) {
        reportInteraction('grafana_explore_logs_log_context_opened', {
          datasourceType: row.datasourceType,
          logRowUid: row.uid,
        });
      }
      onCloseCallbackRef.current = onClose;
    },
    [newLogContextEnabled]
  );

  const onPermalinkClick = useCallback(
    async (row: LogRowModel) => {
      // this is an extra check, to be sure that we are not
      // creating permalinks for logs without an id-field.
      // normally it should never happen, because we do not
      // display the permalink button in such cases.
      if (row.rowId === undefined) {
        return;
      }

      // get explore state, add log-row-id and make timerange absolute
      const urlState = getUrlStateFromPaneState(getState().explore.panes[exploreId]!);
      urlState.panelsState = {
        ...panelState,
        logs: {
          id: row.uid,
          visualisationType: visualisationType ?? getDefaultVisualisationType(),
          displayedFields,
          sortOrder: logsSortOrder,
        },
      };
      urlState.range = getLogsPermalinkRange(row, logRows, absoluteRange);

      // append changed urlState to baseUrl
      const serializedState = serializeStateToUrlParam(urlState);
      const baseUrl = getExploreBaseUrl();
      const url = urlUtil.renderUrl(`${baseUrl}/explore`, { left: serializedState });
      await createAndCopyShortLink(url);

      reportInteraction('grafana_explore_logs_permalink_clicked', {
        datasourceType: row.datasourceType ?? 'unknown',
        logRowUid: row.uid,
        logRowLevel: row.logLevel,
      });
    },
    [absoluteRange, displayedFields, exploreId, logRows, logsSortOrder, panelState, visualisationType]
  );

  const onTablePermalinkClick: BuildLinkToLogLine = useCallback(
    (logId: string) => {
      // this is an extra check, to be sure that we are not
      // creating permalinks for logs without an id-field.
      // normally it should never happen, because we do not
      // display the permalink button in such cases.
      if (logId === undefined) {
        return null;
      }

      // get explore state, add log-row-id and make timerange absolute
      const urlState = getUrlStateFromPaneState(getState().explore.panes[exploreId]!);
      urlState.panelsState = {
        ...panelState,
        logs: {
          id: logId,
          visualisationType: visualisationType ?? getDefaultVisualisationType(),
          displayedFields,
          sortOrder: logsSortOrder,
        },
      };

      urlState.range = {
        from: toUtc(props.range.from).valueOf().toString(),
        to: toUtc(props.range.to).valueOf().toString(),
      };

      // append changed urlState to baseUrl
      const serializedState = serializeStateToUrlParam(urlState);
      const baseUrl = getExploreBaseUrl();
      const url = urlUtil.renderUrl(`${baseUrl}/explore`, { left: serializedState });

      createAndCopyShortLink(url);

      reportInteraction('grafana_explore_logs_table_permalink_clicked', {
        logRowUid: logId,
      });

      return url;
    },
    [displayedFields, exploreId, logsSortOrder, panelState, props.range.from, props.range.to, visualisationType]
  );

  const scrollToTopLogs = useCallback(() => {
    if (logsContainerRef.current) {
      logsContainerRef.current.scroll({
        behavior: 'auto',
        top: 0,
      });
    }
    topLogsRef.current?.scrollIntoView();
  }, []);

  const onPinToContentOutlineClick = useCallback(
    (row: LogRowModel, allowUnPin = true) => {
      if (getPinnedLogsCount() === PINNED_LOGS_LIMIT && !allowUnPin) {
        contentOutlineTrackPinLimitReached();
        return;
      }

      // find the Logs parent item
      const logsParent = outlineItems?.find((item) => item.panelId === PINNED_LOGS_PANELID && item.level === 'root');

      //update the parent's expanded state
      if (logsParent && updateItem) {
        updateItem(logsParent.id, { expanded: true });
      }

      const alreadyPinned = pinnedLogs?.find((pin) => pin === row.rowId);
      if (alreadyPinned && row.rowId && allowUnPin) {
        unregister?.(row.rowId);
        contentOutlineTrackPinRemoved();
      } else if (getPinnedLogsCount() !== PINNED_LOGS_LIMIT && !alreadyPinned) {
        register?.({
          id: row.rowId,
          icon: 'gf-logs',
          title: PINNED_LOGS_TITLE,
          panelId: PINNED_LOGS_PANELID,
          level: 'child',
          ref: null,
          color: LogLevelColor[row.logLevel],
          childOnTop: true,
          onClick: () => {
            onOpenContext(row, () => {});
            contentOutlineTrackPinClicked();
          },
          onRemove: (id: string) => {
            unregister?.(id);
            contentOutlineTrackUnpinClicked();
          },
        });
        contentOutlineTrackPinAdded();
      }

      onPinLineCallback?.();
    },
    [getPinnedLogsCount, onOpenContext, onPinLineCallback, outlineItems, pinnedLogs, register, unregister, updateItem]
  );

  const { dedupedRows, dedupCount } = useMemo(() => dedupRows(logRows, dedupStrategy), [dedupStrategy, logRows]);
  const infiniteScrollAvailable = useMemo(
    () => !logsQueries?.some((query) => 'direction' in query && query.direction === LokiQueryDirection.Scan),
    [logsQueries]
  );

  const visibilityChangedRef = useRef(true);
  const onLogOptionsChange = useCallback(
    (option: LogListOptions, value: string | string[] | boolean) => {
      if (option === 'sortOrder' && isLogsSortOrder(value)) {
        sortOrderChanged(value);
      } else if (option === 'dedupStrategy' && isDedupStrategy(value)) {
        setDedupStrategy(value);
      } else if (option === 'filterLevels' && Array.isArray(value) && logsVolumeEnabled) {
        const logVolData =
          logsVolumeData?.data.filter((frame: DataFrame) => frame.meta?.dataTopic !== DataTopic.Annotations) ?? [];
        const grouped = groupBy(logVolData, 'meta.custom.datasourceName');
        const numberOfLogVolumes = Object.keys(grouped).length;

        // Not supported
        if (numberOfLogVolumes > 1) {
          return;
        }

        const logsVolumeLevels = [
          ...new Set(
            logVolData.map((dataFrame) => {
              const { level } = getLogLevelInfo(dataFrame, logVolData);
              return getLogLevelFromKey(level);
            })
          ),
        ];

        setFilterLevels((currentFilterLevels: LogLevel[] | undefined) => {
          const newLevels = value.map((level) => getLogLevelFromKey(level));
          const newLogsVolumeLevels = newLevels.filter((level) => logsVolumeLevels.includes(level));
          const prevLogsVolumeLevels = currentFilterLevels?.filter((level) => logsVolumeLevels.includes(level)) ?? [];

          if (!newLogsVolumeLevels.length) {
            toggleLegendRef.current?.(undefined, SeriesVisibilityChangeMode.ToggleSelection);
            return undefined;
          }

          const addedLevels = newLogsVolumeLevels.filter((newLevel) => !prevLogsVolumeLevels.includes(newLevel));
          const removedLevels = prevLogsVolumeLevels.filter((prevLevel) => !newLogsVolumeLevels.includes(prevLevel));

          addedLevels.forEach((level) => {
            visibilityChangedRef.current = true;
            toggleLegendRef.current?.(
              level,
              !prevLogsVolumeLevels.length
                ? SeriesVisibilityChangeMode.ToggleSelection
                : SeriesVisibilityChangeMode.AppendToSelection
            );
          });
          removedLevels.forEach((level) => {
            visibilityChangedRef.current = true;
            toggleLegendRef.current?.(level, SeriesVisibilityChangeMode.AppendToSelection);
          });

          return newLevels;
        });
      } else if (option === 'defaultDisplayedFields' && Array.isArray(value)) {
        setDefaultDisplayedFields(value);
      }
    },
    [logsVolumeData?.data, logsVolumeEnabled, sortOrderChanged]
  );

  const onDisplayedSeriesChanged = useCallback((levels: string[]) => {
    if (visibilityChangedRef.current) {
      visibilityChangedRef.current = false;
      return;
    }
    setFilterLevels(levels.map((level) => getLogLevelFromKey(level)));
  }, []);

  const enableNewLogsTable = config.featureToggles.logsTablePanelNG;
  const panelData: PanelData = {
    state: loading ? LoadingState.Loading : LoadingState.Done,
    series: props.logsFrames ?? [],
    timeRange: props.range,
  };

  return (
    <>
      {(!newLogsPanelEnabled || !newLogContextEnabled) && getRowContext && contextRow && (
        <LogRowContextModal
          open={contextOpen}
          row={contextRow}
          onClose={onCloseContext}
          getRowContext={(row, options) => getRowContext(row, contextRow, options)}
          getRowContextQuery={getRowContextQuery}
          getLogRowContextUi={getLogRowContextUi}
          logsSortOrder={logsSortOrder}
          timeZone={timeZone}
        />
      )}
      {newLogsPanelEnabled && newLogContextEnabled && getRowContext && contextRow && (
        <LogLineContext
          open={contextOpen}
          log={contextRow}
          onClose={onCloseContext}
          getRowContext={(row, options) => getRowContext(row, contextRow, options)}
          getRowContextQuery={getRowContextQuery}
          getLogRowContextUi={getLogRowContextUi}
          logOptionsStorageKey={SETTING_KEY_ROOT}
          timeZone={timeZone}
          displayedFields={displayedFields}
        />
      )}
      <div className={styles.logsVolumePanel}>
        <PanelChrome
          title={t('explore.unthemed-logs.title-logs-volume', 'Logs volume')}
          collapsible
          loadingState={logsVolumeData?.state}
          collapsed={!logsVolumeEnabled}
          onToggleCollapse={onToggleLogsVolumeCollapse}
        >
          {logsVolumeEnabled && (
            <LogsVolumePanelList
              toggleLegendRef={toggleLegendRef}
              absoluteRange={absoluteRange}
              width={width}
              logsVolumeData={logsVolumeData}
              onUpdateTimeRange={onChangeTime}
              timeZone={timeZone}
              splitOpen={splitOpen}
              onLoadLogsVolume={loadLogsVolumeData}
              onDisplayedSeriesChanged={onDisplayedSeriesChanged}
              eventBus={logsVolumeEventBus}
              onClose={() => onToggleLogsVolumeCollapse(true)}
              logs={logRows}
            />
          )}
        </PanelChrome>
      </div>
      <PanelChrome
        titleItems={[
          visualisationType === 'logs' ? null : (
            <PanelChrome.TitleItem title={t('explore.unthemed-logs.title-feedback', 'Feedback')} key="A">
              <LogsFeedback feedbackUrl="https://forms.gle/5YyKdRQJ5hzq4c289" />
            </PanelChrome.TitleItem>
          ),
        ]}
        title={t('explore.unthemed-logs.title-logs', 'Logs')}
        actions={
          <>
            <div className={styles.visualisationType}>
              <RadioButtonGroup
                className={styles.visualisationTypeRadio}
                options={[
                  {
                    label: t('explore.unthemed-logs.label.logs', 'Logs'),
                    value: 'logs',
                    description: t(
                      'explore.unthemed-logs.description.show-results-in-logs-visualisation',
                      'Show results in logs visualisation'
                    ),
                  },
                  {
                    label: t('explore.unthemed-logs.label.table', 'Table'),
                    value: 'table',
                    description: t(
                      'explore.unthemed-logs.description.show-results-in-table-visualisation',
                      'Show results in table visualisation'
                    ),
                  },
                ]}
                size="sm"
                value={visualisationType}
                onChange={onChangeVisualisation}
              />
            </div>
          </>
        }
        loadingState={loading ? LoadingState.Loading : LoadingState.Done}
      >
        <div className={styles.stickyNavigation}>
          {visualisationType !== 'table' && !newLogsPanelEnabled && !logsPanelControlsEnabled && (
            <div className={styles.logOptions}>
              <InlineFieldRow>
                <InlineField
                  label={t('explore.unthemed-logs.label-time', 'Time')}
                  className={styles.horizontalInlineLabel}
                  transparent
                >
                  <InlineSwitch
                    value={showTime}
                    onChange={onChangeShowTime}
                    className={styles.horizontalInlineSwitch}
                    transparent
                    id={`show-time_${exploreId}`}
                  />
                </InlineField>
                <InlineField
                  label={t('explore.unthemed-logs.label-unique-labels', 'Unique labels')}
                  className={styles.horizontalInlineLabel}
                  transparent
                >
                  <InlineSwitch
                    value={showLabels}
                    onChange={onChangeLabels}
                    className={styles.horizontalInlineSwitch}
                    transparent
                    id={`unique-labels_${exploreId}`}
                  />
                </InlineField>
                <InlineField
                  label={t('explore.unthemed-logs.label-wrap-lines', 'Wrap lines')}
                  className={styles.horizontalInlineLabel}
                  transparent
                >
                  <InlineSwitch
                    value={wrapLogMessage}
                    onChange={onChangeWrapLogMessage}
                    className={styles.horizontalInlineSwitch}
                    transparent
                    id={`wrap-lines_${exploreId}`}
                  />
                </InlineField>
                <InlineField
                  label={t('explore.unthemed-logs.label-prettify-json', 'Prettify JSON')}
                  className={styles.horizontalInlineLabel}
                  transparent
                >
                  <InlineSwitch
                    value={prettifyLogMessage}
                    onChange={onChangePrettifyLogMessage}
                    className={styles.horizontalInlineSwitch}
                    transparent
                    id={`prettify_${exploreId}`}
                  />
                </InlineField>
                <InlineField
                  label={t('explore.unthemed-logs.label-deduplication', 'Deduplication')}
                  className={styles.horizontalInlineLabel}
                  transparent
                >
                  <RadioButtonGroup
                    options={DEDUP_OPTIONS.map((dedupType) => ({
                      label: capitalize(dedupType),
                      value: dedupType,
                      description: LogsDedupDescription[dedupType],
                    }))}
                    value={dedupStrategy}
                    onChange={onChangeDedup}
                    className={styles.radioButtons}
                  />
                </InlineField>
              </InlineFieldRow>

              <div>
                <InlineField
                  label={t('explore.unthemed-logs.label-display-results', 'Display results')}
                  className={styles.horizontalInlineLabel}
                  transparent
                  disabled={isFlipping || loading}
                >
                  <RadioButtonGroup
                    options={[
                      {
                        label: t('explore.unthemed-logs.label.newest-first', 'Newest first'),
                        value: LogsSortOrder.Descending,
                        description: t(
                          'explore.unthemed-logs.description.show-results-newest-to-oldest',
                          'Show results newest to oldest'
                        ),
                      },
                      {
                        label: t('explore.unthemed-logs.label.oldest-first', 'Oldest first'),
                        value: LogsSortOrder.Ascending,
                        description: t(
                          'explore.unthemed-logs.description.show-results-oldest-to-newest',
                          'Show results oldest to newest'
                        ),
                      },
                    ]}
                    value={logsSortOrder}
                    onChange={onChangeLogsSortOrder}
                    className={styles.radioButtons}
                  />
                </InlineField>
              </div>
            </div>
          )}
          <div ref={topLogsRef} />
          <LogsMetaRow
            logRows={logRows}
            meta={logsMeta || []}
            dedupStrategy={dedupStrategy}
            dedupCount={dedupCount}
            displayedFields={displayedFields}
            clearDisplayedFields={clearDisplayedFields}
            defaultDisplayedFields={defaultDisplayedFields}
            visualisationType={visualisationType}
          />
        </div>
        <div className={cx(styles.logsSection, visualisationType === 'table' ? styles.logsTable : undefined)}>
          {!enableNewLogsTable && !logsPanelControlsEnabled && visualisationType === 'table' && hasData && (
            <div className={styles.logRows} data-testid="logRowsTable">
              <LogsTableWrap
                logsSortOrder={logsSortOrder}
                range={props.range}
                splitOpen={splitOpen}
                timeZone={timeZone}
                // Width should be full width minus logs navigation and padding
                width={width - 80}
                logsFrames={props.logsFrames ?? []}
                onClickFilterLabel={onClickFilterLabel}
                onClickFilterOutLabel={onClickFilterOutLabel}
                panelState={panelState?.logs}
                updatePanelState={updatePanelState}
                datasourceType={props.datasourceType}
                displayedFields={displayedFields}
                exploreId={props.exploreId}
                absoluteRange={props.absoluteRange}
                logRows={props.logRows}
              />
            </div>
          )}

          {enableNewLogsTable && visualisationType === 'table' && hasData && (
            <ExploreLogsTable
              eventBus={eventBus}
              data={panelData}
              timeZone={timeZone}
              buildLinkToLogLine={onTablePermalinkClick}
              externalOptions={{
                sortOrder: logsSortOrder,
                sortBy: tableSortBy,
                displayedFields: displayedFields,
                permalinkedLogId: props.panelState?.logs?.id,
                frameIndex: tableFrameIndex,
                fieldSelectorWidth: fieldSelectorWidth,
              }}
              width={width}
              height={tableHeight}
              onClickFilterLabel={onClickFilterLabel}
              onClickFilterOutLabel={onClickFilterOutLabel}
              onOptionsChange={(options: Options) => {
                if (options.displayedFields && !shallowCompare(options.displayedFields, displayedFields)) {
                  setDisplayedFields(options.displayedFields);
                }
                if (options.sortOrder && options.sortOrder !== logsSortOrder) {
                  onChangeLogsSortOrder(options.sortOrder);
                  onLogOptionsChange('sortOrder', options.sortOrder);
                }
                if (options.fieldSelectorWidth !== undefined && options.fieldSelectorWidth !== fieldSelectorWidth) {
                  store.set(LOGS_TABLE_SETTING_KEYS.fieldSelectorWidth, options.fieldSelectorWidth);
                  setFieldSelectorWidth(options.fieldSelectorWidth);
                }
                if (
                  options.sortBy &&
                  !compareArrayValues(
                    options?.sortBy ?? [],
                    tableSortBy ?? [],
                    (a, b) => a.displayName === b.displayName && a.desc === b.desc
                  )
                ) {
                  handleSetTableSortBy(options.sortBy);
                }

                if (options.frameIndex !== tableFrameIndex) {
                  const refId = props?.logsFrames?.[options.frameIndex]?.refId;
                  if (refId) {
                    updatePanelState({ refId });
                  }
                }
              }}
              onFieldConfigChange={function (config: FieldConfigSource): void {
                // @todo save field overrides somewhere (e.g. column widths)
              }}
              onChangeTimeRange={onChangeTime}
            />
          )}

          {(!newLogsPanelEnabled || (visualisationType === 'table' && !enableNewLogsTable)) &&
            logsPanelControlsEnabled &&
            (visualisationType === 'logs' || !enableNewLogsTable) &&
            hasData && (
              <div className={styles.controlledLogRowsWrapper} data-testid="logRows">
                <ControlledLogRows
                  ref={logsContainerRef}
                  logsTableFrames={props.logsFrames}
                  width={width}
                  updatePanelState={updatePanelState}
                  panelState={panelState?.logs}
                  datasourceType={props.datasourceType}
                  splitOpen={splitOpen}
                  visualisationType={visualisationType}
                  loading={loading}
                  loadMoreLogs={infiniteScrollAvailable ? loadMoreLogs : undefined}
                  range={props.range}
                  pinnedLogs={pinnedLogs}
                  logRows={logRows}
                  deduplicatedRows={dedupedRows}
                  dedupStrategy={dedupStrategy}
                  onClickFilterLabel={onClickFilterLabel}
                  onClickFilterOutLabel={onClickFilterOutLabel}
                  showContextToggle={showContextToggle}
                  getRowContextQuery={getRowContextQuery}
                  showLabels={showLabels}
                  showTime={showTime}
                  enableLogDetails={true}
                  wrapLogMessage={wrapLogMessage}
                  prettifyLogMessage={prettifyLogMessage}
                  timeZone={timeZone}
                  getFieldLinks={getFieldLinks}
                  logsSortOrder={logsSortOrder}
                  displayedFields={displayedFields}
                  onClickShowField={showField}
                  onClickHideField={hideField}
                  app={CoreApp.Explore}
                  onLogRowHover={onLogRowHover}
                  onOpenContext={onOpenContext}
                  onPermalinkClick={onPermalinkClick}
                  permalinkedRowId={panelState?.logs?.id}
                  isFilterLabelActive={props.isFilterLabelActive}
                  onClickFilterString={props.onClickFilterString}
                  onClickFilterOutString={props.onClickFilterOutString}
                  onUnpinLine={onPinToContentOutlineClick}
                  onPinLine={onPinToContentOutlineClick}
                  pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
                  logsMeta={logsMeta}
                  logOptionsStorageKey={SETTING_KEY_ROOT}
                  onLogOptionsChange={onLogOptionsChange}
                  filterLevels={filterLevels}
                  timeRange={props.range}
                  exploreId={props.exploreId}
                  absoluteRange={props.absoluteRange}
                />
              </div>
            )}
          {!logsPanelControlsEnabled && !newLogsPanelEnabled && visualisationType === 'logs' && hasData && (
            <>
              <div className={styles.scrollableLogRows} data-testid="logRows" ref={logsContainerRef}>
                <InfiniteScroll
                  loading={loading}
                  loadMoreLogs={infiniteScrollAvailable ? loadMoreLogs : undefined}
                  range={props.range}
                  timeZone={timeZone}
                  rows={logRows}
                  scrollElement={logsContainerRef.current}
                  sortOrder={logsSortOrder}
                  app={CoreApp.Explore}
                >
                  <LogRows
                    pinnedLogs={pinnedLogs}
                    logRows={logRows}
                    deduplicatedRows={dedupedRows}
                    dedupStrategy={dedupStrategy}
                    onClickFilterLabel={onClickFilterLabel}
                    onClickFilterOutLabel={onClickFilterOutLabel}
                    showContextToggle={showContextToggle}
                    getRowContextQuery={getRowContextQuery}
                    showLabels={showLabels}
                    showTime={showTime}
                    enableLogDetails={true}
                    wrapLogMessage={wrapLogMessage}
                    prettifyLogMessage={prettifyLogMessage}
                    timeZone={timeZone}
                    getFieldLinks={getFieldLinks}
                    logsSortOrder={logsSortOrder}
                    displayedFields={displayedFields}
                    onClickShowField={showField}
                    onClickHideField={hideField}
                    app={CoreApp.Explore}
                    onLogRowHover={onLogRowHover}
                    onOpenContext={onOpenContext}
                    onPermalinkClick={onPermalinkClick}
                    permalinkedRowId={panelState?.logs?.id}
                    scrollIntoView={scrollIntoView}
                    isFilterLabelActive={props.isFilterLabelActive}
                    scrollElement={logsContainerRef.current}
                    onClickFilterString={props.onClickFilterString}
                    onClickFilterOutString={props.onClickFilterOutString}
                    onUnpinLine={onPinToContentOutlineClick}
                    onPinLine={onPinToContentOutlineClick}
                    pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
                    renderPreview
                    timeRange={props.range}
                  />
                </InfiniteScroll>
              </div>
              <LogsNavigation logsSortOrder={logsSortOrder} scrollToTopLogs={scrollToTopLogs} />
            </>
          )}
          {newLogsPanelEnabled && visualisationType === 'logs' && (
            <div data-testid="logRows" ref={logsContainerRef} className={styles.logRowsWrapper}>
              {logsContainerRef.current && hasData && (
                <LogList
                  app={CoreApp.Explore}
                  containerElement={logsContainerRef.current}
                  enableLogDetails={true}
                  dataFrames={props.logsFrames ?? []}
                  dedupStrategy={dedupStrategy}
                  displayedFields={displayedFields}
                  filterLevels={filterLevels}
                  getFieldLinks={getFieldLinks}
                  getRowContextQuery={getRowContextQuery}
                  isLabelFilterActive={props.isFilterLabelActive}
                  loading={loading}
                  loadMore={loadMoreLogs}
                  logOptionsStorageKey={SETTING_KEY_ROOT}
                  logs={dedupedRows}
                  logsMeta={logsMeta}
                  logSupportsContext={showContextToggle}
                  onClickShowField={showField}
                  onClickHideField={hideField}
                  onClickFilterLabel={onClickFilterLabel}
                  onClickFilterOutLabel={onClickFilterOutLabel}
                  onClickFilterString={props.onClickFilterString}
                  onClickFilterOutString={props.onClickFilterOutString}
                  onLogOptionsChange={onLogOptionsChange}
                  onLogLineHover={onLogRowHover}
                  onOpenContext={onOpenContext}
                  onPermalinkClick={onPermalinkClick}
                  onPinLine={onPinToContentOutlineClick}
                  onUnpinLine={onPinToContentOutlineClick}
                  permalinkedLogId={panelState?.logs?.id}
                  pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
                  pinnedLogs={pinnedLogs}
                  setDisplayedFields={setDisplayedFields}
                  showControls
                  showFieldSelector
                  showTime={showTime}
                  sortOrder={logsSortOrder}
                  timeRange={props.range}
                  timeZone={timeZone}
                  wrapLogMessage={wrapLogMessage}
                />
              )}
            </div>
          )}
        </div>
        {!loading && !hasData && !scanning && (
          <div className={styles.noDataWrapper}>
            <div className={styles.noData}>
              <Trans i18nKey="explore.logs.no-logs-found">No logs found.</Trans>
              <Button size="sm" variant="secondary" className={styles.scanButton} onClick={onClickScan}>
                <Trans i18nKey="explore.logs.scan-for-older-logs">Scan for older logs</Trans>
              </Button>
            </div>
          </div>
        )}
        {scanning && (
          <div className={styles.noDataWrapper}>
            <div className={styles.noData}>
              <span>{scanText}</span>
              <Button size="sm" variant="secondary" className={styles.scanButton} onClick={onClickStopScan}>
                <Trans i18nKey="explore.logs.stop-scan">Stop scan</Trans>
              </Button>
            </div>
          </div>
        )}
      </PanelChrome>
    </>
  );
};

export const Logs = withTheme2(UnthemedLogs);

const getStyles = (theme: GrafanaTheme2, wrapLogMessage: boolean, tableHeight: number) => {
  return {
    noDataWrapper: css({
      display: 'flex',
      justifyContent: 'center',
      width: '100%',
      paddingBottom: theme.spacing(2),
    }),
    noData: css({
      display: 'inline-block',
    }),
    scanButton: css({
      marginLeft: theme.spacing(1),
    }),
    logOptions: css({
      display: 'flex',
      justifyContent: 'space-between',
      alignItems: 'baseline',
      flexWrap: 'wrap',
      backgroundColor: theme.colors.background.primary,
      padding: `${theme.spacing(1)} ${theme.spacing(2)}`,
      borderRadius: theme.shape.radius.default,
      margin: `${theme.spacing(0, 0, 1)}`,
      border: `1px solid ${theme.colors.border.medium}`,
    }),
    headerButton: css({
      margin: `${theme.spacing(0.5, 0, 0, 1)}`,
    }),
    horizontalInlineLabel: css({
      '& > label': {
        marginRight: '0',
      },
    }),
    horizontalInlineSwitch: css({
      padding: `0 ${theme.spacing(1)} 0 0`,
    }),
    radioButtons: css({
      margin: '0',
    }),
    logsSection: css({
      display: 'flex',
      flexDirection: 'row',
      justifyContent: 'space-between',
      position: 'relative',
    }),
    logsTable: css({
      maxHeight: `${tableHeight}px`,
    }),
    scrollableLogRows: css({
      overflowY: 'scroll',
      width: '100%',
      maxHeight: '80vh',
    }),
    logRows: css({
      overflowX: `${wrapLogMessage ? 'unset' : 'scroll'}`,
      overflowY: 'visible',
      width: '100%',
    }),
    controlledLogRowsWrapper: css({
      width: '100%',
      maxHeight: '80vh',
    }),
    logRowsWrapper: css({
      width: '100%',
    }),
    visualisationType: css({
      display: 'flex',
      flex: '1',
      justifyContent: 'flex-end',
    }),
    visualisationTypeRadio: css({
      margin: `0 0 0 ${theme.spacing(1)}`,
    }),
    stickyNavigation: css({
      overflow: 'visible',
      marginBottom: '0px',
    }),
    logsVolumePanel: css({
      marginBottom: theme.spacing(1.5),
    }),
  };
};

const dedupRows = (logRows: LogRowModel[], dedupStrategy: LogsDedupStrategy) => {
  const dedupedRows = dedupLogRows(logRows, dedupStrategy);
  const dedupCount = dedupedRows.reduce((sum, row) => (row.duplicates ? sum + row.duplicates : sum), 0);
  return { dedupedRows, dedupCount };
};
