import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';

import type { MessageDescriptor } from '@lingui/core';
import { t } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { AnimatePresence } from 'framer-motion';
import { Check, ChevronsUpDown, Loader, XIcon } from 'lucide-react';
import * as React from 'react';

import { cn } from '../lib/utils';
import { Button } from './button';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from './command';
import { Popover, PopoverContent, PopoverTrigger } from './popover';

type OptionValue = string | number | boolean | null;

type ComboBoxOption<T = OptionValue> = {
  label: string;
  value: T;
  disabled?: boolean;
};

type MultiSelectComboboxProps<T = OptionValue> = {
  emptySelectionPlaceholder?: React.ReactElement | string;
  enableClearAllButton?: boolean;
  enableSearch?: boolean;
  className?: string;
  contentClassName?: string;
  loading?: boolean;
  inputPlaceholder?: MessageDescriptor;
  onChange: (_values: T[]) => void;
  options: ComboBoxOption<T>[];
  selectedValues: T[];
  testId?: string;
};

/**
 * Multi select combo box component which supports:
 *
 * - Label/value pairs
 * - Loading state
 * - Clear all button
 */
export function MultiSelectCombobox<T = OptionValue>({
  emptySelectionPlaceholder = t`Select values...`,
  enableClearAllButton,
  enableSearch = true,
  className,
  contentClassName,
  inputPlaceholder,
  loading,
  onChange,
  options,
  selectedValues,
  testId,
}: MultiSelectComboboxProps<T>) {
  const { _ } = useLingui();

  const [open, setOpen] = React.useState(false);

  const handleSelect = (selectedOption: T) => {
    let newSelectedOptions = [...selectedValues, selectedOption];

    if (selectedValues.includes(selectedOption)) {
      newSelectedOptions = selectedValues.filter((v) => v !== selectedOption);
    }

    onChange(newSelectedOptions);
  };

  const selectedOptions = React.useMemo(() => {
    return selectedValues.map((value): ComboBoxOption<T> => {
      const foundOption = options.find((option) => option.value === value);

      if (foundOption) {
        return foundOption;
      }

      let label = '';

      if (typeof value === 'string' || typeof value === 'number') {
        label = value.toString();
      }

      return {
        label,
        value,
      };
    });
  }, [selectedValues, options]);

  const buttonLabel = React.useMemo(() => {
    if (loading) {
      return '';
    }

    if (selectedOptions.length === 0) {
      return emptySelectionPlaceholder;
    }

    return selectedOptions.map((option) => option.label).join(', ');
  }, [selectedOptions, emptySelectionPlaceholder, loading]);

  const showClearButton = enableClearAllButton && selectedValues.length > 0;

  return (
    <Popover open={open && !loading} onOpenChange={setOpen}>
      <div className="relative">
        <PopoverTrigger asChild>
          <Button
            variant="outline"
            role="combobox"
            disabled={loading}
            aria-expanded={open}
            className={cn('w-[200px] px-3', className)}
            data-testid={testId}
          >
            <AnimatePresence>
              {loading ? (
                <div className="flex items-center justify-center">
                  <Loader className="h-5 w-5 animate-spin text-muted-foreground" />
                </div>
              ) : (
                <AnimateGenericFadeInOut className="flex w-full justify-between">
                  <span className="truncate">{buttonLabel}</span>

                  <div
                    className={cn('ml-2 flex flex-row items-center', {
                      'ml-6': showClearButton,
                    })}
                  >
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                  </div>
                </AnimateGenericFadeInOut>
              )}
            </AnimatePresence>
          </Button>
        </PopoverTrigger>

        {/* This is placed outside the trigger since we can't have nested buttons. */}
        {showClearButton && !loading && (
          <div className="absolute top-0 right-8 bottom-0 flex items-center justify-center">
            <button
              type="button"
              className="flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/20"
              onClick={() => onChange([])}
            >
              <XIcon className="h-3.5 w-3.5 text-muted-foreground" />
            </button>
          </div>
        )}
      </div>

      <PopoverContent className={cn('z-[50000000] w-full p-0', contentClassName)}>
        <Command>
          {enableSearch && <CommandInput placeholder={inputPlaceholder && _(inputPlaceholder)} />}
          <CommandEmpty>
            <Trans>No value found.</Trans>
          </CommandEmpty>
          <CommandGroup>
            {options.map((option, i) => (
              <CommandItem key={i} onSelect={() => handleSelect(option.value)}>
                <Check
                  className={cn('mr-2 h-4 w-4', selectedValues.includes(option.value) ? 'opacity-100' : 'opacity-0')}
                />
                {option.label}
              </CommandItem>
            ))}
          </CommandGroup>
        </Command>
      </PopoverContent>
    </Popover>
  );
}
