import React, { useMemo, useCallback } from "react";
import { Select, Divider, Tooltip } from "antd";
import { calcTextWidth, SELECT_WIDTH_OFFSET_RIGHT } from "../../utils/domUtils";
import { Hooks , Utils } from "@react-awesome-query-builder/ui";
const { fixListValuesGroupOrder } = Utils.Autocomplete;
const { makeCustomListValue } = Utils.ListUtils;
const { useListValuesAutocomplete } = Hooks;

// see type ListItem
const mapListItemToOptionKeys = {
  value: "value",
  title: "label",
  groupTitle: "grouplabel",
};

export default (props) => {
  const { config, placeholder, allowCustomValues, customProps, value, readonly, multiple, useAsyncSearch } = props;

  // hook
  const {
    open,
    onDropdownVisibleChange,
    onChange,
    isSpecialValue,
    onSearch,
    inputValue,
    options,
    isInitialLoading,
    isLoading,
    aPlaceholder,
    extendOptions,
    getOptionDisabled,
    getOptionIsCustom,
    getOptionLabel,
    onInputChange,
  } = useListValuesAutocomplete(props, {
    debounceTimeout: 100,
    multiple,
    uif: "antd"
  });

  const filteredOptions = extendOptions(options);

  // To colorize custom options
  if (multiple && allowCustomValues && value?.length) {
    for (const v of value) {
      if (getOptionIsCustom(v) && !options.find(({value}) => value === v)) {
        filteredOptions.push(makeCustomListValue(v));
      }
    }
  }

  const optionsMaxWidth = useMemo(() => {
    return filteredOptions.reduce((max, option) => {
      return Math.max(max, calcTextWidth(option.title, null));
    }, 0);
  }, [options]);

  const { defaultSelectWidth, defaultSearchWidth, renderSize } = config.settings;
  const placeholderWidth = calcTextWidth(placeholder);
  const isEmptyValue = multiple ? !value?.length : !value;
  const aValue = !isEmptyValue ? value : (multiple ? [] : undefined);
  const width = aValue || !placeholderWidth ? null : placeholderWidth + SELECT_WIDTH_OFFSET_RIGHT;
  const dropdownWidth = optionsMaxWidth && !isNaN(optionsMaxWidth) ? optionsMaxWidth + SELECT_WIDTH_OFFSET_RIGHT : null;
  const minWidth = width || defaultSelectWidth;
  const isClearAllClicked = React.useRef(false);
  
  const style = {
    width: (multiple ? undefined : minWidth),
    minWidth: minWidth
  };
  const dropdownStyle = {
    width: dropdownWidth,
  };

  const mode = !multiple ? undefined : (
    customProps?.mode || (allowCustomValues && customProps?.tokenSeparators ? "tags" : "multiple")
  );
  const dynamicPlaceholder = !readonly ? aPlaceholder : "";

  const nestByGroup = (opts, fix = false) => {
    let newOpts = opts;
    if (fix) {
      newOpts = fixListValuesGroupOrder(newOpts);
    }

    let nestedOpts = [];
    for (const o of newOpts) {
      const groupTitle = o.groupTitle;
      delete o.groupTitle;
      if (groupTitle) {
        o.grouplabel = groupTitle;
        let targetGroup;
        const lastO = nestedOpts[nestedOpts.length-1];
        if (lastO?.options && lastO.label === groupTitle) {
          targetGroup = lastO;
        } else {
          targetGroup = {
            label: groupTitle,
            options: []
          };
          nestedOpts.push(targetGroup);
        }
        targetGroup.options.push(o);
      } else {
        nestedOpts.push(o);
      }
    }

    return nestedOpts;
  };

  const renderOptionLabel = (option) => {
    const { tooltip } = option;
    let label = getOptionLabel(option);
    if (tooltip) {
      label = <Tooltip title={tooltip}>{label}</Tooltip>;
    }
    return label;
  };

  // rendering special 'Load more' option has side effect: on change rc-select will save its title as internal value in own state
  const optionsToRender = nestByGroup(
    filteredOptions
      ?.filter(option => !option.specialValue)
      .map((option) => ({
        label: getOptionIsCustom(option)
          ? <span className={"customSelectOption"}>{getOptionLabel(option)}</span>
          : renderOptionLabel(option),
        value: option.value,
        groupTitle: option.groupTitle,
        disabled: getOptionDisabled(option)
      }))
  );

  const onSpecialClick = (specialValue) => () => {
    const option = filteredOptions.find(opt => opt.specialValue == specialValue);
    onChange(null, specialValue, option);
  };

  const specialOptions = filteredOptions?.filter(option => !!option.specialValue).map((option) => (
    <a 
      style={{ padding: "5px 10px", display: "block", cursor: "pointer" }}
      key={option.specialValue} 
      disabled={getOptionDisabled(option)}
      onClick={onSpecialClick(option.specialValue)}
    >
      {getOptionLabel(option)}
    </a>
  ));

  const aOnSelect = async (newValue, option) => {
    // For both multiple/single `newValue` is string, `option` is {value, children}
    // ! Custom option is always `{}`
    const isAutoTokenization = multiple && !!option.props && !(option.value !== undefined || option.label !== undefined);
    if (isAutoTokenization) {
      // Ignore, already processed in `aOnChange`
      return;
    }
    if (isSpecialValue(option)) {
      await onChange(null, newValue, option);
    } else {
      if (multiple) {
        await onChange(null, newValue, option);
      }
    }
  };

  const aOnClear = () => {
    if (open) {
      // tip: if closed, search is hidden, so let's clear tags AND search value
      isClearAllClicked.current = true;
    }
  };


  const aOnChange = async (newValue, option) => {
    // For multiple `newValue` is array of strings, `option` is array of {value, children}
    // For single `newValue` is string/undefined, `option` is {value, children}/undefined
    // ! Custom option is always `{}`
    //
    // For multiple called on:
    // - click (x) at right (clear all)
    // - tag removal
    // - option selection (`aOnSelect` is also called after)
    // - trying to add new tag from search input (for mode "tags" - unwanted!)
    // - automatic tokenization (like pasting "1,2,3") - issue #1115
    // 
    // For single called on:
    // - click (x) at right (clear all)
    // - option selection (`aOnSelect` is also called after)
    // ! NOT when trying to add new tag

    const isAddFromSearch 
      = multiple
      && newValue.length && newValue.length > aValue.length
      && newValue[newValue.length-1] == inputValue;
    const shouldIgnore = isSpecialValue(option) // use onSelect instead
      || mode === "tags" && isAddFromSearch // obsolete, we don't use tags mode
      || isClearAllClicked.current && inputValue // if there are tags AND input, clear input first
    ;
    isClearAllClicked.current = false;
    if (!shouldIgnore) {
      await onChange(null, newValue, option);
    }
  };

  // to keep compatibility with antD
  const aOnSearch = async (newInputValue) => {
    if (isClearAllClicked.current) {
      isClearAllClicked.current = false;
    }
    // if (newInputValue === "" && !open) {
    //   return; // ?
    // }

    await onInputChange(null, newInputValue);
  };

  const dropdownRender = useCallback((menu) => (
    <div>
      {menu}
      {specialOptions.length > 0
        && <>
          <Divider style={{ margin: "0px" }}/>
          <div style={{ display: "flex", flexDirection: "column" }}>
            {specialOptions}
          </div>
        </>
      }
    </div>
  ), [filteredOptions]);

  const filterOption = useCallback((input, option) => {
    const keysForFilter = config.settings.listKeysForSearch
      .map(k => mapListItemToOptionKeys[k]);
    const valueForFilter = keysForFilter
      .map(k => (typeof option[k] == "string" ? option[k] : ""))
      .join("\0");
    const matches = valueForFilter.toLowerCase().indexOf(input.toLowerCase()) >= 0;
    return matches;
  }, [config]);

  return (
    <Select
      filterOption={useAsyncSearch ? false : filterOption}
      dropdownRender={dropdownRender}
      allowClear={true}
      notFoundContent={isLoading ? (config.settings.loadingLabel ?? "Loading...") : (config.settings.notFoundLabel ?? "Not found")}
      disabled={readonly}
      mode={mode}
      style={customProps?.style || style}
      dropdownStyle={customProps?.dropdownStyle || dropdownStyle}
      key={"widget-autocomplete"}
      popupMatchSelectWidth={customProps?.popupMatchSelectWidth || customProps?.dropdownMatchSelectWidth || false}
      placeholder={customProps?.placeholder || dynamicPlaceholder}
      onDropdownVisibleChange={onDropdownVisibleChange}
      onChange={aOnChange}
      onClear={aOnClear}
      onSelect={aOnSelect}
      onSearch={aOnSearch}
      showSearch
      size={renderSize}
      loading={isLoading}
      value={aValue}
      searchValue={inputValue}
      open={open}
      options={optionsToRender}
      {...customProps}
    >
    </Select>
  );
};
