/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
/* eslint camelcase: 0 */
import {
  cloneElement,
  isValidElement,
  ReactNode,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import { t } from '@apache-superset/core/translation';
import {
  ensureIsArray,
  getChartControlPanelRegistry,
  QueryFormData,
  DatasourceType,
  isDefined,
  JsonValue,
  NO_TIME_RANGE,
  usePrevious,
  isFeatureEnabled,
  FeatureFlag,
  VizType,
} from '@superset-ui/core';
import {
  styled,
  css,
  SupersetTheme,
  useTheme,
} from '@apache-superset/core/theme';
import {
  ControlPanelSectionConfig,
  ControlState,
  CustomControlItem,
  Dataset,
  ExpandedControlItem,
  isCustomControlItem,
  isTemporalColumn,
  sections,
} from '@superset-ui/chart-controls';
import { useSelector } from 'react-redux';
import { kebabCase, isEqual } from 'lodash';

import {
  Collapse,
  Loading,
  Label,
  Tooltip,
} from '@superset-ui/core/components';
import Tabs from '@superset-ui/core/components/Tabs';
import { PluginContext } from 'src/components';
import { useConfirmModal } from 'src/hooks/useConfirmModal';

import { getSectionsToRender } from 'src/explore/controlUtils';
import { ExploreActions } from 'src/explore/actions/exploreActions';
import { ChartState, ExplorePageState } from 'src/explore/types';
import { Icons } from '@superset-ui/core/components/Icons';
import ControlRow from './ControlRow';
import Control from './Control';
import { ExploreAlert } from './ExploreAlert';
import { RunQueryButton } from './RunQueryButton';
import { Operators } from '../constants';
import { Clauses } from './controls/FilterControl/types';
import StashFormDataContainer from './StashFormDataContainer';

const TABS_KEYS = {
  DATA: 'DATA',
  CUSTOMIZE: 'CUSTOMIZE',
  MATRIXIFY: 'MATRIXIFY',
};

// Table charts don't support matrixify feature
const MATRIXIFY_INCOMPATIBLE_CHARTS = new Set([
  VizType.Table,
  VizType.TableAgGrid,
  VizType.PivotTable,
  VizType.TimeTable,
  VizType.TimePivot,
]);

export type ControlPanelsContainerProps = {
  exploreState: ExplorePageState['explore'];
  // Only setControlValue is used from actions in this component
  actions: Pick<ExploreActions, 'setControlValue'>;
  datasource_type: DatasourceType;
  chart: ChartState;
  controls: Record<string, ControlState>;
  form_data: QueryFormData;
  isDatasourceMetaLoading: boolean;
  errorMessage: ReactNode;
  buttonErrorMessage?: ReactNode; // Error message for RunQueryButton (includes all errors)
  onQuery: () => void;
  onStop: () => void;
  canStopQuery: boolean;
  chartIsStale: boolean;
};

export type ExpandedControlPanelSectionConfig = Omit<
  ControlPanelSectionConfig,
  'controlSetRows'
> & {
  controlSetRows: ExpandedControlItem[][];
};

const iconStyles = css`
  &.anticon {
    font-size: unset;
    .anticon {
      line-height: unset;
      vertical-align: unset;
    }
  }
`;

const actionButtonsContainerStyles = (theme: SupersetTheme) => css`
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: ${theme.sizeUnit * 4}px;
  background: ${theme.colorBgContainer};
  flex-shrink: 0;

  & > button {
    min-width: 156px;
  }
`;

const Styles = styled.div`
  position: relative;
  height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;

  // Resizable add overflow-y: auto as a style to this div
  // To override it, we need to use !important
  overflow: visible !important;

  #controlSections {
    flex: 1;
    overflow: auto;
  }

  .tab-content {
    overflow: visible;
    flex: 1 1 100%;
  }

  // Ensure Ant Design tabs allow content to expand
  .ant-tabs-content {
    overflow: visible;
    height: auto;
  }

  .ant-tabs-content-holder {
    overflow: visible;
    height: auto;
  }

  .ant-tabs-tabpane {
    overflow: visible;
    height: auto;
  }

  // Ensure collapse components can expand
  .ant-collapse-content {
    overflow: visible;
  }

  .ant-collapse-content-box {
    overflow: visible;
  }

  .Select__menu {
    max-width: 100%;
  }
  .type-label {
    margin-right: ${({ theme }) => theme.sizeUnit * 3}px;
    width: ${({ theme }) => theme.sizeUnit * 7}px;
    display: inline-block;
    text-align: center;
    font-weight: ${({ theme }) => theme.fontWeightStrong};
  }
`;

const isTimeSection = (section: ControlPanelSectionConfig): boolean =>
  !!section.label && sections.legacyTimeseriesTime.label === section.label;

const hasTimeColumn = (datasource: Dataset): boolean =>
  datasource?.columns?.some(c => c.is_dttm);
const sectionsToExpand = (
  sections: ControlPanelSectionConfig[],
  datasource: Dataset,
): string[] =>
  // avoid expanding time section if datasource doesn't include time column
  sections.reduce(
    (acc, section) =>
      (section.expanded || !section.label) &&
      (!isTimeSection(section) || hasTimeColumn(datasource))
        ? [...acc, String(section.label)]
        : acc,
    [] as string[],
  );

function getState(
  vizType: string,
  datasource: Dataset,
  datasourceType: DatasourceType,
) {
  const querySections: ControlPanelSectionConfig[] = [];
  const customizeSections: ControlPanelSectionConfig[] = [];
  const matrixifySections: ControlPanelSectionConfig[] = [];
  let matrixifyEnableControl: ControlPanelSectionConfig | null = null;

  getSectionsToRender(vizType, datasourceType).forEach(section => {
    if (!section) return;
    if (section.tabOverride === 'matrixify') {
      // Separate the enable control from other sections
      if (section.label === t('Enable Matrixify')) {
        matrixifyEnableControl = section;
      } else {
        matrixifySections.push(section);
      }
    } else if (
      section.tabOverride === 'data' ||
      section.controlSetRows.some(rows =>
        rows.some(
          control =>
            control &&
            typeof control === 'object' &&
            'config' in control &&
            control.config &&
            (!control.config.renderTrigger ||
              control.config.tabOverride === 'data'),
        ),
      )
    ) {
      querySections.push(section);
    } else if (section.controlSetRows && section.controlSetRows.length > 0) {
      customizeSections.push(section);
    }
  });

  const expandedQuerySections: string[] = sectionsToExpand(
    querySections,
    datasource,
  );
  const expandedCustomizeSections: string[] = sectionsToExpand(
    customizeSections,
    datasource,
  );
  const expandedMatrixifySections: string[] = sectionsToExpand(
    matrixifySections,
    datasource,
  );

  return {
    expandedQuerySections,
    expandedCustomizeSections,
    expandedMatrixifySections,
    querySections,
    customizeSections,
    matrixifySections,
    matrixifyEnableControl,
  };
}

function useResetOnChangeRef(initialValue: () => any, resetOnChangeValue: any) {
  const value = useRef(initialValue());
  const prevResetOnChangeValue = useRef(resetOnChangeValue);
  if (prevResetOnChangeValue.current !== resetOnChangeValue) {
    value.current = initialValue();
    prevResetOnChangeValue.current = resetOnChangeValue;
  }

  return value;
}

export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => {
  const theme = useTheme();
  const pluginContext = useContext(PluginContext);
  const { showConfirm, ConfirmModal } = useConfirmModal();

  const prevState = usePrevious(props.exploreState);
  const prevDatasource = usePrevious(props.exploreState.datasource);
  const prevChartStatus = usePrevious(props.chart.chartStatus);

  const [showDatasourceAlert, setShowDatasourceAlert] = useState(false);
  const [activeTabKey, setActiveTabKey] = useState<string>(TABS_KEYS.DATA);

  const containerRef = useRef<HTMLDivElement>(null);

  const controlsTransferred = useSelector<
    ExplorePageState,
    string[] | undefined
  >(state => state.explore.controlsTransferred);

  const defaultTimeFilter = useSelector<ExplorePageState>(
    state => state.common?.conf?.DEFAULT_TIME_FILTER || NO_TIME_RANGE,
  );

  const { form_data, actions } = props;
  const { setControlValue } = actions;
  const { x_axis, adhoc_filters } = form_data;

  const previousXAxis = usePrevious(x_axis);

  useEffect(() => {
    if (
      x_axis &&
      x_axis !== previousXAxis &&
      isTemporalColumn(x_axis, props.exploreState.datasource)
    ) {
      const noFilter = !adhoc_filters?.find(
        filter =>
          filter.expressionType === 'SIMPLE' &&
          filter.operator === Operators.TemporalRange &&
          filter.subject === x_axis,
      );
      if (noFilter) {
        showConfirm({
          title: t('The X-axis is not on the filters list'),
          body: t(
            `The X-axis is not on the filters list which will prevent it from being used in time range filters in dashboards. Would you like to add it to the filters list?`,
          ),
          onConfirm: () => {
            setControlValue('adhoc_filters', [
              ...(adhoc_filters || []),
              {
                clause: Clauses.Where,
                subject: x_axis,
                operator: Operators.TemporalRange,
                comparator: defaultTimeFilter,
                expressionType: 'SIMPLE',
              },
            ]);
          },
          confirmText: t('Yes'),
          cancelText: t('No'),
        });
      }
    }
  }, [
    x_axis,
    adhoc_filters,
    setControlValue,
    defaultTimeFilter,
    previousXAxis,
    props.exploreState.datasource,
    showConfirm,
  ]);

  useEffect(() => {
    let shouldUpdateControls = false;
    const removeDatasourceWarningFromControl = (
      value: JsonValue | undefined,
    ) => {
      if (
        typeof value === 'object' &&
        isDefined(value) &&
        'datasourceWarning' in value &&
        value.datasourceWarning === true
      ) {
        shouldUpdateControls = true;
        return { ...value, datasourceWarning: false };
      }
      return value;
    };
    if (
      props.chart.chartStatus === 'success' &&
      prevChartStatus !== 'success'
    ) {
      controlsTransferred?.forEach(controlName => {
        shouldUpdateControls = false;
        if (!isDefined(props.controls[controlName])) {
          return;
        }
        const alteredControls = Array.isArray(props.controls[controlName].value)
          ? ensureIsArray(props.controls[controlName].value)?.map(
              removeDatasourceWarningFromControl,
            )
          : removeDatasourceWarningFromControl(
              props.controls[controlName].value,
            );
        if (shouldUpdateControls) {
          props.actions.setControlValue(controlName, alteredControls);
        }
      });
    }
  }, [
    controlsTransferred,
    prevChartStatus,
    props.actions,
    props.chart.chartStatus,
    props.controls,
  ]);

  useEffect(() => {
    if (
      prevDatasource &&
      prevDatasource.type !== DatasourceType.Query &&
      (props.exploreState.datasource?.id !== prevDatasource.id ||
        props.exploreState.datasource?.type !== prevDatasource.type)
    ) {
      setShowDatasourceAlert(true);
      containerRef.current?.scrollTo(0, 0);
    }
  }, [
    props.exploreState.datasource?.id,
    props.exploreState.datasource?.type,
    prevDatasource,
  ]);

  const {
    expandedQuerySections,
    expandedCustomizeSections,
    expandedMatrixifySections,
    querySections,
    customizeSections,
    matrixifySections,
    matrixifyEnableControl,
  } = useMemo(
    () =>
      getState(
        form_data.viz_type,
        props.exploreState.datasource,
        props.datasource_type,
      ),
    [props.exploreState.datasource, form_data.viz_type, props.datasource_type],
  );

  const resetTransferredControls = useCallback(() => {
    ensureIsArray(props.exploreState.controlsTransferred).forEach(controlName =>
      props.actions.setControlValue(
        controlName,
        props.controls[controlName].default,
      ),
    );
  }, [props.actions, props.exploreState.controlsTransferred, props.controls]);

  const handleClearFormClick = useCallback(() => {
    resetTransferredControls();
    setShowDatasourceAlert(false);
  }, [resetTransferredControls]);

  const handleContinueClick = useCallback(() => {
    setShowDatasourceAlert(false);
  }, []);

  const shouldRecalculateControlState = ({
    name,
    config,
  }: CustomControlItem): boolean => {
    const { controls, chart, exploreState } = props;

    return Boolean(
      config.shouldMapStateToProps?.(
        prevState || exploreState,
        exploreState,
        controls[name],
        chart,
      ),
    );
  };

  const renderControl = ({ name, config }: CustomControlItem) => {
    const { controls, chart, exploreState } = props;
    const { visibility, hidden, disableStash, ...restConfig } = config;

    // If the control item is not an object, we have to look up the control data from
    // the centralized controls file.
    // When it is an object we read control data straight from `config` instead
    const controlData = {
      ...restConfig,
      ...controls[name],
      ...(shouldRecalculateControlState({ name, config })
        ? config?.mapStateToProps?.(exploreState, controls[name], chart)
        : // for other controls, `mapStateToProps` is already run in
          // controlUtils/getControlState.ts
          undefined),
      name,
    };
    const {
      validationErrors,
      label: baseLabel,
      description: baseDescription,
      ...restProps
    } = controlData as ControlState & {
      validationErrors?: any[];
    };

    const isVisible = visibility
      ? visibility.call(config, props, controlData)
      : undefined;

    const isHidden =
      typeof hidden === 'function'
        ? hidden.call(config, props, controlData)
        : hidden;

    const label =
      typeof baseLabel === 'function'
        ? baseLabel(exploreState, controls[name], chart)
        : baseLabel;

    const description =
      typeof baseDescription === 'function'
        ? baseDescription(exploreState, controls[name], chart)
        : baseDescription;

    if (name.includes('adhoc_filters')) {
      restProps.canDelete = (
        valueToBeDeleted: Record<string, any>,
        values: Record<string, any>[],
      ) => {
        const isTemporalRange = (filter: Record<string, any>) =>
          filter.operator === Operators.TemporalRange;
        if (!controls?.time_range?.value && isTemporalRange(valueToBeDeleted)) {
          const count = values.filter(isTemporalRange).length;
          if (count === 1) {
            // if temporal filter's value is "No filter", prevent deletion
            // otherwise reset the value to "No filter"
            if (valueToBeDeleted.comparator === defaultTimeFilter) {
              return t(
                `You cannot delete the last temporal filter as it's used for time range filters in dashboards.`,
              );
            }
            props.actions.setControlValue(
              name,
              values.map(val => {
                if (isEqual(val, valueToBeDeleted)) {
                  return {
                    ...val,
                    comparator: defaultTimeFilter,
                  };
                }
                return val;
              }),
            );
            return false;
          }
        }
        return true;
      };
    }

    return (
      <StashFormDataContainer
        shouldStash={isVisible === false && disableStash !== true}
        fieldNames={[name]}
        key={`control-container-${name}`}
      >
        <Control
          key={`control-${name}`}
          name={name}
          label={label}
          description={description}
          validationErrors={validationErrors}
          actions={props.actions}
          isVisible={isVisible}
          hidden={isHidden}
          {...restProps}
        />
      </StashFormDataContainer>
    );
  };

  const sectionHasHadNoErrors = useResetOnChangeRef(
    () => ({}),
    form_data.viz_type,
  );

  const renderControlPanelSection = (
    section: ExpandedControlPanelSectionConfig,
  ) => {
    const { controls, chart, exploreState, form_data, actions } = props;
    const { label, description, visibility } = section;

    // Section label can be a ReactNode but in some places we want to
    // have a string ID. Using forced type conversion for now,
    // should probably add a `id` field to sections in the future.
    const sectionId = String(label);
    const isVisible = visibility?.call(this, props, controls) !== false;
    const hasErrors = section.controlSetRows.some(rows =>
      rows.some(item => {
        const controlName =
          typeof item === 'string'
            ? item
            : item && 'name' in item
              ? item.name
              : null;
        return (
          controlName &&
          controlName in controls &&
          controls[controlName].validationErrors &&
          controls[controlName].validationErrors.length > 0
        );
      }),
    );

    if (!hasErrors) {
      sectionHasHadNoErrors.current[sectionId] = true;
    }

    const PanelHeader = () => (
      <span data-test="collapsible-control-panel-header">
        <span
          css={(theme: SupersetTheme) => css`
            font-size: ${theme.fontSize}px;
            line-height: 1.3;
          `}
        >
          {label}
        </span>{' '}
        {description && (
          <Tooltip id={sectionId} title={description}>
            <Icons.InfoCircleOutlined css={iconStyles} />
          </Tooltip>
        )}
        {hasErrors && (
          <Tooltip
            id={`${kebabCase('validation-errors')}-tooltip`}
            title={t('This section contains validation errors')}
          >
            <Icons.ExclamationCircleOutlined iconColor={theme.colorError} />
          </Tooltip>
        )}
      </span>
    );

    const PanelChildren = (
      <>
        <StashFormDataContainer
          key={`sectionId-${sectionId}`}
          shouldStash={!isVisible}
          fieldNames={section.controlSetRows
            .flat()
            .map(item =>
              item && typeof item === 'object'
                ? 'name' in item
                  ? item.name
                  : ''
                : String(item || ''),
            )
            .filter(Boolean)}
        />
        {isVisible && (
          <>
            {section.controlSetRows.map((controlSets, i) => {
              const renderedControls = controlSets
                .map(controlItem => {
                  if (!controlItem) {
                    // When the item is invalid
                    return null;
                  }
                  if (isValidElement(controlItem)) {
                    // When the item is a React element
                    const element = controlItem as React.ReactElement<
                      Record<string, unknown>
                    >;

                    const controlName = (element.props as { name: string })
                      .name;
                    if (!controlName) {
                      return element;
                    }
                    const controlState = controls[controlName];

                    return cloneElement(element, {
                      ...(element.props as Record<string, unknown>),
                      actions,
                      controls,
                      chart,
                      exploreState,
                      form_data,
                      ...(controlState && {
                        value: controlState.value,
                        validationErrors: controlState.validationErrors,
                        default: controlState.default,
                        onChange: (value: unknown, errors: unknown[]) =>
                          setControlValue(controlName, value, errors),
                      }),
                    });
                  }
                  if (
                    isCustomControlItem(controlItem) &&
                    controlItem.name !== 'datasource'
                  ) {
                    return renderControl(controlItem);
                  }
                  return null;
                })
                .filter(x => x !== null);
              // don't show the row if it is empty
              if (renderedControls.length === 0) {
                return null;
              }
              return (
                <ControlRow
                  key={`controlsetrow-${i}`}
                  controls={renderedControls}
                />
              );
            })}
          </>
        )}
      </>
    );

    return {
      key: String(section.label),
      label: <PanelHeader />,
      children: PanelChildren,
      className: section.label ? '' : 'hidden-collapse-header',
      style: { display: isVisible ? 'block' : 'none' },
    };
  };

  const hasControlsTransferred =
    ensureIsArray(props.exploreState.controlsTransferred).length > 0;

  const DatasourceAlert = useCallback(
    () =>
      hasControlsTransferred ? (
        <ExploreAlert
          title={t('Keep control settings?')}
          bodyText={t(
            "You've changed datasets. Any controls with data (columns, metrics) that match this new dataset have been retained.",
          )}
          primaryButtonAction={handleContinueClick}
          secondaryButtonAction={handleClearFormClick}
          primaryButtonText={t('Continue')}
          secondaryButtonText={t('Clear form')}
          type="info"
        />
      ) : (
        <ExploreAlert
          title={t('No form settings were maintained')}
          bodyText={t(
            'We were unable to carry over any controls when switching to this new dataset.',
          )}
          primaryButtonAction={handleContinueClick}
          primaryButtonText={t('Continue')}
          type="warning"
        />
      ),
    [handleClearFormClick, handleContinueClick, hasControlsTransferred],
  );

  const dataTabHasHadNoErrors = useResetOnChangeRef(
    () => false,
    form_data.viz_type,
  );

  const dataTabTitle = useMemo(() => {
    if (!props.errorMessage) {
      dataTabHasHadNoErrors.current = true;
    }

    return (
      <>
        <span>{t('Data')}</span>
        {props.errorMessage && (
          <span
            css={(theme: SupersetTheme) => css`
              margin-left: ${theme.sizeUnit * 2}px;
            `}
          >
            {' '}
            <Tooltip
              id="query-error-tooltip"
              placement="right"
              title={props.errorMessage}
            >
              <Icons.ExclamationCircleOutlined
                data-test="query-error-tooltip-trigger"
                iconColor={theme.colorError}
                iconSize="s"
              />
            </Tooltip>
          </span>
        )}
      </>
    );
  }, [
    theme.colorErrorText,
    theme.colorWarningText,
    dataTabHasHadNoErrors,
    props.errorMessage,
  ]);

  const showCustomizeTab = customizeSections.length > 0;
  const showMatrixifyTab =
    isFeatureEnabled(FeatureFlag.Matrixify) &&
    !MATRIXIFY_INCOMPATIBLE_CHARTS.has(form_data.viz_type as VizType);

  // Check if matrixify is enabled in form_data
  const matrixifyIsEnabled =
    form_data.matrixify_enable === true &&
    ((form_data.matrixify_mode_rows !== undefined &&
      form_data.matrixify_mode_rows !== 'disabled') ||
      (form_data.matrixify_mode_columns !== undefined &&
        form_data.matrixify_mode_columns !== 'disabled'));

  // Auto-switch to Matrixify tab when it's enabled
  useEffect(() => {
    if (showMatrixifyTab && matrixifyIsEnabled) {
      setActiveTabKey(TABS_KEYS.MATRIXIFY);
    }
  }, [showMatrixifyTab, matrixifyIsEnabled]);

  // Check if matrixify sections have validation errors
  const matrixifyHasErrors = useMemo(() => {
    if (!showMatrixifyTab) return false;

    return matrixifySections.some(section =>
      section.controlSetRows.some(rows =>
        rows.some(item => {
          const controlName =
            typeof item === 'string'
              ? item
              : item && 'name' in item
                ? item.name
                : null;
          return (
            controlName &&
            controlName in props.controls &&
            props.controls[controlName].validationErrors &&
            props.controls[controlName].validationErrors.length > 0
          );
        }),
      ),
    );
  }, [showMatrixifyTab, matrixifySections, props.controls]);

  // Create Matrixify tab label with Beta tag and validation errors
  const matrixifyTabLabel = useMemo(
    () => (
      <>
        <span>{t('Matrixify')}</span>
        {matrixifyHasErrors && (
          <span
            css={(theme: SupersetTheme) => css`
              margin-left: ${theme.sizeUnit * 2}px;
            `}
          >
            {' '}
            <Tooltip
              id="matrixify-validation-error-tooltip"
              placement="right"
              title={t('This section contains validation errors')}
            >
              <Icons.ExclamationCircleOutlined
                data-test="matrixify-validation-error-tooltip-trigger"
                iconColor={theme.colorError}
                iconSize="s"
              />
            </Tooltip>
          </span>
        )}{' '}
        <Tooltip
          title={t(
            'This feature is experimental and may change or have limitations',
          )}
          placement="top"
        >
          <Label
            type="info"
            css={css`
              margin-left: ${theme.sizeUnit}px;
              font-size: ${theme.fontSizeSM}px;
            `}
          >
            {t('beta')}
          </Label>
        </Tooltip>
      </>
    ),
    [
      matrixifyHasErrors,
      theme.colorErrorText,
      theme.sizeUnit,
      theme.fontSizeSM,
    ],
  );

  const controlPanelRegistry = getChartControlPanelRegistry();
  if (!controlPanelRegistry.has(form_data.viz_type) && pluginContext.loading) {
    return <Loading />;
  }

  return (
    <>
      <Styles ref={containerRef}>
        <Tabs
          id="controlSections"
          data-test="control-tabs"
          allowOverflow={false}
          activeKey={activeTabKey}
          onChange={(key: string) => setActiveTabKey(key)}
          items={[
            {
              key: TABS_KEYS.DATA,
              label: dataTabTitle,
              children: (
                <>
                  {showDatasourceAlert && <DatasourceAlert />}
                  <Collapse
                    defaultActiveKey={expandedQuerySections}
                    expandIconPosition="end"
                    ghost
                    bordered
                    items={querySections.map(renderControlPanelSection)}
                  />
                </>
              ),
            },
            ...(showCustomizeTab
              ? [
                  {
                    key: TABS_KEYS.CUSTOMIZE,
                    label: t('Customize'),
                    children: (
                      <Collapse
                        defaultActiveKey={expandedCustomizeSections}
                        expandIconPosition="end"
                        ghost
                        bordered
                        items={customizeSections.map(renderControlPanelSection)}
                      />
                    ),
                  },
                ]
              : []),
            ...(showMatrixifyTab
              ? [
                  {
                    key: TABS_KEYS.MATRIXIFY,
                    label: matrixifyTabLabel,
                    children: (
                      <>
                        {/* Render Enable Matrixify control outside collapsible sections */}
                        {matrixifyEnableControl &&
                          (
                            matrixifyEnableControl as ControlPanelSectionConfig
                          ).controlSetRows.map(
                            (controlSetRow: CustomControlItem[], i: number) => (
                              <div
                                key={`matrixify-enable-${i}`}
                                css={css`
                                  padding: ${theme.sizeUnit * 4}px;
                                  border-bottom: 1px solid ${theme.colorBorder};
                                `}
                              >
                                {controlSetRow.map(
                                  (control: CustomControlItem, j: number) => {
                                    if (
                                      !control ||
                                      typeof control === 'string'
                                    ) {
                                      return null;
                                    }
                                    return (
                                      <div key={`control-${i}-${j}`}>
                                        {renderControl(control)}
                                      </div>
                                    );
                                  },
                                )}
                              </div>
                            ),
                          )}
                        <Collapse
                          defaultActiveKey={expandedMatrixifySections}
                          expandIconPosition="end"
                          ghost
                          bordered
                          items={matrixifySections.map(
                            renderControlPanelSection,
                          )}
                        />
                      </>
                    ),
                  },
                ]
              : []),
          ]}
        />
        <div css={actionButtonsContainerStyles}>
          <RunQueryButton
            onQuery={props.onQuery}
            onStop={props.onStop}
            errorMessage={props.buttonErrorMessage || props.errorMessage}
            loading={props.chart.chartStatus === 'loading'}
            isNewChart={!props.chart.queriesResponse}
            canStopQuery={props.canStopQuery}
            chartIsStale={props.chartIsStale}
          />
        </div>
      </Styles>
      {ConfirmModal}
    </>
  );
};

export default ControlPanelsContainer;
