package dialectsfcrm

import (
	"fmt"
	"strings"
	"unicode"

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

const defaultApprovalProcessHierarchyField = "ManagerId"

// NewApprovalProcessDefinitionDecoder exposes the CRM-owned ApprovalProcess
// grammar as validated, product-neutral approval semantics. Formula criteria
// remain fail-closed until the host opts into the validator-bound constructor.
func NewApprovalProcessDefinitionDecoder() approvalir.DefinitionDecoder {
	return approvalProcessDefinitionDecoderFunc()
}

// NewApprovalProcessDefinitionDecoderWithFormulaValidator binds formula
// criteria to the host's single formula-language validator.
func NewApprovalProcessDefinitionDecoderWithFormulaValidator(formulaValidator approvalir.FormulaValidator) approvalir.DefinitionDecoder {
	return approvalir.NewFormulaValidatingDefinitionDecoder(approvalProcessDefinitionDecoderFunc(), formulaValidator)
}

func approvalProcessDefinitionDecoderFunc() approvalir.DefinitionDecoderFunc {
	return func(raw string) (*approvalir.Definition, error) {
		metadata, err := DecodeApprovalProcessMetadata(raw)
		if err != nil || metadata == nil {
			return nil, err
		}
		return ApprovalDefinitionFromMetadata(metadata), nil
	}
}

// ApprovalDefinitionFromMetadata lowers one already-normalized CRM Approval
// source into the neutral Runtime IR. Deployment compilers use this after
// folding an immutable L2 source snapshot; Runtime never imports this package.
func ApprovalDefinitionFromMetadata(metadata *ApprovalProcessMetadata) *approvalir.Definition {
	if metadata == nil {
		return nil
	}
	editability := approvalir.RecordEditability(approvalIRToken(metadata.RecordEditability))
	switch strings.ToLower(strings.TrimSpace(metadata.RecordEditability)) {
	case "", "admin", "locked":
		editability = approvalir.RecordEditabilityAdminOnly
	case "workitem":
		editability = approvalir.RecordEditabilityAdminOrCurrentApprover
	}
	submitters := approvalIRMap(metadata.AllowedSubmitters, func(value ApprovalProcessAllowedSubmitter) approvalir.AllowedSubmitter {
		return approvalir.AllowedSubmitter{Submitter: value.Submitter, Kind: approvalir.SubmitterKind(approvalIRToken(value.Type))}
	})
	if !metadata.HasExplicitAllowedSubmitters {
		submitters = []approvalir.AllowedSubmitter{{Kind: approvalir.SubmitterKindAllInternalUsers}}
	}
	definition := &approvalir.Definition{
		Label: metadata.Label, Active: metadata.Active, Description: metadata.Description,
		AllowRecall: metadata.EffectiveAllowRecall(), Policy: approvalir.ApprovalPolicy{RecordEditability: editability, AllowedSubmitters: submitters},
		FinalApprovalRecordLock: metadata.FinalApprovalRecordLock, FinalRejectionRecordLock: metadata.FinalRejectionRecordLock,
		EntryCriteria: approvalIRCriteria(metadata.EntryCriteria), ProcessOrder: metadata.ProcessOrder,
		InitialSubmissionActions: approvalIRActions(metadata.InitialSubmissionActions), StepApprovalActions: approvalIRActions(metadata.StepApprovalActions),
		FinalApprovalActions: approvalIRActions(metadata.FinalApprovalActions), FinalRejectionActions: approvalIRActions(metadata.FinalRejectionActions),
		RecallActions: approvalIRActions(metadata.RecallActions), ReassignActions: approvalIRActions(metadata.ReassignActions),
	}
	if next := metadata.NextAutomatedApprover; next != nil {
		principal := approvalir.HierarchyInitialPrincipalSubmitter
		if next.UseApproverFieldOfRecordOwner {
			principal = approvalir.HierarchyInitialPrincipalRecordOwner
		}
		definition.HierarchyRouting = &approvalir.HierarchyRouting{UserHierarchyField: next.UserHierarchyField, InitialPrincipal: principal}
	}
	hasHierarchyApprover := false
	definition.Steps = approvalIRMap(metadata.Steps, func(step ApprovalProcessStep) approvalir.Step {
		approvers := approvalIRMap(step.Approvers, func(value ApprovalProcessApprover) approvalir.Approver {
			kind := approvalir.ApproverKind(approvalIRToken(value.Type))
			reference := value.Name
			if kind == approvalir.ApproverKindUserHierarchyField || kind == approvalir.ApproverKindHierarchy {
				kind = approvalir.ApproverKindHierarchy
				reference = ""
				hasHierarchyApprover = true
			}
			return approvalir.Approver{Kind: kind, Reference: reference}
		})
		mode := approvalir.MultipleApproverMode("")
		if len(approvers) > 1 {
			mode = approvalir.MultipleApproverMode(approvalIRToken(step.WhenMultipleApprovers))
		}
		reject := approvalir.RejectBehavior(approvalIRToken(step.RejectBehavior))
		if reject == "" {
			reject = approvalir.RejectBehaviorFinalRejection
		}
		criteria := approvalIRCriteria(step.StepCriteria)
		transition := approvalir.IfCriteriaNotMet(approvalIRToken(step.IfCriteriaNotMet))
		switch transition {
		case "approve_record":
			transition = approvalir.IfCriteriaNotMetApprove
		case "reject_record":
			transition = approvalir.IfCriteriaNotMetReject
		}
		if criteria != nil && transition == "" {
			transition = approvalir.IfCriteriaNotMetApprove
		}
		label := strings.TrimSpace(step.Label)
		if label == "" {
			label = strings.TrimSpace(step.Name)
		}
		return approvalir.Step{Name: step.Name, Label: label, Description: step.Description, AllowDelegate: step.AllowDelegate,
			AllowComments: step.AllowComments, RequireComments: step.RequireComments, Approvers: approvers, MultipleApproverMode: mode,
			RejectBehavior: reject, Criteria: criteria, IfCriteriaNotMet: transition,
			ApprovalActions: approvalIRActions(step.StepApprovalActions), RejectionActions: approvalIRActions(step.StepRejectionActions)}
	})
	if hasHierarchyApprover && definition.HierarchyRouting == nil {
		definition.HierarchyRouting = &approvalir.HierarchyRouting{
			UserHierarchyField: defaultApprovalProcessHierarchyField,
			InitialPrincipal:   approvalir.HierarchyInitialPrincipalSubmitter,
		}
	}
	return definition
}

func approvalIRCriteria(criteria *ApprovalProcessCriteria) *approvalir.Criteria {
	if criteria == nil {
		return nil
	}
	mode := approvalir.CriteriaModeItems
	if criteria.Formula != "" {
		mode = ""
	}
	return &approvalir.Criteria{Mode: mode, Formula: criteria.Formula, BooleanFilter: criteria.BooleanFilter,
		Items: approvalIRMap(criteria.CriteriaItems, func(item ApprovalProcessCriteriaItem) approvalir.CriteriaItem {
			return approvalir.CriteriaItem{Field: item.Field, Operation: approvalir.CriteriaOperator(approvalIRToken(item.Operation)), Value: item.Value}
		})}
}

func approvalIRActions(actions []ApprovalProcessAction) []approvalir.Action {
	out := make([]approvalir.Action, len(actions))
	for index, value := range actions {
		authoredName := strings.TrimSpace(value.Name)
		name := authoredName
		if name == "" {
			name = fmt.Sprintf("#%d", index+1)
		}
		action := approvalir.Action{Name: name, Kind: approvalir.ActionKind(approvalIRToken(value.Type))}
		if value.unknownBodyAuthored || approvalIRActionBodyIsIncompatible(action.Kind, value.bodyAuthorship) {
			out[index] = action
			continue
		}
		if value.referenceForm && authoredName != "" && !value.bodyAuthorship.any() {
			action.Reference = &approvalir.ActionReference{APIName: authoredName}
			out[index] = action
			continue
		}
		switch action.Kind {
		case approvalir.ActionKindFieldUpdate:
			fieldValue := approvalir.FieldUpdateValue{Kind: approvalir.FieldUpdateValueKindSourceText, Null: !value.HasFieldValue}
			if value.HasFieldValue {
				fieldValue.SourceText = &value.FieldValue
			}
			action.FieldUpdate = &approvalir.FieldUpdateAction{FieldAPIName: value.FieldAPIName, Value: fieldValue}
		case approvalir.ActionKindFlow:
			action.Flow = &approvalir.FlowAction{APIName: value.APIName}
		case approvalir.ActionKindAlert:
			action.Alert = &approvalir.AlertAction{EmailTemplateID: value.EmailTemplateID}
		}
		out[index] = action
	}
	return out
}

func approvalIRActionBodyIsIncompatible(kind approvalir.ActionKind, authored approvalProcessActionBodyAuthorship) bool {
	switch kind {
	case approvalir.ActionKindFieldUpdate:
		return authored.emailTemplateID || authored.apiName
	case approvalir.ActionKindFlow:
		return authored.fieldAPIName || authored.fieldValue || authored.emailTemplateID
	case approvalir.ActionKindAlert:
		return authored.fieldAPIName || authored.fieldValue || authored.apiName
	default:
		return false
	}
}

func approvalIRToken(raw string) string {
	var out strings.Builder
	for index, char := range strings.TrimSpace(raw) {
		if index > 0 && unicode.IsUpper(char) {
			out.WriteByte('_')
		}
		out.WriteRune(unicode.ToLower(char))
	}
	return strings.ReplaceAll(out.String(), "adhoc", "ad_hoc")
}

func approvalIRMap[S, T any](values []S, project func(S) T) []T {
	out := make([]T, len(values))
	for index, value := range values {
		out[index] = project(value)
	}
	return out
}
