package sema

import (
	"crypto/sha256"
	"fmt"
	"strings"

	"github.com/marzeq/qk/parser"
	"github.com/marzeq/qk/symbols"
	"github.com/marzeq/qk/types"
)

type genericFunctionInfo struct {
	node            *parser.FunctionDefNode
	root            *parser.RootNode
	module          string
	specializations map[string]*parser.FunctionDefNode
}

type genericValueInfo struct {
	node            *parser.DeclarationNode
	root            *parser.RootNode
	module          string
	trusted         bool
	specializations map[string]*parser.DeclarationNode
}

type genericAliasSpecialization struct {
	state    aliasState
	resolved types.Type
	symbol   *symbols.Symbol
}

type genericAliasInfo struct {
	node            *parser.TypeAliasNode
	root            *parser.RootNode
	module          string
	parameters      []types.TypeParameter
	specializations map[string]*genericAliasSpecialization
}

func genericOwner(module, name string) string {
	return module + ":" + name
}

func specializationKey(arguments []types.Type) string {
	parts := make([]string, len(arguments))
	for i, argument := range arguments {
		parts[i] = types.Identity(argument)
	}
	return strings.Join(parts, ",")
}

func SpecializationName(module, name string, arguments []types.Type) string {
	identity := genericOwner(module, name) + "<" + specializationKey(arguments) + ">"
	return name + "$" + fmt.Sprintf("%x", sha256.Sum256([]byte(identity)))
}

func specializationDisplayName(name string, arguments []types.Type) string {
	parts := make([]string, len(arguments))
	for i, argument := range arguments {
		parts[i] = argument.String()
	}
	return name + "<" + strings.Join(parts, ", ") + ">"
}

func (a *Analyser) makeGenericParameters(name string, nodes []parser.GenericParameterNode) []types.TypeParameter {
	previous := a.typeParameterBindings
	bindings := make(map[string]types.Type, len(previous)+len(nodes))
	for name, binding := range previous {
		bindings[name] = binding
	}
	a.typeParameterBindings = bindings
	parameters := make([]types.TypeParameter, len(nodes))
	owner := genericOwner(a.currentMod, name)
	for i, node := range nodes {
		parameter := types.TypeParameter{Owner: owner, Name: node.Name, Index: i}
		parameters[i] = parameter
		bindings[node.Name] = parameter
	}
	for i, node := range nodes {
		if node.Constraint == nil {
			continue
		}
		constraint := a.resolveTypeNode(node.Constraint)
		if types.HasError(constraint) {
			parameters[i].Constraint = types.ErrorType{}
			bindings[node.Name] = parameters[i]
			continue
		}
		if _, ok := types.Underlying(constraint).(types.TraitType); !ok {
			a.errorf(node, "type parameter constraint must be a trait type, got %v", constraint)
			constraint = types.ErrorType{}
		}
		parameters[i].Constraint = constraint
		bindings[node.Name] = parameters[i]
	}
	a.typeParameterBindings = previous
	return parameters
}

func typeArgumentBindings(parameters []types.TypeParameter, arguments []types.Type) map[string]types.Type {
	bindings := make(map[string]types.Type, len(parameters))
	for i, parameter := range parameters {
		bindings[parameter.Name] = arguments[i]
	}
	return bindings
}

func typeSubstitutionBindings(parameters []types.TypeParameter, arguments []types.Type) map[string]types.Type {
	bindings := make(map[string]types.Type, len(parameters))
	for i, parameter := range parameters {
		bindings[parameter.Key()] = arguments[i]
	}
	return bindings
}

func hasTypeParameters(arguments []types.Type) bool {
	for _, argument := range arguments {
		if types.HasTypeParameter(argument) {
			return true
		}
	}
	return false
}

func substituteFunctionSignature(signature *symbols.FunctionSignature, substitutions map[string]types.Type) *symbols.FunctionSignature {
	if signature == nil {
		return nil
	}
	result := *signature
	result.Parameters = make([]types.Type, len(signature.Parameters))
	for i, parameter := range signature.Parameters {
		result.Parameters[i] = types.Substitute(parameter, substitutions)
	}
	result.ReturnType = types.Substitute(signature.ReturnType, substitutions)
	result.VariadicElement = types.Substitute(signature.VariadicElement, substitutions)
	return &result
}

func dependentGenericFunctionSymbol(template *symbols.Symbol, arguments []types.Type) *symbols.Symbol {
	result := *template
	result.Template = false
	result.TemplateSymbol = template
	result.TypeArguments = append([]types.Type(nil), arguments...)
	result.GenericParameters = nil
	result.Signature = substituteFunctionSignature(
		template.Signature,
		typeSubstitutionBindings(template.GenericParameters, arguments),
	)
	return &result
}

func staticTraitViewForGenericType(t types.Type) *types.StaticTraitView {
	access := types.TraitReceiverValue
	if pointer, ok := t.(types.PointerType); ok {
		t = pointer.Base
		access = types.TraitReceiverPointer
		if pointer.Mutable {
			access = types.TraitReceiverMutablePointer
		}
	}
	parameter, ok := t.(types.TypeParameter)
	if !ok || parameter.Constraint == nil {
		return nil
	}
	trait, ok := types.Underlying(parameter.Constraint).(types.TraitType)
	if !ok {
		return nil
	}
	return &types.StaticTraitView{Trait: trait, Access: access}
}

func genericTypeParameterBase(t types.Type) (types.TypeParameter, bool) {
	if pointer, ok := t.(types.PointerType); ok {
		t = pointer.Base
	}
	parameter, ok := t.(types.TypeParameter)
	return parameter, ok
}

func (a *Analyser) resolveGenericArguments(nodes []parser.TypeNode) []types.Type {
	return a.resolveGenericArgumentsAt(nodes, false)
}

func (a *Analyser) resolveGenericArgumentsAt(nodes []parser.TypeNode, indirect bool) []types.Type {
	arguments := make([]types.Type, len(nodes))
	for i, node := range nodes {
		arguments[i] = a.resolveTypeNodeAt(node, indirect)
	}
	return arguments
}

func (a *Analyser) checkGenericArguments(node parser.Node, parameters []types.TypeParameter, arguments []types.Type) bool {
	return a.checkGenericArgumentsFrom(node, parameters, arguments, 0)
}

func (a *Analyser) checkGenericArgumentsFrom(node parser.Node, parameters []types.TypeParameter, arguments []types.Type, start int) bool {
	if len(parameters) != len(arguments) {
		a.errorf(node, "parameterized binding expects %d type arguments, got %d", len(parameters), len(arguments))
		return false
	}
	valid := true
	for i, parameter := range parameters {
		if types.HasError(arguments[i]) || types.HasError(parameter.Constraint) {
			valid = false
			continue
		}
		if i < start {
			continue
		}
		if parameter.Constraint == nil {
			continue
		}
		if argumentParameter, ok := arguments[i].(types.TypeParameter); ok {
			if argumentParameter.Constraint != nil && argumentParameter.Constraint.Equals(parameter.Constraint) {
				continue
			}
			a.errorf(node, "type parameter %s does not satisfy constraint %v for %s", argumentParameter.Name, parameter.Constraint, parameter.Name)
			valid = false
			continue
		}
		trait, ok := types.Underlying(parameter.Constraint).(types.TraitType)
		if !ok {
			valid = false
			continue
		}
		target := types.TraitPointerType{Trait: trait}
		probe := types.PointerType{Base: arguments[i]}
		if _, conforms := a.structuralConformance(probe, target, node); !conforms {
			a.errorf(node, "type argument %v does not satisfy constraint %v for %s", arguments[i], parameter.Constraint, parameter.Name)
			valid = false
		}
	}
	return valid
}

func (a *Analyser) withDefinitionContext(module string, trusted bool, bindings map[string]types.Type, action func()) {
	previousScope, previousModule := a.current, a.currentMod
	previousTrusted, previousImports := a.currentTrustedStandardLibrary, a.currentImports
	previousImportAliases := a.currentImportAliases
	previousAliases, previousBindings := a.aliases, a.typeParameterBindings
	previousTraitContext := a.resolvingTraitMethodTypes
	a.currentMod = module
	a.current = a.modules[module].Scope
	a.currentTrustedStandardLibrary = trusted
	a.currentImports = a.importsByModule[module]
	a.currentImportAliases = a.importAliasesByModule[module]
	a.aliases = a.aliasesByModule[module]
	a.typeParameterBindings = bindings
	a.resolvingTraitMethodTypes = false
	action()
	a.current, a.currentMod = previousScope, previousModule
	a.currentTrustedStandardLibrary, a.currentImports = previousTrusted, previousImports
	a.currentImportAliases = previousImportAliases
	a.aliases, a.typeParameterBindings = previousAliases, previousBindings
	a.resolvingTraitMethodTypes = previousTraitContext
}

func (a *Analyser) specializeGenericAlias(info *genericAliasInfo, arguments []types.Type, use parser.Node, indirect bool) *symbols.Symbol {
	if info == nil {
		a.errorf(use, "parameterized type metadata is unavailable")
		return nil
	}
	if !a.checkGenericArguments(use, info.parameters, arguments) {
		return nil
	}
	key := specializationKey(arguments)
	displayName := specializationDisplayName(info.node.Name, arguments)
	if existing := info.specializations[key]; existing != nil {
		if existing.state == aliasResolving {
			if indirect {
				if _, ok := info.node.Type.(*parser.TraitTypeNode); ok && !info.node.Transparent {
					return &symbols.Symbol{
						Name: displayName, Kind: symbols.SymbolKindType,
						TypeInfo: types.TraitType{Module: info.module, Name: displayName},
					}
				}
				return &symbols.Symbol{
					Name: displayName,
					Kind: symbols.SymbolKindType,
					TypeInfo: &types.AliasRef{
						Module: info.module, Name: displayName,
						Target: &existing.resolved,
					},
				}
			}
			a.errorf(use, "circular parameterized type definition detected")
			return nil
		}
		return existing.symbol
	}
	specialization := &genericAliasSpecialization{state: aliasResolving}
	info.specializations[key] = specialization
	bindings := typeArgumentBindings(info.parameters, arguments)
	a.withDefinitionContext(info.module, a.modules[info.module].TrustedStandardLibrary, bindings, func() {
		resolved := a.resolveTypeNodeAt(info.node.Type, indirect)
		if trait, ok := resolved.(types.TraitType); ok && !info.node.Transparent {
			trait.Module, trait.Name = info.module, displayName
			resolved = trait
			a.nominalTraits[trait.Module+":"+trait.Name] = trait
			a.registerTraitDefaults(info.node, trait, info.root)
		} else if !info.node.Transparent {
			resolved = types.DefinedType{
				Module: info.module, Name: displayName, Underlying: resolved,
				GenericName: info.node.Name, TypeArguments: append([]types.Type(nil), arguments...),
			}
			a.concreteTypes[info.module+":"+displayName] = resolved
		}
		specialization.resolved = resolved
		underlying := types.Underlying(resolved)
		if types.HasError(resolved) {
			// Type resolution already reported the primary error.
		} else if info.node.Transparent && types.IsOpaque(resolved) {
			a.errorf(info.node, "opaque parameterized type %q cannot be a transparent alias", info.node.Name)
		} else if _, trait := underlying.(types.TraitType); trait {
			if info.node.Transparent {
				a.errorf(info.node, "parameterized trait %q cannot be a transparent alias", info.node.Name)
			}
		} else if _, opaque := underlying.(types.OpaqueType); !opaque && !types.IsComplete(underlying) {
			a.errorf(info.node, "parameterized type %q contains an incomplete type by value", info.node.Name)
		}
	})
	specialization.state = aliasResolved
	specialization.symbol = &symbols.Symbol{
		Name: displayName, Kind: symbols.SymbolKindType, TypeInfo: specialization.resolved,
		Public: info.node.Pub, DefinitionModule: info.module, TemplateSymbol: info.node.Symbol,
		TypeArguments: append([]types.Type(nil), arguments...),
	}
	return specialization.symbol
}

func (a *Analyser) specializeGenericValue(template *symbols.Symbol, arguments []types.Type, use parser.Node) *symbols.Symbol {
	info := a.genericValues[template]
	if info == nil || !a.checkGenericArguments(use, template.GenericParameters, arguments) {
		return nil
	}
	key := specializationKey(arguments)
	if existing := info.specializations[key]; existing != nil {
		return existing.Symbol
	}
	clone := parser.CloneSyntax(info.node).(*parser.DeclarationNode)
	clone.GenericParameters = nil
	clone.Name = SpecializationName(info.module, template.Name, arguments)
	info.specializations[key] = clone
	info.root.Body = append(info.root.Body, clone)
	bindings := typeArgumentBindings(template.GenericParameters, arguments)
	a.withDefinitionContext(info.module, info.trusted, bindings, func() {
		a.collectGlobalVariable(clone)
		if clone.Symbol != nil {
			clone.Symbol.TemplateSymbol = template
			clone.Symbol.TypeArguments = append([]types.Type(nil), arguments...)
		}
		if clone.Value != nil {
			a.visitExpression(clone.Value)
		}
	})
	return clone.Symbol
}

func (a *Analyser) specializeGenericFunction(template *symbols.Symbol, arguments []types.Type, use parser.Node) *parser.FunctionDefNode {
	if template.TraitDefaultTemplate != nil {
		arguments = append([]types.Type{template.TraitDefaultSelf}, arguments...)
		template = template.TraitDefaultTemplate
	}
	info := a.genericFunctions[template]
	checkFrom := 0
	if template.TraitDefault {
		checkFrom = 1
	}
	if info == nil || !a.checkGenericArgumentsFrom(use, template.GenericParameters, arguments, checkFrom) {
		return nil
	}
	key := specializationKey(arguments)
	if existing := info.specializations[key]; existing != nil {
		return existing
	}
	substitutions := typeSubstitutionBindings(template.GenericParameters, arguments)
	name := SpecializationName(info.module, template.Name, arguments)
	instanceSymbol := a.substituteSymbol(template, substitutions, use)
	instanceSymbol.Name = name
	instanceSymbol.Template = false
	instanceSymbol.TemplateSymbol = template
	instanceSymbol.TypeArguments = append([]types.Type(nil), arguments...)
	instanceSymbol.GenericParameters = nil

	instance := &parser.FunctionDefNode{Symbol: instanceSymbol}
	info.specializations[key] = instance

	symbolsByOriginal := map[*symbols.Symbol]*symbols.Symbol{template: instanceSymbol}
	mapSymbol := func(original *symbols.Symbol) *symbols.Symbol {
		return a.instantiateFunctionSymbol(original, substitutions, symbolsByOriginal, use)
	}
	cloned := parser.CloneSemantic(info.node, func(t types.Type) types.Type {
		return a.instantiateType(t, substitutions, use)
	}, mapSymbol, func(node parser.Node) {
		a.instantiateSemanticNode(node)
		if lambda, ok := node.(*parser.LambdaNode); ok && lambda.Function != nil && lambda.Function.Symbol != nil {
			lambda.Function.Symbol.Name += "__" + instanceSymbol.Name
			lambda.Function.Name = lambda.Function.Symbol.Name
		}
	}).(*parser.FunctionDefNode)
	cloned.GenericParameters = nil
	cloned.MethodOwnerGenericParameters = nil
	cloned.MethodOwnerType = nil
	cloned.GenericInstance = true
	cloned.Name = name
	cloned.Symbol = instanceSymbol
	if cloned.MethodOwner != "" {
		cloned.MethodOwner = ""
	}
	*instance = *cloned
	info.root.Body = append(info.root.Body, instance)
	return instance
}

func (a *Analyser) specializeGenericFunctionSymbol(template *symbols.Symbol, arguments []types.Type, use parser.Node) *symbols.Symbol {
	if a.genericFunctions[template] == nil {
		checkFrom := 0
		if template.TraitDefault {
			checkFrom = 1
		}
		if !a.checkGenericArgumentsFrom(use, template.GenericParameters, arguments, checkFrom) {
			return nil
		}
		return dependentGenericFunctionSymbol(template, arguments)
	}
	instance := a.specializeGenericFunction(template, arguments, use)
	if instance == nil {
		return nil
	}
	return instance.Symbol
}

func (a *Analyser) instantiateSemanticNode(node parser.Node) {
	cast, ok := node.(*parser.CastNode)
	if !ok {
		return
	}
	targetType := cast.Type
	if cast.Checked {
		targetType = cast.CheckedType
	}
	if types.HasError(cast.Operand.GetType()) || types.HasError(targetType) {
		cast.SetType(types.ErrorType{})
		return
	}
	if cast.TraitConversion {
		if target, ok := a.traitPointer(targetType); ok {
			methods, conforms := a.structuralConformance(cast.Operand.GetType(), target, cast)
			if !conforms {
				a.errorf(cast, "type %v does not conform to %v", cast.ConcreteType, target.Trait)
			} else {
				pointer, _ := concretePointer(cast.Operand.GetType())
				cast.ConcreteType = pointer.Base
				cast.TraitMethods = methods
			}
		}
	}
	if cast.GenericAssertion {
		target := targetType
		source := cast.Operand.GetType()
		if specializedExplicitCast(cast.Operand, source, target) {
			// A cast involving symbolic types may have been conservatively
			// attributed as an assertion. Once both sides are concrete, use the
			// same cast semantics as the equivalent non-generic expression.
			cast.GenericAssertion = false
			cast.AssertionMatches = false
		} else {
			cast.AssertionMatches = source.Equals(target)
		}
	}
	if cast.StaticTraitView == nil {
		return
	}
	source := cast.Operand.GetType()
	pointer, _ := types.Underlying(source).(types.PointerType)
	_, _, sourceIsPointer, _ := methodOwnerIdentity(source)
	var concrete types.Type
	var probe types.PointerType
	if cast.StaticTraitView.Access == types.TraitReceiverValue {
		if sourceIsPointer {
			concrete = pointer.Base
			probe = pointer
		} else {
			concrete = source
			probe = types.PointerType{Base: source}
		}
	} else {
		concrete = pointer.Base
		probe = pointer
	}
	target := types.TraitPointerType{
		Trait:   cast.StaticTraitView.Trait,
		Mutable: cast.StaticTraitView.Access == types.TraitReceiverMutablePointer,
	}
	methods, conforms := a.structuralConformance(probe, target, cast)
	cast.ConcreteType = concrete
	cast.AssertionMatches = conforms
	cast.TraitMethods = methods
}

func specializedExplicitCast(operand parser.ExpressionNode, source, target types.Type) bool {
	if !types.CanExplicitCast(source, target) {
		return false
	}

	// Array-to-slice casts borrow their operand. Type compatibility alone is
	// insufficient: the equivalent concrete cast must also have a suitable
	// place from which to borrow.
	if _, ok := types.Underlying(source).(types.ArrayType); ok {
		if slice, ok := types.Underlying(target).(types.SliceType); ok {
			if slice.Mutable {
				return mutableArrayPlace(operand)
			}
			switch node := operand.(type) {
			case *parser.IdentifierNode:
				return node.Symbol != nil && node.Symbol.Kind == symbols.SymbolKindVariable
			case *parser.UnaryOpNode:
				if node.Op != parser.UnaryOpDereference {
					return false
				}
				pointer, ok := types.Underlying(node.Operand.GetType()).(types.PointerType)
				return ok && !pointer.Base.Equals(types.PrimitiveVoid)
			default:
				return false
			}
		}
	}

	return true
}

func (a *Analyser) substituteSymbol(
	original *symbols.Symbol,
	substitutions map[string]types.Type,
	use parser.Node,
) *symbols.Symbol {
	result := *original
	result.Type = a.instantiateType(original.Type, substitutions, use)
	result.GenericOrigin = a.instantiateType(original.GenericOrigin, substitutions, use)
	result.Signature = a.instantiateFunctionSignature(original.Signature, substitutions, use)
	result.TypeInfo = a.instantiateType(original.TypeInfo, substitutions, use)
	result.MethodOwnerType = a.instantiateType(original.MethodOwnerType, substitutions, use)
	result.TypeArguments = make([]types.Type, len(original.TypeArguments))
	for i, argument := range original.TypeArguments {
		result.TypeArguments[i] = a.instantiateType(argument, substitutions, use)
	}
	if original.StaticTraitView != nil {
		view := *original.StaticTraitView
		if trait, ok := a.instantiateType(view.Trait, substitutions, use).(types.TraitType); ok {
			view.Trait = trait
		}
		result.StaticTraitView = &view
	}
	if original.TraitRequirement {
		if trait, ok := a.instantiateType(original.RequirementTrait, substitutions, use).(types.TraitType); ok {
			result.RequirementTrait = trait
		}
	}
	return &result
}

func (a *Analyser) instantiateFunctionSignature(
	signature *symbols.FunctionSignature,
	substitutions map[string]types.Type,
	use parser.Node,
) *symbols.FunctionSignature {
	if signature == nil {
		return nil
	}
	result := *signature
	result.Parameters = make([]types.Type, len(signature.Parameters))
	for i, parameter := range signature.Parameters {
		result.Parameters[i] = a.instantiateType(parameter, substitutions, use)
	}
	result.ReturnType = a.instantiateType(signature.ReturnType, substitutions, use)
	result.VariadicElement = a.instantiateType(signature.VariadicElement, substitutions, use)
	return &result
}

func (a *Analyser) instantiateType(t types.Type, substitutions map[string]types.Type, use parser.Node) types.Type {
	t = types.Substitute(t, substitutions)
	if t == nil {
		return nil
	}
	switch current := t.(type) {
	case types.DefinedType:
		for i, argument := range current.TypeArguments {
			current.TypeArguments[i] = a.instantiateType(argument, substitutions, use)
		}
		if current.GenericName != "" && !hasTypeParameters(current.TypeArguments) {
			module := a.modules[current.Module]
			if module != nil {
				if template, ok := module.Scope.Resolve(current.GenericName); ok {
					if info := a.genericAliases[template]; info != nil {
						if specialized := a.specializeGenericAlias(info, current.TypeArguments, use, false); specialized != nil {
							return specialized.TypeInfo
						}
					}
				}
			}
		}
		current.Underlying = a.instantiateType(current.Underlying, substitutions, use)
		return current
	case types.PointerType:
		current.Base = a.instantiateType(current.Base, substitutions, use)
		return current
	case types.SliceType:
		current.Base = a.instantiateType(current.Base, substitutions, use)
		return current
	case types.ArrayType:
		current.Base = a.instantiateType(current.Base, substitutions, use)
		return current
	case types.StructType:
		for i := range current.Fields {
			current.Fields[i].R = a.instantiateType(current.Fields[i].R, substitutions, use)
		}
		return current
	case types.UnionType:
		for i := range current.Fields {
			current.Fields[i].R = a.instantiateType(current.Fields[i].R, substitutions, use)
		}
		return current
	case types.FunctionType:
		for i, parameter := range current.Parameters {
			current.Parameters[i] = a.instantiateType(parameter, substitutions, use)
		}
		current.ReturnType = a.instantiateType(current.ReturnType, substitutions, use)
		current.VariadicElement = a.instantiateType(current.VariadicElement, substitutions, use)
		return current
	case types.MultipleReturnType:
		for i, item := range current.Types {
			current.Types[i] = a.instantiateType(item, substitutions, use)
		}
		return current
	case types.TraitPointerType:
		if trait, ok := a.instantiateType(current.Trait, substitutions, use).(types.TraitType); ok {
			current.Trait = trait
		}
		return current
	case types.TraitType:
		for i := range current.Methods {
			for j := range current.Methods[i].GenericParameters {
				current.Methods[i].GenericParameters[j].Constraint = a.instantiateType(
					current.Methods[i].GenericParameters[j].Constraint,
					substitutions,
					use,
				)
			}
			for j, parameter := range current.Methods[i].Parameters {
				current.Methods[i].Parameters[j] = a.instantiateType(parameter, substitutions, use)
			}
			current.Methods[i].ReturnType = a.instantiateType(current.Methods[i].ReturnType, substitutions, use)
		}
		return current
	default:
		return current
	}
}

func (a *Analyser) instantiateFunctionSymbol(
	original *symbols.Symbol,
	substitutions map[string]types.Type,
	cloned map[*symbols.Symbol]*symbols.Symbol,
	use parser.Node,
) *symbols.Symbol {
	if existing := cloned[original]; existing != nil {
		return existing
	}
	substituted := a.substituteSymbol(original, substitutions, use)
	if original.TraitRequirement {
		receiver := substituted.Signature.Parameters[0]
		if types.HasError(receiver) || types.HasError(substituted.RequirementTrait) {
			cloned[original] = substituted
			return substituted
		}
		var probe types.PointerType
		if original.RequirementAccess == types.TraitReceiverValue {
			probe = types.PointerType{Base: receiver}
		} else {
			var ok bool
			probe, ok = types.Underlying(receiver).(types.PointerType)
			if !ok {
				a.errorf(use, "cannot instantiate trait receiver %v", receiver)
				cloned[original] = substituted
				return substituted
			}
		}
		target := types.TraitPointerType{
			Trait:   substituted.RequirementTrait,
			Mutable: original.RequirementAccess == types.TraitReceiverMutablePointer,
		}
		methods, conforms := a.structuralConformance(probe, target, use)
		if !conforms || original.RequirementSlot >= len(methods) {
			a.errorf(use, "type %v does not implement %v", probe.Base, target.Trait)
			cloned[original] = substituted
			return substituted
		}
		method := methods[original.RequirementSlot]
		if method.Template {
			if len(substituted.TypeArguments) != len(method.GenericParameters) || hasTypeParameters(substituted.TypeArguments) {
				a.errorf(use, "cannot resolve parameterized trait method %q type arguments", method.Name)
				cloned[original] = substituted
				return substituted
			}
			specialization := a.specializeGenericFunction(method, substituted.TypeArguments, use)
			if specialization == nil || specialization.Symbol == nil {
				cloned[original] = substituted
				return substituted
			}
			method = specialization.Symbol
		}
		cloned[original] = method
		return method
	}
	if original.TemplateSymbol != nil {
		if !hasTypeParameters(substituted.TypeArguments) {
			switch original.TemplateSymbol.Kind {
			case symbols.SymbolKindFunction:
				specialization := a.specializeGenericFunction(
					original.TemplateSymbol,
					substituted.TypeArguments,
					use,
				)
				if specialization != nil && specialization.Symbol != nil {
					cloned[original] = specialization.Symbol
					return specialization.Symbol
				}
			case symbols.SymbolKindVariable:
				if specialization := a.specializeGenericValue(
					original.TemplateSymbol,
					substituted.TypeArguments,
					use,
				); specialization != nil {
					cloned[original] = specialization
					return specialization
				}
			case symbols.SymbolKindType:
				if specialization := a.specializeGenericAlias(
					a.genericAliases[original.TemplateSymbol],
					substituted.TypeArguments,
					use,
					false,
				); specialization != nil {
					cloned[original] = specialization
					return specialization
				}
			}
		}
		cloned[original] = substituted
		return substituted
	}
	if !a.isGlobalSymbol(original) {
		cloned[original] = substituted
		return substituted
	}
	return original
}

func (a *Analyser) isGlobalSymbol(symbol *symbols.Symbol) bool {
	for _, candidate := range a.universe.Symbols {
		if candidate == symbol {
			return true
		}
	}
	for _, module := range a.modules {
		for _, candidate := range module.Scope.Symbols {
			if candidate == symbol {
				return true
			}
		}
	}
	return false
}

func inferGenericArguments(
	parameters []types.TypeParameter,
	patterns, actuals []types.Type,
	typedVariadic, variadicExpansion bool,
) ([]types.Type, error) {
	return inferGenericArgumentsForCall(parameters, patterns, actuals, nil, nil, typedVariadic, variadicExpansion, false)
}

func inferGenericArgumentsPartial(
	parameters []types.TypeParameter,
	patterns, actuals []types.Type,
	typedVariadic, variadicExpansion bool,
) ([]types.Type, error) {
	return inferGenericArgumentsForCall(parameters, patterns, actuals, nil, nil, typedVariadic, variadicExpansion, true)
}

// preserveResolvedTypeArguments keeps explicit or argument-derived choices
// fixed while inference fills parameter slots that are still symbolic.
func preserveResolvedTypeArguments(parameters []types.TypeParameter, fixed, inferred []types.Type) []types.Type {
	for i := range inferred {
		if i < len(fixed) && i < len(parameters) && fixed[i] != nil && !fixed[i].Equals(parameters[i]) {
			inferred[i] = fixed[i]
		}
	}
	return inferred
}

func inferGenericArgumentsWithResult(
	parameters []types.TypeParameter,
	patterns, actuals []types.Type,
	resultPattern, expectedResult types.Type,
	typedVariadic, variadicExpansion bool,
) ([]types.Type, error) {
	return inferGenericArgumentsForCall(
		parameters, patterns, actuals, resultPattern, expectedResult,
		typedVariadic, variadicExpansion, false,
	)
}

func inferGenericArgumentsForCall(
	parameters []types.TypeParameter,
	patterns, actuals []types.Type,
	resultPattern, expectedResult types.Type,
	typedVariadic, variadicExpansion, allowIncomplete bool,
) ([]types.Type, error) {
	inferred := make(map[string]types.Type)
	for i, actual := range actuals {
		if len(patterns) == 0 {
			break
		}
		patternIndex := i
		if patternIndex >= len(patterns) {
			if !typedVariadic {
				break
			}
			patternIndex = len(patterns) - 1
		}
		pattern := patterns[patternIndex]
		if typedVariadic && patternIndex == len(patterns)-1 && !variadicExpansion {
			if slice, ok := types.Underlying(pattern).(types.SliceType); ok {
				pattern = slice.Base
			}
		}
		if err := inferGenericType(pattern, actual, inferred); err != nil {
			return nil, err
		}
	}
	if resultPattern != nil && expectedResult != nil {
		inferMissingGenericType(resultPattern, expectedResult, inferred)
	}
	arguments := make([]types.Type, len(parameters))
	for i, parameter := range parameters {
		argument, ok := inferred[parameter.Key()]
		if !ok {
			if allowIncomplete {
				arguments[i] = parameter
				continue
			}
			return nil, fmt.Errorf("cannot infer type argument %s", parameter.Name)
		}
		if types.HasUntyped(argument) {
			if allowIncomplete {
				arguments[i] = parameter
				continue
			}
			return nil, fmt.Errorf("untyped numeric value cannot infer type argument %s", parameter.Name)
		}
		arguments[i] = argument
	}
	return arguments, nil
}

func inferMissingGenericType(pattern, actual types.Type, inferred map[string]types.Type) {
	if parameter, ok := pattern.(types.TypeParameter); ok {
		if previous, exists := inferred[parameter.Key()]; !exists || types.HasUntyped(previous) {
			if !types.HasUntyped(actual) {
				inferred[parameter.Key()] = actual
			}
		}
		return
	}
	switch pattern := pattern.(type) {
	case types.PointerType:
		if actual, ok := actual.(types.PointerType); ok {
			inferMissingGenericType(pattern.Base, actual.Base, inferred)
		}
	case types.SliceType:
		if actual, ok := actual.(types.SliceType); ok {
			inferMissingGenericType(pattern.Base, actual.Base, inferred)
		}
	case types.ArrayType:
		if actual, ok := actual.(types.ArrayType); ok && pattern.Length == actual.Length {
			inferMissingGenericType(pattern.Base, actual.Base, inferred)
		}
	case types.DefinedType:
		actual, ok := actual.(types.DefinedType)
		if !ok || pattern.Module != actual.Module || pattern.GenericName == "" || pattern.GenericName != actual.GenericName ||
			len(pattern.TypeArguments) != len(actual.TypeArguments) {
			return
		}
		for i := range pattern.TypeArguments {
			inferMissingGenericType(pattern.TypeArguments[i], actual.TypeArguments[i], inferred)
		}
	case types.FunctionType:
		actual, ok := actual.(types.FunctionType)
		if !ok || len(pattern.Parameters) != len(actual.Parameters) {
			return
		}
		for i := range pattern.Parameters {
			inferMissingGenericType(pattern.Parameters[i], actual.Parameters[i], inferred)
		}
		inferMissingGenericType(pattern.ReturnType, actual.ReturnType, inferred)
	case types.MultipleReturnType:
		actual, ok := actual.(types.MultipleReturnType)
		if !ok || len(pattern.Types) != len(actual.Types) {
			return
		}
		for i := range pattern.Types {
			inferMissingGenericType(pattern.Types[i], actual.Types[i], inferred)
		}
	}
}

func inferGenericType(pattern, actual types.Type, inferred map[string]types.Type) error {
	if parameter, ok := pattern.(types.TypeParameter); ok {
		if _, ambiguous := actual.(types.SequenceType); ambiguous {
			return nil
		}
		if types.HasUntyped(actual) {
			if previous, exists := inferred[parameter.Key()]; !exists || types.HasUntyped(previous) {
				inferred[parameter.Key()] = actual
			}
			// A typed argument may replace or already have replaced this
			// placeholder; validation then performs the numeric coercion.
			return nil
		}
		if previous, exists := inferred[parameter.Key()]; exists {
			if types.HasUntyped(previous) {
				inferred[parameter.Key()] = actual
				return nil
			}
			if !previous.Equals(actual) {
				return fmt.Errorf("conflicting inferred types for %s: %v and %v", parameter.Name, previous, actual)
			}
		}
		inferred[parameter.Key()] = actual
		return nil
	}
	switch pattern := pattern.(type) {
	case types.PointerType:
		actual, ok := actual.(types.PointerType)
		if !ok {
			return nil
		}
		return inferGenericType(pattern.Base, actual.Base, inferred)
	case types.SliceType:
		switch actual := actual.(type) {
		case types.SliceType:
			return inferGenericType(pattern.Base, actual.Base, inferred)
		case types.SequenceType:
			return inferGenericType(pattern.Base, actual.Base, inferred)
		}
		return nil
	case types.ArrayType:
		switch actual := actual.(type) {
		case types.ArrayType:
			if pattern.Length == actual.Length {
				return inferGenericType(pattern.Base, actual.Base, inferred)
			}
		case types.SequenceType:
			if pattern.Length == actual.Length {
				return inferGenericType(pattern.Base, actual.Base, inferred)
			}
		}
		return nil
	case types.DefinedType:
		actual, ok := actual.(types.DefinedType)
		if !ok || pattern.Module != actual.Module || pattern.GenericName == "" || pattern.GenericName != actual.GenericName ||
			len(pattern.TypeArguments) != len(actual.TypeArguments) {
			return nil
		}
		for i := range pattern.TypeArguments {
			if err := inferGenericType(pattern.TypeArguments[i], actual.TypeArguments[i], inferred); err != nil {
				return err
			}
		}
	case types.FunctionType:
		actual, ok := actual.(types.FunctionType)
		if !ok || len(pattern.Parameters) != len(actual.Parameters) {
			return nil
		}
		for i := range pattern.Parameters {
			if err := inferGenericType(pattern.Parameters[i], actual.Parameters[i], inferred); err != nil {
				return err
			}
		}
		return inferGenericType(pattern.ReturnType, actual.ReturnType, inferred)
	}
	return nil
}

func expressionTypes(expressions []parser.ExpressionNode) []types.Type {
	result := make([]types.Type, len(expressions))
	for i, expression := range expressions {
		result[i] = expression.GetType()
	}
	return result
}

func (a *Attributor) attributeGenericSpecialization(template *symbols.Symbol, arguments []types.Type, use parser.Node) *parser.FunctionDefNode {
	specialization := a.analyser.specializeGenericFunction(template, arguments, use)
	return specialization
}

func (a *Attributor) enterSpecialization(symbol *symbols.Symbol) func() {
	previous := a.analyser.typeParameterBindings
	if symbol != nil && symbol.TemplateSymbol != nil {
		a.analyser.typeParameterBindings = typeArgumentBindings(symbol.TemplateSymbol.GenericParameters, symbol.TypeArguments)
	}
	return func() {
		a.analyser.typeParameterBindings = previous
	}
}