import { default as ReactSelect } from 'react-select';
import classNames from 'classnames';
import { InjectedFieldProps } from '../Field/InjectedFieldProps';
import { FormDefaults } from '../FormDefaults';
import Group, { GroupProps } from '../Group';

export interface SelectOption<TValue> {
  /** Value for the select. This will be sent to the API. */
  value: (TValue extends Array<infer P> ? P : TValue) | undefined;
  /** Label for the select. This is displayed to the user. */
  label: string;
}

// types roughly like the useService (envoc-core) result
interface OptionsApiResult<TValue> {
  // we must apply Partial here because all results from template code gen are optional
  result?: Partial<SelectOption<TValue>>[];
}

interface OptionsUseServiceResult<TValue> {
  loading?: boolean;
  // we must apply Partial here because all results from template code gen are optional
  resp?: Partial<SelectOption<TValue>>[] | OptionsApiResult<TValue> | null;
  error?: any;
}

export interface SelectGroupProps<TValue>
  extends InjectedFieldProps<TValue | undefined | null>,
    Omit<GroupProps, 'input' | 'meta' | 'children'> {
  // allows for "useService" or other handles to control the data - including cache
  // TODO: do we still want a version of select that has a "url" or maybe a promise func or something?
  // eventually we can just add the shape of, say, useQuery (TanStack) to the union type
  /** Options for the dropdown. Includes the label and value. */
  options: SelectOption<TValue>[] | OptionsUseServiceResult<TValue>;
  /** Whether the user should be able to have multiple values selected. */
  multiple: TValue extends Array<any> ? true : false;
  /** Text diplayed when no value is selected. */
  placeholder?: string;
}

// TODO: we could also name this "ReactSelectGroup" or similar but the types are strongly defined now so kept the names consistent
/** Generic select dropdown. Uses [react-select](https://react-select.com/home). */
export default function SelectGroup<TValue>({
  input,
  meta,
  className,
  required,
  disabled,
  options,
  multiple,
  placeholder,
  ...rest
}: SelectGroupProps<TValue>) {
  const effectiveOptions: Partial<SelectOption<TValue>>[] = !options
    ? []
    : Array.isArray(options)
    ? options
    : 'resp' in options &&
      !!options.resp &&
      'result' in options.resp &&
      !!options.resp.result
    ? options.resp.result
    : 'resp' in options && !!options.resp && Array.isArray(options.resp)
    ? options.resp
    : [];

  const isLoading =
    (options && 'loading' in options && options.loading) || false;

  return (
    <Group
      {...rest}
      input={input}
      meta={meta}
      required={required}
      disabled={disabled}
      className={classNames(
        className,
        {
          [FormDefaults.cssClassPrefix + 'multiple']: multiple,
          [FormDefaults.cssClassPrefix + 'loading']: isLoading,
        },
        FormDefaults.cssClassPrefix + 'select-group'
      )}>
      <ReactSelect<
        Partial<SelectOption<TValue>>,
        TValue extends Array<any> ? true : false
      >
        inputId={input.id}
        isMulti={multiple}
        isDisabled={disabled}
        options={effectiveOptions}
        onBlur={input.onBlur}
        value={getValue()}
        onChange={(e: any) => {
          if (multiple === true) {
            input.onChange(e?.map((x: any) => x.value));
          } else {
            input.onChange(e?.value as any);
          }
        }}
        getOptionLabel={(option) => option?.label ?? ''}
        className={classNames(
          className,
          FormDefaults.cssClassPrefix + 'select-group'
        )}
        classNamePrefix="react-select"
        menuPortalTarget={document.body}
        menuPlacement="auto"
        placeholder={placeholder}
      />
    </Group>
  );

  function getValue() {
    if (multiple) {
      return effectiveOptions.filter(
        (o) =>
          Array.isArray(input.value) && !!input.value.find((x) => o.value === x)
      );
    }
    return effectiveOptions.find((o) => o.value === input.value);
  }
}
