package sfcrmappservice

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/url"
	"regexp"
	"sort"
	"strconv"
	"strings"

	crmwire "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/wire"
	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	flowsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowv1"
)

const standardActionMaxInputs = 200

type RuntimePreviewCatalogActionFunc func(
	context.Context,
	string,
	flowsemantic.CatalogActionPreviewRequest,
) (flowsemantic.CatalogActionPreviewResponse, error)

type RuntimeExecuteCatalogActionFunc func(
	context.Context,
	string,
	flowsemantic.CatalogActionRequest,
) (flowsemantic.ResponseV2, error)

type RuntimeStandardActionsClient struct {
	execute              RuntimeExecuteCatalogActionFunc
	preview              RuntimePreviewCatalogActionFunc
	appMetadata          RuntimeGetAppMetadataViewV3Func
	readEffective        RuntimeReadEffectivePermissionsFunc
	reconcileAppMetadata RuntimeReconcileAppMetadataV3ReceiptsFunc
}

type standardActionDefinition struct {
	Name                     string                      `json:"name"`
	LegacyNames              []string                    `json:"legacyNames"`
	Label                    string                      `json:"label"`
	Description              string                      `json:"description"`
	Category                 string                      `json:"category"`
	FlowDeveloperName        string                      `json:"flowDeveloperName"`
	PreviewFlowDeveloperName string                      `json:"previewFlowDeveloperName"`
	CreatePreviewSupported   bool                        `json:"createPreviewSupported"`
	IsAtomic                 bool                        `json:"isAtomic"`
	RequiredPermissions      []string                    `json:"requiredPermissions"`
	TargetObjects            []string                    `json:"targetObjects"`
	HeaderFallbackEligible   *bool                       `json:"headerFallbackEligible"`
	RendererID               string                      `json:"rendererId"`
	VisibilityCondition      string                      `json:"visibilityCondition"`
	Config                   json.RawMessage             `json:"config"`
	SortOrder                int                         `json:"sortOrder"`
	Inputs                   []standardActionSchemaField `json:"inputs"`
	Outputs                  []standardActionSchemaField `json:"outputs"`
	InputGuards              []standardActionInputGuard  `json:"inputGuards"`
	component                catalogsemantic.AppMetadataComponent
}

type standardActionSchemaField struct {
	APIName                   string `json:"apiName"`
	DataType                  string `json:"dataType"`
	IsCollection              bool   `json:"isCollection"`
	IsRequired                bool   `json:"isRequired"`
	ReferenceTo               string `json:"referenceTo"`
	Label                     string `json:"label"`
	UISection                 string `json:"uiSection"`
	UIMode                    string `json:"uiMode"`
	TargetObject              string `json:"targetObject"`
	TargetFieldAPIName        string `json:"targetFieldAPIName"`
	RequiredPicklistTag       string `json:"requiredPicklistTag"`
	MappingSourceObject       string `json:"mappingSourceObject"`
	MappingActionType         string `json:"mappingActionType"`
	PrefillSourceFieldAPIName string `json:"prefillSourceFieldAPIName"`
	TargetRequired            bool   `json:"targetRequired"`
	TargetDefaulted           bool   `json:"targetDefaulted"`
	Defaulted                 bool   `json:"defaulted"`
	DefaultValue              any    `json:"defaultValue"`
}

type standardActionInputGuard struct {
	GatedInput         string `json:"gatedInput"`
	RequiredPermission string `json:"requiredPermission"`
	RequiredObject     string `json:"requiredObject"`
	RequiredOperation  string `json:"requiredOperation"`
	Condition          string `json:"condition"`
}

type standardActionWireField struct {
	Name                      string `json:"name"`
	Type                      string `json:"type"`
	IsCollection              bool   `json:"isCollection,omitempty"`
	Required                  bool   `json:"required,omitempty"`
	ReferenceTo               string `json:"referenceTo,omitempty"`
	Label                     string `json:"label,omitempty"`
	UISection                 string `json:"uiSection,omitempty"`
	UIMode                    string `json:"uiMode,omitempty"`
	TargetObject              string `json:"targetObject,omitempty"`
	TargetFieldAPIName        string `json:"targetFieldAPIName,omitempty"`
	RequiredPicklistTag       string `json:"requiredPicklistTag,omitempty"`
	MappingSourceObject       string `json:"mappingSourceObject,omitempty"`
	MappingActionType         string `json:"mappingActionType,omitempty"`
	PrefillSourceFieldAPIName string `json:"prefillSourceFieldAPIName,omitempty"`
	TargetRequired            bool   `json:"targetRequired,omitempty"`
	TargetDefaulted           bool   `json:"targetDefaulted,omitempty"`
	Defaulted                 bool   `json:"defaulted,omitempty"`
	DefaultValue              any    `json:"defaultValue,omitempty"`
}

type standardActionWireGuard struct {
	GatedInput          string `json:"gatedInput"`
	RequiredPermission  string `json:"requiredPermission,omitempty"`
	RequiredObject      string `json:"requiredObject,omitempty"`
	RequiredOperation   string `json:"requiredOperation,omitempty"`
	Condition           string `json:"condition,omitempty"`
	CallerHasPermission bool   `json:"callerHasPermission"`
}

type standardActionWireDescriptor struct {
	Name                     string                    `json:"name"`
	LegacyNames              []string                  `json:"legacyNames,omitempty"`
	Label                    string                    `json:"label"`
	Description              string                    `json:"description,omitempty"`
	Category                 string                    `json:"category,omitempty"`
	FlowDeveloperName        string                    `json:"flowDeveloperName"`
	IsAtomic                 bool                      `json:"isAtomic"`
	RequiredPermissions      []string                  `json:"requiredPermissions"`
	TargetObjects            []string                  `json:"targetObjects"`
	HeaderFallbackEligible   *bool                     `json:"headerFallbackEligible,omitempty"`
	RendererID               string                    `json:"rendererId,omitempty"`
	InputGuards              []standardActionWireGuard `json:"inputGuards"`
	Inputs                   []standardActionWireField `json:"inputs"`
	Outputs                  []standardActionWireField `json:"outputs"`
	Endpoint                 string                    `json:"endpoint"`
	PreviewEndpoint          string                    `json:"previewEndpoint,omitempty"`
	VisibilityCondition      string                    `json:"visibilityCondition,omitempty"`
	PreviewFlowDeveloperName string                    `json:"previewFlowDeveloperName,omitempty"`
	Config                   json.RawMessage           `json:"config,omitempty"`
}

type standardActionsRouteKind uint8

const (
	standardActionsRouteList standardActionsRouteKind = iota + 1
	standardActionsRouteExecute
	standardActionsRouteCreatePreview
	standardActionsRouteOutputPreview
)

type matchedStandardActionsRoute struct {
	kind standardActionsRouteKind
	name string
	err  error
}

func NewRuntimeStandardActionsClient(
	execute RuntimeExecuteCatalogActionFunc,
	preview RuntimePreviewCatalogActionFunc,
	appMetadata RuntimeGetAppMetadataViewV3Func,
	readEffective RuntimeReadEffectivePermissionsFunc,
) (*RuntimeStandardActionsClient, error) {
	if execute == nil || preview == nil || appMetadata == nil || readEffective == nil {
		return nil, errors.New("sfcrm AppService: standard actions require execute, preview, AppMetadata and effective permissions")
	}
	return &RuntimeStandardActionsClient{
		execute: execute, preview: preview,
		appMetadata: appMetadata, readEffective: readEffective,
	}, nil
}

func matchStandardActionsRoute(escapedPath string) matchedStandardActionsRoute {
	parts := strings.Split(strings.Trim(escapedPath, "/"), "/")
	if len(parts) < 5 || parts[0] != "services" || parts[1] != "data" ||
		!supportedFlowActionVersion(parts[2]) || parts[3] != "actions" || parts[4] != "standard" {
		return matchedStandardActionsRoute{}
	}
	if len(parts) == 5 {
		return matchedStandardActionsRoute{kind: standardActionsRouteList}
	}
	if len(parts) != 6 && len(parts) != 7 {
		return matchedStandardActionsRoute{}
	}
	name, err := url.PathUnescape(parts[5])
	// The same predicate the discovery projection publishes by, so the executable set and the
	// advertised set cannot drift: a name no projection would publish is not executable here, and a
	// name this accepts always has an endpoint. The previous checks were a third spelling of the
	// rule and a looser one — they admitted names with spaces, which no projection publishes.
	if err != nil || !standardActionIDAddressable(name) {
		return matchedStandardActionsRoute{
			kind: standardActionsRouteExecute, name: name,
			err: errors.New("invalid standard action name"),
		}
	}
	if len(parts) == 6 {
		return matchedStandardActionsRoute{kind: standardActionsRouteExecute, name: name}
	}
	switch parts[6] {
	case "preview":
		return matchedStandardActionsRoute{kind: standardActionsRouteCreatePreview, name: name}
	case "output-preview":
		return matchedStandardActionsRoute{kind: standardActionsRouteOutputPreview, name: name}
	default:
		return matchedStandardActionsRoute{}
	}
}

func supportedFlowActionVersion(version string) bool {
	_, ok := recordViewVersions[version]
	return ok
}

func (service *Service) serveStandardActions(writer http.ResponseWriter, request *http.Request) bool {
	route := matchStandardActionsRoute(request.URL.EscapedPath())
	if route.kind == 0 {
		return false
	}
	allowed := http.MethodPost
	if route.kind == standardActionsRouteList {
		allowed = http.MethodGet
	}
	if request.Method != allowed {
		writer.Header().Set("Allow", allowed)
		writeCRMError(writer, http.StatusMethodNotAllowed, crmwire.SFCodeInvalidRequest, "Method not allowed")
		return true
	}
	service.serveMatchedStandardActions(writer, request, route)
	return true
}

func (service *Service) serveMatchedStandardActions(
	writer http.ResponseWriter,
	request *http.Request,
	route matchedStandardActionsRoute,
) {
	service.requests.Add(1)
	if service.draining.Load() {
		service.drainRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusServiceUnavailable, crmwire.SFCodeUnknownException, "Service is draining")
		return
	}
	if route.err != nil {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, route.err.Error())
		return
	}
	bearer, ok := endUserBearer(request.Header.Values("Authorization"))
	if !ok {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusUnauthorized, crmwire.SFCodeInvalidSessionID, "Session expired or invalid")
		return
	}
	service.inFlight.Add(1)
	defer service.inFlight.Add(-1)
	ctx, cancel := context.WithTimeout(request.Context(), service.requestTimeout)
	defer cancel()
	var (
		view        catalogsemantic.AppMetadataView
		permissions permissionsemantic.Response
		definitions map[string]standardActionDefinition
		err         error
	)
	if route.kind == standardActionsRouteList {
		// Discovery still projects caller-visible Salesforce wire metadata
		// locally and therefore needs its current permission view.
		view, permissions, definitions, err = service.standardActions.authorizedDefinitions(ctx, bearer)
	} else {
		// Runtime owns Execute and Preview authority. Pulling the complete
		// effective permission surface here duplicated Runtime's checks,
		// including thousands of unrelated field grants. L1 resolves only the
		// immutable dialect descriptor needed to spell and project the request.
		view, definitions, err = service.standardActions.definitions(ctx)
	}
	if err != nil {
		service.failures.Add(1)
		writeRuntimeFailure(writer, err)
		return
	}
	if route.kind == standardActionsRouteList {
		target := strings.TrimSpace(request.URL.Query().Get("targetObject"))
		service.successes.Add(1)
		writeJSON(writer, http.StatusOK, map[string]any{
			"actions": standardActionList(definitions, permissions, target, partsVersion(request.URL.Path)),
		})
		return
	}
	definition, found := standardActionDefinitionByName(definitions, route.name)
	if !found || !definition.IsAtomic ||
		(route.kind == standardActionsRouteList &&
			!standardActionRequiredPermissionsGranted(definition, permissions)) {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusNotFound, crmwire.SFCodeNotFound, "standard action \""+route.name+"\" not found")
		return
	}
	switch route.kind {
	case standardActionsRouteExecute:
		service.executeStandardAction(writer, request, ctx, bearer, view, definition)
	case standardActionsRouteCreatePreview, standardActionsRouteOutputPreview:
		service.previewStandardAction(writer, request, ctx, bearer, view, definition, route.kind)
	default:
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, "invalid standard action route")
	}
}

func standardActionDefinitionByName(
	definitions map[string]standardActionDefinition,
	requestedName string,
) (standardActionDefinition, bool) {
	for name, definition := range definitions {
		if strings.EqualFold(name, requestedName) {
			return definition, true
		}
	}
	return standardActionDefinition{}, false
}

func (client *RuntimeStandardActionsClient) authorizedDefinitions(
	ctx context.Context,
	bearer string,
) (catalogsemantic.AppMetadataView, permissionsemantic.Response, map[string]standardActionDefinition, error) {
	if client == nil || client.appMetadata == nil || client.readEffective == nil {
		return catalogsemantic.AppMetadataView{}, permissionsemantic.Response{}, nil, unavailableFlowActions()
	}
	view, definitions, err := client.definitions(ctx)
	if err != nil {
		return catalogsemantic.AppMetadataView{}, permissionsemantic.Response{}, nil, err
	}
	revision := view.Receipts.CatalogRevision
	permissions, err := readEffectivePermissionSelection(
		ctx, client.readEffective, bearer, "", revision,
		[]permissionsemantic.Section{
			permissionsemantic.SectionSystemPermissions,
			permissionsemantic.SectionCustomPermissions,
		},
		nil,
	)
	if err != nil {
		return catalogsemantic.AppMetadataView{}, permissionsemantic.Response{}, nil, err
	}
	guardObjects, err := standardActionGuardObjects(definitions)
	if err != nil {
		return catalogsemantic.AppMetadataView{}, permissionsemantic.Response{}, nil, unavailableFlowActions()
	}
	if len(guardObjects) != 0 {
		objectPermissions, readErr := readEffectivePermissionSelection(
			ctx, client.readEffective, bearer, "", revision,
			[]permissionsemantic.Section{permissionsemantic.SectionObjectPermissions},
			guardObjects,
		)
		if readErr != nil {
			return catalogsemantic.AppMetadataView{}, permissionsemantic.Response{}, nil, readErr
		}
		if err := permissions.appendAggregate(objectPermissions); err != nil {
			return catalogsemantic.AppMetadataView{}, permissionsemantic.Response{}, nil, err
		}
	}
	if validateFlowActionAuthority(
		ctx, client.reconcileAppMetadata, view.Receipts, permissions.Receipts,
	) != nil {
		return catalogsemantic.AppMetadataView{}, permissionsemantic.Response{}, nil, unavailableFlowActions()
	}
	return view, permissions.response(), definitions, nil
}

func standardActionGuardObjects(
	definitions map[string]standardActionDefinition,
) ([]string, error) {
	resources := make(map[string]string)
	for _, definition := range definitions {
		for _, guard := range definition.InputGuards {
			if guard.RequiredObject == "" {
				continue
			}
			if !commonsemantic.ValidObjectIdentifier(guard.RequiredObject) {
				return nil, unavailableFlowActions()
			}
			key := strings.ToLower(guard.RequiredObject)
			if existing, found := resources[key]; found && existing != guard.RequiredObject {
				return nil, unavailableFlowActions()
			}
			resources[key] = guard.RequiredObject
		}
	}
	result := make([]string, 0, len(resources))
	for _, resource := range resources {
		result = append(result, resource)
	}
	sort.Strings(result)
	return result, nil
}

func (client *RuntimeStandardActionsClient) definitions(
	ctx context.Context,
) (catalogsemantic.AppMetadataView, map[string]standardActionDefinition, error) {
	if client == nil || client.appMetadata == nil {
		return catalogsemantic.AppMetadataView{}, nil, unavailableFlowActions()
	}
	completeView, err := client.appMetadata(ctx, catalogsemantic.GetRequest{})
	if err != nil {
		return catalogsemantic.AppMetadataView{}, nil, err
	}
	if err := catalogsemantic.ValidateAppMetadataViewV3(completeView); err != nil ||
		completeView.AppID != AppID {
		return catalogsemantic.AppMetadataView{}, nil, unavailableFlowActions()
	}
	view, effectiveAction, err := standardActionViewFromV3(completeView)
	if err != nil {
		return catalogsemantic.AppMetadataView{}, nil, unavailableFlowActions()
	}
	definitions, err := standardActionDefinitionsFromViewProfile(
		view, !effectiveAction,
	)
	if err != nil {
		return catalogsemantic.AppMetadataView{}, nil, unavailableFlowActions()
	}
	return view, definitions, nil
}

func standardActionDefinitionsFromView(view catalogsemantic.AppMetadataView) (map[string]standardActionDefinition, error) {
	return standardActionDefinitionsFromViewProfile(view, true)
}

func standardActionViewFromV3(
	view catalogsemantic.AppMetadataViewV3,
) (catalogsemantic.AppMetadataView, bool, error) {
	normalized := catalogsemantic.AppMetadataView{
		OrgID: view.OrgID, AppInstallationID: view.AppInstallationID,
		AppID: view.AppID, AppReleaseDigest: view.AppReleaseDigest,
		Lineage: view.Lineage, Components: append(
			[]catalogsemantic.AppMetadataComponent(nil), view.AppComponents...,
		),
		Receipts: view.Receipts,
	}
	var effective *catalogsemantic.EffectiveMetadataComponent
	for index := range view.EffectiveComponents {
		candidate := &view.EffectiveComponents[index]
		if candidate.ComponentKind !=
			catalogsemantic.EffectiveStandardActionComponentKind {
			continue
		}
		if effective != nil || candidate.ComponentFormatVersion != 1 {
			return catalogsemantic.AppMetadataView{}, false, errors.New(
				"ambiguous effective standard action component",
			)
		}
		effective = candidate
	}
	if effective == nil {
		return normalized, false, nil
	}
	found := 0
	for index := range normalized.Components {
		component := &normalized.Components[index]
		if component.ComponentKind != invocableActionKind ||
			component.BusinessKey != effective.BusinessKey {
			continue
		}
		component.CanonicalBehavior = append(
			[]byte(nil), effective.CanonicalBehavior...,
		)
		component.ContentDigest = effective.ContentDigest
		found++
	}
	if found != 1 {
		return catalogsemantic.AppMetadataView{}, false, errors.New(
			"effective standard action component has no unique AppRelease anchor",
		)
	}
	return normalized, true, nil
}

func standardActionDefinitionsFromViewProfile(
	view catalogsemantic.AppMetadataView,
	requireAppFlowAuthority bool,
) (map[string]standardActionDefinition, error) {
	flows := make(map[string]catalogsemantic.AppMetadataComponent)
	var actionComponent *catalogsemantic.AppMetadataComponent
	for index := range view.Components {
		component := view.Components[index]
		switch component.ComponentKind {
		case flowComponentKind:
			if _, duplicate := flows[component.BusinessKey]; duplicate {
				return nil, errors.New("duplicate Flow component")
			}
			flows[component.BusinessKey] = component
		case invocableActionKind:
			if actionComponent != nil {
				return nil, errors.New("ambiguous standard action component")
			}
			actionComponent = &component
		}
	}
	if actionComponent == nil || actionComponent.ComponentFormatVersion != 1 ||
		actionComponent.ComponentID == "" || len(actionComponent.CanonicalBehavior) == 0 {
		return nil, errors.New("standard action component is unavailable")
	}
	var definitions []standardActionDefinition
	decoder := json.NewDecoder(bytes.NewReader(actionComponent.CanonicalBehavior))
	decoder.UseNumber()
	if err := decoder.Decode(&definitions); err != nil || definitions == nil {
		return nil, errors.New("invalid standard action component")
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		return nil, errors.New("standard action component contains trailing JSON")
	}
	result := make(map[string]standardActionDefinition, len(definitions))
	seenNames := make(map[string]struct{}, len(definitions))
	for index := range definitions {
		definition := definitions[index]
		if err := validateStandardActionDefinition(definition); err != nil {
			return nil, err
		}
		nameKey := strings.ToLower(definition.Name)
		if _, duplicate := seenNames[nameKey]; duplicate {
			return nil, errors.New("duplicate standard action")
		}
		seenNames[nameKey] = struct{}{}
		if requireAppFlowAuthority {
			flow, found := flows[definition.FlowDeveloperName]
			if !found || flow.DefinitionVersion == "" {
				return nil, errors.New("standard action Flow is unavailable")
			}
			flowDefinition, err := decodeFlowActionDefinition(flow)
			if err != nil || flowDefinition.status != "Active" ||
				flowDefinition.startTrigger != "" ||
				flowDefinition.runAtomically != definition.IsAtomic {
				return nil, errors.New("standard action Flow authority is inconsistent")
			}
		}
		definition.component = *actionComponent
		if requireAppFlowAuthority && definition.PreviewFlowDeveloperName != "" {
			previewFlow, found := flows[definition.PreviewFlowDeveloperName]
			if !found || previewFlow.DefinitionVersion == "" {
				return nil, errors.New("standard action preview Flow is unavailable")
			}
			previewDefinition, err := decodeFlowActionDefinition(previewFlow)
			if err != nil || previewDefinition.status != "Active" ||
				previewDefinition.startTrigger != "" {
				return nil, errors.New("standard action preview Flow authority is inconsistent")
			}
		}
		result[definition.Name] = definition
	}
	if err := validateStandardActionReferenceAuthority(result); err != nil {
		return nil, err
	}
	return result, nil
}

func validateStandardActionDefinition(definition standardActionDefinition) error {
	for _, value := range []string{definition.Name, definition.Label, definition.FlowDeveloperName} {
		if value == "" || value != strings.TrimSpace(value) {
			return errors.New("invalid standard action identity")
		}
	}
	// The name has to be addressable, not merely non-empty. Without this the failure surfaced at
	// runtime instead of at authoring time: standardActionToWire returns no endpoint for a name that
	// is not one path segment, so the action was silently dropped from discovery with nothing but a
	// log line to say why — an action that vanishes from the UI for a reason no one can see. It is the
	// same predicate the projection and the route parser use, so validation cannot accept what
	// serving would refuse.
	if !standardActionIDAddressable(definition.Name) {
		return errors.New("invalid standard action identity")
	}
	seenLegacyNames := make(map[string]struct{}, len(definition.LegacyNames))
	for _, legacyName := range definition.LegacyNames {
		if !standardActionIDAddressable(legacyName) || strings.EqualFold(legacyName, definition.Name) {
			return errors.New("invalid standard action legacy identity")
		}
		key := strings.ToLower(legacyName)
		if _, duplicate := seenLegacyNames[key]; duplicate {
			return errors.New("duplicate standard action legacy identity")
		}
		seenLegacyNames[key] = struct{}{}
	}
	if len(definition.RequiredPermissions) == 0 {
		return errors.New("standard action must declare required permissions")
	}
	for _, values := range [][]string{definition.RequiredPermissions, definition.TargetObjects} {
		seen := make(map[string]struct{}, len(values))
		for _, value := range values {
			if value == "" || value != strings.TrimSpace(value) {
				return errors.New("invalid standard action authority")
			}
			key := strings.ToLower(value)
			if _, duplicate := seen[key]; duplicate {
				return errors.New("duplicate standard action authority")
			}
			seen[key] = struct{}{}
		}
	}
	for _, fields := range [][]standardActionSchemaField{definition.Inputs, definition.Outputs} {
		seenFields := make(map[string]struct{}, len(fields))
		for _, field := range fields {
			if field.APIName == "" || field.APIName != strings.TrimSpace(field.APIName) ||
				field.DataType == "" || field.DataType != strings.TrimSpace(field.DataType) {
				return errors.New("invalid standard action field")
			}
			key := strings.ToLower(field.APIName)
			if _, duplicate := seenFields[key]; duplicate {
				return errors.New("duplicate standard action field")
			}
			seenFields[key] = struct{}{}
		}
	}
	for _, guard := range definition.InputGuards {
		hasPermission := strings.TrimSpace(guard.RequiredPermission) != ""
		hasObject := strings.TrimSpace(guard.RequiredObject) != "" ||
			strings.TrimSpace(guard.RequiredOperation) != ""
		if guard.Condition == "" || guard.Condition != strings.TrimSpace(guard.Condition) ||
			hasPermission == hasObject {
			return errors.New("invalid standard action input guard")
		}
		if hasPermission && guard.RequiredPermission != strings.TrimSpace(guard.RequiredPermission) {
			return errors.New("invalid standard action input guard permission")
		}
		if hasObject && (guard.RequiredObject == "" ||
			guard.RequiredObject != strings.TrimSpace(guard.RequiredObject) ||
			!standardActionOperationSupported(guard.RequiredOperation)) {
			return errors.New("invalid standard action input guard object authority")
		}
	}
	return nil
}

// validateStandardActionReferenceAuthority proves that one authored layout
// reference resolves to at most one action for a given object. Legacy names
// may intentionally overlap another action's canonical name only when their
// target-object sets are disjoint (for example taskEmail on Opportunity and
// its sendRecordEmail successor on Lead).
func validateStandardActionReferenceAuthority(definitions map[string]standardActionDefinition) error {
	owners := make(map[string]string)
	for _, definition := range definitions {
		references := append([]string{definition.Name}, definition.LegacyNames...)
		for _, targetObject := range definition.TargetObjects {
			for _, reference := range references {
				key := strings.ToLower(targetObject) + "\x00" + strings.ToLower(reference)
				if owner, duplicate := owners[key]; duplicate && !strings.EqualFold(owner, definition.Name) {
					return errors.New("ambiguous standard action layout reference")
				}
				owners[key] = definition.Name
			}
		}
	}
	return nil
}

func standardActionOperationSupported(operation string) bool {
	if operation != strings.TrimSpace(operation) {
		return false
	}
	switch operation {
	case "Read", "Create", "Edit", "Delete":
		return true
	default:
		return false
	}
}

func standardActionRequiredPermissionsGranted(
	definition standardActionDefinition,
	permissions permissionsemantic.Response,
) bool {
	for _, required := range definition.RequiredPermissions {
		if !hasSystemPermission(permissions.SystemPermissions, required) &&
			!hasSystemPermission(permissions.CustomPermissions, required) {
			return false
		}
	}
	return true
}

func standardActionList(
	definitions map[string]standardActionDefinition,
	permissions permissionsemantic.Response,
	targetObject string,
	version string,
) []standardActionWireDescriptor {
	names := make([]string, 0, len(definitions))
	for name, definition := range definitions {
		if !definition.IsAtomic || !standardActionRequiredPermissionsGranted(definition, permissions) ||
			(targetObject != "" && !standardActionTargetsObject(definition, targetObject)) {
			continue
		}
		names = append(names, name)
	}
	sort.Slice(names, func(left, right int) bool {
		leftDefinition, rightDefinition := definitions[names[left]], definitions[names[right]]
		if leftDefinition.SortOrder != rightDefinition.SortOrder {
			return leftDefinition.SortOrder < rightDefinition.SortOrder
		}
		return names[left] < names[right]
	})
	result := make([]standardActionWireDescriptor, 0, len(names))
	for _, name := range names {
		descriptor := standardActionToWire(definitions[name], permissions, version)
		if descriptor.Endpoint == "" {
			// Unaddressable means uninvocable: the route parser below refuses a name this shape,
			// so listing it would advertise a surface that does not exist.
			continue
		}
		result = append(result, descriptor)
	}
	return result
}

func standardActionTargetsObject(definition standardActionDefinition, target string) bool {
	for _, object := range definition.TargetObjects {
		if strings.EqualFold(object, target) {
			return true
		}
	}
	return false
}

// standardActionIDPattern bounds a standard action identifier to one URL path segment: a Salesforce
// DeveloperName, optionally qualified by an object, so `Case.Escalate` and `Lead.Convert` stay
// addressable. Because every dot must be followed by a letter, a matching identifier can never *be* a
// dot segment, which is what makes the surface class of the published endpoint decidable.
//
// This mirrors sfcrm/wire.IsAddressableStandardActionID, deliberately and temporarily. This module
// consumes released dependencies with no replace directive — sealed by
// tests/test_sfcrm_appservice_boundary.py — and the shared predicate landed in sfcrm/wire after the
// pinned v0.1.1. TestTheWirePinStillPredatesTheSharedPredicate fails the moment that pin moves, which
// is the signal to delete this mirror and call the owner instead.
var standardActionIDPattern = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)*$`)

// standardActionIDAddressable reports whether an identifier can be both published as an endpoint and
// accepted from a request path. The identifier is not trimmed: padding is a different identifier, not
// a spelling of this one, and canonicalising it would advertise one action under another's URL.
func standardActionIDAddressable(actionID string) bool {
	return standardActionIDPattern.MatchString(actionID)
}

// addressableStandardActionIDs drops the identifiers that have no endpoint, for callers that hand a
// list to the pinned owner rather than spelling one URL. Naming one action fewer is better than
// naming a URL that resolves onto a different API surface.
func addressableStandardActionIDs(actionIDs []string) []string {
	addressable := make([]string, 0, len(actionIDs))
	for _, actionID := range actionIDs {
		if standardActionIDAddressable(actionID) {
			addressable = append(addressable, actionID)
		}
	}
	return addressable
}

// standardActionEndpoint returns the endpoint for an addressable identifier, or "" for one this
// module would refuse to execute. The spelling itself still comes from the sfcrm/wire owner, so the
// version fallback and the path shape are not restated here.
func standardActionEndpoint(version, actionID string) string {
	if !standardActionIDAddressable(actionID) {
		return ""
	}
	return crmwire.StandardActionEndpoint(version, actionID)
}

func standardActionToWire(
	definition standardActionDefinition,
	permissions permissionsemantic.Response,
	version string,
) standardActionWireDescriptor {
	result := standardActionWireDescriptor{
		Name: definition.Name, Label: definition.Label, Description: definition.Description,
		LegacyNames: append([]string(nil), definition.LegacyNames...),
		Category:    definition.Category, FlowDeveloperName: definition.FlowDeveloperName,
		IsAtomic:               definition.IsAtomic,
		RequiredPermissions:    append([]string(nil), definition.RequiredPermissions...),
		TargetObjects:          append([]string(nil), definition.TargetObjects...),
		HeaderFallbackEligible: definition.HeaderFallbackEligible, RendererID: definition.RendererID,
		InputGuards: []standardActionWireGuard{}, Inputs: []standardActionWireField{},
		Outputs: []standardActionWireField{}, VisibilityCondition: definition.VisibilityCondition,
		PreviewFlowDeveloperName: definition.PreviewFlowDeveloperName,
		Config:                   append(json.RawMessage(nil), definition.Config...),
		// Spelled by the sfcrm/wire owner, not here, and only for a name this module will also
		// execute. Building the path inline was wrong twice over: escaping the metadata-authored
		// name does not bound the surface class (a percent-encoded separator is decoded back into
		// URL.Path before any router sees it, so `../../composite/batch` still normalises onto
		// another API surface), and the route parser below rejects such a name after unescaping —
		// so the name was advertised here and refused there. Concatenating the version without
		// defaulting it was a second hole: a blank version yielded `/services/data//actions/...`,
		// one segment short after normalisation.
		Endpoint: standardActionEndpoint(version, definition.Name),
	}
	if definition.CreatePreviewSupported {
		result.PreviewEndpoint = result.Endpoint + "/preview"
	}
	for _, field := range definition.Inputs {
		result.Inputs = append(result.Inputs, standardActionFieldToWire(field))
	}
	for _, field := range definition.Outputs {
		result.Outputs = append(result.Outputs, standardActionFieldToWire(field))
	}
	for _, guard := range definition.InputGuards {
		result.InputGuards = append(result.InputGuards, standardActionWireGuard{
			GatedInput: guard.GatedInput, RequiredPermission: guard.RequiredPermission,
			RequiredObject: guard.RequiredObject, RequiredOperation: guard.RequiredOperation,
			Condition:           guard.Condition,
			CallerHasPermission: standardActionGuardPermissionGranted(guard, permissions),
		})
	}
	return result
}

func standardActionFieldToWire(field standardActionSchemaField) standardActionWireField {
	return standardActionWireField{
		Name: field.APIName, Type: field.DataType, IsCollection: field.IsCollection, Required: field.IsRequired,
		ReferenceTo: field.ReferenceTo, Label: field.Label, UISection: field.UISection,
		UIMode: field.UIMode, TargetObject: field.TargetObject,
		TargetFieldAPIName: field.TargetFieldAPIName, RequiredPicklistTag: field.RequiredPicklistTag,
		MappingSourceObject: field.MappingSourceObject, MappingActionType: field.MappingActionType,
		PrefillSourceFieldAPIName: field.PrefillSourceFieldAPIName,
		TargetRequired:            field.TargetRequired, TargetDefaulted: field.TargetDefaulted,
		Defaulted: field.Defaulted, DefaultValue: field.DefaultValue,
	}
}

func standardActionGuardPermissionGranted(
	guard standardActionInputGuard,
	permissions permissionsemantic.Response,
) bool {
	if guard.RequiredPermission != "" {
		return hasSystemPermission(permissions.SystemPermissions, guard.RequiredPermission) ||
			hasSystemPermission(permissions.CustomPermissions, guard.RequiredPermission)
	}
	if guard.RequiredObject == "" || guard.RequiredOperation == "" {
		return true
	}
	for _, object := range permissions.ObjectPermissions {
		if object.ResourceType != guard.RequiredObject {
			continue
		}
		switch strings.ToLower(guard.RequiredOperation) {
		case "read":
			return object.CanRead
		case "create":
			return object.CanCreate
		case "edit":
			return object.CanEdit
		case "delete":
			return object.CanDelete
		}
	}
	return false
}

func (service *Service) executeStandardAction(
	writer http.ResponseWriter,
	request *http.Request,
	ctx context.Context,
	bearer string,
	view catalogsemantic.AppMetadataView,
	definition standardActionDefinition,
) {
	request.Body = http.MaxBytesReader(writer, request.Body, service.maxRequestBytes)
	decoder := json.NewDecoder(request.Body)
	decoder.UseNumber()
	var body struct {
		Inputs []map[string]any `json:"inputs"`
	}
	if err := decoder.Decode(&body); err != nil || requireJSONEOF(decoder) != nil || len(body.Inputs) == 0 {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, "inputs array is required")
		return
	}
	if len(body.Inputs) > standardActionMaxInputs {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeLimitExceeded, "Maximum 200 action inputs allowed")
		return
	}
	baseKey, err := mutationCommandID(request.Header.Values(crmMutationIdempotencyHeader))
	if err == nil && baseKey == "" {
		baseKey, err = newMutationCommandID()
	}
	if err != nil {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, err.Error())
		return
	}
	results := make([]flowActionExecutionResult, 0, len(body.Inputs))
	for index, input := range body.Inputs {
		canonical, marshalErr := canonicalActionInputs(input)
		if marshalErr != nil {
			results = append(results, failedFlowActionResult(definition.Name, flowInvalidRequestReason, flowExecutionErrorCode))
			continue
		}
		revision := view.Receipts.CatalogRevision
		flowRequest := flowsemantic.CatalogActionRequest{
			IdempotencyKey:  baseKey + "-" + strconv.Itoa(index),
			CanonicalInputs: canonical, MinCatalogRevision: &revision,
			CatalogAction: flowsemantic.CatalogActionBinding{
				ComponentRef: definition.component.ComponentID,
				ActionRef:    definition.Name,
			},
		}
		if err := flowsemantic.ValidateCatalogActionRequest(ctx, flowRequest); err != nil {
			if len(body.Inputs) == 1 && standardActionDeadlineExceeded(err) {
				service.failures.Add(1)
				writeCRMError(writer, http.StatusServiceUnavailable, crmwire.SFCodeLimitExceeded,
					standardActionTimeoutMessage(definition.Name))
				return
			}
			results = append(results, standardActionRuntimeFailure(definition.Name, err))
			continue
		}
		response, executeErr := service.standardActions.execute(ctx, bearer, flowRequest)
		if executeErr == nil {
			executeErr = flowsemantic.ValidateResponseV2(response)
		}
		if executeErr == nil {
			executeErr = validateFlowActionAuthority(
				ctx, service.standardActions.reconcileAppMetadata,
				view.Receipts, response.Receipts,
			)
		}
		if executeErr != nil {
			if index == 0 && standardActionNotVisible(executeErr) {
				service.localRejected.Add(1)
				service.failures.Add(1)
				writeCRMError(writer, http.StatusNotFound, crmwire.SFCodeNotFound,
					"standard action \""+definition.Name+"\" not found")
				return
			}
			if len(body.Inputs) == 1 && standardActionDeadlineExceeded(executeErr) {
				service.failures.Add(1)
				writeCRMError(writer, http.StatusServiceUnavailable, crmwire.SFCodeLimitExceeded,
					standardActionTimeoutMessage(definition.Name))
				return
			}
			results = append(results, standardActionRuntimeFailure(definition.Name, executeErr))
			continue
		}
		if failed, present := presentedStandardActionFailure(definition.Name, response); present {
			results = append(results, failed)
			continue
		}
		outputs, decodeErr := decodeFlowActionOutputs(response.CanonicalOutputs)
		if decodeErr != nil {
			results = append(results, failedFlowActionResult(definition.Name, crmwire.SFGenericInternalErrorMessage, flowUnknownErrorCode))
			continue
		}
		results = append(results, flowActionExecutionResult{
			ActionName: definition.Name, IsSuccess: true,
			Errors:       []flowActionExecutionError{},
			OutputValues: projectStandardActionOutputValues(outputs),
		})
	}
	service.successes.Add(1)
	writeJSON(writer, http.StatusOK, standardActionExecutionResultsToWire(results))
}

func standardActionExecutionResultsToWire(results []flowActionExecutionResult) []map[string]any {
	projected := make([]map[string]any, 0, len(results))
	for _, result := range results {
		row := map[string]any{"isSuccess": result.IsSuccess}
		if result.ActionName != "" {
			row["actionName"] = result.ActionName
		}
		if len(result.Errors) > 0 {
			row["errors"] = result.Errors
		} else if result.ActionName == "" {
			row["errors"] = []flowActionExecutionError{}
		}
		if len(result.OutputValues) > 0 {
			row["outputValues"] = result.OutputValues
		}
		projected = append(projected, row)
	}
	return projected
}

func presentedStandardActionFailure(
	name string,
	response flowsemantic.ResponseV2,
) (flowActionExecutionResult, bool) {
	return presentedFlowActionFailureWithStatusCode(
		name, response, standardActionExecutionStatusCode,
	)
}

func standardActionExecutionStatusCode(code string) string {
	if mapped := standardActionPreviewStatusCode(code); mapped != code {
		return mapped
	}
	return flowActionStatusCode(code)
}

func standardActionRuntimeFailure(name string, err error) flowActionExecutionResult {
	if standardActionNotVisible(err) {
		return failedFlowActionResult(
			name,
			"standard action \""+name+"\" not found",
			crmwire.SFCodeNotFound,
		)
	}
	if standardActionDeadlineExceeded(err) {
		return failedFlowActionResult(name, standardActionTimeoutMessage(name), crmwire.SFCodeLimitExceeded)
	}
	return flowActionRuntimeFailure(name, err)
}

func standardActionNotVisible(err error) bool {
	var failure *commonsemantic.Failure
	return errors.As(err, &failure) && failure.Category == commonsemantic.FailureMissing
}

func standardActionDeadlineExceeded(err error) bool {
	if errors.Is(err, context.DeadlineExceeded) {
		return true
	}
	var failure *commonsemantic.Failure
	return errors.As(err, &failure) && failure.Category == commonsemantic.FailureDeadlineExceeded
}

func standardActionTimeoutMessage(name string) string {
	return "standard action " + strconv.Quote(name) + " exceeded its execution time budget"
}

func projectStandardActionOutputValues(values map[string]any) map[string]any {
	if len(values) == 0 {
		return values
	}
	result := make(map[string]any, len(values))
	for key, value := range values {
		result[key] = projectStandardActionPreviewValue(value, true)
	}
	return result
}

type runtimeStandardActionPreviewError struct {
	Code    string   `json:"code"`
	Message string   `json:"message"`
	Fields  []string `json:"fields"`
}

type runtimeStandardActionPreviewImage struct {
	Object                    string         `json:"object"`
	Fields                    map[string]any `json:"fields"`
	SiblingMaxDefaultedFields []string       `json:"siblingMaxDefaultedFields,omitempty"`
}

type runtimeStandardActionChildPreview struct {
	Object string           `json:"object"`
	Count  int              `json:"count"`
	Rows   []map[string]any `json:"rows,omitempty"`
}

type runtimeStandardActionCreatePreview struct {
	ActionName    string                              `json:"actionName"`
	IsSuccess     bool                                `json:"isSuccess"`
	Errors        []runtimeStandardActionPreviewError `json:"errors"`
	ParentPreview *runtimeStandardActionPreviewImage  `json:"parentPreview"`
	ChildPreview  []runtimeStandardActionChildPreview `json:"childPreview"`
}

type runtimeStandardActionOutputPreview struct {
	OutputValues map[string]any                      `json:"outputValues"`
	IsSuccess    bool                                `json:"isSuccess"`
	Errors       []runtimeStandardActionPreviewError `json:"errors"`
}

type standardActionCreatePreviewWire struct {
	ActionName    string                              `json:"actionName"`
	IsSuccess     bool                                `json:"isSuccess"`
	Errors        []flowActionExecutionError          `json:"errors,omitempty"`
	ParentPreview *runtimeStandardActionPreviewImage  `json:"parentPreview,omitempty"`
	ChildPreview  []runtimeStandardActionChildPreview `json:"childPreview,omitempty"`
}

type standardActionOutputPreviewWire struct {
	OutputValues map[string]any             `json:"outputValues"`
	IsSuccess    bool                       `json:"isSuccess"`
	Errors       []flowActionExecutionError `json:"errors"`
}

func (service *Service) previewStandardAction(
	writer http.ResponseWriter,
	request *http.Request,
	ctx context.Context,
	bearer string,
	view catalogsemantic.AppMetadataView,
	definition standardActionDefinition,
	kind standardActionsRouteKind,
) {
	request.Body = http.MaxBytesReader(writer, request.Body, service.maxRequestBytes)
	revision := view.Receipts.CatalogRevision
	previewRequest := flowsemantic.CatalogActionPreviewRequest{
		MinCatalogRevision: &revision,
		CatalogAction: flowsemantic.CatalogActionBinding{
			ComponentRef: definition.component.ComponentID,
			ActionRef:    definition.Name,
		},
	}
	decoder := json.NewDecoder(request.Body)
	decoder.UseNumber()
	switch kind {
	case standardActionsRouteCreatePreview:
		if !definition.CreatePreviewSupported {
			service.localRejected.Add(1)
			service.failures.Add(1)
			writeCRMError(
				writer, http.StatusNotFound, crmwire.SFCodeNotFound,
				"standard action \""+definition.Name+"\" has no create preview",
			)
			return
		}
		var body struct {
			Input map[string]any `json:"input"`
		}
		if err := decoder.Decode(&body); err != nil {
			service.rejectStandardActionPreview(writer, "Invalid JSON: "+err.Error())
			return
		}
		if err := requireJSONEOF(decoder); err != nil {
			service.rejectStandardActionPreview(writer, "Invalid JSON: "+err.Error())
			return
		}
		canonical, err := canonicalActionInputs(body.Input)
		if err != nil {
			service.rejectStandardActionPreview(writer, "Invalid standard action preview input")
			return
		}
		previewRequest.Mode = flowsemantic.CatalogActionPreviewCreateRecords
		previewRequest.CanonicalInputs = canonical
	case standardActionsRouteOutputPreview:
		if definition.PreviewFlowDeveloperName == "" {
			service.localRejected.Add(1)
			service.failures.Add(1)
			writeCRMError(
				writer, http.StatusNotFound, crmwire.SFCodeNotFound,
				"standard action \""+definition.Name+"\" has no preview producer",
			)
			return
		}
		decoder.DisallowUnknownFields()
		var body struct {
			RecordID string `json:"recordId"`
		}
		if err := decoder.Decode(&body); err != nil {
			service.rejectStandardActionPreview(writer, "Invalid JSON: "+err.Error())
			return
		}
		if err := requireJSONEOF(decoder); err != nil {
			service.rejectStandardActionPreview(writer, "Invalid JSON: "+err.Error())
			return
		}
		previewRequest.Mode = flowsemantic.CatalogActionPreviewOutputs
		previewRequest.RecordID = body.RecordID
	default:
		service.rejectStandardActionPreview(writer, "Invalid standard action preview route")
		return
	}
	if err := flowsemantic.ValidateCatalogActionPreviewRequest(ctx, previewRequest); err != nil {
		service.rejectStandardActionPreview(writer, "Invalid standard action preview request")
		return
	}
	response, err := service.standardActions.preview(ctx, bearer, previewRequest)
	if err == nil {
		err = flowsemantic.ValidateCatalogActionPreviewResponse(previewRequest, response)
	}
	if err == nil {
		err = validateFlowActionAuthority(
			ctx, service.standardActions.reconcileAppMetadata,
			view.Receipts, response.Receipts,
		)
	}
	if err != nil {
		service.failures.Add(1)
		writeRuntimeFailure(writer, err)
		return
	}
	if err := service.writeStandardActionPreviewResult(
		writer, definition.Name, kind, response.CanonicalResult,
	); err != nil {
		service.failures.Add(1)
		writeCRMError(
			writer, http.StatusInternalServerError, crmwire.SFCodeUnknownException,
			crmwire.SFGenericInternalErrorMessage,
		)
		return
	}
	service.successes.Add(1)
}

func (service *Service) rejectStandardActionPreview(
	writer http.ResponseWriter,
	message string,
) {
	service.localRejected.Add(1)
	service.failures.Add(1)
	writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, message)
}

func (service *Service) writeStandardActionPreviewResult(
	writer http.ResponseWriter,
	actionName string,
	kind standardActionsRouteKind,
	canonical []byte,
) error {
	decoder := json.NewDecoder(bytes.NewReader(canonical))
	decoder.UseNumber()
	decoder.DisallowUnknownFields()
	switch kind {
	case standardActionsRouteCreatePreview:
		var result runtimeStandardActionCreatePreview
		if err := decoder.Decode(&result); err != nil || requireJSONEOF(decoder) != nil ||
			result.ActionName != actionName {
			return errors.New("invalid Runtime create preview result")
		}
		if result.ParentPreview != nil {
			result.ParentPreview.Fields = projectStandardActionPreviewRecord(
				result.ParentPreview.Fields, false,
			)
		}
		for index := range result.ChildPreview {
			for row := range result.ChildPreview[index].Rows {
				result.ChildPreview[index].Rows[row] = projectStandardActionPreviewRecord(
					result.ChildPreview[index].Rows[row], false,
				)
			}
		}
		writeJSON(writer, http.StatusOK, standardActionCreatePreviewWire{
			ActionName: result.ActionName, IsSuccess: result.IsSuccess,
			Errors:        standardActionPreviewErrors(result.Errors),
			ParentPreview: result.ParentPreview, ChildPreview: result.ChildPreview,
		})
		return nil
	case standardActionsRouteOutputPreview:
		var result runtimeStandardActionOutputPreview
		if err := decoder.Decode(&result); err != nil || requireJSONEOF(decoder) != nil {
			return errors.New("invalid Runtime output preview result")
		}
		for key, value := range result.OutputValues {
			result.OutputValues[key] = projectStandardActionPreviewValue(value, true)
		}
		writeJSON(writer, http.StatusOK, standardActionOutputPreviewWire{
			OutputValues: result.OutputValues, IsSuccess: result.IsSuccess,
			Errors: standardActionPreviewErrors(result.Errors),
		})
		return nil
	default:
		return errors.New("invalid Runtime preview result profile")
	}
}

func standardActionPreviewErrors(
	input []runtimeStandardActionPreviewError,
) []flowActionExecutionError {
	if len(input) == 0 {
		return nil
	}
	result := make([]flowActionExecutionError, 0, len(input))
	for _, item := range input {
		result = append(result, flowActionExecutionError{
			StatusCode: standardActionPreviewStatusCode(item.Code),
			Message:    item.Message, Fields: append([]string(nil), item.Fields...),
		})
	}
	return result
}

func standardActionPreviewStatusCode(code string) string {
	switch code {
	case "invalid_request":
		return crmwire.SFCodeInvalidRequest
	case "required_field_missing":
		return crmwire.SFCodeRequiredFieldMissing
	case "invalid_field":
		return crmwire.SFCodeInvalidField
	case "insufficient_permissions":
		return crmwire.SFCodeInsufficientPerms
	case "unknown_exception":
		return crmwire.SFCodeUnknownException
	default:
		return code
	}
}

func projectStandardActionPreviewRecord(
	record map[string]any,
	includeAttributes bool,
) map[string]any {
	if record == nil {
		return nil
	}
	result := make(map[string]any, len(record))
	objectType, _ := record["_objectType"].(string)
	for key, value := range record {
		if strings.HasPrefix(key, "_") || strings.EqualFold(key, "attributes") {
			continue
		}
		result[key] = projectStandardActionPreviewValue(value, true)
	}
	if includeAttributes && objectType != "" {
		result["attributes"] = map[string]any{"type": objectType}
	}
	return result
}

func projectStandardActionPreviewValue(value any, includeAttributes bool) any {
	switch typed := value.(type) {
	case map[string]any:
		if objectType, _ := typed["_objectType"].(string); objectType != "" {
			return projectStandardActionPreviewRecord(typed, includeAttributes)
		}
		result := make(map[string]any, len(typed))
		for key, item := range typed {
			result[key] = projectStandardActionPreviewValue(item, includeAttributes)
		}
		return result
	case []any:
		result := make([]any, 0, len(typed))
		for _, item := range typed {
			result = append(result, projectStandardActionPreviewValue(item, includeAttributes))
		}
		return result
	default:
		return value
	}
}
