package compiler

import (
	"fmt"
	"os"
	"sort"
	"strings"

	"c65gm/internal/preproc"
	"c65gm/internal/utils"
)

// ParamDirection represents parameter passing direction
type ParamDirection uint8

const (
	DirIn ParamDirection = 1 << iota
	DirOut
)

func (d ParamDirection) Has(flag ParamDirection) bool {
	return d&flag != 0
}

// FuncParam represents a function parameter
type FuncParam struct {
	Symbol    *Symbol
	Direction ParamDirection
}

// FuncDecl represents a function declaration
type FuncDecl struct {
	Name   string
	Params []*FuncParam
	Line   preproc.Line // Declaration location for warnings
}

// FuncGroup represents a set of functions that share a single FEND (share the same body)
type FuncGroup struct {
	Names []string // All function names in this group, in declaration order
}

// FunctionHandler manages function declarations and calls
type FunctionHandler struct {
	functions       []*FuncDecl
	currentFuncs    []string // stack of current function names (for nested scope)
	symTable        *SymbolTable
	labelStack      *LabelStack
	constStrHandler *ConstantStringHandler
	pragma          *preproc.Pragma

	// Absolute address tracking for overlap detection
	absoluteAddrs map[string]map[uint16]bool // funcName -> set of absolute addresses used
	callGraph     map[string][]string        // funcName -> list of functions it calls
	
	// Function usage tracking for unused function warnings
	calledFunctions map[string]bool // funcName -> true if function is called

	// Function groups for multi-FUNC-per-FEND tracking
	funcGroups  []*FuncGroup
	funcToGroup map[string]*FuncGroup
}

// NewFunctionHandler creates a new function handler
func NewFunctionHandler(st *SymbolTable, ls *LabelStack, csh *ConstantStringHandler, pragma *preproc.Pragma) *FunctionHandler {
	return &FunctionHandler{
		functions:       make([]*FuncDecl, 0),
		currentFuncs:    make([]string, 0),
		symTable:        st,
		labelStack:      ls,
		constStrHandler: csh,
		pragma:          pragma,
		absoluteAddrs:   make(map[string]map[uint16]bool),
		callGraph:       make(map[string][]string),
		calledFunctions: make(map[string]bool),
		funcGroups:      make([]*FuncGroup, 0),
		funcToGroup:     make(map[string]*FuncGroup),
	}
}

// HandleFuncDecl parses and processes a FUNC declaration
// Syntax: FUNC name ( param1 param2 ... )
// Or: FUNC name (void function)
// Returns the function name
func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) (string, error) {
	// Normalize parentheses and commas
	text := fixIntuitiveFuncs(line.Text)

	params, err := parseParams(text)
	if err != nil {
		return "", fmt.Errorf("%s:%d: %w", line.Filename, line.LineNo, err)
	}

	if len(params) < 2 {
		return "", fmt.Errorf("%s:%d: FUNC: expected at least function name", line.Filename, line.LineNo)
	}

	if strings.ToUpper(params[0]) != "FUNC" {
		return "", fmt.Errorf("%s:%d: not a FUNC declaration", line.Filename, line.LineNo)
	}

	funcName := params[1]

	// Check for redeclaration
	if fh.FuncExists(funcName) {
		return "", fmt.Errorf("%s:%d: function %q already declared", line.Filename, line.LineNo, funcName)
	}

	// Push function name to current function stack early
	// (so param declarations get correct scope)
	fh.currentFuncs = append(fh.currentFuncs, funcName)

	// Parse parameters
	var funcParams []*FuncParam

	if len(params) == 2 {
		// Void function: FUNC name
		// No parameters
	} else if len(params) >= 5 {
		// FUNC name ( param1 param2 )
		if params[2] != "(" || params[len(params)-1] != ")" {
			fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
			return "", fmt.Errorf("%s:%d: FUNC: expected parentheses around parameters", line.Filename, line.LineNo)
		}

		// Extract params between ( and ) - need to handle {BYTE x} specially
		rawParamTokens := params[3 : len(params)-1]
		paramSpecs, err := buildComplexParams(rawParamTokens)
		if err != nil {
			fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
			return "", fmt.Errorf("%s:%d: FUNC %s: %w", line.Filename, line.LineNo, funcName, err)
		}

		for _, spec := range paramSpecs {
			direction, varName, isImplicit, implicitDecl, err := parseParamSpec(spec)
			if err != nil {
				fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
				return "", fmt.Errorf("%s:%d: FUNC %s: %w", line.Filename, line.LineNo, funcName, err)
			}

			if isImplicit {
				// Parse and add implicit variable declaration
				// Format: {BYTE varname} or {WORD varname}
				if err := fh.parseImplicitDecl(implicitDecl, funcName, line); err != nil {
					fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
					return "", fmt.Errorf("%s:%d: FUNC %s: implicit declaration: %w", line.Filename, line.LineNo, funcName, err)
				}
			}

			// Look up variable in symbol table (validation only, not usage)
			sym := fh.symTable.LookupWithoutUsage(varName, []string{funcName})
			if sym == nil {
				fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
				return "", fmt.Errorf("%s:%d: FUNC %s: parameter %q not declared", line.Filename, line.LineNo, funcName, varName)
			}

			if sym.IsConst() {
				fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
				return "", fmt.Errorf("%s:%d: FUNC %s: parameter %q cannot be a constant", line.Filename, line.LineNo, funcName, varName)
			}

			if sym.IsRegister() && direction.Has(DirOut) {
				fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
				return "", fmt.Errorf("%s:%d: FUNC %s: REGISTER parameter %q cannot be out: or io: (REGISTER values do not persist after the function call)", line.Filename, line.LineNo, funcName, varName)
			}

			funcParams = append(funcParams, &FuncParam{
				Symbol:    sym,
				Direction: direction,
			})
		}
	} else {
		fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
		return "", fmt.Errorf("%s:%d: FUNC: invalid syntax", line.Filename, line.LineNo)
	}

	// Store function declaration
	fh.functions = append(fh.functions, &FuncDecl{
		Name:   funcName,
		Params: funcParams,
		Line:   line,
	})

	// Record absolute addresses used by this function
	fh.recordAbsoluteAddresses(funcName, funcParams)

	return funcName, nil
}

// recordAbsoluteAddresses stores which absolute addresses a function uses
func (fh *FunctionHandler) recordAbsoluteAddresses(funcName string, params []*FuncParam) {
	addrSet := make(map[uint16]bool)

	for _, param := range params {
		if param.Symbol.IsAbsolute() {
			addr := param.Symbol.AbsAddr
			// Mark address as used
			addrSet[addr] = true
			// If it's a word, also mark the next byte
			if param.Symbol.IsWord() {
				addrSet[addr+1] = true
			}
		}
	}

	// Only store if function uses absolute addresses
	if len(addrSet) > 0 {
		fh.absoluteAddrs[funcName] = addrSet
	}
}

// RecordAbsoluteVar is called by SymbolTable when an absolute variable is added to a function scope
func (fh *FunctionHandler) RecordAbsoluteVar(funcScope string, addr uint16, isWord bool) {
	if funcScope == "" {
		return // global variable, skip
	}

	if fh.absoluteAddrs[funcScope] == nil {
		fh.absoluteAddrs[funcScope] = make(map[uint16]bool)
	}

	fh.absoluteAddrs[funcScope][addr] = true
	if isWord {
		fh.absoluteAddrs[funcScope][addr+1] = true
	}
}

// buildComplexParams handles parameter lists that may contain {BYTE x} style declarations
// Tokens like {BYTE x} are spread across multiple tokens and need to be reassembled
func buildComplexParams(tokens []string) ([]string, error) {
	var result []string
	var current string
	inBraces := false

	for _, token := range tokens {
		hasStart := strings.Contains(token, "{")
		hasEnd := strings.Contains(token, "}")

		if !inBraces {
			// Not currently in braces
			if hasEnd && !hasStart {
				return nil, fmt.Errorf("unexpected } without matching {")
			}
			if hasStart {
				// Starting a brace block
				inBraces = true
				current = token
				// Check if it also ends on same token
				if hasEnd {
					result = append(result, current)
					current = ""
					inBraces = false
				}
			} else {
				// Regular param
				result = append(result, token)
			}
		} else {
			// Currently accumulating in braces
			if hasStart {
				return nil, fmt.Errorf("unexpected { while already in braces")
			}
			current += " " + token
			if hasEnd {
				result = append(result, current)
				current = ""
				inBraces = false
			}
		}
	}

	if inBraces {
		return nil, fmt.Errorf("unclosed { in parameter list")
	}

	return result, nil
}

// HandleFuncCall generates code for a function call
// Syntax: CALL funcname ( arg1 arg2 ... )
// Or: funcname ( arg1 arg2 ... )
func (fh *FunctionHandler) HandleFuncCall(line preproc.Line) ([]string, error) {
	// Normalize parentheses and commas
	text := fixIntuitiveFuncs(line.Text)

	params, err := parseParams(text)
	if err != nil {
		return nil, fmt.Errorf("%s:%d: %w", line.Filename, line.LineNo, err)
	}

	if len(params) < 1 {
		return nil, fmt.Errorf("%s:%d: CALL: empty line", line.Filename, line.LineNo)
	}

	// Check if starts with CALL keyword
	startsWithCall := strings.ToUpper(params[0]) == "CALL"

	funcNameIdx := 0
	if startsWithCall {
		if len(params) < 2 {
			return nil, fmt.Errorf("%s:%d: CALL: expected function name", line.Filename, line.LineNo)
		}
		funcNameIdx = 1
	}

	funcName := params[funcNameIdx]

	// Check if function exists
	funcDecl := fh.findFunc(funcName)
	if funcDecl == nil {
		return nil, fmt.Errorf("%s:%d: function %q not declared", line.Filename, line.LineNo, funcName)
	}

	// Parse call arguments
	var callArgs []string

	if len(params) == funcNameIdx+1 {
		// No arguments: funcname or CALL funcname
		callArgs = []string{}
	} else if len(params) == funcNameIdx+3 && params[funcNameIdx+1] == "(" && params[funcNameIdx+2] == ")" {
		// Empty parens: funcname() or CALL funcname()
		callArgs = []string{}
	} else if len(params) >= funcNameIdx+4 {
		// funcname ( arg1 arg2 ) or CALL funcname ( arg1 arg2 )
		if params[funcNameIdx+1] != "(" || params[len(params)-1] != ")" {
			return nil, fmt.Errorf("%s:%d: CALL %s: expected parentheses around arguments", line.Filename, line.LineNo, funcName)
		}
		callArgs = params[funcNameIdx+2 : len(params)-1]
	} else {
		return nil, fmt.Errorf("%s:%d: CALL %s: invalid syntax", line.Filename, line.LineNo, funcName)
	}

	// Check argument count matches
	if len(callArgs) != len(funcDecl.Params) {
		return nil, fmt.Errorf("%s:%d: CALL %s: expected %d arguments, got %d",
			line.Filename, line.LineNo, funcName, len(funcDecl.Params), len(callArgs))
	}

	// Get pragma set for this line
	pragmaSet := fh.pragma.GetPragmaSetByIndex(line.PragmaSetIndex)

	var asmLines []string
	var inAssigns []string
	var outAssigns []string

	// Process each argument
	for i, arg := range callArgs {
		param := funcDecl.Params[i]

		// Handle special cases first
		if strings.HasPrefix(arg, "@") {
			// Label reference: @labelname
			if err := fh.processLabelArg(arg, param, funcName, line, &inAssigns); err != nil {
				return nil, err
			}
			continue
		}

		if strings.HasPrefix(arg, "\"") && strings.HasSuffix(arg, "\"") {
			// String constant
			if err := fh.processStringArg(arg, param, funcName, line, pragmaSet, &inAssigns); err != nil {
				return nil, err
			}
			continue
		}

		// Use ParseOperandParam for variables, constants, and expressions
		constLookup := fh.symTable.ConstantLookupFunc(fh.currentFuncs)

		_, _, value, isVar, err := ParseOperandParam(
			arg, fh.symTable, fh.currentFuncs, constLookup)
		if err != nil {
			return nil, fmt.Errorf("%s:%d: CALL %s arg %d (%q): %w",
				line.Filename, line.LineNo, funcName, i+1, arg, err)
		}

		// Out/IO parameters must be writable variables
		if param.Direction.Has(DirOut) && !isVar {
			return nil, fmt.Errorf("%s:%d: CALL %s: cannot pass constant/expression to out/io parameter %q",
				line.Filename, line.LineNo, funcName, param.Symbol.Name)
		}

		if isVar {
			// Variable - get full symbol for type checking
			sym := fh.symTable.Lookup(arg, fh.currentFuncs)
			if sym == nil {
				return nil, fmt.Errorf("%s:%d: CALL %s: internal error - variable %q not found",
					line.Filename, line.LineNo, funcName, arg)
			}
			if err := fh.processVarArg(sym, param, funcName, line, &inAssigns, &outAssigns); err != nil {
				return nil, err
			}
		} else {
			// Constant or expression result
			if err := fh.processConstValue(value, param, funcName, line, &inAssigns); err != nil {
				return nil, err
			}
		}
	}

	// Record the call in the call graph
	fh.recordCall(funcName)

	// Generate final assembly
	asmLines = append(asmLines, inAssigns...)
	asmLines = append(asmLines, fmt.Sprintf("  jsr %s", funcName))
	asmLines = append(asmLines, outAssigns...)

	return asmLines, nil
}

// recordCall tracks which function calls which
func (fh *FunctionHandler) recordCall(calledFunc string) {
	// Mark function as called (for unused function detection)
	fh.calledFunctions[calledFunc] = true

	// Get current function (caller)
	if len(fh.currentFuncs) == 0 {
		// Call from global scope - not inside a function
		// Don't add to call graph (only needed for overlap detection between functions)
		return
	}

	caller := fh.currentFuncs[len(fh.currentFuncs)-1]

	// Add to call graph (for absolute address overlap detection)
	if fh.callGraph[caller] == nil {
		fh.callGraph[caller] = []string{}
	}

	// Avoid duplicates (though they're harmless for our analysis)
	for _, existing := range fh.callGraph[caller] {
		if existing == calledFunc {
			return
		}
	}

	fh.callGraph[caller] = append(fh.callGraph[caller], calledFunc)
}

// processLabelArg handles @label arguments
func (fh *FunctionHandler) processLabelArg(arg string, param *FuncParam, funcName string, line preproc.Line, inAssigns *[]string) error {
	labelName := arg[1:] // strip @

	if param.Symbol.IsByte() {
		return fmt.Errorf("%s:%d: CALL %s: cannot pass label to byte parameter", line.Filename, line.LineNo, funcName)
	}

	if param.Direction.Has(DirOut) {
		return fmt.Errorf("%s:%d: CALL %s: cannot pass label to out/io parameter", line.Filename, line.LineNo, funcName)
	}

	*inAssigns = append(*inAssigns,
		fmt.Sprintf("  lda #<%s", labelName),
		fmt.Sprintf("  sta %s", param.Symbol.FullName()),
		fmt.Sprintf("  lda #>%s", labelName),
		fmt.Sprintf("  sta %s+1", param.Symbol.FullName()),
	)

	return nil
}

// processStringArg handles "string" arguments
func (fh *FunctionHandler) processStringArg(arg string, param *FuncParam, funcName string, line preproc.Line, pragmaSet preproc.PragmaSet, inAssigns *[]string) error {
	if param.Symbol.IsByte() {
		return fmt.Errorf("%s:%d: CALL %s: cannot pass string to byte parameter", line.Filename, line.LineNo, funcName)
	}

	if param.Direction.Has(DirOut) {
		return fmt.Errorf("%s:%d: CALL %s: cannot pass string to out/io parameter", line.Filename, line.LineNo, funcName)
	}

	// Generate label for string constant
	labelName := fh.labelStack.Push()
	actualLabel := fh.constStrHandler.AddConstStr(labelName, arg, true, pragmaSet)

	*inAssigns = append(*inAssigns,
		fmt.Sprintf("  lda #<%s", actualLabel),
		fmt.Sprintf("  sta %s", param.Symbol.FullName()),
		fmt.Sprintf("  lda #>%s", actualLabel),
		fmt.Sprintf("  sta %s+1", param.Symbol.FullName()),
	)

	return nil
}

func (fh *FunctionHandler) processVarArg(sym *Symbol, param *FuncParam, funcName string, line preproc.Line, inAssigns, outAssigns *[]string) error {
	if sym.IsConst() {
		return fmt.Errorf("%s:%d: CALL %s: cannot pass constant to function", line.Filename, line.LineNo, funcName)
	}

	// Generate IN assignments (sym -> param)
	if param.Direction.Has(DirIn) {
		*inAssigns = append(*inAssigns,
			fmt.Sprintf("  lda %s", sym.FullName()),
			fmt.Sprintf("  sta %s", param.Symbol.FullName()),
		)
		if param.Symbol.IsWord() {
			if sym.IsWord() {
				*inAssigns = append(*inAssigns,
					fmt.Sprintf("  lda %s+1", sym.FullName()),
					fmt.Sprintf("  sta %s+1", param.Symbol.FullName()),
				)
			} else {
				// byte -> word: zero extend
				*inAssigns = append(*inAssigns,
					"  lda #0",
					fmt.Sprintf("  sta %s+1", param.Symbol.FullName()),
				)
			}
		} else if sym.IsWord() {
			// word -> byte: lossy
			fmt.Printf("%s:%d: warning: CALL %s: truncating word '%s' to byte parameter '%s'\n",
				line.Filename, line.LineNo, funcName, sym.Name, param.Symbol.Name)
		}
	}

	// Generate OUT assignments (param -> sym)
	if param.Direction.Has(DirOut) {
		*outAssigns = append(*outAssigns,
			fmt.Sprintf("  lda %s", param.Symbol.FullName()),
			fmt.Sprintf("  sta %s", sym.FullName()),
		)
		if sym.IsWord() {
			if param.Symbol.IsWord() {
				*outAssigns = append(*outAssigns,
					fmt.Sprintf("  lda %s+1", param.Symbol.FullName()),
					fmt.Sprintf("  sta %s+1", sym.FullName()),
				)
			} else {
				// byte -> word: zero extend
				*outAssigns = append(*outAssigns,
					"  lda #0",
					fmt.Sprintf("  sta %s+1", sym.FullName()),
				)
			}
		} else if param.Symbol.IsWord() {
			// word -> byte: lossy
			fmt.Printf("%s:%d: warning: CALL %s: truncating word parameter '%s' to byte '%s'\n",
				line.Filename, line.LineNo, funcName, param.Symbol.Name, sym.Name)
		}
	}

	return nil
}

// processConstArg handles numeric constant arguments
func (fh *FunctionHandler) processConstArg(arg string, param *FuncParam, funcName string, line preproc.Line, inAssigns *[]string) error {
	if param.Direction.Has(DirOut) {
		return fmt.Errorf("%s:%d: CALL %s: cannot pass constant to out/io parameter", line.Filename, line.LineNo, funcName)
	}

	// Parse numeric value (supports decimal and hex with $ prefix)
	var value int64
	var err error

	if strings.HasPrefix(arg, "$") {
		_, err = fmt.Sscanf(arg[1:], "%x", &value)
	} else {
		_, err = fmt.Sscanf(arg, "%d", &value)
	}

	if err != nil {
		return fmt.Errorf("%s:%d: CALL %s: invalid numeric constant %q", line.Filename, line.LineNo, funcName, arg)
	}

	if param.Symbol.IsByte() && (value < 0 || value > 255) {
		return fmt.Errorf("%s:%d: CALL %s: constant %d out of byte range", line.Filename, line.LineNo, funcName, value)
	}

	if value < 0 || value > 65535 {
		return fmt.Errorf("%s:%d: CALL %s: constant %d out of word range", line.Filename, line.LineNo, funcName, value)
	}

	lowByte := uint8(value & 0xFF)
	highByte := uint8((value >> 8) & 0xFF)

	*inAssigns = append(*inAssigns,
		fmt.Sprintf("  lda #%d", lowByte),
		fmt.Sprintf("  sta %s", param.Symbol.FullName()),
	)

	if param.Symbol.IsWord() {
		// Optimize: only reload A if high byte differs
		if highByte != lowByte {
			*inAssigns = append(*inAssigns, fmt.Sprintf("  lda #%d", highByte))
		}
		*inAssigns = append(*inAssigns, fmt.Sprintf("  sta %s+1", param.Symbol.FullName()))
	}

	return nil
}

// processConstValue handles pre-computed constant/expression values
// Used after ParseOperandParam has already validated and computed the value
func (fh *FunctionHandler) processConstValue(value uint16, param *FuncParam, funcName string, line preproc.Line, inAssigns *[]string) error {
	// Verify value fits in parameter type
	if param.Symbol.IsByte() && value > 255 {
		return fmt.Errorf("%s:%d: CALL %s: value %d out of byte range for parameter %q",
			line.Filename, line.LineNo, funcName, value, param.Symbol.Name)
	}

	lowByte := uint8(value & 0xFF)
	highByte := uint8((value >> 8) & 0xFF)

	*inAssigns = append(*inAssigns,
		fmt.Sprintf("  lda #%d", lowByte),
		fmt.Sprintf("  sta %s", param.Symbol.FullName()),
	)

	if param.Symbol.IsWord() {
		// Optimize: only reload A if high byte differs
		if highByte != lowByte {
			*inAssigns = append(*inAssigns, fmt.Sprintf("  lda #%d", highByte))
		}
		*inAssigns = append(*inAssigns, fmt.Sprintf("  sta %s+1", param.Symbol.FullName()))
	}

	return nil
}

// parseImplicitDecl parses {BYTE varname} or {WORD varname} or {BYTE REGISTER varname} or {BYTE varname @ address} and adds to symbol table
func (fh *FunctionHandler) parseImplicitDecl(decl string, funcName string, line preproc.Line) error {
	parts := strings.Fields(decl)
	if len(parts) < 2 || len(parts) > 4 {
		return fmt.Errorf("implicit declaration must be 'TYPE name', 'TYPE REGISTER name', or 'TYPE name @ addr', got: %q", decl)
	}

	typeIdx := 0
	typeStr := strings.ToUpper(parts[0])
	register := false

	// Check for REGISTER keyword after type
	if len(parts) >= 3 && strings.ToUpper(parts[1]) == "REGISTER" {
		register = true
		typeIdx = 1 // parts[1] consumed as REGISTER
	}

	if register && typeStr == "WORD" {
		return fmt.Errorf("WORD REGISTER is not supported; only BYTE variables may use the REGISTER hint")
	}

	var kind VarKind
	switch typeStr {
	case "BYTE":
		kind = KindByte
	case "WORD":
		kind = KindWord
	default:
		return fmt.Errorf("implicit declaration type must be BYTE or WORD, got: %s", typeStr)
	}

	if register && kind != KindByte {
		return fmt.Errorf("REGISTER hint is only valid for BYTE variables")
	}

	varName := parts[1+typeIdx]

	// Simple declaration: TYPE [REGISTER] name
	if len(parts) == 2+typeIdx {
		if register {
			return fh.symTable.AddRegisterVar(varName, funcName, 0, line)
		}
		return fh.symTable.AddVar(varName, funcName, kind, 0, line)
	}

	// Extended: TYPE [REGISTER] name @ address
	if len(parts) == 4+typeIdx {
		operator := parts[2+typeIdx]
		addrStr := parts[3+typeIdx]

		if register {
			return fmt.Errorf("REGISTER variable cannot be @-mapped; REGISTER is incompatible with fixed address")
		}

		if operator != "@" {
			return fmt.Errorf("expected '@' operator, got: %q", operator)
		}

		constLookup := fh.symTable.ConstantLookupFunc([]string{funcName})

		addr, err := utils.EvaluateExpression(addrStr, constLookup)
		if err != nil {
			return fmt.Errorf("invalid address %q: %w", addrStr, err)
		}

		if addr < 0 || addr > 0xFFFF {
			return fmt.Errorf("absolute address $%X out of range", addr)
		}

		return fh.symTable.AddAbsolute(varName, funcName, kind, uint16(addr), line)
	}

	return fmt.Errorf("invalid implicit declaration format: %q", decl)
}

// EndFunction pops all functions from the stack (called by FEND)
func (fh *FunctionHandler) EndFunction() {
	// Record multi-function groups (2+ funcs sharing a single FEND)
	if len(fh.currentFuncs) > 1 {
		group := &FuncGroup{
			Names: make([]string, len(fh.currentFuncs)),
		}
		copy(group.Names, fh.currentFuncs)
		fh.funcGroups = append(fh.funcGroups, group)
		for _, name := range fh.currentFuncs {
			fh.funcToGroup[name] = group
		}
	}
	fh.currentFuncs = fh.currentFuncs[:0]
}

// FuncExists checks if a function is declared
func (fh *FunctionHandler) FuncExists(name string) bool {
	return fh.findFunc(name) != nil
}

// CurrentFunction returns the current function name (or empty if global scope)
func (fh *FunctionHandler) CurrentFunction() string {
	if len(fh.currentFuncs) == 0 {
		return ""
	}
	return fh.currentFuncs[len(fh.currentFuncs)-1]
}

// GetCurrentFunctions returns all function names on the current stack
func (fh *FunctionHandler) GetCurrentFunctions() []string {
	result := make([]string, len(fh.currentFuncs))
	copy(result, fh.currentFuncs)
	return result
}

// GetGroup returns the FuncGroup for a function, or nil if it's a standalone function
func (fh *FunctionHandler) GetGroup(name string) *FuncGroup {
	return fh.funcToGroup[name]
}

// findFunc finds a function declaration by name
func (fh *FunctionHandler) findFunc(name string) *FuncDecl {
	for _, f := range fh.functions {
		if f.Name == name {
			return f
		}
	}
	return nil
}

// fixIntuitiveFuncs normalizes function syntax
// Separates '(' and ')' into own tokens, removes commas
// Example: "func(a,b)" -> "func ( a b )"
func fixIntuitiveFuncs(s string) string {
	var result strings.Builder
	inString := false

	for i := 0; i < len(s); i++ {
		ch := s[i]

		if ch == '"' {
			inString = !inString
			result.WriteByte(ch)
			continue
		}

		if !inString {
			if ch == '(' || ch == ')' {
				result.WriteByte(' ')
				result.WriteByte(ch)
				result.WriteByte(' ')
			} else if ch == ',' {
				result.WriteByte(' ')
			} else {
				result.WriteByte(ch)
			}
		} else {
			result.WriteByte(ch)
		}
	}

	return normalizeSpaces(result.String())
}

// normalizeSpaces reduces multiple spaces to single space
func normalizeSpaces(s string) string {
	s = strings.TrimSpace(s)
	var result strings.Builder
	inString := false
	lastWasSpace := false

	for i := 0; i < len(s); i++ {
		ch := s[i]

		if ch == '"' {
			inString = !inString
			result.WriteByte(ch)
			lastWasSpace = false
			continue
		}

		if !inString {
			if ch == ' ' || ch == '\t' {
				if !lastWasSpace {
					result.WriteByte(' ')
					lastWasSpace = true
				}
			} else {
				result.WriteByte(ch)
				lastWasSpace = false
			}
		} else {
			result.WriteByte(ch)
			lastWasSpace = false
		}
	}

	return result.String()
}

// parseParams splits line into space-separated parameters, respecting quoted strings
func parseParams(s string) ([]string, error) {
	s = strings.TrimSpace(s)
	if s == "" {
		return []string{}, nil
	}

	var params []string
	var current strings.Builder
	inString := false

	for i := 0; i < len(s); i++ {
		ch := s[i]

		if ch == '"' {
			inString = !inString
			current.WriteByte(ch)
			continue
		}

		if !inString && (ch == ' ' || ch == '\t') {
			if current.Len() > 0 {
				params = append(params, current.String())
				current.Reset()
			}
		} else {
			current.WriteByte(ch)
		}
	}

	if current.Len() > 0 {
		params = append(params, current.String())
	}

	if inString {
		return nil, fmt.Errorf("unterminated string in line")
	}

	return params, nil
}

// parseParamSpec parses a parameter specification
// Returns: direction, varName, isImplicit, implicitDecl, error
// Examples:
//
//	"varname" -> DirIn, "varname", false, "", nil
//	"in:varname" -> DirIn, "varname", false, "", nil
//	"out:varname" -> DirOut, "varname", false, "", nil
//	"io:varname" -> DirIn|DirOut, "varname", false, "", nil
//	"{BYTE temp}" -> DirIn, "temp", true, "BYTE temp", nil
//	"out:{WORD result}" -> DirOut, "result", true, "WORD result", nil
func parseParamSpec(spec string) (ParamDirection, string, bool, string, error) {
	direction := DirIn // default
	varName := spec
	isImplicit := false
	implicitDecl := ""

	// Check for direction prefix
	if strings.Contains(spec, ":") {
		parts := strings.SplitN(spec, ":", 2)
		if len(parts) != 2 {
			return 0, "", false, "", fmt.Errorf("invalid parameter spec: %q", spec)
		}

		dirStr := strings.ToLower(parts[0])
		varName = parts[1]

		switch dirStr {
		case "in":
			direction = DirIn
		case "out":
			direction = DirOut
		case "io":
			direction = DirIn | DirOut
		default:
			return 0, "", false, "", fmt.Errorf("invalid parameter direction: %q", dirStr)
		}
	}

	// Check for implicit declaration {TYPE name} or {TYPE REGISTER name}
	if strings.HasPrefix(varName, "{") && strings.HasSuffix(varName, "}") {
		isImplicit = true
		implicitDecl = varName[1 : len(varName)-1] // strip { }

		// Extract variable name from implicit declaration
		parts := strings.Fields(implicitDecl)
		if len(parts) < 2 {
			return 0, "", false, "", fmt.Errorf("invalid implicit declaration: %q", varName)
		}
		// Handle {TYPE REGISTER name} — variable name is parts[2]
		if len(parts) >= 3 && strings.ToUpper(parts[1]) == "REGISTER" {
			varName = parts[2]
		} else {
			varName = parts[1]
		}
	}

	return direction, varName, isImplicit, implicitDecl, nil
}

// AbsoluteOverlap represents a detected overlap in absolute addresses
type AbsoluteOverlap struct {
	Func1      string   // First function using the address
	Func2      string   // Second function using the address
	Address    uint16   // Overlapping address
	CallChain  []string // Call chain from Func1 to Func2
}

// AnalyzeAbsoluteOverlaps checks for overlapping absolute addresses in call chains
func (fh *FunctionHandler) AnalyzeAbsoluteOverlaps() []AbsoluteOverlap {
	// Use map to deduplicate: key is "func1:func2:addr", value is overlap with shortest chain
	seen := make(map[string]*AbsoluteOverlap)

	// For each function that uses absolute addresses
	for funcName, addrSet := range fh.absoluteAddrs {
		// Get all functions transitively called by this function
		transitiveAddrs := fh.getTransitiveAddresses(funcName, make(map[string]bool))

		// Check for overlaps
		for addr := range addrSet {
			for otherFunc, otherAddrs := range transitiveAddrs {
				if otherAddrs[addr] {
					// Found overlap!
					callChain := fh.findCallChain(funcName, otherFunc)

					// Create unique key for this conflict
					key := fmt.Sprintf("%s:%s:%04x", funcName, otherFunc, addr)

					// Keep only if this is the first occurrence or has shorter chain
					if existing, exists := seen[key]; !exists || len(callChain) < len(existing.CallChain) {
						seen[key] = &AbsoluteOverlap{
							Func1:     funcName,
							Func2:     otherFunc,
							Address:   addr,
							CallChain: callChain,
						}
					}
				}
			}
		}
	}

	// Convert map to slice
	var overlaps []AbsoluteOverlap
	for _, overlap := range seen {
		overlaps = append(overlaps, *overlap)
	}

	return overlaps
}

// ReportAbsoluteOverlaps analyzes and reports overlapping absolute addresses to stderr
func (fh *FunctionHandler) ReportAbsoluteOverlaps() {
	overlaps := fh.AnalyzeAbsoluteOverlaps()

	if len(overlaps) == 0 {
		return
	}

	// Group overlaps by address
	byAddress := make(map[uint16]map[string]bool)
	for _, overlap := range overlaps {
		if byAddress[overlap.Address] == nil {
			byAddress[overlap.Address] = make(map[string]bool)
		}
		byAddress[overlap.Address][overlap.Func1] = true
		byAddress[overlap.Address][overlap.Func2] = true
	}

	// Extract and sort addresses
	addresses := make([]uint16, 0, len(byAddress))
	for addr := range byAddress {
		addresses = append(addresses, addr)
	}
	sort.Slice(addresses, func(i, j int) bool {
		return addresses[i] < addresses[j]
	})

	_, _ = fmt.Fprintf(os.Stderr, "\nWarning: Detected overlapping absolute addresses in function call chains:\n\n")

	// Report each address with its conflicting functions (in sorted order)
	for _, addr := range addresses {
		funcs := byAddress[addr]
		funcList := make([]string, 0, len(funcs))
		for f := range funcs {
			funcList = append(funcList, f)
		}

		_, _ = fmt.Fprintf(os.Stderr, "  Address $%04X used by: %s\n", addr, strings.Join(funcList, ", "))
		_, _ = fmt.Fprintf(os.Stderr, "    %d function(s) share this address in overlapping call chains\n\n", len(funcList))
	}

	_, _ = fmt.Fprintf(os.Stderr, "  These conflicts may cause data corruption when functions are active simultaneously.\n\n")
}

// getTransitiveAddresses returns all addresses used by transitively called functions
// Returns map: funcName -> set of addresses used by that function
func (fh *FunctionHandler) getTransitiveAddresses(funcName string, visited map[string]bool) map[string]map[uint16]bool {
	result := make(map[string]map[uint16]bool)

	// Avoid infinite recursion
	if visited[funcName] {
		return result
	}
	visited[funcName] = true

	// Get functions directly called by this function
	for _, calledFunc := range fh.callGraph[funcName] {
		// Add addresses used by the called function
		if addrs, exists := fh.absoluteAddrs[calledFunc]; exists {
			result[calledFunc] = addrs
		}

		// Recursively get addresses from transitively called functions
		transitiveAddrs := fh.getTransitiveAddresses(calledFunc, visited)
		for f, addrs := range transitiveAddrs {
			result[f] = addrs
		}
	}

	return result
}

// findCallChain finds the call path from funcA to funcB
func (fh *FunctionHandler) findCallChain(from, to string) []string {
	// BFS to find shortest path
	type node struct {
		funcName string
		path     []string
	}

	queue := []node{{funcName: from, path: []string{from}}}
	visited := make(map[string]bool)

	for len(queue) > 0 {
		current := queue[0]
		queue = queue[1:]

		if visited[current.funcName] {
			continue
		}
		visited[current.funcName] = true

		if current.funcName == to {
			return current.path
		}

		// Explore called functions
		for _, calledFunc := range fh.callGraph[current.funcName] {
			if !visited[calledFunc] {
				newPath := make([]string, len(current.path)+1)
				copy(newPath, current.path)
				newPath[len(current.path)] = calledFunc
				queue = append(queue, node{funcName: calledFunc, path: newPath})
			}
		}
	}

	// No path found (shouldn't happen if overlap detected correctly)
	return []string{from, to}
}

// CheckUnusedFunctions returns warnings for functions that are declared but never called
func (fh *FunctionHandler) CheckUnusedFunctions() []string {
	var warnings []string

	for _, funcDecl := range fh.functions {
		// Skip functions that have been called
		if fh.calledFunctions[funcDecl.Name] {
			continue
		}

		// For functions in a multi-FUNC group: if ANY sibling is called,
		// suppress the warning (they share a body needed by the called sibling)
		if group := fh.funcToGroup[funcDecl.Name]; group != nil {
			anySiblingCalled := false
			for _, name := range group.Names {
				if fh.calledFunctions[name] {
					anySiblingCalled = true
					break
				}
			}
			if anySiblingCalled {
				continue
			}
		}

		// Check if pragma indicates we should ignore unused warnings for this function
		if fh.pragma != nil {
			pragmaSet := fh.pragma.GetPragmaSetByIndex(funcDecl.Line.PragmaSetIndex)
			// Skip warning if _P_IGNORE_UNUSED is enabled
			value := pragmaSet.GetPragma("_P_IGNORE_UNUSED")
			if value != "" && value != "0" {
				continue // Skip warning for this function
			}
			// Also skip warning if _P_REMOVE_UNUSED is enabled (function will be removed)
			removeValue := pragmaSet.GetPragma("_P_REMOVE_UNUSED")
			if removeValue != "" && removeValue != "0" {
				continue // Skip warning for this function (it will be removed)
			}
		}

		// Format warning message with file and line info
		warning := fmt.Sprintf("%s:%d: warning: function '%s' declared but never called",
			funcDecl.Line.Filename, funcDecl.Line.LineNo, funcDecl.Name)
		warnings = append(warnings, warning)
	}

	return warnings
}

// hasRemovePragma checks if a specific FuncDecl has _P_REMOVE_UNUSED enabled
func hasRemovePragma(funcDecl *FuncDecl, pragma *preproc.Pragma) bool {
	if pragma == nil {
		return false
	}
	pragmaSet := pragma.GetPragmaSetByIndex(funcDecl.Line.PragmaSetIndex)
	value := pragmaSet.GetPragma("_P_REMOVE_UNUSED")
	return value != "" && value != "0"
}

// GetFunctionsToRemove returns a map of function names that should be removed from assembly output
// Functions are removed if they are never called AND have _P_REMOVE_UNUSED pragma enabled
// For functions in a multi-FUNC group, ALL functions must be unused and ALL must have the pragma,
// or none are removed (they share a body atomically)
func (fh *FunctionHandler) GetFunctionsToRemove() map[string]bool {
	toRemove := make(map[string]bool)
	processed := make(map[string]bool)

	for _, funcDecl := range fh.functions {
		name := funcDecl.Name
		if processed[name] {
			continue
		}
		processed[name] = true

		group := fh.funcToGroup[name]
		if group == nil {
			// Standalone function - existing logic
			if fh.calledFunctions[name] {
				continue
			}
			if hasRemovePragma(funcDecl, fh.pragma) {
				toRemove[name] = true
			}
		} else {
			// Multi-function group: all must be unused AND all must have pragma
			allUnused := true
			allHavePragma := true
			for _, gname := range group.Names {
				processed[gname] = true
				if fh.calledFunctions[gname] {
					allUnused = false
				}
				gDecl := fh.findFunc(gname)
				if gDecl == nil || !hasRemovePragma(gDecl, fh.pragma) {
					allHavePragma = false
				}
			}
			if allUnused && allHavePragma {
				for _, gname := range group.Names {
					toRemove[gname] = true
				}
			}
		}
	}

	return toRemove
}

// GetFunctionsWithRemovePragma returns a map of function names that have _P_REMOVE_UNUSED pragma enabled
// This is used to suppress variable warnings for functions marked for removal
// For functions in a multi-FUNC group, if any member has the pragma, all group members are included
func (fh *FunctionHandler) GetFunctionsWithRemovePragma() map[string]bool {
	withPragma := make(map[string]bool)
	processed := make(map[string]bool)

	for _, funcDecl := range fh.functions {
		if processed[funcDecl.Name] {
			continue
		}

		group := fh.funcToGroup[funcDecl.Name]
		if group == nil {
			// Standalone function
			if fh.pragma != nil {
				pragmaSet := fh.pragma.GetPragmaSetByIndex(funcDecl.Line.PragmaSetIndex)
				removeValue := pragmaSet.GetPragma("_P_REMOVE_UNUSED")
				if removeValue != "" && removeValue != "0" {
					withPragma[funcDecl.Name] = true
				}
			}
			processed[funcDecl.Name] = true
		} else {
			// Multi-function group: if any has pragma, all are included
			anyHasPragma := false
			for _, gname := range group.Names {
				processed[gname] = true
				gDecl := fh.findFunc(gname)
				if gDecl != nil && fh.pragma != nil {
					pragmaSet := fh.pragma.GetPragmaSetByIndex(gDecl.Line.PragmaSetIndex)
					removeValue := pragmaSet.GetPragma("_P_REMOVE_UNUSED")
					if removeValue != "" && removeValue != "0" {
						anyHasPragma = true
					}
				}
			}
			if anyHasPragma {
				for _, gname := range group.Names {
					withPragma[gname] = true
				}
			}
		}
	}

	return withPragma
}
