package plan

import (
	"fmt"
	"strings"
)

// authoringCapabilityCatalog is the compiler-owned inventory of the public
// PlanVersion authoring vocabulary. Validation consumes this inventory, and
// the official workflow-plan-author Skill reference is tested for exact
// coverage of its non-legacy vocabulary. Adding an authorable type, field,
// node, mode, outcome, port, value/reference form, or expression feature
// therefore requires updating the reference in the same change.
type authoringCapabilityCatalog struct {
	ValueTypes          []ValueType
	LegacyValueTypes    []ValueType
	Nodes               []nodeAuthoringCapability
	CompletionModes     map[CompletionMode]ArtifactOpcode
	SwitchModes         map[SwitchMode]ArtifactOpcode
	JoinModes           map[JoinMode]ArtifactOpcode
	FanOutFailureModes  map[FanOutFailureMode]struct{}
	LoopModes           map[LoopMode]ArtifactOpcode
	EndOutcomes         map[EndOutcome]ArtifactOpcode
	ValueExpressions    []valueExpressionCapability
	ValueLiterals       []valueLiteralKind
	ExpressionOperators map[string]ArtifactExpressionOp
	ExpressionFunctions map[string]expressionFunctionCapability
	ExpressionLiterals  []expressionLiteralKind
	ArrayExpressionOp   ArtifactExpressionOp
	ReferenceSpaces     []referenceSpaceCapability
}

type nodeAuthoringCapability struct {
	Type               NodeType
	ConfigField        string
	ConfigPresent      func(*Node) bool
	Opcode             ArtifactOpcode
	SourcePorts        []string
	TargetPorts        []string
	DynamicSourcePorts bool
	DynamicTargetPorts bool
}

type valueExpressionKind string

const (
	valueExpressionRef     valueExpressionKind = "ref"
	valueExpressionLiteral valueExpressionKind = "value"
	valueExpressionExpr    valueExpressionKind = "expr"
)

type valueExpressionCapability struct {
	Kind    valueExpressionKind
	Present func(ValueExpr) bool
}

type valueLiteralKind string

const (
	valueLiteralString  valueLiteralKind = "string"
	valueLiteralNumber  valueLiteralKind = "number"
	valueLiteralBoolean valueLiteralKind = "boolean"
	valueLiteralObject  valueLiteralKind = "object"
	valueLiteralArray   valueLiteralKind = "array"
)

type expressionFunctionCapability struct {
	Opcode     ArtifactExpressionOp
	ResultType ValueType
	Arity      int
}

type expressionLiteralKind string

const (
	expressionLiteralString expressionLiteralKind = "string"
	expressionLiteralNumber expressionLiteralKind = "number"
	expressionLiteralTrue   expressionLiteralKind = "true"
	expressionLiteralFalse  expressionLiteralKind = "false"
	expressionLiteralNull   expressionLiteralKind = "null"
	expressionLiteralArray  expressionLiteralKind = "array"
)

type referenceSpace string

const (
	referencePlanInput        referenceSpace = "plan.input"
	referenceActivationInput  referenceSpace = "input"
	referenceActivationOutput referenceSpace = "output"
	referenceRunState         referenceSpace = "state"
	referenceLoopState        referenceSpace = "loop.state"
	referenceLoopIteration    referenceSpace = "loop.iteration"
	referenceLoopIsLast       referenceSpace = "loop.isLast"
	referenceJoinBranch       referenceSpace = "branches"
	referenceJoinWinnerBranch referenceSpace = "winner.branch"
	referenceJoinWinnerValue  referenceSpace = "winner.value"
	referenceFanOutItem       referenceSpace = "item"
	referenceJoinItems        referenceSpace = "items"
)

type referenceSpaceCapability struct {
	Reference referenceSpace
	LoadSpace ArtifactLoadSpace
}

// compilerAuthoringCapabilities is initialized once and is read-only after
// package initialization. Callers must never mutate its slices or maps.
var compilerAuthoringCapabilities = authoringCapabilityCatalog{
	ValueTypes: []ValueType{
		TypeChannel,
		TypeString,
		TypeEnum,
		TypeNumber,
		TypeBoolean,
		TypeMember,
		TypeDuration,
		TypeDatetime,
		TypeObject,
		TypeArray,
		TypeAgent,
		TypeResponder,
		TypeScope,
		TypeWorkItems,
	},
	// LegacyValueTypes remain parseable for existing Plans but are omitted
	// from the official generator reference and new examples.
	LegacyValueTypes: []ValueType{TypeChannel},
	Nodes: []nodeAuthoringCapability{
		{
			Type: NodeSendMessage, ConfigField: "sendMessage",
			ConfigPresent: func(node *Node) bool { return node.SendMessage != nil },
			SourcePorts:   []string{"completed", "failed"},
			TargetPorts:   []string{"in"},
		},
		{
			Type: NodeSetVariable, ConfigField: "setVariable",
			ConfigPresent: func(node *Node) bool { return node.SetVariable != nil },
			Opcode:        OpcodeSetVariable,
			SourcePorts:   []string{"completed", "failed"},
			TargetPorts:   []string{"in"},
		},
		{
			Type: NodeWait, ConfigField: "wait",
			ConfigPresent: func(node *Node) bool { return node.Wait != nil },
			Opcode:        OpcodeWait,
			SourcePorts:   []string{"completed", "failed", "timed_out"},
			TargetPorts:   []string{"in"},
		},
		{
			Type: NodeRequestApproval, ConfigField: "requestApproval",
			ConfigPresent: func(node *Node) bool { return node.RequestApproval != nil },
			Opcode:        OpcodeRequestApproval,
			SourcePorts:   []string{"approved", "rejected", "failed"},
			TargetPorts:   []string{"in"},
		},
		{
			Type: NodeSwitch, ConfigField: "switch",
			ConfigPresent:      func(node *Node) bool { return node.Switch != nil },
			DynamicSourcePorts: true,
			TargetPorts:        []string{"in"},
		},
		{
			Type: NodeFork, ConfigField: "fork",
			ConfigPresent:      func(node *Node) bool { return node.Fork != nil },
			Opcode:             OpcodeFork,
			DynamicSourcePorts: true,
			TargetPorts:        []string{"in"},
		},
		{
			Type: NodeFanOut, ConfigField: "fanOut",
			ConfigPresent: func(node *Node) bool { return node.FanOut != nil },
			Opcode:        OpcodeFanOut,
			SourcePorts:   []string{"item"},
			TargetPorts:   []string{"in"},
		},
		{
			Type: NodeJoin, ConfigField: "join",
			ConfigPresent:      func(node *Node) bool { return node.Join != nil },
			SourcePorts:        []string{"completed", "failed"},
			DynamicTargetPorts: true,
		},
		{
			Type: NodeLoop, ConfigField: "loop",
			ConfigPresent: func(node *Node) bool { return node.Loop != nil },
			SourcePorts:   []string{"body", "done", "failed"},
			TargetPorts:   []string{"enter", "continue", "break"},
		},
		{
			Type: NodeEnd, ConfigField: "end",
			ConfigPresent: func(node *Node) bool { return node.End != nil },
			TargetPorts:   []string{"in"},
		},
	},
	CompletionModes: map[CompletionMode]ArtifactOpcode{
		CompletionSent:     OpcodeSendMessageSent,
		CompletionCallback: OpcodeSendMessageCallback,
	},
	SwitchModes: map[SwitchMode]ArtifactOpcode{
		SwitchFirstMatch: OpcodeSwitchFirstMatch,
		SwitchAllMatch:   OpcodeSwitchAllMatch,
	},
	JoinModes: map[JoinMode]ArtifactOpcode{
		JoinAll: OpcodeJoinAll,
		JoinAny: OpcodeJoinAny,
	},
	FanOutFailureModes: map[FanOutFailureMode]struct{}{
		FanOutFailureCollect: {},
	},
	LoopModes: map[LoopMode]ArtifactOpcode{
		LoopCounted: OpcodeLoop,
	},
	EndOutcomes: map[EndOutcome]ArtifactOpcode{
		EndSucceeded: OpcodeEndSucceeded,
		EndFailed:    OpcodeEndFailed,
	},
	ValueExpressions: []valueExpressionCapability{
		{Kind: valueExpressionRef, Present: func(expression ValueExpr) bool { return strings.TrimSpace(expression.Ref) != "" }},
		{Kind: valueExpressionLiteral, Present: func(expression ValueExpr) bool { return expression.Value != nil }},
		{Kind: valueExpressionExpr, Present: func(expression ValueExpr) bool { return strings.TrimSpace(expression.Expr) != "" }},
	},
	ValueLiterals: []valueLiteralKind{
		valueLiteralString,
		valueLiteralNumber,
		valueLiteralBoolean,
		valueLiteralObject,
		valueLiteralArray,
	},
	ExpressionOperators: map[string]ArtifactExpressionOp{
		"!":  ArtifactOpNot,
		"&&": ArtifactOpAnd,
		"||": ArtifactOpOr,
		"==": ArtifactOpEqual,
		"!=": ArtifactOpNotEqual,
		"<":  ArtifactOpLess,
		"<=": ArtifactOpLessEqual,
		">":  ArtifactOpGreater,
		">=": ArtifactOpGreaterEq,
		"in": ArtifactOpIn,
	},
	ExpressionFunctions: map[string]expressionFunctionCapability{
		"contains":   {Opcode: ArtifactOpContains, ResultType: TypeBoolean, Arity: 2},
		"startsWith": {Opcode: ArtifactOpStartsWith, ResultType: TypeBoolean, Arity: 2},
		"endsWith":   {Opcode: ArtifactOpEndsWith, ResultType: TypeBoolean, Arity: 2},
		"length":     {Opcode: ArtifactOpLength, ResultType: TypeNumber, Arity: 1},
	},
	ExpressionLiterals: []expressionLiteralKind{
		expressionLiteralString,
		expressionLiteralNumber,
		expressionLiteralTrue,
		expressionLiteralFalse,
		expressionLiteralNull,
		expressionLiteralArray,
	},
	ArrayExpressionOp: ArtifactOpArray,
	ReferenceSpaces: []referenceSpaceCapability{
		{Reference: referencePlanInput, LoadSpace: ArtifactLoadPlanInput},
		{Reference: referenceActivationInput, LoadSpace: ArtifactLoadActivationInput},
		{Reference: referenceActivationOutput, LoadSpace: ArtifactLoadActivationOutput},
		{Reference: referenceRunState, LoadSpace: ArtifactLoadRunState},
		{Reference: referenceLoopState, LoadSpace: ArtifactLoadLoopState},
		{Reference: referenceLoopIteration, LoadSpace: ArtifactLoadLoopIteration},
		{Reference: referenceLoopIsLast, LoadSpace: ArtifactLoadLoopIsLast},
		{Reference: referenceJoinBranch, LoadSpace: ArtifactLoadJoinBranch},
		{Reference: referenceJoinWinnerBranch, LoadSpace: ArtifactLoadJoinWinnerBranch},
		{Reference: referenceJoinWinnerValue, LoadSpace: ArtifactLoadJoinWinnerValue},
		{Reference: referenceFanOutItem, LoadSpace: ArtifactLoadFanOutItem},
		{Reference: referenceJoinItems, LoadSpace: ArtifactLoadJoinItems},
	},
}

func authoringContains[T comparable](values []T, target T) bool {
	for _, value := range values {
		if value == target {
			return true
		}
	}
	return false
}

func authoringMapContains[K comparable, V any](values map[K]V, target K) bool {
	_, ok := values[target]
	return ok
}

func (catalog authoringCapabilityCatalog) nodeCapability(nodeType NodeType) (nodeAuthoringCapability, bool) {
	for _, capability := range catalog.Nodes {
		if capability.Type == nodeType {
			return capability, true
		}
	}
	return nodeAuthoringCapability{}, false
}

func (catalog authoringCapabilityCatalog) nodeConfig(node *Node) (string, map[string]bool, bool) {
	configs := make(map[string]bool, len(catalog.Nodes))
	for _, capability := range catalog.Nodes {
		configs[capability.ConfigField] = capability.ConfigPresent(node)
	}
	capability, ok := catalog.nodeCapability(node.Type)
	return capability.ConfigField, configs, ok
}

func (catalog authoringCapabilityCatalog) opcode(node *Node) (ArtifactOpcode, error) {
	if node == nil {
		return "", fmt.Errorf("node is nil")
	}
	capability, ok := catalog.nodeCapability(node.Type)
	if !ok {
		return "", fmt.Errorf("unsupported node type %q", node.Type)
	}
	if !capability.ConfigPresent(node) {
		return "", fmt.Errorf("node type %q requires %s config", node.Type, capability.ConfigField)
	}
	if capability.Opcode != "" {
		return capability.Opcode, nil
	}
	switch node.Type {
	case NodeSendMessage:
		if opcode, ok := catalog.CompletionModes[node.SendMessage.Completion]; ok {
			return opcode, nil
		}
		return "", fmt.Errorf("unsupported send_message completion %q", node.SendMessage.Completion)
	case NodeSwitch:
		if opcode, ok := catalog.SwitchModes[node.Switch.Mode]; ok {
			return opcode, nil
		}
		return "", fmt.Errorf("unsupported switch mode %q", node.Switch.Mode)
	case NodeJoin:
		if opcode, ok := catalog.JoinModes[node.Join.Mode]; ok {
			return opcode, nil
		}
		return "", fmt.Errorf("unsupported join mode %q", node.Join.Mode)
	case NodeLoop:
		if opcode, ok := catalog.LoopModes[node.Loop.Mode]; ok {
			return opcode, nil
		}
		return "", fmt.Errorf("unsupported loop mode %q", node.Loop.Mode)
	case NodeEnd:
		if opcode, ok := catalog.EndOutcomes[node.End.Outcome]; ok {
			return opcode, nil
		}
		return "", fmt.Errorf("unsupported end outcome %q", node.End.Outcome)
	default:
		return "", fmt.Errorf("node type %q has no compiler opcode", node.Type)
	}
}

func (catalog authoringCapabilityCatalog) presentValueExpressionKinds(expression ValueExpr) []valueExpressionKind {
	kinds := make([]valueExpressionKind, 0, 1)
	for _, capability := range catalog.ValueExpressions {
		if capability.Present(expression) {
			kinds = append(kinds, capability.Kind)
		}
	}
	return kinds
}

func (catalog authoringCapabilityCatalog) expressionOperator(symbol string) (ArtifactExpressionOp, bool) {
	opcode, ok := catalog.ExpressionOperators[symbol]
	return opcode, ok
}

func (catalog authoringCapabilityCatalog) valueLiteralKind(value any) (valueLiteralKind, bool) {
	var kind valueLiteralKind
	switch value.(type) {
	case string:
		kind = valueLiteralString
	case int, int8, int16, int32, int64,
		uint, uint8, uint16, uint32, uint64,
		float32, float64:
		kind = valueLiteralNumber
	case bool:
		kind = valueLiteralBoolean
	case map[string]any:
		kind = valueLiteralObject
	case []any:
		kind = valueLiteralArray
	default:
		return "", false
	}
	return kind, authoringContains(catalog.ValueLiterals, kind)
}

func (catalog authoringCapabilityCatalog) expressionFunction(name string) (expressionFunctionCapability, bool) {
	function, ok := catalog.ExpressionFunctions[name]
	return function, ok
}

func (catalog authoringCapabilityCatalog) supportsExpressionLiteral(kind expressionLiteralKind) bool {
	return authoringContains(catalog.ExpressionLiterals, kind)
}

func (catalog authoringCapabilityCatalog) referenceLoadSpace(reference referenceSpace) (ArtifactLoadSpace, bool) {
	for _, capability := range catalog.ReferenceSpaces {
		if capability.Reference == reference {
			return capability.LoadSpace, true
		}
	}
	return "", false
}
