import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
import { ZEditorRecipientsFormSchema } from '@documenso/lib/client-only/hooks/use-editor-recipients';
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useOptionalSession } from '@documenso/lib/client-only/providers/session';

import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
import { nanoid } from '@documenso/lib/universal/id';
import {
  isCcRecipient,
  normalizeRecipientSigningOrders,
  canRecipientBeModified as utilCanRecipientBeModified,
} from '@documenso/lib/utils/recipients';
import { trpc } from '@documenso/trpc/react';

import type { RecipientAutoCompleteOption } from '@documenso/ui/components/recipient/recipient-autocomplete-input';

import { useToast } from '@documenso/ui/primitives/use-toast';
import type { SensorAPI } from '@hello-pangea/dnd';

import { useLingui } from '@lingui/react/macro';
import { DocumentSigningOrder, EnvelopeType, RecipientRole, SendStatus } from '@prisma/client';

import { useEffect, useMemo, useRef, useState } from 'react';
import { useFieldArray, useWatch } from 'react-hook-form';
import { useRevalidator, useSearchParams } from 'react-router';
import { isDeepEqual } from 'remeda';

import { useCurrentTeam } from '~/providers/team';

import { useEnvelopeRecipientActions } from './use-envelope-recipient-actions';

export const useEnvelopeEditorRecipients = () => {
  const { envelope, setRecipientsDebounced, updateEnvelope, editorRecipients, isEmbedded, editorConfig } =
    useCurrentEnvelopeEditor();

  const organisation = useCurrentOrganisation();
  const team = useCurrentTeam();

  const { t } = useLingui();
  const { toast } = useToast();
  const { remaining } = useLimits();
  const { sessionData } = useOptionalSession();

  const user = sessionData?.user;

  const [searchParams, setSearchParams] = useSearchParams();
  const [recipientSearchQuery, setRecipientSearchQuery] = useState('');
  const [isAiEnableDialogOpen, setIsAiEnableDialogOpen] = useState(false);

  // AI recipient detection dialog state
  const [isAiDialogOpen, setIsAiDialogOpen] = useState(() => searchParams.get('ai') === 'true');
  const { revalidate } = useRevalidator();

  const onAiDialogOpenChange = (open: boolean) => {
    if (open && !team.preferences.aiFeaturesEnabled) {
      setIsAiEnableDialogOpen(true);
      setIsAiDialogOpen(false);
      return;
    }

    setIsAiDialogOpen(open);

    if (!open && searchParams.get('ai') === 'true') {
      setSearchParams(
        (prev) => {
          const newParams = new URLSearchParams(prev);

          newParams.delete('ai');

          return newParams;
        },
        { replace: true },
      );
    }
  };

  const onDetectRecipientsClick = () => {
    if (!team.preferences.aiFeaturesEnabled) {
      setIsAiEnableDialogOpen(true);
      return;
    }

    setIsAiDialogOpen(true);
  };

  const onAiFeaturesEnabled = () => {
    void revalidate().then(() => {
      setIsAiEnableDialogOpen(false);
      setIsAiDialogOpen(true);
    });
  };

  const debouncedRecipientSearchQuery = useDebouncedValue(recipientSearchQuery, 500);

  const $sensorApi = useRef<SensorAPI | null>(null);
  const isFirstRender = useRef(true);
  const { recipients, fields } = envelope;

  const { data: recipientSuggestionsData, isLoading } = trpc.recipient.suggestions.find.useQuery(
    {
      query: debouncedRecipientSearchQuery,
    },
    {
      enabled: debouncedRecipientSearchQuery.length > 1 && !isEmbedded,
      retry: false,
    },
  );

  const recipientSuggestions = recipientSuggestionsData?.results || [];

  const { form } = editorRecipients;

  const recipientHasAuthSettings = useMemo(() => {
    const recipientHasAuthOptions = recipients.find((recipient) => {
      const recipientAuthOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions);

      return recipientAuthOptions.actionAuth.length > 0;
    });

    const formHasActionAuth = form.getValues('signers').find((signer) => signer.actionAuth.length > 0);

    return recipientHasAuthOptions !== undefined || formHasActionAuth !== undefined;
  }, [recipients, form]);

  const [showAdvancedSettings, setShowAdvancedSettings] = useState(recipientHasAuthSettings);
  const [showSigningOrderConfirmation, setShowSigningOrderConfirmation] = useState(false);

  const {
    setValue,
    formState: { errors, isSubmitting },
    control,
    watch,
  } = form;

  const formValues = useWatch({
    control,
  });

  const watchedSigners = watch('signers');
  const isSigningOrderSequential = watch('signingOrder') === DocumentSigningOrder.SEQUENTIAL;

  const hasAssistantRole = useMemo(() => {
    return watchedSigners.some((signer) => signer.role === RecipientRole.ASSISTANT);
  }, [watchedSigners]);

  const normalizeSigningOrders = (signers: typeof watchedSigners) => {
    return normalizeRecipientSigningOrders(signers, (signer) => canRecipientBeModified(signer.id));
  };

  const activeRecipientCount = watchedSigners.filter((signer) => !isCcRecipient(signer)).length;

  const { fields: signers, remove: removeSigner } = useFieldArray({
    control,
    name: 'signers',
    keyName: 'nativeId',
  });

  const emptySignerIndex = watchedSigners.findIndex(
    (signer) =>
      !signer.name && !signer.email && envelope.fields.filter((field) => field.recipientId === signer.id).length === 0,
  );

  const currentEditorEmail = isEmbedded ? editorConfig.embedded?.user?.email : user?.email;

  const currentEditorName = isEmbedded ? editorConfig.embedded?.user?.name : user?.name;

  const hasCurrentEditorInfo = Boolean(currentEditorEmail || currentEditorName);

  const isUserAlreadyARecipient = watchedSigners.some(
    (signer) => signer.email.toLowerCase() === currentEditorEmail?.toLowerCase(),
  );

  const hasDocumentBeenSent = recipients.some(
    (recipient) => recipient.role !== RecipientRole.CC && recipient.sendStatus === SendStatus.SENT,
  );

  const canRecipientBeModified = (recipientId?: number) => {
    if (envelope.type === EnvelopeType.TEMPLATE) {
      return true;
    }

    if (recipientId === undefined) {
      return true;
    }

    const recipient = recipients.find((recipient) => recipient.id === recipientId);

    if (!recipient) {
      return false;
    }

    return utilCanRecipientBeModified(recipient, fields);
  };

  const appendNormalizedSigner = (signer: (typeof watchedSigners)[number], shouldFocus = false) => {
    const updatedSigners = normalizeSigningOrders([...form.getValues('signers'), signer]);

    form.setValue('signers', updatedSigners, {
      shouldValidate: true,
      shouldDirty: true,
    });

    if (shouldFocus) {
      const signerIndex = updatedSigners.findIndex((updatedSigner) => updatedSigner.formId === signer.formId);

      if (signerIndex !== -1) {
        requestAnimationFrame(() => form.setFocus(`signers.${signerIndex}.email`));
      }
    }
  };

  const onAddSigner = () => {
    appendNormalizedSigner({
      formId: nanoid(12),
      name: '',
      email: '',
      role: RecipientRole.SIGNER,
      actionAuth: [],
      signingOrder: activeRecipientCount + 1,
    });
  };

  const onRemoveSigner = (index: number) => {
    const signer = signers[index];

    if (!canRecipientBeModified(signer.id)) {
      toast({
        title: t`Cannot remove signer`,
        description: t`This signer has already signed the document.`,
        variant: 'destructive',
      });

      return;
    }

    const formStateIndex = form.getValues('signers').findIndex((s) => s.formId === signer.formId);
    if (formStateIndex !== -1) {
      removeSigner(formStateIndex);

      const updatedSigners = form.getValues('signers').filter((s) => s.formId !== signer.formId);

      form.setValue('signers', normalizeSigningOrders(updatedSigners), {
        shouldValidate: true,
        shouldDirty: true,
      });
    }
  };

  const onAddSelfSigner = () => {
    if (emptySignerIndex !== -1) {
      setValue(`signers.${emptySignerIndex}.name`, currentEditorName ?? '', {
        shouldValidate: true,
        shouldDirty: true,
      });
      setValue(`signers.${emptySignerIndex}.email`, currentEditorEmail ?? '', {
        shouldValidate: true,
        shouldDirty: true,
      });

      form.setFocus(`signers.${emptySignerIndex}.email`);
    } else {
      appendNormalizedSigner(
        {
          formId: nanoid(12),
          name: currentEditorName ?? '',
          email: currentEditorEmail ?? '',
          role: RecipientRole.SIGNER,
          actionAuth: [],
          signingOrder: activeRecipientCount + 1,
        },
        true,
      );

      void form.trigger('signers');
    }
  };

  const handleRecipientAutoCompleteSelect = (index: number, suggestion: RecipientAutoCompleteOption) => {
    setValue(`signers.${index}.email`, suggestion.email, {
      shouldValidate: true,
      shouldDirty: true,
    });
    setValue(`signers.${index}.name`, suggestion.name || '', {
      shouldValidate: true,
      shouldDirty: true,
    });
  };

  const { onAiDetectionComplete, onDragEnd, handleRoleChange, handleSigningOrderChange, handleSigningOrderDisable } =
    useEnvelopeRecipientActions({
      form,
      watchedSigners,
      canRecipientBeModified,
      normalizeSigningOrders,
      setShowSigningOrderConfirmation,
    });

  // Dupecode/Inefficient: Done because native isValid won't work for our usecase.
  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
      return;
    }

    const validatedFormValues = ZEditorRecipientsFormSchema.safeParse(formValues);

    if (!validatedFormValues.success) {
      return;
    }

    const { data } = validatedFormValues;

    // Weird edge case where the whole envelope is created via API
    // with no signing order. If they come to this page it will show an error
    // since they aren't equal and the recipient is no longer editable.
    const envelopeRecipients = data.signers.map((recipient) => {
      if (!canRecipientBeModified(recipient.id)) {
        return {
          ...recipient,
          signingOrder: recipient.signingOrder,
        };
      }
      return recipient;
    });

    const hasSigningOrderChanged = envelope.documentMeta.signingOrder !== data.signingOrder;
    const hasAllowDictateNextSignerChanged =
      envelope.documentMeta.allowDictateNextSigner !== data.allowDictateNextSigner;

    const hasSignersChanged =
      envelopeRecipients.length !== recipients.length ||
      envelopeRecipients.some((signer) => {
        const recipient = recipients.find((recipient) => recipient.id === signer.id);

        if (!recipient) {
          return true;
        }

        const signerActionAuth = signer.actionAuth;
        const recipientActionAuth = recipient.authOptions?.actionAuth || [];

        return (
          signer.email !== recipient.email ||
          signer.name !== recipient.name ||
          signer.role !== recipient.role ||
          signer.signingOrder !== recipient.signingOrder ||
          !isDeepEqual(signerActionAuth, recipientActionAuth)
        );
      });

    if (hasSignersChanged) {
      setRecipientsDebounced(envelopeRecipients);
    }

    if (hasSigningOrderChanged || hasAllowDictateNextSignerChanged) {
      updateEnvelope({
        meta: {
          signingOrder: validatedFormValues.data.signingOrder,
          allowDictateNextSigner: validatedFormValues.data.allowDictateNextSigner,
        },
      });
    }
  }, [formValues]);

  const recipientCountLimit = organisation.organisationClaim.recipientCount;
  const isOverRecipientLimit = recipientCountLimit > 0 && signers.length > recipientCountLimit;

  return {
    envelope,
    editorConfig,
    isEmbedded,
    organisation,
    team,
    remaining,
    form,
    isSubmitting,
    errors,
    signers,
    activeRecipientCount,
    hasCurrentEditorInfo,
    isUserAlreadyARecipient,
    onAddSelfSigner,
    onAddSigner,
    onDetectRecipientsClick,
    isOverRecipientLimit,
    recipientCountLimit,
    showAdvancedSettings,
    setShowAdvancedSettings,
    hasAssistantRole,
    setShowSigningOrderConfirmation,
    hasDocumentBeenSent,
    isSigningOrderSequential,
    onDragEnd,
    $sensorApi,
    canRecipientBeModified,
    handleSigningOrderChange,
    recipientSuggestions,
    handleRecipientAutoCompleteSelect,
    setRecipientSearchQuery,
    isLoading,
    handleRoleChange,
    onRemoveSigner,
    showSigningOrderConfirmation,
    handleSigningOrderDisable,
    isAiDialogOpen,
    onAiDialogOpenChange,
    onAiDetectionComplete,
    isAiEnableDialogOpen,
    setIsAiEnableDialogOpen,
    onAiFeaturesEnabled,
  };
};
