package dialectsfcrm

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"strconv"
	"strings"

	"github.com/nexuscrm/shared/pkg/constants"
)

// ApprovalProcessMetadata is the CRM dialect's canonical, normalized view of
// ApprovalProcess metadata. The wire grammar remains L1-owned: callers outside
// this module should project this model into the platform's neutral approval IR
// instead of parsing ProcessDefinition.Metadata themselves.
type ApprovalProcessMetadata struct {
	Label                    string
	Active                   bool
	Description              string
	AllowRecall              *bool
	RecordEditability        string
	FinalApprovalRecordLock  bool
	FinalRejectionRecordLock bool
	EntryCriteria            *ApprovalProcessCriteria

	HasExplicitAllowedSubmitters bool
	AllowedSubmitters            []ApprovalProcessAllowedSubmitter
	NextAutomatedApprover        *ApprovalProcessNextAutomatedApprover
	Steps                        []ApprovalProcessStep
	ApprovalPageFields           []string
	EnableMobileDeviceAccess     bool
	ProcessOrder                 *int

	InitialSubmissionActions []ApprovalProcessAction
	StepApprovalActions      []ApprovalProcessAction
	FinalApprovalActions     []ApprovalProcessAction
	FinalRejectionActions    []ApprovalProcessAction
	RecallActions            []ApprovalProcessAction
	ReassignActions          []ApprovalProcessAction
}

// EffectiveAllowRecall applies the Salesforce-compatible default used when
// allowRecall is omitted from ApprovalProcess metadata.
func (p *ApprovalProcessMetadata) EffectiveAllowRecall() bool {
	if p != nil && p.AllowRecall != nil {
		return *p.AllowRecall
	}
	return true
}

type ApprovalProcessNextAutomatedApprover struct {
	UserHierarchyField            string
	UseApproverFieldOfRecordOwner bool
}

type ApprovalProcessStep struct {
	Name                  string
	Label                 string
	Description           string
	AllowComments         *bool
	RequireComments       *bool
	AllowDelegate         bool
	RejectBehavior        string
	Approvers             []ApprovalProcessApprover
	WhenMultipleApprovers string
	StepCriteria          *ApprovalProcessCriteria
	IfCriteriaNotMet      string
	StepApprovalActions   []ApprovalProcessAction
	StepRejectionActions  []ApprovalProcessAction
}

type ApprovalProcessApprover struct {
	Name string
	Type string
}

type ApprovalProcessAllowedSubmitter struct {
	Submitter string
	Type      string
}

type ApprovalProcessCriteria struct {
	Formula       string
	BooleanFilter string
	CriteriaItems []ApprovalProcessCriteriaItem
}

type ApprovalProcessCriteriaItem struct {
	Field     string
	Operation string
	Value     string
}

type ApprovalProcessAction struct {
	Name                string
	Type                string
	referenceForm       bool // authored through the singular Salesforce action wrapper
	bodyAuthorship      approvalProcessActionBodyAuthorship
	unknownBodyAuthored bool // at least one unsupported payload field or element was present
	FieldAPIName        string
	HasFieldValue       bool
	FieldValue          string
	EmailTemplateID     string
	APIName             string
}

type approvalProcessActionBodyAuthorship struct {
	fieldAPIName    bool
	fieldValue      bool
	emailTemplateID bool
	apiName         bool
}

func (a approvalProcessActionBodyAuthorship) any() bool {
	return a.fieldAPIName || a.fieldValue || a.emailTemplateID || a.apiName
}

// StepForNames finds a normalized step using the identities stored on a
// ProcessNode. DeveloperName wins; label/name matching is the compatibility
// fallback used by the current approval runtime.
func (p *ApprovalProcessMetadata) StepForNames(developerName, label string) *ApprovalProcessStep {
	if p == nil {
		return nil
	}
	for index := range p.Steps {
		step := &p.Steps[index]
		if developerName != "" && strings.EqualFold(step.Name, developerName) {
			return step
		}
		if label != "" && (strings.EqualFold(step.Label, label) || strings.EqualFold(step.Name, label)) {
			return step
		}
	}
	return nil
}

// DecodeApprovalProcessMetadata is the single CRM-owned decoder for
// ProcessDefinition.Metadata. It accepts Salesforce Metadata API XML and the
// legacy JSON representation still present in existing NexusCRM records.
func DecodeApprovalProcessMetadata(raw string) (*ApprovalProcessMetadata, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil, nil
	}
	switch trimmed[0] {
	case '<':
		return decodeApprovalProcessXML(trimmed)
	case '{':
		return decodeApprovalProcessJSON(trimmed)
	default:
		return nil, fmt.Errorf("unsupported approval metadata format")
	}
}

type approvalProcessXMLWire struct {
	XMLName                  xml.Name                              `xml:"ApprovalProcess"`
	Label                    string                                `xml:"label"`
	Active                   approvalProcessBoolXMLWire            `xml:"active"`
	Description              string                                `xml:"description"`
	AllowRecall              *approvalProcessBoolXMLWire           `xml:"allowRecall"`
	RecordEditability        string                                `xml:"recordEditability"`
	FinalApprovalRecordLock  approvalProcessBoolXMLWire            `xml:"finalApprovalRecordLock"`
	FinalRejectionRecordLock approvalProcessBoolXMLWire            `xml:"finalRejectionRecordLock"`
	EntryCriteria            *approvalProcessCriteriaXMLWire       `xml:"entryCriteria,omitempty"`
	AllowedSubmitters        []approvalAllowedSubmitterXMLWire     `xml:"allowedSubmitters"`
	NextAutomatedApprover    *approvalNextAutomatedApproverXMLWire `xml:"nextAutomatedApprover,omitempty"`
	Steps                    []approvalProcessStepXMLWire          `xml:"approvalStep"`
	ApprovalPageFields       []string                              `xml:"approvalPageFields>field"`
	EnableMobileDeviceAccess approvalProcessBoolXMLWire            `xml:"enableMobileDeviceAccess,omitempty"`
	ProcessOrder             string                                `xml:"processOrder"`
	InitialSubmissionActions *approvalProcessActionsXMLWire        `xml:"initialSubmissionActions,omitempty"`
	StepApprovalActions      *approvalProcessActionsXMLWire        `xml:"stepApprovalActions,omitempty"`
	FinalApprovalActions     *approvalProcessActionsXMLWire        `xml:"finalApprovalActions,omitempty"`
	FinalRejectionActions    *approvalProcessActionsXMLWire        `xml:"finalRejectionActions,omitempty"`
	RecallActions            *approvalProcessActionsXMLWire        `xml:"recallActions,omitempty"`
	ReassignActions          *approvalProcessActionsXMLWire        `xml:"reassignActions,omitempty"`
}

type approvalProcessBoolXMLWire string

type approvalNextAutomatedApproverXMLWire struct {
	UserHierarchyField            string `xml:"userHierarchyField"`
	UseApproverFieldOfRecordOwner *bool  `xml:"useApproverFieldOfRecordOwner"`
}

type approvalAllowedSubmitterXMLWire struct {
	Submitter string `xml:"submitter"`
	Type      string `xml:"type"`
}

type approvalProcessCriteriaXMLWire struct {
	Formula       string                               `xml:"formula,omitempty"`
	BooleanFilter string                               `xml:"booleanFilter,omitempty"`
	CriteriaItems []approvalProcessCriteriaItemXMLWire `xml:"criteriaItems,omitempty"`
}

type approvalProcessCriteriaItemXMLWire struct {
	Field     string `xml:"field"`
	Operation string `xml:"operation"`
	Value     string `xml:"value"`
}

type approvalProcessActionsXMLWire struct {
	Action []approvalProcessActionXMLWire `xml:"action"` // external-reference wrapper when no body is authored
	// Actions is the legacy inline wrapper. It remains readable for complete
	// inline payloads, but name/type alone is not an external reference.
	Actions []approvalProcessActionXMLWire `xml:"actions"`
}

type approvalProcessActionXMLWire struct {
	Name            string                                `xml:"name"`
	Type            string                                `xml:"type"`
	FieldAPIName    *string                               `xml:"fieldApiName,omitempty"`
	FieldValue      *string                               `xml:"fieldValue,omitempty"`
	EmailTemplateID *string                               `xml:"emailTemplateId,omitempty"`
	APIName         *string                               `xml:"apiName,omitempty"`
	Unknown         []approvalProcessActionUnknownXMLWire `xml:",any"`
}

type approvalProcessActionUnknownXMLWire struct {
	XMLName xml.Name
}

type approvalProcessStepXMLWire struct {
	Name                 string                           `xml:"name"`
	Label                string                           `xml:"label"`
	Description          string                           `xml:"description,omitempty"`
	AllowComments        *bool                            `xml:"allowComments,omitempty"`
	RequireComments      *bool                            `xml:"requireComments,omitempty"`
	AllowDelegate        *bool                            `xml:"allowDelegate,omitempty"`
	RejectBehavior       *approvalRejectBehaviorXMLWire   `xml:"rejectBehavior,omitempty"`
	AssignedApprover     *approvalAssignedApproverXMLWire `xml:"assignedApprover,omitempty"`
	StepCriteria         *approvalProcessCriteriaXMLWire  `xml:"stepCriteria,omitempty"`
	EntryCriteria        *approvalProcessCriteriaXMLWire  `xml:"entryCriteria,omitempty"`
	IfCriteriaNotMet     string                           `xml:"ifCriteriaNotMet,omitempty"`
	StepApprovalActions  *approvalProcessActionsXMLWire   `xml:"stepApprovalActions,omitempty"`
	StepRejectionActions *approvalProcessActionsXMLWire   `xml:"stepRejectionActions,omitempty"`
}

type approvalRejectBehaviorXMLWire struct {
	Type string `xml:"type"`
	Text string `xml:",chardata"`
}

type approvalAssignedApproverXMLWire struct {
	Approvers             []approvalApproverXMLWire `xml:"approver"`
	WhenMultipleApprovers string                    `xml:"whenMultipleApprovers"`
}

type approvalApproverXMLWire struct {
	Name string `xml:"name"`
	Type string `xml:"type"`
}

func decodeApprovalProcessXML(raw string) (*ApprovalProcessMetadata, error) {
	var wire approvalProcessXMLWire
	if err := xml.Unmarshal([]byte(raw), &wire); err != nil {
		return nil, err
	}
	if wire.XMLName.Local != "ApprovalProcess" {
		return nil, fmt.Errorf("approval metadata root element must be ApprovalProcess")
	}
	active, err := parseApprovalProcessXMLBool(wire.Active, "active")
	if err != nil {
		return nil, err
	}
	allowRecall, err := parseOptionalApprovalProcessXMLBool(wire.AllowRecall, "allowRecall")
	if err != nil {
		return nil, err
	}
	finalApprovalRecordLock, err := parseApprovalProcessXMLBool(wire.FinalApprovalRecordLock, "finalApprovalRecordLock")
	if err != nil {
		return nil, err
	}
	finalRejectionRecordLock, err := parseApprovalProcessXMLBool(wire.FinalRejectionRecordLock, "finalRejectionRecordLock")
	if err != nil {
		return nil, err
	}
	enableMobileDeviceAccess, err := parseApprovalProcessXMLBool(wire.EnableMobileDeviceAccess, "enableMobileDeviceAccess")
	if err != nil {
		return nil, err
	}
	metadata := &ApprovalProcessMetadata{
		Label:                        strings.TrimSpace(wire.Label),
		Active:                       active,
		Description:                  strings.TrimSpace(wire.Description),
		AllowRecall:                  allowRecall,
		RecordEditability:            strings.TrimSpace(wire.RecordEditability),
		FinalApprovalRecordLock:      finalApprovalRecordLock,
		FinalRejectionRecordLock:     finalRejectionRecordLock,
		EntryCriteria:                normalizeApprovalCriteriaXML(wire.EntryCriteria),
		HasExplicitAllowedSubmitters: len(wire.AllowedSubmitters) > 0,
		AllowedSubmitters:            normalizeApprovalAllowedSubmittersXML(wire.AllowedSubmitters),
		NextAutomatedApprover:        normalizeApprovalNextAutomatedApproverXML(wire.NextAutomatedApprover),
		Steps:                        make([]ApprovalProcessStep, 0, len(wire.Steps)),
		ApprovalPageFields:           normalizeApprovalPageFields(wire.ApprovalPageFields),
		EnableMobileDeviceAccess:     enableMobileDeviceAccess,
		ProcessOrder:                 parseOptionalApprovalProcessOrder(wire.ProcessOrder),
		InitialSubmissionActions:     normalizeApprovalActionsXML(wire.InitialSubmissionActions),
		StepApprovalActions:          normalizeApprovalActionsXML(wire.StepApprovalActions),
		FinalApprovalActions:         normalizeApprovalActionsXML(wire.FinalApprovalActions),
		FinalRejectionActions:        normalizeApprovalActionsXML(wire.FinalRejectionActions),
		RecallActions:                normalizeApprovalActionsXML(wire.RecallActions),
		ReassignActions:              normalizeApprovalActionsXML(wire.ReassignActions),
	}
	for _, step := range wire.Steps {
		criteria := step.EntryCriteria
		if criteria == nil {
			criteria = step.StepCriteria
		}
		metadata.Steps = append(metadata.Steps, ApprovalProcessStep{
			Name:                  strings.TrimSpace(step.Name),
			Label:                 strings.TrimSpace(step.Label),
			Description:           strings.TrimSpace(step.Description),
			AllowComments:         cloneBool(step.AllowComments),
			RequireComments:       cloneBool(step.RequireComments),
			AllowDelegate:         step.AllowDelegate != nil && *step.AllowDelegate,
			RejectBehavior:        normalizeApprovalRejectBehaviorXML(step.RejectBehavior),
			Approvers:             normalizeApprovalApproversXML(step.AssignedApprover),
			WhenMultipleApprovers: normalizeApprovalModeXML(step.AssignedApprover),
			StepCriteria:          normalizeApprovalCriteriaXML(criteria),
			IfCriteriaNotMet:      normalizeApprovalIfCriteriaNotMet(step.IfCriteriaNotMet),
			StepApprovalActions:   normalizeApprovalActionsXML(step.StepApprovalActions),
			StepRejectionActions:  normalizeApprovalActionsXML(step.StepRejectionActions),
		})
	}
	return metadata, nil
}

type approvalProcessJSONWire struct {
	Label                    string                                `json:"label"`
	Active                   flexibleBool                          `json:"active"`
	Description              string                                `json:"description"`
	AllowRecall              optionalFlexibleBool                  `json:"allowRecall"`
	RecordEditability        string                                `json:"recordEditability"`
	FinalApprovalRecordLock  flexibleBool                          `json:"finalApprovalRecordLock"`
	FinalRejectionRecordLock flexibleBool                          `json:"finalRejectionRecordLock"`
	EntryCriteria            *approvalProcessCriteriaJSONWire      `json:"entryCriteria"`
	AllowedSubmittersRaw     json.RawMessage                       `json:"allowedSubmitters"`
	NextAutomatedApprover    approvalNextAutomatedApproverJSONWire `json:"nextAutomatedApprover"`
	Steps                    approvalProcessStepListJSONWire       `json:"approvalStep"`
	ApprovalPageFieldsRaw    json.RawMessage                       `json:"approvalPageFields"`
	EnableMobileDeviceAccess flexibleBool                          `json:"enableMobileDeviceAccess"`
	ProcessOrder             optionalFlexibleInt                   `json:"processOrder"`
	InitialSubmissionActions *approvalProcessActionsJSONWire       `json:"initialSubmissionActions"`
	StepApprovalActions      *approvalProcessActionsJSONWire       `json:"stepApprovalActions"`
	FinalApprovalActions     *approvalProcessActionsJSONWire       `json:"finalApprovalActions"`
	FinalRejectionActions    *approvalProcessActionsJSONWire       `json:"finalRejectionActions"`
	RecallActions            *approvalProcessActionsJSONWire       `json:"recallActions"`
	ReassignActions          *approvalProcessActionsJSONWire       `json:"reassignActions"`
}

type approvalNextAutomatedApproverJSONWire struct {
	UserHierarchyField            string       `json:"userHierarchyField"`
	UseApproverFieldOfRecordOwner flexibleBool `json:"useApproverFieldOfRecordOwner"`
}

type approvalAllowedSubmitterJSONWire struct {
	Submitter string `json:"submitter"`
	Type      string `json:"type"`
}

type approvalAllowedSubmitterListJSONWire []approvalAllowedSubmitterJSONWire

func (list *approvalAllowedSubmitterListJSONWire) UnmarshalJSON(data []byte) error {
	return decodeApprovalOneOrMany(data, (*[]approvalAllowedSubmitterJSONWire)(list))
}

type approvalProcessStepListJSONWire []approvalProcessStepJSONWire

func (list *approvalProcessStepListJSONWire) UnmarshalJSON(data []byte) error {
	return decodeApprovalOneOrMany(data, (*[]approvalProcessStepJSONWire)(list))
}

type approvalProcessStepJSONWire struct {
	Name                 string                           `json:"name"`
	Label                string                           `json:"label"`
	Description          string                           `json:"description"`
	AllowComments        optionalFlexibleBool             `json:"allowComments"`
	RequireComments      optionalFlexibleBool             `json:"requireComments"`
	AllowDelegate        flexibleBool                     `json:"allowDelegate"`
	RejectBehavior       approvalRejectBehaviorJSONWire   `json:"rejectBehavior"`
	AssignedApprover     approvalAssignedApproverJSONWire `json:"assignedApprover"`
	StepCriteria         *approvalProcessCriteriaJSONWire `json:"stepCriteria"`
	EntryCriteria        *approvalProcessCriteriaJSONWire `json:"entryCriteria"`
	IfCriteriaNotMet     string                           `json:"ifCriteriaNotMet"`
	StepApprovalActions  *approvalProcessActionsJSONWire  `json:"stepApprovalActions"`
	StepRejectionActions *approvalProcessActionsJSONWire  `json:"stepRejectionActions"`
}

type approvalAssignedApproverJSONWire struct {
	Approvers             approvalApproverListJSONWire `json:"approver"`
	WhenMultipleApprovers string                       `json:"whenMultipleApprovers"`
}

type approvalApproverJSONWire struct {
	Name string `json:"name"`
	Type string `json:"type"`
}

type approvalApproverListJSONWire []approvalApproverJSONWire

func (list *approvalApproverListJSONWire) UnmarshalJSON(data []byte) error {
	return decodeApprovalOneOrMany(data, (*[]approvalApproverJSONWire)(list))
}

type approvalRejectBehaviorJSONWire struct {
	Type string
}

func (behavior *approvalRejectBehaviorJSONWire) UnmarshalJSON(data []byte) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		return nil
	}
	if trimmed[0] == '"' {
		return json.Unmarshal(trimmed, &behavior.Type)
	}
	var object struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(trimmed, &object); err != nil {
		return err
	}
	behavior.Type = object.Type
	return nil
}

type approvalProcessCriteriaJSONWire struct {
	Formula       string                                  `json:"formula"`
	BooleanFilter string                                  `json:"booleanFilter"`
	CriteriaItems approvalProcessCriteriaItemListJSONWire `json:"criteriaItems"`
}

type approvalProcessCriteriaItemJSONWire struct {
	Field     string               `json:"field"`
	Operation string               `json:"operation"`
	Value     flexibleScalarString `json:"value"`
}

type approvalProcessCriteriaItemListJSONWire []approvalProcessCriteriaItemJSONWire

func (list *approvalProcessCriteriaItemListJSONWire) UnmarshalJSON(data []byte) error {
	return decodeApprovalOneOrMany(data, (*[]approvalProcessCriteriaItemJSONWire)(list))
}

type approvalProcessActionsJSONWire struct {
	Action  approvalProcessActionListJSONWire `json:"action"`
	Actions approvalProcessActionListJSONWire `json:"actions"`
}

func (actions *approvalProcessActionsJSONWire) UnmarshalJSON(data []byte) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		*actions = approvalProcessActionsJSONWire{}
		return nil
	}

	switch trimmed[0] {
	case '[':
		var direct approvalProcessActionListJSONWire
		if err := json.Unmarshal(trimmed, &direct); err != nil {
			return err
		}
		*actions = approvalProcessActionsJSONWire{Actions: direct}
		return nil
	case '{':
		var object map[string]json.RawMessage
		if err := json.Unmarshal(trimmed, &object); err != nil {
			return err
		}
		var decoded approvalProcessActionsJSONWire
		wrapped := false
		if raw, ok := object["action"]; ok {
			wrapped = true
			if err := json.Unmarshal(raw, &decoded.Action); err != nil {
				return err
			}
		}
		if raw, ok := object["actions"]; ok {
			wrapped = true
			if err := json.Unmarshal(raw, &decoded.Actions); err != nil {
				return err
			}
		}
		if wrapped {
			*actions = decoded
			return nil
		}

		var direct approvalProcessActionJSONWire
		if err := json.Unmarshal(trimmed, &direct); err != nil {
			return err
		}
		if direct.hasAuthoredValue() {
			decoded.Actions = approvalProcessActionListJSONWire{direct}
		}
		*actions = decoded
		return nil
	default:
		return fmt.Errorf("approval action group must be an object or array")
	}
}

type approvalProcessActionJSONWire struct {
	Name                string                           `json:"name"`
	Type                string                           `json:"type"`
	FieldAPIName        approvalActionBodyStringJSONWire `json:"fieldApiName"`
	FieldValue          approvalActionBodyStringJSONWire `json:"fieldValue"`
	EmailTemplateID     approvalActionBodyStringJSONWire `json:"emailTemplateId"`
	APIName             approvalActionBodyStringJSONWire `json:"apiName"`
	unknownBodyAuthored bool
}

func (action *approvalProcessActionJSONWire) UnmarshalJSON(data []byte) error {
	type actionAlias approvalProcessActionJSONWire
	var decoded actionAlias
	if err := json.Unmarshal(data, &decoded); err != nil {
		return err
	}
	var object map[string]json.RawMessage
	if err := json.Unmarshal(data, &object); err != nil {
		return err
	}
	*action = approvalProcessActionJSONWire(decoded)
	for key := range object {
		switch key {
		case "name", "type", "fieldApiName", "fieldValue", "emailTemplateId", "apiName":
		default:
			action.unknownBodyAuthored = true
			return nil
		}
	}
	return nil
}

type approvalActionBodyStringJSONWire struct {
	authored bool
	null     bool
	value    string
}

func (value *approvalActionBodyStringJSONWire) UnmarshalJSON(data []byte) error {
	value.authored = true
	value.null = false
	value.value = ""
	trimmed := bytes.TrimSpace(data)
	if bytes.Equal(trimmed, []byte("null")) {
		value.null = true
		return nil
	}
	return json.Unmarshal(trimmed, &value.value)
}

func (value approvalActionBodyStringJSONWire) normalizedValue() *string {
	if !value.authored || value.null {
		return nil
	}
	return &value.value
}

func (action approvalProcessActionJSONWire) hasAuthoredValue() bool {
	return action.Name != "" || action.Type != "" || action.FieldAPIName.authored || action.FieldValue.authored || action.EmailTemplateID.authored || action.APIName.authored || action.unknownBodyAuthored
}

type approvalProcessActionListJSONWire []approvalProcessActionJSONWire

func (list *approvalProcessActionListJSONWire) UnmarshalJSON(data []byte) error {
	return decodeApprovalOneOrMany(data, (*[]approvalProcessActionJSONWire)(list))
}

func decodeApprovalProcessJSON(raw string) (*ApprovalProcessMetadata, error) {
	var wire approvalProcessJSONWire
	if err := json.Unmarshal([]byte(raw), &wire); err != nil {
		return nil, err
	}
	var allowedSubmitters approvalAllowedSubmitterListJSONWire
	hasExplicitAllowedSubmitters := false
	if trimmed := bytes.TrimSpace(wire.AllowedSubmittersRaw); len(trimmed) > 0 && !bytes.Equal(trimmed, []byte("null")) {
		hasExplicitAllowedSubmitters = true
		if err := json.Unmarshal(trimmed, &allowedSubmitters); err != nil {
			return nil, err
		}
	}
	metadata := &ApprovalProcessMetadata{
		Label:                        strings.TrimSpace(wire.Label),
		Active:                       bool(wire.Active),
		Description:                  strings.TrimSpace(wire.Description),
		AllowRecall:                  wire.AllowRecall.Pointer(),
		RecordEditability:            strings.TrimSpace(wire.RecordEditability),
		FinalApprovalRecordLock:      bool(wire.FinalApprovalRecordLock),
		FinalRejectionRecordLock:     bool(wire.FinalRejectionRecordLock),
		EntryCriteria:                normalizeApprovalCriteriaJSON(wire.EntryCriteria),
		HasExplicitAllowedSubmitters: hasExplicitAllowedSubmitters,
		AllowedSubmitters:            normalizeApprovalAllowedSubmittersJSON(allowedSubmitters),
		NextAutomatedApprover:        normalizeApprovalNextAutomatedApproverJSON(wire.NextAutomatedApprover),
		Steps:                        make([]ApprovalProcessStep, 0, len(wire.Steps)),
		ApprovalPageFields:           decodeApprovalPageFieldsJSON(wire.ApprovalPageFieldsRaw),
		EnableMobileDeviceAccess:     bool(wire.EnableMobileDeviceAccess),
		ProcessOrder:                 wire.ProcessOrder.Pointer(),
		InitialSubmissionActions:     normalizeApprovalActionsJSON(wire.InitialSubmissionActions),
		StepApprovalActions:          normalizeApprovalActionsJSON(wire.StepApprovalActions),
		FinalApprovalActions:         normalizeApprovalActionsJSON(wire.FinalApprovalActions),
		FinalRejectionActions:        normalizeApprovalActionsJSON(wire.FinalRejectionActions),
		RecallActions:                normalizeApprovalActionsJSON(wire.RecallActions),
		ReassignActions:              normalizeApprovalActionsJSON(wire.ReassignActions),
	}
	for _, step := range wire.Steps {
		criteria := step.EntryCriteria
		if criteria == nil {
			criteria = step.StepCriteria
		}
		metadata.Steps = append(metadata.Steps, ApprovalProcessStep{
			Name:                  strings.TrimSpace(step.Name),
			Label:                 strings.TrimSpace(step.Label),
			Description:           strings.TrimSpace(step.Description),
			AllowComments:         step.AllowComments.Pointer(),
			RequireComments:       step.RequireComments.Pointer(),
			AllowDelegate:         bool(step.AllowDelegate),
			RejectBehavior:        constants.NormalizeApprovalRejectBehavior(step.RejectBehavior.Type),
			Approvers:             normalizeApprovalApproversJSON(step.AssignedApprover.Approvers),
			WhenMultipleApprovers: constants.NormalizeApprovalMode(step.AssignedApprover.WhenMultipleApprovers),
			StepCriteria:          normalizeApprovalCriteriaJSON(criteria),
			IfCriteriaNotMet:      normalizeApprovalIfCriteriaNotMet(step.IfCriteriaNotMet),
			StepApprovalActions:   normalizeApprovalActionsJSON(step.StepApprovalActions),
			StepRejectionActions:  normalizeApprovalActionsJSON(step.StepRejectionActions),
		})
	}
	return metadata, nil
}

func normalizeApprovalApproversXML(assigned *approvalAssignedApproverXMLWire) []ApprovalProcessApprover {
	if assigned == nil {
		return nil
	}
	out := make([]ApprovalProcessApprover, 0, len(assigned.Approvers))
	for _, approver := range assigned.Approvers {
		out = append(out, ApprovalProcessApprover{
			Name: strings.TrimSpace(approver.Name),
			Type: constants.NormalizeApprovalApproverType(approver.Type),
		})
	}
	return out
}

func normalizeApprovalApproversJSON(approvers approvalApproverListJSONWire) []ApprovalProcessApprover {
	if len(approvers) == 0 {
		return nil
	}
	out := make([]ApprovalProcessApprover, 0, len(approvers))
	for _, approver := range approvers {
		out = append(out, ApprovalProcessApprover{
			Name: strings.TrimSpace(approver.Name),
			Type: constants.NormalizeApprovalApproverType(approver.Type),
		})
	}
	return out
}

func normalizeApprovalAllowedSubmittersXML(allowed []approvalAllowedSubmitterXMLWire) []ApprovalProcessAllowedSubmitter {
	out := make([]ApprovalProcessAllowedSubmitter, 0, len(allowed))
	for _, submitter := range allowed {
		if normalized, ok := normalizeApprovalAllowedSubmitter(submitter.Submitter, submitter.Type); ok {
			out = append(out, normalized)
		}
	}
	return out
}

func normalizeApprovalAllowedSubmittersJSON(allowed approvalAllowedSubmitterListJSONWire) []ApprovalProcessAllowedSubmitter {
	out := make([]ApprovalProcessAllowedSubmitter, 0, len(allowed))
	for _, submitter := range allowed {
		if normalized, ok := normalizeApprovalAllowedSubmitter(submitter.Submitter, submitter.Type); ok {
			out = append(out, normalized)
		}
	}
	return out
}

func normalizeApprovalAllowedSubmitter(submitter, rawType string) (ApprovalProcessAllowedSubmitter, bool) {
	normalizedType := constants.NormalizeApprovalSubmitterType(rawType)
	if normalizedType == "" {
		return ApprovalProcessAllowedSubmitter{}, false
	}
	normalized := ApprovalProcessAllowedSubmitter{
		Submitter: strings.TrimSpace(submitter),
		Type:      normalizedType,
	}
	switch normalized.Type {
	case constants.ApprovalSubmitterTypeAllInternalUsers,
		constants.ApprovalSubmitterTypeRecordCreator,
		constants.ApprovalSubmitterTypeRecordOwner:
		normalized.Submitter = ""
	}
	return normalized, true
}

func normalizeApprovalNextAutomatedApproverXML(raw *approvalNextAutomatedApproverXMLWire) *ApprovalProcessNextAutomatedApprover {
	if raw == nil || strings.TrimSpace(raw.UserHierarchyField) == "" {
		return nil
	}
	return &ApprovalProcessNextAutomatedApprover{
		UserHierarchyField:            strings.TrimSpace(raw.UserHierarchyField),
		UseApproverFieldOfRecordOwner: raw.UseApproverFieldOfRecordOwner != nil && *raw.UseApproverFieldOfRecordOwner,
	}
}

func normalizeApprovalNextAutomatedApproverJSON(raw approvalNextAutomatedApproverJSONWire) *ApprovalProcessNextAutomatedApprover {
	if strings.TrimSpace(raw.UserHierarchyField) == "" {
		return nil
	}
	return &ApprovalProcessNextAutomatedApprover{
		UserHierarchyField:            strings.TrimSpace(raw.UserHierarchyField),
		UseApproverFieldOfRecordOwner: bool(raw.UseApproverFieldOfRecordOwner),
	}
}

func normalizeApprovalCriteriaXML(raw *approvalProcessCriteriaXMLWire) *ApprovalProcessCriteria {
	if raw == nil {
		return nil
	}
	normalized := &ApprovalProcessCriteria{
		Formula:       strings.TrimSpace(raw.Formula),
		BooleanFilter: strings.TrimSpace(raw.BooleanFilter),
		CriteriaItems: make([]ApprovalProcessCriteriaItem, 0, len(raw.CriteriaItems)),
	}
	for _, item := range raw.CriteriaItems {
		normalized.CriteriaItems = append(normalized.CriteriaItems, ApprovalProcessCriteriaItem{
			Field:     strings.TrimSpace(item.Field),
			Operation: strings.TrimSpace(item.Operation),
			Value:     strings.TrimSpace(item.Value),
		})
	}
	if normalized.Formula == "" && normalized.BooleanFilter == "" && len(normalized.CriteriaItems) == 0 {
		return nil
	}
	return normalized
}

func normalizeApprovalCriteriaJSON(raw *approvalProcessCriteriaJSONWire) *ApprovalProcessCriteria {
	if raw == nil {
		return nil
	}
	normalized := &ApprovalProcessCriteria{
		Formula:       strings.TrimSpace(raw.Formula),
		BooleanFilter: strings.TrimSpace(raw.BooleanFilter),
		CriteriaItems: make([]ApprovalProcessCriteriaItem, 0, len(raw.CriteriaItems)),
	}
	for _, item := range raw.CriteriaItems {
		normalized.CriteriaItems = append(normalized.CriteriaItems, ApprovalProcessCriteriaItem{
			Field:     strings.TrimSpace(item.Field),
			Operation: strings.TrimSpace(item.Operation),
			Value:     strings.TrimSpace(string(item.Value)),
		})
	}
	if normalized.Formula == "" && normalized.BooleanFilter == "" && len(normalized.CriteriaItems) == 0 {
		return nil
	}
	return normalized
}

func normalizeApprovalActionsXML(raw *approvalProcessActionsXMLWire) []ApprovalProcessAction {
	if raw == nil {
		return nil
	}
	out := make([]ApprovalProcessAction, 0, len(raw.Action)+len(raw.Actions))
	for _, action := range raw.Action {
		normalized := normalizeApprovalActionXML(action)
		normalized.referenceForm = true
		out = append(out, normalized)
	}
	for _, action := range raw.Actions {
		out = append(out, normalizeApprovalActionXML(action))
	}
	return out
}

func normalizeApprovalActionXML(action approvalProcessActionXMLWire) ApprovalProcessAction {
	normalized := normalizeApprovalAction(
		action.Name,
		action.Type,
		approvalProcessActionBodyAuthorship{
			fieldAPIName:    action.FieldAPIName != nil,
			fieldValue:      action.FieldValue != nil,
			emailTemplateID: action.EmailTemplateID != nil,
			apiName:         action.APIName != nil,
		},
		action.FieldAPIName,
		action.FieldValue,
		action.EmailTemplateID,
		action.APIName,
	)
	normalized.unknownBodyAuthored = len(action.Unknown) > 0
	return normalized
}

func normalizeApprovalActionsJSON(raw *approvalProcessActionsJSONWire) []ApprovalProcessAction {
	if raw == nil {
		return nil
	}
	out := make([]ApprovalProcessAction, 0, len(raw.Action)+len(raw.Actions))
	for _, action := range raw.Action {
		normalized := normalizeApprovalActionJSON(action)
		normalized.referenceForm = true
		out = append(out, normalized)
	}
	for _, action := range raw.Actions {
		out = append(out, normalizeApprovalActionJSON(action))
	}
	return out
}

func normalizeApprovalActionJSON(action approvalProcessActionJSONWire) ApprovalProcessAction {
	normalized := normalizeApprovalAction(
		action.Name,
		action.Type,
		approvalProcessActionBodyAuthorship{
			fieldAPIName:    action.FieldAPIName.authored,
			fieldValue:      action.FieldValue.authored,
			emailTemplateID: action.EmailTemplateID.authored,
			apiName:         action.APIName.authored,
		},
		action.FieldAPIName.normalizedValue(),
		action.FieldValue.normalizedValue(),
		action.EmailTemplateID.normalizedValue(),
		action.APIName.normalizedValue(),
	)
	normalized.unknownBodyAuthored = action.unknownBodyAuthored
	return normalized
}

func normalizeApprovalAction(name, rawType string, bodyAuthorship approvalProcessActionBodyAuthorship, fieldAPIName, fieldValue, emailTemplateID, apiName *string) ApprovalProcessAction {
	action := ApprovalProcessAction{
		Name:           strings.TrimSpace(name),
		Type:           normalizeApprovalActionType(rawType),
		bodyAuthorship: bodyAuthorship,
	}
	if fieldAPIName != nil {
		action.FieldAPIName = strings.TrimSpace(*fieldAPIName)
	}
	if fieldValue != nil {
		action.HasFieldValue = true
		action.FieldValue = normalizeApprovalFieldValue(*fieldValue)
	}
	if emailTemplateID != nil {
		action.EmailTemplateID = strings.TrimSpace(*emailTemplateID)
	}
	if apiName != nil {
		action.APIName = strings.TrimSpace(*apiName)
	}
	return action
}

func normalizeApprovalModeXML(assigned *approvalAssignedApproverXMLWire) string {
	if assigned == nil {
		return constants.NormalizeApprovalMode("")
	}
	return constants.NormalizeApprovalMode(assigned.WhenMultipleApprovers)
}

func normalizeApprovalRejectBehaviorXML(raw *approvalRejectBehaviorXMLWire) string {
	if raw == nil {
		return ""
	}
	value := raw.Type
	if strings.TrimSpace(value) == "" {
		value = raw.Text
	}
	return constants.NormalizeApprovalRejectBehavior(value)
}

func normalizeApprovalIfCriteriaNotMet(raw string) string {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "gotonextstep":
		return "GotoNextStep"
	case "approverecord":
		return "ApproveRecord"
	default:
		return strings.TrimSpace(raw)
	}
}

func normalizeApprovalActionType(raw string) string {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "fieldupdate":
		return constants.ProcessActionTypeFieldUpdate
	case "alert":
		return constants.ProcessActionTypeAlert
	case "flow":
		return constants.ProcessActionTypeFlow
	default:
		return strings.TrimSpace(raw)
	}
}

func normalizeApprovalFieldValue(raw string) string {
	const literalPrefix = "Literal: "
	leftTrimmed := strings.TrimLeft(raw, " \t\r\n")
	if strings.HasPrefix(leftTrimmed, literalPrefix) {
		return strings.TrimPrefix(leftTrimmed, literalPrefix)
	}
	return raw
}

func normalizeApprovalPageFields(fields []string) []string {
	out := make([]string, 0, len(fields))
	for _, field := range fields {
		if trimmed := strings.TrimSpace(field); trimmed != "" {
			out = append(out, trimmed)
		}
	}
	return out
}

func decodeApprovalPageFieldsJSON(raw json.RawMessage) []string {
	trimmed := bytes.TrimSpace(raw)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		return nil
	}
	var direct []string
	if trimmed[0] == '[' {
		if json.Unmarshal(trimmed, &direct) == nil {
			return normalizeApprovalPageFields(direct)
		}
		return nil
	}
	var object struct {
		Fields approvalStringListJSONWire `json:"field"`
	}
	if json.Unmarshal(trimmed, &object) != nil {
		return nil
	}
	return normalizeApprovalPageFields(object.Fields)
}

func parseApprovalProcessXMLBool(raw approvalProcessBoolXMLWire, fieldName string) (bool, error) {
	trimmed := strings.TrimSpace(string(raw))
	if trimmed == "" {
		return false, nil
	}
	value, err := strconv.ParseBool(trimmed)
	if err != nil {
		return false, fmt.Errorf("invalid %s value %q: %w", fieldName, trimmed, err)
	}
	return value, nil
}

func parseOptionalApprovalProcessXMLBool(raw *approvalProcessBoolXMLWire, fieldName string) (*bool, error) {
	if raw == nil {
		return nil, nil
	}
	value, err := parseApprovalProcessXMLBool(*raw, fieldName)
	if err != nil {
		return nil, err
	}
	return &value, nil
}

func parseOptionalApprovalProcessOrder(raw string) *int {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil
	}
	value, err := strconv.Atoi(trimmed)
	if err != nil {
		return nil
	}
	return &value
}

type approvalStringListJSONWire []string

func (list *approvalStringListJSONWire) UnmarshalJSON(data []byte) error {
	return decodeApprovalOneOrMany(data, (*[]string)(list))
}

type flexibleBool bool

func (value *flexibleBool) UnmarshalJSON(data []byte) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		return nil
	}
	if trimmed[0] == '"' {
		var text string
		if err := json.Unmarshal(trimmed, &text); err != nil {
			return err
		}
		*value = flexibleBool(strings.EqualFold(strings.TrimSpace(text), "true"))
		return nil
	}
	var raw bool
	if err := json.Unmarshal(trimmed, &raw); err != nil {
		return err
	}
	*value = flexibleBool(raw)
	return nil
}

type optionalFlexibleBool struct {
	set   bool
	value bool
}

func (value *optionalFlexibleBool) UnmarshalJSON(data []byte) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		return nil
	}
	var decoded flexibleBool
	if err := decoded.UnmarshalJSON(trimmed); err != nil {
		return err
	}
	value.set = true
	value.value = bool(decoded)
	return nil
}

func (value optionalFlexibleBool) Pointer() *bool {
	if !value.set {
		return nil
	}
	copy := value.value
	return &copy
}

type optionalFlexibleInt struct {
	set   bool
	value int
}

func (value *optionalFlexibleInt) UnmarshalJSON(data []byte) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		return nil
	}
	if trimmed[0] == '"' {
		var text string
		if err := json.Unmarshal(trimmed, &text); err != nil {
			return err
		}
		parsed, err := strconv.Atoi(strings.TrimSpace(text))
		if err != nil {
			return nil
		}
		value.set = true
		value.value = parsed
		return nil
	}
	var number json.Number
	if err := json.Unmarshal(trimmed, &number); err != nil {
		return err
	}
	parsed, err := strconv.Atoi(number.String())
	if err != nil {
		return nil
	}
	value.set = true
	value.value = parsed
	return nil
}

func (value optionalFlexibleInt) Pointer() *int {
	if !value.set {
		return nil
	}
	copy := value.value
	return &copy
}

type flexibleScalarString string

func (value *flexibleScalarString) UnmarshalJSON(data []byte) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		*value = ""
		return nil
	}
	if trimmed[0] == '"' {
		var text string
		if err := json.Unmarshal(trimmed, &text); err != nil {
			return err
		}
		*value = flexibleScalarString(text)
		return nil
	}
	*value = flexibleScalarString(trimmed)
	return nil
}

func decodeApprovalOneOrMany[T any](data []byte, out *[]T) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		*out = nil
		return nil
	}
	if trimmed[0] == '[' {
		return json.Unmarshal(trimmed, out)
	}
	var item T
	if err := json.Unmarshal(trimmed, &item); err != nil {
		return err
	}
	*out = []T{item}
	return nil
}

func cloneBool(value *bool) *bool {
	if value == nil {
		return nil
	}
	copy := *value
	return &copy
}
