package plan

// omittedBindings lowers whole-payload pass-through into the same explicit
// field mappings used by authored bindings. It succeeds only when every
// target field that can be populated from the source is type-compatible and
// every required target field is guaranteed by the source contract.
func omittedBindings(source map[string]Schema, root string, target map[string]Schema) (map[string]ValueExpr, bool) {
	bindings := map[string]ValueExpr{}
	for _, name := range sortedSchemaKeys(target) {
		targetSchema := target[name]
		sourceSchema, exists := source[name]
		if !exists {
			if schemaRequired(targetSchema) {
				return nil, false
			}
			continue
		}
		if !schemaAssignable(sourceSchema, targetSchema) {
			return nil, false
		}
		if schemaRequired(targetSchema) && !schemaGuaranteed(sourceSchema) {
			return nil, false
		}
		bindings[name] = ValueExpr{Ref: root + "." + name}
	}
	return bindings, true
}

func schemaRequired(schema Schema) bool {
	return schema.Required != nil && *schema.Required && schema.Default == nil
}

func schemaGuaranteed(schema Schema) bool {
	return schema.Required != nil && *schema.Required || schema.Default != nil
}

// sourcePayloadContract returns the complete typed payload exposed by a
// source port. Structural nodes preserve their explicit graph payload;
// ordinary actions never fall back to their activation input.
func sourcePayloadContract(node *Node, port string) (map[string]Schema, string, bool) {
	if node == nil {
		return nil, "", false
	}
	switch node.Type {
	case NodeFork, NodeSwitch:
		return node.Input, "input", true
	case NodeFanOut:
		if port == "item" {
			return FanOutItemContract(), "item", true
		}
	case NodeSendMessage:
		if port == "completed" && node.SendMessage != nil {
			return sendMessageCompletedOutput(node.SendMessage), "output", node.SendMessage.Completion == CompletionCallback || node.SendMessage.StartThread
		}
	case NodeJoin:
		if port == "completed" && node.Join != nil {
			return node.Join.Result, "output", true
		}
	case NodeLoop:
		if node.Loop == nil {
			break
		}
		switch port {
		case "body":
			return node.Loop.State, "loop.state", true
		case "done":
			return node.Loop.outputContract(), "loop.state", true
		}
	}
	return nil, "", false
}

func sendMessageCompletedOutput(config *SendMessageConfig) map[string]Schema {
	if config == nil {
		return nil
	}
	output := make(map[string]Schema, len(config.CallbackOutput)+4)
	for name, schema := range config.CallbackOutput {
		output[name] = schema
	}
	if config.StartThread {
		output[sendMessageOutputMessageID] = guaranteedSchema(Schema{Type: TypeString})
		output[sendMessageOutputThreadScope] = guaranteedSchema(Schema{Type: TypeScope})
	}
	if config.hasWorkScope() {
		output[sendMessageOutputWorkScope] = guaranteedSchema(Schema{Type: TypeScope})
	}
	if config.WorkItems != nil {
		output[sendMessageOutputWorkItems] = guaranteedSchema(Schema{Type: TypeWorkItems})
	}
	return output
}

const (
	sendMessageOutputMessageID   = "messageId"
	sendMessageOutputThreadScope = "threadScope"
	sendMessageOutputWorkScope   = "workScope"
	sendMessageOutputWorkItems   = "workItems"

	WorkItemFieldKey            = "key"
	WorkItemFieldDependsOn      = "dependsOn"
	WorkItemFieldWorkScope      = "workScope"
	WorkItemFieldRelatedScopes  = "relatedScopes"
	WorkItemFieldResponder      = "responder"
	WorkItemFieldInstruction    = "instruction"
	FanOutItemFieldDependencies = "dependencies"
)

// WorkItemContract returns a fresh copy of the closed per-item payload
// contract. It is shared by compiler edge typing and callback/runtime
// normalized validation; callers may safely mutate the returned map.
func WorkItemContract() map[string]Schema {
	required := func(schema Schema) Schema { return guaranteedSchema(schema) }
	return map[string]Schema{
		WorkItemFieldKey: required(Schema{Type: TypeString}),
		WorkItemFieldDependsOn: {
			Type:    TypeArray,
			Items:   &Schema{Type: TypeString},
			Default: []any{},
		},
		WorkItemFieldWorkScope: required(Schema{Type: TypeScope}),
		WorkItemFieldRelatedScopes: required(Schema{
			Type:  TypeArray,
			Items: &Schema{Type: TypeScope},
		}),
		WorkItemFieldResponder:   required(Schema{Type: TypeResponder}),
		WorkItemFieldInstruction: required(Schema{Type: TypeString}),
	}
}

// FanOutItemContract extends the Agent-authored closed WorkItem with the one
// compiler/runtime-owned dependency context exposed only on item.* edges.
// Agents cannot forge dependencies in callback output.
func FanOutItemContract() map[string]Schema {
	contract := WorkItemContract()
	contract[FanOutItemFieldDependencies] = guaranteedSchema(Schema{Type: TypeObject})
	return contract
}

func isWorkItemContractField(name string) bool {
	switch name {
	case WorkItemFieldKey, WorkItemFieldDependsOn, WorkItemFieldWorkScope,
		WorkItemFieldRelatedScopes, WorkItemFieldResponder, WorkItemFieldInstruction,
		FanOutItemFieldDependencies:
		return true
	default:
		return false
	}
}

// isWorkItemCamelContractField identifies the WorkItem vocabulary that the
// pre-v4 artifact contract ID grammar could not represent. It is kept
// separate from the snake-case-compatible fields so version feature detection
// can reject this newly accepted vocabulary in v1-v3 envelopes.
func isWorkItemCamelContractField(name string) bool {
	switch name {
	case WorkItemFieldDependsOn, WorkItemFieldWorkScope, WorkItemFieldRelatedScopes:
		return true
	default:
		return false
	}
}
