package approvalir

import (
	"fmt"
	"strings"
)

// SubmitterKind is the closed set of principal-selection rules that an
// approval engine may use when deciding who can submit a record.
type SubmitterKind string

const (
	SubmitterKindAllInternalUsers    SubmitterKind = "all_internal_users"
	SubmitterKindRecordCreator       SubmitterKind = "record_creator"
	SubmitterKindRecordOwner         SubmitterKind = "record_owner"
	SubmitterKindUser                SubmitterKind = "user"
	SubmitterKindRole                SubmitterKind = "role"
	SubmitterKindRoleAndSubordinates SubmitterKind = "role_and_subordinates"
)

// Valid reports whether k is a defined SubmitterKind.
func (k SubmitterKind) Valid() bool {
	switch k {
	case SubmitterKindAllInternalUsers, SubmitterKindRecordCreator,
		SubmitterKindRecordOwner, SubmitterKindUser, SubmitterKindRole,
		SubmitterKindRoleAndSubordinates:
		return true
	default:
		return false
	}
}

// RecordEditability defines who may edit a record while an approval instance
// is pending. Its zero value is invalid in an ApprovalPolicy; product decoders
// must resolve omitted authoring values before constructing the neutral policy.
type RecordEditability string

const (
	RecordEditabilityAdminOnly              RecordEditability = "admin_only"
	RecordEditabilityAdminOrCurrentApprover RecordEditability = "admin_or_current_approver"
)

// Valid reports whether e is a defined non-zero RecordEditability.
func (e RecordEditability) Valid() bool {
	switch e {
	case RecordEditabilityAdminOnly, RecordEditabilityAdminOrCurrentApprover:
		return true
	default:
		return false
	}
}

// AllowedSubmitter is one identity allowed to submit a record for approval.
// Submitter is empty for intrinsic selections such as the record creator and
// contains the principal reference for user- and role-backed selections.
type AllowedSubmitter struct {
	Submitter string
	Kind      SubmitterKind
}

// ApprovalPolicy is the neutral submitter and pending-record editability
// policy. Its presence represents an explicit policy; AllowedSubmitters may be
// empty when the authored policy allows no submitter.
type ApprovalPolicy struct {
	RecordEditability RecordEditability
	AllowedSubmitters []AllowedSubmitter
}

// Validate rejects an absent or unknown editability policy and malformed
// submitter selections. Product decoders must normalize wire defaults before
// publishing an ApprovalPolicy.
func (p *ApprovalPolicy) Validate() error {
	if p == nil {
		return fmt.Errorf("approval policy is nil")
	}
	if !p.RecordEditability.Valid() {
		return fmt.Errorf("invalid record editability %q", p.RecordEditability)
	}
	for index, submitter := range p.AllowedSubmitters {
		if err := submitter.validate(); err != nil {
			return fmt.Errorf("allowed submitter %d: %w", index, err)
		}
	}
	return nil
}

func (s AllowedSubmitter) validate() error {
	if !s.Kind.Valid() {
		return fmt.Errorf("invalid kind %q", s.Kind)
	}
	principal := strings.TrimSpace(s.Submitter)
	switch s.Kind {
	case SubmitterKindAllInternalUsers, SubmitterKindRecordCreator, SubmitterKindRecordOwner:
		if principal != "" {
			return fmt.Errorf("kind %q must not carry a principal reference", s.Kind)
		}
	case SubmitterKindUser, SubmitterKindRole, SubmitterKindRoleAndSubordinates:
		if principal == "" {
			return fmt.Errorf("kind %q requires a principal reference", s.Kind)
		}
	}
	return nil
}
