package approvalir

import (
	"fmt"
	"strings"
)

// ActionKind identifies the semantics of an Action, whether its payload is
// inline or represented by an unresolved reference.
type ActionKind string

const (
	ActionKindFieldUpdate ActionKind = "field_update"
	ActionKindFlow        ActionKind = "flow"
	ActionKindAlert       ActionKind = "alert"
)

// Valid reports whether k is a defined ActionKind.
func (k ActionKind) Valid() bool {
	switch k {
	case ActionKindFieldUpdate, ActionKindFlow, ActionKindAlert:
		return true
	default:
		return false
	}
}

// Action is a closed tagged union. Kind selects the action's semantics and the
// action carries exactly one inline payload or one inert reference to an action
// materialized by another authority. Consumers must resolve Reference before
// execution; it is never an implicit no-op or an inline payload substitute.
type Action struct {
	Name        string
	Kind        ActionKind
	Reference   *ActionReference
	FieldUpdate *FieldUpdateAction
	Flow        *FlowAction
	Alert       *AlertAction
}

// ActionReference identifies an action whose executable payload is
// materialized outside this definition.
type ActionReference struct {
	APIName string
}

// FieldUpdateAction applies Value to FieldAPIName.
type FieldUpdateAction struct {
	FieldAPIName string
	Value        FieldUpdateValue
}

// FlowAction invokes the named flow.
type FlowAction struct {
	APIName string
}

// AlertAction carries the template reference of a legacy alert. Whether a host
// supports alerts remains a runtime capability decision.
type AlertAction struct {
	EmailTemplateID string
}

func validateActions(actions []Action) error {
	for index := range actions {
		if err := actions[index].Validate(); err != nil {
			return fmt.Errorf("action %d: %w", index, err)
		}
	}
	return nil
}

// Validate rejects unknown action kinds, missing payloads, and payloads that do
// not match Kind.
func (a *Action) Validate() error {
	if a == nil {
		return fmt.Errorf("action is nil")
	}
	if strings.TrimSpace(a.Name) == "" {
		return fmt.Errorf("name is required")
	}
	if !a.Kind.Valid() {
		return fmt.Errorf("invalid kind %q", a.Kind)
	}
	payloadCount := 0
	if a.Reference != nil {
		payloadCount++
	}
	if a.FieldUpdate != nil {
		payloadCount++
	}
	if a.Flow != nil {
		payloadCount++
	}
	if a.Alert != nil {
		payloadCount++
	}
	if payloadCount != 1 {
		return fmt.Errorf("kind %q requires exactly one action payload, got %d", a.Kind, payloadCount)
	}
	if a.Reference != nil {
		if strings.TrimSpace(a.Reference.APIName) == "" {
			return fmt.Errorf("reference payload requires an API name")
		}
		return nil
	}
	switch a.Kind {
	case ActionKindFieldUpdate:
		if a.FieldUpdate == nil {
			return fmt.Errorf("field-update kind requires a field-update payload")
		}
		if err := a.FieldUpdate.Validate(); err != nil {
			return fmt.Errorf("field-update payload: %w", err)
		}
	case ActionKindFlow:
		if a.Flow == nil {
			return fmt.Errorf("flow kind requires a flow payload")
		}
		if strings.TrimSpace(a.Flow.APIName) == "" {
			return fmt.Errorf("flow payload requires an API name")
		}
	case ActionKindAlert:
		if a.Alert == nil {
			return fmt.Errorf("alert kind requires an alert payload")
		}
		if strings.TrimSpace(a.Alert.EmailTemplateID) == "" {
			return fmt.Errorf("alert payload requires an email-template reference")
		}
	}
	return nil
}

// Validate checks the target field and the tagged update value.
func (a *FieldUpdateAction) Validate() error {
	if a == nil {
		return fmt.Errorf("field-update action is nil")
	}
	if strings.TrimSpace(a.FieldAPIName) == "" {
		return fmt.Errorf("field API name is required")
	}
	if err := a.Value.Validate(); err != nil {
		return fmt.Errorf("value: %w", err)
	}
	return nil
}
