import React, { useMemo } from 'react';

import { Field, FieldProps } from '../field';
import { FieldHint } from '../field-hint';
import {
  Select,
  SelectSeparator,
  SelectButton,
  SelectOptions,
  SelectOption,
  SelectContainer,
  SelectLabel,
  type SelectProps,
} from '../select';

/*-- Types --*/

export type SelectFieldOption = {
  type?: 'item';
  value: string | number;
  label: string;
  data?: unknown;
};

export type SelectFieldOptionSeparator = { type: 'separator' };

type SelectFieldFlattenOptions = Array<
  SelectFieldOption | SelectFieldOptionSeparator
>;

export type SelectFieldOptionGroup = {
  type: 'group';
  options: SelectFieldFlattenOptions;
};

export type SelectFieldOptions = Array<
  SelectFieldOption | SelectFieldOptionSeparator | SelectFieldOptionGroup
>;

export interface SelectFieldProps extends Omit<SelectProps, 'value'> {
  value: SelectFieldOption['value'];
  options: SelectFieldOptions;
  label?: React.ReactNode;
  error?: boolean | React.ReactNode;
  hint?: React.ReactNode;
  /**
   * If `true`, it adds some space below the select so a layout doesn't jump
   * when a helper text (`error`, `hint`) appears.
   */
  hasHelperTextSpace?: boolean;
  width?: FieldProps['width'];
}

/*-- Main --*/

export const SelectField = React.forwardRef<HTMLDivElement, SelectFieldProps>(
  function SelectField(
    {
      label,
      error,
      hint,
      hasHelperTextSpace,
      options = [],
      value,
      width = 'full',
      ...rest
    },
    ref,
  ) {
    const isInvalid = !!error;
    const hintText = error && typeof error !== 'boolean' ? error : hint;

    const flatOptions = useMemo(() => flattenOptions(options), [options]);

    const selectedOption = useMemo(
      () => getOptionByValue(flatOptions, value),
      [flatOptions, value],
    );

    return (
      <Select value={value} {...rest}>
        <Field ref={ref} invalid={isInvalid} width={width}>
          {label && <SelectLabel>{label}</SelectLabel>}

          <SelectContainer>
            <SelectButton title={selectedOption?.label} width="full">
              {selectedOption?.label || <>&nbsp;</>}
            </SelectButton>

            <SelectOptions>
              {flatOptions.map((option, index) =>
                option.type === 'separator' ? (
                  <SelectSeparator key={index} />
                ) : (
                  <SelectOption key={option.value} value={option.value}>
                    {option.label}
                  </SelectOption>
                ),
              )}
            </SelectOptions>
          </SelectContainer>

          {hasHelperTextSpace ? (
            <FieldHint preserveSpace={hasHelperTextSpace}>{hintText}</FieldHint>
          ) : (
            hintText && <FieldHint>{hintText}</FieldHint>
          )}
        </Field>
      </Select>
    );
  },
);

/*-- Utils --*/

function flattenOptions(
  options: SelectFieldOptions,
): SelectFieldFlattenOptions {
  const result: SelectFieldFlattenOptions = [];

  for (const option of options) {
    if (option.type === 'group') {
      const isLastItem = options.indexOf(option) === options.length - 1;

      result.push(...flattenOptions(option.options));

      if (!isLastItem) {
        result.push({ type: 'separator' });
      }

      continue;
    }

    result.push(option);
  }

  return result;
}

function getOptionByValue(
  options: SelectFieldFlattenOptions,
  value: SelectFieldOption['value'],
) {
  return options.find(option =>
    option.type === 'separator' ? false : option.value === value,
  ) as SelectFieldOption | undefined;
}
