// 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.

import React, {Component, MouseEvent} from 'react';
import uniq from 'lodash/uniq';
import styled from 'styled-components';
import {createSelector} from 'reselect';

import ItemSelector from 'components/common/item-selector/item-selector';
import {PanelLabel, Button, Tooltip} from 'components/common/styled-components';
import Switch from 'components/common/switch';
import ColorPalette from './color-palette';
import CustomPalette from './custom-palette';
import {COLOR_RANGES, ColorRange, hexColor} from 'constants/color-ranges';
import {numberSort} from 'utils/data-utils';
import {reverseColorRange} from 'utils/color-utils';
import {FormattedMessage} from 'localization';
import {ColorUI} from 'layers/layer-factory';
import {NestedPartial} from 'reducers';
import {SwapHorizontal} from 'components/common/icons';
import {HexColumnConfigFactory} from './hex-column-config';

type ColorRangeSelectorProps = {
  fields: any[];
  colorPaletteUI: ColorUI;
  selectedColorRange: ColorRange;
  onSelectColorRange: (p: ColorRange, e: MouseEvent) => void;
  onSelectColorColumn: Function;
  setColorPaletteUI: (newConfig: NestedPartial<ColorUI>) => void;
  noHexColor?: boolean;
};

type PaletteConfigProps = {
  label: string;
  value: string | number | boolean;
  config: {
    type: string;
    options: (string | number | boolean)[];
  };
  onChange: (v: string | number | boolean | object | null) => void;
};

type ColorPaletteGroupProps = {
  reversed?: boolean;
  selected: ColorRange;
  colorRanges: ColorRange[];
  onSelect: (p: ColorRange, e: MouseEvent) => void;
  title?: string;
};

export const ALL_TYPES = uniq([
  'all',
  ...COLOR_RANGES.map(c => c.type).filter(ctype => ctype),
  'custom',
  {value: hexColor, divider: true}
]);

export const ALL_STEPS: number[] = uniq(COLOR_RANGES.map(d => d.colors.length)).sort(numberSort);

const StyledColorConfig = styled.div`
  padding: 12px 12px 0 12px;
`;

const StyledColorRangeSelector = styled.div.attrs({
  className: 'color-range-selector'
})`
  padding-bottom: 12px;
`;

const StyledPaletteConfig = styled.div`
  margin-bottom: 8px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  .color-palette__config__label {
    flex-grow: 1;
  }
  .color-palette__config__select {
    flex-grow: 1;
  }
  .item-selector .item-selector__dropdown {
    ${props => props.theme.secondaryInput};
  }
  .color-palette__config__button {
    svg {
      margin: 0;
    }
  }
`;

const CONFIG_SETTINGS_CUSTOM_REVERSED_BUTTON_TYPE = 'custom-reversed-button';

const CONFIG_SETTINGS = {
  type: {
    type: 'select',
    options: ALL_TYPES
  },
  steps: {
    type: 'select',
    options: ALL_STEPS
  },
  reversed: {
    type: CONFIG_SETTINGS_CUSTOM_REVERSED_BUTTON_TYPE,
    options: [true, false]
  }
};

ColorRangeSelectorFactory.deps = [
  ColorPaletteGroupFactory,
  PaletteConfigFactory,
  HexColumnConfigFactory
];

export default function ColorRangeSelectorFactory(
  ColorPaletteGroup,
  PaletteConfig,
  HexColumnConfig
) {
  class ColorRangeSelector extends Component<ColorRangeSelectorProps> {
    static defaultProps = {
      colorRanges: COLOR_RANGES,
      onSelectColorRange: () => {},
      setColorPaletteUI: () => {}
    };

    colorRangesSelector = props => props.colorRanges;
    configTypeSelector = (props: ColorRangeSelectorProps) =>
      props.colorPaletteUI.colorRangeConfig.type;
    configStepSelector = (props: ColorRangeSelectorProps) =>
      props.colorPaletteUI.colorRangeConfig.steps;
    filteredColorRange = createSelector(
      this.colorRangesSelector,
      this.configTypeSelector,
      this.configStepSelector,
      (colorRanges, type, steps) => {
        return COLOR_RANGES.filter(colorRange => {
          const isType = type === 'all' || type === colorRange.type;
          const isStep = Number(steps) === colorRange.colors.length;

          return isType && isStep;
        });
      }
    );

    _updateConfig = ({
      key,
      value
    }: {
      key: string;
      value: string | number | boolean | object | null;
    }) => {
      let steps = {};
      const {colorRangeConfig} = this.props.colorPaletteUI;
      if (key === 'type' && colorRangeConfig.isHexColumn) {
        // If you're changing from HEX to a different one, then reset steps to 6
        steps = {steps: 6};
      }
      this._setColorRangeConfig({
        [key]: value,
        ...(key === 'type' && {custom: value === 'custom', isHexColumn: value === hexColor}),
        ...(key === 'type' && value !== hexColor && {hexColumn: null}),
        ...steps
      });
    };

    _onSetCustomPalette = (config: NestedPartial<ColorRange>) => {
      this.props.setColorPaletteUI({
        customPalette: config
      });
    };

    _setColorRangeConfig = (
      newConfig: Record<string, string | number | boolean | object | null>
    ) => {
      this.props.setColorPaletteUI({
        colorRangeConfig: newConfig
      });
    };

    _onCustomPaletteCancel = () => {
      this.props.setColorPaletteUI({
        showSketcher: false,
        colorRangeConfig: {custom: false, isHexColumn: false}
      });
    };

    _onToggleSketcher = (val: boolean | number) => {
      this.props.setColorPaletteUI({
        showSketcher: val
      });
    };

    _onSelectColumn = (column: any) => {
      this.props.onSelectColorColumn(column);
      this._setColorRangeConfig({
        type: hexColor,
        hexColumn: column?.name
      });
    };

    render() {
      const {noHexColor} = this.props;
      const {customPalette, showSketcher, colorRangeConfig} = this.props.colorPaletteUI;
      const filteredColorRanges = this.filteredColorRange(this.props);

      return (
        <StyledColorRangeSelector>
          <StyledColorConfig className="color-range-selector__config">
            {Object.keys(colorRangeConfig)
              .filter(
                key =>
                  Boolean(CONFIG_SETTINGS[key]) &&
                  (colorRangeConfig.custom || colorRangeConfig.isHexColumn ? key === 'type' : true)
              )
              .map(key => {
                let config = CONFIG_SETTINGS[key];
                if (noHexColor) {
                  config = {...config, options: config.options.filter(c => c.value !== hexColor)};
                }
                return (
                  <PaletteConfig
                    key={key}
                    label={CONFIG_SETTINGS[key].label || key}
                    config={config}
                    value={colorRangeConfig[key]}
                    onChange={value => this._updateConfig({key, value})}
                  />
                );
              })}
          </StyledColorConfig>

          {colorRangeConfig.custom ? (
            <CustomPalette
              customPalette={customPalette}
              showSketcher={showSketcher}
              // selected={this.props.selectedColorRange} // TODO: does not exist?
              onApply={this.props.onSelectColorRange}
              onToggleSketcher={this._onToggleSketcher}
              setCustomPalette={this._onSetCustomPalette}
              onCancel={this._onCustomPaletteCancel}
            />
          ) : colorRangeConfig.isHexColumn ? (
            <HexColumnConfig
              fields={this.props.fields}
              value={colorRangeConfig.hexColumn}
              onSelectColumn={this._onSelectColumn}
            />
          ) : (
            <ColorPaletteGroup
              colorRanges={filteredColorRanges}
              onSelect={this.props.onSelectColorRange}
              selected={this.props.selectedColorRange}
              reversed={colorRangeConfig.reversed}
              configTypeSelector={() => this.props.colorPaletteUI.colorRangeConfig.type}
              configStepSelector={() => this.props.colorPaletteUI.colorRangeConfig.steps}
            />
          )}
        </StyledColorRangeSelector>
      );
    }
  }

  return ColorRangeSelector;
}

export function PaletteConfigFactory() {
  const PaletteConfig: React.FC<PaletteConfigProps> = ({
    label,
    value,
    config: {type, options},
    onChange
  }) => {
    return (
      <StyledPaletteConfig
        className={`color-palette__config color-palette__config--${label}`}
        onClick={e => e.stopPropagation()}
      >
        <div className="color-palette__config__label">
          {type !== CONFIG_SETTINGS_CUSTOM_REVERSED_BUTTON_TYPE && (
            <PanelLabel>
              <FormattedMessage id={`color.${label}`} />
            </PanelLabel>
          )}
        </div>
        {type === 'select' && (
          <div className="color-palette__config__select">
            <ItemSelector
              selectedItems={value}
              options={options}
              multiSelect={false}
              searchable={false}
              onChange={onChange}
            />
          </div>
        )}
        {type === 'switch' && (
          <Switch
            checked={Boolean(value)}
            id={`${label}-toggle`}
            onChange={() => onChange(!value)}
            secondary
          />
        )}
        {type === CONFIG_SETTINGS_CUSTOM_REVERSED_BUTTON_TYPE && (
          <Button
            className={`color-palette__config__button ${
              value ? 'color-palette__config__button--active' : ''
            }`}
            data-tip
            data-for={`${label}-tooltip`}
            {...(!value && {link: true})}
            onClick={() => onChange(!value)}
          >
            <SwapHorizontal />
            <Tooltip id={`${label}-tooltip`} effect="solid" place="top">
              {label}
            </Tooltip>
          </Button>
        )}
      </StyledPaletteConfig>
    );
  };
  return PaletteConfig;
}

const StyledColorRange = styled.div.attrs({
  className: 'color-palette-outer'
})`
  padding: 0 8px;
  :hover {
    background-color: ${props => props.theme.panelBackgroundHover};
    cursor: pointer;
  }
`;

export function ColorPaletteGroupFactory() {
  const ColorPaletteGroup: React.FC<ColorPaletteGroupProps> = ({
    reversed,
    onSelect,
    selected,
    colorRanges,
    title
  }) => {
    return (
      <div className="color-palette__group">
        {title && title}
        {colorRanges.map((colorRange, i) => (
          <StyledColorRange
            key={`${colorRange.name}-${i}`}
            onClick={e => onSelect(reversed ? reverseColorRange(true, colorRange) : colorRange, e)}
          >
            <ColorPalette
              colors={colorRange.colors}
              isReversed={reversed}
              isSelected={colorRange.name === selected.name}
              rootProps={{
                'data-tip': true,
                'data-for': `color-range-${i}-tooltip`
              }}
            >
              <Tooltip id={`color-range-${i}-tooltip`} effect="solid" place="top">
                {colorRange.name}
              </Tooltip>
            </ColorPalette>
          </StyledColorRange>
        ))}
      </div>
    );
  };
  return ColorPaletteGroup;
}
