import { css } from '@emotion/css';
import { memo, useState } from 'react';
import { connect, type ConnectedProps } from 'react-redux';

import {
  applyFieldOverrides,
  type SplitOpen,
  type DataFrame,
  LoadingState,
  FieldType,
  DataLinksContext,
  type EventBus,
  EventBusSrv,
} from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { config, getTemplateSrv, PanelRenderer } from '@grafana/runtime';
import { type TimeZone } from '@grafana/schema';
import { type AdHocFilterItem, PanelChrome, useTheme2, PanelContextProvider } from '@grafana/ui';
import { TEMPO_STREAMING_PROGRESS_REF_ID } from 'app/plugins/datasource/tempo/streaming';
import {
  hasDeprecatedParentRowIndex,
  migrateFromParentRowIndexToNestedFrames,
} from 'app/plugins/panel/table/migrations';
import { type ExploreItemState } from 'app/types/explore';
import { type StoreState } from 'app/types/store';

import { LimitedDataDisclaimer } from '../LimitedDataDisclaimer';
import { MetaInfoText } from '../MetaInfoText';
import { selectIsWaitingForData } from '../state/query';
import { exploreDataLinkPostProcessorFactory } from '../utils/links';

const MAX_NUMBER_OF_COLUMNS = 20;

interface TableContainerProps {
  exploreId: string;
  width: number;
  timeZone: TimeZone;
  onCellFilterAdded?: (filter: AdHocFilterItem) => void;
  splitOpenFn: SplitOpen;
  eventBus?: EventBus;
  ariaLabel?: string;
}

function mapStateToProps(state: StoreState, { exploreId }: TableContainerProps) {
  const explore = state.explore;
  const item: ExploreItemState = explore.panes[exploreId]!;
  const { tableResult, range } = item;
  const loadingInState = selectIsWaitingForData(exploreId);
  const loading = tableResult && tableResult.length > 0 ? false : loadingInState;
  const hasTempoStreamingProgressTable = tableResult?.some((f) => f.refId === TEMPO_STREAMING_PROGRESS_REF_ID);
  return {
    loading,
    tableResult,
    range,
    queryStreaming: item.queryResponse.state === LoadingState.Streaming || Boolean(hasTempoStreamingProgressTable),
  };
}

const connector = connect(mapStateToProps, {});
type Props = TableContainerProps & ConnectedProps<typeof connector>;

export const TableContainer = memo(function TableContainer({
  loading,
  onCellFilterAdded,
  tableResult,
  width,
  splitOpenFn,
  range,
  timeZone,
  eventBus,
  queryStreaming = false,
}: Props) {
  const theme = useTheme2();
  const [showAll, setShowAll] = useState(false);

  function hasSubFrames(data: DataFrame) {
    return data.fields.some((f) => f.type === FieldType.nestedFrames);
  }

  function getTableHeight(rowCount: number, hasSubFrames: boolean, queryStreaming: boolean) {
    if (rowCount === 0) {
      return 200;
    }
    // tries to estimate table height, with a min of 300 and a max of 600
    // if there are multiple tables, there is no min
    const height = Math.min(600, Math.max(rowCount * 36, hasSubFrames ? 300 : 0) + 40 + 46);

    if (queryStreaming) {
      return height;
    }
    // esure minimum height of 300
    return Math.max(height, 300);
  }

  function getTableTitle(dataFrames: DataFrame[] | null, data: DataFrame, i: number) {
    let name = data.name;
    if (!name && (dataFrames?.length ?? 0) > 1) {
      name = data.refId || `${i}`;
    }

    return name
      ? t('explore.table.title-with-name', 'Table - {{name}}', { name, interpolation: { escapeValue: false } })
      : t('explore.table.title', 'Table');
  }

  let dataFrames = hasDeprecatedParentRowIndex(tableResult)
    ? migrateFromParentRowIndexToNestedFrames(tableResult)
    : tableResult;
  const dataLinkPostProcessor = exploreDataLinkPostProcessorFactory(splitOpenFn, range);

  let dataLimited = false;

  if (dataFrames?.length) {
    dataFrames = dataFrames.map((frame) => {
      frame.fields.forEach((field, index) => {
        const custom = field.config.custom ?? {};

        const hiddenByColumnLimit = showAll ? false : index >= MAX_NUMBER_OF_COLUMNS;
        dataLimited = dataLimited || hiddenByColumnLimit;

        const hiddenByDatasource = custom.hideFrom?.viz === true || custom.hidden === true;
        const hidden = hiddenByDatasource || hiddenByColumnLimit;

        field.config.custom = {
          ...custom,
          hidden,
          hideFrom: {
            ...custom.hideFrom,
            viz: hidden,
          },
        };
      });
      return frame;
    });

    dataFrames = applyFieldOverrides({
      data: dataFrames,
      timeZone,
      theme: config.theme2,
      replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
      fieldConfig: {
        defaults: {},
        overrides: [],
      },
      dataLinkPostProcessor,
    });
  }

  const frames = dataFrames?.filter(
    (frame: DataFrame | undefined): frame is DataFrame => !!frame && frame.length !== 0
  );

  return (
    <>
      {frames && frames.length === 0 && (
        <PanelChrome title={t('explore.table.title', 'Table')} width={width} height={200}>
          {() => <MetaInfoText metaItems={[{ value: t('explore.table.no-data', '0 series returned') }]} />}
        </PanelChrome>
      )}
      {frames && frames.length > 0 && (
        <div className={css({ display: 'flex', flexDirection: 'column', gap: theme.spacing(1) })}>
          {frames.map((data, i) => (
            <PanelChrome
              key={data.refId || `table-${i}`}
              title={getTableTitle(dataFrames, data, i)}
              titleItems={[
                !showAll && dataLimited && (
                  <LimitedDataDisclaimer
                    toggleShowAllSeries={() => setShowAll(true)}
                    info={
                      <Trans i18nKey={'table.container.show-only-series'}>
                        Showing only {{ MAX_NUMBER_OF_COLUMNS }} columns
                      </Trans>
                    }
                    tooltip={t(
                      'table.container.content',
                      'Showing too many columns in a single table may impact performance and make data harder to read. Consider refining your queries.'
                    )}
                    buttonLabel={<Trans i18nKey={'table.container.show-all-series'}>Show all columns</Trans>}
                  />
                ),
              ]}
              width={width}
              height={getTableHeight(data.length, hasSubFrames(data), queryStreaming)}
              loadingState={loading ? LoadingState.Loading : undefined}
            >
              {(innerWidth, innerHeight) => (
                <DataLinksContext.Provider value={{ dataLinkPostProcessor }}>
                  <PanelContextProvider
                    value={{
                      eventsScope: 'explore',
                      eventBus: eventBus ?? new EventBusSrv(),
                      onAddAdHocFilter: onCellFilterAdded,
                    }}
                  >
                    <PanelRenderer
                      data={{
                        series: [data],
                        state: loading ? LoadingState.Loading : LoadingState.Done,
                        timeRange: range,
                      }}
                      pluginId={'table'}
                      title=""
                      width={innerWidth}
                      height={innerHeight}
                      timeZone={timeZone}
                    />
                  </PanelContextProvider>
                </DataLinksContext.Provider>
              )}
            </PanelChrome>
          ))}
        </div>
      )}
    </>
  );
});

export default connector(TableContainer);
