package dialectsfcrm

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"slices"
	"strings"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/approvalir"
)

// ApprovalPublicationSeed is the stopped-install snapshot format used by the
// current CRM pack installer. It contains product-authored semantics and must
// be lowered here, never in Catalog or Runtime.
type ApprovalPublicationSeed struct {
	ProcessDefinitions []ApprovalPublicationProcessDefinition `json:"processDefinitions"`
	ProcessNodes       []ApprovalPublicationProcessNode       `json:"processNodes"`
	ProcessActions     []ApprovalPublicationProcessAction     `json:"processActions"`
}

type ApprovalPublicationProcessDefinition struct {
	ID                       string          `json:"id"`
	DeveloperName            string          `json:"developerName"`
	Name                     string          `json:"name"`
	TableEnumOrID            string          `json:"tableEnumOrId"`
	Description              string          `json:"description"`
	State                    string          `json:"state"`
	IsActive                 bool            `json:"isActive"`
	AllowRecall              bool            `json:"allowRecall"`
	FinalApprovalRecordLock  bool            `json:"finalApprovalRecordLock"`
	FinalRejectionRecordLock bool            `json:"finalRejectionRecordLock"`
	ProcessOrder             *int            `json:"processOrder"`
	EntryFilter              json.RawMessage `json:"entryFilter"`
	MetadataBase64           string          `json:"metadataBase64"`
}

type ApprovalPublicationProcessNode struct {
	ID                   string `json:"id"`
	DeveloperName        string `json:"developerName"`
	Name                 string `json:"name"`
	ProcessDefinitionID  string `json:"processDefinitionId"`
	ProcessDeveloperName string `json:"processDeveloperName,omitempty"`
	ApproverType         string `json:"approverType"`
	ApproverRelatedField string `json:"approverRelatedField"`
	StepOrder            int    `json:"stepOrder"`
}

type ApprovalPublicationProcessAction struct {
	ID                   string `json:"id"`
	Name                 string `json:"name"`
	ProcessDefinitionID  string `json:"processDefinitionId"`
	ProcessDeveloperName string `json:"processDeveloperName,omitempty"`
	ProcessNodeID        string `json:"processNodeId"`
	ActionType           string `json:"actionType"`
	ActionEvent          string `json:"actionEvent"`
	FieldAPIName         string `json:"fieldApiName"`
	FieldValue           string `json:"fieldValue"`
	EmailTemplateID      string `json:"emailTemplateId"`
	APIName              string `json:"apiName"`
	Order                int    `json:"order"`
}

// ApprovalPublicationMetadataSource is one complete product-authored Approval
// definition that has not previously been materialized as stopped-install
// ProcessDefinition/Node/Action rows. Its CRM wire grammar remains L1-owned;
// Catalog receives only the compiled neutral definition.
type ApprovalPublicationMetadataSource struct {
	ObjectID       string
	DefinitionName string
	Raw            []byte
}

// CompileApprovalPublication combines stopped-install snapshots with complete
// immutable metadata sources into one Catalog bundle. This is the composition
// boundary used by release tooling; Runtime never reads either source grammar.
func CompileApprovalPublication(
	metadataSourceDigest string,
	rawSeeds [][]byte,
	metadataSources []ApprovalPublicationMetadataSource,
) ([]byte, string, error) {
	raw, _, err := CompileApprovalPublicationSeeds(metadataSourceDigest, rawSeeds...)
	if err != nil {
		return nil, "", err
	}
	bundle, _, err := approvalir.DecodePublicationBundle(raw, nil)
	if err != nil {
		return nil, "", fmt.Errorf("verify stopped-install Approval publication: %w", err)
	}
	seen := make(map[string]struct{}, len(bundle.Definitions)+len(metadataSources))
	for _, definition := range bundle.Definitions {
		seen[strings.ToLower(definition.ObjectID)+"\x00"+strings.ToLower(definition.DefinitionName)] = struct{}{}
	}
	for _, source := range metadataSources {
		published, compileErr := compileApprovalPublicationMetadataSource(source)
		if compileErr != nil {
			return nil, "", compileErr
		}
		key := strings.ToLower(published.ObjectID) + "\x00" + strings.ToLower(published.DefinitionName)
		if _, duplicate := seen[key]; duplicate {
			return nil, "", fmt.Errorf("Approval publication definition %s/%s is duplicated across source forms", published.ObjectID, published.DefinitionName)
		}
		seen[key] = struct{}{}
		bundle.Definitions = append(bundle.Definitions, published)
	}
	slices.SortFunc(bundle.Definitions, func(left, right approvalir.PublishedDefinition) int {
		if order := strings.Compare(left.ObjectID, right.ObjectID); order != 0 {
			return order
		}
		return strings.Compare(left.DefinitionName, right.DefinitionName)
	})
	return approvalir.EncodePublicationBundle(*bundle, nil)
}

func compileApprovalPublicationMetadataSource(source ApprovalPublicationMetadataSource) (approvalir.PublishedDefinition, error) {
	objectID := strings.TrimSpace(source.ObjectID)
	definitionName := strings.TrimSpace(source.DefinitionName)
	if !metadataDeploySafeIdentifier.MatchString(objectID) || !metadataDeploySafeIdentifier.MatchString(definitionName) {
		return approvalir.PublishedDefinition{}, fmt.Errorf("Approval metadata source has invalid object/definition identity %q/%q", objectID, definitionName)
	}
	metadata, err := DecodeApprovalProcessMetadata(string(source.Raw))
	if err != nil {
		return approvalir.PublishedDefinition{}, fmt.Errorf("decode Approval metadata source %s/%s: %w", objectID, definitionName, err)
	}
	if metadata == nil || !metadata.Active {
		return approvalir.PublishedDefinition{}, fmt.Errorf("Approval metadata source %s/%s is not active", objectID, definitionName)
	}
	definition := ApprovalDefinitionFromMetadata(metadata)
	if err := validateApprovalPublicationActions(definition); err != nil {
		return approvalir.PublishedDefinition{}, fmt.Errorf("Approval metadata source %s/%s: %w", objectID, definitionName, err)
	}
	envelopeRaw, err := approvalir.EncodeDefinitionEnvelope(source.Raw, definition, nil)
	if err != nil {
		return approvalir.PublishedDefinition{}, fmt.Errorf("lower Approval metadata source %s/%s: %w", objectID, definitionName, err)
	}
	envelope, err := approvalir.DecodeDefinitionEnvelope(envelopeRaw, nil)
	if err != nil {
		return approvalir.PublishedDefinition{}, fmt.Errorf("verify Approval metadata source %s/%s: %w", objectID, definitionName, err)
	}
	return approvalir.PublishedDefinition{
		DefinitionID:   "l2.approval." + objectID + "." + definitionName,
		ObjectID:       objectID,
		DefinitionName: definitionName,
		Envelope:       *envelope,
	}, nil
}

// CompileApprovalPublicationSeeds folds one or more immutable stopped-install
// snapshots into the neutral bundle accepted by Catalog. Duplicate natural
// identities, dangling node/action references, and unsupported entry filters
// fail closed before any Catalog operation begins.
func CompileApprovalPublicationSeeds(
	metadataSourceDigest string,
	rawSeeds ...[]byte,
) ([]byte, string, error) {
	var definitions []ApprovalPublicationProcessDefinition
	var nodes []ApprovalPublicationProcessNode
	var actions []ApprovalPublicationProcessAction
	for index, raw := range rawSeeds {
		var seed ApprovalPublicationSeed
		if err := json.Unmarshal(raw, &seed); err != nil {
			return nil, "", fmt.Errorf("decode Approval publication seed[%d]: %w", index, err)
		}
		definitions = append(definitions, seed.ProcessDefinitions...)
		nodes = append(nodes, seed.ProcessNodes...)
		actions = append(actions, seed.ProcessActions...)
	}
	if len(definitions) == 0 {
		return nil, "", fmt.Errorf("Approval publication seeds contain no process definitions")
	}
	definitionsByID := make(map[string]ApprovalPublicationProcessDefinition, len(definitions))
	definitionIDByDeveloperName := make(map[string]string, len(definitions))
	ambiguousDeveloperNames := make(map[string]struct{})
	seenDefinitions := make(map[string]struct{}, len(definitions))
	for _, definition := range definitions {
		if definition.ID == "" || strings.TrimSpace(definition.ID) != definition.ID ||
			definition.TableEnumOrID == "" || definition.DeveloperName == "" {
			return nil, "", fmt.Errorf("Approval publication definition has incomplete identity")
		}
		if _, duplicate := definitionsByID[definition.ID]; duplicate {
			return nil, "", fmt.Errorf("Approval publication definition id %q is duplicated", definition.ID)
		}
		naturalKey := strings.ToLower(definition.TableEnumOrID) + "\x00" + strings.ToLower(definition.DeveloperName)
		if _, duplicate := seenDefinitions[naturalKey]; duplicate {
			return nil, "", fmt.Errorf("Approval publication definition %s/%s is duplicated", definition.TableEnumOrID, definition.DeveloperName)
		}
		definitionsByID[definition.ID] = definition
		seenDefinitions[naturalKey] = struct{}{}
		developerNameKey := strings.ToLower(definition.DeveloperName)
		if previous, found := definitionIDByDeveloperName[developerNameKey]; found && previous != definition.ID {
			ambiguousDeveloperNames[developerNameKey] = struct{}{}
		} else {
			definitionIDByDeveloperName[developerNameKey] = definition.ID
		}
	}
	normalizeParent := func(definitionID, developerName, childKind, childID string) (string, error) {
		if definitionID != "" {
			definition, found := definitionsByID[definitionID]
			if !found {
				return "", fmt.Errorf("Approval publication %s %q has dangling process definition %q", childKind, childID, definitionID)
			}
			if developerName != "" && !strings.EqualFold(developerName, definition.DeveloperName) {
				return "", fmt.Errorf("Approval publication %s %q has conflicting process identities", childKind, childID)
			}
			return definitionID, nil
		}
		developerNameKey := strings.ToLower(strings.TrimSpace(developerName))
		if developerNameKey == "" {
			return "", fmt.Errorf("Approval publication %s %q has no process definition identity", childKind, childID)
		}
		if _, ambiguous := ambiguousDeveloperNames[developerNameKey]; ambiguous {
			return "", fmt.Errorf("Approval publication %s %q has ambiguous process developer name %q", childKind, childID, developerName)
		}
		resolved, found := definitionIDByDeveloperName[developerNameKey]
		if !found {
			return "", fmt.Errorf("Approval publication %s %q has dangling process developer name %q", childKind, childID, developerName)
		}
		return resolved, nil
	}
	for index := range nodes {
		resolved, err := normalizeParent(nodes[index].ProcessDefinitionID, nodes[index].ProcessDeveloperName, "node", nodes[index].ID)
		if err != nil {
			return nil, "", err
		}
		nodes[index].ProcessDefinitionID = resolved
		nodes[index].ProcessDeveloperName = ""
	}
	for index := range actions {
		resolved, err := normalizeParent(actions[index].ProcessDefinitionID, actions[index].ProcessDeveloperName, "action", actions[index].ID)
		if err != nil {
			return nil, "", err
		}
		actions[index].ProcessDefinitionID = resolved
		actions[index].ProcessDeveloperName = ""
	}
	nodesByID := make(map[string]ApprovalPublicationProcessNode, len(nodes))
	seenNodeNames := make(map[string]struct{}, len(nodes))
	for _, node := range nodes {
		if node.ID == "" || strings.TrimSpace(node.ID) != node.ID ||
			node.ProcessDefinitionID == "" || strings.TrimSpace(node.ProcessDefinitionID) != node.ProcessDefinitionID ||
			!metadataDeploySafeIdentifier.MatchString(node.DeveloperName) {
			return nil, "", fmt.Errorf("Approval publication node has incomplete identity")
		}
		if _, duplicate := nodesByID[node.ID]; duplicate {
			return nil, "", fmt.Errorf("Approval publication node id %q is duplicated", node.ID)
		}
		if _, found := definitionsByID[node.ProcessDefinitionID]; !found {
			return nil, "", fmt.Errorf("Approval publication node %q has dangling process definition %q", node.ID, node.ProcessDefinitionID)
		}
		naturalKey := node.ProcessDefinitionID + "\x00" + strings.ToLower(node.DeveloperName)
		if _, duplicate := seenNodeNames[naturalKey]; duplicate {
			return nil, "", fmt.Errorf("Approval publication node developer name %q is duplicated in process definition %q", node.DeveloperName, node.ProcessDefinitionID)
		}
		nodesByID[node.ID] = node
		seenNodeNames[naturalKey] = struct{}{}
	}
	seenActionIDs := make(map[string]struct{}, len(actions))
	seenActionKeys := make(map[string]struct{}, len(actions))
	for _, action := range actions {
		if action.ID == "" || strings.TrimSpace(action.ID) != action.ID ||
			action.ProcessDefinitionID == "" || strings.TrimSpace(action.ProcessDefinitionID) != action.ProcessDefinitionID {
			return nil, "", fmt.Errorf("Approval publication action has incomplete identity")
		}
		if _, duplicate := seenActionIDs[action.ID]; duplicate {
			return nil, "", fmt.Errorf("Approval publication action id %q is duplicated", action.ID)
		}
		if _, found := definitionsByID[action.ProcessDefinitionID]; !found {
			return nil, "", fmt.Errorf("Approval publication action %q has dangling process definition %q", action.ID, action.ProcessDefinitionID)
		}
		if action.ProcessNodeID != "" {
			node, found := nodesByID[action.ProcessNodeID]
			if !found || node.ProcessDefinitionID != action.ProcessDefinitionID {
				return nil, "", fmt.Errorf("Approval publication action %q has cross-definition or dangling node %q", action.ID, action.ProcessNodeID)
			}
		}
		if strings.EqualFold(action.ActionType, "FieldUpdate") &&
			(strings.TrimSpace(action.FieldAPIName) == "" || strings.TrimSpace(action.FieldValue) == "") {
			return nil, "", fmt.Errorf("Approval publication action %q is a FieldUpdate without fieldApiName/fieldValue", action.ID)
		}
		semanticKey := strings.ToLower(strings.Join([]string{
			action.ProcessDefinitionID, action.ActionType, action.ActionEvent,
			action.ProcessNodeID, action.FieldAPIName, action.APIName,
		}, "\x00"))
		if _, duplicate := seenActionKeys[semanticKey]; duplicate {
			return nil, "", fmt.Errorf("Approval publication action semantic identity is duplicated in process definition %q", action.ProcessDefinitionID)
		}
		seenActionIDs[action.ID] = struct{}{}
		seenActionKeys[semanticKey] = struct{}{}
	}
	published := make([]approvalir.PublishedDefinition, 0, len(definitions))
	for _, source := range definitions {
		if !source.IsActive || !strings.EqualFold(source.State, "Active") {
			continue
		}
		definitionNodes := filterApprovalPublicationNodes(nodes, source.ID)
		definitionActions := filterApprovalPublicationActions(actions, source.ID)
		envelope, err := compileApprovalPublicationDefinition(source, definitionNodes, definitionActions, nodesByID)
		if err != nil {
			return nil, "", err
		}
		published = append(published, approvalir.PublishedDefinition{
			DefinitionID:   "l2.approval." + source.TableEnumOrID + "." + source.DeveloperName,
			ObjectID:       source.TableEnumOrID,
			DefinitionName: source.DeveloperName,
			Envelope:       *envelope,
		})
	}
	return approvalir.EncodePublicationBundle(approvalir.PublicationBundle{
		Schema: approvalir.PublicationBundleSchemaV1, SourceDigest: metadataSourceDigest,
		Definitions: published,
	}, nil)
}

func compileApprovalPublicationDefinition(
	source ApprovalPublicationProcessDefinition,
	nodes []ApprovalPublicationProcessNode,
	actions []ApprovalPublicationProcessAction,
	nodesByID map[string]ApprovalPublicationProcessNode,
) (*approvalir.DefinitionEnvelope, error) {
	rawMetadata, err := base64.StdEncoding.DecodeString(source.MetadataBase64)
	if err != nil {
		return nil, fmt.Errorf("decode Approval definition %s/%s metadata: %w", source.TableEnumOrID, source.DeveloperName, err)
	}
	metadata, err := DecodeApprovalProcessMetadata(string(rawMetadata))
	if err != nil {
		return nil, fmt.Errorf("decode Approval definition %s/%s: %w", source.TableEnumOrID, source.DeveloperName, err)
	}
	if metadata == nil {
		return nil, fmt.Errorf("Approval definition %s/%s has empty metadata", source.TableEnumOrID, source.DeveloperName)
	}
	metadata.Label = source.Name
	metadata.Description = source.Description
	metadata.Active = source.IsActive
	metadata.AllowRecall = boolPointer(source.AllowRecall)
	metadata.FinalApprovalRecordLock = source.FinalApprovalRecordLock
	metadata.FinalRejectionRecordLock = source.FinalRejectionRecordLock
	metadata.ProcessOrder = source.ProcessOrder
	if metadata.EntryCriteria == nil && len(source.EntryFilter) != 0 && string(source.EntryFilter) != "null" {
		metadata.EntryCriteria, err = decodeApprovalPublicationEntryFilter(source.EntryFilter)
		if err != nil {
			return nil, fmt.Errorf("Approval definition %s/%s entry filter: %w", source.TableEnumOrID, source.DeveloperName, err)
		}
	}
	// A stopped-install seed is the executable state the monolith actually
	// consumed: lifecycle actions came from ProcessAction rows, not by resolving
	// reference-form actions in the original Metadata document at request time.
	// Rebuild the neutral action closure exclusively from that same snapshot.
	// Keeping both sources would either duplicate inline actions or make stale
	// Salesforce metadata references an accidental runtime dependency.
	clearApprovalPublicationMetadataActions(metadata)
	for _, node := range nodes {
		step := metadata.StepForNames(node.DeveloperName, node.Name)
		if step == nil {
			return nil, fmt.Errorf("Approval definition %s/%s node %q is absent from source metadata", source.TableEnumOrID, source.DeveloperName, node.DeveloperName)
		}
		if len(step.Approvers) == 0 && node.ApproverType != "" {
			step.Approvers = []ApprovalProcessApprover{{Name: node.ApproverRelatedField, Type: node.ApproverType}}
		}
	}
	slices.SortFunc(actions, func(left, right ApprovalPublicationProcessAction) int {
		if left.Order != right.Order {
			return left.Order - right.Order
		}
		return strings.Compare(left.ID, right.ID)
	})
	for _, sourceAction := range actions {
		action := approvalProcessActionFromPublication(sourceAction)
		if sourceAction.ProcessNodeID != "" {
			node, ok := nodesByID[sourceAction.ProcessNodeID]
			if !ok || node.ProcessDefinitionID != source.ID {
				return nil, fmt.Errorf("Approval definition %s/%s action %q has dangling node %q", source.TableEnumOrID, source.DeveloperName, sourceAction.ID, sourceAction.ProcessNodeID)
			}
			step := metadata.StepForNames(node.DeveloperName, node.Name)
			if step == nil {
				return nil, fmt.Errorf("Approval definition %s/%s action %q has unresolved step", source.TableEnumOrID, source.DeveloperName, sourceAction.ID)
			}
			switch strings.ToLower(sourceAction.ActionEvent) {
			case "stepapproval":
				step.StepApprovalActions = append(step.StepApprovalActions, action)
			case "steprejection":
				step.StepRejectionActions = append(step.StepRejectionActions, action)
			default:
				return nil, fmt.Errorf("Approval definition %s/%s node action %q has unsupported event %q", source.TableEnumOrID, source.DeveloperName, sourceAction.ID, sourceAction.ActionEvent)
			}
			continue
		}
		switch strings.ToLower(sourceAction.ActionEvent) {
		case "submit", "initialsubmission":
			metadata.InitialSubmissionActions = append(metadata.InitialSubmissionActions, action)
		case "stepapproval":
			metadata.StepApprovalActions = append(metadata.StepApprovalActions, action)
		case "finalapproval":
			metadata.FinalApprovalActions = append(metadata.FinalApprovalActions, action)
		case "finalrejection":
			metadata.FinalRejectionActions = append(metadata.FinalRejectionActions, action)
		case "recall":
			metadata.RecallActions = append(metadata.RecallActions, action)
		case "reassign":
			metadata.ReassignActions = append(metadata.ReassignActions, action)
		default:
			return nil, fmt.Errorf("Approval definition %s/%s action %q has unsupported event %q", source.TableEnumOrID, source.DeveloperName, sourceAction.ID, sourceAction.ActionEvent)
		}
	}
	definition := ApprovalDefinitionFromMetadata(metadata)
	if err := validateApprovalPublicationActions(definition); err != nil {
		return nil, fmt.Errorf("Approval definition %s/%s: %w", source.TableEnumOrID, source.DeveloperName, err)
	}
	sourceDocument := struct {
		Definition ApprovalPublicationProcessDefinition `json:"processDefinition"`
		Nodes      []ApprovalPublicationProcessNode     `json:"processNodes"`
		Actions    []ApprovalPublicationProcessAction   `json:"processActions"`
	}{Definition: source, Nodes: nodes, Actions: actions}
	canonicalSource, err := json.Marshal(sourceDocument)
	if err != nil {
		return nil, fmt.Errorf("encode Approval definition %s/%s source: %w", source.TableEnumOrID, source.DeveloperName, err)
	}
	envelopeRaw, err := approvalir.EncodeDefinitionEnvelope(canonicalSource, definition, nil)
	if err != nil {
		return nil, fmt.Errorf("lower Approval definition %s/%s: %w", source.TableEnumOrID, source.DeveloperName, err)
	}
	envelope, err := approvalir.DecodeDefinitionEnvelope(envelopeRaw, nil)
	if err != nil {
		return nil, fmt.Errorf("verify Approval definition %s/%s: %w", source.TableEnumOrID, source.DeveloperName, err)
	}
	return envelope, nil
}

func clearApprovalPublicationMetadataActions(metadata *ApprovalProcessMetadata) {
	if metadata == nil {
		return
	}
	metadata.InitialSubmissionActions = nil
	metadata.StepApprovalActions = nil
	metadata.FinalApprovalActions = nil
	metadata.FinalRejectionActions = nil
	metadata.RecallActions = nil
	metadata.ReassignActions = nil
	for index := range metadata.Steps {
		metadata.Steps[index].StepApprovalActions = nil
		metadata.Steps[index].StepRejectionActions = nil
	}
}

func validateApprovalPublicationActions(definition *approvalir.Definition) error {
	if definition == nil {
		return fmt.Errorf("definition is nil")
	}
	groups := [][]approvalir.Action{
		definition.InitialSubmissionActions,
		definition.StepApprovalActions,
		definition.FinalApprovalActions,
		definition.FinalRejectionActions,
		definition.RecallActions,
		definition.ReassignActions,
	}
	for _, step := range definition.Steps {
		groups = append(groups, step.ApprovalActions, step.RejectionActions)
	}
	for _, actions := range groups {
		for _, action := range actions {
			if action.Reference != nil {
				return fmt.Errorf("unresolved action reference %q cannot be published", action.Reference.APIName)
			}
		}
	}
	return nil
}

func approvalProcessActionFromPublication(source ApprovalPublicationProcessAction) ApprovalProcessAction {
	action := ApprovalProcessAction{
		Name: source.Name, Type: source.ActionType,
		FieldAPIName: source.FieldAPIName, FieldValue: source.FieldValue,
		EmailTemplateID: source.EmailTemplateID, APIName: source.APIName,
		HasFieldValue: source.FieldAPIName != "" && source.FieldValue != "",
	}
	action.bodyAuthorship = approvalProcessActionBodyAuthorship{
		fieldAPIName: source.FieldAPIName != "", fieldValue: source.FieldValue != "",
		emailTemplateID: source.EmailTemplateID != "", apiName: source.APIName != "",
	}
	action.referenceForm = source.Name != "" && !action.bodyAuthorship.any()
	return action
}

func decodeApprovalPublicationEntryFilter(raw json.RawMessage) (*ApprovalProcessCriteria, error) {
	trimmed := strings.TrimSpace(string(raw))
	if strings.HasPrefix(trimmed, "{") {
		var criteria ApprovalProcessCriteria
		if err := json.Unmarshal(raw, &criteria); err != nil {
			return nil, err
		}
		return &criteria, nil
	}
	var legacy string
	if err := json.Unmarshal(raw, &legacy); err != nil {
		return nil, fmt.Errorf("must be a legacy string or criteria object: %w", err)
	}
	if strings.TrimSpace(legacy) == "" {
		return nil, nil
	}
	parts := strings.Fields(strings.TrimSpace(legacy))
	if len(parts) < 3 {
		return nil, fmt.Errorf("unsupported legacy entry filter %q", legacy)
	}
	return &ApprovalProcessCriteria{CriteriaItems: []ApprovalProcessCriteriaItem{{
		Field: parts[0], Operation: parts[1], Value: strings.Join(parts[2:], " "),
	}}}, nil
}

func filterApprovalPublicationNodes(values []ApprovalPublicationProcessNode, definitionID string) []ApprovalPublicationProcessNode {
	result := make([]ApprovalPublicationProcessNode, 0)
	for _, value := range values {
		if value.ProcessDefinitionID == definitionID {
			result = append(result, value)
		}
	}
	slices.SortFunc(result, func(left, right ApprovalPublicationProcessNode) int {
		if left.StepOrder != right.StepOrder {
			return left.StepOrder - right.StepOrder
		}
		return strings.Compare(left.ID, right.ID)
	})
	return result
}

func filterApprovalPublicationActions(values []ApprovalPublicationProcessAction, definitionID string) []ApprovalPublicationProcessAction {
	result := make([]ApprovalPublicationProcessAction, 0)
	for _, value := range values {
		if value.ProcessDefinitionID == definitionID {
			result = append(result, value)
		}
	}
	return result
}

func boolPointer(value bool) *bool { return &value }
