// 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, ElementType, createRef} from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import ItemSelectorSubmenu from './item-selector-submenu';
import {ChevronRight} from 'components/common/icons';
import {FIELD_AGGREGATORS} from 'constants/default-settings';
import Portaled from '../portaled';

export const classList = {
  list: 'list-selector',
  listHeader: 'list__header',
  listSection: 'list__section',
  listItem: 'list__item',
  listItemAnchor: 'list__item__anchor'
};

const defaultDisplay = d => d.value || d;

const Divider = styled.div`
  border-top: 1px solid rgba(44, 48, 50, 0.12);
  margin: 8px 0;
`;

export const ListItem = ({value, displayOption = defaultDisplay, light}) => (
  <span className={classList.listItemAnchor}>{displayOption(value)}</span>
);

interface DropdownListWrapperProps {
  light?: boolean;
}

const DropdownListWrapper = styled.div<DropdownListWrapperProps>`
  background-color: ${props =>
    props.light ? props.theme.dropdownListBgdLT : props.theme.dropdownListBgd};
  border-top: 1px solid
    ${props =>
      props.light ? props.theme.dropdownListBorderTopLT : props.theme.dropdownListBorderTop};
  ${props => (props.light ? props.theme.dropdownListLT : props.theme.dropdownList)};
`;

const DropdownListContainer = styled.div`
  position: relative;
`;

const Item = styled.div`
  position: relative;
  padding-right: 25px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  svg {
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    opacity: 0.6;
  }
  &:hover, &.hover {
    svg {
      opacity: 1;
    }
  }
`;

interface DropdownListProps {
  options?: any[];
  subOptions?: any[];
  allowCustomValues?: number;
  customClasses?: object;
  customValues?: any[];
  customListItemComponent?: ElementType;
  customListHeaderComponent?: ElementType;
  selectionIndex?: number;
  onOptionSelected?: Function;
  displayOption?: Function;
  defaultClassNames?: boolean;
  areResultsTruncated?: boolean;
  resultsTruncatedMessage?: string;
  listItemComponent?: Function;
  light?: boolean;
  fixedOptions?: any[];
  searchEntryValue?: string;
  withSubOptions?: boolean;
};

export default class DropdownList extends Component<DropdownListProps> {
  static defaultProps = {
    customClasses: {},
    customListItemComponent: ListItem,
    customListHeaderComponent: null,
    allowCustomValues: 0,
    customValues: [],
    displayOption: defaultDisplay,
    onOptionSelected: () => {},
    defaultClassNames: true,
    selectionIndex: null,
    withSubOptions: false
  };

  state = {
    currentOption: {} as any,
    submenuVisible: false,
    submenuPosition: {} as {left: number, top: number}
  };

  hasSubOptions = !!this.props.subOptions;

  ref = createRef<HTMLDivElement>();

  _resetState(callback?) {
    this.setState({currentOption: {}, submenuVisible: false, submenuPosition: {}}, callback);
  }

  _onClick(option, subOption, event) {
    const optionSelected =
      typeof option !== 'object'
        ? option
        : {
            ...option,
            selectedAggregator: subOption?.id
          };
    if (
      (this.props.withSubOptions && (subOption?.id || !option.aggregators)) ||
      !this.props.withSubOptions
    ) {
      event.preventDefault();
      this.props.onOptionSelected?.(optionSelected, event);
    }
  }

  _onMouseOver(option, event) {
    const el = event.target
    this.setState((previousState) => ({
      currentOption: option,
      submenuVisible: true,
      submenuPosition: {
        top: el.getBoundingClientRect().top,
        left: (previousState as any).submenuPosition.left ?(previousState as any).submenuPosition.left : this.ref.current?.getBoundingClientRect().right
      }
    }));
  }

  componentDidUpdate(prevProps) {
    if (prevProps.searchEntryValue !== this.props.searchEntryValue) {
      this._resetState();
    }
  }

  componentDidMount() {
    this.setState({
      subMenuPosition: { left: this.ref.current?.getBoundingClientRect().right }
    })
  }

  render() {
    const {
      fixedOptions,
      light,
      allowCustomValues = 0,
      customListItemComponent: CustomListItemComponent = ListItem
    } = this.props;
    const {displayOption: display = defaultDisplay} = this.props;

    // Don't render if there are no options to display
    if (!this.props.options?.length && allowCustomValues <= 0) {
      return false;
    }

    const valueOffset = Array.isArray(fixedOptions) ? fixedOptions.length : 0;

    // For some reason onClick is not fired when clicked on an option
    // onMouseDown is used here as a workaround of #205 and other
    return (
      <DropdownListContainer onMouseLeave={() => this._resetState()}>
        <DropdownListWrapper className={classList.list} light={light}>
          {this.props.customListHeaderComponent ? (
            <div className={classList.listHeader}>
              <this.props.customListHeaderComponent />
            </div>
          ) : null}
          <div ref={this.ref}>
            {valueOffset > 0 ? (
              <div className={classList.listSection}>
                {fixedOptions?.map((value, i) => (
                  <div
                    className={classNames(classList.listItem, {
                      active: this.props.selectionIndex === i,
                      hover: value.id && (this.state.currentOption?.id === value.id),
                      fixed: true
                    })}
                    key={`${display(value)}_${i}`}
                    onMouseDown={e => !this.hasSubOptions && this._onClick(value, null, e)}
                    onClick={e => !this.hasSubOptions && this._onClick(value, null, e)}
                    onMouseEnter={e => this._onMouseOver(value, e)}
                  >
                    <CustomListItemComponent value={value} displayOption={display} />
                  </div>
                ))}
              </div>
            ) : null}

            {this.props.options?.map((value, i) => (
              <div key={`${display(value)}_${i}`}>
                {value.divider && <Divider />}
                <Item
                  className={classNames(classList.listItem, {
                    active: this.props.selectionIndex === i + valueOffset,
                    hover: value.id && (this.state.currentOption?.id === value.id)
                  })}
                  onMouseDown={e => !this.hasSubOptions && this._onClick(value, null, e)}
                  onClick={e => !this.hasSubOptions && this._onClick(value, null, e)}
                  onMouseEnter={e => this._onMouseOver(value, e)}
                >
                  <CustomListItemComponent value={value} displayOption={display} />
                  {this.props.withSubOptions && value.aggregators && <ChevronRight />}
                </Item>
              </div>
            ))}
          </div>
        </DropdownListWrapper>
        {this.props.withSubOptions && (
          // The display: 'none' is a trick to be able to render the ItemSelectorSubmenu in the correct position
          // Even with display: 'none' the Portaled component render its child on the top of the DOM
          <div style={{display: 'none'}}>
            <Portaled
              isOpened={this.state.currentOption && this.state.currentOption.aggregators}
              left={this.state.submenuPosition.left}
              top={this.state.submenuPosition.top}
              modalStyle={{
                overlay: {
                  width: 0,
                  height: 0
                } as any
              }}
            >
              <ItemSelectorSubmenu
                position={this.state.submenuPosition}
                options={this.state.currentOption?.aggregators?.map(a =>
                  FIELD_AGGREGATORS.find(am => am.id === a)
                )}
                onClick={(aggregator, e) => this._onClick(this.state.currentOption, aggregator, e)}
              />
            </Portaled>
          </div>
        )}
      </DropdownListContainer>
    );
  }
}
