package commands

import (
	"strings"
	"testing"

	"c65gm/internal/compiler"
	"c65gm/internal/preproc"
)

func TestPointerCommand_Generate(t *testing.T) {
	tests := []struct {
		name      string
		line      string
		setupVars func(*compiler.SymbolTable)
		wantAsm   []string
	}{
		{
			name: "pointer to label — uses A only",
			line: "POINTER ptr -> TARGET",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("ptr", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #<TARGET",
				"\tsta ptr",
				"\tlda #>TARGET",
				"\tsta ptr+1",
			},
		},
		{
			name: "pointer to variable — uses A only",
			line: "POINTER ptr TO targetVar",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("ptr", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddVar("targetVar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #<targetVar",
				"\tsta ptr",
				"\tlda #>targetVar",
				"\tsta ptr+1",
			},
		},
		{
			name: "pointer to numeric address — uses A only",
			line: "POINTER ptr -> 53280",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("ptr", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pragma := preproc.NewPragma()
			ctx := compiler.NewCompilerContext(pragma)
			tt.setupVars(ctx.SymbolTable)

			cmd := &PointerCommand{}
			line := preproc.Line{
				Text:           tt.line,
				Kind:           preproc.Source,
				PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
			}

			if err := cmd.Interpret(line, ctx); err != nil {
				t.Fatalf("Interpret() error = %v", err)
			}

			asm, err := cmd.Generate(ctx)
			if err != nil {
				t.Fatalf("Generate() error = %v", err)
			}

			if tt.name == "pointer to numeric address — uses A only" {
				foundLo := false
				foundHi := false
				foundSta := false
				for _, a := range asm {
					if strings.Contains(a, "lda #<") {
						foundLo = true
					}
					if strings.Contains(a, "lda #>") {
						foundHi = true
					}
					if strings.Contains(a, "sta ptr+1") {
						foundSta = true
					}
				}
				if !foundLo || !foundHi || !foundSta {
					t.Errorf("expected A-only pattern (lda #< / sta ptr / lda #> / sta ptr+1), got:\n%s", strings.Join(asm, "\n"))
				}
				return
			}

			if !equalAsm(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}

func TestPointerCommand_NoXRegister(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)
	ctx.SymbolTable.AddVar("ptr", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	cmd := &PointerCommand{}
	line := preproc.Line{
		Text:           "POINTER ptr -> $0400",
		Kind:           preproc.Source,
		PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
	}

	if err := cmd.Interpret(line, ctx); err != nil {
		t.Fatalf("Interpret() error = %v", err)
	}

	asm, err := cmd.Generate(ctx)
	if err != nil {
		t.Fatalf("Generate() error = %v", err)
	}

	joined := strings.Join(asm, "\n")
	if strings.Contains(joined, "ldx") || strings.Contains(joined, "stx") {
		t.Errorf("POINTER should not use X register, got:\n%s", joined)
	}
}
