package sema

import (
	"fmt"
	"strconv"
	"strings"

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

type Validator struct {
	analyser        *Analyser
	currentFunction *symbols.Symbol
	errors          []error
}

func (a *Analyser) NewValidator() *Validator {
	return &Validator{analyser: a}
}

func (v *Validator) errorf(node parser.Node, format string, args ...any) {
	v.errors = append(v.errors, shared.NewError(node.GetLoc(), format, args...))
}

func (v *Validator) ValidateModule(root *parser.RootNode) {
	v.validateNode(root)
}

func (v *Validator) Errors() []error {
	return v.errors
}

func (v *Validator) validateNode(node parser.Node) {
	switch n := node.(type) {

	case *parser.RootNode:
		for _, stmt := range n.Body {
			v.validateNode(stmt)
		}

	case *parser.ImportNode, *parser.ModuleNode:

	case *parser.FunctionDefNode:
		prev := v.currentFunction
		v.currentFunction = n.Symbol

		if n.Body != nil {
			v.validateNode(n.Body)
		} else {
			if n.ExternFrom == "" {
				v.errorf(n, "function declaration missing body or extern")
				return
			}
		}

		v.currentFunction = prev

	case *parser.BlockNode:
		for _, stmt := range n.Body {
			switch s := stmt.(type) {
			case *parser.DeclarationNode:
				v.finaliseDeclaration(s)
			default:
				v.validateNode(s)
			}
		}

	case *parser.DeclarationNode:
		v.finaliseDeclaration(n)

	case *parser.AssignmentNode:
		v.validateAssignment(n)

	case *parser.PointerAssignmentNode:
		v.validatePointerAssignment(n)

	case *parser.IndexAssignmentNode:
		v.validateIndexAssignment(n)

	case *parser.IfNode:
		v.validateIf(n)

	case *parser.ForNode:
		v.validateNode(n.Body)

	case *parser.ControlKeywordNode:
		if n.ReturnValue != nil {
			v.validateReturn(n)
		}

	case parser.ExpressionNode:
		v.validateExpr(n)

	case *parser.TypeAliasNode:
		// pass

	default:
		panic(fmt.Sprintf("unhandled node type %T", n))
	}
}

func (v *Validator) finaliseDeclaration(n *parser.DeclarationNode) {
	if n.Value == nil {
		return
	}

	if n.TypeNode != nil {
		declared := v.analyser.resolveTypeNode(n.TypeNode)
		n.Value = v.validateExprWithExpected(n.Value, declared)

		n.Symbol.Type = declared
		return
	}

	v.validateExpr(n.Value)

	valueType := n.Value.GetType()

	if types.IsUntyped(valueType) {
		valueType = types.DefaultUntyped(valueType)
	}

	n.Symbol.Type = valueType
}

func (v *Validator) validateAssignment(n *parser.AssignmentNode) {
	if !v.validateLValue(n.Assignee) {
		return
	}

	v.validateExpr(n.Assignee)

	lhsType := n.Assignee.GetType()
	n.Value = v.validateExprWithExpected(n.Value, lhsType)
}

func (v *Validator) validatePointerAssignment(n *parser.PointerAssignmentNode) {
	if !v.validateLValue(n.Assignee) {
		return
	}

	v.validateExpr(n.Assignee)

	lhsType := n.Assignee.GetType()
	n.Value = v.validateExprWithExpected(n.Value, lhsType)
}

func (v *Validator) validateLValue(expr parser.ExpressionNode) bool {
	switch e := expr.(type) {

	case *parser.IdentifierNode:
		if !e.Symbol.Mutable {
			v.errorf(e, "cannot assign to immutable symbol")
			return false
		}

	case *parser.UnaryOpNode:
		if e.Op == parser.UnaryOpDereference {
			if ptrType, ok := e.Operand.GetType().(types.PointerType); ok {
				if ptrType.Base.Equals(types.PrimitiveVoid) {
					v.errorf(e, "cannot assign to dereferenced void pointer")
					return false
				}
			}
			v.validateExpr(e.Operand)
			return true
		}
		v.errorf(expr, "invalid assignment target")
		return false

	default:

		v.errorf(expr, "invalid assignment target!")
		return false
	}

	return true
}

func (v *Validator) validateIndexAssignment(n *parser.IndexAssignmentNode) {
	v.validateExpr(n.Assignee)
	v.validateExpr(n.Index)

	containerType := n.Assignee.GetType()

	indexType := n.Index.GetType()
	if !types.IsInteger(indexType) {
		v.errorf(n, "index must be integer")
		return
	}

	var base types.Type
	switch t := containerType.(type) {
	case types.SliceType:
		if t.Size != -1 {
			if idxLit, ok := n.Index.(*parser.IntegerLiteralNode); ok {
				idx, _ := strconv.Atoi(idxLit.Value)
				if idx < 0 || idx >= t.Size {
					v.errorf(n, "index %d out of bounds for slice of size %d", idx, t.Size)
				}
			}
		}
		base = t.Base
	case types.PointerType:
		base = t.Base
	default:
		v.errorf(n, "cannot index into non-slice type")
		return
	}

	n.Value = v.validateExprWithExpected(n.Value, base)
}

func (v *Validator) validateIf(n *parser.IfNode) {
	v.validateExpr(n.IfBranch.Condition)
	if !n.IfBranch.Condition.GetType().Equals(types.PrimitiveBool) {
		v.errorf(n, "if condition must be bool")
	}

	v.validateNode(n.IfBranch.Node)

	for _, elif := range n.ElseIfBranches {
		v.validateExpr(elif.Condition)
		if !elif.Condition.GetType().Equals(types.PrimitiveBool) {
			v.errorf(n, "elseif condition must be bool")
		}
		v.validateNode(elif.Node)
	}

	if n.ElseBranch != nil {
		v.validateNode(n.ElseBranch)
	}
}

func (v *Validator) validateReturn(n *parser.ControlKeywordNode) {
	if v.currentFunction == nil {
		v.errorf(n, "return outside of function")
		return
	}
	expected := v.currentFunction.Signature.ReturnType
	n.ReturnValue = v.validateExprWithExpected(n.ReturnValue, expected)
}

func (v *Validator) validateExpr(node parser.ExpressionNode) {
	if _, ok := node.GetType().(types.ErrorType); ok {
		v.errorf(node, "uncaught error type in expression")
		return
	}

	switch n := node.(type) {

	case *parser.FunctionCallNode:
		if n.Symbol == nil || n.Symbol.Kind != symbols.SymbolKindFunction {
			v.errorf(n, "not callable")
			return
		}

		sig := n.Symbol.Signature

		if !sig.Variadic && len(n.Args) != len(sig.Parameters) {
			v.errorf(n, "wrong number of arguments")
			return
		}

		for i, arg := range n.Args {
			if i >= len(sig.Parameters) {
				v.validateExpr(arg)
				continue
			}

			paramType := sig.Parameters[i]
			n.Args[i] = v.validateExprWithExpected(arg, paramType)
		}

	case *parser.CastNode:
		v.validateExpr(n.Operand)

		if !n.Operand.GetType().CanCastTo(n.Type) {
			v.errorf(n, "invalid cast")
		}

	case *parser.UnaryOpNode:
		v.validateExpr(n.Operand)

		operandType := n.Operand.GetType()

		switch n.Op {

		case parser.UnaryOpLogicalNot:
			if !operandType.Equals(types.PrimitiveBool) {
				v.errorf(n, "operator not requires bool")
			}

		case parser.UnaryOpNegate:
			if !types.IsNumeric(operandType) {
				v.errorf(n, "operator - requires numeric type")
			}

		case parser.UnaryOpReference:

		case parser.UnaryOpDereference:
			if _, ok := operandType.(types.PointerType); !ok {
				v.errorf(n, "cannot dereference non-pointer type")
			}
			if ptrType, ok := operandType.(types.PointerType); ok {
				if ptrType.Base.Equals(types.PrimitiveVoid) {
					v.errorf(n, "cannot dereference void pointer")
				}
			}

		case parser.UnaryOpSliceLen:
			switch operandType.(type) {
			case types.SliceType:
				// OK
			default:
				v.errorf(n, "[] operator requires slice or pointer")
			}

		default:
			v.errorf(n, "unknown unary operator")
		}

	case *parser.BinaryOpNode:
		v.validateExpr(n.Operand1)
		v.validateExpr(n.Operand2)

		t1 := n.Operand1.GetType()
		t2 := n.Operand2.GetType()

		switch n.Op {

		case parser.BinaryOpAdd,
			parser.BinaryOpSubtract,
			parser.BinaryOpMultiply,
			parser.BinaryOpDivide,
			parser.BinaryOpModulo:

			if !types.IsNumeric(t1) || !types.IsNumeric(t2) {
				v.errorf(n, "arithmetic operators require numeric operands")
			}

		case parser.BinaryOpLogicalAnd,
			parser.BinaryOpLogicalOr:

			if !t1.Equals(types.PrimitiveBool) ||
				!t2.Equals(types.PrimitiveBool) {
				v.errorf(n, "logical operators require bool operands")
			}

		case parser.BinaryOpEqual,
			parser.BinaryOpNotEqual:

			if !t1.CanCoerceTo(t2) && !t2.CanCoerceTo(t1) {
				v.errorf(n, "incompatible types for comparison: %v and %v", t1, t2)
			}

		case parser.BinaryOpLess,
			parser.BinaryOpLessEqual,
			parser.BinaryOpGreater,
			parser.BinaryOpGreaterEqual:

			if !types.IsNumeric(t1) || !types.IsNumeric(t2) {
				v.errorf(n, "ordering operators require numeric operands")
			}

		default:
			v.errorf(n, "unknown binary operator")
		}

	case *parser.IndexExprNode:
		v.validateExpr(n.Subject)
		v.validateExpr(n.Index)

		indexType := n.Index.GetType()
		if !types.IsInteger(indexType) {
			v.errorf(n, "index must be integer")
		}

		switch n.Subject.GetType().(type) {
		case types.SliceType:
			if t, ok := n.Subject.GetType().(types.SliceType); ok {
				if t.Size != -1 {
					if idxLit, ok := n.Index.(*parser.IntegerLiteralNode); ok {
						idx, _ := strconv.Atoi(idxLit.Value)
						if idx < 0 || idx >= t.Size {
							v.errorf(n, "index %d out of bounds for slice of size %d", idx, t.Size)
						}
					}
				}
			} else {
				panic("unreachable")
			}

		case types.PointerType:
			// OK
		default:
			v.errorf(n, "cannot index into non-slice type")
		}

	case *parser.FieldAccessNode:
		v.validateExpr(n.Subject)
		subjectType := n.Subject.GetType()
		structType, ok := subjectType.(types.StructType)
		if !ok {
			v.errorf(n, "cannot access field of non-struct type")
			return
		}
		fieldIndex := -1
		for i, field := range structType.Fields {
			if field.L == n.Field.Name {
				fieldIndex = i
				break
			}
		}
		if fieldIndex == -1 {
			v.errorf(n, "struct type has no field %q", n.Field)
			return
		}
		n.SetType(structType.Fields[fieldIndex].R)

	case *parser.IfExprNode:
		v.validateExpr(n.IfBranch.Condition)

		if !n.IfBranch.Condition.GetType().Equals(types.PrimitiveBool) {
			v.errorf(n, "if expression condition must be bool")
		}

		v.validateExpr(n.IfBranch.Node)

		for _, elif := range n.ElseIfBranches {
			v.validateExpr(elif.Condition)

			if !elif.Condition.GetType().Equals(types.PrimitiveBool) {
				v.errorf(n, "elseif condition must be bool")
			}

			v.validateExpr(elif.Node)
		}

		if n.ElseBranch != nil {
			v.validateExpr(n.ElseBranch)
		}

	case *parser.GivenExprNode:
		v.validateNode(n.Block)
		v.validateExpr(n.FinalExpr)

	case *parser.SliceLiteralNode:
		var common types.Type = nil

		if len(n.Elements) == 0 {
			n.Type = types.SliceType{
				Base: types.PrimitiveVoid,
				Size: 0,
			}
			break
		}

		for _, el := range n.Elements {
			v.validateExpr(el)

			if common == nil {
				common = el.GetType()
			} else {
				common = types.PromoteNumeric(common, el.GetType())
				if _, isErr := common.(types.ErrorType); isErr {
					v.errorf(n, "slice element type mismatch")
					return
				}
			}
		}

		if types.IsUntyped(common) {
			common = types.DefaultUntyped(common)
		}

		n.Type = types.SliceType{
			Base: common,
			Size: len(n.Elements),
		}

		for i, el := range n.Elements {
			if !el.GetType().Equals(common) {
				n.Elements[i] = v.createCast(el, common)
			}
		}

	case *parser.StructLiteralNode:
		if n.Symbol != nil {
			v.validateStructLiteralWithExpected(n, n.Symbol.TypeInfo)
			break
		}

		fields := make([]shared.Pair[string, types.Type], 0, len(n.Fields))
		for i, field := range n.Fields {
			v.validateExpr(field.R)
			n.Fields[i].R = field.R
			fields = append(fields, shared.Pair[string, types.Type]{
				L: field.L,
				R: field.R.GetType(),
			})
		}

		n.SetType(types.StructType{Fields: fields})

	case *parser.IntegerLiteralNode,
		*parser.FloatLiteralNode,
		*parser.BoolLiteralNode,
		*parser.StringLiteralNode,
		*parser.CharLiteralNode,
		*parser.NilLiteralNode,
		*parser.IdentifierNode,
		*parser.ModuleAccessNode,
		*parser.SizeOfNode:
		// nothing to validate

	default:
		panic(fmt.Sprintf("unhandled expression type %T", n))
	}

	if types.IsUntyped(node.GetType()) {
		node.SetType(types.DefaultUntyped(node.GetType()))
	}
}

func (v *Validator) createCast(node parser.ExpressionNode, target types.Type) parser.ExpressionNode {
	if node.GetType().Equals(target) {
		return node
	}

	switch n := node.(type) {
	case *parser.IntegerLiteralNode:
		n.SetType(target)
		return n
	case *parser.FloatLiteralNode:
		n.SetType(target)
		return n
	}

	return &parser.CastNode{
		Operand: node,
		Type:    target,
	}
}

func (v *Validator) validateExprWithExpected(node parser.ExpressionNode, expected types.Type) parser.ExpressionNode {
	switch n := node.(type) {
	case *parser.IntegerLiteralNode:
		n.SetType(types.UntypedInt{})
	case *parser.FloatLiteralNode:
		n.SetType(types.UntypedFloat{})
	}

	if n, ok := node.(*parser.StructLiteralNode); ok {
		v.validateStructLiteralWithExpected(n, expected)
		return n
	}

	if n, ok := node.(*parser.SliceLiteralNode); ok {
		v.validateSliceLiteralWithExpected(n, expected)
		return n
	}

	v.validateExpr(node)

	got := node.GetType()
	if !got.CanCoerceTo(expected) {
		v.errorf(node, "cannot assign %v to %v", got, expected)
		return node
	}

	if !got.Equals(expected) {
		return v.createCast(node, expected)
	}

	return node
}

func (v *Validator) validateSliceLiteralWithExpected(n *parser.SliceLiteralNode, expected types.Type) {
	sliceType, ok := expected.(types.SliceType)
	if !ok {
		v.validateExpr(n)
		got := n.GetType()
		if !got.CanCoerceTo(expected) {
			v.errorf(n, "cannot assign %v to %v", got, expected)
			return
		}
		if !got.Equals(expected) {
			n.SetType(expected)
		}
		return
	}

	if sliceType.Size != -1 && len(n.Elements) != sliceType.Size {
		v.errorf(n, "cannot assign [%v, %d] to [%v, %d]", sliceType.Base, len(n.Elements), sliceType.Base, sliceType.Size)
	}

	for i, element := range n.Elements {
		n.Elements[i] = v.validateExprWithExpected(element, sliceType.Base)
	}

	n.SetType(types.SliceType{
		Base: sliceType.Base,
		Size: len(n.Elements),
	})
}

func (v *Validator) validateStructLiteralWithExpected(n *parser.StructLiteralNode, expected types.Type) {
	structType, ok := expected.(types.StructType)
	if !ok {
		v.errorf(n, "cannot use struct literal for non-struct type %v", expected)
		n.SetType(types.ErrorType{})
		return
	}

	fieldTypes := make(map[string]types.Type, len(structType.Fields))
	for _, field := range structType.Fields {
		fieldTypes[field.L] = field.R
	}

	seen := make(map[string]struct{}, len(n.Fields))
	for i, field := range n.Fields {
		expectedFieldType, exists := fieldTypes[field.L]
		if !exists {
			v.errorf(n, "unknown field %q in struct literal", field.L)
			continue
		}

		if _, dup := seen[field.L]; dup {
			v.errorf(n, "duplicate field %q in struct literal", field.L)
			continue
		}
		seen[field.L] = struct{}{}

		n.Fields[i].R = v.validateExprWithExpected(field.R, expectedFieldType)
	}

	missingFields := []string{}
	for _, field := range structType.Fields {
		if _, ok := seen[field.L]; !ok {
			missingFields = append(missingFields, field.L)
		}
	}
	if len(missingFields) > 0 {
		v.errorf(n, "missing fields in struct literal: %v", strings.Join(missingFields, ", "))
	}

	n.SetType(structType)
}