import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
import type { TLocalField } from '@documenso/lib/client-only/hooks/use-editor-fields';
import { usePageRenderer } from '@documenso/lib/client-only/hooks/use-page-renderer';
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
import {
  type PageRenderData,
  useCurrentEnvelopeRender,
} from '@documenso/lib/client-only/providers/envelope-render-provider';
import { FIELD_META_DEFAULT_VALUES } from '@documenso/lib/types/field-meta';
import {
  convertPixelToPercentage,
  MIN_FIELD_HEIGHT_PX,
  MIN_FIELD_WIDTH_PX,
} from '@documenso/lib/universal/field-renderer/field-renderer';
import { renderField } from '@documenso/lib/universal/field-renderer/render-field';
import { getClientSideFieldTranslations } from '@documenso/lib/utils/fields';
import { getOverlappingFieldPairs } from '@documenso/lib/utils/fields-overlap';
import { canRecipientFieldsBeModified } from '@documenso/lib/utils/recipients';
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@documenso/ui/primitives/command';
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
import { useLingui } from '@lingui/react/macro';
import type { FieldType } from '@prisma/client';
import Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node';
import type { Transformer } from 'konva/lib/shapes/Transformer';
import { CopyPlusIcon, ShapesIcon, SquareStackIcon, TrashIcon, UserCircleIcon } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';

import { fieldButtonList } from './envelope-editor-fields-drag-drop';
import { EnvelopeRecipientSelectorCommand } from './envelope-recipient-selector';

export const EnvelopeEditorFieldsPageRenderer = ({ pageData }: { pageData: PageRenderData }) => {
  const { t, i18n } = useLingui();
  const { envelope, editorFields, getRecipientColorKey } = useCurrentEnvelopeEditor();
  const { currentEnvelopeItem, setRenderError } = useCurrentEnvelopeRender();

  const interactiveTransformer = useRef<Transformer | null>(null);

  const [selectedKonvaFieldGroups, setSelectedKonvaFieldGroups] = useState<Konva.Group[]>([]);

  const [isFieldChanging, setIsFieldChanging] = useState(false);
  const [pendingFieldCreation, setPendingFieldCreation] = useState<Konva.Rect | null>(null);

  /**
   * Whether the field was automatically selected on creation (drag-drop or marquee).
   *
   * We purposefully supress the floating toolbar for newly created fields.
   */
  const [isAutoSelectedField, setIsAutoSelectedField] = useState(false);

  const { stage, pageLayer, konvaContainer, scaledViewport, unscaledViewport } = usePageRenderer(
    ({ stage, pageLayer }) => createPageCanvas(stage, pageLayer),
    pageData,
  );

  const { scale, pageNumber } = pageData;

  const localPageFields = useMemo(
    () =>
      editorFields.localFields.filter(
        (field) => field.page === pageNumber && field.envelopeItemId === currentEnvelopeItem?.id,
      ),
    [editorFields.localFields, pageNumber, currentEnvelopeItem?.id],
  );

  /**
   * Debounce the fields used for overlap highlighting so we don't recompute on every
   * small drag/resize tick. Overlaps only occur within the same page and envelope
   * item, so computing from this page's fields alone is sufficient.
   */
  const debouncedPageFields = useDebouncedValue(localPageFields, 300);

  const overlappingFieldFormIds = useMemo(() => {
    const formIds = new Set<string>();

    const pairs = getOverlappingFieldPairs(
      debouncedPageFields.map((field) => ({
        id: field.formId,
        envelopeItemId: field.envelopeItemId,
        page: field.page,
        positionX: field.positionX,
        positionY: field.positionY,
        width: field.width,
        height: field.height,
      })),
    );

    for (const pair of pairs) {
      formIds.add(pair.fieldA.id);
      formIds.add(pair.fieldB.id);
    }

    return formIds;
  }, [debouncedPageFields]);

  const handleResizeOrMove = (event: KonvaEventObject<Event>) => {
    const isDragEvent = event.type === 'dragend';

    const fieldGroup = event.target as Konva.Group;
    const fieldFormId = fieldGroup.id();

    // Note: This values are scaled.
    const {
      width: fieldPixelWidth,
      height: fieldPixelHeight,
      x: fieldX,
      y: fieldY,
    } = fieldGroup.getClientRect({
      skipStroke: true,
      skipShadow: true,
    });

    const pageHeight = scaledViewport.height;
    const pageWidth = scaledViewport.width;

    // Calculate x and y as a percentage of the page width and height
    const positionPercentX = (fieldX / pageWidth) * 100;
    const positionPercentY = (fieldY / pageHeight) * 100;

    // Get the bounds as a percentage of the page width and height
    const fieldPageWidth = (fieldPixelWidth / pageWidth) * 100;
    const fieldPageHeight = (fieldPixelHeight / pageHeight) * 100;

    const fieldUpdates: Partial<TLocalField> = {
      positionX: positionPercentX,
      positionY: positionPercentY,
    };

    // Do not update the width/height unless the field has actually been resized.
    // This is because our calculations will shift the width/height slightly
    // due to the way we convert between pixel and percentage.
    if (!isDragEvent) {
      fieldUpdates.width = fieldPageWidth;
      fieldUpdates.height = fieldPageHeight;
    }

    editorFields.updateFieldByFormId(fieldFormId, fieldUpdates);

    // Select the field if it is not already selected.
    if (isDragEvent && interactiveTransformer.current?.nodes().length === 0) {
      setSelectedFields([fieldGroup]);
    }

    pageLayer.current?.batchDraw();
  };

  /**
   * Draws (or removes) a dashed warning outline over a field that significantly
   * overlaps another field. The highlight is a child of the field group so it moves
   * and resizes with the field, and sits on top of the field's own rect (which is
   * re-styled on every render and would otherwise clobber a direct stroke change).
   */
  const syncOverlapHighlight = (fieldGroup: Konva.Group, isOverlapping: boolean) => {
    const existingHighlight = fieldGroup.findOne('.field-overlap-highlight');

    // Skip while a field is actively being dragged/resized. The highlight is driven
    // by debounced field data, so it would lag behind and distort during the gesture.
    // It is repainted once the gesture settles (the effect re-runs on isFieldChanging).
    if (isFieldChanging) {
      existingHighlight?.destroy();
      return;
    }

    if (!isOverlapping) {
      existingHighlight?.destroy();
      return;
    }

    const fieldRect = fieldGroup.findOne('.field-rect');

    if (!fieldRect) {
      return;
    }

    const highlightAttrs = {
      x: 0,
      y: 0,
      width: fieldRect.width(),
      height: fieldRect.height(),
      stroke: '#f59e0b',
      strokeWidth: 2,
      dash: [6, 4],
      cornerRadius: 2,
      strokeScaleEnabled: false,
      listening: false,
    } satisfies Partial<Konva.RectConfig>;

    if (existingHighlight instanceof Konva.Rect) {
      existingHighlight.setAttrs(highlightAttrs);
      existingHighlight.moveToTop();
      return;
    }

    const highlight = new Konva.Rect({
      name: 'field-overlap-highlight',
      ...highlightAttrs,
    });

    fieldGroup.add(highlight);
    highlight.moveToTop();
  };

  const unsafeRenderFieldOnLayer = (field: TLocalField) => {
    if (!pageLayer.current) {
      return;
    }

    const recipient = envelope.recipients.find((r) => r.id === field.recipientId);
    const isFieldEditable = recipient !== undefined && canRecipientFieldsBeModified(recipient, envelope.fields);

    const { fieldGroup } = renderField({
      scale,
      pageLayer: pageLayer.current,
      field: {
        renderId: field.formId,
        ...field,
        customText: '',
        inserted: false,
        fieldMeta: field.fieldMeta,
      },
      translations: getClientSideFieldTranslations(i18n),
      pageWidth: unscaledViewport.width,
      pageHeight: unscaledViewport.height,
      color: getRecipientColorKey(field.recipientId),
      editable: isFieldEditable,
      mode: 'edit',
    });

    syncOverlapHighlight(fieldGroup, overlappingFieldFormIds.has(field.formId));

    if (!isFieldEditable) {
      return;
    }

    fieldGroup.off('click');
    fieldGroup.off('transformend');
    fieldGroup.off('dragend');

    // Set up field selection. Shift + click toggles this field in/out of the current
    // multi-selection, so fields can be added to a group by clicking them --
    // complementing marquee drag-selection. A plain click (no modifier) selects just
    // this field.
    fieldGroup.on('click', (event) => {
      removePendingField();

      const isMultiSelectModifier = event.evt.shiftKey;

      if (isMultiSelectModifier) {
        const currentNodes = interactiveTransformer.current?.nodes() ?? [];
        const isAlreadySelected = currentNodes.includes(fieldGroup);

        setSelectedFields(
          isAlreadySelected ? currentNodes.filter((node) => node !== fieldGroup) : [...currentNodes, fieldGroup],
        );
      } else {
        setSelectedFields([fieldGroup]);
      }

      pageLayer.current?.batchDraw();
    });

    fieldGroup.on('transformend', handleResizeOrMove);
    fieldGroup.on('dragend', handleResizeOrMove);
  };

  const renderFieldOnLayer = (field: TLocalField) => {
    try {
      unsafeRenderFieldOnLayer(field);
    } catch (err) {
      console.error(err);
      setRenderError(true);
    }
  };

  /**
   * Initialize the Konva page canvas and all fields and interactions.
   */
  const createPageCanvas = (currentStage: Konva.Stage, currentPageLayer: Konva.Layer) => {
    // Initialize snap guides layer
    // snapGuideLayer.current = initializeSnapGuides(stage.current);

    // Add transformer for resizing and rotating.
    interactiveTransformer.current = createInteractiveTransformer(currentStage, currentPageLayer);

    // Render the fields.
    for (const field of localPageFields) {
      renderFieldOnLayer(field);
    }

    // Handle stage click to deselect.
    currentStage.on('mousedown', (e) => {
      removePendingField();

      if (e.target === stage.current) {
        setSelectedFields([]);
        currentPageLayer.batchDraw();
      }
    });

    // When an item is dragged, select it automatically.
    const onDragStartOrEnd = (e: KonvaEventObject<Event>) => {
      removePendingField();

      if (!e.target.hasName('field-group')) {
        return;
      }

      setIsFieldChanging(e.type === 'dragstart');

      const itemAlreadySelected = (interactiveTransformer.current?.nodes() || []).includes(e.target);

      // Do nothing and allow the transformer to handle it.
      // Required so when multiple items are selected, this won't deselect them.
      if (itemAlreadySelected) {
        return;
      }

      setSelectedFields([e.target]);
    };

    currentStage.on('dragstart', onDragStartOrEnd);
    currentStage.on('dragend', onDragStartOrEnd);
    currentStage.on('transformstart', () => setIsFieldChanging(true));
    currentStage.on('transformend', () => setIsFieldChanging(false));

    currentPageLayer.batchDraw();
  };

  /**
   * Creates an interactive transformer for the fields.
   *
   * Allows:
   * - Resizing
   * - Moving
   * - Selecting multiple fields
   * - Selecting empty area to create fields
   */
  const createInteractiveTransformer = (currentStage: Konva.Stage, currentPageLayer: Konva.Layer) => {
    const transformer = new Konva.Transformer({
      rotateEnabled: false,
      keepRatio: false,
      shouldOverdrawWholeArea: true,
      ignoreStroke: true,
      flipEnabled: false,
      boundBoxFunc: (oldBox, newBox) => {
        // Enforce minimum size
        if (newBox.width < 30 || newBox.height < 20) {
          return oldBox;
        }

        return newBox;
      },
    });

    currentPageLayer.add(transformer);

    // Add selection rectangle.
    const selectionRectangle = new Konva.Rect({
      fill: 'rgba(24, 160, 251, 0.3)',
      visible: false,
    });
    currentPageLayer.add(selectionRectangle);

    let x1: number;
    let y1: number;
    let x2: number;
    let y2: number;

    currentStage.on('mousedown touchstart', (e) => {
      // do nothing if we mousedown on any shape
      if (e.target !== currentStage) {
        return;
      }

      const pointerPosition = currentStage.getPointerPosition();

      if (!pointerPosition) {
        return;
      }

      x1 = pointerPosition.x / scale;
      y1 = pointerPosition.y / scale;
      x2 = pointerPosition.x / scale;
      y2 = pointerPosition.y / scale;

      selectionRectangle.setAttrs({
        x: x1,
        y: y1,
        width: 0,
        height: 0,
        visible: true,
      });
    });

    currentStage.on('mousemove touchmove', () => {
      // do nothing if we didn't start selection
      if (!selectionRectangle.visible()) {
        return;
      }

      selectionRectangle.moveToTop();

      const pointerPosition = currentStage.getPointerPosition();

      if (!pointerPosition) {
        return;
      }

      x2 = pointerPosition.x / scale;
      y2 = pointerPosition.y / scale;

      selectionRectangle.setAttrs({
        x: Math.min(x1, x2),
        y: Math.min(y1, y2),
        width: Math.abs(x2 - x1),
        height: Math.abs(y2 - y1),
      });
    });

    currentStage.on('mouseup touchend', () => {
      // do nothing if we didn't start selection
      if (!selectionRectangle.visible()) {
        return;
      }

      // Update visibility in timeout, so we can check it in click event
      setTimeout(() => {
        selectionRectangle.visible(false);
      });

      const stageFieldGroups = currentStage.find('.field-group') || [];
      const box = selectionRectangle.getClientRect();
      const selectedFieldGroups = stageFieldGroups.filter(
        (shape) => Konva.Util.haveIntersection(box, shape.getClientRect()) && shape.draggable(),
      );
      setSelectedFields(selectedFieldGroups);

      const unscaledBoxWidth = box.width / scale;
      const unscaledBoxHeight = box.height / scale;

      // Create a field if no items are selected or the size is too small.
      if (
        selectedFieldGroups.length === 0 &&
        unscaledBoxWidth > MIN_FIELD_WIDTH_PX &&
        unscaledBoxHeight > MIN_FIELD_HEIGHT_PX &&
        editorFields.selectedRecipient &&
        canRecipientFieldsBeModified(editorFields.selectedRecipient, envelope.fields)
      ) {
        const pendingFieldCreation = new Konva.Rect({
          name: 'pending-field-creation',
          x: box.x / scale,
          y: box.y / scale,
          width: unscaledBoxWidth,
          height: unscaledBoxHeight,
          fill: 'rgba(24, 160, 251, 0.3)',
        });

        currentPageLayer.add(pendingFieldCreation);
        setPendingFieldCreation(pendingFieldCreation);
      }
    });

    // Clicking empty stage area clears the selection. Field clicks -- including
    // Shift+click multi-select -- are handled by each field group's own click
    // handler in `unsafeRenderFieldOnLayer`.
    currentStage.on('click tap', (e) => {
      // If we are selecting with the marquee rectangle, do nothing.
      if (selectionRectangle.visible() && selectionRectangle.width() > 0 && selectionRectangle.height() > 0) {
        return;
      }

      // If empty area clicked, remove all selections.
      if (e.target === stage.current) {
        setSelectedFields([]);
      }
    });

    return transformer;
  };

  /**
   * Render fields when they are added or removed from the localFields.
   */
  useEffect(() => {
    if (!pageLayer.current || !stage.current) {
      return;
    }

    // If doesn't exist in localFields, destroy it since it's been deleted.
    pageLayer.current.find('Group').forEach((group) => {
      if (group.name() === 'field-group' && !localPageFields.some((field) => field.formId === group.id())) {
        group.destroy();
      }
    });

    // If it exists, rerender.
    localPageFields.forEach((field) => {
      renderFieldOnLayer(field);
    });

    // Reconcile selection state with live field nodes after flush/sync updates.
    const liveSelectedFieldGroups = selectedKonvaFieldGroups.filter((fieldGroup) => {
      if (!fieldGroup.getStage() || !fieldGroup.getParent()) {
        return false;
      }

      return localPageFields.some((field) => field.formId === fieldGroup.id());
    });

    if (liveSelectedFieldGroups.length !== selectedKonvaFieldGroups.length) {
      setSelectedFields(liveSelectedFieldGroups);
    }

    // Mirror the editor's single selected field onto the canvas (Konva) selection.
    //
    // `addField` already marks a newly created field as the selected field, so this
    // makes a field placed via the palette (drag-drop) or marquee creation show its
    // resize handles immediately -- no second click needed. It also clears the canvas
    // selection when the selected field is cleared (e.g. when the author starts
    // placing another field), so the floating action toolbar can't intercept the next
    // placement click. Runs after the render loop above so the field's group exists.
    const selectedFormId = editorFields.selectedField?.formId ?? null;
    const isSingleCanvasSelection = selectedKonvaFieldGroups.length === 1;

    if (selectedFormId && localPageFields.some((field) => field.formId === selectedFormId)) {
      const isAlreadySelected = isSingleCanvasSelection && selectedKonvaFieldGroups[0].id() === selectedFormId;

      if (!isAlreadySelected) {
        const fieldGroupToSelect = pageLayer.current.findOne(`#${selectedFormId}`);

        if (fieldGroupToSelect instanceof Konva.Group) {
          setSelectedFields([fieldGroupToSelect], { isAutoSelect: true });
        }
      }
    } else if (selectedFormId === null && isSingleCanvasSelection) {
      setSelectedFields([]);
    }

    // Rerender the transformer
    interactiveTransformer.current?.forceUpdate();

    pageLayer.current.batchDraw();
  }, [
    localPageFields,
    selectedKonvaFieldGroups,
    overlappingFieldFormIds,
    isFieldChanging,
    editorFields.selectedField?.formId,
  ]);

  const setSelectedFields = (nodes: Konva.Node[], options?: { isAutoSelect?: boolean }) => {
    // Any explicit (user-driven) selection shows the action toolbar; only auto-selection
    // on field creation suppresses it.
    setIsAutoSelectedField(Boolean(options?.isAutoSelect));

    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    const fieldGroups = nodes.filter(
      (node) => node.hasName('field-group') && Boolean(node.getStage()) && Boolean(node.getParent()),
    ) as Konva.Group[];

    interactiveTransformer.current?.nodes(fieldGroups);
    setSelectedKonvaFieldGroups(fieldGroups);

    if (fieldGroups.length === 0 || fieldGroups.length > 1) {
      editorFields.setSelectedField(null);
    }

    // Handle single field selection.
    if (fieldGroups.length === 1) {
      const fieldGroup = fieldGroups[0];

      editorFields.setSelectedField(fieldGroup.id());
      fieldGroup.moveToTop();
    }
  };

  const deletedSelectedFields = () => {
    const fieldFormids = selectedKonvaFieldGroups.map((field) => field.id()).filter((field) => field !== undefined);

    editorFields.removeFieldsByFormId(fieldFormids);

    setSelectedFields([]);
  };

  const changeSelectedFieldsRecipients = (recipientId: number) => {
    const fields = selectedKonvaFieldGroups
      .map((field) => editorFields.getFieldByFormId(field.id()))
      .filter((field) => field !== undefined);

    for (const field of fields) {
      if (field.recipientId !== recipientId) {
        editorFields.updateFieldByFormId(field.formId, { recipientId, id: undefined });
      }
    }
  };

  const changeSelectedFieldsType = (type: FieldType) => {
    const fields = selectedKonvaFieldGroups
      .map((field) => editorFields.getFieldByFormId(field.id()))
      .filter((field) => field !== undefined);

    for (const field of fields) {
      if (field.type !== type) {
        editorFields.updateFieldByFormId(field.formId, {
          type,
          fieldMeta: structuredClone(FIELD_META_DEFAULT_VALUES[type]),
          id: undefined,
        });
      }
    }
  };

  const duplicatedSelectedFields = () => {
    const fields = selectedKonvaFieldGroups
      .map((field) => editorFields.getFieldByFormId(field.id()))
      .filter((field) => field !== undefined);

    for (const field of fields) {
      editorFields.duplicateField(field);
    }
  };

  const duplicatedSelectedFieldsOnAllPages = () => {
    const fields = selectedKonvaFieldGroups
      .map((field) => editorFields.getFieldByFormId(field.id()))
      .filter((field) => field !== undefined);

    for (const field of fields) {
      editorFields.duplicateFieldToAllPages(field);
    }

    setSelectedFields([]);
  };

  /**
   * Create a field from a pending field.
   */
  const createFieldFromPendingTemplate = (pendingFieldCreation: Konva.Rect, type: FieldType) => {
    const pixelWidth = pendingFieldCreation.width();
    const pixelHeight = pendingFieldCreation.height();
    const pixelX = pendingFieldCreation.x();
    const pixelY = pendingFieldCreation.y();

    removePendingField();

    if (!currentEnvelopeItem || !editorFields.selectedRecipient) {
      return;
    }

    const { fieldX, fieldY, fieldWidth, fieldHeight } = convertPixelToPercentage({
      width: pixelWidth,
      height: pixelHeight,
      positionX: pixelX,
      positionY: pixelY,
      pageWidth: unscaledViewport.width,
      pageHeight: unscaledViewport.height,
    });

    editorFields.addField({
      envelopeItemId: currentEnvelopeItem.id,
      page: pageNumber,
      type,
      positionX: fieldX,
      positionY: fieldY,
      width: fieldWidth,
      height: fieldHeight,
      recipientId: editorFields.selectedRecipient.id,
      fieldMeta: structuredClone(FIELD_META_DEFAULT_VALUES[type]),
    });
  };

  /**
   * Remove any pending fields or rectangle on the canvas.
   */
  const removePendingField = () => {
    setPendingFieldCreation(null);

    const pendingFieldCreation = pageLayer.current?.find('.pending-field-creation') || [];

    for (const field of pendingFieldCreation) {
      field.destroy();
    }
  };

  if (!currentEnvelopeItem) {
    return null;
  }

  return (
    <>
      {selectedKonvaFieldGroups.length > 0 &&
        interactiveTransformer.current &&
        !isFieldChanging &&
        !isAutoSelectedField && (
          <FieldActionButtons
            handleDuplicateSelectedFields={duplicatedSelectedFields}
            handleDuplicateSelectedFieldsOnAllPages={duplicatedSelectedFieldsOnAllPages}
            handleDeleteSelectedFields={deletedSelectedFields}
            handleChangeRecipient={changeSelectedFieldsRecipients}
            handleChangeFieldType={changeSelectedFieldsType}
            selectedFieldFormId={selectedKonvaFieldGroups.map((field) => field.id())}
            style={{
              position: 'absolute',
              top:
                interactiveTransformer.current.y() + interactiveTransformer.current.getClientRect().height + 5 + 'px',
              left:
                interactiveTransformer.current.x() + interactiveTransformer.current.getClientRect().width / 2 + 'px',
              transform: 'translateX(-50%)',
              gap: '8px',
              pointerEvents: 'auto',
              zIndex: 50,
            }}
          />
        )}

      {pendingFieldCreation && (
        <div
          style={{
            position: 'absolute',
            top: pendingFieldCreation.y() * scale + pendingFieldCreation.getClientRect().height + 5 + 'px',
            left: pendingFieldCreation.x() * scale + pendingFieldCreation.getClientRect().width / 2 + 'px',
            transform: 'translateX(-50%)',
            zIndex: 50,
          }}
          // Don't use darkmode for this component, it should look the same for both light/dark modes.
          className="grid w-max grid-cols-5 gap-x-1 gap-y-0.5 rounded-md border border-gray-300 bg-white p-1 text-gray-500 shadow-sm"
        >
          {fieldButtonList.map((field) => (
            <button
              key={field.type}
              onClick={() => createFieldFromPendingTemplate(pendingFieldCreation, field.type)}
              className="col-span-1 w-full flex-shrink-0 rounded-sm px-2 py-1 text-xs hover:bg-gray-100 hover:text-gray-600"
            >
              {t(field.name)}
            </button>
          ))}
        </div>
      )}

      {/* The element Konva will inject it's canvas into. */}
      <div className="konva-container absolute inset-0 z-10 w-full" ref={konvaContainer}></div>
    </>
  );
};

type FieldActionButtonsProps = React.HTMLAttributes<HTMLDivElement> & {
  handleDuplicateSelectedFields: () => void;
  handleDuplicateSelectedFieldsOnAllPages: () => void;
  handleDeleteSelectedFields: () => void;
  handleChangeRecipient: (recipientId: number) => void;
  handleChangeFieldType: (type: FieldType) => void;
  selectedFieldFormId: string[];
};

const FieldActionButtons = ({
  handleDuplicateSelectedFields,
  handleDuplicateSelectedFieldsOnAllPages,
  handleDeleteSelectedFields,
  handleChangeRecipient,
  handleChangeFieldType,
  selectedFieldFormId,
  ...props
}: FieldActionButtonsProps) => {
  const { t } = useLingui();

  const [showRecipientSelector, setShowRecipientSelector] = useState(false);
  const [showFieldTypeSelector, setShowFieldTypeSelector] = useState(false);

  const { editorFields, envelope } = useCurrentEnvelopeEditor();

  /**
   * Decide the preselected field type in the command input.
   *
   * If all fields share the same type, use that as the default selection.
   * Otherwise show no preselection.
   */
  const preselectedFieldType = useMemo(() => {
    if (selectedFieldFormId.length === 0) {
      return null;
    }

    const fields = editorFields.localFields.filter((field) => selectedFieldFormId.includes(field.formId));

    if (fields.length === 0) {
      return null;
    }

    const firstType = fields[0].type;
    const isTypesSame = fields.every((field) => field.type === firstType);

    return isTypesSame ? firstType : null;
  }, [editorFields.localFields, selectedFieldFormId]);

  /**
   * Decide the preselected recipient in the command input.
   *
   * If all fields belong to the same recipient then use that recipient as the default.
   *
   * Otherwise show the placeholder.
   */
  const preselectedRecipient = useMemo(() => {
    if (selectedFieldFormId.length === 0) {
      return null;
    }

    const fields = editorFields.localFields.filter((field) => selectedFieldFormId.includes(field.formId));

    if (fields.length === 0) {
      return null;
    }

    const recipient = envelope.recipients.find((recipient) => recipient.id === fields[0].recipientId);

    if (!recipient) {
      return null;
    }

    const isRecipientsSame = fields.every((field) => field.recipientId === recipient.id);

    if (isRecipientsSame) {
      return recipient;
    }

    return null;
  }, [editorFields.localFields, envelope.recipients, selectedFieldFormId]);

  return (
    <div className="flex flex-col items-center" {...props}>
      <div className="group flex w-fit items-center justify-evenly gap-x-1 rounded-md border bg-gray-900 p-0.5">
        <button
          type="button"
          title={t`Change Recipient`}
          className="rounded-sm p-1.5 text-gray-400 transition-colors hover:bg-white/10 hover:text-gray-100"
          onClick={() => setShowRecipientSelector(true)}
          onTouchEnd={() => setShowRecipientSelector(true)}
        >
          <UserCircleIcon className="h-3 w-3" />
        </button>

        <button
          type="button"
          title={t`Change Field Type`}
          className="rounded-sm p-1.5 text-gray-400 transition-colors hover:bg-white/10 hover:text-gray-100"
          onClick={() => setShowFieldTypeSelector(true)}
          onTouchEnd={() => setShowFieldTypeSelector(true)}
        >
          <ShapesIcon className="h-3 w-3" />
        </button>

        <button
          type="button"
          title={t`Duplicate`}
          className="rounded-sm p-1.5 text-gray-400 transition-colors hover:bg-white/10 hover:text-gray-100"
          onClick={handleDuplicateSelectedFields}
          onTouchEnd={handleDuplicateSelectedFields}
        >
          <CopyPlusIcon className="h-3 w-3" />
        </button>

        <button
          type="button"
          title={t`Duplicate on all pages`}
          className="rounded-sm p-1.5 text-gray-400 transition-colors hover:bg-white/10 hover:text-gray-100"
          onClick={handleDuplicateSelectedFieldsOnAllPages}
          onTouchEnd={handleDuplicateSelectedFieldsOnAllPages}
        >
          <SquareStackIcon className="h-3 w-3" />
        </button>

        <button
          type="button"
          title={t`Remove`}
          className="rounded-sm p-1.5 text-gray-400 transition-colors hover:bg-white/10 hover:text-gray-100"
          onClick={handleDeleteSelectedFields}
          onTouchEnd={handleDeleteSelectedFields}
        >
          <TrashIcon className="h-3 w-3" />
        </button>
      </div>

      <CommandDialog position="start" open={showRecipientSelector} onOpenChange={setShowRecipientSelector}>
        <EnvelopeRecipientSelectorCommand
          placeholder={t`Select a recipient`}
          selectedRecipient={preselectedRecipient}
          onSelectedRecipientChange={(recipient) => {
            editorFields.setSelectedRecipient(recipient.id);
            handleChangeRecipient(recipient.id);
            setShowRecipientSelector(false);
          }}
          recipients={envelope.recipients}
          fields={envelope.fields}
        />
      </CommandDialog>

      <CommandDialog position="start" open={showFieldTypeSelector} onOpenChange={setShowFieldTypeSelector}>
        <Command defaultValue={preselectedFieldType ? t(FRIENDLY_FIELD_TYPE[preselectedFieldType]) : undefined}>
          <CommandInput placeholder={t`Select a field type`} />

          <CommandList>
            <CommandEmpty>
              <span className="inline-block px-4 text-muted-foreground">
                {t`No field type matching this description was found.`}
              </span>
            </CommandEmpty>

            <CommandGroup>
              {fieldButtonList.map((field) => {
                const FieldIcon = field.icon;
                const label = t(FRIENDLY_FIELD_TYPE[field.type]);

                return (
                  <CommandItem
                    key={field.type}
                    className="px-2"
                    onSelect={() => {
                      handleChangeFieldType(field.type);
                      setShowFieldTypeSelector(false);
                    }}
                  >
                    <FieldIcon className="mr-2 h-4 w-4" />
                    <span className="truncate">{label}</span>
                  </CommandItem>
                );
              })}
            </CommandGroup>
          </CommandList>
        </Command>
      </CommandDialog>
    </div>
  );
};
