import React, { Fragment, useEffect, useMemo, useState } from "react";
import classNames from "classnames";
import { Checkbox } from "../checkbox";
import { DropdownBox } from "../dropdown";
import { List } from "../list";
import { EmptyTip } from "../tips";
import { useConfig } from "../util";
import { getPlainText } from "../_util/getPlainText";
import { searchFilter } from "../_util/search-filter";
import { highlight } from "../_util/text-highlight";
import {
  CascaderData,
  CascaderOption,
  CascaderSearchProps,
} from "./CascaderProps";
import { noop } from "../_util/noop";

function getOptionPaths(data: CascaderData, all: boolean): CascaderOption[][] {
  const optionPaths: CascaderOption[][] = [];

  const traverse = (data: CascaderData, optionPath: CascaderOption[] = []) => {
    (data.options || []).forEach(option => {
      const options = [...optionPath, option];
      if (option.child) {
        if (all) {
          optionPaths.push(options);
        }
        traverse(option.child as CascaderData, options);
      } else {
        optionPaths.push(options);
      }
    });
  };

  traverse(data);
  return optionPaths;
}

function filterPaths(
  paths: CascaderOption[][],
  inputValue: string,
  filter: CascaderSearchProps["filter"]
): CascaderOption[][] {
  return paths.filter(path => path.find(option => filter(inputValue, option)));
}

const defaultFilter = (
  inputValue: string,
  { label, value }: CascaderOption
) => {
  return searchFilter(getPlainText(label) || value, inputValue);
};

export function CascaderSearch({
  data,
  searchValue,
  onChange,
  changeOnSelect,
  multiple,
  className,
  style,
  isSelected,
  filter = defaultFilter,
  scheduleUpdate,
  tips,
  bottomTips,
  onScrollBottom,
  onSearch = noop,
}: CascaderSearchProps) {
  const [currentIndex, setCurrentIndex] = useState<number>(-1);

  useEffect(() => {
    setCurrentIndex(-1);
  }, []);

  const allOptionPaths = useMemo(() => getOptionPaths(data, changeOnSelect), [
    data,
    changeOnSelect,
  ]);

  useEffect(() => {
    onSearch(searchValue);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [searchValue]);

  const optionPaths = useMemo(
    () => filterPaths(allOptionPaths, searchValue, filter),
    [filter, searchValue, allOptionPaths]
  );

  useEffect(() => {
    if (scheduleUpdate) {
      scheduleUpdate();
    }
  }, [optionPaths, scheduleUpdate]);

  const { classPrefix } = useConfig();

  const [listTips, listBottomTips] = useMemo(() => {
    return [
      tips ? tips(null, searchValue) : null,
      bottomTips ? bottomTips(null, searchValue) : null,
    ];
  }, [bottomTips, searchValue, tips]);

  return (
    <DropdownBox
      className={classNames(className, "size-auto-width")}
      style={style}
    >
      <div
        className={`${classPrefix}-cascader-menu ${classPrefix}-cascader-menu--thin`}
      >
        <List
          type="option"
          className={multiple ? `${classPrefix}-list--checkoption` : ""}
          onScrollBottom={() => onScrollBottom(null, searchValue)}
        >
          {!listTips && !listBottomTips && optionPaths.length === 0 && (
            <List.StatusTip>
              <EmptyTip />
            </List.StatusTip>
          )}
          {listTips && <List.StatusTip>{listTips}</List.StatusTip>}
          {optionPaths.map((options, index) => {
            const valuePath = options.map(({ value }) => value);
            const selected = isSelected && isSelected(valuePath);
            const item = options.map(({ label, value }, i) => {
              const content = highlight(label, searchValue);

              if (i === 0) {
                return <Fragment key={`${i}-${value}`}>{content}</Fragment>;
              }
              return (
                <Fragment key={`${i}-${value}`}>
                  {" / "}
                  {content}
                </Fragment>
              );
            });
            return (
              <List.Item
                key={valuePath.join("$")}
                current={currentIndex === index}
                onClick={event =>
                  !multiple &&
                  onChange(valuePath, { event, options, selected: !selected })
                }
              >
                {multiple ? (
                  <Checkbox
                    value={selected}
                    onClick={event =>
                      onChange(valuePath, {
                        event,
                        options,
                        selected: !selected,
                      })
                    }
                  >
                    {item}
                  </Checkbox>
                ) : (
                  item
                )}
              </List.Item>
            );
          })}
          {listBottomTips && <List.StatusTip>{listBottomTips}</List.StatusTip>}
        </List>
      </div>
    </DropdownBox>
  );
}

CascaderSearch.displayName = "CascaderSearch";
