'use client';

import { ComponentType, ReactNode, useEffect, useMemo, useState } from 'react';
import Select, {
  OptionProps,
  GroupBase,
  InputActionMeta,
  MultiValue,
  SingleValue,
} from 'react-select';
import CreatableSelect from 'react-select/creatable';
import makeAnimated from 'react-select/animated';
import { useDebounce } from 'use-debounce';
import { cn } from '@/lib/helper';

export interface OptionType {
  value: string | number;
  label: string;
  className?: string;
  labelClassName?: string;
}

export type OptionComponent<T = OptionType> = ComponentType<
  OptionProps<T, boolean, GroupBase<T>>
>;

interface SelectInputBaseProps<T = OptionType> {
  label?: ReactNode;
  bottomLabel?: ReactNode;
  options: T[];
  optionComponent?: OptionComponent<T>;
  isDisabled?: boolean;
  isLoading?: boolean;
  isClearable?: boolean;
  isRtl?: boolean;
  isSearchable?: boolean;
  isMulti?: boolean;
  placeholder?: string;
  required?: boolean;
  className?: {
    wrapper?: string;
    label?: string;
    select?: string;
  };
  isError?: boolean;
  errorMessage?: string;
  isAnimated?: boolean;
  openMenu?: boolean;
  delay?: number;
  onInputChange?: (search: string) => void;
}

interface SelectInputProps<T = OptionType> extends SelectInputBaseProps<T> {
  createables?: boolean;
  value?: T | T[] | null;
  onChange?: (val: T | T[] | null) => void;
}

const animatedComponents = makeAnimated();

const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
  const {
    label,
    bottomLabel,
    value,
    onChange,
    options,
    optionComponent,
    isDisabled,
    isLoading,
    isClearable,
    isRtl,
    isSearchable = true,
    isMulti,
    placeholder,
    required,
    className,
    isError,
    errorMessage,
    isAnimated = true,
    openMenu,
    delay = 300,
    createables = false,
    onInputChange,
  } = props;

  const [internalInputValue, setInternalInputValue] = useState('');
  const [debouncedInputValue] = useDebounce(internalInputValue, delay);

  const components = useMemo(() => {
    const base = isAnimated ? animatedComponents : {};
    return { ...base, IndicatorSeparator: () => null };
  }, [isAnimated]);

  const internalInputChangeHandler = (val: string, meta: InputActionMeta) => {
    if (meta.action === 'input-change') setInternalInputValue(val);
    if (meta.action === 'menu-close') setInternalInputValue('');
  };

  useEffect(() => {
    onInputChange?.(debouncedInputValue);
  }, [onInputChange, debouncedInputValue]);

  const SelectComponent = createables ? CreatableSelect : Select;

  /** 🎯 handleChange tanpa any */
  const handleChange = (val: MultiValue<T> | SingleValue<T>): void => {
    if (!val) {
      onChange?.(null);
      return;
    }

    if (isMulti) {
      onChange?.(val as T[]);
    } else {
      onChange?.(val as T);
    }
  };

  return (
    <div
      className={cn(
        'w-full flex flex-col gap-2 text-start',
        className?.wrapper
      )}
    >
      {label && (
        <span
          className={cn(
            'w-full text-sm font-normal leading-5',
            { 'text-error': isError },
            className?.label
          )}
        >
          {label}
          {required && (
            <span className='tooltip tooltip-error' data-tip='required'>
              <span className='text-error'> *</span>
            </span>
          )}
        </span>
      )}

      <SelectComponent<T, boolean, GroupBase<T>>
        instanceId='select'
        value={value ?? (isMulti ? [] : null)}
        onChange={handleChange}
        options={options}
        menuIsOpen={openMenu}
        inputValue={internalInputValue}
        onInputChange={internalInputChangeHandler}
        isMulti={isMulti}
        isDisabled={isDisabled}
        isLoading={isLoading}
        isClearable={isClearable}
        isRtl={isRtl}
        isSearchable={isSearchable}
        placeholder={placeholder}
        className={cn('w-full', className?.select)}
        classNames={{
          control: ({ isFocused, isDisabled }) =>
            cn(
              'w-full min-h-12! rounded border bg-white transition-shadow cursor-pointer!',
              {
                'border-red-500! ring-2 ring-red-200': isError,
                'border-indigo-500 ring-2 ring-indigo-200': isFocused,
                'border-gray-300': !isError && !isFocused,
                'bg-gray-100 text-gray-400 cursor-not-allowed': isDisabled,
              }
            ),
          valueContainer: () => cn('flex-1 px-4! py-2! gap-1'),
          placeholder: () =>
            cn({ 'text-gray-400': !isError, 'text-red-300!': isError }),
          singleValue: () =>
            cn({ 'text-gray-900': !isError, 'text-error!': isError }),
          input: () => cn('text-gray-900'),
          indicatorsContainer: () => cn('flex items-center gap-1 pr-2'),
          dropdownIndicator: ({ isFocused }) =>
            cn('p-1 rounded hover:bg-gray-100', {
              'text-gray-900': isFocused,
              'text-gray-500': !isFocused,
              'text-error!': isError,
            }),
          menu: () =>
            cn('border border-gray-200 rounded-lg bg-white shadow-lg!'),
          menuList: () => cn('p-2! max-h-60 overflow-auto'),
          option: ({ isFocused, isSelected }) =>
            cn('mt-1 px-3 py-2 rounded cursor-pointer!', {
              'bg-indigo-600 text-white': isFocused,
              'bg-blue-500!': isSelected,
              'text-gray-700': !isFocused && !isSelected,
            }),
          multiValue: ({ getValue, index }) => {
            const selectedValues = getValue() as T[];
            return cn(
              'bg-indigo-50 rounded py-0.5 pl-2 pr-1 flex items-center gap-1!',
              selectedValues[index]?.className
            );
          },
          multiValueLabel: ({ getValue, index }) => {
            const selectedValues = getValue() as T[];
            return cn('text-indigo-700', selectedValues[index]?.labelClassName);
          },
        }}
        components={{
          ...components,
          ...(optionComponent ? { Option: optionComponent } : {}),
        }}
        menuPortalTarget={
          typeof document !== 'undefined' ? document.body : undefined
        }
        styles={{
          menuPortal: (base) => ({ ...base, zIndex: 9999 }),
        }}
      />

      {isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
      {!isError && bottomLabel && (
        <p className='w-full text-sm opacity-60'>{bottomLabel}</p>
      )}
    </div>
  );
};

export default SelectInput;
