package compiler

import (
	"fmt"
	"strings"
	"testing"

	"c65gm/internal/preproc"
)

// Helper to create a test Line
func makeLine(text string) preproc.Line {
	return preproc.Line{
		RawText:        text,
		Text:           text,
		Filename:       "test.c65",
		LineNo:         1,
		Kind:           preproc.Source,
		PragmaSetIndex: 0,
	}
}

func TestFixIntuitiveFuncs(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"func(a,b)", "func ( a b )"},
		{"func( a, b )", "func ( a b )"},
		{"func(a,b,c)", "func ( a b c )"},
		{"CALL func()", "CALL func ( )"},
		{"func()", "func ( )"},
		{`func("hello",x)`, `func ( "hello" x )`},
		{`func("a,b",c)`, `func ( "a,b" c )`},
		{"func  (  a  ,  b  )", "func ( a b )"},
	}

	for _, tt := range tests {
		result := fixIntuitiveFuncs(tt.input)
		if result != tt.expected {
			t.Errorf("fixIntuitiveFuncs(%q) = %q, want %q", tt.input, result, tt.expected)
		}
	}
}

func TestBuildComplexParams(t *testing.T) {
	tests := []struct {
		input    []string
		expected []string
		wantErr  bool
	}{
		{
			input:    []string{"a", "b", "c"},
			expected: []string{"a", "b", "c"},
			wantErr:  false,
		},
		{
			input:    []string{"{BYTE", "x}"},
			expected: []string{"{BYTE x}"},
			wantErr:  false,
		},
		{
			input:    []string{"{WORD", "ptr}"},
			expected: []string{"{WORD ptr}"},
			wantErr:  false,
		},
		{
			input:    []string{"{BYTE", "a}", "{WORD", "b}"},
			expected: []string{"{BYTE a}", "{WORD b}"},
			wantErr:  false,
		},
		{
			input:    []string{"x", "{BYTE", "a}", "y"},
			expected: []string{"x", "{BYTE a}", "y"},
			wantErr:  false,
		},
		{
			input:    []string{"{BYTE", "x"},
			expected: nil,
			wantErr:  true, // unclosed
		},
		{
			input:    []string{"x}"},
			expected: nil,
			wantErr:  true, // unmatched close
		},
		{
			input:    []string{"{BYTE", "{WORD", "x}"},
			expected: nil,
			wantErr:  true, // nested open
		},
	}

	for _, tt := range tests {
		result, err := buildComplexParams(tt.input)
		if tt.wantErr {
			if err == nil {
				t.Errorf("buildComplexParams(%v) expected error, got nil", tt.input)
			}
			continue
		}
		if err != nil {
			t.Errorf("buildComplexParams(%v) unexpected error: %v", tt.input, err)
			continue
		}
		if len(result) != len(tt.expected) {
			t.Errorf("buildComplexParams(%v) = %v, want %v", tt.input, result, tt.expected)
			continue
		}
		for i := range result {
			if result[i] != tt.expected[i] {
				t.Errorf("buildComplexParams(%v)[%d] = %q, want %q", tt.input, i, result[i], tt.expected[i])
			}
		}
	}
}

func TestParseParams(t *testing.T) {
	tests := []struct {
		input    string
		expected []string
		wantErr  bool
	}{
		{"FUNC test", []string{"FUNC", "test"}, false},
		{"FUNC test ( a b )", []string{"FUNC", "test", "(", "a", "b", ")"}, false},
		{`CALL print ( "hello world" )`, []string{"CALL", "print", "(", `"hello world"`, ")"}, false},
		{"  FUNC   test  ", []string{"FUNC", "test"}, false},
		{`func("unterminated`, nil, true},
	}

	for _, tt := range tests {
		result, err := parseParams(tt.input)
		if tt.wantErr {
			if err == nil {
				t.Errorf("parseParams(%q) expected error, got nil", tt.input)
			}
			continue
		}
		if err != nil {
			t.Errorf("parseParams(%q) unexpected error: %v", tt.input, err)
			continue
		}
		if len(result) != len(tt.expected) {
			t.Errorf("parseParams(%q) = %v, want %v", tt.input, result, tt.expected)
			continue
		}
		for i := range result {
			if result[i] != tt.expected[i] {
				t.Errorf("parseParams(%q)[%d] = %q, want %q", tt.input, i, result[i], tt.expected[i])
			}
		}
	}
}

func TestParseParamSpec(t *testing.T) {
	tests := []struct {
		input        string
		wantDir      ParamDirection
		wantName     string
		wantImplicit bool
		wantImplDecl string
		wantErr      bool
	}{
		{"varname", DirIn, "varname", false, "", false},
		{"in:varname", DirIn, "varname", false, "", false},
		{"out:varname", DirOut, "varname", false, "", false},
		{"io:varname", DirIn | DirOut, "varname", false, "", false},
		{"{BYTE temp}", DirIn, "temp", true, "BYTE temp", false},
		{"{WORD result}", DirIn, "result", true, "WORD result", false},
		{"out:{BYTE x}", DirOut, "x", true, "BYTE x", false},
		{"io:{WORD ptr}", DirIn | DirOut, "ptr", true, "WORD ptr", false},
		{"invalid:dir:x", 0, "", false, "", true},
	}

	for _, tt := range tests {
		dir, name, isImpl, implDecl, err := parseParamSpec(tt.input)
		if tt.wantErr {
			if err == nil {
				t.Errorf("parseParamSpec(%q) expected error, got nil", tt.input)
			}
			continue
		}
		if err != nil {
			t.Errorf("parseParamSpec(%q) unexpected error: %v", tt.input, err)
			continue
		}
		if dir != tt.wantDir {
			t.Errorf("parseParamSpec(%q) direction = %v, want %v", tt.input, dir, tt.wantDir)
		}
		if name != tt.wantName {
			t.Errorf("parseParamSpec(%q) name = %q, want %q", tt.input, name, tt.wantName)
		}
		if isImpl != tt.wantImplicit {
			t.Errorf("parseParamSpec(%q) implicit = %v, want %v", tt.input, isImpl, tt.wantImplicit)
		}
		if implDecl != tt.wantImplDecl {
			t.Errorf("parseParamSpec(%q) implDecl = %q, want %q", tt.input, implDecl, tt.wantImplDecl)
		}
	}
}

func TestHandleFuncDecl_VoidFunction(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	funcName, err := fh.HandleFuncDecl(makeLine("FUNC test_void"))
	if err != nil {
		t.Fatalf("HandleFuncDecl failed: %v", err)
	}

	if funcName != "test_void" {
		t.Fatalf("expected funcName = \"test_void\", got %q", funcName)
	}

	if !fh.FuncExists("test_void") {
		t.Error("function should exist")
	}
}

func TestHandleFuncDecl_WithExistingParams(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Pre-declare parameters
	st.AddVar("x", "test_func", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	st.AddVar("y", "test_func", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	funcName, err := fh.HandleFuncDecl(makeLine("FUNC test_func ( x y )"))
	if err != nil {
		t.Fatalf("HandleFuncDecl failed: %v", err)
	}

	if funcName != "test_func" {
		t.Fatalf("expected funcName = \"test_func\", got %q", funcName)
	}

	funcDecl := fh.findFunc("test_func")
	if funcDecl == nil {
		t.Fatal("function not found")
	}
	if len(funcDecl.Params) != 2 {
		t.Fatalf("expected 2 params, got %d", len(funcDecl.Params))
	}
}

func TestHandleFuncDecl_ImplicitDeclarations(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	funcName, err := fh.HandleFuncDecl(makeLine("FUNC test_impl ( {BYTE a} {WORD b} )"))
	if err != nil {
		t.Fatalf("HandleFuncDecl failed: %v", err)
	}

	if funcName != "test_impl" {
		t.Fatalf("expected funcName = \"test_impl\", got %q", funcName)
	}

	// Check that variables were declared
	symA := st.Lookup("a", []string{"test_impl"})
	if symA == nil {
		t.Fatal("parameter 'a' not declared")
	}
	if !symA.IsByte() {
		t.Error("parameter 'a' should be byte")
	}

	symB := st.Lookup("b", []string{"test_impl"})
	if symB == nil {
		t.Fatal("parameter 'b' not declared")
	}
	if !symB.IsWord() {
		t.Error("parameter 'b' should be word")
	}
}

func TestHandleFuncDecl_WithDirections(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	_, err := fh.HandleFuncDecl(makeLine("FUNC test_dir ( in:{BYTE a} out:{BYTE b} io:{WORD c} )"))
	if err != nil {
		t.Fatalf("HandleFuncDecl failed: %v", err)
	}

	funcDecl := fh.findFunc("test_dir")
	if funcDecl == nil {
		t.Fatal("function not found")
	}

	if len(funcDecl.Params) != 3 {
		t.Fatalf("expected 3 params, got %d", len(funcDecl.Params))
	}

	if funcDecl.Params[0].Direction != DirIn {
		t.Error("param 0 should be DirIn")
	}
	if funcDecl.Params[1].Direction != DirOut {
		t.Error("param 1 should be DirOut")
	}
	if funcDecl.Params[2].Direction != (DirIn | DirOut) {
		t.Error("param 2 should be DirIn|DirOut")
	}
}

func TestHandleFuncDecl_Errors(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		preDecl func(*SymbolTable)
		wantErr string
	}{
		{
			name:    "redeclaration",
			line:    "FUNC duplicate ( {BYTE x} )",
			preDecl: func(st *SymbolTable) {},
			wantErr: "already declared",
		},
		{
			name:    "missing param",
			line:    "FUNC test ( missing )",
			wantErr: "not declared",
		},
		{
			name: "const param",
			line: "FUNC test ( constval )",
			preDecl: func(st *SymbolTable) {
				st.AddConst("constval", "test", KindByte, 42, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantErr: "cannot be a constant",
		},
		{
			name:    "invalid implicit",
			line:    "FUNC test ( {INVALID x} )",
			wantErr: "must be BYTE or WORD",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			st := NewSymbolTable()
			ls := NewLabelStack("L")
			csh := NewConstantStringHandler()
			pragma := preproc.NewPragma()
			fh := NewFunctionHandler(st, ls, csh, pragma)

			if tt.preDecl != nil {
				tt.preDecl(st)
			}

			// Special case for redeclaration test
			if tt.name == "redeclaration" {
				_, _ = fh.HandleFuncDecl(makeLine("FUNC duplicate ( {BYTE x} )"))
			}

			_, err := fh.HandleFuncDecl(makeLine(tt.line))
			if err == nil {
				t.Fatal("expected error, got nil")
			}
			if !strings.Contains(err.Error(), tt.wantErr) {
				t.Errorf("error %q does not contain %q", err.Error(), tt.wantErr)
			}
		})
	}
}

func TestHandleFuncCall_VarArgs(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Declare function with params
	st.AddVar("param_a", "test_func", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	st.AddVar("param_b", "test_func", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	_, _ = fh.HandleFuncDecl(makeLine("FUNC test_func ( param_a param_b )"))

	// Declare caller variables
	st.AddVar("var_a", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	st.AddVar("var_b", "", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	asm, err := fh.HandleFuncCall(makeLine("CALL test_func ( var_a var_b )"))
	if err != nil {
		t.Fatalf("HandleFuncCall failed: %v", err)
	}

	// Check generated assembly
	expectedLines := []string{
		"  lda var_a",
		"  sta test_func_param_a",
		"  lda var_b",
		"  sta test_func_param_b",
		"  lda var_b+1",
		"  sta test_func_param_b+1",
		"  jsr test_func",
	}

	if len(asm) != len(expectedLines) {
		t.Fatalf("expected %d asm lines, got %d", len(expectedLines), len(asm))
	}

	for i, expected := range expectedLines {
		if asm[i] != expected {
			t.Errorf("asm[%d] = %q, want %q", i, asm[i], expected)
		}
	}
}

func TestHandleFuncCall_OutParams(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Declare function with out param
	st.AddVar("result", "get_result", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	_, _ = fh.HandleFuncDecl(makeLine("FUNC get_result ( out:result )"))

	// Declare caller variable
	st.AddVar("output", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	asm, err := fh.HandleFuncCall(makeLine("CALL get_result ( output )"))
	if err != nil {
		t.Fatalf("HandleFuncCall failed: %v", err)
	}

	// Should have JSR and OUT assignment
	found_jsr := false
	found_out := false
	for _, line := range asm {
		if strings.Contains(line, "jsr get_result") {
			found_jsr = true
		}
		if strings.Contains(line, "lda get_result_result") {
			found_out = true
		}
	}

	if !found_jsr {
		t.Error("missing jsr instruction")
	}
	if !found_out {
		t.Error("missing out assignment")
	}
}

func TestHandleFuncCall_ConstArgs(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Declare function
	st.AddVar("x", "test_const", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	st.AddVar("y", "test_const", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	_, _ = fh.HandleFuncDecl(makeLine("FUNC test_const ( x y )"))

	asm, err := fh.HandleFuncCall(makeLine("CALL test_const ( 42 $1234 )"))
	if err != nil {
		t.Fatalf("HandleFuncCall failed: %v", err)
	}

	// Check for immediate loads
	foundByte := false
	foundWord := false
	for _, line := range asm {
		if strings.Contains(line, "lda #42") {
			foundByte = true
		}
		if strings.Contains(line, "lda #18") { // 0x12
			foundWord = true
		}
	}

	if !foundByte {
		t.Error("missing byte constant load")
	}
	if !foundWord {
		t.Error("missing word constant load")
	}
}

func TestHandleFuncCall_LabelArg(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Declare function
	st.AddVar("ptr", "test_label", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	_, _ = fh.HandleFuncDecl(makeLine("FUNC test_label ( ptr )"))

	asm, err := fh.HandleFuncCall(makeLine("CALL test_label ( @my_label )"))
	if err != nil {
		t.Fatalf("HandleFuncCall failed: %v", err)
	}

	// Check for label reference
	foundLow := false
	foundHigh := false
	for _, line := range asm {
		if strings.Contains(line, "#<my_label") {
			foundLow = true
		}
		if strings.Contains(line, "#>my_label") {
			foundHigh = true
		}
	}

	if !foundLow || !foundHigh {
		t.Error("missing label reference code")
	}
}

func TestHandleFuncCall_StringArg(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Declare function
	st.AddVar("str_ptr", "print", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	_, _ = fh.HandleFuncDecl(makeLine("FUNC print ( str_ptr )"))

	asm, err := fh.HandleFuncCall(makeLine(`CALL print ( "hello" )`))
	if err != nil {
		t.Fatalf("HandleFuncCall failed: %v", err)
	}

	// Check that label was generated
	if ls.Size() != 1 {
		t.Errorf("expected 1 label generated, got %d", ls.Size())
	}

	// Check for label reference in asm
	foundLabel := false
	for _, line := range asm {
		if strings.Contains(line, "#<L1") || strings.Contains(line, "#>L1") {
			foundLabel = true
			break
		}
	}

	if !foundLabel {
		t.Error("missing string label reference")
	}
}

func TestHandleFuncCall_Errors(t *testing.T) {
	tests := []struct {
		name    string
		setup   func(*FunctionHandler, *SymbolTable)
		line    string
		wantErr string
	}{
		{
			name:    "function not declared",
			setup:   func(fh *FunctionHandler, st *SymbolTable) {},
			line:    "CALL undefined ( )",
			wantErr: "not declared",
		},
		{
			name: "wrong arg count",
			setup: func(fh *FunctionHandler, st *SymbolTable) {
				st.AddVar("x", "test", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				_, _ = fh.HandleFuncDecl(makeLine("FUNC test ( x )"))
			},
			line:    "CALL test ( 1 2 )",
			wantErr: "expected 1 arguments, got 2",
		},
		//
		/* This is no error anymore, just a warning.
		{
			name: "type mismatch",
			setup: func(fh *FunctionHandler, st *SymbolTable) {
				st.AddVar("param", "test", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				_, _ = fh.HandleFuncDecl(makeLine("FUNC test ( param )"))
				st.AddVar("wvar", "", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:    "CALL test ( wvar )",
			wantErr: "type mismatch",
		}, */
		{
			name: "const to out param",
			setup: func(fh *FunctionHandler, st *SymbolTable) {
				st.AddVar("result", "test", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				_, _ = fh.HandleFuncDecl(makeLine("FUNC test ( out:result )"))
			},
			line:    "CALL test ( 42 )",
			wantErr: "out/io parameter",
		},
		{
			name: "label to byte param",
			setup: func(fh *FunctionHandler, st *SymbolTable) {
				st.AddVar("x", "test", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				_, _ = fh.HandleFuncDecl(makeLine("FUNC test ( x )"))
			},
			line:    "CALL test ( @label )",
			wantErr: "byte parameter",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			st := NewSymbolTable()
			ls := NewLabelStack("L")
			csh := NewConstantStringHandler()
			pragma := preproc.NewPragma()
			fh := NewFunctionHandler(st, ls, csh, pragma)

			tt.setup(fh, st)

			_, err := fh.HandleFuncCall(makeLine(tt.line))
			if err == nil {
				t.Fatal("expected error, got nil")
			}
			if !strings.Contains(err.Error(), tt.wantErr) {
				t.Errorf("error %q does not contain %q", err.Error(), tt.wantErr)
			}
		})
	}
}

func TestHandleFuncCall_EmptyParens(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Declare void function
	_, _ = fh.HandleFuncDecl(makeLine("FUNC init"))

	tests := []struct {
		name string
		line string
	}{
		{"naked call", "init"},
		{"empty parens", "init()"},
		{"CALL keyword", "CALL init"},
		{"CALL with parens", "CALL init()"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			asm, err := fh.HandleFuncCall(makeLine(tt.line))
			if err != nil {
				t.Fatalf("HandleFuncCall failed: %v", err)
			}

			// Should have JSR instruction
			foundJSR := false
			for _, line := range asm {
				if strings.Contains(line, "jsr init") {
					foundJSR = true
					break
				}
			}

			if !foundJSR {
				t.Error("missing jsr instruction")
			}
		})
	}
}

func TestEndFunction(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Declare function (pushes to stack)
	_, _ = fh.HandleFuncDecl(makeLine("FUNC test ( {BYTE x} )"))

	if fh.CurrentFunction() != "test" {
		t.Errorf("current function = %q, want 'test'", fh.CurrentFunction())
	}

	// End function
	fh.EndFunction()

	if fh.CurrentFunction() != "" {
		t.Errorf("current function = %q, want ''", fh.CurrentFunction())
	}
}

func TestCurrentFunction(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	if fh.CurrentFunction() != "" {
		t.Error("expected empty current function initially")
	}

	_, _ = fh.HandleFuncDecl(makeLine("FUNC func1 ( {BYTE x} )"))
	if fh.CurrentFunction() != "func1" {
		t.Errorf("expected 'func1', got %q", fh.CurrentFunction())
	}

	_, _ = fh.HandleFuncDecl(makeLine("FUNC func2 ( {BYTE y} )"))
	if fh.CurrentFunction() != "func2" {
		t.Errorf("expected 'func2', got %q", fh.CurrentFunction())
	}

	fh.EndFunction()
	if fh.CurrentFunction() != "" {
		t.Errorf("expected '', got %q", fh.CurrentFunction())
	}
}

func TestHandleFuncDecl_AbsoluteVariables(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	_, err := fh.HandleFuncDecl(makeLine("FUNC test_abs ( {BYTE param1 @ $fa} {WORD param2 @ $fb} )"))
	if err != nil {
		t.Fatalf("HandleFuncDecl failed: %v", err)
	}

	// Check that variables were declared as absolute
	symByte := st.Lookup("param1", []string{"test_abs"})
	if symByte == nil {
		t.Fatal("parameter 'param1' not declared")
	}
	if !symByte.IsByte() {
		t.Error("parameter 'param1' should be byte")
	}
	if !symByte.IsAbsolute() {
		t.Error("parameter 'param1' should be absolute")
	}
	if symByte.AbsAddr != 0xfa {
		t.Errorf("parameter 'param1' address = $%02x, want $fa", symByte.AbsAddr)
	}
	if !symByte.IsZeroPage() {
		t.Error("parameter 'param1' should be in zero page")
	}

	symWord := st.Lookup("param2", []string{"test_abs"})
	if symWord == nil {
		t.Fatal("parameter 'param2' not declared")
	}
	if !symWord.IsWord() {
		t.Error("parameter 'param2' should be word")
	}
	if !symWord.IsAbsolute() {
		t.Error("parameter 'param2' should be absolute")
	}
	if symWord.AbsAddr != 0xfb {
		t.Errorf("parameter 'param2' address = $%02x, want $fb", symWord.AbsAddr)
	}
	if !symWord.IsZeroPage() {
		t.Error("parameter 'param2' should be in zero page")
	}

	// Check function declaration
	funcDecl := fh.findFunc("test_abs")
	if funcDecl == nil {
		t.Fatal("function not found")
	}
	if len(funcDecl.Params) != 2 {
		t.Fatalf("expected 2 params, got %d", len(funcDecl.Params))
	}
}

func TestHandleFuncDecl_AbsoluteWithDirections(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	_, err := fh.HandleFuncDecl(makeLine("FUNC test_abs_dir ( in:{BYTE input @ $fa} out:{BYTE output @ $fb} io:{WORD data @ $fc} )"))
	if err != nil {
		t.Fatalf("HandleFuncDecl failed: %v", err)
	}

	funcDecl := fh.findFunc("test_abs_dir")
	if funcDecl == nil {
		t.Fatal("function not found")
	}

	if len(funcDecl.Params) != 3 {
		t.Fatalf("expected 3 params, got %d", len(funcDecl.Params))
	}

	if funcDecl.Params[0].Direction != DirIn {
		t.Error("param 0 should be DirIn")
	}
	if funcDecl.Params[1].Direction != DirOut {
		t.Error("param 1 should be DirOut")
	}
	if funcDecl.Params[2].Direction != (DirIn | DirOut) {
		t.Error("param 2 should be DirIn|DirOut")
	}

	// Verify absolute addresses
	if funcDecl.Params[0].Symbol.AbsAddr != 0xfa {
		t.Errorf("param 0 address = $%02x, want $fa", funcDecl.Params[0].Symbol.AbsAddr)
	}
	if funcDecl.Params[1].Symbol.AbsAddr != 0xfb {
		t.Errorf("param 1 address = $%02x, want $fb", funcDecl.Params[1].Symbol.AbsAddr)
	}
	if funcDecl.Params[2].Symbol.AbsAddr != 0xfc {
		t.Errorf("param 2 address = $%02x, want $fc", funcDecl.Params[2].Symbol.AbsAddr)
	}
}

func TestHandleFuncDecl_AbsoluteMixedParams(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Mix absolute and regular params
	_, err := fh.HandleFuncDecl(makeLine("FUNC test_mixed ( {BYTE abs_param @ $fa} {WORD reg_param} )"))
	if err != nil {
		t.Fatalf("HandleFuncDecl failed: %v", err)
	}

	symAbs := st.Lookup("abs_param", []string{"test_mixed"})
	if symAbs == nil {
		t.Fatal("parameter 'abs_param' not declared")
	}
	if !symAbs.IsAbsolute() {
		t.Error("parameter 'abs_param' should be absolute")
	}

	symReg := st.Lookup("reg_param", []string{"test_mixed"})
	if symReg == nil {
		t.Fatal("parameter 'reg_param' not declared")
	}
	if symReg.IsAbsolute() {
		t.Error("parameter 'reg_param' should not be absolute")
	}
}

func TestHandleFuncDecl_AbsoluteErrors(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		wantErr string
	}{
		{
			name:    "invalid operator",
			line:    "FUNC test ( {BYTE x = $fa} )",
			wantErr: "expected '@' operator",
		},
		{
			name:    "invalid address",
			line:    "FUNC test ( {BYTE x @ invalid} )",
			wantErr: "invalid address",
		},
		{
			name:    "address out of range",
			line:    "FUNC test ( {BYTE x @ $10000} )",
			wantErr: "out of range",
		},
		{
			name:    "negative address",
			line:    "FUNC test ( {BYTE x @ -1} )",
			wantErr: "invalid address",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			st := NewSymbolTable()
			ls := NewLabelStack("L")
			csh := NewConstantStringHandler()
			pragma := preproc.NewPragma()
			fh := NewFunctionHandler(st, ls, csh, pragma)

			_, err := fh.HandleFuncDecl(makeLine(tt.line))
			if err == nil {
				t.Fatal("expected error, got nil")
			}
			if !strings.Contains(err.Error(), tt.wantErr) {
				t.Errorf("error %q does not contain %q", err.Error(), tt.wantErr)
			}
		})
	}
}

func TestHandleFuncCall_AbsoluteParams(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Declare function with absolute params
	_, _ = fh.HandleFuncDecl(makeLine("FUNC test_abs ( {BYTE param_a @ $fa} {WORD param_b @ $fb} )"))

	// Declare caller variables
	st.AddVar("var_a", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	st.AddVar("var_b", "", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	asm, err := fh.HandleFuncCall(makeLine("CALL test_abs ( var_a var_b )"))
	if err != nil {
		t.Fatalf("HandleFuncCall failed: %v", err)
	}

	// Check generated assembly uses correct names
	expectedLines := []string{
		"  lda var_a",
		"  sta test_abs_param_a",
		"  lda var_b",
		"  sta test_abs_param_b",
		"  lda var_b+1",
		"  sta test_abs_param_b+1",
		"  jsr test_abs",
	}

	if len(asm) != len(expectedLines) {
		t.Fatalf("expected %d asm lines, got %d", len(expectedLines), len(asm))
	}

	for i, expected := range expectedLines {
		if asm[i] != expected {
			t.Errorf("asm[%d] = %q, want %q", i, asm[i], expected)
		}
	}
}

func TestHandleFuncCall_AbsoluteOutParams(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Declare function with absolute out param
	_, _ = fh.HandleFuncDecl(makeLine("FUNC get_result ( out:{BYTE result @ $fa} )"))

	// Declare caller variable
	st.AddVar("output", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	asm, err := fh.HandleFuncCall(makeLine("CALL get_result ( output )"))
	if err != nil {
		t.Fatalf("HandleFuncCall failed: %v", err)
	}

	// Should have JSR and OUT assignment
	found_jsr := false
	found_out := false
	for _, line := range asm {
		if strings.Contains(line, "jsr get_result") {
			found_jsr = true
		}
		if strings.Contains(line, "lda get_result_result") {
			found_out = true
		}
	}

	if !found_jsr {
		t.Error("missing jsr instruction")
	}
	if !found_out {
		t.Error("missing out assignment")
	}
}

func TestParseImplicitDecl_Absolute(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	tests := []struct {
		name     string
		decl     string
		funcName string
		wantErr  bool
		checkFn  func(*testing.T, *SymbolTable)
	}{
		{
			name:     "byte at hex address",
			decl:     "BYTE x @ $fa",
			funcName: "test",
			wantErr:  false,
			checkFn: func(t *testing.T, st *SymbolTable) {
				sym := st.Lookup("x", []string{"test"})
				if sym == nil {
					t.Fatal("symbol not found")
				}
				if !sym.IsAbsolute() || sym.AbsAddr != 0xfa {
					t.Errorf("expected absolute at $fa, got absolute=%v addr=$%02x", sym.IsAbsolute(), sym.AbsAddr)
				}
			},
		},
		{
			name:     "word at decimal address",
			decl:     "WORD ptr @ 251",
			funcName: "test",
			wantErr:  false,
			checkFn: func(t *testing.T, st *SymbolTable) {
				sym := st.Lookup("ptr", []string{"test"})
				if sym == nil {
					t.Fatal("symbol not found")
				}
				if !sym.IsAbsolute() || sym.AbsAddr != 251 {
					t.Errorf("expected absolute at 251, got absolute=%v addr=%d", sym.IsAbsolute(), sym.AbsAddr)
				}
			},
		},
		{
			name:     "invalid operator",
			decl:     "BYTE x = $fa",
			funcName: "test",
			wantErr:  true,
		},
		{
			name:     "too few parts",
			decl:     "BYTE x @",
			funcName: "test",
			wantErr:  true,
		},
		{
			name:     "too many parts",
			decl:     "BYTE x @ $fa extra",
			funcName: "test",
			wantErr:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Clear symbol table for each test
			st = NewSymbolTable()
			fh.symTable = st

			err := fh.parseImplicitDecl(tt.decl, tt.funcName, preproc.Line{Filename: "test.c65", LineNo: 1})
			if tt.wantErr {
				if err == nil {
					t.Fatal("expected error, got nil")
				}
				return
			}
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if tt.checkFn != nil {
				tt.checkFn(t, st)
			}
		})
	}
}

func TestAbsoluteOverlapDetection(t *testing.T) {
	tests := []struct {
		name            string
		code            string
		wantOverlapAddr uint16
		wantFunc1       string
		wantFunc2       string
	}{
		{
			name: "direct call with overlap",
			code: `
BYTE dummy
FUNC funcB ( {BYTE data @ $fa} )
FEND

FUNC funcA ( {BYTE temp @ $fa} )
  CALL funcB(dummy)
FEND
`,
			wantOverlapAddr: 0xFA,
			wantFunc1:       "funcA",
			wantFunc2:       "funcB",
		},
		{
			name: "transitive call with overlap",
			code: `
BYTE dummy
FUNC funcC ( {BYTE data @ $fa} )
FEND

FUNC funcB
  CALL funcC(dummy)
FEND

FUNC funcA ( {BYTE temp @ $fa} )
  CALL funcB()
FEND
`,
			wantOverlapAddr: 0xFA,
			wantFunc1:       "funcA",
			wantFunc2:       "funcC",
		},
		{
			name: "word overlap with byte",
			code: `
BYTE dummy
FUNC funcB ( {BYTE data @ $fb} )
FEND

FUNC funcA ( {WORD value @ $fa} )
  CALL funcB(dummy)
FEND
`,
			wantOverlapAddr: 0xFB,
			wantFunc1:       "funcA",
			wantFunc2:       "funcB",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			st := NewSymbolTable()
			ls := NewLabelStack("L")
			csh := NewConstantStringHandler()
			pragma := preproc.NewPragma()
			fh := NewFunctionHandler(st, ls, csh, pragma)

			// Parse code into lines
			lines := strings.Split(strings.TrimSpace(tt.code), "\n")

			// Process each line
			for i, lineText := range lines {
				lineText = strings.TrimSpace(lineText)
				if lineText == "" {
					continue
				}

				pline := preproc.Line{
					Text:     lineText,
					LineNo:   i + 1,
					Filename: "test.c65",
				}

				if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
					// Variable declaration - add to symbol table
					parts := strings.Fields(lineText)
					if len(parts) >= 2 {
						varKind := KindByte
						if strings.ToUpper(parts[0]) == "WORD" {
							varKind = KindWord
						}
						st.AddVar(parts[1], "", varKind, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
					}
				} else if strings.HasPrefix(lineText, "FUNC ") {
					_, err := fh.HandleFuncDecl(pline)
					if err != nil {
						t.Fatalf("HandleFuncDecl failed: %v", err)
					}
				} else if strings.HasPrefix(lineText, "CALL ") {
					_, err := fh.HandleFuncCall(pline)
					if err != nil {
						t.Fatalf("HandleFuncCall failed: %v", err)
					}
				} else if lineText == "FEND" {
					fh.EndFunction()
				}
			}

			// Analyze overlaps
			overlaps := fh.AnalyzeAbsoluteOverlaps()

			if len(overlaps) == 0 {
				t.Fatalf("Expected overlap at address $%04X, but none detected", tt.wantOverlapAddr)
			}

			// Find the expected overlap
			found := false
			for _, overlap := range overlaps {
				if overlap.Address == tt.wantOverlapAddr &&
					overlap.Func1 == tt.wantFunc1 &&
					overlap.Func2 == tt.wantFunc2 {
					found = true
					break
				}
			}

			if !found {
				t.Errorf("Expected overlap at $%04X between %s and %s, got: %+v",
					tt.wantOverlapAddr, tt.wantFunc1, tt.wantFunc2, overlaps)
			}
		})
	}
}

func TestAbsoluteLocalVariables(t *testing.T) {
	tests := []struct {
		name            string
		code            string
		wantOverlapAddr uint16
		wantFunc1       string
		wantFunc2       string
	}{
		{
			name: "local absolute variables overlap",
			code: `
FUNC funcB
  BYTE localB @ $fa
FEND

FUNC funcA
  BYTE localA @ $fa
  CALL funcB()
FEND
`,
			wantOverlapAddr: 0xFA,
			wantFunc1:       "funcA",
			wantFunc2:       "funcB",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			st := NewSymbolTable()
			ls := NewLabelStack("L")
			csh := NewConstantStringHandler()
			pragma := preproc.NewPragma()
			fh := NewFunctionHandler(st, ls, csh, pragma)

			// Connect symbol table to function handler
			st.SetFunctionHandler(fh)

			// Parse code into lines
			lines := strings.Split(strings.TrimSpace(tt.code), "\n")
			currentFunc := ""

			// Process each line
			for i, lineText := range lines {
				lineText = strings.TrimSpace(lineText)
				if lineText == "" {
					continue
				}

				pline := preproc.Line{
					Text:     lineText,
					LineNo:   i + 1,
					Filename: "test.c65",
				}

				if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
					// Parse variable declaration
					parts := strings.Fields(lineText)
					if len(parts) >= 4 && parts[2] == "@" {
						// Absolute variable: BYTE name @ $addr
						varKind := KindByte
						if strings.ToUpper(parts[0]) == "WORD" {
							varKind = KindWord
						}
						addrStr := strings.TrimPrefix(parts[3], "$")
						var addr uint16
						fmt.Sscanf(addrStr, "%x", &addr)
						st.AddAbsolute(parts[1], currentFunc, varKind, addr, preproc.Line{Filename: "test.c65", LineNo: 1})
					}
				} else if strings.HasPrefix(lineText, "FUNC ") {
					_, err := fh.HandleFuncDecl(pline)
					if err != nil {
						t.Fatalf("HandleFuncDecl failed: %v", err)
					}
					// Extract function name
					funcParts := strings.Fields(lineText)
					if len(funcParts) >= 2 {
						currentFunc = funcParts[1]
					}
				} else if strings.HasPrefix(lineText, "CALL ") {
					_, err := fh.HandleFuncCall(pline)
					if err != nil {
						t.Fatalf("HandleFuncCall failed: %v", err)
					}
				} else if lineText == "FEND" {
					fh.EndFunction()
					currentFunc = ""
				}
			}

			// Analyze overlaps
			overlaps := fh.AnalyzeAbsoluteOverlaps()

			if len(overlaps) == 0 {
				t.Fatalf("Expected overlap at address $%04X, but none detected", tt.wantOverlapAddr)
			}

			// Find the expected overlap
			found := false
			for _, overlap := range overlaps {
				if overlap.Address == tt.wantOverlapAddr &&
					overlap.Func1 == tt.wantFunc1 &&
					overlap.Func2 == tt.wantFunc2 {
					found = true
					break
				}
			}

			if !found {
				t.Errorf("Expected overlap at $%04X between %s and %s, got: %+v",
					tt.wantOverlapAddr, tt.wantFunc1, tt.wantFunc2, overlaps)
			}
		})
	}
}

func TestAbsoluteNoOverlap(t *testing.T) {
	tests := []struct {
		name string
		code string
	}{
		{
			name: "different addresses",
			code: `
BYTE dummy
FUNC funcB ( {BYTE data @ $fb} )
FEND

FUNC funcA ( {BYTE temp @ $fa} )
  CALL funcB(dummy)
FEND
`,
		},
		{
			name: "no call relationship",
			code: `
FUNC funcA ( {BYTE temp @ $fa} )
FEND

FUNC funcB ( {BYTE data @ $fa} )
FEND
`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			st := NewSymbolTable()
			ls := NewLabelStack("L")
			csh := NewConstantStringHandler()
			pragma := preproc.NewPragma()
			fh := NewFunctionHandler(st, ls, csh, pragma)

			// Parse code into lines
			lines := strings.Split(strings.TrimSpace(tt.code), "\n")

			// Process each line
			for i, lineText := range lines {
				lineText = strings.TrimSpace(lineText)
				if lineText == "" {
					continue
				}

				pline := preproc.Line{
					Text:     lineText,
					LineNo:   i + 1,
					Filename: "test.c65",
				}

				if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
					// Variable declaration - add to symbol table
					parts := strings.Fields(lineText)
					if len(parts) >= 2 {
						varKind := KindByte
						if strings.ToUpper(parts[0]) == "WORD" {
							varKind = KindWord
						}
						st.AddVar(parts[1], "", varKind, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
					}
				} else if strings.HasPrefix(lineText, "FUNC ") {
					_, err := fh.HandleFuncDecl(pline)
					if err != nil {
						t.Fatalf("HandleFuncDecl failed: %v", err)
					}
				} else if strings.HasPrefix(lineText, "CALL ") {
					_, err := fh.HandleFuncCall(pline)
					if err != nil {
						t.Fatalf("HandleFuncCall failed: %v", err)
					}
				} else if lineText == "FEND" {
					fh.EndFunction()
				}
			}

			// Analyze overlaps
			overlaps := fh.AnalyzeAbsoluteOverlaps()

			if len(overlaps) > 0 {
				t.Errorf("Expected no overlaps, but got: %+v", overlaps)
			}
		})
	}
}

func TestCheckUnusedFunctions(t *testing.T) {
	// Create a mock pragma
	pragma := preproc.NewPragma()
	
	// Create function handler with dependencies
	st := NewSymbolTable()
	ls := NewLabelStack("_L")
	csh := NewConstantStringHandler()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	// Test 1: Function called from global scope should not warn
	t.Run("function called from global scope", func(t *testing.T) {
		// Declare a function with implicit parameter
		line1 := preproc.Line{
			RawText:        "FUNC myFunc ( {BYTE x} )",
			Text:           "FUNC myFunc ( {BYTE x} )",
			Filename:       "test.c65",
			LineNo:         10,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err := fh.HandleFuncDecl(line1)
		if err != nil {
			t.Fatalf("Failed to declare function: %v", err)
		}

		// Call the function from global scope
		line2 := preproc.Line{
			RawText:        "CALL myFunc ( 42 )",
			Text:           "CALL myFunc ( 42 )",
			Filename:       "test.c65",
			LineNo:         20,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err = fh.HandleFuncCall(line2)
		if err != nil {
			t.Fatalf("Failed to call function: %v", err)
		}

		// Check for unused functions - should be empty
		warnings := fh.CheckUnusedFunctions()
		if len(warnings) > 0 {
			t.Errorf("Expected no warnings for called function, got: %v", warnings)
		}
	})

	// Test 2: Function never called should warn
	t.Run("function never called", func(t *testing.T) {
		// Reset for new test
		st = NewSymbolTable()
		ls = NewLabelStack("_L")
		csh = NewConstantStringHandler()
		fh = NewFunctionHandler(st, ls, csh, preproc.NewPragma())

		// Declare a function but never call it
		line := preproc.Line{
			RawText:        "FUNC unusedFunc ( {BYTE a} {BYTE b} )",
			Text:           "FUNC unusedFunc ( {BYTE a} {BYTE b} )",
			Filename:       "test.c65",
			LineNo:         30,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err := fh.HandleFuncDecl(line)
		if err != nil {
			t.Fatalf("Failed to declare function: %v", err)
		}

		// Check for unused functions - should warn
		warnings := fh.CheckUnusedFunctions()
		if len(warnings) != 1 {
			t.Fatalf("Expected 1 warning for unused function, got %d: %v", len(warnings), warnings)
		}

		expected := "test.c65:30: warning: function 'unusedFunc' declared but never called"
		if warnings[0] != expected {
			t.Errorf("Expected warning %q, got %q", expected, warnings[0])
		}
	})

	// Test 3: Function called from another function should not warn (for the callee)
	t.Run("function called from another function", func(t *testing.T) {
		// Reset for new test
		st = NewSymbolTable()
		ls = NewLabelStack("_L")
		csh = NewConstantStringHandler()
		fh = NewFunctionHandler(st, ls, csh, preproc.NewPragma())

		// Declare helper function (will be called from main)
		line1 := preproc.Line{
			RawText:        "FUNC helper ( {BYTE x} )",
			Text:           "FUNC helper ( {BYTE x} )",
			Filename:       "test.c65",
			LineNo:         40,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err := fh.HandleFuncDecl(line1)
		if err != nil {
			t.Fatalf("Failed to declare helper function: %v", err)
		}

		// Declare main function (void function) - will be called from global scope
		line2 := preproc.Line{
			RawText:        "FUNC main",
			Text:           "FUNC main",
			Filename:       "test.c65",
			LineNo:         50,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err = fh.HandleFuncDecl(line2)
		if err != nil {
			t.Fatalf("Failed to declare main function: %v", err)
		}

		// Call main from global scope
		line3 := preproc.Line{
			RawText:        "CALL main",
			Text:           "CALL main",
			Filename:       "test.c65",
			LineNo:         55,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err = fh.HandleFuncCall(line3)
		if err != nil {
			t.Fatalf("Failed to call main function: %v", err)
		}

		// Enter main function scope (simulate being inside main)
		fh.currentFuncs = append(fh.currentFuncs, "main")

		// Call helper from main
		line4 := preproc.Line{
			RawText:        "CALL helper ( 42 )",
			Text:           "CALL helper ( 42 )",
			Filename:       "test.c65",
			LineNo:         60,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err = fh.HandleFuncCall(line4)
		if err != nil {
			t.Fatalf("Failed to call helper function: %v", err)
		}

		// Exit main function scope
		fh.EndFunction()

		// Check for unused functions - neither should warn
		// main is called from global scope, helper is called from main
		warnings := fh.CheckUnusedFunctions()
		if len(warnings) > 0 {
			t.Errorf("Expected no warnings for called functions, got: %v", warnings)
		}
	})

	// Test 4: Multiple unused functions should all warn
	t.Run("multiple unused functions", func(t *testing.T) {
		// Reset for new test
		st = NewSymbolTable()
		ls = NewLabelStack("_L")
		csh = NewConstantStringHandler()
		fh = NewFunctionHandler(st, ls, csh, preproc.NewPragma())

		// Declare multiple unused functions
		lines := []preproc.Line{
			{
				RawText:        "FUNC func1",
				Text:           "FUNC func1",
				Filename:       "test.c65",
				LineNo:         70,
				Kind:           preproc.Source,
				PragmaSetIndex: 0,
			},
			{
				RawText:        "FUNC func2 ( {BYTE x} )",
				Text:           "FUNC func2 ( {BYTE x} )",
				Filename:       "test.c65",
				LineNo:         80,
				Kind:           preproc.Source,
				PragmaSetIndex: 0,
			},
			{
				RawText:        "FUNC func3 ( {WORD y} )",
				Text:           "FUNC func3 ( {WORD y} )",
				Filename:       "test.c65",
				LineNo:         90,
				Kind:           preproc.Source,
				PragmaSetIndex: 0,
			},
		}

		for _, line := range lines {
			_, err := fh.HandleFuncDecl(line)
			if err != nil {
				t.Fatalf("Failed to declare function: %v", err)
			}
		}

		// Check for unused functions - should have 3 warnings
		warnings := fh.CheckUnusedFunctions()
		if len(warnings) != 3 {
			t.Fatalf("Expected 3 warnings for unused functions, got %d: %v", len(warnings), warnings)
		}

		// Verify each warning contains the correct function name
		expectedFuncs := []string{"func1", "func2", "func3"}
		for _, funcName := range expectedFuncs {
			found := false
			for _, warning := range warnings {
				if strings.Contains(warning, fmt.Sprintf("function '%s'", funcName)) {
					found = true
					break
				}
			}
			if !found {
				t.Errorf("Missing warning for function '%s'", funcName)
			}
		}
	})

	// Test 5: Function with _P_IGNORE_UNUSED pragma should not warn
	t.Run("function with ignore unused pragma", func(t *testing.T) {
		// This test is complex because we need to properly set up pragmas
		// For now, we'll skip it since pragma functionality is tested elsewhere
		// The CheckUnusedFunctions method includes pragma checking code
		// which will be exercised in integration tests
		t.Skip("Pragma testing requires proper pragma setup, tested elsewhere")
	})

	// Test 6: Implicit function call syntax (without CALL keyword)
	t.Run("implicit function call syntax", func(t *testing.T) {
		// Reset for new test
		st = NewSymbolTable()
		ls = NewLabelStack("_L")
		csh = NewConstantStringHandler()
		fh := NewFunctionHandler(st, ls, csh, pragma)

		// Declare a function
		line1 := preproc.Line{
			RawText:        "FUNC myFunc ( {BYTE x} )",
			Text:           "FUNC myFunc ( {BYTE x} )",
			Filename:       "test.c65",
			LineNo:         110,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err := fh.HandleFuncDecl(line1)
		if err != nil {
			t.Fatalf("Failed to declare function: %v", err)
		}

		// Call the function using implicit syntax (without CALL keyword)
		line2 := preproc.Line{
			RawText:        "myFunc ( 42 )",
			Text:           "myFunc ( 42 )",
			Filename:       "test.c65",
			LineNo:         120,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err = fh.HandleFuncCall(line2)
		if err != nil {
			t.Fatalf("Failed to call function with implicit syntax: %v", err)
		}

		// Check for unused functions - should be empty
		warnings := fh.CheckUnusedFunctions()
		if len(warnings) > 0 {
			t.Errorf("Expected no warnings for called function (implicit syntax), got: %v", warnings)
		}
	})
}

// TestMultiFuncGroupWarnings tests that functions sharing an FEND don't generate spurious warnings
func TestMultiFuncGroupWarnings(t *testing.T) {
	// Test 1: Multi-FUNC group with one called sibling → no warnings
	t.Run("group with one called sibling suppresses warnings", func(t *testing.T) {
		st := NewSymbolTable()
		ls := NewLabelStack("_L")
		csh := NewConstantStringHandler()
		fh := NewFunctionHandler(st, ls, csh, preproc.NewPragma())

		// Declare two functions in a group (simulating multi-FUNC-per-FEND pattern)
		line1 := preproc.Line{
			RawText:        "FUNC funcA ( {BYTE x} )",
			Text:           "FUNC funcA ( {BYTE x} )",
			Filename:       "test.c65",
			LineNo:         10,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err := fh.HandleFuncDecl(line1)
		if err != nil {
			t.Fatalf("Failed to declare funcA: %v", err)
		}

		line2 := preproc.Line{
			RawText:        "FUNC funcB ( {BYTE x} )",
			Text:           "FUNC funcB ( {BYTE x} )",
			Filename:       "test.c65",
			LineNo:         11,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err = fh.HandleFuncDecl(line2)
		if err != nil {
			t.Fatalf("Failed to declare funcB: %v", err)
		}

		// End the function group (simulating FEND)
		fh.EndFunction()

		// Verify the group was recorded
		group := fh.GetGroup("funcA")
		if group == nil {
			t.Fatal("Expected funcA to be in a group")
		}
		if len(group.Names) != 2 || group.Names[0] != "funcA" || group.Names[1] != "funcB" {
			t.Errorf("Expected group [funcA funcB], got %v", group.Names)
		}

		// Only call funcB
		line3 := preproc.Line{
			RawText:        "CALL funcB ( 42 )",
			Text:           "CALL funcB ( 42 )",
			Filename:       "test.c65",
			LineNo:         20,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err = fh.HandleFuncCall(line3)
		if err != nil {
			t.Fatalf("Failed to call funcB: %v", err)
		}

		// Check for unused functions - should be empty (funcA's warning suppressed by funcB's usage)
		warnings := fh.CheckUnusedFunctions()
		if len(warnings) > 0 {
			t.Errorf("Expected no warnings (sibling in group is called), got: %v", warnings)
		}
	})

	// Test 2: Multi-FUNC group with none called → both get warnings
	t.Run("group with none called shows warnings for all", func(t *testing.T) {
		st := NewSymbolTable()
		ls := NewLabelStack("_L")
		csh := NewConstantStringHandler()
		fh := NewFunctionHandler(st, ls, csh, preproc.NewPragma())

		// Declare two functions in a group
		line1 := preproc.Line{
			RawText:        "FUNC funcX ( {BYTE a} )",
			Text:           "FUNC funcX ( {BYTE a} )",
			Filename:       "test.c65",
			LineNo:         30,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err := fh.HandleFuncDecl(line1)
		if err != nil {
			t.Fatalf("Failed to declare funcX: %v", err)
		}

		line2 := preproc.Line{
			RawText:        "FUNC funcY ( {BYTE a} )",
			Text:           "FUNC funcY ( {BYTE a} )",
			Filename:       "test.c65",
			LineNo:         31,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err = fh.HandleFuncDecl(line2)
		if err != nil {
			t.Fatalf("Failed to declare funcY: %v", err)
		}
		fh.EndFunction()

		// Neither is called - both should warn
		warnings := fh.CheckUnusedFunctions()
		if len(warnings) != 2 {
			t.Fatalf("Expected 2 warnings for unused functions in group, got %d: %v", len(warnings), warnings)
		}

		// Verify each function appears in a warning
		foundX := false
		foundY := false
		for _, w := range warnings {
			if strings.Contains(w, "'funcX'") {
				foundX = true
			}
			if strings.Contains(w, "'funcY'") {
				foundY = true
			}
		}
		if !foundX {
			t.Error("Missing warning for funcX")
		}
		if !foundY {
			t.Error("Missing warning for funcY")
		}
	})

	// Test 3: Standalone functions (not in group) unaffected
	t.Run("standalone functions unaffected by new logic", func(t *testing.T) {
		st := NewSymbolTable()
		ls := NewLabelStack("_L")
		csh := NewConstantStringHandler()
		fh := NewFunctionHandler(st, ls, csh, preproc.NewPragma())

		// Declare a standalone function
		line1 := preproc.Line{
			RawText:        "FUNC standalone ( {BYTE x} )",
			Text:           "FUNC standalone ( {BYTE x} )",
			Filename:       "test.c65",
			LineNo:         40,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}
		_, err := fh.HandleFuncDecl(line1)
		if err != nil {
			t.Fatalf("Failed to declare standalone: %v", err)
		}
		fh.EndFunction()

		// Not called - should warn
		warnings := fh.CheckUnusedFunctions()
		if len(warnings) != 1 {
			t.Fatalf("Expected 1 warning for standalone function, got %d: %v", len(warnings), warnings)
		}
		if !strings.Contains(warnings[0], "'standalone'") {
			t.Errorf("Expected warning about 'standalone', got: %s", warnings[0])
		}
	})
}

// TestMultiFuncGroupRemoval tests GetFunctionsToRemove with multi-FUNC groups
func TestMultiFuncGroupRemoval(t *testing.T) {
	// Test 1: Group with all unused and all have pragma → all removable
	t.Run("all unused all have pragma", func(t *testing.T) {
		pragma := preproc.NewPragma()

		st := NewSymbolTable()
		ls := NewLabelStack("_L")
		csh := NewConstantStringHandler()
		fh := NewFunctionHandler(st, ls, csh, pragma)

		// Set _P_REMOVE_UNUSED before declaring funcA
		pragma.AddPragma("_P_REMOVE_UNUSED", "1")
		line1 := preproc.Line{
			RawText:        "FUNC funcA ( {BYTE x} )",
			Text:           "FUNC funcA ( {BYTE x} )",
			Filename:       "test.c65",
			LineNo:         10,
			Kind:           preproc.Source,
			PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
		}
		_, err := fh.HandleFuncDecl(line1)
		if err != nil {
			t.Fatalf("Failed to declare funcA: %v", err)
		}

		// Set _P_REMOVE_UNUSED before declaring funcB
		pragma.AddPragma("_P_REMOVE_UNUSED", "1")
		line2 := preproc.Line{
			RawText:        "FUNC funcB ( {BYTE x} )",
			Text:           "FUNC funcB ( {BYTE x} )",
			Filename:       "test.c65",
			LineNo:         11,
			Kind:           preproc.Source,
			PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
		}
		_, err = fh.HandleFuncDecl(line2)
		if err != nil {
			t.Fatalf("Failed to declare funcB: %v", err)
		}
		fh.EndFunction()

		toRemove := fh.GetFunctionsToRemove()
		if !toRemove["funcA"] || !toRemove["funcB"] {
			t.Errorf("Expected both funcA and funcB to be removable, got: %v", toRemove)
		}
		if len(toRemove) != 2 {
			t.Errorf("Expected exactly 2 functions to remove, got %d", len(toRemove))
		}
	})

	// Test 2: Group with one called → none removable
	t.Run("one called none removable", func(t *testing.T) {
		pragma := preproc.NewPragma()

		st := NewSymbolTable()
		ls := NewLabelStack("_L")
		csh := NewConstantStringHandler()
		fh := NewFunctionHandler(st, ls, csh, pragma)

		pragma.AddPragma("_P_REMOVE_UNUSED", "1")
		line1 := preproc.Line{
			RawText:        "FUNC funcA ( {BYTE x} )",
			Text:           "FUNC funcA ( {BYTE x} )",
			Filename:       "test.c65",
			LineNo:         10,
			Kind:           preproc.Source,
			PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
		}
		_, err := fh.HandleFuncDecl(line1)
		if err != nil {
			t.Fatalf("Failed to declare funcA: %v", err)
		}

		pragma.AddPragma("_P_REMOVE_UNUSED", "1")
		line2 := preproc.Line{
			RawText:        "FUNC funcB ( {BYTE x} )",
			Text:           "FUNC funcB ( {BYTE x} )",
			Filename:       "test.c65",
			LineNo:         11,
			Kind:           preproc.Source,
			PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
		}
		_, err = fh.HandleFuncDecl(line2)
		if err != nil {
			t.Fatalf("Failed to declare funcB: %v", err)
		}
		fh.EndFunction()

		// Call funcB
		line3 := preproc.Line{
			RawText:        "CALL funcB ( 42 )",
			Text:           "CALL funcB ( 42 )",
			Filename:       "test.c65",
			LineNo:         20,
			Kind:           preproc.Source,
			PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
		}
		_, err = fh.HandleFuncCall(line3)
		if err != nil {
			t.Fatalf("Failed to call funcB: %v", err)
		}

		toRemove := fh.GetFunctionsToRemove()
		if len(toRemove) != 0 {
			t.Errorf("Expected no removables (funcB is called), got: %v", toRemove)
		}
	})
}

func TestParseImplicitDecl_Register(t *testing.T) {
	pragma := preproc.NewPragma()
	symTable := NewSymbolTable()
	fh := NewFunctionHandler(symTable, NewLabelStack("L"), nil, pragma)

	err := fh.parseImplicitDecl("BYTE REGISTER temp", "myFunc", preproc.Line{Filename: "test.c65", LineNo: 1})
	if err != nil {
		t.Fatalf("parseImplicitDecl with REGISTER failed: %v", err)
	}

	sym := symTable.Lookup("temp", []string{"myFunc"})
	if sym == nil {
		t.Fatal("expected symbol temp to be found")
	}
	if !sym.IsRegister() {
		t.Error("expected IsRegister() to be true")
	}
}

func TestParseImplicitDecl_WordRegisterError(t *testing.T) {
	pragma := preproc.NewPragma()
	symTable := NewSymbolTable()
	fh := NewFunctionHandler(symTable, NewLabelStack("L"), nil, pragma)

	err := fh.parseImplicitDecl("WORD REGISTER ptr", "myFunc", preproc.Line{Filename: "test.c65", LineNo: 1})
	if err == nil {
		t.Fatal("expected error for WORD REGISTER")
	}
	if !strings.Contains(err.Error(), "REGISTER") {
		t.Errorf("error should mention REGISTER, got: %v", err)
	}
}

func TestParseImplicitDecl_RegisterAtError(t *testing.T) {
	pragma := preproc.NewPragma()
	symTable := NewSymbolTable()
	fh := NewFunctionHandler(symTable, NewLabelStack("L"), nil, pragma)

	err := fh.parseImplicitDecl("BYTE REGISTER temp @ $FB", "myFunc", preproc.Line{Filename: "test.c65", LineNo: 1})
	if err == nil {
		t.Fatal("expected error for REGISTER with @")
	}
}

func TestHandleFuncDecl_OutRegisterError(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	_, err := fh.HandleFuncDecl(makeLine("FUNC test_out_reg ( out:{BYTE REGISTER temp} )"))
	if err == nil {
		t.Fatal("expected error for out: REGISTER parameter")
	}
	if !strings.Contains(err.Error(), "REGISTER") && !strings.Contains(err.Error(), "out:") {
		t.Errorf("error should mention REGISTER and out:, got: %v", err)
	}
}

func TestHandleFuncDecl_ImplicitRegisterParam(t *testing.T) {
	st := NewSymbolTable()
	ls := NewLabelStack("L")
	csh := NewConstantStringHandler()
	pragma := preproc.NewPragma()
	fh := NewFunctionHandler(st, ls, csh, pragma)

	funcName, err := fh.HandleFuncDecl(makeLine("FUNC test_reg ( {BYTE REGISTER temp} )"))
	if err != nil {
		t.Fatalf("HandleFuncDecl with REGISTER failed: %v", err)
	}

	if funcName != "test_reg" {
		t.Fatalf("expected funcName = \"test_reg\", got %q", funcName)
	}

	sym := st.Lookup("temp", []string{"test_reg"})
	if sym == nil {
		t.Fatal("REGISTER parameter not declared")
	}
	if !sym.IsRegister() {
		t.Error("expected IsRegister() to be true")
	}
	if !sym.IsByte() {
		t.Error("expected IsByte() to be true")
	}
}
