import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import type { TEnvelopeRecipientLite } from '@documenso/lib/types/recipient';
import { canRecipientFieldsBeModified } from '@documenso/lib/utils/recipients';
import { getRecipientColorStyles } from '@documenso/ui/lib/recipient-colors';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '@documenso/ui/primitives/command';
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import type { I18n } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { RecipientRole, SendStatus } from '@prisma/client';
import { Check, ChevronsUpDown, Info } from 'lucide-react';
import { useCallback, useState } from 'react';
import { sortBy } from 'remeda';

export interface EnvelopeRecipientSelectorProps {
  className?: string;
  selectedRecipient: TEnvelopeRecipientLite | null;
  onSelectedRecipientChange: (recipient: TEnvelopeRecipientLite) => void;
  recipients: TEnvelopeRecipientLite[];
  fields: Field[];
  align?: 'center' | 'end' | 'start';
}

export const EnvelopeRecipientSelector = ({
  className,
  selectedRecipient,
  onSelectedRecipientChange,
  recipients,
  fields,
  align = 'start',
}: EnvelopeRecipientSelectorProps) => {
  const { i18n } = useLingui();

  const [showRecipientsSelector, setShowRecipientsSelector] = useState(false);

  const getRecipientLabel = useCallback(
    (recipient: TEnvelopeRecipientLite) => extractRecipientLabel(recipient, recipients, i18n),
    [recipients],
  );

  return (
    <Popover open={showRecipientsSelector} onOpenChange={setShowRecipientsSelector}>
      <PopoverTrigger asChild>
        <Button
          type="button"
          variant="outline"
          role="combobox"
          className={cn(
            'justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
            getRecipientColorStyles(recipients.findIndex((r) => r.id === selectedRecipient?.id)).comboBoxTrigger,
            className,
          )}
        >
          {selectedRecipient && (
            <span className="flex-1 truncate text-left">{getRecipientLabel(selectedRecipient)}</span>
          )}

          <ChevronsUpDown className="ml-2 h-4 w-4" />
        </Button>
      </PopoverTrigger>

      <PopoverContent className="p-0" align={align}>
        <EnvelopeRecipientSelectorCommand
          fields={fields}
          selectedRecipient={selectedRecipient}
          onSelectedRecipientChange={(recipient) => {
            onSelectedRecipientChange(recipient);
            setShowRecipientsSelector(false);
          }}
          recipients={recipients}
        />
      </PopoverContent>
    </Popover>
  );
};

interface EnvelopeRecipientSelectorCommandProps {
  className?: string;
  selectedRecipient: TEnvelopeRecipientLite | null;
  onSelectedRecipientChange: (recipient: TEnvelopeRecipientLite) => void;
  recipients: TEnvelopeRecipientLite[];
  fields: Field[];
  placeholder?: string;
}

export const EnvelopeRecipientSelectorCommand = ({
  className,
  selectedRecipient,
  onSelectedRecipientChange,
  recipients,
  fields,
  placeholder,
}: EnvelopeRecipientSelectorCommandProps) => {
  const { t, i18n } = useLingui();

  const recipientsByRole = useCallback(() => {
    const recipientsByRole: Record<RecipientRole, TEnvelopeRecipientLite[]> = {
      CC: [],
      VIEWER: [],
      SIGNER: [],
      APPROVER: [],
      ASSISTANT: [],
    };

    recipients.forEach((recipient) => {
      recipientsByRole[recipient.role].push(recipient);
    });

    return recipientsByRole;
  }, [recipients]);

  const recipientsByRoleToDisplay = useCallback(() => {
    return Object.entries(recipientsByRole())
      .filter(
        ([role]) => role !== RecipientRole.CC && role !== RecipientRole.VIEWER && role !== RecipientRole.ASSISTANT,
      )
      .map(
        ([role, roleRecipients]) =>
          [
            role,
            sortBy(roleRecipients, [(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'], [(r) => r.id, 'asc']),
          ] as [RecipientRole, TEnvelopeRecipientLite[]],
      );
  }, [recipientsByRole]);

  const isRecipientDisabled = useCallback(
    (recipientId: number) => {
      const recipient = recipients.find((r) => r.id === recipientId);
      const recipientFields = fields.filter((f) => f.recipientId === recipientId);

      return !recipient || !canRecipientFieldsBeModified(recipient, recipientFields);
    },
    [fields, recipients],
  );

  const getRecipientLabel = useCallback(
    (recipient: TEnvelopeRecipientLite) => extractRecipientLabel(recipient, recipients, i18n),
    [recipients],
  );

  return (
    <Command value={selectedRecipient ? selectedRecipient.id.toString() : undefined} className={className}>
      <CommandInput placeholder={placeholder} />

      <CommandEmpty>
        <span className="inline-block px-4 text-muted-foreground">
          <Trans>No recipient matching this description was found.</Trans>
        </span>
      </CommandEmpty>

      {recipientsByRoleToDisplay().map(([role, roleRecipients], roleIndex) => (
        <CommandGroup key={roleIndex}>
          <div className="mt-2 mb-1 ml-2 font-medium text-muted-foreground text-xs">
            {t(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
          </div>

          {roleRecipients.length === 0 && (
            <div key={`${role}-empty`} className="px-4 pt-2.5 pb-4 text-center text-muted-foreground/80 text-xs">
              <Trans>No recipients with this role</Trans>
            </div>
          )}

          {roleRecipients.map((recipient) => (
            <CommandItem
              key={recipient.id}
              className={cn(
                'px-2 last:mb-1 [&:not(:first-child)]:mt-1',
                getRecipientColorStyles(recipients.findIndex((r) => r.id === recipient.id)).comboBoxItem,
                {
                  'text-muted-foreground': recipient.sendStatus === SendStatus.SENT,
                  'cursor-not-allowed': isRecipientDisabled(recipient.id),
                },
              )}
              onSelect={() => {
                if (!isRecipientDisabled(recipient.id)) {
                  onSelectedRecipientChange(recipient);
                }
              }}
            >
              <span
                className={cn('truncate text-foreground/70', {
                  'text-foreground/80': recipient.id === selectedRecipient?.id,
                  'opacity-50': isRecipientDisabled(recipient.id),
                })}
              >
                {getRecipientLabel(recipient)}
              </span>

              <div className="ml-auto flex items-center justify-center">
                {!isRecipientDisabled(recipient.id) ? (
                  <Check
                    aria-hidden={recipient.id !== selectedRecipient?.id}
                    className={cn('h-4 w-4 flex-shrink-0', {
                      'opacity-0': recipient.id !== selectedRecipient?.id,
                      'opacity-100': recipient.id === selectedRecipient?.id,
                    })}
                  />
                ) : (
                  <Tooltip>
                    <TooltipTrigger disabled={false}>
                      <Info className="z-50 ml-2 h-4 w-4" />
                    </TooltipTrigger>

                    <TooltipContent className="max-w-xs text-muted-foreground">
                      <Trans>
                        This document has already been sent to this recipient. You can no longer edit this recipient.
                      </Trans>
                    </TooltipContent>
                  </Tooltip>
                )}
              </div>
            </CommandItem>
          ))}
        </CommandGroup>
      ))}
    </Command>
  );
};

const extractRecipientLabel = (recipient: TEnvelopeRecipientLite, recipients: TEnvelopeRecipientLite[], i18n: I18n) => {
  if (recipient.name && recipient.email) {
    return `${recipient.name} (${recipient.email})`;
  }

  if (recipient.name) {
    return recipient.name;
  }

  if (recipient.email) {
    return recipient.email;
  }

  // Since objects are basically pointers we can use `indexOf` rather than `findIndex`
  const index = recipients.indexOf(recipient);

  return i18n._(msg`Recipient ${index + 1}`);
};
