import React, { useState, useEffect } from 'react';
import { useParams, useSearchParams, useNavigate, useLocation } from 'react-router-dom';
import { useObjectMetadata, useLayout } from '../infrastructure/api/objectMetadataHooks';
import { MetadataRecordList } from '../features/record/components/MetadataRecordList';
import { MetadataRecordDetail } from '../features/record/components/MetadataRecordDetail';
import {
    buildMetadataRecordFormSubmitData,
    MetadataRecordForm,
} from '../features/record/components/MetadataRecordForm';
import { RecordTypePicker } from '../features/record/components/RecordTypePicker';
import {
    applyStandardActionRecordGuards,
    resolveVisibleStandardActionsForRecord,
} from '../features/record/components/ActionRendererHost';
import { standardActionAvailabilityPolicy } from '../features/record/components/standardActionAvailabilityConfig';
import {
    standardActionAvailabilityFromQueryState,
    standardActionsForAvailabilityPolicy,
} from '../features/record/components/standardActionResponse';
import { resolveRecordStatePresentation } from '../features/record/recordStatePresentation';
import { MetadataAwareSkeleton } from '@nexuscrm/salesforce-crm-standard-frontend/platform/components/ui';
import { AccessDeniedEmptyState, EmptyState } from '@nexuscrm/salesforce-crm-standard-frontend/platform/components/ui';
import { getObjectPagePermissionErrorCopy } from '../features/objects/objectPagePermissionErrors';

import { COMMON_FIELDS, OBJECT_CREATE_PARENT_CONTEXT_PARAMS, ROUTES } from '../core/constants';
import { RECORD_TAB_PARAM, buildExitEditUrl } from '../features/record/recordTabs';
import { tryGetSetupCapabilityPagePathForAnyOwner, tryGetSetupPagePathForAnyOwner } from '../core/navigation/setupPagePaths';
import { Button } from '@nexuscrm/salesforce-crm-standard-frontend/platform/components/ui';
import { ArrowLeft } from 'lucide-react';
import { SYSTEM_TABLE_NAMES } from '@nexuscrm/generated-schema';
import { useRecord, RECORD_KEYS } from '../features/record/hooks/useRecord';
import { useRecordUI, selectRecordFromUI, RECORD_UI_KEYS } from '../features/record/hooks/useRecordUI';
import { normalizeRecordAccess, selectRecordAccessFromUI } from '../infrastructure/api/data/recordUtils';
import { useQueryClient, useQuery } from '@tanstack/react-query';
import { escapeSoqlValue, isMetadataObject, isUIAPIRecordUnsupported, isValidFieldApiName } from '../infrastructure/api/types';
import { PageLayout, normalizeRelatedListId, type RecordAccess, type SObjectDescribe } from '../types';
import { uiAPI } from '../infrastructure/api/ui';
import { actionAPI, type StandardAction } from '../infrastructure/api/actions';
import { dataAPI } from '../infrastructure/api/data';
import type { StandardActionPreviewResult } from '../infrastructure/api/data/actions';
import { APIError } from '../infrastructure/api/client';
import { useCanCreateObject, useCanEditObject } from '../permissions/objectPermissions';
import { useApp } from '@nexuscrm/host-sdk';
import type { UIFieldInfo, UIObjectInfo, UIRecordDefaults } from '../infrastructure/api/ui';
import { normalizeFieldInputValue } from '../features/objects/fieldInputValue';
import {
    buildActionCreateRoutePlan,
    buildActionCreateSubmitInput,
    findStandardAction,
    getCreatedRecordIdFromActionOutput,
    getEditableCreateLayoutFieldNames,
    getMissingRequiredActionCreateInputNames,
    isActionCreateRouteParam,
    mergePreviewFieldsIntoInitialData,
    readActionCreateRouteParams,
    type ActionCreateRoutePlan,
    validatePreviewParent,
} from '../features/record/standardActionCreateRoute';
import {
    buildCreateDefaultFieldValues,
    mergeCreateFieldValueOverrides,
    requiresCreateRecordTypeSelection,
    resolveCloneRecordTypeState,
} from '../features/record/recordDefaultFieldValues';
import {
    resolveCrmParentBackRoute as resolveParentBackRoute,
    resolveCrmRecordDefaultsParentContext as resolveRecordDefaultsParentContext,
} from '../../object-view-route-policy';

export interface ObjectViewRouteCapabilityContext {
    objectApiName: string;
    normalizedObjectApiName: string;
    recordId?: string;
    recordData?: Record<string, unknown> | null;
    locationState?: unknown;
    currentFrontendAppId?: string | null;
    navigate: ReturnType<typeof useNavigate>;
    navigateToObjectList: () => void;
    openCreateModal: () => void;
}

export interface ObjectViewRouteCapability {
    shouldFetchRecord?: (context: ObjectViewRouteCapabilityContext) => boolean;
    onCreateNew?: (context: ObjectViewRouteCapabilityContext) => void;
    renderDetail?: (context: ObjectViewRouteCapabilityContext) => React.ReactNode;
    renderCreateModal?: (context: {
        isOpen: boolean;
        onClose: () => void;
    }) => React.ReactNode;
}

export type ObjectViewRouteCapabilityMap = Readonly<Record<string, ObjectViewRouteCapability>>;

interface ObjectViewProps {
    objectRouteCapabilities?: ObjectViewRouteCapabilityMap;
}

type UIRecordTypeInfo = NonNullable<UIObjectInfo['recordTypeInfos']>[string];

// Map URL parameter names to actual field names using metadata-driven approach
// Instead of hardcoding all possible __c field names, look up fields based on
// the target object's metadata to find reference fields that match the pattern
function mapUrlParamToField(paramName: string, metadata: SObjectDescribe | null | undefined): string {
  // For common standard fields, use direct mapping
  const standardParamMap: Record<string, string> = {
    'OwnerId': 'OwnerId',
    'RecordTypeId': 'RecordTypeId',
  };

  if (standardParamMap[paramName]) {
    return standardParamMap[paramName];
  }

  // For lookup fields, try to find corresponding field in target object
  // e.g., ParentId parameter should map to a field on the target object that references Parent
  if (paramName.endsWith('Id')) {
    const baseObjectName = paramName.slice(0, -2); // Remove 'Id' suffix

    // Look for a field on the target object that references the base object
    if (metadata?.fields) {
      for (const field of metadata.fields) {
        if (field.type === 'reference' && field.referenceTo &&
            Array.isArray(field.referenceTo) && field.referenceTo.includes(baseObjectName)) {
          return field.name; // Return the actual field name from the object metadata
        }
      }
    }

    // If no exact match found, try common naming patterns for custom objects
    // This is a fallback approach that's more metadata-aware
    const fallbackFieldName = baseObjectName + '__c';
    return fallbackFieldName;
  }

  // Return the original param name if no mapping found
  return paramName;
}

export function getCreateUrlPrefillData(
    searchParams: URLSearchParams,
    metadata: SObjectDescribe | null | undefined,
    actionCreateRouteRequested: boolean,
): Record<string, string> {
    if (actionCreateRouteRequested) {
        return {};
    }

    return Object.fromEntries(
        Array.from(searchParams.entries())
            .filter(([key]) => !RESERVED_CREATE_PARAMS.has(key) && !isActionCreateRouteParam(key))
            .map(([key, value]) => [mapUrlParamToField(key, metadata), value])
    );
}

export function getCreatePrefillSearchFromListParams(searchParams: URLSearchParams): string {
    const preservedParams = new URLSearchParams();
    searchParams.forEach((value, key) => {
        if (RESERVED_PARENT_CONTEXT_PARAMS.includes(key) || (!RESERVED_LIST_PARAMS.has(key) && isValidFieldApiName(key))) {
            preservedParams.set(key, value);
        }
    });
    const queryString = preservedParams.toString();
    return queryString ? `?${queryString}` : '';
}

export function getSystemObjectCreateRedirectTarget(
    objectApiName: string | undefined,
): string | undefined {
    if (!objectApiName) return undefined;
    const systemObjectRedirects: Record<string, string | undefined> = {
        [SYSTEM_TABLE_NAMES.FLOWDEFINITION]: tryGetSetupCapabilityPagePathForAnyOwner('flowList'),
        [SYSTEM_TABLE_NAMES.FLOW]: tryGetSetupCapabilityPagePathForAnyOwner('flowList'),
        [SYSTEM_TABLE_NAMES.SHARINGRULE]: tryGetSetupPagePathForAnyOwner('sharing'),
        [SYSTEM_TABLE_NAMES.GROUP]: tryGetSetupPagePathForAnyOwner('groups'),
    };
    return systemObjectRedirects[objectApiName];
}

export function shouldFetchCloneDefaultsForCreate(
    isCreate: boolean,
    cloneId: string | null,
    actionCreateRouteRequested: boolean,
): boolean {
    return !!cloneId && isCreate && !actionCreateRouteRequested;
}

export function getActionCreateContextFieldNames(
    isCreate: boolean,
    metadata: SObjectDescribe | null | undefined,
    createInitialData: Record<string, unknown> | undefined,
    requiredContextFieldNames: Set<string> = new Set(),
): Set<string> {
    const names = new Set<string>();
    if (!isCreate || !metadata || !createInitialData) {
        return names;
    }
    const fieldMap = new Map((metadata.fields || []).map(field => [field.name, field]));
    requiredContextFieldNames.forEach(fieldName => {
        const value = createInitialData[fieldName];
        if (value === undefined || value === null || value === '') {
            return;
        }
        if (fieldName === 'RecordTypeId') {
            names.add(fieldName);
            return;
        }
        const field = fieldMap.get(fieldName);
        if (field?.createable) {
            names.add(fieldName);
        }
    });
    return names;
}

export function buildCreateInitialData({
    isCreate,
    useCloneDefaults,
    cloneDefaults,
    createDefaults,
    objectFields,
    searchParams,
    metadata,
    actionCreateRouteRequested,
    activeSelectedRecordTypeId,
    fieldSurfacePolicyScope,
}: {
    isCreate: boolean;
    useCloneDefaults: boolean;
    cloneDefaults: UIRecordDefaults | undefined;
    createDefaults: UIRecordDefaults | undefined;
    objectFields: Record<string, UIFieldInfo> | undefined;
    searchParams: URLSearchParams;
    metadata: SObjectDescribe | null | undefined;
    actionCreateRouteRequested: boolean;
    activeSelectedRecordTypeId: string | null | undefined;
    fieldSurfacePolicyScope?: string;
}): Record<string, unknown> | undefined {
    if (!isCreate) return undefined;

    const defaultSource = useCloneDefaults ? cloneDefaults : createDefaults;
    const defaultFieldInfos = defaultSource?.objectInfo?.fields ?? objectFields ?? {};
    const defaultFieldValues = buildCreateDefaultFieldValues(
        defaultSource,
        defaultFieldInfos,
        fieldSurfacePolicyScope,
    );
    const urlParamData = getCreateUrlPrefillData(searchParams, metadata, actionCreateRouteRequested);
    return mergeCreateFieldValueOverrides(defaultFieldValues, {
        ...urlParamData,
        ...(activeSelectedRecordTypeId ? { RecordTypeId: activeSelectedRecordTypeId } : {}),
    }, fieldSurfacePolicyScope);
}

export function buildActionCreateBaseInitialData({
    actionCreateRouteRequested,
    createInitialData,
    resolvedCreateRecordTypeId,
}: {
    actionCreateRouteRequested: boolean;
    createInitialData: Record<string, unknown> | undefined;
    resolvedCreateRecordTypeId: string | null | undefined;
}): Record<string, unknown> | undefined {
    if (!actionCreateRouteRequested || !resolvedCreateRecordTypeId) {
        return createInitialData;
    }
    const initialRecordTypeId = createInitialData?.RecordTypeId;
    if (initialRecordTypeId !== undefined && initialRecordTypeId !== null && initialRecordTypeId !== '') {
        return createInitialData;
    }
    return {
        ...(createInitialData || {}),
        RecordTypeId: resolvedCreateRecordTypeId,
    };
}

export function shouldBuildActionCreateRoutePlan({
    actionCreateRouteRequested,
    actionCreateRouteParamReady,
    needsRecordTypePicker,
    standardActionsLoading,
    targetDefaultsLoading,
    targetLayoutLoading,
    sourceRecordLoading,
    sourceMetadataLoading,
    sourceAccessLoading,
    sourceLayoutLoading,
}: {
    actionCreateRouteRequested: boolean;
    actionCreateRouteParamReady: boolean;
    needsRecordTypePicker: boolean;
    standardActionsLoading: boolean;
    targetDefaultsLoading: boolean;
    targetLayoutLoading: boolean;
    sourceRecordLoading: boolean;
    sourceMetadataLoading: boolean;
    sourceAccessLoading: boolean;
    sourceLayoutLoading: boolean;
}): boolean {
    return actionCreateRouteRequested
        && actionCreateRouteParamReady
        && !needsRecordTypePicker
        && !standardActionsLoading
        && !targetDefaultsLoading
        && !targetLayoutLoading
        && !sourceRecordLoading
        && !sourceMetadataLoading
        && !sourceAccessLoading
        && !sourceLayoutLoading;
}

export function getActionCreateInputKey(input: Record<string, unknown>): string {
    return JSON.stringify(input);
}

export function isActionCreateInputPreviewed(
    inputKey: string,
    lastPreviewedInputKey: string | null,
): boolean {
    return !!lastPreviewedInputKey && lastPreviewedInputKey === inputKey;
}

export function buildActionCreatePreviewFormData(
    metadata: SObjectDescribe | null | undefined,
    createInitialData: Record<string, unknown> | undefined,
    draftData: Record<string, unknown> | null,
    editableFieldNames: Set<string> = new Set(),
    dirtyFieldNames?: Set<string>,
    createHintFieldNames?: Set<string> | string[],
    forceIncludeFieldNames?: Set<string> | string[],
): Record<string, unknown> {
    if (!metadata) {
        return draftData || {};
    }
    const fieldMap = new Map((metadata.fields || []).map(field => [field.name, field]));
    const forceIncludeFields = forceIncludeFieldNames instanceof Set
        ? forceIncludeFieldNames
        : new Set(forceIncludeFieldNames || []);
    const hiddenCreateInitialData = createInitialData
        ? Object.fromEntries(
            Object.entries(createInitialData)
                .filter(([fieldName]) => !editableFieldNames.has(fieldName))
        )
        : undefined;
    const actionDraftData = Object.fromEntries(
        Object.entries(draftData || {}).filter(([fieldName, value]) => {
            if (!editableFieldNames.has(fieldName)) {
                return false;
            }
            if (forceIncludeFields.has(fieldName)) {
                return true;
            }
            if (dirtyFieldNames) {
                return dirtyFieldNames.has(fieldName);
            }
            if (!createInitialData || !(fieldName in createInitialData)) {
                return true;
            }
            const field = fieldMap.get(fieldName);
            if (!field) {
                return true;
            }
            const normalizedDraftValue = normalizeFieldInputValue(field, value);
            const normalizedDefaultValue = normalizeFieldInputValue(field, createInitialData[fieldName]);
            return !Object.is(normalizedDraftValue, normalizedDefaultValue);
        })
    );
    return buildMetadataRecordFormSubmitData({
        data: actionDraftData,
        initialData: hiddenCreateInitialData,
        objectMetadata: metadata,
        mutationMode: 'create',
        createHintFieldNames,
        dirtyFieldNames,
    });
}

export function buildCreateHintFieldNames({
    isCreate,
    useCloneDefaults,
    createDefaults,
    explicitFieldNames,
}: {
    isCreate: boolean;
    useCloneDefaults: boolean;
    createDefaults: UIRecordDefaults | undefined;
    explicitFieldNames?: Set<string> | string[];
}): Set<string> {
    if (!isCreate || useCloneDefaults) {
        return new Set<string>();
    }

    const hintFieldNames = new Set(createDefaults?.siblingMaxDefaultedFields || []);
    const explicitFields = explicitFieldNames instanceof Set
        ? explicitFieldNames
        : new Set(explicitFieldNames || []);
    explicitFields.forEach(fieldName => hintFieldNames.delete(fieldName));
    return hintFieldNames;
}

export function buildEffectiveCreateHintFieldNames(
    createHintFieldNames: Set<string> | string[],
    actionCreatePreview?: Pick<StandardActionPreviewResult, 'parentPreview'>,
): Set<string> {
    const hintFieldNames = createHintFieldNames instanceof Set
        ? new Set(createHintFieldNames)
        : new Set(createHintFieldNames || []);
    const parentPreview = actionCreatePreview?.parentPreview;
    if (!parentPreview?.fields) {
        return hintFieldNames;
    }
    Object.keys(parentPreview.fields).forEach(fieldName => {
        hintFieldNames.delete(fieldName);
    });
    parentPreview.siblingMaxDefaultedFields?.forEach(fieldName => {
        if (fieldName) {
            hintFieldNames.add(fieldName);
        }
    });
    return hintFieldNames;
}

export function buildActionCreatePreviewSupersededHintFieldNames(
    createHintFieldNames: Set<string> | string[],
    actionCreatePreview?: Pick<StandardActionPreviewResult, 'parentPreview'>,
): Set<string> {
    const baseHintFieldNames = createHintFieldNames instanceof Set
        ? createHintFieldNames
        : new Set(createHintFieldNames || []);
    const parentPreview = actionCreatePreview?.parentPreview;
    if (!parentPreview?.fields) {
        return new Set<string>();
    }
    const previewHintFieldNames = new Set(parentPreview.siblingMaxDefaultedFields || []);
    const out = new Set<string>();
    Object.keys(parentPreview.fields).forEach(fieldName => {
        if (baseHintFieldNames.has(fieldName) && !previewHintFieldNames.has(fieldName)) {
            out.add(fieldName);
        }
    });
    return out;
}

export function buildActionCreatePreviewValidationInput(
    plan: Pick<ActionCreateRoutePlan, 'sourceInputs' | 'fieldInputByTargetField' | 'blockedInputNames'>,
    actionInput: Record<string, unknown>,
    previewOwnedFieldNames: Set<string> | string[],
    dirtyFieldNames?: Set<string> | string[],
): Record<string, unknown> {
    const previewOwnedFields = previewOwnedFieldNames instanceof Set
        ? previewOwnedFieldNames
        : new Set(previewOwnedFieldNames || []);
    if (previewOwnedFields.size === 0) {
        return actionInput;
    }
    const dirtyFields = dirtyFieldNames instanceof Set
        ? dirtyFieldNames
        : new Set(dirtyFieldNames || []);
    const out = { ...actionInput };
    previewOwnedFields.forEach(fieldName => {
        if (dirtyFields.has(fieldName)) {
            return;
        }
        const actionInputConfig = plan.fieldInputByTargetField.get(fieldName);
        if (!actionInputConfig || plan.blockedInputNames.has(actionInputConfig.name)) {
            return;
        }
        if (Object.prototype.hasOwnProperty.call(plan.sourceInputs, actionInputConfig.name)) {
            out[actionInputConfig.name] = plan.sourceInputs[actionInputConfig.name];
        } else {
            delete out[actionInputConfig.name];
        }
    });
    return out;
}

const RESERVED_PARENT_CONTEXT_PARAMS: string[] = Object.values(OBJECT_CREATE_PARENT_CONTEXT_PARAMS);
// RECORD_TAB_PARAM is reserved on both routes even though the tab belongs to the
// record page: MetadataRecordDetail is also mounted inside SplitViewPanel, which
// renders on the list route, and it is a published package entry any host can
// mount. An unreserved parameter here is not inert — unreserved keys that look
// like field API names are compiled straight into the list's WHERE clause and
// into create-form prefills, so `?tab=related` would become `tab = 'related'`.
const RESERVED_LIST_PARAMS = new Set(['edit', 'clone', RECORD_TAB_PARAM, ...RESERVED_PARENT_CONTEXT_PARAMS]);
const RESERVED_CREATE_PARAMS = new Set(['edit', 'clone', RECORD_TAB_PARAM, ...RESERVED_PARENT_CONTEXT_PARAMS]);
const LOCKED_ACTION_CREATE_SOURCE_ACCESS: RecordAccess = {
    hasReadAccess: true,
    hasEditAccess: false,
    hasDeleteAccess: false,
    isLocked: true,
};

function isForbiddenError(error: unknown): error is APIError {
    return error instanceof APIError && error.status === 403;
}

export const ObjectView: React.FC<ObjectViewProps> = ({
    objectRouteCapabilities,
}) => {
    const { objectApiName, recordId } = useParams<{ objectApiName: string; recordId: string }>();
    const [searchParams] = useSearchParams();
    const navigate = useNavigate();
    const location = useLocation();
    const queryClient = useQueryClient();
    const { currentFrontendAppId } = useApp();
    const fieldSurfacePolicyScope = currentFrontendAppId ?? undefined;
    const normalizedObjectApiName = (objectApiName || '').toLowerCase();
    const objectRouteCapability = objectRouteCapabilities?.[normalizedObjectApiName];
    const locationState = location.state;
    const canCreateObject = useCanCreateObject(objectApiName || '');
    const canEditObject = useCanEditObject(objectApiName || '');

    // Local state for editing
    const [isEditing, setIsEditing] = useState(false);
    const [activeCreateModalObject, setActiveCreateModalObject] = useState<string | null>(null);
    const [selectedRecordType, setSelectedRecordType] = useState<{ objectApiName: string; recordTypeId: string } | null>(null);
    const [actionCreateDraftData, setActionCreateDraftData] = useState<Record<string, unknown> | null>(null);
    const [actionCreateDirtyFieldNames, setActionCreateDirtyFieldNames] = useState<Set<string>>(() => new Set());
    const [actionCreateLastPreviewedInputKey, setActionCreateLastPreviewedInputKey] = useState<string | null>(null);

    const navigateToObjectList = React.useCallback(() => {
        navigate(ROUTES.OBJECT.LIST(objectApiName || ''));
    }, [navigate, objectApiName]);

    const openCreateModal = React.useCallback(() => {
        setActiveCreateModalObject(normalizedObjectApiName);
    }, [normalizedObjectApiName]);

    const buildObjectRouteCapabilityContext = React.useCallback((
        currentRecordData?: Record<string, unknown> | null,
    ): ObjectViewRouteCapabilityContext => ({
        objectApiName: objectApiName || '',
        normalizedObjectApiName,
        recordId,
        recordData: currentRecordData,
        locationState,
        currentFrontendAppId,
        navigate,
        navigateToObjectList,
        openCreateModal,
    }), [
        currentFrontendAppId,
        locationState,
        navigate,
        navigateToObjectList,
        normalizedObjectApiName,
        objectApiName,
        openCreateModal,
        recordId,
    ]);

    // Derived State
    const isCreate = recordId === 'new';
    const mode = isCreate ? 'Create' : (isEditing ? 'Edit' : 'Detail');
    const activeSelectedRecordTypeId = selectedRecordType?.objectApiName === (objectApiName || '')
        ? selectedRecordType.recordTypeId
        : null;
    const actionCreateRouteParams = React.useMemo(
        () => isCreate ? readActionCreateRouteParams(searchParams) : null,
        [isCreate, searchParams],
    );
    const actionCreateRouteRequested = !!actionCreateRouteParams;
    const actionCreateRouteParamReady = !!(
        actionCreateRouteParams?.actionName &&
        actionCreateRouteParams.sourceObject &&
        actionCreateRouteParams.sourceRecordId
    );
    const urlRecordTypeId = actionCreateRouteRequested ? null : searchParams.get('RecordTypeId');
    const explicitCreateRecordTypeId = activeSelectedRecordTypeId || urlRecordTypeId || null;

    const shouldFetchRecord = !!recordId
        && recordId !== 'new'
        && (objectRouteCapability?.shouldFetchRecord?.(buildObjectRouteCapabilityContext()) ?? true);
    const isMeta = isMetadataObject(objectApiName || '');
    const isUnsupportedByUIAPI = isUIAPIRecordUnsupported(objectApiName || '');
    const skipRecordUI = isMeta || isUnsupportedByUIAPI;
    const { data: uiData, isLoading: uiLoading, error: uiError, refetch: refetchUI } =
        useRecordUI(objectApiName || '', (shouldFetchRecord && !skipRecordUI) ? recordId : undefined);

    // Sync isEditing state with URL query param
    useEffect(() => {
        const editParam = searchParams.get('edit');
        if (editParam === 'true') {
            setIsEditing(true);
        } else {
            setIsEditing(false);
        }
    }, [searchParams]);

    // Fetch Metadata
    const { metadata, loading: metaLoading, error: metaError } = useObjectMetadata(objectApiName || '');
    const metaPermissionCopy = getObjectPagePermissionErrorCopy(metaError);
    const createDefaultsEnabled =
        isCreate &&
        !!objectApiName &&
        !isMetadataObject(objectApiName || '') &&
        !isUIAPIRecordUnsupported(objectApiName || '');
    const cloneId = searchParams.get('clone');
    const shouldFetchClone = shouldFetchCloneDefaultsForCreate(isCreate, cloneId, actionCreateRouteRequested);
    // Fetch object info to get record type information (for create mode)
    const { data: objectInfo, isLoading: objectInfoLoading, error: objectInfoError } = useQuery({
        queryKey: ['ui', 'objectInfo', objectApiName],
        queryFn: () => uiAPI.getObjectInfo(objectApiName || ''),
        enabled: createDefaultsEnabled && !shouldFetchClone,
        staleTime: 10 * 60 * 1000,
    });
    const recordTypeInfos: Record<string, UIRecordTypeInfo> | undefined = objectInfo?.recordTypeInfos;
    const hasMultipleRecordTypes = React.useMemo(() => {
        if (!recordTypeInfos) return false;
        const nonMasterTypes = Object.values(recordTypeInfos).filter(
            rt => !rt.master && rt.available
        );
        return nonMasterTypes.length > 1;
    }, [recordTypeInfos]);
    const needsRecordTypePicker = requiresCreateRecordTypeSelection(
        isCreate,
        shouldFetchClone,
        hasMultipleRecordTypes,
        explicitCreateRecordTypeId,
    );
    const resolvedCreateRecordTypeId = explicitCreateRecordTypeId || objectInfo?.defaultRecordTypeId || null;
    const hasVisibleCreateRecordType = React.useMemo(() => {
        if (!recordTypeInfos) return true;
        return Object.values(recordTypeInfos).some(rt => rt.available);
    }, [recordTypeInfos]);
    const recordDefaultsParentContext = React.useMemo(
        () => isCreate ? resolveRecordDefaultsParentContext(metadata, searchParams) : null,
        [isCreate, metadata, searchParams],
    );

    const cloneRequestRecordTypeState = resolveCloneRecordTypeState(explicitCreateRecordTypeId, undefined);

    const { data: createDefaults, isLoading: createDefaultsLoading, error: createDefaultsError } = useQuery({
        queryKey: [
            'ui',
            'record-defaults',
            objectApiName,
            resolvedCreateRecordTypeId,
            recordDefaultsParentContext?.parentFieldApiName || '',
            recordDefaultsParentContext?.parentRecordId || '',
        ],
        queryFn: () => uiAPI.getRecordDefaults(objectApiName || '', resolvedCreateRecordTypeId || undefined, recordDefaultsParentContext || undefined),
        enabled: createDefaultsEnabled && !shouldFetchClone && !needsRecordTypePicker && !!resolvedCreateRecordTypeId,
        staleTime: 10 * 60 * 1000,
    });
    const { data: cloneDefaults, isLoading: cloneDefaultsLoading, error: cloneDefaultsError } = useQuery({
        queryKey: ['ui', 'record-defaults', 'clone', cloneId, objectApiName, cloneRequestRecordTypeState.requestOverrideRecordTypeId],
        queryFn: () => uiAPI.getCloneDefaults(
            cloneId || '',
            objectApiName || '',
            cloneRequestRecordTypeState.requestOverrideRecordTypeId,
        ),
        enabled: shouldFetchClone,
        staleTime: 10 * 60 * 1000,
    });
    const cloneRecordTypeState = resolveCloneRecordTypeState(
        explicitCreateRecordTypeId,
        cloneDefaults?.recordTypeId,
    );

    // Metadata-driven button discovery. GET /actions/standard returns only the
    // active _System_StandardAction rows the current user is allowed to invoke —
    // so admin `IsActive=0` deactivation AND profile-level permission denial
    // both translate directly into "button does not render", with no frontend
    // code changes and no 404 surprises after the user clicks.
    const standardActionsTargetObject = actionCreateRouteRequested
        ? actionCreateRouteParams?.sourceObject || objectApiName || ''
        : objectApiName || '';
    const {
        data: standardActionsResponse,
        isLoading: standardActionsLoading,
        isFetching: standardActionsFetching,
        isError: standardActionsUnavailable,
        error: standardActionsError,
    } = useQuery({
        queryKey: ['actions', 'standard', standardActionsTargetObject],
        queryFn: () => actionAPI.getStandardActions(standardActionsTargetObject),
        enabled: actionCreateRouteRequested || !shouldFetchRecord || skipRecordUI,
        // Standard action inputs/defaults are admin metadata. Keep this
        // query live so UI validation follows metadata changes without a
        // frontend release or a long client-side cache window.
        staleTime: 0,
    });
    const standardActionAvailability = React.useMemo(
        () => standardActionAvailabilityFromQueryState<StandardAction>({
            response: standardActionsResponse,
            isFetching: standardActionsFetching,
            isError: standardActionsUnavailable,
        }),
        [standardActionsFetching, standardActionsUnavailable, standardActionsResponse],
    );
    const standardActionsForTarget = React.useMemo(
        () => standardActionsForAvailabilityPolicy({
            standardActionAvailability,
            fallbackActions: standardActionsResponse?.actions,
            failClosed: standardActionAvailabilityPolicy.failClosedStructuredActionsDuringRefresh,
        }),
        [standardActionAvailability, standardActionsResponse?.actions],
    );
    const {
        data: actionCreateSourceRecord,
        isLoading: actionCreateSourceLoading,
        error: actionCreateSourceError,
    } = useQuery({
        queryKey: [
            'actions',
            'standard',
            'create-source',
            actionCreateRouteParams?.sourceObject,
            actionCreateRouteParams?.sourceRecordId,
        ],
        queryFn: () => dataAPI.getRichRecord(
            actionCreateRouteParams?.sourceObject || '',
            actionCreateRouteParams?.sourceRecordId || '',
        ),
        enabled: actionCreateRouteParamReady,
        staleTime: 60 * 1000,
    });
    const {
        metadata: actionCreateSourceMetadata,
        loading: actionCreateSourceMetadataLoading,
        error: actionCreateSourceMetadataError,
    } = useObjectMetadata(actionCreateRouteParams?.sourceObject || '');
    const {
        data: actionCreateSourceAccess,
        isLoading: actionCreateSourceAccessLoading,
    } = useQuery({
        queryKey: [
            'actions',
            'standard',
            'create-source-access',
            actionCreateRouteParams?.sourceObject,
            actionCreateRouteParams?.sourceRecordId,
        ],
        queryFn: async () => {
            const access = normalizeRecordAccess(await dataAPI.getRecordAccess(
                actionCreateRouteParams?.sourceObject || '',
                actionCreateRouteParams?.sourceRecordId || '',
            ));
            // Normalised before the fallback, not instead of it: the fallback covers "no access
            // object", and normalising covers "an access object that says nothing about the lock".
            // Without the first, a partial answer reached `isLocked ?? false` below as not-locked.
            return access ?? LOCKED_ACTION_CREATE_SOURCE_ACCESS;
        },
        enabled: actionCreateRouteParamReady,
        staleTime: 60 * 1000,
    });
    const actionCreateSourceRecordTypeId = typeof actionCreateSourceRecord?.RecordTypeId === 'string'
        ? actionCreateSourceRecord.RecordTypeId
        : undefined;
    const actionCreateSourceLayoutObject = actionCreateRouteParamReady && actionCreateSourceRecord
        ? actionCreateRouteParams?.sourceObject || ''
        : '';
    const {
        layout: actionCreateSourceLayout,
        loading: actionCreateSourceLayoutLoading,
        error: actionCreateSourceLayoutError,
    } = useLayout(
        actionCreateSourceLayoutObject,
        undefined,
        'View',
        actionCreateSourceRecordTypeId,
    );
    const actionCreateRouteVisibleSourceActions = React.useMemo(
        () => resolveVisibleStandardActionsForRecord({
            layout: actionCreateSourceLayout,
            standardActions: standardActionsForTarget,
            objectApiName: actionCreateRouteParams?.sourceObject || '',
            record: actionCreateSourceRecord || null,
        }),
        [
            actionCreateSourceLayout,
            actionCreateSourceRecord,
            actionCreateRouteParams?.sourceObject,
            standardActionsForTarget,
        ],
    );
    const actionCreateSourceRecordState = React.useMemo(
        () => actionCreateSourceRecord && actionCreateSourceMetadata
            ? resolveRecordStatePresentation(
                actionCreateSourceLayout,
                actionCreateSourceRecord,
                actionCreateSourceMetadata,
            )
            : null,
        [actionCreateSourceLayout, actionCreateSourceMetadata, actionCreateSourceRecord],
    );
    const actionCreateRouteSourceActions = React.useMemo(() => {
        const sourceRecordIsLocked = actionCreateSourceAccessLoading
            || !actionCreateSourceAccess
            || (actionCreateSourceAccess.isLocked ?? false);
        return applyStandardActionRecordGuards(actionCreateRouteVisibleSourceActions, {
            isLocked: sourceRecordIsLocked,
            recordStateOverrides: actionCreateSourceRecordState?.actionOverrides,
        }).invokableActions;
    }, [
        actionCreateRouteVisibleSourceActions,
        actionCreateSourceAccess,
        actionCreateSourceAccessLoading,
        actionCreateSourceRecordState?.actionOverrides,
    ]);
    const actionCreateRouteAction = React.useMemo(
        () => actionCreateRouteParams
            ? findStandardAction(actionCreateRouteSourceActions, actionCreateRouteParams.actionName)
            : null,
        [actionCreateRouteParams, actionCreateRouteSourceActions],
    );
    const defaultsError = shouldFetchClone ? cloneDefaultsError : createDefaultsError;
    const hasUnavailableCreateRecordTypeState =
        isCreate &&
        !needsRecordTypePicker &&
        !!objectInfo &&
        !hasVisibleCreateRecordType &&
        !resolvedCreateRecordTypeId;

    const createInitialData = React.useMemo(() => {
        return buildCreateInitialData({
            isCreate,
            useCloneDefaults: shouldFetchClone,
            cloneDefaults,
            createDefaults,
            objectFields: objectInfo?.fields,
            searchParams,
            metadata,
            actionCreateRouteRequested,
            activeSelectedRecordTypeId,
            fieldSurfacePolicyScope,
        });
    }, [actionCreateRouteRequested, activeSelectedRecordTypeId, cloneDefaults, createDefaults, fieldSurfacePolicyScope, isCreate, metadata, objectInfo?.fields, searchParams, shouldFetchClone]);
    const explicitCreateFieldNames = React.useMemo(() => {
        if (!isCreate || shouldFetchClone) {
            return new Set<string>();
        }

        const fieldNames = new Set(Object.keys(getCreateUrlPrefillData(searchParams, metadata, actionCreateRouteRequested)));
        if (activeSelectedRecordTypeId) {
            fieldNames.add('RecordTypeId');
        }
        return fieldNames;
    }, [actionCreateRouteRequested, activeSelectedRecordTypeId, isCreate, metadata, searchParams, shouldFetchClone]);
    const createHintFieldNames = React.useMemo(
        () => buildCreateHintFieldNames({
            isCreate,
            useCloneDefaults: shouldFetchClone,
            createDefaults,
            explicitFieldNames: explicitCreateFieldNames,
        }),
        [createDefaults, explicitCreateFieldNames, isCreate, shouldFetchClone],
    );
    const actionCreateBaseInitialData = React.useMemo(() => buildActionCreateBaseInitialData({
        actionCreateRouteRequested,
        createInitialData,
        resolvedCreateRecordTypeId,
    }), [actionCreateRouteRequested, createInitialData, resolvedCreateRecordTypeId]);

    // A picker selection belongs to one create intent. Do not carry it from a
    // normal create into clone/action create, where it would become an
    // unintended Record Type override for a different operation.
    useEffect(() => {
        setSelectedRecordType(null);
    }, [
        actionCreateRouteParams?.actionName,
        actionCreateRouteParams?.sourceObject,
        actionCreateRouteParams?.sourceRecordId,
        cloneId,
        isCreate,
        objectApiName,
        urlRecordTypeId,
    ]);

    // Fetch Record Data for Edit / Detail using TanStack Query.
    // App-owned object route capabilities may opt out when the app route owns
    // data loading for the current object.
    // 2. Standard REST API for metadata objects and UI-API-unsupported objects.
    const { data: metaRecordData, isLoading: recordMetaLoading, error: recordMetaError, refetch: refetchMeta } =
        useRecord(objectApiName || '', (shouldFetchRecord && skipRecordUI) ? recordId : undefined);

    // Build SOQL filter from URL query params set by RelatedList "View All" links.
    // e.g. /object/child?ParentId=abc... → "ParentId = 'abc...'"
    const listFilterExpr = React.useMemo(() => {
        const parts: string[] = [];
        searchParams.forEach((value, key) => {
            if (!RESERVED_LIST_PARAMS.has(key) && isValidFieldApiName(key)) {
                parts.push(`${key} = '${escapeSoqlValue(value)}'`);
            }
        });
        return parts.join(' AND ');
    }, [searchParams]);

    const createPrefillSearch = React.useMemo(() => {
        return getCreatePrefillSearchFromListParams(searchParams);
    }, [searchParams]);

    // Merge States
    const recordLoading = (skipRecordUI ? recordMetaLoading : uiLoading) || cloneDefaultsLoading;
    const recordErrorObj = skipRecordUI ? recordMetaError : uiError;
    const refetchRecord = skipRecordUI ? refetchMeta : refetchUI;

    // Derive record from UI Data or Standard REST Data
    // selectRecordFromUI handles lookup name resolution via __r relationship fields
    const recordData = React.useMemo(() => {
        if (skipRecordUI) return metaRecordData;
        if (!uiData || !recordId) return null;
        return selectRecordFromUI(uiData, recordId, fieldSurfacePolicyScope);
    }, [fieldSurfacePolicyScope, skipRecordUI, metaRecordData, uiData, recordId]);

    // Extract record-level access (edit/delete) from UI API response
    const recordAccess = React.useMemo(() => {
        if (skipRecordUI || !uiData || !recordId) return undefined;
        // Normalised, not defaulted: a response that states access without a lock must not reach
        // `isLocked ?? false` as silence. undefined still means "no access object here", which this
        // page also reaches by not asking, so that case keeps its own meaning.
        return normalizeRecordAccess(selectRecordAccessFromUI(uiData, recordId));
    }, [skipRecordUI, uiData, recordId]);

    // 3a. Full layout for UI-API-unsupported objects and metadata objects.
    //     Also used for Create and Edit modes to get the correct mode on the layout response.
    //     One layout model: same layout for View/Edit/Create, mode controls rendering context.
    //     recordTypeId ensures correct layout assignment resolution (Profile × RecordType).
    const editRecordTypeId = isEditing && recordData ? (recordData['RecordTypeId'] as string) || undefined : undefined;
    const formRecordTypeId = isCreate
        ? (shouldFetchClone ? cloneRecordTypeState.effectiveRecordTypeId : resolvedCreateRecordTypeId || undefined)
        : editRecordTypeId;
    const createLayoutReady = !isCreate || !shouldFetchClone || !!formRecordTypeId;
    const { layout: describeLayout, loading: describeLayoutLoading, error: describeLayoutError } = useLayout(
        (((shouldFetchRecord && isUnsupportedByUIAPI) || isCreate || isEditing) && createLayoutReady) ? (objectApiName || '') : '',
        undefined,
        isCreate ? 'Create' : (isEditing ? 'Edit' : undefined),
        formRecordTypeId
    );

    // Salesforce returns: layouts[objectApiName][recordTypeId].Full.View.sections
    // NexusCRM backend returns: layouts[objectApiName][layoutId] = { id, mode, sections, relatedLists }
    const layout = React.useMemo((): PageLayout | null => {
        const withNormalizedIds = (l: PageLayout): PageLayout => {
            if (!l.relatedLists?.length) return l;
            return {
                ...l,
                relatedLists: l.relatedLists.map(rl => ({
                    ...rl,
                    id: normalizeRelatedListId(rl.objectApiName, rl.lookupField),
                })),
            };
        };

        // For UI-API-unsupported objects, use the describe/layouts result directly
        if (isUnsupportedByUIAPI) return describeLayout ? withNormalizedIds(describeLayout) : null;

        if (!uiData?.layouts || !objectApiName) return null;

        // Try exact match first, then case-insensitive fallback.
        // The backend normalizes response keys from schema.APIName (PascalCase),
        // but in edge cases (cache miss), keys may be lowercase.
        let objectLayouts = uiData.layouts[objectApiName];
        if (!objectLayouts) {
            const lowerKey = objectApiName.toLowerCase();
            const matchingKey = Object.keys(uiData.layouts).find(k => k.toLowerCase() === lowerKey);
            if (matchingKey) objectLayouts = uiData.layouts[matchingKey];
        }
        if (!objectLayouts) return null;

        // Get first layout entry (keyed by layoutId or recordTypeId)
        const firstLayout = Object.values(objectLayouts)[0] as unknown;
        if (!firstLayout || typeof firstLayout !== 'object') return null;

        const layoutObj = firstLayout as Record<string, unknown>;

        let resolvedLayout: PageLayout | null = null;

        // NexusCRM flat layout: { id, mode, sections, relatedLists }
        if ('sections' in layoutObj && Array.isArray(layoutObj.sections)) {
            resolvedLayout = layoutObj as unknown as PageLayout;
        }

        if (!resolvedLayout) {
            // Salesforce nested layout: { Full: { View: { sections } } }
            const modeKey = mode === 'Detail' ? 'View' : mode;
            const fullLayout = layoutObj.Full as Record<string, unknown> | undefined;

            if (fullLayout && fullLayout[modeKey]) {
                const modeLayout = fullLayout[modeKey] as PageLayout;
                if (modeLayout.sections) resolvedLayout = modeLayout;
            }

            // Fallback: Full itself has sections
            if (!resolvedLayout && fullLayout && 'sections' in fullLayout) {
                resolvedLayout = fullLayout as unknown as PageLayout;
            }
        }

        return resolvedLayout ? withNormalizedIds(resolvedLayout) : null;
    }, [isUnsupportedByUIAPI, describeLayout, uiData, objectApiName, mode]);

    const actionCreateEditableFieldNames = React.useMemo(
        () => isCreate && metadata
            ? getEditableCreateLayoutFieldNames(metadata, describeLayout)
            : new Set<string>(),
        [describeLayout, isCreate, metadata],
    );
    const actionCreateRequiredContextFieldNames = React.useMemo(() => {
        const names = new Set<string>();
        const recordTypeId = actionCreateBaseInitialData?.RecordTypeId;
        if (actionCreateRouteRequested && recordTypeId !== undefined && recordTypeId !== null && recordTypeId !== '') {
            names.add('RecordTypeId');
        }
        return names;
    }, [actionCreateBaseInitialData, actionCreateRouteRequested]);
    const actionCreateContextFieldNames = React.useMemo(() => {
        return getActionCreateContextFieldNames(
            isCreate,
            metadata,
            actionCreateBaseInitialData,
            actionCreateRequiredContextFieldNames,
        );
    }, [actionCreateBaseInitialData, actionCreateRequiredContextFieldNames, isCreate, metadata]);

    const actionCreatePlanResult = React.useMemo(() => {
        if (!actionCreateRouteRequested) {
            return { plan: null, errors: [] };
        }
        const targetDefaultsLoading = createDefaultsEnabled && createDefaultsLoading;
        const readyToPlan = shouldBuildActionCreateRoutePlan({
            actionCreateRouteRequested,
            actionCreateRouteParamReady,
            needsRecordTypePicker,
            standardActionsLoading,
            targetDefaultsLoading,
            targetLayoutLoading: describeLayoutLoading,
            sourceRecordLoading: actionCreateSourceLoading,
            sourceMetadataLoading: actionCreateSourceMetadataLoading,
            sourceAccessLoading: actionCreateSourceAccessLoading,
            sourceLayoutLoading: !!actionCreateSourceRecord && actionCreateSourceLayoutLoading,
        });
        if (!readyToPlan) {
            return { plan: null, errors: [] };
        }
        return buildActionCreateRoutePlan({
            action: actionCreateRouteAction,
            targetObject: objectApiName || '',
            sourceObject: actionCreateRouteParams?.sourceObject || '',
            sourceRecordId: actionCreateRouteParams?.sourceRecordId || '',
            sourceRecord: actionCreateSourceRecord || null,
            createContextValues: actionCreateBaseInitialData || null,
            createContextFieldNames: actionCreateContextFieldNames,
            editableFieldNames: actionCreateEditableFieldNames,
        });
    }, [
        actionCreateBaseInitialData,
        actionCreateContextFieldNames,
        actionCreateEditableFieldNames,
        actionCreateRouteAction,
        actionCreateRouteParamReady,
        actionCreateRouteParams?.sourceObject,
        actionCreateRouteParams?.sourceRecordId,
        actionCreateRouteRequested,
        actionCreateSourceAccessLoading,
        actionCreateSourceLayoutLoading,
        actionCreateSourceLoading,
        actionCreateSourceMetadataLoading,
        actionCreateSourceRecord,
        createDefaultsEnabled,
        createDefaultsLoading,
        describeLayoutLoading,
        needsRecordTypePicker,
        objectApiName,
        standardActionsLoading,
    ]);
    const actionCreatePlan = actionCreatePlanResult.plan;
    useEffect(() => {
        setActionCreateDraftData(null);
        setActionCreateDirtyFieldNames(new Set());
        setActionCreateLastPreviewedInputKey(null);
    }, [
        actionCreatePlan?.action.name,
        actionCreatePlan?.sourceObject,
        actionCreatePlan?.sourceRecordId,
        objectApiName,
    ]);
    const actionCreatePreviewFormData = React.useMemo(
        () => actionCreatePlan
            ? buildActionCreatePreviewFormData(
                metadata,
                actionCreateBaseInitialData,
                actionCreateDraftData,
                actionCreateEditableFieldNames,
                actionCreateDirtyFieldNames,
                createHintFieldNames,
            )
            : {},
        [
            actionCreateDirtyFieldNames,
            actionCreateDraftData,
            actionCreateEditableFieldNames,
            actionCreatePlan,
            actionCreateBaseInitialData,
            createHintFieldNames,
            metadata,
        ],
    );
    const actionCreatePreviewInput = React.useMemo(
        () => actionCreatePlan
            ? buildActionCreateSubmitInput(actionCreatePlan, actionCreatePreviewFormData)
            : {},
        [actionCreatePlan, actionCreatePreviewFormData],
    );
    const actionCreatePreviewInputKey = React.useMemo(
        () => getActionCreateInputKey(actionCreatePreviewInput),
        [actionCreatePreviewInput],
    );
    const actionCreateMissingRequiredInputNames = React.useMemo(
        () => actionCreatePlan
            ? getMissingRequiredActionCreateInputNames(actionCreatePlan, actionCreatePreviewFormData)
            : [],
        [actionCreatePlan, actionCreatePreviewFormData],
    );
    const actionCreatePreviewReady = !!actionCreatePlan && actionCreateMissingRequiredInputNames.length === 0;
    const {
        data: actionCreatePreview,
        isLoading: actionCreatePreviewLoading,
        isFetching: actionCreatePreviewFetching,
        error: actionCreatePreviewError,
    } = useQuery({
        queryKey: [
            'actions',
            'standard',
            'create-preview',
            actionCreatePlan?.action.name,
            actionCreatePlan?.sourceObject,
            actionCreatePlan?.sourceRecordId,
            actionCreatePreviewInputKey,
        ],
        queryFn: () => dataAPI.previewStandardAction(
            actionCreatePlan?.action.previewEndpoint || '',
            actionCreatePreviewInput,
        ),
        enabled: actionCreatePreviewReady && !needsRecordTypePicker,
        staleTime: 0,
    });
    const actionCreatePreviewParentErrors = React.useMemo(
        () => validatePreviewParent(actionCreatePreview, objectApiName || ''),
        [actionCreatePreview, objectApiName],
    );
    useEffect(() => {
        if (!actionCreateRouteRequested) {
            setActionCreateLastPreviewedInputKey(null);
            return;
        }
        if (!actionCreatePreviewReady || actionCreatePreviewError || actionCreatePreviewParentErrors.length > 0) {
            setActionCreateLastPreviewedInputKey(null);
            return;
        }
        if (actionCreatePreview && !actionCreatePreviewFetching) {
            setActionCreateLastPreviewedInputKey(actionCreatePreviewInputKey);
        }
    }, [
        actionCreatePreview,
        actionCreatePreviewError,
        actionCreatePreviewFetching,
        actionCreatePreviewInputKey,
        actionCreatePreviewParentErrors.length,
        actionCreatePreviewReady,
        actionCreateRouteRequested,
    ]);
    const effectiveCreateInitialData = React.useMemo(
        () => actionCreateRouteRequested && actionCreatePreviewParentErrors.length === 0
            ? mergePreviewFieldsIntoInitialData(
                actionCreateBaseInitialData,
                actionCreatePreview,
                fieldSurfacePolicyScope,
            )
            : createInitialData,
        [
            actionCreateBaseInitialData,
            actionCreatePreview,
            actionCreatePreviewParentErrors.length,
            actionCreateRouteRequested,
            createInitialData,
            fieldSurfacePolicyScope,
        ],
    );
    const effectiveCreateHintFieldNames = React.useMemo(
        () => actionCreateRouteRequested
            ? buildEffectiveCreateHintFieldNames(createHintFieldNames, actionCreatePreview)
            : createHintFieldNames,
        [actionCreatePreview, actionCreateRouteRequested, createHintFieldNames],
    );
    const actionCreatePreviewSupersededHintFieldNames = React.useMemo(
        () => actionCreateRouteRequested
            ? buildActionCreatePreviewSupersededHintFieldNames(createHintFieldNames, actionCreatePreview)
            : new Set<string>(),
        [actionCreatePreview, actionCreateRouteRequested, createHintFieldNames],
    );
    const actionCreatePreviewErrorMessage = actionCreatePreviewError instanceof Error
        ? actionCreatePreviewError.message
        : (actionCreatePreviewError ? 'Action preview failed' : '');
    const actionCreatePreviewParentErrorMessage = actionCreatePreviewParentErrors.join(' ');
    const actionCreatePreviewBlockingMessage = actionCreatePreviewParentErrorMessage || actionCreatePreviewErrorMessage;
    const actionCreateMissingRequiredInputMessage = actionCreateMissingRequiredInputNames.length > 0
        ? 'Complete the required fields before saving.'
        : '';
    const actionCreateCurrentInputPreviewed = !actionCreateRouteRequested
        || isActionCreateInputPreviewed(actionCreatePreviewInputKey, actionCreateLastPreviewedInputKey);
    const actionCreateSubmitDisabled = actionCreateRouteRequested && (
        !actionCreatePlan ||
        !actionCreatePreviewReady ||
        !actionCreateCurrentInputPreviewed ||
        actionCreatePreviewFetching ||
        !!actionCreatePreviewBlockingMessage
    );

    const handleActionCreateSuccess = React.useCallback((record: Record<string, unknown>) => {
        queryClient.invalidateQueries({ queryKey: RECORD_KEYS.list(objectApiName || '') });
        const createdId = record[COMMON_FIELDS.ID];
        if (createdId) {
            navigate(ROUTES.OBJECT.DETAIL(objectApiName || '', String(createdId)));
        } else {
            navigate(ROUTES.OBJECT.LIST(objectApiName || ''));
        }
    }, [navigate, objectApiName, queryClient]);

    const handleActionCreateOverride = React.useCallback(async (
        formData: Record<string, unknown>,
        dirtyFieldNames: Set<string>,
    ) => {
        if (!actionCreatePlan?.action.endpoint) {
            throw new Error('Action-backed create route is not ready');
        }
        const actionFormData = buildActionCreatePreviewFormData(
            metadata,
            effectiveCreateInitialData,
            formData,
            actionCreateEditableFieldNames,
            dirtyFieldNames,
            effectiveCreateHintFieldNames,
            actionCreatePreviewSupersededHintFieldNames,
        );
        const actionInput = buildActionCreateSubmitInput(actionCreatePlan, actionFormData);
        const previewValidationInput = buildActionCreatePreviewValidationInput(
            actionCreatePlan,
            actionInput,
            actionCreatePreviewSupersededHintFieldNames,
            dirtyFieldNames,
        );
        const actionInputKey = getActionCreateInputKey(previewValidationInput);
        if (!isActionCreateInputPreviewed(actionInputKey, actionCreateLastPreviewedInputKey)) {
            throw new Error('Action preview is stale. Wait for the preview to update before saving.');
        }
        const outputValues = await dataAPI.executeStandardAction(actionCreatePlan.action.endpoint, [actionInput]);
        const createdId = getCreatedRecordIdFromActionOutput(
            actionCreatePlan.action,
            outputValues,
            objectApiName || '',
        );
        return createdId
            ? { ...outputValues, [COMMON_FIELDS.ID]: createdId }
            : outputValues;
    }, [
        actionCreateEditableFieldNames,
        actionCreateLastPreviewedInputKey,
        actionCreatePlan,
        actionCreatePreviewSupersededHintFieldNames,
        effectiveCreateInitialData,
        effectiveCreateHintFieldNames,
        metadata,
        objectApiName,
    ]);

    const handleActionCreateDraftChange = React.useCallback((
        data: Record<string, unknown>,
        dirtyFieldNames: Set<string>,
    ) => {
        setActionCreateDraftData(data);
        setActionCreateDirtyFieldNames(dirtyFieldNames);
    }, []);

    const renderActionCreateExtra = React.useCallback(() => {
        if (!actionCreateRouteRequested) {
            return null;
        }

        const readOnlyPreviewEntries = Object.entries(actionCreatePreview?.parentPreview?.fields || {})
            .filter(([fieldName]) => !actionCreateEditableFieldNames.has(fieldName));
        const childPreview = actionCreatePreview?.childPreview || [];

        if (readOnlyPreviewEntries.length === 0 && childPreview.length === 0) {
            return null;
        }

        return (
            <div className="space-y-3">
                {readOnlyPreviewEntries.length > 0 && (
                    <div className="rounded-md border border-slate-200 bg-slate-50 p-4">
                        <h3 className="text-sm font-medium text-slate-800">Server values</h3>
                        <dl className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2">
                            {readOnlyPreviewEntries.map(([fieldName, value]) => (
                                <div key={fieldName}>
                                    <dt className="text-xs font-medium uppercase text-slate-500">{fieldName}</dt>
                                    <dd className="mt-1 text-sm text-slate-900">{String(value ?? '')}</dd>
                                </div>
                            ))}
                        </dl>
                    </div>
                )}
                {childPreview.length > 0 && (
                    <div className="rounded-md border border-slate-200 bg-slate-50 p-4">
                        <h3 className="text-sm font-medium text-slate-800">Related records</h3>
                        <ul className="mt-3 space-y-2 text-sm text-slate-700">
                            {childPreview.map(child => (
                                <li key={child.object}>
                                    {child.object}: {child.count}
                                </li>
                            ))}
                        </ul>
                    </div>
                )}
            </div>
        );
    }, [
        actionCreateEditableFieldNames,
        actionCreatePreview?.childPreview,
        actionCreatePreview?.parentPreview?.fields,
        actionCreateRouteRequested,
    ]);

    // Transitional bridge: only non-empty layout placements take over.
    // Greenfield public-core layouts still seed [] today, so empty arrays
    // continue to fall back until payload authoring catches up.
    const activeActionsForThisObject = React.useMemo(() => {
        // Pre-filter by visibilityCondition against the current record.
        // When recordData is not yet loaded (null), the shared resolver returns
        // the full list (default-visible), matching LayoutRenderer behavior for
        // section.visibilityCondition. ActionRendererHost re-checks as a
        // belt-and-suspenders guard for other callers.
        return resolveVisibleStandardActionsForRecord({
            layout,
            standardActions: standardActionsForTarget,
            objectApiName: objectApiName || '',
            record: recordData as Record<string, unknown> | null | undefined,
        });
    }, [layout, objectApiName, standardActionsForTarget, recordData]);

    // Redirect system objects to their dedicated Setup pages
    const systemRedirectTarget = isCreate && objectApiName ? getSystemObjectCreateRedirectTarget(objectApiName) : undefined;
    useEffect(() => {
        if (systemRedirectTarget) {
            navigate(systemRedirectTarget, { replace: true });
        }
    }, [systemRedirectTarget, navigate]);

    // View Mode Determination
    const isDetail = !!recordId && !isCreate;
    const isList = !recordId;

    // Handlers
    const handleCreateSuccess = (record: Record<string, unknown>) => {
        // Invalidate list
        queryClient.invalidateQueries({ queryKey: RECORD_KEYS.list(objectApiName || '') });
        navigate(ROUTES.OBJECT.DETAIL(objectApiName || '', record[COMMON_FIELDS.ID] as string));
    };

    const exitEditMode = React.useCallback(() => {
        navigate(
            buildExitEditUrl(ROUTES.OBJECT.DETAIL(objectApiName || '', recordId || ''), searchParams),
            { replace: true },
        );
    }, [navigate, objectApiName, recordId, searchParams]);

    const handleUpdateSuccess = () => {
        setIsEditing(false);
        // Invalidate record detail
        if (recordId) {
            queryClient.invalidateQueries({ queryKey: RECORD_KEYS.detail(objectApiName || '', recordId) });
            queryClient.invalidateQueries({ queryKey: RECORD_UI_KEYS.detail(objectApiName || '', recordId) });
        }
        queryClient.invalidateQueries({ queryKey: RECORD_KEYS.list(objectApiName || '') });
        exitEditMode();
    };

    // Used by handleCancel + the Back button so that creating a child from a
    // parent record returns the user to that parent on cancel/back rather than
    // dumping them on the child object's list view. See resolveParentBackRoute
    // for the parent-context detection logic.
    const parentBackRoute = React.useMemo(
        () => isCreate ? resolveParentBackRoute(metadata, searchParams) : null,
        [isCreate, metadata, searchParams],
    );

    const handleCancel = () => {
        if (isCreate) {
            // Return to the parent record when the form was reached from a
            // parent-context lookup, otherwise fall back to the child list.
            if (parentBackRoute) {
                navigate(parentBackRoute);
            } else {
                navigate(ROUTES.OBJECT.LIST(objectApiName || ''));
            }
        } else {
            setIsEditing(false);
            exitEditMode();
        }
    };

    if (metaLoading) return (
        <div className="max-w-7xl mx-auto p-6">
            <MetadataAwareSkeleton fieldCount={5} layout="list" message="Loading data, this may take a moment..." />
        </div>
    );
    if (metaError || !metadata) {
        if (metaPermissionCopy) {
            return (
                <AccessDeniedEmptyState
                    title={metaPermissionCopy.title}
                    description={metaPermissionCopy.description}
                    onGoBack={() => navigate(-1)}
                />
            );
        }
        if (isForbiddenError(metaError)) {
            return <AccessDeniedEmptyState onGoBack={() => navigate(-1)} />;
        }
        return (
            <EmptyState
                variant="error"
                title="Error Loading Object"
                description={metaError?.message || 'Object not found'}
                action={{ label: 'Retry', onClick: () => window.location.reload() }}
            />
        );
    }



    if (systemRedirectTarget) {
        return <div className="p-8 text-center text-slate-500">Redirecting to setup page...</div>;
    }

    if ((isCreate && !canCreateObject) || (isEditing && !canEditObject)) {
        return (
            <AccessDeniedEmptyState
                title="Insufficient Access"
                description={`You don't have permission to ${isCreate ? 'create' : 'edit'} this ${metadata.label.toLowerCase()}.`}
                onGoBack={handleCancel}
            />
        );
    }
    if (createDefaultsEnabled && !shouldFetchClone && objectInfoLoading) {
        return <div className="p-8 text-center text-slate-500">Loading create options...</div>;
    }
    if (createDefaultsEnabled && !shouldFetchClone && objectInfoError) {
        const objectInfoPermissionCopy = getObjectPagePermissionErrorCopy(objectInfoError);
        if (objectInfoPermissionCopy) {
            return (
                <AccessDeniedEmptyState
                    title={objectInfoPermissionCopy.title}
                    description={objectInfoPermissionCopy.description}
                    onGoBack={handleCancel}
                />
            );
        }
        if (isForbiddenError(objectInfoError)) {
            return <AccessDeniedEmptyState onGoBack={handleCancel} />;
        }
        return (
            <EmptyState
                variant="error"
                title="Error Loading Create Options"
                description={objectInfoError.message || 'Could not load record type options'}
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }

    // Block rendering during Edit/Create until layout is loaded to prevent flat-form flash.
    if ((isCreate || isEditing) && describeLayoutLoading) return <div className="p-8 text-center text-slate-500">Loading layout...</div>;
    // Show error if layout fetch failed during Create/Edit (prevents silent flat-form degradation)
    if ((isCreate || isEditing) && describeLayoutError) {
        const layoutPermissionCopy = getObjectPagePermissionErrorCopy(describeLayoutError);
        if (layoutPermissionCopy) {
            return (
                <AccessDeniedEmptyState
                    title={layoutPermissionCopy.title}
                    description={layoutPermissionCopy.description}
                    onGoBack={handleCancel}
                />
            );
        }
        return (
            <EmptyState
                variant="error"
                title="Error Loading Layout"
                description={describeLayoutError.message || 'Could not load page layout'}
                action={{ label: 'Go Back', onClick: () => navigate(-1) }}
            />
        );
    }
    if ((createDefaultsEnabled && !needsRecordTypePicker && !shouldFetchClone && createDefaultsLoading)
        || (shouldFetchClone && cloneDefaultsLoading)) {
        return <div className="p-8 text-center text-slate-500">Loading defaults...</div>;
    }
    if (hasUnavailableCreateRecordTypeState) {
        return (
            <EmptyState
                variant="noAccess"
                title="No Available Record Type"
                description={`You don't have an available record type to ${shouldFetchClone ? 'clone' : 'create'} this ${metadata.label.toLowerCase()}.`}
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }
    if (defaultsError) {
        if (isForbiddenError(defaultsError)) {
            return (
                <EmptyState
                    variant="noAccess"
                    title="No Available Record Type"
                    description={`You don't have an available record type to ${shouldFetchClone ? 'clone' : 'create'} this ${metadata.label.toLowerCase()}.`}
                    action={{ label: 'Go Back', onClick: handleCancel }}
                />
            );
        }
        return (
            <EmptyState
                variant="error"
                title="Error Loading Defaults"
                description={defaultsError.message || 'Could not load record defaults'}
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }
    if (actionCreateRouteRequested && standardActionsLoading) {
        return <div className="p-8 text-center text-slate-500">Loading action...</div>;
    }
    if (actionCreateRouteRequested && standardActionsError) {
        const message = standardActionsError instanceof Error ? standardActionsError.message : 'Could not load action metadata';
        return (
            <EmptyState
                variant="error"
                title="Error Loading Action"
                description={message}
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }
    if (actionCreateRouteRequested && !actionCreateRouteParamReady) {
        return (
            <EmptyState
                variant="error"
                title="Incomplete Action Route"
                description="The create action route is missing actionName, sourceObject, or sourceRecordId."
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }
    if (actionCreateRouteRequested && actionCreateSourceLoading) {
        return <div className="p-8 text-center text-slate-500">Loading source record...</div>;
    }
    if (actionCreateRouteRequested && actionCreateSourceError) {
        const message = actionCreateSourceError instanceof Error ? actionCreateSourceError.message : 'Could not load source record';
        return (
            <EmptyState
                variant="error"
                title="Error Loading Source Record"
                description={message}
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }
    if (actionCreateRouteRequested && actionCreateSourceMetadataLoading) {
        return <div className="p-8 text-center text-slate-500">Loading source actions...</div>;
    }
    if (actionCreateRouteRequested && actionCreateSourceMetadataError) {
        const message = actionCreateSourceMetadataError instanceof Error
            ? actionCreateSourceMetadataError.message
            : 'Could not load source action metadata';
        return (
            <EmptyState
                variant="error"
                title="Error Loading Source Actions"
                description={message}
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }
    if (actionCreateRouteRequested && actionCreateSourceAccessLoading) {
        return <div className="p-8 text-center text-slate-500">Loading source actions...</div>;
    }
    if (actionCreateRouteRequested && actionCreateSourceLayoutLoading) {
        return <div className="p-8 text-center text-slate-500">Loading source actions...</div>;
    }
    if (actionCreateRouteRequested && actionCreateSourceLayoutError) {
        const message = actionCreateSourceLayoutError instanceof Error
            ? actionCreateSourceLayoutError.message
            : 'Could not load source action placements';
        return (
            <EmptyState
                variant="error"
                title="Error Loading Source Actions"
                description={message}
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }
    if (actionCreateRouteRequested && actionCreatePlanResult.errors.length > 0) {
        return (
            <EmptyState
                variant="error"
                title="Action Create Route Not Available"
                description={actionCreatePlanResult.errors.join(' ')}
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }
    if (actionCreateRouteRequested
        && actionCreatePreviewParentErrorMessage
        && actionCreateDraftData === null
        && actionCreatePreviewReady) {
        return (
            <EmptyState
                variant="error"
                title="Action Preview Mismatch"
                description={actionCreatePreviewParentErrorMessage}
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }
    if (actionCreateRouteRequested
        && actionCreatePreviewErrorMessage
        && actionCreateDraftData === null
        && actionCreatePreviewReady) {
        return (
            <EmptyState
                variant="error"
                title="Action Preview Failed"
                description={actionCreatePreviewErrorMessage}
                action={{ label: 'Go Back', onClick: handleCancel }}
            />
        );
    }
    if (actionCreateRouteRequested
        && actionCreatePreviewLoading
        && actionCreateDraftData === null
        && actionCreatePreviewReady) {
        return <div className="p-8 text-center text-slate-500">Loading preview...</div>;
    }
    if (recordLoading && isEditing) return <div className="p-8 text-center text-slate-500">Loading record...</div>;
    if (recordLoading && isDetail) return (
        <div className="max-w-7xl mx-auto p-6">
            <MetadataAwareSkeleton fieldCount={5} layout="detail" />
        </div>
    );
    // Show error in both Edit and Detail modes if record fetch failed
    if (recordErrorObj) {
        if (isForbiddenError(recordErrorObj)) {
            return <AccessDeniedEmptyState onGoBack={() => navigate(-1)} />;
        }
        return (
            <EmptyState
                variant="error"
                title="Error Loading Record"
                description={recordErrorObj.message || 'Could not load record data'}
                action={{ label: 'Retry', onClick: () => refetchRecord() }}
            />
        );
    }

    const renderMetadataRecordDetail = () => (
        <MetadataRecordDetail
            objectMetadata={metadata}
            recordId={recordId || ''}
            layout={layout}
            record={recordData}
            recordAccess={recordAccess}
            recordSource="external"
            onRefresh={refetchRecord}
            onBack={() => navigate(ROUTES.OBJECT.LIST(objectApiName || ''))}
            standardActions={activeActionsForThisObject}
            standardActionAvailability={standardActionAvailability}
            fieldSurfacePolicyScope={fieldSurfacePolicyScope}
        />
    );

    return (
        <div className="max-w-7xl mx-auto p-6">
            {/* List View */}
            {isList && (
                <MetadataRecordList
                    key={objectApiName}
                    objectMetadata={metadata}
                    fields={metadata.listFields}
                    filterExpr={listFilterExpr || undefined}
                    fieldSurfacePolicyScope={fieldSurfacePolicyScope}
                    onCreateNew={() => {
                        if (objectRouteCapability?.onCreateNew) {
                            objectRouteCapability.onCreateNew(buildObjectRouteCapabilityContext(recordData));
                            return;
                        }
                        navigate(`${ROUTES.OBJECT.NEW(objectApiName || '')}${createPrefillSearch}`);
                    }}
                />
            )}

            {/* Record Type Picker (shown before create form when multiple types exist) */}
            {needsRecordTypePicker && recordTypeInfos && (
                <RecordTypePicker
                    isOpen={true}
                    objectLabel={metadata.label}
                    recordTypes={recordTypeInfos}
                    defaultRecordTypeId={objectInfo?.defaultRecordTypeId}
                    onSelect={(rtId) => setSelectedRecordType({ objectApiName: objectApiName || '', recordTypeId: rtId })}
                    onCancel={handleCancel}
                />
            )}

            {/* Create / Edit Form */}
            {((isCreate && !needsRecordTypePicker) || isEditing) && (
                <div className="bg-white rounded-xl shadow-lg border border-slate-200 overflow-hidden">
                    <div className="px-6 pt-4 pb-0 border-b border-slate-100">
                        <Button variant="ghost" size="sm" onClick={handleCancel} icon={<ArrowLeft className="w-4 h-4" />}>
                            Back
                        </Button>
                        <div className="relative text-center py-4">
                            <h1 className="text-2xl font-semibold text-slate-900">
                                {isCreate ? `New ${metadata.label}` : `Edit ${metadata.label}`}
                            </h1>
                            <span className="absolute right-0 top-1/2 -translate-y-1/2 text-sm text-red-500">
                                * = Required Information
                            </span>
                        </div>
                    </div>
                    <div className="p-6">
                        {actionCreateMissingRequiredInputMessage && (
                            <div className="mb-6 rounded-md border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800" role="status">
                                {actionCreateMissingRequiredInputMessage}
                            </div>
                        )}
                        {actionCreatePreviewFetching && actionCreateDraftData !== null && actionCreatePreviewReady && (
                            <div className="mb-6 rounded-md border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700" role="status">
                                Updating preview...
                            </div>
                        )}
                        {actionCreatePreviewBlockingMessage && (
                            <div className="mb-6 rounded-md border border-red-200 bg-red-50 p-4 text-sm text-red-700" role="alert">
                                {actionCreatePreviewBlockingMessage}
                            </div>
                        )}
                        <MetadataRecordForm
                            objectMetadata={metadata}
                            recordId={isEditing && recordId ? recordId : undefined}
                            // Pass the data fetched from hook
                            initialData={isCreate ? effectiveCreateInitialData : (recordData as Record<string, unknown>)}
                            onSuccess={isCreate
                                ? (actionCreateRouteRequested ? handleActionCreateSuccess : handleCreateSuccess)
                                : handleUpdateSuccess}
                            onSaveAndNew={isCreate && actionCreateRouteRequested ? undefined : () => {
                                queryClient.invalidateQueries({ queryKey: RECORD_KEYS.list(objectApiName || '') });
                                if (isEditing && recordId) {
                                    queryClient.invalidateQueries({ queryKey: RECORD_KEYS.detail(objectApiName || '', recordId) });
                                    queryClient.invalidateQueries({ queryKey: RECORD_UI_KEYS.detail(objectApiName || '', recordId) });
                                }
                                navigate(ROUTES.OBJECT.NEW(objectApiName || ''));
                            }}
                            onCancel={handleCancel}
                            layout={(isCreate || isEditing) ? describeLayout : (layout || describeLayout || undefined)}
                            onCreateOverride={isCreate && actionCreateRouteRequested ? handleActionCreateOverride : undefined}
                            submitDisabled={actionCreateSubmitDisabled}
                            onDraftChange={isCreate && actionCreateRouteRequested ? handleActionCreateDraftChange : undefined}
                            createHintFieldNames={effectiveCreateHintFieldNames}
                            renderExtra={actionCreateRouteRequested ? renderActionCreateExtra : undefined}
                            fieldSurfacePolicyScope={fieldSurfacePolicyScope}
                        />
                    </div>
                </div>
            )}

            {/* Detail View */}
            {isDetail && !isEditing && (
                objectRouteCapability?.renderDetail
                    ? objectRouteCapability.renderDetail(buildObjectRouteCapabilityContext(recordData))
                    : renderMetadataRecordDetail()
            )}
            {objectRouteCapability?.renderCreateModal?.({
                isOpen: activeCreateModalObject === normalizedObjectApiName,
                onClose: () => setActiveCreateModalObject(null),
            })}
        </div>
    );
};
