'use client';

import { CheckIcon, ChevronDownIcon, XIcon } from 'lucide-react';
import {
  KeyboardEvent,
  MouseEventHandler,
  ReactNode,
  forwardRef,
  useCallback,
  useEffect,
  useId,
  useMemo,
  useState,
} from 'react';

import { Button } from '@/components/ui/button';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/components/ui/command';
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { t } from 'i18next';
import { RAGFlowSelectOptionType } from '../ui/select';
import { Separator } from '../ui/separator';

export type SelectWithSearchOptionType = RAGFlowSelectOptionType & {
  description?: ReactNode;
};

export type SelectWithSearchFlagOptionType = {
  label: ReactNode;
  value?: string;
  disabled?: boolean;
  options?: SelectWithSearchOptionType[];
  keywords?: string[];
  description?: ReactNode;
};

export type SelectWithSearchFlagProps = {
  options?: SelectWithSearchFlagOptionType[];
  value?: string;
  onChange?(value: string): void;
  triggerClassName?: string;
  allowClear?: boolean;
  disabled?: boolean;
  placeholder?: string;
  emptyData?: string;
  allowCustomValue?: boolean;
  onNoMatchEnter?(searchValue: string): void;
  disableAutoSelectOnEnter?: boolean;
  testId?: string;
  optionTestIdPrefix?: string;
};

function filterFn(value: string, search: string, keywords?: string[]) {
  const searchLower = search.toLowerCase();
  const extendValue = (value + ' ' + (keywords?.join(' ') || '')).toLowerCase();
  if (extendValue.includes(searchLower)) return 1;
  return 0;
}

function findLabelWithoutOptions(
  options: SelectWithSearchFlagOptionType[],
  value: string,
) {
  return options.find((opt) => opt.value === value)?.label || '';
}

function findLabelWithOptions(
  options: SelectWithSearchFlagOptionType[],
  value: string,
) {
  return options
    .map((group) => group?.options?.find((item) => item.value === value))
    .filter(Boolean)[0]?.label;
}

function hasMatchingOptions(
  options: SelectWithSearchFlagOptionType[],
  searchValue: string,
) {
  const search = searchValue.trim();
  if (!search) {
    return true;
  }
  return options.some((group) => {
    if (group.options) {
      return group.options.some(
        (option) =>
          filterFn(
            option.value ?? '',
            search,
            typeof option.label === 'string' ? [option.label] : [],
          ) === 1,
      );
    }
    return (
      filterFn(
        group.value ?? '',
        search,
        group.keywords ??
          (typeof group.label === 'string' ? [group.label] : []),
      ) === 1
    );
  });
}

export const SelectWithSearch = forwardRef<
  React.ElementRef<typeof Button>,
  SelectWithSearchFlagProps
>(
  (
    {
      value: val = '',
      onChange,
      options = [],
      triggerClassName,
      allowClear = false,
      disabled = false,
      placeholder = t('common.selectPlaceholder'),
      emptyData = t('common.noDataFound'),
      allowCustomValue = false,
      onNoMatchEnter,
      disableAutoSelectOnEnter = false,
      testId,
      optionTestIdPrefix,
    },
    ref,
  ) => {
    const id = useId();
    const [open, setOpen] = useState<boolean>(false);
    const [value, setValue] = useState<string>('');
    const [searchValue, setSearchValue] = useState<string>('');

    const selectLabel = useMemo(() => {
      if (options.every((x) => x.options === undefined)) {
        return findLabelWithoutOptions(options, value);
      } else if (options.every((x) => Array.isArray(x.options))) {
        return findLabelWithOptions(options, value);
      } else {
        // Some have options, some don't
        const optionsWithOptions = options.filter((x) =>
          Array.isArray(x.options),
        );
        const optionsWithoutOptions = options.filter(
          (x) => x.options === undefined,
        );

        const label = findLabelWithOptions(optionsWithOptions, value);
        if (label) {
          return label;
        }
        return findLabelWithoutOptions(optionsWithoutOptions, value);
      }
    }, [options, value]);

    const showSearch = useMemo(() => {
      if (allowCustomValue) {
        return true;
      }
      if (Array.isArray(options) && options.length > 5) {
        return true;
      }
      if (Array.isArray(options)) {
        const optionsNum = options.reduce((acc, option) => {
          return acc + (option?.options?.length || 0);
        }, 0);
        return optionsNum > 5;
      }
      return false;
    }, [allowCustomValue, options]);

    const hasCustomSearchValue = useMemo(() => {
      const customValue = searchValue.trim();
      if (!allowCustomValue || !customValue) {
        return false;
      }

      const values = options.flatMap((option) =>
        option.options
          ? option.options.map((item) => item.value)
          : option.value,
      );
      return !values.includes(customValue);
    }, [allowCustomValue, options, searchValue]);

    const handleSelect = useCallback(
      (val: string) => {
        setValue(val);
        setOpen(false);
        onChange?.(val);
      },
      [onChange],
    );

    const handleClear: MouseEventHandler<SVGElement> = useCallback(
      (e) => {
        e.stopPropagation();
        setValue('');
        onChange?.('');
      },
      [onChange],
    );

    const handleInputKeyDown = useCallback(
      (e: KeyboardEvent<HTMLInputElement>) => {
        const keywords = searchValue.trim();
        if (e.key === 'Enter' && keywords) {
          if (disableAutoSelectOnEnter) {
            e.preventDefault();
            onNoMatchEnter?.(keywords);
            setSearchValue('');
            setOpen(false);
          } else if (!hasMatchingOptions(options, keywords)) {
            onNoMatchEnter?.(keywords);
          }
        }
      },
      [searchValue, options, onNoMatchEnter, disableAutoSelectOnEnter],
    );

    useEffect(() => {
      setValue(val);
    }, [val]);

    return (
      <Popover open={open} onOpenChange={setOpen}>
        <PopoverTrigger asChild>
          <Button
            id={id}
            variant="outline"
            role="combobox"
            aria-expanded={open}
            ref={ref}
            disabled={disabled}
            data-testid={testId}
            className={cn(
              '!bg-bg-input hover:bg-background border-border-button w-full  justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px] [&_svg]:pointer-events-auto group',
              triggerClassName,
            )}
          >
            {selectLabel || value ? (
              <span className="flex min-w-0 options-center gap-2 truncate">
                {selectLabel || value}
              </span>
            ) : (
              <span className="text-text-disabled">{placeholder}</span>
            )}
            <div className="flex items-center justify-between">
              {value && allowClear && (
                <>
                  <XIcon
                    className="h-4 mx-2 cursor-pointer text-text-disabled hidden group-hover:block"
                    onClick={handleClear}
                  />
                  <Separator
                    orientation="vertical"
                    className=" min-h-6 h-full hidden group-hover:flex"
                  />
                </>
              )}
              <ChevronDownIcon
                size={16}
                className="text-text-disabled shrink-0 ml-2"
                aria-hidden="true"
              />
            </div>
          </Button>
        </PopoverTrigger>
        <PopoverContent
          className="border-border-button w-full min-w-[var(--radix-popper-anchor-width)] p-0"
          align="start"
        >
          <Command className="p-5" filter={filterFn}>
            {showSearch && (
              <CommandInput
                placeholder={t('common.search') + '...'}
                className=" placeholder:text-text-disabled"
                value={searchValue}
                onValueChange={setSearchValue}
                onKeyDown={handleInputKeyDown}
              />
            )}
            <CommandList className="mt-2 outline-none">
              <CommandEmpty>
                <div dangerouslySetInnerHTML={{ __html: emptyData }}></div>
              </CommandEmpty>
              {hasCustomSearchValue && (
                <CommandItem
                  value={searchValue.trim()}
                  onSelect={handleSelect}
                  className="mb-1 min-h-10"
                >
                  <span className="leading-none">{searchValue.trim()}</span>
                </CommandItem>
              )}
              {options.map((group, groupIndex) => {
                if (group.options) {
                  return (
                    <CommandGroup
                      key={group.value || `group-${groupIndex}`}
                      heading={group.label}
                      className="mb-1"
                    >
                      {group.options.map((option, optionIndex) => (
                        <CommandItem
                          key={
                            option.value ||
                            `option-${groupIndex}-${optionIndex}`
                          }
                          value={option.value}
                          disabled={option.disabled}
                          keywords={
                            typeof option.label === 'string'
                              ? [option.label]
                              : []
                          }
                          onSelect={handleSelect}
                          data-testid={
                            optionTestIdPrefix && option.value
                              ? `${optionTestIdPrefix}${option.value}`
                              : 'combobox-option'
                          }
                          className={cn(
                            'relative flex flex-col min-h-10',
                            option.description
                              ? 'items-start gap-1'
                              : 'justify-center items-start',
                            value === option.value ? 'bg-bg-card' : '',
                          )}
                        >
                          <span className="leading-none">{option.label}</span>
                          {option.description && (
                            <span className="text-text-secondary text-xs leading-none">
                              {option.description}
                            </span>
                          )}
                          {value === option.value && (
                            <CheckIcon
                              size={16}
                              className="absolute top-1/2 -translate-y-1/2 right-2"
                            />
                          )}
                        </CommandItem>
                      ))}
                    </CommandGroup>
                  );
                } else {
                  return (
                    <CommandItem
                      key={group.value || `item-${groupIndex}`}
                      value={group.value}
                      disabled={group.disabled}
                      keywords={
                        group.keywords ??
                        (typeof group.label === 'string' ? [group.label] : [])
                      }
                      onSelect={handleSelect}
                      data-testid={
                        optionTestIdPrefix && group.value
                          ? `${optionTestIdPrefix}${group.value}`
                          : 'combobox-option'
                      }
                      className={cn(
                        'relative flex flex-col min-h-10 mb-1',
                        group.description
                          ? 'items-start gap-1'
                          : 'justify-center items-start',
                        {
                          'bg-bg-card ': value === group.value,
                        },
                      )}
                    >
                      <span className="leading-none">{group.label}</span>
                      {group.description && (
                        <span className="text-text-secondary text-xs leading-none">
                          {group.description}
                        </span>
                      )}
                      {value === group.value && (
                        <CheckIcon
                          size={16}
                          className="absolute top-1/2 -translate-y-1/2 right-2"
                        />
                      )}
                    </CommandItem>
                  );
                }
              })}
            </CommandList>
          </Command>
        </PopoverContent>
      </Popover>
    );
  },
);

SelectWithSearch.displayName = 'SelectWithSearch';
