// Copyright (c) 2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

/* eslint-disable complexity */
import React, {Component, Fragment} from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import {FormattedMessage} from 'localization';

import {Button, Input, PanelLabel, SidePanelSection} from 'components/common/styled-components';
import ItemSelector from 'components/common/item-selector/item-selector';

import VisConfigByFieldSelectorFactory from './vis-config-by-field-selector';
import LayerColumnConfigFactory from './layer-column-config';
import LayerTypeSelectorFactory from './layer-type-selector';
import LayerVisibilityByZoomFactory from './layer-visibility-by-zoom';
import DimensionScaleSelector from './dimension-scale-selector';
import SourceDataSelectorFactory from 'components/side-panel/common/source-data-selector';
import VisConfigSwitchFactory from './vis-config-switch';
import VisConfigSliderFactory from './vis-config-slider';
import LayerConfigGroupFactory, {ConfigGroupCollapsibleContent} from './layer-config-group';
import CustomMarkersConfigGroupFactory from './custom-markers-config-group';
import TextLabelPanelFactory from './text-label-panel';

import {capitalizeFirstLetter} from 'utils/utils';

import {
  CHANNEL_SCALES,
  CHANNEL_SCALE_SUPPORTED_FIELDS,
  SCALE_TYPES
} from 'constants/default-settings';
import {LAYER_TYPES} from 'layers/types';
import {injectIntl} from 'react-intl';
import ColorDimensionScaleSelectorFactory from './color-dimension-scale-selector';
import ColorSelectorFactory from './color-selector';
import LayerAggregationResolutionFactory from './layer-aggregation-resolution';
import {DEFAULT_COLOR_RANGE, hexColorCustomPalette} from 'constants/color-ranges';

const StyledLayerConfigurator = styled.div.attrs({
  className: 'layer-panel__config'
})`
  position: relative;
  margin-top: ${props => props.theme.layerConfiguratorMargin};
  padding: ${props => props.theme.layerConfiguratorPadding};
  border-left: ${props => props.theme.layerConfiguratorBorder} dashed
    ${props => props.theme.layerConfiguratorBorderColor};
`;

const StyledLayerVisualConfigurator = styled.div.attrs({
  className: 'layer-panel__config__visualC-config'
})`
  margin-top: 12px;
`;

export const getLayerFields = (datasets, layer) =>
  layer.config && datasets[layer.config.dataId] ? datasets[layer.config.dataId].fields : [];

export const getLayerDataset = (datasets, layer) =>
  layer.config && datasets[layer.config.dataId] ? datasets[layer.config.dataId] : null;

export const getLayerConfiguratorProps = props => ({
  layer: props.layer,
  disabled: Boolean(props.disabled),
  fields: getLayerFields(props.datasets, props.layer),
  onChange: props.updateLayerConfig,
  setColorUI: props.updateLayerColorUI
});

export const getVisConfiguratorProps = props => ({
  layer: props.layer,
  fields: getLayerFields(props.datasets, props.layer),
  onChange: props.updateLayerVisConfig,
  setColorUI: props.updateLayerColorUI
});

export const getLayerChannelConfigProps = props => ({
  layer: props.layer,
  fields: getLayerFields(props.datasets, props.layer),
  onChange: props.updateLayerVisualChannelConfig,
  onChangeVisConfig: props.updateLayerVisConfig
});

LayerConfiguratorFactory.deps = [
  SourceDataSelectorFactory,
  VisConfigSliderFactory,
  TextLabelPanelFactory,
  LayerConfigGroupFactory,
  ChannelByValueSelectorFactory,
  LayerColumnConfigFactory,
  LayerTypeSelectorFactory,
  VisConfigSwitchFactory,
  AggrScaleSelectorFactory,
  LayerVisibilityByZoomFactory,
  LayerAggregationResolutionFactory,
  LayerColorSelectorFactory,
  LayerColorRangeSelectorFactory,
  ArcLayerColorSelectorFactory,
  CustomMarkersConfigGroupFactory
];

export default function LayerConfiguratorFactory(
  SourceDataSelector,
  VisConfigSlider,
  TextLabelPanel,
  LayerConfigGroup,
  ChannelByValueSelector,
  LayerColumnConfig,
  LayerTypeSelector,
  VisConfigSwitch,
  AggrScaleSelector,
  LayerVisibilityByZoom,
  LayerAggregationResolution,
  LayerColorSelector,
  LayerColorRangeSelector,
  ArcLayerColorSelector,
  CustomMarkersConfigGroup
) {
  class LayerConfigurator extends Component {
    static propTypes = {
      disabled: PropTypes.bool,
      layer: PropTypes.object.isRequired,
      datasets: PropTypes.object.isRequired,
      layerTypeOptions: PropTypes.arrayOf(PropTypes.any).isRequired,
      openModal: PropTypes.func.isRequired,
      updateLayerConfig: PropTypes.func.isRequired,
      updateLayerType: PropTypes.func.isRequired,
      updateLayerTextLabel: PropTypes.func.isRequired,
      updateLayerVisConfig: PropTypes.func.isRequired,
      updateLayerVisualChannelConfig: PropTypes.func.isRequired,
      updateLayerColorUI: PropTypes.func.isRequired
    };

    _renderPointLayerConfig(props) {
      return this._renderScatterplotLayerConfig(props);
    }

    _renderIconLayerConfig(props) {
      return this._renderScatterplotLayerConfig(props);
    }

    _renderScatterplotLayerConfig({
      layer,
      visConfiguratorProps,
      layerChannelConfigProps,
      layerConfiguratorProps
    }) {
      return (
        <StyledLayerVisualConfigurator>
          {/* Fill Color */}
          <LayerConfigGroup
            {...(layer.visConfigSettings.filled || {label: 'layer.color'})}
            {...visConfiguratorProps}
            collapsible
          >
            {layer.config.colorField ? (
              <LayerColorRangeSelector {...visConfiguratorProps} />
            ) : (
              <LayerColorSelector {...layerConfiguratorProps} />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.color}
                {...layerChannelConfigProps}
              />
              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* outline color */}
          {layer.type === LAYER_TYPES.point ? (
            <LayerConfigGroup
              {...layer.visConfigSettings.outline}
              {...visConfiguratorProps}
              collapsible
              disabled={Boolean(layer.config.visConfig.customMarkers)}
            >
              {layer.config.strokeColorField ? (
                <LayerColorRangeSelector {...visConfiguratorProps} channel="strokeColor" />
              ) : (
                <LayerColorSelector
                  {...visConfiguratorProps}
                  selectedColor={layer.config.visConfig.strokeColor}
                  property="strokeColor"
                />
              )}
              <ConfigGroupCollapsibleContent>
                <ChannelByValueSelector
                  channel={layer.visualChannels.strokeColor}
                  {...layerChannelConfigProps}
                />
                <VisConfigSlider
                  {...layer.visConfigSettings.thickness}
                  {...visConfiguratorProps}
                  disabled={!layer.config.visConfig.outline}
                />
              </ConfigGroupCollapsibleContent>
            </LayerConfigGroup>
          ) : null}

          {layer.type === LAYER_TYPES.point ? (
            <CustomMarkersConfigGroup
              layer={layer}
              visConfiguratorProps={visConfiguratorProps}
              layerChannelConfigProps={layerChannelConfigProps}
            />
          ) : null}

          {/* Radius */}
          <LayerConfigGroup
            label={layer.config.visConfig.customMarkers ? 'layer.size' : 'layer.radius'}
            collapsible
          >
            {!layer.config.sizeField ? (
              <VisConfigSlider
                {...layer.visConfigSettings.radius}
                {...visConfiguratorProps}
                label={false}
                disabled={Boolean(layer.config.sizeField)}
              />
            ) : (
              <VisConfigSlider
                {...layer.visConfigSettings.radiusRange}
                {...visConfiguratorProps}
                label={false}
                disabled={!layer.config.sizeField}
              />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.size}
                {...layerChannelConfigProps}
              />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* text label */}
          <TextLabelPanel
            fields={visConfiguratorProps.fields}
            updateLayerTextLabel={(this.props as any).updateLayerTextLabel}
            textLabel={layer.config.textLabel}
            colorPalette={visConfiguratorProps.colorPalette}
            setColorPaletteUI={visConfiguratorProps.setColorPaletteUI}
          />
        </StyledLayerVisualConfigurator>
      );
    }

    _renderClusterLayerConfig({
      layer,
      visConfiguratorProps,
      layerConfiguratorProps,
      layerChannelConfigProps
    }) {
      return (
        <StyledLayerVisualConfigurator>
          {/* Color */}
          <LayerConfigGroup label={'layer.color'} collapsible>
            <LayerColorRangeSelector {...visConfiguratorProps} />
            <ConfigGroupCollapsibleContent>
              <AggrScaleSelector
                {...layerConfiguratorProps}
                channel={layer.visualChannels.color}
                onChangeVisConfig={layerChannelConfigProps.onChangeVisConfig}
              />
              <ChannelByValueSelector
                channel={layer.visualChannels.color}
                {...layerChannelConfigProps}
              />
              {layer.visConfigSettings.colorAggregation.condition(layer.config) ? (
                <AggregationTypeSelector
                  {...layer.visConfigSettings.colorAggregation}
                  {...layerChannelConfigProps}
                  channel={layer.visualChannels.color}
                />
              ) : null}
              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Cluster Radius */}
          <LayerConfigGroup label={'layer.radius'} collapsible>
            <VisConfigSlider {...layer.visConfigSettings.clusterRadius} {...visConfiguratorProps} />
            <ConfigGroupCollapsibleContent>
              <VisConfigSlider {...layer.visConfigSettings.radiusRange} {...visConfiguratorProps} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>
        </StyledLayerVisualConfigurator>
      );
    }

    _renderHeatmapLayerConfig({
      layer,
      visConfiguratorProps,
      layerConfiguratorProps,
      layerChannelConfigProps
    }) {
      return (
        <StyledLayerVisualConfigurator>
          {/* Color */}
          <LayerConfigGroup label={'layer.color'} collapsible>
            <LayerColorRangeSelector {...visConfiguratorProps} />
            <ConfigGroupCollapsibleContent>
              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>
          {/* Radius */}
          <LayerConfigGroup label={'layer.radius'}>
            <VisConfigSlider
              {...layer.visConfigSettings.radius}
              {...visConfiguratorProps}
              label={false}
            />
          </LayerConfigGroup>
          {/* Weight */}
          <LayerConfigGroup label={'layer.weight'}>
            <ChannelByValueSelector
              channel={layer.visualChannels.weight}
              {...layerChannelConfigProps}
            />
          </LayerConfigGroup>
        </StyledLayerVisualConfigurator>
      );
    }

    _renderGridLayerConfig(props) {
      return this._renderAggregationLayerConfig(props);
    }

    _renderHexagonLayerConfig(props) {
      return this._renderAggregationLayerConfig(props);
    }

    _renderAggregationLayerConfig({
      layer,
      visConfiguratorProps,
      layerConfiguratorProps,
      layerChannelConfigProps
    }) {
      const {config} = layer;
      const {
        visConfig: {enable3d}
      } = config;
      const elevationByDescription = 'layer.elevationByDescription';
      const colorByDescription = 'layer.colorByDescription';

      return (
        <StyledLayerVisualConfigurator>
          {/* Color */}
          <LayerConfigGroup label={'layer.color'} collapsible>
            <LayerColorRangeSelector {...visConfiguratorProps} />
            <ConfigGroupCollapsibleContent>
              <AggrScaleSelector
                {...layerConfiguratorProps}
                channel={layer.visualChannels.color}
                onChangeVisConfig={layerChannelConfigProps.onChangeVisConfig}
              />
              <ChannelByValueSelector
                channel={layer.visualChannels.color}
                {...layerChannelConfigProps}
              />
              {layer.visConfigSettings.colorAggregation.condition(layer.config) ? (
                <AggregationTypeSelector
                  {...layer.visConfigSettings.colorAggregation}
                  {...layerChannelConfigProps}
                  description={colorByDescription}
                  channel={layer.visualChannels.color}
                />
              ) : null}
              {layer.visConfigSettings.percentile &&
              layer.visConfigSettings.percentile.condition(layer.config) ? (
                <VisConfigSlider
                  {...layer.visConfigSettings.percentile}
                  {...visConfiguratorProps}
                />
              ) : null}
              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Cell size */}
          <LayerConfigGroup label={'layer.radius'} collapsible>
            <VisConfigSlider {...layer.visConfigSettings.worldUnitSize} {...visConfiguratorProps} />
            <ConfigGroupCollapsibleContent>
              <VisConfigSlider {...layer.visConfigSettings.coverage} {...visConfiguratorProps} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Elevation */}
          {layer.visConfigSettings.enable3d ? (
            <LayerConfigGroup
              {...layer.visConfigSettings.enable3d}
              {...visConfiguratorProps}
              collapsible
            >
              <VisConfigSlider
                {...layer.visConfigSettings.elevationScale}
                {...visConfiguratorProps}
                label="layerVisConfigs.heightMultiplier"
              />
              <ConfigGroupCollapsibleContent>
                <ChannelByValueSelector
                  {...layerChannelConfigProps}
                  channel={layer.visualChannels.size}
                  description={elevationByDescription}
                  disabled={!enable3d}
                />
                <AggrScaleSelector
                  {...layerConfiguratorProps}
                  channel={layer.visualChannels.size}
                  onChangeVisConfig={layerChannelConfigProps.onChangeVisConfig}
                />
                <VisConfigSlider
                  {...layer.visConfigSettings.sizeRange}
                  {...visConfiguratorProps}
                  label="layerVisConfigs.heightRange"
                />
                {layer.visConfigSettings.sizeAggregation.condition(layer.config) ? (
                  <AggregationTypeSelector
                    {...layer.visConfigSettings.sizeAggregation}
                    {...layerChannelConfigProps}
                    channel={layer.visualChannels.size}
                  />
                ) : null}
                {layer.visConfigSettings.elevationPercentile.condition(layer.config) ? (
                  <VisConfigSlider
                    {...layer.visConfigSettings.elevationPercentile}
                    {...visConfiguratorProps}
                  />
                ) : null}
              </ConfigGroupCollapsibleContent>
            </LayerConfigGroup>
          ) : null}
        </StyledLayerVisualConfigurator>
      );
    }

    // TODO: Shan move these into layer class
    _renderHexagonIdLayerConfig({
      layer,
      visConfiguratorProps,
      layerConfiguratorProps,
      layerChannelConfigProps
    }) {
      return (
        <StyledLayerVisualConfigurator>
          {/* Color */}
          <LayerConfigGroup label={'layer.color'} collapsible>
            {layer.config.colorField ? (
              <LayerColorRangeSelector {...visConfiguratorProps} />
            ) : (
              <LayerColorSelector {...layerConfiguratorProps} />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.color}
                {...layerChannelConfigProps}
              />
              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Coverage */}
          <LayerConfigGroup label={'layer.coverage'} collapsible>
            {!layer.config.coverageField ? (
              <VisConfigSlider
                {...layer.visConfigSettings.coverage}
                {...visConfiguratorProps}
                label={false}
              />
            ) : (
              <VisConfigSlider
                {...layer.visConfigSettings.coverageRange}
                {...visConfiguratorProps}
                label={false}
              />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.coverage}
                {...layerChannelConfigProps}
              />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* height */}
          <LayerConfigGroup
            {...layer.visConfigSettings.enable3d}
            {...visConfiguratorProps}
            collapsible
          >
            <ChannelByValueSelector
              channel={layer.visualChannels.size}
              {...layerChannelConfigProps}
            />
            <ConfigGroupCollapsibleContent>
              <VisConfigSlider
                {...layer.visConfigSettings.elevationScale}
                {...visConfiguratorProps}
              />
              <VisConfigSlider
                {...layer.visConfigSettings.sizeRange}
                {...visConfiguratorProps}
                label="layerVisConfigs.heightRange"
              />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>
        </StyledLayerVisualConfigurator>
      );
    }

    _renderArcLayerConfig(args) {
      return this._renderLineLayerConfig(args);
    }

    _renderLineLayerConfig({
      layer,
      visConfiguratorProps,
      layerConfiguratorProps,
      layerChannelConfigProps
    }) {
      return (
        <StyledLayerVisualConfigurator>
          {/* Color */}
          <LayerConfigGroup label={'layer.color'} collapsible>
            {layer.config.colorField ? (
              <LayerColorRangeSelector {...visConfiguratorProps} />
            ) : (
              <ArcLayerColorSelector
                layer={layer}
                setColorUI={layerConfiguratorProps.setColorUI}
                onChangeConfig={layerConfiguratorProps.onChange}
                onChangeVisConfig={visConfiguratorProps.onChange}
              />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.sourceColor}
                {...layerChannelConfigProps}
              />
              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* thickness */}
          <LayerConfigGroup label={'layer.stroke'} collapsible>
            {layer.config.sizeField ? (
              <VisConfigSlider
                {...layer.visConfigSettings.sizeRange}
                {...visConfiguratorProps}
                disabled={!layer.config.sizeField}
                label={false}
              />
            ) : (
              <VisConfigSlider
                {...layer.visConfigSettings.thickness}
                {...visConfiguratorProps}
                label={false}
              />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.size}
                {...layerChannelConfigProps}
              />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* elevation scale */}
          {layer.visConfigSettings.elevationScale ? (
            <LayerConfigGroup label="layerVisConfigs.elevationScale" collapsible>
              <VisConfigSlider
                {...layer.visConfigSettings.elevationScale}
                {...visConfiguratorProps}
              />
            </LayerConfigGroup>
          ) : null}
        </StyledLayerVisualConfigurator>
      );
    }

    _renderTripLayerConfig({
      layer,
      visConfiguratorProps,
      layerConfiguratorProps,
      layerChannelConfigProps
    }) {
      const {
        meta: {featureTypes = {}}
      } = layer;

      return (
        <StyledLayerVisualConfigurator>
          {/* Color */}
          <LayerConfigGroup label={'layer.color'} collapsible>
            {layer.config.colorField ? (
              <LayerColorRangeSelector {...visConfiguratorProps} />
            ) : (
              <LayerColorSelector {...layerConfiguratorProps} />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.color}
                {...layerChannelConfigProps}
              />
              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Stroke Width */}
          <LayerConfigGroup {...visConfiguratorProps} label="layer.strokeWidth" collapsible>
            {layer.config.sizeField ? (
              <VisConfigSlider
                {...layer.visConfigSettings.sizeRange}
                {...visConfiguratorProps}
                label={false}
              />
            ) : (
              <VisConfigSlider
                {...layer.visConfigSettings.thickness}
                {...visConfiguratorProps}
                label={false}
              />
            )}

            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.size}
                {...layerChannelConfigProps}
              />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Trail Length*/}
          <LayerConfigGroup
            {...visConfiguratorProps}
            {...(featureTypes.polygon ? layer.visConfigSettings.stroked : {})}
            label="layer.trailLength"
            description="layer.trailLengthDescription"
          >
            <VisConfigSlider
              {...layer.visConfigSettings.trailLength}
              {...visConfiguratorProps}
              label={false}
            />
          </LayerConfigGroup>
        </StyledLayerVisualConfigurator>
      );
    }

    _renderGeojsonLayerConfig({
      layer,
      visConfiguratorProps,
      layerConfiguratorProps,
      layerChannelConfigProps
    }) {
      const {
        meta: {featureTypes = {}},
        config: {visConfig}
      } = layer;

      return (
        <StyledLayerVisualConfigurator>
          {/* Fill Color */}
          {featureTypes.polygon || featureTypes.point ? (
            <LayerConfigGroup
              {...layer.visConfigSettings.filled}
              {...visConfiguratorProps}
              label="layer.fillColor"
              collapsible
            >
              {layer.config.colorField ? (
                <LayerColorRangeSelector {...visConfiguratorProps} />
              ) : (
                <LayerColorSelector {...layerConfiguratorProps} />
              )}
              <ConfigGroupCollapsibleContent>
                <ChannelByValueSelector
                  channel={layer.visualChannels.color}
                  {...layerChannelConfigProps}
                />
                <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />
              </ConfigGroupCollapsibleContent>
            </LayerConfigGroup>
          ) : null}

          {/* stroke color */}
          <LayerConfigGroup
            {...layer.visConfigSettings.stroked}
            {...visConfiguratorProps}
            label="layer.strokeColor"
            collapsible
          >
            {layer.config.strokeColorField ? (
              <LayerColorRangeSelector {...visConfiguratorProps} channel="strokeColor" />
            ) : (
              <LayerColorSelector
                {...visConfiguratorProps}
                selectedColor={layer.config.visConfig.strokeColor}
                property="strokeColor"
              />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.strokeColor}
                {...layerChannelConfigProps}
              />
              <VisConfigSlider
                {...layer.visConfigSettings.strokeOpacity}
                {...visConfiguratorProps}
              />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Stroke Width */}
          <LayerConfigGroup
            {...visConfiguratorProps}
            {...(featureTypes.polygon ? layer.visConfigSettings.stroked : {})}
            label="layer.strokeWidth"
            collapsible
          >
            {layer.config.sizeField ? (
              <VisConfigSlider
                {...layer.visConfigSettings.sizeRange}
                {...visConfiguratorProps}
                label={false}
              />
            ) : (
              <VisConfigSlider
                {...layer.visConfigSettings.thickness}
                {...visConfiguratorProps}
                label={false}
              />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.size}
                {...layerChannelConfigProps}
              />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Elevation */}
          {featureTypes.polygon ? (
            <LayerConfigGroup
              {...visConfiguratorProps}
              {...layer.visConfigSettings.enable3d}
              disabled={!visConfig.filled}
              collapsible
            >
              <VisConfigSlider
                {...layer.visConfigSettings.elevationScale}
                {...visConfiguratorProps}
                label={false}
              />
              <ConfigGroupCollapsibleContent>
                <ChannelByValueSelector
                  channel={layer.visualChannels.height}
                  {...layerChannelConfigProps}
                />
                <VisConfigSwitch {...visConfiguratorProps} {...layer.visConfigSettings.wireframe} />
              </ConfigGroupCollapsibleContent>
            </LayerConfigGroup>
          ) : null}

          {/* Radius */}
          {featureTypes.point ? (
            <LayerConfigGroup label={'layer.radius'} collapsible>
              {!layer.config.radiusField ? (
                <VisConfigSlider
                  {...layer.visConfigSettings.radius}
                  {...visConfiguratorProps}
                  label={false}
                  disabled={Boolean(layer.config.radiusField)}
                />
              ) : (
                <VisConfigSlider
                  {...layer.visConfigSettings.radiusRange}
                  {...visConfiguratorProps}
                  label={false}
                  disabled={!layer.config.radiusField}
                />
              )}
              <ConfigGroupCollapsibleContent>
                <ChannelByValueSelector
                  channel={layer.visualChannels.radius}
                  {...layerChannelConfigProps}
                />
              </ConfigGroupCollapsibleContent>
            </LayerConfigGroup>
          ) : null}
        </StyledLayerVisualConfigurator>
      );
    }

    _render3DLayerConfig({layer, visConfiguratorProps}) {
      return (
        <Fragment>
          <LayerConfigGroup label={'layer.3DModel'} collapsible>
            <Input
              type="file"
              accept=".glb,.gltf"
              onChange={e => {
                if (e.target.files && e.target.files[0]) {
                  const url = URL.createObjectURL(e.target.files[0]);
                  visConfiguratorProps.onChange({scenegraph: url});
                }
              }}
            />
          </LayerConfigGroup>
          <LayerConfigGroup label={'layer.3DModelOptions'} collapsible>
            <VisConfigSlider
              {...layer.visConfigSettings.sizeScale}
              {...visConfiguratorProps}
              disabled={false}
            />
            <VisConfigSlider
              {...layer.visConfigSettings.angleX}
              {...visConfiguratorProps}
              disabled={false}
            />
            <VisConfigSlider
              {...layer.visConfigSettings.angleY}
              {...visConfiguratorProps}
              disabled={false}
            />
            <VisConfigSlider
              {...layer.visConfigSettings.angleZ}
              {...visConfiguratorProps}
              disabled={false}
            />
          </LayerConfigGroup>
        </Fragment>
      );
    }

    _renderS2LayerConfig({
      layer,
      visConfiguratorProps,
      layerConfiguratorProps,
      layerChannelConfigProps
    }) {
      const {
        config: {visConfig}
      } = layer;

      return (
        <StyledLayerVisualConfigurator>
          {/* Color */}
          <LayerConfigGroup
            {...layer.visConfigSettings.filled}
            {...visConfiguratorProps}
            label="layer.fillColor"
            collapsible
          >
            {layer.config.colorField ? (
              <LayerColorRangeSelector {...visConfiguratorProps} />
            ) : (
              <LayerColorSelector {...layerConfiguratorProps} />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.color}
                {...layerChannelConfigProps}
              />
              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Stroke */}
          <LayerConfigGroup
            {...layer.visConfigSettings.stroked}
            {...visConfiguratorProps}
            label="layer.strokeColor"
            collapsible
          >
            {layer.config.strokeColorField ? (
              <LayerColorRangeSelector {...visConfiguratorProps} channel="strokeColor" />
            ) : (
              <LayerColorSelector
                {...visConfiguratorProps}
                selectedColor={layer.config.visConfig.strokeColor}
                property="strokeColor"
              />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.strokeColor}
                {...layerChannelConfigProps}
              />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Stroke Width */}
          <LayerConfigGroup {...visConfiguratorProps} label="layer.strokeWidth" collapsible>
            {layer.config.sizeField ? (
              <VisConfigSlider
                {...layer.visConfigSettings.sizeRange}
                {...visConfiguratorProps}
                label={false}
              />
            ) : (
              <VisConfigSlider
                {...layer.visConfigSettings.thickness}
                {...visConfiguratorProps}
                label={false}
              />
            )}
            <ConfigGroupCollapsibleContent>
              <ChannelByValueSelector
                channel={layer.visualChannels.size}
                {...layerChannelConfigProps}
              />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>

          {/* Elevation */}
          <LayerConfigGroup
            {...visConfiguratorProps}
            {...layer.visConfigSettings.enable3d}
            disabled={!visConfig.filled}
            collapsible
          >
            <ChannelByValueSelector
              channel={layer.visualChannels.height}
              {...layerChannelConfigProps}
            />
            <VisConfigSlider
              {...layer.visConfigSettings.elevationScale}
              {...visConfiguratorProps}
              label="layerVisConfigs.elevationScale"
            />
            <ConfigGroupCollapsibleContent>
              <VisConfigSlider
                {...layer.visConfigSettings.heightRange}
                {...visConfiguratorProps}
                label="layerVisConfigs.heightRange"
              />
              <VisConfigSwitch {...visConfiguratorProps} {...layer.visConfigSettings.wireframe} />
            </ConfigGroupCollapsibleContent>
          </LayerConfigGroup>
        </StyledLayerVisualConfigurator>
      );
    }

    render() {
      // @ts-expect-error
      const {layer, datasets, updateLayerConfig, layerTypeOptions, updateLayerType} = this.props;
      const {fields = [], fieldPairs = undefined} = layer.config.dataId
        ? datasets[layer.config.dataId]
        : {};
      const {config} = layer;

      const visConfiguratorProps = getVisConfiguratorProps(this.props);
      const layerConfiguratorProps = getLayerConfiguratorProps(this.props);
      const layerChannelConfigProps = getLayerChannelConfigProps(this.props);
      const dataset = getLayerDataset(datasets, layer);
      const renderTemplate = layer.type && `_render${capitalizeFirstLetter(layer.type)}LayerConfig`;

      return (
        <StyledLayerConfigurator>
          {layer.layerInfoModal ? (
            // @ts-expect-error
            <HowToButton onClick={() => this.props.openModal(layer.layerInfoModal)} />
          ) : null}
          <LayerConfigGroup
            label={'layer.basic'}
            collapsible={Object.keys(layer.config.columns).length > 0}
            expanded={!layer.hasAllColumns()}
          >
            <LayerTypeSelector
              datasets={datasets}
              layer={layer}
              layerTypeOptions={layerTypeOptions}
              onSelect={updateLayerType}
            />
            <LayerVisibilityByZoom
              // @ts-expect-error
              updateLayerConfig={this.props.updateLayerConfig}
              layer={layer}
              disabled={layerConfiguratorProps.disabled}
            />
            <LayerAggregationResolution layer={layer} disabled={layerConfiguratorProps.disabled} />
            {Object.keys(datasets).length > 1 && (
              <SourceDataSelector
                datasets={datasets}
                id={layer.id}
                dataId={config.dataId}
                onSelect={value => updateLayerConfig({dataId: value})}
              />
            )}
            {Object.keys(layer.config.columns).length > 0 && (
              <ConfigGroupCollapsibleContent>
                <LayerColumnConfig
                  columnPairs={layer.columnPairs}
                  columns={layer.config.columns}
                  assignColumnPairs={layer.assignColumnPairs.bind(layer)}
                  assignColumn={layer.assignColumn.bind(layer)}
                  columnLabels={layer.columnLabels}
                  fields={fields}
                  fieldPairs={fieldPairs}
                  updateLayerConfig={updateLayerConfig}
                  // @ts-expect-error
                  updateLayerType={this.props.updateLayerType}
                />
              </ConfigGroupCollapsibleContent>
            )}
          </LayerConfigGroup>
          {this[renderTemplate] &&
            this[renderTemplate]({
              layer,
              dataset,
              visConfiguratorProps,
              layerChannelConfigProps,
              layerConfiguratorProps
            })}
        </StyledLayerConfigurator>
      );
    }
  }

  return LayerConfigurator;
}
/*
 * Componentize config component into pure functional components
 */

const StyledHowToButton = styled.div`
  position: absolute;
  right: 12px;
  top: -4px;
`;

export const HowToButton = ({onClick}) => (
  <StyledHowToButton>
    <Button link small onClick={onClick}>
      <FormattedMessage id={'layerConfiguration.howTo'} />
    </Button>
  </StyledHowToButton>
);

LayerColorSelectorFactory.deps = [ColorSelectorFactory];
export function LayerColorSelectorFactory(ColorSelector) {
  class LayerColorSelector extends Component {
    render() {
      // @ts-expect-error
      const {layer, onChange, selectedColor, property = 'color', setColorUI, fields} = this.props;
      return (
        <SidePanelSection>
          <PanelLabel>
            <FormattedMessage id="layer.color" />
          </PanelLabel>
          <ColorSelector
            fields={fields}
            colorSets={[
              {
                selectedColor: selectedColor || layer.config.color,
                setColor: rgbValue => onChange({[property]: rgbValue})
              }
            ]}
            colorUI={layer.config.colorUI[property]}
            setColorUI={newConfig => setColorUI(property, newConfig)}
          />
        </SidePanelSection>
      );
    }
  }

  return LayerColorSelector;
}

ArcLayerColorSelectorFactory.deps = [ColorSelectorFactory];
export function ArcLayerColorSelectorFactory(ColorSelector) {
  class ArcLayerColorSelector extends Component {
    render() {
      const {
        layer,
        onChangeConfig,
        onChangeVisConfig,
        property = 'color',
        setColorUI,
        fields
      } = this.props as any;

      return (
        <SidePanelSection>
          <PanelLabel>
            <FormattedMessage id="layer.colorRange" />
          </PanelLabel>
          <ColorSelector
            fields={fields}
            colorSets={[
              {
                selectedColor: layer.config.color,
                setColor: rgbValue => onChangeConfig({color: rgbValue}),
                label: 'Source'
              },
              {
                selectedColor: layer.config.visConfig.targetColor || layer.config.color,
                setColor: rgbValue => onChangeVisConfig({targetColor: rgbValue}),
                label: 'Target'
              }
            ]}
            colorUI={layer.config.colorUI[property]}
            setColorUI={newConfig => setColorUI(property, newConfig)}
          />
        </SidePanelSection>
      );
    }
  }
  return ArcLayerColorSelector;
}

LayerColorRangeSelectorFactory.deps = [ColorSelectorFactory];
export function LayerColorRangeSelectorFactory(ColorSelector) {
  class LayerColorRangeSelector extends Component {
    __renderHeatmapColorSelectorComponent() {
      const property = 'colorRange';
      const {layer, onChange, setColorUI} = this.props as any;
      return (
        <ColorSelector
          colorSets={[
            {
              selectedColor: layer.config.visConfig[property],
              isRange: true,
              setColor: colorRange => {
                const {colorMap} = layer.config.visConfig[property];
                const colorRangeUpdated = colorMap
                  ? layer.updateColorMap(property, colorMap, colorRange)
                  : colorRange;
                onChange({[property]: colorRangeUpdated});
              }
            }
          ]}
          colorUI={layer.config.colorUI[property]}
          setColorUI={newConfig => setColorUI(property, newConfig)}
        />
      );
    }
    __renderDefaultColorSelectorComponent() {
      const {
        layer,
        onChange,
        channel = 'color',
        setColorUI,
        fields,
        handleCustomOnSelectHexColumn = () => null
      } = this.props as any;
      const {range: property, field, scale} = layer.visualChannels[channel];
      return (
        <ColorSelector
          fields={fields}
          colorSets={[
            {
              selectedColor: layer.config.visConfig[property],
              isRange: true,
              setColor: colorRange => {
                const {colorMap} = layer.config.visConfig[property];
                const colorRangeUpdated = colorMap
                  ? layer.updateColorMap(property, colorMap, colorRange)
                  : colorRange;
                // Ensure that scale changes from identity to ordinal
                // Validate scale to ensure that it changes from `identity` to the correct one.
                if (layer.isColoredByColorColumn(property)) {
                  layer.updateLayerConfig({
                    [field]: {
                      ...layer.config[field],
                      colorColumn: null,
                      colorColumnIdx: null
                    },
                    [scale]: SCALE_TYPES.ordinal
                  });
                  layer.validateScale(channel);
                }
                // ---------------------------------------
                onChange({[property]: colorRangeUpdated});
              }
            }
          ]}
          colorUI={layer.config.colorUI[property]}
          setColorUI={newConfig => setColorUI(property, newConfig)}
          onSelectHexColorColumn={colorColumn => {
            // It's defined in CN: see workspace-www/src/features/builder/ui/KeplerGl/SidePanel/LayerPanel/LayerColorRangeSelector.tsx
            handleCustomOnSelectHexColumn(layer, colorColumn);

            layer.updateLayerConfig({
              [field]: {
                ...layer.config[field],
                colorColumn: colorColumn?.name,
                colorColumnIdx: colorColumn?.fieldIdx
              },
              [scale]: SCALE_TYPES.identity
            });

            onChange({[property]: hexColorCustomPalette});
          }}
        />
      );
    }

    render() {
      const {layer} = this.props as any;
      let renderColorSelectorComponent = this.__renderDefaultColorSelectorComponent.bind(this);

      if (layer.type === LAYER_TYPES.heatmap) {
        renderColorSelectorComponent = this.__renderHeatmapColorSelectorComponent.bind(this);
      }

      return (
        <SidePanelSection>
          <PanelLabel>
            <FormattedMessage id="layer.colorRange" />
          </PanelLabel>
          {renderColorSelectorComponent()}
        </SidePanelSection>
      );
    }
  }
  return LayerColorRangeSelector;
}

ChannelByValueSelectorFactory.deps = [VisConfigByFieldSelectorFactory];
export function ChannelByValueSelectorFactory(VisConfigByFieldSelector) {
  const ChannelByValueSelector = ({
    layer,
    channel,
    onChange,
    onChangeVisConfig,
    fields,
    fieldsAggregated,
    description,
    isLoading
  }) => {
    const {
      channelScaleType,
      domain,
      field,
      aggregation,
      key,
      property,
      range,
      scale,
      defaultMeasure,
      supportedFieldTypes
    } = channel;
    const channelSupportedFieldTypes =
      supportedFieldTypes || CHANNEL_SCALE_SUPPORTED_FIELDS[channelScaleType];
    const supportedFields = fields.filter(({type}) => channelSupportedFieldTypes.includes(type));
    const scaleOptions = layer.getScaleOptions(channel.key);
    const showScaleCustomizable =
      channelScaleType === CHANNEL_SCALES.color &&
      scale &&
      layer.config[scale] &&
      Boolean(layer.config[field]) &&
      scaleOptions?.length > 0;
    const showScale = Boolean(
      !showScaleCustomizable &&
        scale &&
        !layer.isAggregated &&
        layer.config[scale] &&
        scaleOptions?.length > 1
    );
    const defaultDescription = 'layerConfiguration.defaultDescription';
    const visRange = layer.config.visConfig[range];
    const aggregator = layer.config.visConfig[aggregation];

    return (
      <VisConfigByFieldSelector
        channel={channel.key}
        description={description || defaultDescription}
        domain={layer.config[domain]}
        fields={supportedFields}
        aggregator={aggregator}
        fieldsAggregated={fieldsAggregated}
        id={layer.id}
        key={`${key}-channel-selector`}
        property={property}
        placeholder={defaultMeasure || 'placeholder.selectField'}
        range={visRange}
        scaleOptions={scaleOptions}
        scaleType={scale ? layer.config[scale] : null}
        selectedField={layer.config[field]}
        showScale={showScale}
        showScaleCustomizable={showScaleCustomizable}
        updateField={val => {
          if (visRange?.colorMap) {
            onChangeVisConfig({[range]: {...visRange, colorMap: undefined}});
          }
          if (!val) {
            // Clean everything and comeback to default behaviour
            onChange({[field]: null}, key);
            onChangeVisConfig({[range]: DEFAULT_COLOR_RANGE});
          }
          onChange(
            {
              [field]: val,
              ...(fieldsAggregated && {
                visConfig: {
                  ...layer.config.visConfig,
                  [aggregation]: val?.selectedAggregator
                }
              })
            },
            key
          );
          layer.validateScale(key);
        }}
        updateScale={val => onChange({[scale]: val}, key)}
        updateCustomPalette={(colorMap, uiCustomScaleType) => {
          onChangeVisConfig({[range]: {...visRange, colorMap, uiCustomScaleType}});
        }}
        isLoading={isLoading}
      />
    );
  };

  return ChannelByValueSelector;
}

AggrScaleSelectorFactory.deps = [ColorDimensionScaleSelectorFactory];

function AggrScaleSelectorFactory(ColorDimensionScaleSelector) {
  class AggrScaleSelector extends Component {
    render() {
      const {channel, layer, onChange, onChangeVisConfig} = this.props as any;
      const {scale, key, channelScaleType, domain, range} = channel;
      const scaleOptions = layer.getScaleOptions(key);
      const visRange = layer.config.visConfig[range];
      const showScaleCustomizable =
        channelScaleType === CHANNEL_SCALES.colorAggr &&
        Array.isArray(scaleOptions) &&
        scaleOptions.length > 0;
      const showScale = Array.isArray(scaleOptions) && scaleOptions.length > 1;

      if (showScaleCustomizable) {
        return (
          <SidePanelSection>
            <ColorDimensionScaleSelector
              label={`${key} Scale`}
              scaleType={layer.config[scale]}
              options={scaleOptions}
              domain={layer.config[domain]}
              range={visRange}
              onSelect={val => onChange({[scale]: val}, key)}
              onSetCustomPalette={(colorMap, uiCustomScaleType) => {
                onChangeVisConfig({[range]: {...visRange, colorMap, uiCustomScaleType}});
              }}
            />
          </SidePanelSection>
        );
      }

      if (showScale) {
        return (
          <DimensionScaleSelector
            label={`${key} Scale`}
            options={scaleOptions}
            scaleType={layer.config[scale]}
            onSelect={val => onChange({[scale]: val}, key)}
          />
        );
      }
      return null;
    }
  }
  return injectIntl(AggrScaleSelector);
}

export const AggregationTypeSelector = ({layer, channel, onChange}) => {
  const {field, aggregation, key} = channel;
  const selectedField = layer.config[field];
  const {visConfig} = layer.config;

  // aggregation should only be selectable when field is selected
  const aggregationOptions = layer.getAggregationOptions(key);

  return (
    <SidePanelSection>
      <PanelLabel>
        <FormattedMessage id={'layer.aggregateBy'} values={{field: selectedField.name}} />
      </PanelLabel>
      <ItemSelector
        selectedItems={visConfig[aggregation]}
        options={aggregationOptions}
        multiSelect={false}
        searchable={false}
        onChange={value =>
          onChange(
            {
              visConfig: {
                ...layer.config.visConfig,
                [aggregation]: value
              }
            },
            channel.key
          )
        }
      />
    </SidePanelSection>
  );
};

/* eslint-enable max-params */
