package plan

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
)

func (v *validator) validateSendMessageConfig(path string, node *Node) {
	config := node.SendMessage
	hasTarget := strings.TrimSpace(config.Target) != ""
	hasScope := config.Scope != nil
	var staticTargetKind domain.MessageTargetKind
	var staticScopeKind domain.MessageScopeKind
	switch {
	case !hasTarget && !hasScope:
		v.error("send_message.destination_required", path+".sendMessage", "exactly one of target or scope is required", node.ID, "")
	case hasTarget && hasScope:
		v.error("send_message.destination_conflict", path+".sendMessage", "target and scope are mutually exclusive", node.ID, "")
	}
	if strings.TrimSpace(config.Text) == "" {
		v.error("send_message.text_required", path+".sendMessage.text", "text is required", node.ID, "")
	}
	if hasTarget {
		v.validateTemplate(path+".sendMessage.target", config.Target, node)
		v.validateMessageTargetTemplateTypes(path+".sendMessage.target", config.Target, node)
		if target := strings.TrimSpace(config.Target); !templatePattern.MatchString(config.Target) {
			reference, err := domain.ParseMessageTargetReference(target)
			if err != nil {
				v.error("send_message.invalid_target", path+".sendMessage.target", err.Error(), node.ID, "")
			} else if reference.Kind == domain.MessageTargetKindWorkflow {
				v.error("send_message.invalid_target", path+".sendMessage.target", "Workflow Builder edit scopes are not Workflow message destinations", node.ID, "")
			} else {
				staticTargetKind = reference.Kind
			}
		}
	}
	if hasScope {
		scopeSchema := v.validateValueExpr(
			path+".sendMessage.scope",
			*config.Scope,
			node,
			referenceContext{activationOutputUnavailable: true},
			"",
		)
		requiredScope := guaranteedSchema(Schema{Type: TypeScope})
		if scopeSchema.Type != "" && !valueExprAssignable(*config.Scope, scopeSchema, requiredScope) {
			v.error("send_message.scope_type", path+".sendMessage.scope", "scope must resolve to a required scope value", node.ID, "")
		}
		if config.Scope.Value != nil {
			if scope, err := domain.NormalizeMessageScopeValue(*config.Scope.Value); err == nil {
				staticScopeKind = scope.Kind
			}
		}
	}
	v.validateSendMessageResponder(path, node)
	v.validateSendMessageCollaborationScopes(path, node)
	v.validateTemplate(path+".sendMessage.text", config.Text, node)
	v.validateRetry(path+".sendMessage.retry", effectiveActionRetry(v.document, config.Retry), node.ID)
	if config.Completion == "" {
		v.error("send_message.completion_required", path+".sendMessage.completion", "completion is required", node.ID, "")
	} else if !authoringMapContains(compilerAuthoringCapabilities.CompletionModes, config.Completion) {
		v.error("send_message.invalid_completion", path+".sendMessage.completion", "completion must be sent or callback", node.ID, "")
	} else if config.Completion == CompletionSent {
		if len(config.CallbackOutput) != 0 {
			v.error("send_message.callback_output_not_allowed", path+".sendMessage.callbackOutput", "callbackOutput requires callback completion", node.ID, "")
		}
		if config.hasWorkScope() {
			v.error("send_message.work_scope_not_allowed", path+".sendMessage.workScope", "workScope requires callback completion", node.ID, "")
		}
		if config.hasRelatedScopes() {
			v.error("send_message.related_scopes_not_allowed", path+".sendMessage.relatedScopes", "relatedScopes requires callback completion", node.ID, "")
		}
		if config.WorkItems != nil {
			v.error("send_message.work_items_not_allowed", path+".sendMessage.workItems", "workItems requires callback completion", node.ID, "")
		}
		if config.Responder != nil {
			v.error("send_message.responder_not_allowed", path+".sendMessage.responder", "responder requires callback completion", node.ID, "")
		}
	} else if config.Completion == CompletionCallback {
		if len(config.CallbackOutput) == 0 && !config.hasWorkScope() && config.WorkItems == nil {
			v.error("send_message.callback_output_required", path+".sendMessage.callbackOutput", "callback completion requires typed callbackOutput, workScope, or workItems", node.ID, "")
		}
		v.validateSchemaMap(path+".sendMessage.callbackOutput", config.CallbackOutput, node.ID)
		v.rejectAuthoredWorkItemsSchemas(path+".sendMessage.callbackOutput", config.CallbackOutput, node.ID)
	}
	v.validateSendMessageWorkItems(path, node)
	if _, reserved := config.CallbackOutput[sendMessageOutputMessageID]; reserved {
		v.error("send_message.system_output_reserved", path+".sendMessage.callbackOutput."+sendMessageOutputMessageID, "messageId is reserved for compiler-owned delivery output", node.ID, "")
	}
	if _, reserved := config.CallbackOutput[sendMessageOutputThreadScope]; reserved {
		v.error("send_message.system_output_reserved", path+".sendMessage.callbackOutput."+sendMessageOutputThreadScope, "threadScope is reserved for compiler-owned delivery output", node.ID, "")
	}
	if config.hasWorkScope() {
		if _, reserved := config.CallbackOutput[sendMessageOutputWorkScope]; reserved {
			v.error("send_message.system_output_reserved", path+".sendMessage.callbackOutput."+sendMessageOutputWorkScope, "workScope is reserved for compiler-owned Agent work-scope output", node.ID, "")
		}
	}
	if _, reserved := config.CallbackOutput[sendMessageOutputWorkItems]; reserved {
		v.error("send_message.system_output_reserved", path+".sendMessage.callbackOutput."+sendMessageOutputWorkItems, "workItems is reserved for compiler-owned WorkItem output", node.ID, "")
	}
	if config.StartThread {
		if config.Completion != CompletionSent {
			v.error("send_message.start_thread_completion", path+".sendMessage.startThread", "startThread requires sent completion", node.ID, "")
		}
		if staticTargetKind != "" && staticTargetKind != domain.MessageTargetKindChannel {
			v.error("send_message.start_thread_destination", path+".sendMessage.target", "startThread requires a Channel root destination", node.ID, "")
		}
		if staticScopeKind != "" && staticScopeKind != domain.MessageScopeKindChannel {
			v.error("send_message.start_thread_destination", path+".sendMessage.scope", "startThread requires a Channel root destination", node.ID, "")
		}
	}
}

func (v *validator) validateSendMessageResponder(path string, node *Node) {
	responder := node.SendMessage.Responder
	if responder == nil || responder.IsZero() {
		return
	}
	if fixed := responder.fixed(); fixed != nil {
		responderSchema := v.validateValueExpr(
			path+".sendMessage.responder",
			*fixed,
			node,
			referenceContext{activationOutputUnavailable: true},
			"",
		)
		requiredAgent := guaranteedSchema(Schema{Type: TypeAgent})
		if responderSchema.Type != "" && responderSchema.Type != TypeResponder &&
			!valueExprAssignable(*fixed, responderSchema, requiredAgent) {
			v.error("send_message.responder_type", path+".sendMessage.responder", "responder must resolve to a required agent or responder value", node.ID, "")
		}
		return
	}
	if selected := responder.selectOne(); selected != nil {
		v.validateSendMessageSelectOne(path+".sendMessage.responder.selectOne", *selected, node)
		if strategy := responder.Strategy; strategy != "" && !ValidSelectOneStrategy(string(strategy)) {
			v.error("send_message.responder_select_one_strategy", path+".sendMessage.responder", "responder.selectOne strategy is invalid (expected \"ordered\" or \"random\")", node.ID, "")
		}
	}
}

func (v *validator) validateSendMessageWorkItems(path string, node *Node) {
	config := node.SendMessage
	if config.WorkItems == nil {
		return
	}
	allowedSchema := v.validateValueExpr(
		path+".sendMessage.workItems.allowedResponders",
		config.WorkItems.AllowedResponders,
		node,
		referenceContext{activationOutputUnavailable: true},
		"",
	)
	requiredAgents := guaranteedSchema(Schema{Type: TypeArray, Items: &Schema{Type: TypeAgent}})
	if allowedSchema.Type != "" && !valueExprAssignable(config.WorkItems.AllowedResponders, allowedSchema, requiredAgents) {
		v.error("send_message.allowed_responders_type", path+".sendMessage.workItems.allowedResponders", "allowedResponders must resolve to a required array<agent> value", node.ID, "")
	}
	if config.WorkItems.AllowedResponders.Value != nil {
		if values, ok := (*config.WorkItems.AllowedResponders.Value).([]any); ok && len(values) == 0 {
			v.error("send_message.allowed_responders_empty", path+".sendMessage.workItems.allowedResponders", "allowedResponders must not be empty", node.ID, "")
		}
	}
}

// validateSendMessageSelectOne validates the ordered candidate pool declared by
// `sendMessage.responder.selectOne`. The pool must resolve to a required
// array<agent>, must not be statically provably empty, and a statically known
// literal must not exceed MaxAllowedResponders. A dynamic (reference or
// expression) pool is validated against the same bound by runtime normalize,
// so a ref cannot bypass the contract that an authored literal enforces.
func (v *validator) validateSendMessageSelectOne(path string, selected ValueExpr, node *Node) {
	poolSchema := v.validateValueExpr(
		path,
		selected,
		node,
		referenceContext{activationOutputUnavailable: true},
		"",
	)
	requiredAgents := guaranteedSchema(Schema{Type: TypeArray, Items: &Schema{Type: TypeAgent}})
	if poolSchema.Type != "" && !valueExprAssignable(selected, poolSchema, requiredAgents) {
		v.error("send_message.responder_select_one_type", path, "responder.selectOne must resolve to a required array<agent> value", node.ID, "")
	}
	if selected.Value != nil {
		values, ok := (*selected.Value).([]any)
		if !ok {
			v.error("send_message.responder_select_one_literal", path, "responder.selectOne literal must be an array of agents", node.ID, "")
			return
		}
		if len(values) == 0 {
			v.error("send_message.responder_select_one_empty", path, "responder.selectOne must not be empty", node.ID, "")
		}
		if len(values) > MaxAllowedResponders {
			v.error(
				"send_message.responder_select_one_upper_bound",
				path,
				fmt.Sprintf("responder.selectOne must not contain more than %d candidates", MaxAllowedResponders),
				node.ID,
				"",
			)
		}
		for index, raw := range values {
			agent, ok := raw.(string)
			if !ok {
				v.error(
					"send_message.responder_select_one_element_type",
					path+"["+strconv.Itoa(index)+"]",
					"each responder.selectOne candidate must resolve to an agent value",
					node.ID,
					"",
				)
				continue
			}
			if strings.TrimSpace(agent) == "" {
				v.error(
					"send_message.responder_select_one_element_blank",
					path+"["+strconv.Itoa(index)+"]",
					"each responder.selectOne candidate must not be blank",
					node.ID,
					"",
				)
			}
		}
		if node.SendMessage.Responder.Strategy.Normalized() == SelectOneStrategyRandom && len(uniqueNonBlankCandidates(values)) < 2 {
			v.error(
				"send_message.responder_select_one_random_pool_too_small",
				path,
				"responder.selectOne strategy \"random\" requires at least two deduplicated candidates",
				node.ID,
				"",
			)
		}
	}
}

// uniqueNonBlankCandidates returns the deduplicated, non-blank string candidate
// values of a statically-known selectOne literal.
func uniqueNonBlankCandidates(values []any) []string {
	seen := make(map[string]struct{}, len(values))
	deduped := make([]string, 0, len(values))
	for _, raw := range values {
		value, ok := raw.(string)
		if !ok {
			continue
		}
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		if _, exists := seen[trimmed]; exists {
			continue
		}
		seen[trimmed] = struct{}{}
		deduped = append(deduped, trimmed)
	}
	return deduped
}

func (v *validator) validateSendMessageCollaborationScopes(path string, node *Node) {
	config := node.SendMessage
	requiredScope := guaranteedSchema(Schema{Type: TypeScope})
	if config.hasWorkScope() && config.WorkScope != nil {
		workScopeSchema := v.validateValueExpr(
			path+".sendMessage.workScope",
			*config.WorkScope,
			node,
			referenceContext{activationOutputUnavailable: true},
			"",
		)
		if workScopeSchema.Type != "" && !valueExprAssignable(*config.WorkScope, workScopeSchema, requiredScope) {
			v.error("send_message.work_scope_type", path+".sendMessage.workScope", "workScope must be null or resolve to a required scope value", node.ID, "")
		}
	}
	for index, relatedScope := range config.RelatedScopes.Items {
		relatedScopePath := path + ".sendMessage.relatedScopes[" + strconv.Itoa(index) + "]"
		relatedScopeSchema := v.validateValueExpr(
			relatedScopePath,
			relatedScope,
			node,
			referenceContext{activationOutputUnavailable: true},
			"",
		)
		if relatedScopeSchema.Type != "" && !valueExprAssignable(relatedScope, relatedScopeSchema, requiredScope) {
			v.error("send_message.related_scope_type", relatedScopePath, "each relatedScopes item must resolve to a required scope value", node.ID, "")
		}
	}
	if config.RelatedScopes.Array != nil {
		relatedScopesSchema := v.validateValueExpr(
			path+".sendMessage.relatedScopes",
			*config.RelatedScopes.Array,
			node,
			referenceContext{activationOutputUnavailable: true},
			"",
		)
		requiredScopes := guaranteedSchema(Schema{Type: TypeArray, Items: &Schema{Type: TypeScope}})
		if relatedScopesSchema.Type != "" && !valueExprAssignable(*config.RelatedScopes.Array, relatedScopesSchema, requiredScopes) {
			v.error("send_message.related_scopes_type", path+".sendMessage.relatedScopes", "relatedScopes must resolve to a required array<scope> value", node.ID, "")
		}
	}
	if config.hasRelatedScopes() && !config.hasWorkScope() {
		v.error("send_message.work_scope_required", path+".sendMessage.workScope", "relatedScopes requires workScope to declare the Agent's working scope", node.ID, "")
	}
}
