crm-integration-card.tsx123.2 KBView on GitHub
'use client';

import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';
import {
  DATE_FORMAT_OPTIONS,
  type FieldMapping,
  isSimpleMapping,
  isComplexMapping,
  normalizeFieldMappings,
  extractPicklistValues,
  extractPicklistOptions,
} from './field-mapping-utils';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/components/ui/command';
import {
  Loader2,
  Unplug,
  Eye,
  EyeOff,
  ChevronDown,
  ChevronUp,
  X,
  Plus,
  RefreshCw,
} from 'lucide-react';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { FieldRows, SelectField } from '@/components/ui/field';
import type { ProviderId } from '../../../server/src/services/integrations/constants';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import { useTRPC, trpcClient } from '@/providers/query-provider';
import { useSession } from '@/modules/auth/utils/auth-client';
import { getAvailableCedarFields } from '@/modules/crm/utils';
import { Check, ChevronsUpDown } from 'lucide-react';
import { useState, useEffect, useMemo } from 'react';
import { Checkbox } from '@/components/ui/checkbox';
import { CrmDealManager } from './crm-deal-manager';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { useAOPs } from '@/modules/aop';
import { resolveDealsAopId } from '@/modules/aop/utils/deals-aop';
import { cn } from '@/lib/utils';
import { toast } from 'sonner';

interface CrmIntegrationCardProps {
  providerId: 'hubspot' | 'attio' | 'salesforce' | 'copper';
  userId?: string;
  showDealManager?: boolean;
  userFacing?: boolean; // When true, hides the deal manager (for user-facing settings page)
}

export function CrmIntegrationCard({
  providerId,
  userId,
  showDealManager = false,
  userFacing = false,
}: CrmIntegrationCardProps) {
  const [isConnecting, setIsConnecting] = useState(false);
  const [isDisconnecting, setIsDisconnecting] = useState(false);
  const [showDirectInputForm, setShowDirectInputForm] = useState(false);
  const [formFields, setFormFields] = useState<Record<string, string>>({});
  const [showApiKey, setShowApiKey] = useState(false);
  const [showFieldSelection, setShowFieldSelection] = useState(false);
  const [fieldMappings, setFieldMappings] = useState<Array<FieldMapping>>([]);
  // Which AOP field mappings/filters/stage-requirements below are being configured FOR — the
  // customer only ever picks an AOP by name; the CRM object it's bound to (Opportunity, a
  // Lead, a Partner record, ...) is resolved server-side from AOP.boundObjectApiName. See
  // docs/design/crm-custom-object-linkage-leads.md's "aopId, not objectApiName" principle.
  const [selectedAopId, setSelectedAopId] = useState<string | null>(null);
  const [isSavingFields, setIsSavingFields] = useState(false);
  // Store raw input values for allowed values to preserve cursor position
  const [allowedValuesInputs, setAllowedValuesInputs] = useState<Record<string, string>>({});
  // Deal sync filter state - filter deals based on a CRM field value
  const [dealSyncFilter, setDealSyncFilter] = useState<Record<string, string>>({});
  const [showFilterConfig, setShowFilterConfig] = useState(false);
  const [stageRequiredFields, setStageRequiredFields] = useState<Record<string, string[]>>({});
  const [showStageRequiredConfig, setShowStageRequiredConfig] = useState(false);
  const [stageSourceField, setStageSourceField] = useState<string | null>(null);
  // Salesforce custom object config (admin-only, shown when userId is provided)
  const [customObjectObjectName, setCustomObjectObjectName] = useState('');
  const [customObjectFilterField, setCustomObjectFilterField] = useState('');
  const [customObjectFieldsInput, setCustomObjectFieldsInput] = useState('');
  const [customObjectLimit, setCustomObjectLimit] = useState('');
  const [showCustomObjectConfig, setShowCustomObjectConfig] = useState(false);
  const [isSavingCustomObjectConfig, setIsSavingCustomObjectConfig] = useState(false);
  const trpc = useTRPC();
  const queryClient = useQueryClient();
  const { data: session } = useSession();

  // Fetch integration status
  const { data: integrationsData, refetch: refetchIntegrations } = useQuery(
    trpc.integrations.list.queryOptions(userId ? { userId } : undefined),
  );

  const integration = integrationsData?.integrations.find((i) => i.id === providerId);
  const isConnected = integration?.connected || false;
  const connectionId = integration?.connectionId;
  const connectionType = integration?.capabilities?.connectionType;
  const isPersonalConnection = (integration as { isPersonalConnection?: boolean } | undefined)
    ?.isPersonalConnection;
  const isOrgManaged = (integration as { isOrgManaged?: boolean } | undefined)?.isOrgManaged;
  const connectionFields = useMemo(
    () => integration?.capabilities?.connectionFields || [],
    [integration?.capabilities?.connectionFields],
  );
  const apiKeyInstructions = (
    integration?.capabilities as { apiKeyInstructions?: string } | undefined
  )?.apiKeyInstructions;
  const requiredFields = connectionFields;
  const hasUpdateOpportunityFields =
    (integration?.capabilities as { hasUpdateOpportunityFields?: boolean } | undefined)
      ?.hasUpdateOpportunityFields || false;
  const defaultWriteFieldMappings = useMemo(
    () =>
      (
        integration?.capabilities as
          | {
              defaultWriteFieldMappings?: Array<FieldMapping>;
            }
          | undefined
      )?.defaultWriteFieldMappings || [],
    [integration?.capabilities],
  );

  // Fetch AOPs — both for custom field definitions in the Cedar field dropdowns, and as the
  // AOP picker's candidate list below (which object a mapping group targets).
  const { data: aopsData } = useAOPs(userId);

  // Default to whichever AOP looks like "the deals one" — same heuristic /statistics and the
  // home rail already use (see resolveDealsAopId's own doc comment) — so a customer who never
  // touches the picker keeps configuring the mapping group they always have.
  useEffect(() => {
    if (selectedAopId !== null || !aopsData?.aops) return;
    const [firstAop] = aopsData.aops;
    if (!firstAop) return;
    setSelectedAopId(resolveDealsAopId(aopsData.aops) ?? firstAop.id);
  }, [aopsData, selectedAopId]);

  const selectedAop = useMemo(
    () => aopsData?.aops?.find((aop) => aop.id === selectedAopId) ?? null,
    [aopsData?.aops, selectedAopId],
  );
  // Read-only, internal: never rendered as a pickable option — the picker above only ever
  // shows AOP names. "Opportunity" is treated the same as unset (the default Deal group).
  const selectedObjectApiName =
    selectedAop?.boundObjectApiName && selectedAop.boundObjectApiName !== 'Opportunity'
      ? selectedAop.boundObjectApiName
      : undefined;

  // Fetch CRM properties when connected (allow configuring mappings even if writes aren't enabled)
  const shouldFetchProperties = isConnected && hasUpdateOpportunityFields && !!userId;
  const {
    data: propertiesData,
    refetch: refetchProperties,
    isFetching: isFetchingProperties,
  } = useQuery({
    queryKey: ['integrations', 'crm', 'getProperties', { providerId, userId: userId!, aopId: selectedAopId }],
    queryFn: () =>
      trpcClient.integrations.crm.getProperties.query({
        providerId,
        userId: userId!,
        aopId: selectedAopId ?? undefined,
      }),
    enabled: shouldFetchProperties,
  });

  // Fetch current field mappings from connection metadata
  const { data: connectionData } = useQuery({
    queryKey: ['connections', connectionId],
    queryFn: () =>
      connectionId
        ? trpcClient.connections.get.query({ connectionId, userId })
        : Promise.resolve(null),
    enabled: !!connectionId && !!userId,
  });

  // Fetch org defaults for CRM settings
  const { data: orgDefaultsData } = useQuery({
    queryKey: ['integrations', 'crm', 'getOrgDefaults', { providerId }],
    queryFn: () => trpcClient.integrations.crm.getOrgDefaults.query({ providerId }),
    enabled: isConnected && !!userId,
  });

  // Load Salesforce custom object config (admin-only)
  const { data: customObjectConfigData, refetch: refetchCustomObjectConfig } = useQuery({
    queryKey: ['admin', 'customObjectConfig', userId, providerId],
    queryFn: () =>
      trpcClient.admin.crmSync.getCustomObjectConfig.query({ userId: userId!, provider: providerId }),
    enabled: providerId === 'salesforce' && isConnected && !!userId,
  });

  useEffect(() => {
    if (customObjectConfigData?.config) {
      setCustomObjectObjectName(customObjectConfigData.config.object_name ?? '');
      setCustomObjectFilterField(customObjectConfigData.config.filter_field ?? '');
      setCustomObjectFieldsInput((customObjectConfigData.config.fields ?? []).join(', '));
      setCustomObjectLimit(customObjectConfigData.config.limit ? String(customObjectConfigData.config.limit) : '');
    }
  }, [customObjectConfigData]);

  const { mutateAsync: updateWriteSettings } = useMutation(
    trpc.integrations.crm.updateWriteSettings.mutationOptions(),
  );

  const { mutateAsync: updateCrmSettings } = useMutation(
    trpc.connections.updateCrmSettings.mutationOptions(),
  );

  // Get current settings from connection metadata
  const currentSettings = useMemo(() => {
    if (!connectionData?.metadata)
      return {
        periodicDealSyncEnabled: false,
        externalCrmPushEnabled: false,
        requireLinkedDealForAgentOutputs: false,
        fetchUnownedDeals: false,
      };
    const metadata = connectionData.metadata as Record<string, unknown>;
    const settings = (metadata.settings as Record<string, unknown>) || {};
    return {
      periodicDealSyncEnabled: (settings.periodicDealSyncEnabled as boolean) ?? false,
      externalCrmPushEnabled: (settings.externalCrmPushEnabled as boolean) ?? false,
      requireLinkedDealForAgentOutputs:
        (settings.requireLinkedDealForAgentOutputs as boolean | undefined) ?? false,
      fetchUnownedDeals: (settings.fetchUnownedDeals as boolean | undefined) ?? false,
    };
  }, [connectionData]);

  // Check if settings differ from org defaults
  const isCustomizedFromOrgDefaults = useMemo(() => {
    if (!orgDefaultsData) return { periodicSync: false, crmWrites: false };
    const orgSyncDefault = orgDefaultsData.defaultPeriodicDealSyncEnabled ?? false;
    const orgWritesDefault = orgDefaultsData.defaultExternalCrmPushEnabled ?? false;
    return {
      periodicSync: currentSettings.periodicDealSyncEnabled !== orgSyncDefault,
      crmWrites: currentSettings.externalCrmPushEnabled !== orgWritesDefault,
    };
  }, [orgDefaultsData, currentSettings]);

  const handleSettingsChange = async (
    setting:
      | 'periodicDealSyncEnabled'
      | 'externalCrmPushEnabled'
      | 'requireLinkedDealForAgentOutputs'
      | 'fetchUnownedDeals',
    value: boolean,
  ) => {
    if (!connectionId) {
      toast.error('Connection ID not found');
      return;
    }

    try {
      await updateCrmSettings({
        connectionId,
        settings: { [setting]: value },
        userId,
      });
      queryClient.invalidateQueries({ queryKey: ['connections', connectionId] });
    } catch (error) {
      console.error('[CrmIntegrationCard] Error updating settings:', error);
      toast.error(
        `Failed to update settings: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  };

  // Initialize field mappings and deal sync filter from connection metadata or driver defaults.
  // Reads the DEFAULT (flat) metadata group for the Deals AOP, or
  // `metadata.objectSettings[selectedObjectApiName]` for any other AOP the picker selects — the
  // SAME per-object namespace the connection settings toggles above (`updateCrmSettings`) and
  // the pull-sync read path (`getEffectiveCrmToCedarMappings`) already use — and re-runs on
  // every AOP switch, so switching away from a Lead mapping group back to Deals doesn't leave
  // the Lead group's values sitting in the form.
  useEffect(() => {
    if (isConnected && hasUpdateOpportunityFields && connectionData) {
      const metadata = (connectionData.metadata as Record<string, unknown>) || {};
      const objectSettings = metadata.objectSettings as
        | Record<string, Record<string, unknown>>
        | undefined;
      const scoped = selectedObjectApiName ? objectSettings?.[selectedObjectApiName] ?? {} : metadata;

      const rawMappings = Array.isArray(scoped.fieldMappings) ? scoped.fieldMappings : [];
      const mappings = normalizeFieldMappings(rawMappings);
      if (mappings.length > 0) {
        setFieldMappings(mappings);
      } else if (defaultWriteFieldMappings.length > 0 && !selectedObjectApiName) {
        // Driver-provided defaults are Deal-shaped only — never applied to another object's
        // (empty) mapping group, which should read as genuinely unconfigured instead.
        const normalizedDefaults = normalizeFieldMappings(
          defaultWriteFieldMappings as Array<unknown>,
        );
        setFieldMappings(normalizedDefaults);
      } else {
        setFieldMappings([]);
      }

      // `scoped` is a raw JSONB blob (`connection.metadata`, typed `unknown` at this boundary) —
      // dealSyncFilter/stageRequiredFields/stageSourceField below are cast, not validated,
      // because the ONLY writers of this slot (`updateWriteSettings` in
      // trpc/routes/integrations.ts, `computeUpdatedSyncSettings` in connection-config.ts) already
      // validate these shapes (DealSyncFilterSchema, etc.) before persisting — this read side
      // trusts that contract rather than re-validating it.
      const savedFilter =
        typeof scoped.dealSyncFilter === 'object' && scoped.dealSyncFilter !== null
          ? (scoped.dealSyncFilter as Record<string, string>)
          : undefined;
      setDealSyncFilter(savedFilter ?? {});

      const savedStageRequired =
        typeof scoped.stageRequiredFields === 'object' && scoped.stageRequiredFields !== null
          ? (scoped.stageRequiredFields as Record<string, string[]>)
          : undefined;
      setStageRequiredFields(
        savedStageRequired && Object.keys(savedStageRequired).length > 0 ? savedStageRequired : {},
      );
      const savedStageField =
        typeof scoped.stageSourceField === 'string' ? scoped.stageSourceField : undefined;
      setStageSourceField(savedStageField ?? null);
    } else if (
      isConnected &&
      hasUpdateOpportunityFields &&
      defaultWriteFieldMappings.length > 0 &&
      !selectedObjectApiName
    ) {
      const normalizedDefaults = normalizeFieldMappings(
        defaultWriteFieldMappings as Array<unknown>,
      );
      setFieldMappings(normalizedDefaults);
    }
  }, [
    isConnected,
    hasUpdateOpportunityFields,
    connectionData,
    defaultWriteFieldMappings,
    selectedObjectApiName,
  ]);

  // When the user picks a stage source field, populate rows from that field's picklist values
  const handleStageSourceFieldChange = (fieldName: string) => {
    setStageSourceField(fieldName);
    const raw = propertiesData?.picklist_fields?.[fieldName];
    const values: string[] = !raw
      ? []
      : (raw as Array<string | { label: string; value: string }>).map((v) =>
          typeof v === 'string' ? v : v.label,
        );
    setStageRequiredFields((prev) => {
      const updated: Record<string, string[]> = {};
      for (const v of values) {
        updated[v] = prev[v] ?? [];
      }
      return updated;
    });
  };

  // Fetch credentials for API key providers when connected
  const isApiKeyProvider = connectionType === 'api_key';
  const shouldFetchCredentials = isConnected && isApiKeyProvider && !!userId;
  const { data: credentialsData } = useQuery({
    queryKey: ['integrations', 'crm', 'getCredentials', { providerId, userId: userId! }],
    queryFn: () =>
      trpcClient.integrations.crm.getCredentials.query({ providerId, userId: userId! }),
    enabled: shouldFetchCredentials,
  });

  const { mutateAsync: initiateOAuth } = useMutation(
    trpc.integrations.initiateOAuth.mutationOptions(),
  );

  const { mutateAsync: connect } = useMutation(trpc.integrations.connect.mutationOptions());

  const { mutateAsync: deleteConnection } = useMutation(trpc.connections.delete.mutationOptions());

  // Populate form fields with credentials when API key provider is connected
  useEffect(() => {
    if (
      isConnected &&
      connectionType === 'api_key' &&
      credentialsData &&
      typeof credentialsData === 'object' &&
      credentialsData !== null
    ) {
      setFormFields(credentialsData as Record<string, string>);
    }
  }, [isConnected, connectionType, credentialsData]);

  // Populate email field with user's email if empty (for API key providers with userEmail field)
  // This runs when the form is shown and email is empty
  useEffect(() => {
    if (
      connectionType === 'api_key' &&
      session?.user?.email &&
      (showDirectInputForm || (isConnected && connectionType === 'api_key'))
    ) {
      const hasEmailField = connectionFields.some((field) => field.key === 'userEmail');
      if (hasEmailField) {
        setFormFields((prev) => {
          // Only set email if it's not already set or is empty
          if (!prev.userEmail || prev.userEmail.trim() === '') {
            return { ...prev, userEmail: session.user.email };
          }
          return prev;
        });
      }
    }
  }, [connectionType, session?.user?.email, showDirectInputForm, isConnected, connectionFields]);

  // Handle OAuth callback
  useEffect(() => {
    const handleWindowFocus = async () => {
      const storageKey=[redacted];
      const pendingAuth = sessionStorage.getItem(storageKey);

      if (pendingAuth && isConnecting) {
        console.log(
          `[CrmIntegrationCard] User returned from OAuth tab for ${providerId}, checking connection...`,
        );

        try {
          const { strataServerUrl } = JSON.parse(pendingAuth);

          await connect({
            providerId: providerId as ProviderId,
            connectionParams: { strataServerUrl },
            userId,
          });

          sessionStorage.removeItem(storageKey);
          setIsConnecting(false);
          void refetchIntegrations();
          queryClient.invalidateQueries({ queryKey=[redacted] });
        } catch (error) {
          const errorMessage = error instanceof Error ? error.message : String(error);
          // Ignore expected auth-in-progress errors
          const isExpectedError =
            errorMessage.includes('not authenticated') ||
            errorMessage.includes('Unauthorized') ||
            errorMessage.includes('Please complete OAuth flow');

          if (!isExpectedError) {
            console.error(`[CrmIntegrationCard] Error connecting ${providerId}:`, error);
            sessionStorage.removeItem(storageKey);
            setIsConnecting(false);
            toast.error(`Failed to connect ${providerId}: ${errorMessage}`);
          }
        }
      }
    };

    window.addEventListener('focus', handleWindowFocus);
    return () => window.removeEventListener('focus', handleWindowFocus);
  }, [isConnecting, connect, providerId, refetchIntegrations, trpc, userId, queryClient]);

  const handleConnect = async () => {
    // Check if this provider requires direct API key input (connectionType === 'api_key')
    if (connectionType === 'api_key' && requiredFields.length > 0) {
      setShowDirectInputForm(true);
      // Initialize form fields
      const initialFields: Record<string, string> = {};
      requiredFields.forEach((field) => {
        // Populate email from session if available and field is userEmail
        if (field.key === 'userEmail' && session?.user?.email) {
          initialFields[field.key] = session.user.email;
        } else {
          initialFields[field.key] = '';
        }
      });
      setFormFields(initialFields);
      return;
    }

    // For manual connection type, show message
    if (connectionType === 'manual') {
      toast.info('This provider requires manual setup. The Cedar team will walk you through it :)');
      return;
    }

    // Otherwise, use OAuth flow (connectionType === 'oauth' or undefined)
    try {
      setIsConnecting(true);
      const { oauthUrl, strataServerUrl } = await initiateOAuth({
        integration: providerId,
        userId,
      });

      if (!oauthUrl) {
        setIsConnecting(false);
        toast.error('Failed to get OAuth URL');
        return;
      }

      sessionStorage.setItem(`${providerId}_oauth_pending`, JSON.stringify({ strataServerUrl }));

      window.open(oauthUrl, '_blank');
    } catch (error) {
      console.error(`[CrmIntegrationCard] Error initiating auth for ${providerId}:`, error);
      setIsConnecting(false);
      toast.error(`Failed to initiate connection for ${providerId}`);
    }
  };

  const handleDirectInputConnect = async () => {
    const providerName = providerId.charAt(0).toUpperCase() + providerId.slice(1);

    // Validate all required fields are filled
    const missingFields = requiredFields.filter((field) => !formFields[field.key]?.trim());
    if (missingFields.length > 0) {
      toast.error(
        `Please fill in all required fields: ${missingFields.map((f) => f.label).join(', ')}`,
      );
      return;
    }

    try {
      setIsConnecting(true);
      await connect({
        providerId: providerId as ProviderId,
        connectionParams: formFields,
        userId,
      });

      // Only reset form for non-update flows (when not connected)
      if (!isUpdate) {
        setShowDirectInputForm(false);
        setFormFields({});
      }
      setIsConnecting(false);
      void refetchIntegrations();
      queryClient.invalidateQueries({ queryKey=[redacted] });
    } catch (error) {
      console.error(
        `[CrmIntegrationCard] Error ${isUpdate ? 'updating' : 'connecting'} ${providerId}:`,
        error,
      );
      setIsConnecting(false);
      toast.error(
        `Failed to connect ${providerName}: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  };

  const handleDisconnect = async () => {
    try {
      setIsDisconnecting(true);
      if (connectionId) {
        await deleteConnection({
          connectionId,
          userId,
        });
        void refetchIntegrations();
        queryClient.invalidateQueries({ queryKey=[redacted] });
      } else {
        toast.error('Could not find connection ID to disconnect.');
      }
    } catch (error) {
      console.error(`[CrmIntegrationCard] Error disconnecting ${providerId}:`, error);
      toast.error(`Failed to disconnect ${providerId}`);
    } finally {
      setIsDisconnecting(false);
    }
  };

  // Icon rendering
  const renderIcon = () => {
    if (providerId === 'hubspot') {
      return (
        <div className="flex h-5 w-5 items-center justify-center rounded bg-[#FF7A59] text-xs font-bold text-white">
          H
        </div>
      );
    } else if (providerId === 'copper') {
      return (
        <div className="flex h-5 w-5 items-center justify-center rounded bg-[#B87333] text-xs font-bold text-white">
          C
        </div>
      );
    } else if (providerId === 'salesforce') {
      return (
        <div className="flex h-5 w-5 items-center justify-center rounded bg-[#00A1E0] text-xs font-bold text-white">
          S
        </div>
      );
    } else {
      return (
        <div className="flex h-5 w-5 items-center justify-center rounded bg-black text-xs font-bold text-white">
          A
        </div>
      );
    }
  };

  const providerName = providerId.charAt(0).toUpperCase() + providerId.slice(1);
  const buttonText = `Continue with ${providerName}`;
  const isUpdate = isApiKeyProvider && isConnected;
  const shouldShowForm =
    isApiKeyProvider && isConnected
      ? true
      : showDirectInputForm && connectionType === 'api_key' && requiredFields.length > 0;

  // Convert field name to human-readable label (e.g., "NextStep" -> "Next Step")
  const formatFieldLabel = (fieldName: string): string => {
    // Convert camelCase/PascalCase to space-separated
    return fieldName
      .replace(/([A-Z])/g, ' $1')
      .replace(/^./, (str) => str.toUpperCase())
      .trim();
  };

  // Get available Cedar fields (core + custom + common values)
  const availableCedarFields = useMemo(() => {
    return getAvailableCedarFields(aopsData?.aops);
  }, [aopsData?.aops]);

  const handleSimpleMappingChange = (
    crmFieldName: string,
    cedarFieldName: string,
    allowedValues?: string[],
  ) => {
    setFieldMappings((prev) => {
      const existing = prev.find((m) => m.crmFieldName === crmFieldName);
      if (existing) {
        // Update existing mapping, preserve allowedValues
        const currentAllowedValues =
          isSimpleMapping(existing) || isComplexMapping(existing)
            ? existing.allowedValues
            : undefined;
        return prev.map((m) =>
          m.crmFieldName === crmFieldName
            ? { crmFieldName, cedarFieldName, allowedValues: allowedValues ?? currentAllowedValues }
            : m,
        );
      } else {
        // Add new mapping
        return [...prev, { crmFieldName, cedarFieldName, allowedValues }];
      }
    });
  };

  const handleAllowedValuesChange = (crmFieldName: string, allowedValues: string[]) => {
    setFieldMappings((prev) => {
      return prev.map((m) => {
        if (m.crmFieldName !== crmFieldName) return m;
        if (isSimpleMapping(m)) {
          return { ...m, allowedValues: allowedValues.length > 0 ? allowedValues : undefined };
        } else if (isComplexMapping(m)) {
          return { ...m, allowedValues: allowedValues.length > 0 ? allowedValues : undefined };
        }
        return m;
      });
    });
  };

  const handleComplexMappingChange = (
    crmFieldName: string,
    template: string,
    transformations?: Record<string, { type: 'date' | 'number' | 'string'; format?: string }>,
    allowedValues?: string[],
    prefix?: string,
  ) => {
    setFieldMappings((prev) => {
      const existing = prev.find((m) => m.crmFieldName === crmFieldName);
      if (existing) {
        // Update existing mapping, preserve allowedValues and prefix
        const currentAllowedValues =
          isSimpleMapping(existing) || isComplexMapping(existing)
            ? existing.allowedValues
            : undefined;
        const currentPrefix = isComplexMapping(existing) ? existing.prefix : undefined;
        return prev.map((m) =>
          m.crmFieldName === crmFieldName
            ? {
                crmFieldName,
                template,
                prefix: prefix !== undefined ? prefix : currentPrefix,
                transformations,
                allowedValues: allowedValues ?? currentAllowedValues,
              }
            : m,
        );
      } else {
        // Add new mapping
        return [...prev, { crmFieldName, template, prefix, transformations, allowedValues }];
      }
    });
  };

  const handleToggleMappingType = (crmFieldName: string, isSimple: boolean) => {
    setFieldMappings((prev) => {
      return prev.map((m) => {
        if (m.crmFieldName !== crmFieldName) return m;
        // Preserve allowedValues when toggling
        const allowedValues =
          isSimpleMapping(m) || isComplexMapping(m) ? m.allowedValues : undefined;
        if (isSimple) {
          // Convert complex to simple - extract first field from template if possible
          if (isComplexMapping(m)) {
            const placeholders = m.template.match(/\{([^}]+)\}/g);
            const firstField = placeholders?.[0]?.replace(/[{}]/g, '') || '';
            return { crmFieldName, cedarFieldName: firstField || '', allowedValues };
          }
          return m;
        } else {
          // Convert simple to complex - create template from cedarFieldName
          if (isSimpleMapping(m)) {
            return {
              crmFieldName,
              template: `{${m.cedarFieldName}}`,
              transformations: {},
              allowedValues,
            };
          }
          return m;
        }
      });
    });
  };

  const handleRemoveMapping = (crmFieldName: string) => {
    setFieldMappings((prev) => prev.filter((m) => m.crmFieldName !== crmFieldName));
  };

  const handleCrmFieldNameChange = (oldCrmFieldName: string, newCrmFieldName: string) => {
    // Check if the new CRM field name is already mapped
    const mappedCrmFields = new Set(fieldMappings.map((m) => m.crmFieldName));
    if (mappedCrmFields.has(newCrmFieldName) && newCrmFieldName !== oldCrmFieldName) {
      toast.error(`Field "${newCrmFieldName}" already has a mapping`);
      return;
    }

    // Auto-populate allowedValues if the new field has picklist values
    const newAllowedValues = extractPicklistValues(
      propertiesData?.picklist_fields?.[newCrmFieldName],
    );

    setFieldMappings((prev) => {
      return prev.map((m) => {
        if (m.crmFieldName !== oldCrmFieldName) return m;

        // Update the CRM field name and allowedValues
        if (isSimpleMapping(m)) {
          return {
            ...m,
            crmFieldName: newCrmFieldName,
            allowedValues: newAllowedValues,
          };
        } else if (isComplexMapping(m)) {
          return {
            ...m,
            allowedValues: newAllowedValues,
            crmFieldName: newCrmFieldName,
          };
        }
        return m;
      });
    });
  };

  const handleAddMapping = (selectedCrmField?: string) => {
    if (!selectedCrmField) return;
    // Check if this CRM field already has a mapping
    const mappedCrmFields = new Set(fieldMappings.map((m) => m.crmFieldName));
    if (mappedCrmFields.has(selectedCrmField)) {
      toast.error(`Field "${selectedCrmField}" already has a mapping`);
      return;
    }
    // Auto-populate allowedValues if this field has picklist values
    const picklistValues = extractPicklistValues(
      propertiesData?.picklist_fields?.[selectedCrmField],
    );
    setFieldMappings((prev) => [
      ...prev,
      {
        crmFieldName: selectedCrmField,
        cedarFieldName: '',
        allowedValues: picklistValues,
      },
    ]);
  };

  const handleSaveCustomObjectConfig = async () => {
    if (!userId) return;
    if (!customObjectObjectName.trim() || !customObjectFilterField.trim()) {
      toast.error('Object name and filter field are required');
      return;
    }
    setIsSavingCustomObjectConfig(true);
    try {
      const fields = customObjectFieldsInput
        .split(',')
        .map((f) => f.trim())
        .filter(Boolean);
      const limit = customObjectLimit ? parseInt(customObjectLimit, 10) : undefined;
      await trpcClient.admin.crmSync.updateCustomObjectConfig.mutate({
        userId,
        provider: 'salesforce',
        config: {
          object_name: customObjectObjectName.trim(),
          filter_field: customObjectFilterField.trim(),
          fields: fields.length > 0 ? fields : undefined,
          limit: limit && !isNaN(limit) ? limit : undefined,
        },
      });
      await refetchCustomObjectConfig();
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Failed to save config');
    } finally {
      setIsSavingCustomObjectConfig(false);
    }
  };

  const handleSaveFields = async () => {
    if (!connectionId) {
      toast.error('Connection ID not found');
      return;
    }

    // Filter out invalid mappings
    const validMappings = fieldMappings.filter((m) => {
      if (isSimpleMapping(m)) {
        return !!m.cedarFieldName && m.cedarFieldName.trim().length > 0;
      } else if (isComplexMapping(m)) {
        return !!m.template && m.template.trim().length > 0;
      }
      return false;
    });

    try {
      setIsSavingFields(true);
      await updateWriteSettings({
        connectionId,
        fieldMappings: validMappings,
        dealSyncFilter: Object.keys(dealSyncFilter).length > 0 ? dealSyncFilter : {},
        stageRequiredFields: Object.keys(stageRequiredFields).length > 0 ? stageRequiredFields : undefined,
        stageSourceField: stageSourceField ?? undefined,
        userId,
        aopId: selectedAopId ?? undefined,
      });
      void refetchIntegrations();
      queryClient.invalidateQueries({ queryKey: ['connections', connectionId] });
    } catch (error) {
      console.error('[CrmIntegrationCard] Error saving settings:', error);
      toast.error(
        `Failed to save settings: ${error instanceof Error ? error.message : String(error)}`,
      );
    } finally {
      setIsSavingFields(false);
    }
  };

  // Render form field with eye toggle for password fields
  const renderFormField = (field: {
    key=[redacted];
    label: string;
    type: string;
    placeholder: string;
  }) => {
    const isPasswordField = field.type === 'password';
    const isApiKeyField = field.key === 'apiKey';

    return (
      <div key=[redacted] className="space-y-2">
        <Label htmlFor={`${providerId}-${field.key}`}>{field.label}</Label>
        <div className="relative">
          <Input
            id={`${providerId}-${field.key}`}
            type={isPasswordField && !showApiKey ? 'password' : 'text'}
            placeholder={field.placeholder}
            value={formFields[field.key] || ''}
            onChange={(e) => setFormFields((prev) => ({ ...prev, [field.key]: e.target.value }))}
            disabled={isConnecting}
            className={isPasswordField ? 'pr-10' : ''}
          />
          {isPasswordField && isApiKeyField && (
            <Button
              type="button"
              variant="ghost"
              size="icon"
              className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
              onClick={() => setShowApiKey(!showApiKey)}
            >
              {showApiKey ? (
                <EyeOff className="h-4 w-4 text-gray-500" />
              ) : (
                <Eye className="h-4 w-4 text-gray-500" />
              )}
            </Button>
          )}
        </div>
      </div>
    );
  };

  return (
    <div className="space-y-4">
      {/* Personal connection badge - shown when user has a legacy connection not configured by their org */}
      {isPersonalConnection && isConnected && (
        <Badge
          variant="outline"
          className="bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/30 dark:text-amber-300 dark:border-amber-800"
        >
          Personal connection (not configured by organization)
        </Badge>
      )}
      {/* Org-managed badge - shown when connection is an org admin install */}
      {isOrgManaged && isConnected && (
        <Badge
          variant="outline"
          className="bg-green-50 text-green-700 border-green-200 dark:bg-green-950/30 dark:text-green-300 dark:border-green-800"
        >
          Connected by organization
        </Badge>
      )}
      {apiKeyInstructions && connectionType === 'api_key' && !isConnected && (
        <div className="bg-muted/30 rounded-lg border p-4">
          <div className="text-muted-foreground whitespace-pre-line text-xs">
            {apiKeyInstructions}
          </div>
        </div>
      )}
      {isConnected && connectionType !== 'api_key' ? (
        <>
          {!isOrgManaged && (
            <Dialog>
              <DialogTrigger asChild>
                <Button
                  size="lg"
                  className="!flex w-full items-center gap-3 !border !border-gray-200 !bg-white !text-gray-800 !shadow-sm hover:!bg-gray-50"
                  disabled={isDisconnecting}
                >
                  {isDisconnecting ? (
                    <>
                      <Loader2 className="h-5 w-5 animate-spin" />
                      <span>Disconnecting...</span>
                    </>
                  ) : (
                    <>
                      <Unplug className="h-5 w-5" />
                      <span>Disconnect {providerName}</span>
                    </>
                  )}
                </Button>
              </DialogTrigger>
              <DialogContent>
                <DialogHeader>
                  <DialogTitle>Disconnect {providerName}</DialogTitle>
                  <DialogDescription>
                    Are you sure you want to disconnect {providerName}?
                  </DialogDescription>
                </DialogHeader>
                <div className="flex justify-end gap-4">
                  <DialogClose asChild>
                    <Button variant="outline">Cancel</Button>
                  </DialogClose>
                  <DialogClose asChild>
                    <Button onClick={handleDisconnect}>Disconnect</Button>
                  </DialogClose>
                </div>
              </DialogContent>
            </Dialog>
          )}

          {/* Reconnect — refreshes OAuth token without disconnecting (safe: upserts token only) */}
          {/* connectionType is already known to be non-'api_key' inside this branch. */}
          {!isOrgManaged && (
            <Button
              variant="outline"
              size="lg"
              className="!flex w-full items-center gap-3 !border !border-gray-200 !bg-white !text-gray-800 !shadow-sm hover:!bg-gray-50"
              onClick={handleConnect}
              disabled={isConnecting}
            >
              {isConnecting ? (
                <>
                  <Loader2 className="h-5 w-5 animate-spin" />
                  <span>Reconnecting...</span>
                </>
              ) : (
                <>
                  <RefreshCw className="h-5 w-5" />
                  <span>Reconnect {providerName}</span>
                </>
              )}
            </Button>
          )}

          {/* CRM Settings */}
          <div className="space-y-3 rounded-lg border bg-muted/30 p-4">
            <div className="text-sm font-medium">CRM Settings</div>
            <div className="space-y-3">
              <div className="flex items-center space-x-2">
                <Checkbox
                  id={`${providerId}-periodic-sync`}
                  checked={currentSettings.periodicDealSyncEnabled}
                  onCheckedChange={(checked) =>
                    handleSettingsChange('periodicDealSyncEnabled', checked === true)
                  }
                />
                <Label
                  htmlFor={`${providerId}-periodic-sync`}
                  className="text-sm font-normal cursor-pointer"
                >
                  <span className="flex items-center gap-2">
                    Enable periodic deal sync
                    {isCustomizedFromOrgDefaults.periodicSync && (
                      <span className="rounded bg-blue-100 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 dark:bg-blue-950 dark:text-blue-300">
                        Customized
                      </span>
                    )}
                  </span>
                  <div className="text-muted-foreground text-xs mt-1">
                    Automatically sync deals from {providerName} on a schedule
                  </div>
                </Label>
              </div>
              <div className="flex items-center space-x-2">
                <Checkbox
                  id={`${providerId}-crm-push`}
                  checked={currentSettings.externalCrmPushEnabled}
                  onCheckedChange={(checked) =>
                    handleSettingsChange('externalCrmPushEnabled', checked === true)
                  }
                />
                <Label
                  htmlFor={`${providerId}-crm-push`}
                  className="text-sm font-normal cursor-pointer"
                >
                  <span className="flex items-center gap-2">
                    Enable CRM writes
                    {isCustomizedFromOrgDefaults.crmWrites && (
                      <span className="rounded bg-blue-100 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 dark:bg-blue-950 dark:text-blue-300">
                        Customized
                      </span>
                    )}
                  </span>
                  <div className="text-muted-foreground text-xs mt-1">
                    Allow writing updates back to {providerName}
                  </div>
                </Label>
              </div>
              <div className="flex items-center space-x-2">
                <Checkbox
                  id={`${providerId}-require-linked-deal-agent-outputs`}
                  checked={currentSettings.requireLinkedDealForAgentOutputs}
                  onCheckedChange={(checked) =>
                    handleSettingsChange('requireLinkedDealForAgentOutputs', checked === true)
                  }
                />
                <Label
                  htmlFor={`${providerId}-require-linked-deal-agent-outputs`}
                  className="text-sm font-normal cursor-pointer"
                >
                  Require linked deal for agent outputs
                  <div className="text-muted-foreground text-xs mt-1">
                    Only create drafts, Slack messages, and tasks for conversations linked to a deal
                    in {providerName}. High-signal meetings can be exceptions, and CRM opportunity
                    tasks are allowed so the agent can create or link the deal.
                  </div>
                </Label>
              </div>
              <div className="flex items-center space-x-2">
                <Checkbox
                  id={`${providerId}-fetch-unowned-deals`}
                  checked={currentSettings.fetchUnownedDeals}
                  onCheckedChange={(checked) =>
                    handleSettingsChange('fetchUnownedDeals', checked === true)
                  }
                />
                <Label
                  htmlFor={`${providerId}-fetch-unowned-deals`}
                  className="text-sm font-normal cursor-pointer"
                >
                  Fetch deals with no owner
                  <div className="text-muted-foreground text-xs mt-1">
                    Also sync deals in {providerName} that have no assigned owner, treating them as
                    belonging to you in Cedar
                  </div>
                </Label>
              </div>
            </div>
          </div>

          {/* AOP picker — scopes everything below (field mappings, deal sync filter, stage
              requirements) to a specific AOP's bound CRM object. Only the AOP is picked here;
              which raw object it's bound to is resolved server-side, never shown as an option. */}
          {hasUpdateOpportunityFields && (aopsData?.aops?.length ?? 0) > 1 && (
            <FieldRows bare dividers={false} density="compact" className="rounded-lg border bg-muted/30 p-3">
              <SelectField
                label="Configuring mappings for"
                options={(aopsData?.aops ?? []).map((aop) => ({ value: aop.id, label: aop.name }))}
                value={selectedAopId ?? undefined}
                onValueChange={(value) => setSelectedAopId(value)}
                placeholder="Select an AOP…"
                triggerClassName="w-auto min-w-[160px]"
              />
            </FieldRows>
          )}

          {/* Deal Sync Filter - filter which deals are synced based on a CRM field value */}
          {hasUpdateOpportunityFields && propertiesData?.field_names && propertiesData.field_names.length > 0 && (
            <div className="space-y-3">
              <div className="rounded-lg border bg-muted/30 p-4">
                <div className="mb-2 flex items-center justify-between">
                  <div>
                    <div className="text-sm font-medium">Deal Sync Filter</div>
                    <p className="text-muted-foreground text-xs mt-1">
                      Only sync deals matching specific criteria
                    </p>
                  </div>
                  <Button
                    variant="outline"
                    size="sm"
                    onClick={() => setShowFilterConfig(!showFilterConfig)}
                  >
                    {showFilterConfig ? (
                      <>
                        <ChevronUp className="mr-1 h-4 w-4" />
                        Hide
                      </>
                    ) : (
                      <>
                        <ChevronDown className="mr-1 h-4 w-4" />
                        Configure
                      </>
                    )}
                  </Button>
                </div>
                {showFilterConfig && (
                  <div className="space-y-3 rounded-lg border bg-muted/30 p-4">
                    <div className="text-muted-foreground text-xs">
                      Filter deals based on a specific field value. Only deals matching the filter
                      will be synced from {providerName}.
                    </div>
                    {Object.keys(dealSyncFilter).length > 0 ? (
                      <div className="space-y-2">
                        {Object.entries(dealSyncFilter).map(([fieldName, fieldValue]) => {
                          const picklistValues = propertiesData?.picklist_fields?.[fieldName];
                          const options = extractPicklistOptions(picklistValues);
                          return (
                            <div
                              key=[redacted]
                              className="flex items-center gap-2 rounded-lg border p-3"
                            >
                              <div className="flex-1">
                                <Popover>
                                  <PopoverTrigger asChild>
                                    <Button
                                      variant="outline"
                                      role="combobox"
                                      className="h-8 w-full justify-between text-xs"
                                    >
                                      {formatFieldLabel(fieldName)}
                                      <ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
                                    </Button>
                                  </PopoverTrigger>
                                  <PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
                                    <Command>
                                      <CommandInput placeholder="Search fields..." />
                                      <CommandList>
                                        <CommandEmpty>No field found.</CommandEmpty>
                                        <CommandGroup>
                                          {propertiesData?.field_names
                                            ?.filter(
                                              (f) => !!propertiesData.picklist_fields?.[f],
                                            )
                                            .map((field) => {
                                              const isSelected = fieldName === field;
                                              return (
                                                <CommandItem
                                                  key=[redacted]
                                                  value={field}
                                                  onSelect={() => {
                                                    // Update the filter with new field, clear the value
                                                    const newFilter = { ...dealSyncFilter };
                                                    delete newFilter[fieldName];
                                                    newFilter[field] = '';
                                                    setDealSyncFilter(newFilter);
                                                  }}
                                                >
                                                  <Check
                                                    className={cn(
                                                      'mr-2 h-4 w-4',
                                                      isSelected ? 'opacity-100' : 'opacity-0',
                                                    )}
                                                  />
                                                  {formatFieldLabel(field)}
                                                  <span className="ml-2 text-xs text-blue-500">
                                                    (picklist)
                                                  </span>
                                                </CommandItem>
                                              );
                                            })}
                                        </CommandGroup>
                                      </CommandList>
                                    </Command>
                                  </PopoverContent>
                                </Popover>
                              </div>
                              <span className="text-muted-foreground text-sm">=</span>
                              <div className="flex-1">
                                {options && options.length > 0 ? (
                                  <Select
                                    value={fieldValue}
                                    onValueChange={(value) => {
                                      setDealSyncFilter((prev) => ({
                                        ...prev,
                                        [fieldName]: value,
                                      }));
                                    }}
                                  >
                                    <SelectTrigger >
                                      <SelectValue placeholder="Select value..." />
                                    </SelectTrigger>
                                    <SelectContent>
                                      {options.map((option) => (
                                        <SelectItem key=[redacted] value={option.value}>
                                          {option.label}
                                        </SelectItem>
                                      ))}
                                    </SelectContent>
                                  </Select>
                                ) : (
                                  <Input
                                    value={fieldValue}
                                    onChange={(e) => {
                                      setDealSyncFilter((prev) => ({
                                        ...prev,
                                        [fieldName]: e.target.value,
                                      }));
                                    }}
                                    placeholder="Enter value..."
                                    className="h-8 text-xs"
                                  />
                                )}
                              </div>
                              <Button
                                variant="ghost"
                                size="icon"
                                onClick={() => {
                                  const newFilter = { ...dealSyncFilter };
                                  delete newFilter[fieldName];
                                  setDealSyncFilter(newFilter);
                                }}
                                className="h-8 w-8"
                              >
                                <X className="h-4 w-4" />
                              </Button>
                            </div>
                          );
                        })}
                      </div>
                    ) : (
                      <div className="text-muted-foreground text-sm text-center py-4">
                        No filter configured. All deals will be synced.
                      </div>
                    )}
                    {/* Add filter button - only show picklist fields */}
                    {(() => {
                      const picklistFields = propertiesData?.field_names?.filter(
                        (f) =>
                          !!propertiesData.picklist_fields?.[f] &&
                          !Object.keys(dealSyncFilter).includes(f),
                      );
                      if (!picklistFields || picklistFields.length === 0) return null;
                      return (
                        <Popover>
                          <PopoverTrigger asChild>
                            <Button variant="outline" className="w-full">
                              <Plus className="mr-2 h-4 w-4" />
                              Add Filter
                            </Button>
                          </PopoverTrigger>
                          <PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
                            <Command>
                              <CommandInput placeholder="Search picklist fields..." />
                              <CommandList>
                                <CommandEmpty>No picklist field found.</CommandEmpty>
                                <CommandGroup>
                                  {picklistFields.map((field) => (
                                    <CommandItem
                                      key=[redacted]
                                      value={field}
                                      onSelect={() => {
                                        setDealSyncFilter((prev) => ({
                                          ...prev,
                                          [field]: '',
                                        }));
                                      }}
                                    >
                                      {formatFieldLabel(field)}
                                      <span className="ml-2 text-xs text-blue-500">(picklist)</span>
                                    </CommandItem>
                                  ))}
                                </CommandGroup>
                              </CommandList>
                            </Command>
                          </PopoverContent>
                        </Popover>
                      );
                    })()}
                    <Button
                      size="sm"
                      onClick={handleSaveFields}
                      disabled={isSavingFields}
                      className="w-full"
                    >
                      {isSavingFields ? (
                        <>
                          <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                          Saving...
                        </>
                      ) : (
                        'Save Filter'
                      )}
                    </Button>
                  </div>
                )}
              </div>
            </div>
          )}

          {/* Stage Required Fields */}
          {hasUpdateOpportunityFields && (
            <div className="space-y-3">
              <div className="rounded-lg border bg-muted/30 p-4">
                <div className="mb-2 flex items-center justify-between">
                  <div>
                    <div className="text-sm font-medium">Stage Required Fields</div>
                    <p className="text-muted-foreground mt-1 text-xs">
                      Fields required when moving a deal to each stage.
                    </p>
                  </div>
                  <Button
                    variant="outline"
                    size="sm"
                    onClick={() => setShowStageRequiredConfig(!showStageRequiredConfig)}
                  >
                    {showStageRequiredConfig ? (
                      <><ChevronUp className="mr-1 h-4 w-4" />Hide</>
                    ) : (
                      <><ChevronDown className="mr-1 h-4 w-4" />Configure</>
                    )}
                  </Button>
                </div>
                {showStageRequiredConfig && (
                  <div className="space-y-3">
                    {/* Stage field picker */}
                    <div className="flex items-center gap-2">
                      <span className="text-xs text-muted-foreground shrink-0">Stage field:</span>
                      <Popover>
                        <PopoverTrigger asChild>
                          <button type="button" className="flex items-center gap-1 rounded border px-2 py-1 text-xs hover:bg-muted min-w-[140px] justify-between">
                            <span className="font-mono truncate">{stageSourceField ?? 'Pick a field…'}</span>
                            <ChevronDown className="h-3 w-3 shrink-0 text-muted-foreground" />
                          </button>
                        </PopoverTrigger>
                        <PopoverContent className="w-[260px] p-0">
                          <Command>
                            <CommandInput placeholder="Search fields..." />
                            <CommandList className="max-h-[200px]">
                              <CommandEmpty>No fields found.</CommandEmpty>
                              <CommandGroup>
                                {(propertiesData?.field_names ?? [])
                                  .filter((f) => !!propertiesData?.picklist_fields?.[f])
                                  .map((field) => (
                                    <CommandItem
                                      key=[redacted]
                                      value={field}
                                      onSelect={() => handleStageSourceFieldChange(field)}
                                    >
                                      <span className="font-mono text-xs">{field}</span>
                                    </CommandItem>
                                  ))}
                              </CommandGroup>
                            </CommandList>
                          </Command>
                        </PopoverContent>
                      </Popover>
                    </div>
                    {Object.keys(stageRequiredFields).length === 0 ? (
                      <p className="text-muted-foreground py-2 text-center text-xs">
                        {stageSourceField ? 'No values found for this field.' : 'Pick a field above to see its stages.'}
                      </p>
                    ) : (
                      <div className="space-y-2">
                        {(() => {
                          const raw = stageSourceField
                            ? propertiesData?.picklist_fields?.[stageSourceField]
                            : null;
                          const orderedKeys = raw
                            ? (raw as Array<string | { label: string; value: string }>).map((v) =>
                                typeof v === 'string' ? v : v.label,
                              )
                            : [];
                          const entries =
                            orderedKeys.length > 0
                              ? orderedKeys
                                  .filter((k) => k in stageRequiredFields)
                                  .map((k) => [k, stageRequiredFields[k]!] as [string, string[]])
                              : Object.entries(stageRequiredFields);
                          return entries.map(([stageName, requiredFields]) => (
                          <div key=[redacted] className="rounded-lg border bg-background p-2 space-y-1.5">
                            <div className="text-xs font-medium text-muted-foreground">
                              To enter: <span className="text-foreground">{stageName}</span>
                            </div>
                            <div className="flex flex-wrap gap-1 min-h-[24px]">
                              {requiredFields.map((field) => (
                                <span
                                  key=[redacted]
                                  className="inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 font-mono text-xs"
                                >
                                  {field}
                                  <button
                                    type="button"
                                    onClick={() =>
                                      setStageRequiredFields((prev) => ({
                                        ...prev,
                                        [stageName]: prev[stageName]!.filter((f) => f !== field),
                                      }))
                                    }
                                    className="text-muted-foreground hover:text-foreground"
                                  >
                                    <X className="h-2.5 w-2.5" />
                                  </button>
                                </span>
                              ))}
                              <Popover>
                                <PopoverTrigger asChild>
                                  <button
                                    type="button"
                                    className="inline-flex items-center gap-0.5 rounded border border-dashed px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-muted"
                                  >
                                    <Plus className="h-2.5 w-2.5" />
                                    Add field
                                  </button>
                                </PopoverTrigger>
                                <PopoverContent className="w-[260px] p-0">
                                  <Command>
                                    <CommandInput placeholder="Search fields..." />
                                    <CommandList className="max-h-[200px]">
                                      <CommandEmpty>No fields found.</CommandEmpty>
                                      <CommandGroup>
                                        {(propertiesData?.field_names ?? [])
                                          .filter((f) => !requiredFields.includes(f))
                                          .map((field) => (
                                            <CommandItem
                                              key=[redacted]
                                              value={field}
                                              onSelect={() =>
                                                setStageRequiredFields((prev) => ({
                                                  ...prev,
                                                  [stageName]: [...(prev[stageName] ?? []), field],
                                                }))
                                              }
                                            >
                                              <span className="font-mono text-xs">{field}</span>
                                            </CommandItem>
                                          ))}
                                      </CommandGroup>
                                    </CommandList>
                                  </Command>
                                </PopoverContent>
                              </Popover>
                            </div>
                          </div>
                        ));
                        })()}
                      </div>
                    )}
                    <Button
                      size="sm"
                      onClick={handleSaveFields}
                      disabled={isSavingFields}
                      className="w-full"
                    >
                      {isSavingFields ? <Loader2 className="mr-2 h-3 w-3 animate-spin" /> : null}
                      Save Stage Requirements
                    </Button>
                  </div>
                )}
              </div>
            </div>
          )}

          {/* Field Selection - shown when provider supports field mappings (can configure even if writes aren't enabled) */}
          {hasUpdateOpportunityFields && propertiesData?.field_names && propertiesData.field_names.length > 0 && (
            <div className="space-y-3">
              <div className="rounded-lg border bg-muted/30 p-4">
                <div className="mb-2 flex items-center justify-between">
                  <div>
                    <div className="text-sm font-medium">
                      Field Mappings{selectedObjectApiName && selectedAop ? ` (${selectedAop.name})` : ''}
                    </div>
                    {!currentSettings.externalCrmPushEnabled && (
                      <p className="text-muted-foreground text-xs mt-1">
                        Configure mappings now. They will be used when CRM writes are enabled.
                      </p>
                    )}
                  </div>
                  <div className="flex items-center gap-2">
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={async () => {
                        try {
                          await refetchProperties();
                        } catch (error) {
                          toast.error(
                            `Failed to refresh field names: ${error instanceof Error ? error.message : String(error)}`,
                          );
                        }
                      }}
                      disabled={isFetchingProperties}
                    >
                      {isFetchingProperties ? (
                        <Loader2 className="h-4 w-4 animate-spin" />
                      ) : (
                        <RefreshCw className="h-4 w-4" />
                      )}
                    </Button>
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={() => setShowFieldSelection(!showFieldSelection)}
                    >
                      {showFieldSelection ? (
                        <>
                          <ChevronUp className="mr-1 h-4 w-4" />
                          Hide
                        </>
                      ) : (
                        <>
                          <ChevronDown className="mr-1 h-4 w-4" />
                          Configure
                        </>
                      )}
                    </Button>
                  </div>
                </div>
                {showFieldSelection && (
                  <div className="space-y-3 rounded-lg border bg-muted/30 p-4">
                    <div className="text-muted-foreground text-xs">
                      Map each CRM field to a Cedar field. When Cedar fields are updated, they will
                      automatically sync to the mapped CRM fields.
                    </div>
                    <div className="space-y-3">
                      {fieldMappings.length > 0 ? (
                        fieldMappings.map((mapping) => {
                          const isSimple = isSimpleMapping(mapping);
                          return (
                            <div
                              key=[redacted]
                              className="space-y-2 rounded-lg border p-3"
                            >
                              <div className="flex items-center justify-between">
                                <div className="flex-1 mr-2">
                                  <Popover>
                                    <PopoverTrigger asChild>
                                      <Button
                                        variant="outline"
                                        role="combobox"
                                        className="h-7 w-full justify-between text-xs font-medium"
                                      >
                                        {formatFieldLabel(mapping.crmFieldName)}
                                        <ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
                                      </Button>
                                    </PopoverTrigger>
                                    <PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
                                      <Command>
                                        <CommandInput placeholder="Search CRM fields..." />
                                        <CommandList>
                                          <CommandEmpty>No CRM field found.</CommandEmpty>
                                          <CommandGroup>
                                            {propertiesData?.field_names?.map((field) => {
                                              const isMapped = fieldMappings.some(
                                                (m) =>
                                                  m.crmFieldName === field &&
                                                  m.crmFieldName !== mapping.crmFieldName,
                                              );
                                              const isSelected = mapping.crmFieldName === field;
                                              const hasPicklist =
                                                !!propertiesData.picklist_fields?.[field];
                                              return (
                                                <CommandItem
                                                  key=[redacted]
                                                  value={field}
                                                  disabled={isMapped}
                                                  onSelect={() => {
                                                    if (!isMapped) {
                                                      handleCrmFieldNameChange(
                                                        mapping.crmFieldName,
                                                        field,
                                                      );
                                                    }
                                                  }}
                                                >
                                                  <Check
                                                    className={cn(
                                                      'mr-2 h-4 w-4',
                                                      isSelected ? 'opacity-100' : 'opacity-0',
                                                    )}
                                                  />
                                                  {formatFieldLabel(field)}
                                                  {hasPicklist && (
                                                    <span className="ml-2 text-xs text-blue-500">
                                                      (picklist)
                                                    </span>
                                                  )}
                                                  {isMapped && ' (already mapped)'}
                                                </CommandItem>
                                              );
                                            })}
                                          </CommandGroup>
                                        </CommandList>
                                      </Command>
                                    </PopoverContent>
                                  </Popover>
                                </div>
                                <div className="flex items-center gap-2">
                                  <ToggleGroup
                                    type="single"
                                    value={isSimple ? 'simple' : 'advanced'}
                                    onValueChange={(value) => {
                                      if (value === 'simple' || value === 'advanced') {
                                        handleToggleMappingType(
                                          mapping.crmFieldName,
                                          value === 'simple',
                                        );
                                      }
                                    }}
                                    className="h-7"
                                  >
                                    <ToggleGroupItem
                                      value="simple"
                                      aria-label="Simple mapping"
                                      size="sm"
                                    >
                                      Simple
                                    </ToggleGroupItem>
                                    <ToggleGroupItem
                                      value="advanced"
                                      aria-label="Advanced mapping"
                                      size="sm"
                                    >
                                      Advanced
                                    </ToggleGroupItem>
                                  </ToggleGroup>
                                  <Button
                                    variant="ghost"
                                    size="icon"
                                    onClick={() => handleRemoveMapping(mapping.crmFieldName)}
                                    className="h-7 w-7"
                                  >
                                    <X className="h-4 w-4" />
                                  </Button>
                                </div>
                              </div>
                              {isSimple ? (
                                <div className="space-y-2">
                                  <Select
                                    value={mapping.cedarFieldName || undefined}
                                    onValueChange={(value) =>
                                      handleSimpleMappingChange(
                                        mapping.crmFieldName,
                                        value,
                                        mapping.allowedValues,
                                      )
                                    }
                                  >
                                    <SelectTrigger className="mt-1">
                                      <SelectValue placeholder="Select Cedar field" />
                                    </SelectTrigger>
                                    <SelectContent>
                                      {availableCedarFields.map((field) => (
                                        <SelectItem key=[redacted] value={field.value}>
                                          {field.label}
                                        </SelectItem>
                                      ))}
                                    </SelectContent>
                                  </Select>
                                  <div>
                                    <Label className="text-xs text-muted-foreground mb-1 block">
                                      Allowed Values (optional, comma-separated)
                                    </Label>
                                    <Input
                                      id={`allowed-values-simple-${mapping.crmFieldName}`}
                                      value={
                                        allowedValuesInputs[`simple-${mapping.crmFieldName}`] ??
                                        (mapping.allowedValues && mapping.allowedValues.length > 0
                                          ? // Detect format preference from existing data or default to ", "
                                            mapping.allowedValues.join(', ')
                                          : '') ??
                                        ''
                                      }
                                      onChange={(e) => {
                                        const rawValue = e.target.value;
                                        setAllowedValuesInputs((prev) => ({
                                          ...prev,
                                          [`simple-${mapping.crmFieldName}`]: rawValue,
                                        }));
                                      }}
                                      onBlur={() => {
                                        const rawValue =
                                          allowedValuesInputs[`simple-${mapping.crmFieldName}`] ??
                                          mapping.allowedValues?.join(',') ??
                                          '';
                                        // Detect if user prefers ", " or "," format
                                        const hasSpacesAfterCommas = rawValue.includes(', ');
                                        const separator = hasSpacesAfterCommas ? ', ' : ',';

                                        const values = rawValue
                                          .split(',')
                                          .map((v) => v.trim())
                                          .filter((v) => v.length > 0);
                                        handleAllowedValuesChange(mapping.crmFieldName, values);
                                        // Preserve the user's formatting preference
                                        const normalizedRaw = values.join(separator);
                                        setAllowedValuesInputs((prev) => ({
                                          ...prev,
                                          [`simple-${mapping.crmFieldName}`]: normalizedRaw,
                                        }));
                                      }}
                                      placeholder="Prospecting, Qualification, Closed Won"
                                      className="text-xs"
                                    />
                                    <div className="text-muted-foreground text-xs mt-1">
                                      Only values in this list will be written to the CRM (useful
                                      for enum fields like deal stage)
                                    </div>
                                  </div>
                                </div>
                              ) : (
                                <div className="space-y-2 mt-2">
                                  <div>
                                    <Label className="text-xs text-muted-foreground mb-1 block">
                                      Template (use {'{fieldName}'} for placeholders)
                                    </Label>
                                    <div className="flex gap-2">
                                      <Input
                                        id={`template-${mapping.crmFieldName}`}
                                        value={mapping.template}
                                        onChange={(e) =>
                                          handleComplexMappingChange(
                                            mapping.crmFieldName,
                                            e.target.value,
                                            mapping.transformations,
                                            mapping.allowedValues,
                                            mapping.prefix,
                                          )
                                        }
                                        placeholder="{nextStepDate} – {nextSteps}"
                                        className="text-xs font-mono flex-1"
                                      />
                                      <Select
                                        onValueChange={(value) => {
                                          const input = document.getElementById(
                                            `template-${mapping.crmFieldName}`,
                                          ) as HTMLInputElement;
                                          if (input) {
                                            const start = input.selectionStart || 0;
                                            const end = input.selectionEnd || 0;
                                            const currentValue = mapping.template;
                                            const newValue =
                                              currentValue.slice(0, start) +
                                              `{${value}}` +
                                              currentValue.slice(end);
                                            handleComplexMappingChange(
                                              mapping.crmFieldName,
                                              newValue,
                                              mapping.transformations,
                                              mapping.allowedValues,
                                              mapping.prefix,
                                            );
                                            // Set cursor position after inserted variable
                                            setTimeout(() => {
                                              input.focus();
                                              const newCursorPos = start + value.length + 2; // +2 for { and }
                                              input.setSelectionRange(newCursorPos, newCursorPos);
                                            }, 0);
                                          }
                                        }}
                                      >
                                        <SelectTrigger className="w-[140px]">
                                          <Plus className="h-3 w-3 mr-1" />
                                          <span>Insert Variable</span>
                                        </SelectTrigger>
                                        <SelectContent>
                                          {availableCedarFields.map((field) => (
                                            <SelectItem key=[redacted] value={field.value}>
                                              {field.label}
                                            </SelectItem>
                                          ))}
                                        </SelectContent>
                                      </Select>
                                    </div>
                                  </div>
                                  <div>
                                    <Label className="text-xs text-muted-foreground mb-1 block">
                                      Prefix (optional, use {'{variable}'} for placeholders)
                                    </Label>
                                    <div className="flex gap-2">
                                      <Input
                                        id={`prefix-${mapping.crmFieldName}`}
                                        value={mapping.prefix || ''}
                                        onChange={(e) =>
                                          handleComplexMappingChange(
                                            mapping.crmFieldName,
                                            mapping.template,
                                            mapping.transformations,
                                            mapping.allowedValues,
                                            e.target.value || undefined,
                                          )
                                        }
                                        placeholder="MS:  or {status}: "
                                        className="text-xs font-mono flex-1"
                                      />
                                      <Select
                                        onValueChange={(value) => {
                                          const input = document.getElementById(
                                            `prefix-${mapping.crmFieldName}`,
                                          ) as HTMLInputElement;
                                          if (input) {
                                            const start = input.selectionStart || 0;
                                            const end = input.selectionEnd || 0;
                                            const currentValue = mapping.prefix || '';
                                            const newValue =
                                              currentValue.slice(0, start) +
                                              `{${value}}` +
                                              currentValue.slice(end);
                                            handleComplexMappingChange(
                                              mapping.crmFieldName,
                                              mapping.template,
                                              mapping.transformations,
                                              mapping.allowedValues,
                                              newValue || undefined,
                                            );
                                            // Set cursor position after inserted variable
                                            setTimeout(() => {
                                              input.focus();
                                              const newCursorPos = start + value.length + 2; // +2 for { and }
                                              input.setSelectionRange(newCursorPos, newCursorPos);
                                            }, 0);
                                          }
                                        }}
                                      >
                                        <SelectTrigger className="w-[140px]">
                                          <Plus className="h-3 w-3 mr-1" />
                                          <span>Insert Variable</span>
                                        </SelectTrigger>
                                        <SelectContent>
                                          {availableCedarFields.map((field) => (
                                            <SelectItem key=[redacted] value={field.value}>
                                              {field.label}
                                            </SelectItem>
                                          ))}
                                        </SelectContent>
                                      </Select>
                                    </div>
                                    <div className="text-muted-foreground text-xs mt-1">
                                      Prefix will be added to the value only if it&apos;s not
                                      already present (case-insensitive)
                                    </div>
                                  </div>
                                  <div>
                                    <Label className="text-xs text-muted-foreground mb-1 block">
                                      Allowed Values (optional, comma-separated)
                                    </Label>
                                    <Input
                                      id={`allowed-values-complex-${mapping.crmFieldName}`}
                                      value={
                                        allowedValuesInputs[`complex-${mapping.crmFieldName}`] ??
                                        (mapping.allowedValues && mapping.allowedValues.length > 0
                                          ? // Detect format preference from existing data or default to ", "
                                            mapping.allowedValues.join(', ')
                                          : '') ??
                                        ''
                                      }
                                      onChange={(e) => {
                                        const rawValue = e.target.value;
                                        setAllowedValuesInputs((prev) => ({
                                          ...prev,
                                          [`complex-${mapping.crmFieldName}`]: rawValue,
                                        }));
                                      }}
                                      onBlur={() => {
                                        const rawValue =
                                          allowedValuesInputs[`complex-${mapping.crmFieldName}`] ??
                                          mapping.allowedValues?.join(',') ??
                                          '';
                                        // Detect if user prefers ", " or "," format
                                        const hasSpacesAfterCommas = rawValue.includes(', ');
                                        const separator = hasSpacesAfterCommas ? ', ' : ',';

                                        const values = rawValue
                                          .split(',')
                                          .map((v) => v.trim())
                                          .filter((v) => v.length > 0);
                                        handleAllowedValuesChange(mapping.crmFieldName, values);
                                        // Preserve the user's formatting preference
                                        const normalizedRaw = values.join(separator);
                                        setAllowedValuesInputs((prev) => ({
                                          ...prev,
                                          [`complex-${mapping.crmFieldName}`]: normalizedRaw,
                                        }));
                                      }}
                                      placeholder="Prospecting, Qualification, Closed Won"
                                      className="text-xs"
                                    />
                                    <div className="text-muted-foreground text-xs mt-1">
                                      Only values in this list will be written to the CRM (useful
                                      for enum fields like deal stage)
                                    </div>
                                  </div>
                                  {(mapping.template || mapping.prefix) && (
                                    <div className="space-y-2">
                                      {(() => {
                                        // Extract placeholders from both template and prefix
                                        const templatePlaceholders =
                                          mapping.template?.match(/\{([^}]+)\}/g) || [];
                                        const prefixPlaceholders =
                                          mapping.prefix?.match(/\{([^}]+)\}/g) || [];
                                        const allPlaceholders = [
                                          ...new Set([
                                            ...templatePlaceholders,
                                            ...prefixPlaceholders,
                                          ]),
                                        ];
                                        return allPlaceholders.map((placeholder) => {
                                          const fieldName = placeholder.replace(/[{}]/g, '');
                                          const transformation =
                                            mapping.transformations?.[fieldName];
                                          return (
                                            <div
                                              key=[redacted]
                                              className="flex items-center gap-2 text-xs"
                                            >
                                              <Label className="text-muted-foreground min-w-[100px]">
                                                {fieldName}:
                                              </Label>
                                              <Select
                                                value={transformation?.type || 'none'}
                                                onValueChange={(value) => {
                                                  const newTransformations: Record<
                                                    string,
                                                    {
                                                      type: 'date' | 'number' | 'string';
                                                      format?: string;
                                                    }
                                                  > = {};
                                                  if (
                                                    value !== 'none' &&
                                                    (value === 'date' ||
                                                      value === 'number' ||
                                                      value === 'string')
                                                  ) {
                                                    Object.keys(
                                                      mapping.transformations || {},
                                                    ).forEach((key) => {
                                                      if (
                                                        key !== fieldName &&
                                                        mapping.transformations?.[key]
                                                      ) {
                                                        const trans = mapping.transformations[key];
                                                        if (
                                                          trans.type === 'date' ||
                                                          trans.type === 'number' ||
                                                          trans.type === 'string'
                                                        ) {
                                                          newTransformations[key] = {
                                                            type: trans.type,
                                                            format: trans.format,
                                                          };
                                                        }
                                                      }
                                                    });
                                                    newTransformations[fieldName] = {
                                                      type: value,
                                                      format: transformation?.format,
                                                    };
                                                  } else {
                                                    // Keep other transformations, just remove this one
                                                    Object.keys(
                                                      mapping.transformations || {},
                                                    ).forEach((key) => {
                                                      if (
                                                        key !== fieldName &&
                                                        mapping.transformations?.[key]
                                                      ) {
                                                        const trans = mapping.transformations[key];
                                                        if (
                                                          trans.type === 'date' ||
                                                          trans.type === 'number' ||
                                                          trans.type === 'string'
                                                        ) {
                                                          newTransformations[key] = {
                                                            type: trans.type,
                                                            format: trans.format,
                                                          };
                                                        }
                                                      }
                                                    });
                                                  }
                                                  handleComplexMappingChange(
                                                    mapping.crmFieldName,
                                                    mapping.template,
                                                    Object.keys(newTransformations).length > 0
                                                      ? (newTransformations as Record<
                                                          string,
                                                          {
                                                            type: 'date' | 'number' | 'string';
                                                            format?: string;
                                                          }
                                                        >)
                                                      : undefined,
                                                    mapping.allowedValues,
                                                    mapping.prefix,
                                                  );
                                                }}
                                              >
                                                <SelectTrigger size="sm" >
                                                  <SelectValue />
                                                </SelectTrigger>
                                                <SelectContent>
                                                  <SelectItem value="none">
                                                    No transformation
                                                  </SelectItem>
                                                  <SelectItem value="date">Date</SelectItem>
                                                  <SelectItem value="number">Number</SelectItem>
                                                  <SelectItem value="string">String</SelectItem>
                                                </SelectContent>
                                              </Select>
                                              {transformation?.type === 'date' && (
                                                <div className="flex gap-1">
                                                  <Select
                                                    value={
                                                      transformation.format &&
                                                      DATE_FORMAT_OPTIONS.find(
                                                        (opt) =>
                                                          opt.value === transformation.format,
                                                      )
                                                        ? transformation.format
                                                        : 'custom'
                                                    }
                                                    onValueChange={(value) => {
                                                      if (value === 'custom') return;
                                                      const newTransformations: Record<
                                                        string,
                                                        {
                                                          type: 'date' | 'number' | 'string';
                                                          format?: string;
                                                        }
                                                      > = {};
                                                      // Copy existing transformations
                                                      if (mapping.transformations) {
                                                        for (const [key, trans] of Object.entries(
                                                          mapping.transformations,
                                                        )) {
                                                          if (
                                                            trans.type === 'date' ||
                                                            trans.type === 'number' ||
                                                            trans.type === 'string'
                                                          ) {
                                                            newTransformations[key] = {
                                                              type: trans.type,
                                                              format: trans.format,
                                                            };
                                                          }
                                                        }
                                                      }
                                                      // Update or add the current field's transformation
                                                      newTransformations[fieldName] = {
                                                        type: 'date' as const,
                                                        format: value,
                                                      };
                                                      handleComplexMappingChange(
                                                        mapping.crmFieldName,
                                                        mapping.template,
                                                        newTransformations,
                                                        mapping.allowedValues,
                                                        mapping.prefix,
                                                      );
                                                    }}
                                                  >
                                                    <SelectTrigger size="sm" className="w-32">
                                                      <SelectValue placeholder="Format" />
                                                    </SelectTrigger>
                                                    <SelectContent>
                                                      {DATE_FORMAT_OPTIONS.map((option) => (
                                                        <SelectItem
                                                          key=[redacted]
                                                          value={option.value}
                                                        >
                                                          <div className="flex items-center gap-2">
                                                            <span className="font-mono text-xs">
                                                              {option.value}
                                                            </span>
                                                            <span className="text-muted-foreground text-xs">
                                                              {option.example}
                                                            </span>
                                                          </div>
                                                        </SelectItem>
                                                      ))}
                                                      <SelectItem value="custom">
                                                        Custom...
                                                      </SelectItem>
                                                    </SelectContent>
                                                  </Select>
                                                  <Input
                                                    value={transformation.format || ''}
                                                    onChange={(e) => {
                                                      const newTransformations: Record<
                                                        string,
                                                        {
                                                          type: 'date' | 'number' | 'string';
                                                          format?: string;
                                                        }
                                                      > = {};
                                                      // Copy existing transformations
                                                      if (mapping.transformations) {
                                                        for (const [key, trans] of Object.entries(
                                                          mapping.transformations,
                                                        )) {
                                                          if (
                                                            trans.type === 'date' ||
                                                            trans.type === 'number' ||
                                                            trans.type === 'string'
                                                          ) {
                                                            newTransformations[key] = {
                                                              type: trans.type,
                                                              format: trans.format,
                                                            };
                                                          }
                                                        }
                                                      }
                                                      // Update or add the current field's transformation
                                                      newTransformations[fieldName] = {
                                                        type: 'date' as const,
                                                        format: e.target.value,
                                                      };
                                                      handleComplexMappingChange(
                                                        mapping.crmFieldName,
                                                        mapping.template,
                                                        newTransformations,
                                                        mapping.allowedValues,
                                                        mapping.prefix,
                                                      );
                                                    }}
                                                    placeholder="yyyy-MM-dd"
                                                    className="h-7 text-xs font-mono w-32"
                                                  />
                                                </div>
                                              )}
                                            </div>
                                          );
                                        });
                                      })()}
                                    </div>
                                  )}
                                </div>
                              )}
                            </div>
                          );
                        })
                      ) : (
                        <div className="text-muted-foreground text-sm text-center py-4">
                          No field mappings configured. Click &quot;Add Field Mapping&quot; to get
                          started.
                        </div>
                      )}
                      {propertiesData?.field_names && propertiesData.field_names.length > 0 && (
                        <div className="space-y-2">
                          <Popover>
                            <PopoverTrigger asChild>
                              <Button
                                variant="outline"
                                role="combobox"
                                className="w-full justify-between"
                              >
                                {fieldMappings.length === 0
                                  ? 'Select CRM field to map...'
                                  : 'Add another CRM field mapping...'}
                                <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
                              </Button>
                            </PopoverTrigger>
                            <PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
                              <Command>
                                <CommandInput placeholder="Search CRM fields..." />
                                <CommandList>
                                  <CommandEmpty>No CRM field found.</CommandEmpty>
                                  <CommandGroup>
                                    {propertiesData.field_names.map((field) => {
                                      const isMapped = fieldMappings.some(
                                        (m) => m.crmFieldName === field,
                                      );
                                      const hasPicklist =
                                        !!propertiesData.picklist_fields?.[field];
                                      return (
                                        <CommandItem
                                          key=[redacted]
                                          value={field}
                                          disabled={isMapped}
                                          onSelect={() => {
                                            if (!isMapped) {
                                              handleAddMapping(field);
                                            }
                                          }}
                                        >
                                          <Check className={cn('mr-2 h-4 w-4', 'opacity-0')} />
                                          {formatFieldLabel(field)}
                                          {hasPicklist && (
                                            <span className="ml-2 text-xs text-blue-500">
                                              (picklist)
                                            </span>
                                          )}
                                          {isMapped && ' (already mapped)'}
                                        </CommandItem>
                                      );
                                    })}
                                  </CommandGroup>
                                </CommandList>
                              </Command>
                            </PopoverContent>
                          </Popover>
                        </div>
                      )}
                    </div>
                    <Button
                      size="sm"
                      onClick={handleSaveFields}
                      disabled={isSavingFields}
                      className="w-full"
                    >
                      {isSavingFields ? (
                        <>
                          <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                          Saving...
                        </>
                      ) : (
                        'Save Field Mappings'
                      )}
                    </Button>
                  </div>
                )}
              </div>
            </div>
          )}

          {/* Salesforce custom object config — admin-only (userId required) */}
          {providerId === 'salesforce' && !!userId && (
            <div className="space-y-3 rounded-lg border bg-muted/30 p-4">
              <div className="flex items-center justify-between">
                <div>
                  <div className="text-sm font-medium">Custom Object Sync</div>
                  <div className="text-muted-foreground text-xs mt-0.5">
                    Configure a Salesforce custom object to sync per conversation via Account ID.
                  </div>
                </div>
                <Button
                  variant="outline"
                  size="sm"
                  onClick={() => setShowCustomObjectConfig(!showCustomObjectConfig)}
                >
                  {showCustomObjectConfig ? (
                    <><ChevronUp className="mr-1 h-4 w-4" />Hide</>
                  ) : (
                    <><ChevronDown className="mr-1 h-4 w-4" />Configure</>
                  )}
                </Button>
              </div>
              {customObjectConfigData?.config && !showCustomObjectConfig && (
                <div className="flex flex-wrap gap-1.5 text-xs">
                  <Badge variant="secondary">{customObjectConfigData.config.object_name}</Badge>
                  <Badge variant="secondary">filter: {customObjectConfigData.config.filter_field}</Badge>
                  {(customObjectConfigData.config.fields?.length ?? 0) > 0 && (
                    <Badge variant="secondary">{customObjectConfigData.config.fields!.length} fields</Badge>
                  )}
                </div>
              )}
              {showCustomObjectConfig && (
                <div className="space-y-3">
                  <div className="grid grid-cols-2 gap-3">
                    <div className="space-y-1">
                      <Label className="text-xs">Object Name *</Label>
                      <Input
                        placeholder="e.g. Cursor_Team__c"
                        value={customObjectObjectName}
                        onChange={(e) => setCustomObjectObjectName(e.target.value)}
                        className="h-8 text-xs"
                      />
                    </div>
                    <div className="space-y-1">
                      <Label className="text-xs">Filter Field *</Label>
                      <Input
                        placeholder="e.g. Account__c"
                        value={customObjectFilterField}
                        onChange={(e) => setCustomObjectFilterField(e.target.value)}
                        className="h-8 text-xs"
                      />
                    </div>
                  </div>
                  <div className="space-y-1">
                    <Label className="text-xs">Fields (optional, comma-separated)</Label>
                    <Input
                      placeholder="e.g. Id, Name, Team_ID__c"
                      value={customObjectFieldsInput}
                      onChange={(e) => setCustomObjectFieldsInput(e.target.value)}
                      className="h-8 text-xs"
                    />
                  </div>
                  <div className="flex items-end gap-3">
                    <div className="w-24 space-y-1">
                      <Label className="text-xs">Limit</Label>
                      <Input
                        type="number"
                        placeholder="50"
                        value={customObjectLimit}
                        onChange={(e) => setCustomObjectLimit(e.target.value)}
                        className="h-8 text-xs"
                      />
                    </div>
                    <Button
                      size="sm"
                      onClick={handleSaveCustomObjectConfig}
                      disabled={isSavingCustomObjectConfig}
                    >
                      {isSavingCustomObjectConfig ? (
                        <><Loader2 className="mr-2 h-4 w-4 animate-spin" />Saving...</>
                      ) : (
                        'Save'
                      )}
                    </Button>
                  </div>
                </div>
              )}
            </div>
          )}

          {/* Deal Manager - shown in playground/admin mode, hidden in user-facing mode */}
          {showDealManager && !userFacing && (
            <CrmDealManager providerId={providerId} userId={userId} />
          )}
        </>
      ) : isConnected && connectionType === 'api_key' ? (
        <>
          {/* CRM Settings for API key connections */}
          <div className="space-y-3 rounded-lg border bg-muted/30 p-4">
            <div className="text-sm font-medium">CRM Settings</div>
            <div className="space-y-3">
              <div className="flex items-center space-x-2">
                <Checkbox
                  id={`${providerId}-periodic-sync-api`}
                  checked={currentSettings.periodicDealSyncEnabled}
                  onCheckedChange={(checked) =>
                    handleSettingsChange('periodicDealSyncEnabled', checked === true)
                  }
                />
                <Label
                  htmlFor={`${providerId}-periodic-sync-api`}
                  className="text-sm font-normal cursor-pointer"
                >
                  <span className="flex items-center gap-2">
                    Enable periodic deal sync
                    {isCustomizedFromOrgDefaults.periodicSync && (
                      <span className="rounded bg-blue-100 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 dark:bg-blue-950 dark:text-blue-300">
                        Customized
                      </span>
                    )}
                  </span>
                  <div className="text-muted-foreground text-xs mt-1">
                    Automatically sync deals from {providerName} on a schedule
                  </div>
                </Label>
              </div>
              <div className="flex items-center space-x-2">
                <Checkbox
                  id={`${providerId}-crm-push-api`}
                  checked={currentSettings.externalCrmPushEnabled}
                  onCheckedChange={(checked) =>
                    handleSettingsChange('externalCrmPushEnabled', checked === true)
                  }
                />
                <Label
                  htmlFor={`${providerId}-crm-push-api`}
                  className="text-sm font-normal cursor-pointer"
                >
                  <span className="flex items-center gap-2">
                    Enable CRM writes
                    {isCustomizedFromOrgDefaults.crmWrites && (
                      <span className="rounded bg-blue-100 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 dark:bg-blue-950 dark:text-blue-300">
                        Customized
                      </span>
                    )}
                  </span>
                  <div className="text-muted-foreground text-xs mt-1">
                    Allow writing updates back to {providerName}
                  </div>
                </Label>
              </div>
              <div className="flex items-center space-x-2">
                <Checkbox
                  id={`${providerId}-require-linked-deal-agent-outputs-api`}
                  checked={currentSettings.requireLinkedDealForAgentOutputs}
                  onCheckedChange={(checked) =>
                    handleSettingsChange('requireLinkedDealForAgentOutputs', checked === true)
                  }
                />
                <Label
                  htmlFor={`${providerId}-require-linked-deal-agent-outputs-api`}
                  className="text-sm font-normal cursor-pointer"
                >
                  Require linked deal for agent outputs
                  <div className="text-muted-foreground text-xs mt-1">
                    Only create drafts, Slack messages, and tasks for conversations linked to a deal
                    in {providerName}. High-signal meetings can be exceptions, and CRM opportunity
                    tasks are allowed so the agent can create or link the deal.
                  </div>
                </Label>
              </div>
              <div className="flex items-center space-x-2">
                <Checkbox
                  id={`${providerId}-fetch-unowned-deals-api`}
                  checked={currentSettings.fetchUnownedDeals}
                  onCheckedChange={(checked) =>
                    handleSettingsChange('fetchUnownedDeals', checked === true)
                  }
                />
                <Label
                  htmlFor={`${providerId}-fetch-unowned-deals-api`}
                  className="text-sm font-normal cursor-pointer"
                >
                  Fetch deals with no owner
                  <div className="text-muted-foreground text-xs mt-1">
                    Also sync deals in {providerName} that have no assigned owner, treating them as
                    belonging to you in Cedar
                  </div>
                </Label>
              </div>
            </div>
          </div>

          {/* Deal Manager - shown in playground/admin mode, hidden in user-facing mode */}
          {showDealManager && !userFacing && (
            <CrmDealManager providerId={providerId} userId={userId} />
          )}
        </>
      ) : shouldShowForm && connectionType === 'api_key' && requiredFields.length > 0 ? (
        <div className="space-y-4">
          {requiredFields.map(renderFormField)}
          <div className="flex gap-2">
            {!isUpdate && (
              <Button
                variant="outline"
                size="lg"
                className="flex-1"
                onClick={() => {
                  setShowDirectInputForm(false);
                  setFormFields({});
                }}
                disabled={isConnecting}
              >
                Cancel
              </Button>
            )}
            {isApiKeyProvider && isConnected && (
              <Dialog>
                <DialogTrigger asChild>
                  <Button variant="outline" size="lg" className="flex-1" disabled={isDisconnecting}>
                    {isDisconnecting ? (
                      <>
                        <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                        Disconnecting...
                      </>
                    ) : (
                      <>
                        <Unplug className="mr-2 h-4 w-4" />
                        Disconnect
                      </>
                    )}
                  </Button>
                </DialogTrigger>
                <DialogContent>
                  <DialogHeader>
                    <DialogTitle>Disconnect {providerName}</DialogTitle>
                    <DialogDescription>
                      Are you sure you want to disconnect {providerName}?
                    </DialogDescription>
                  </DialogHeader>
                  <div className="flex justify-end gap-4">
                    <DialogClose asChild>
                      <Button variant="outline">Cancel</Button>
                    </DialogClose>
                    <DialogClose asChild>
                      <Button onClick={handleDisconnect}>Disconnect</Button>
                    </DialogClose>
                  </div>
                </DialogContent>
              </Dialog>
            )}
            <Button
              size="lg"
              className={`${isApiKeyProvider && isConnected ? 'flex-1' : 'flex-1'} border border-gray-200 bg-white text-gray-800 shadow-sm hover:bg-gray-50`}
              onClick={handleDirectInputConnect}
              disabled={isConnecting}
            >
              {isConnecting ? (
                <>
                  <Loader2 className="mr-2 h-5 w-5 animate-spin" />
                  <span>Connecting...</span>
                </>
              ) : isApiKeyProvider && isConnected ? (
                'Update'
              ) : (
                'Connect'
              )}
            </Button>
          </div>
          {/* Deal Manager - shown in playground/admin mode, hidden in user-facing mode */}
          {isApiKeyProvider && isConnected && showDealManager && !userFacing && (
            <CrmDealManager providerId={providerId} userId={userId} />
          )}
        </div>
      ) : connectionType === 'manual' ? null : ( // Manual providers shouldn't show connect button
        <Button
          size="lg"
          className="flex w-full items-center gap-3 border border-gray-200 bg-white text-gray-800 shadow-sm hover:bg-gray-50"
          onClick={handleConnect}
          disabled={isConnecting}
        >
          {isConnecting ? (
            <>
              <Loader2 className="h-5 w-5 animate-spin" />
              <span>Connecting...</span>
            </>
          ) : (
            <>
              {renderIcon()}
              <span>{buttonText}</span>
            </>
          )}
        </Button>
      )}
    </div>
  );
}