package commands

import (
	"strings"
	"testing"

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

// setupPokeVars adds all needed symbols to a symbol table for POKE/POKEW tests.
func setupPokeVars(st *compiler.SymbolTable) {
	st.AddConst("vic2", "", compiler.KindWord, 0xd000, preproc.Line{Filename: "test.c65", LineNo: 1})
	st.AddVar("addrvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 2})
	st.AddVar("valvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 3})
	st.AddVar("wvalvar", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 4})
	st.AddVar("waddrvar", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 5})
	st.AddAbsolute("zpptr", "", compiler.KindWord, 0x80, preproc.Line{Filename: "test.c65", LineNo: 6})
	st.AddVar("offsvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 7})
	st.AddAbsolute("byaddr", "", compiler.KindByte, 0x40, preproc.Line{Filename: "test.c65", LineNo: 8})
}

func newCtx() *compiler.CompilerContext {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)
	setupPokeVars(ctx.SymbolTable)
	return ctx
}

func newLine(text string, pragma *preproc.Pragma) preproc.Line {
	return preproc.Line{
		Text:           text,
		Kind:           preproc.Source,
		PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
	}
}

// =============================================================================
// POKE comma-spacing tests (constant address + literal value — Case 4)
// =============================================================================

func TestPokeCommaSpacing(t *testing.T) {
	expectedAsm := []string{
		"\tlda #5",
		"\tsta 53280",
	}

	tests := []struct {
		name string
		line string
	}{
		{"space before and after comma", "POKE $d020 , 5"},
		{"no space before or after comma", "POKE $d020,5"},
		{"no space before comma, space after", "POKE $d020, 5"},
		{"space before comma, no space after", "POKE $d020 ,5"},
		{"no space comma, expression address", "POKE $d020+0, 5"},
		{"WITH keyword", "POKE $d020 WITH 5"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()
			cmd := &PokeCommand{}
			line := newLine(tt.line, ctx.Pragma)

			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 !equalAsm(asm, expectedAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(expectedAsm, "\n"))
			}
		})
	}
}

// =============================================================================
// POKEW comma-spacing tests (constant address + literal value — Case 4)
// =============================================================================

func TestPokeWCommaSpacing(t *testing.T) {
	expectedAsm := []string{
		"\tlda #$65",
		"\tsta 53280",
		"\tlda #$00",
		"\tsta 53281",
	}

	tests := []struct {
		name string
		line string
	}{
		{"space before and after comma", "POKEW $d020 , 101"},
		{"no space before or after comma", "POKEW $d020,101"},
		{"no space before comma, space after", "POKEW $d020, 101"},
		{"space before comma, no space after", "POKEW $d020 ,101"},
		{"WITH keyword", "POKEW $d020 WITH 101"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()
			cmd := &PokeWCommand{}
			line := newLine(tt.line, ctx.Pragma)

			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 !equalAsm(asm, expectedAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(expectedAsm, "\n"))
			}
		})
	}
}

// =============================================================================
// POKE Case 4 — Direct addressing (expression/constant address)
// =============================================================================

func TestPokeDirectAddr(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		wantAsm []string
	}{
		{
			name: "expression constant + expression value, no-space comma",
			line: "POKE vic2+15,5+2",
			wantAsm: []string{
				"\tlda #7",
				"\tsta 53263",
			},
		},
		{
			name: "variable value, no-space comma",
			line: "POKE $d020,valvar",
			wantAsm: []string{
				"\tlda valvar",
				"\tsta 53280",
			},
		},
		{
			name: "variable value, space before comma",
			line: "POKE $d020 ,valvar",
			wantAsm: []string{
				"\tlda valvar",
				"\tsta 53280",
			},
		},
		{
			name: "variable value, space after comma",
			line: "POKE $d020, valvar",
			wantAsm: []string{
				"\tlda valvar",
				"\tsta 53280",
			},
		},
		{
			name: "expression address + var value, no-space comma",
			line: "POKE vic2+15,valvar",
			wantAsm: []string{
				"\tlda valvar",
				"\tsta 53263",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()
			cmd := &PokeCommand{}
			line := newLine(tt.line, ctx.Pragma)

			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 !equalAsm(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}

// =============================================================================
// POKE Case 1 — ZP pointer (indexed indirect addressing)
// =============================================================================

func TestPokeZPPointer(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		wantAsm []string
	}{
		{
			name: "no offset, literal value, no-space comma",
			line: "POKE zpptr,10",
			wantAsm: []string{
				"\tldy #0",
				"\tlda #10",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "no offset, var value, no-space comma",
			line: "POKE zpptr,valvar",
			wantAsm: []string{
				"\tldy #0",
				"\tlda valvar",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "literal offset, literal value, no-space comma",
			line: "POKE zpptr[5],10",
			wantAsm: []string{
				"\tldy #5",
				"\tlda #10",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "literal offset, var value, no-space comma",
			line: "POKE zpptr[5],valvar",
			wantAsm: []string{
				"\tldy #5",
				"\tlda valvar",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "var offset, literal value, no-space comma",
			line: "POKE zpptr[offsvar],10",
			wantAsm: []string{
				"\tldy offsvar",
				"\tlda #10",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "var offset, var value, no-space comma",
			line: "POKE zpptr[offsvar],valvar",
			wantAsm: []string{
				"\tldy offsvar",
				"\tlda valvar",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "literal offset, var value, spaced comma",
			line: "POKE zpptr[5] , valvar",
			wantAsm: []string{
				"\tldy #5",
				"\tlda valvar",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "word variable value (uses low byte), no-space comma",
			line: "POKE zpptr[5],wvalvar",
			wantAsm: []string{
				"\tldy #5",
				"\tlda wvalvar",
				"\tsta (zpptr),y",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()
			cmd := &PokeCommand{}
			line := newLine(tt.line, ctx.Pragma)

			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 !equalAsm(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}

// =============================================================================
// POKE Case 2 — Byte variable address (self-modifying code)
// =============================================================================

func TestPokeSMByteAddr(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		wantAsm []string
	}{
		{
			name: "literal value, no-space comma",
			line: "POKE addrvar,10",
			wantAsm: []string{
				"\tlda addrvar",
				"\tsta _L1+1",
				"\tlda #10",
				"_L1",
				"\tsta $ff",
			},
		},
		{
			name: "var value, no-space comma",
			line: "POKE addrvar,valvar",
			wantAsm: []string{
				"\tlda addrvar",
				"\tsta _L1+1",
				"\tlda valvar",
				"_L1",
				"\tsta $ff",
			},
		},
		{
			name: "word var value (uses low byte), no-space comma",
			line: "POKE addrvar,wvalvar",
			wantAsm: []string{
				"\tlda addrvar",
				"\tsta _L1+1",
				"\tlda wvalvar",
				"_L1",
				"\tsta $ff",
			},
		},
		{
			name: "literal value, spaced comma",
			line: "POKE addrvar , 10",
			wantAsm: []string{
				"\tlda addrvar",
				"\tsta _L1+1",
				"\tlda #10",
				"_L1",
				"\tsta $ff",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()
			cmd := &PokeCommand{}
			line := newLine(tt.line, ctx.Pragma)

			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 !equalAsm(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}

// =============================================================================
// POKE Case 3 — Word variable address (self-modifying code)
// =============================================================================

func TestPokeSMWordAddr(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		wantAsm []string
	}{
		{
			name: "literal value, no-space comma",
			line: "POKE waddrvar,10",
			wantAsm: []string{
				"\tlda waddrvar",
				"\tsta _L1+1",
				"\tlda waddrvar+1",
				"\tsta _L1+2",
				"\tlda #10",
				"_L1",
				"\tsta $ffff",
			},
		},
		{
			name: "var value, no-space comma",
			line: "POKE waddrvar,valvar",
			wantAsm: []string{
				"\tlda waddrvar",
				"\tsta _L1+1",
				"\tlda waddrvar+1",
				"\tsta _L1+2",
				"\tlda valvar",
				"_L1",
				"\tsta $ffff",
			},
		},
		{
			name: "word var value (uses low byte), no-space comma",
			line: "POKE waddrvar,wvalvar",
			wantAsm: []string{
				"\tlda waddrvar",
				"\tsta _L1+1",
				"\tlda waddrvar+1",
				"\tsta _L1+2",
				"\tlda wvalvar",
				"_L1",
				"\tsta $ffff",
			},
		},
		{
			name: "literal value, spaced comma",
			line: "POKE waddrvar , 10",
			wantAsm: []string{
				"\tlda waddrvar",
				"\tsta _L1+1",
				"\tlda waddrvar+1",
				"\tsta _L1+2",
				"\tlda #10",
				"_L1",
				"\tsta $ffff",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()
			cmd := &PokeCommand{}
			line := newLine(tt.line, ctx.Pragma)

			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 !equalAsm(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}

// =============================================================================
// POKEW Case 4 — Direct addressing
// =============================================================================

func TestPokeWDirectAddr(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		wantAsm []string
	}{
		{
			name: "literal value, no-space comma",
			line: "POKEW $d020,101",
			wantAsm: []string{
				"\tlda #$65",
				"\tsta 53280",
				"\tlda #$00",
				"\tsta 53281",
			},
		},
		{
			name: "literal value >255, no-space comma",
			line: "POKEW $d020,$1234",
			wantAsm: []string{
				"\tlda #$34",
				"\tsta 53280",
				"\tlda #$12",
				"\tsta 53281",
			},
		},
		{
			name: "word var value, no-space comma",
			line: "POKEW $d020,wvalvar",
			wantAsm: []string{
				"\tlda wvalvar",
				"\tsta 53280",
				"\tlda wvalvar+1",
				"\tsta 53281",
			},
		},
		{
			name: "word var value, spaced comma",
			line: "POKEW $d020 , wvalvar",
			wantAsm: []string{
				"\tlda wvalvar",
				"\tsta 53280",
				"\tlda wvalvar+1",
				"\tsta 53281",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()
			cmd := &PokeWCommand{}
			line := newLine(tt.line, ctx.Pragma)

			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 !equalAsm(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}

// =============================================================================
// POKEW Case 1 — ZP pointer (indexed indirect, two stores)
// =============================================================================

func TestPokeWZPPointer(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		wantAsm []string
	}{
		{
			name: "no offset, literal value, no-space comma",
			line: "POKEW zpptr,101",
			wantAsm: []string{
				"\tldy #0",
				"\tlda #$65",
				"\tsta (zpptr),y",
				"\tiny",
				"\tlda #$00",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "no offset, word var value, no-space comma",
			line: "POKEW zpptr,wvalvar",
			wantAsm: []string{
				"\tldy #0",
				"\tlda wvalvar",
				"\tsta (zpptr),y",
				"\tiny",
				"\tlda wvalvar+1",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "literal offset, literal value, no-space comma",
			line: "POKEW zpptr[5],101",
			wantAsm: []string{
				"\tldy #5",
				"\tlda #$65",
				"\tsta (zpptr),y",
				"\tiny",
				"\tlda #$00",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "literal offset, word var value, no-space comma",
			line: "POKEW zpptr[5],wvalvar",
			wantAsm: []string{
				"\tldy #5",
				"\tlda wvalvar",
				"\tsta (zpptr),y",
				"\tiny",
				"\tlda wvalvar+1",
				"\tsta (zpptr),y",
			},
		},
		{
			name: "literal offset, literal value, spaced comma",
			line: "POKEW zpptr[5] , 101",
			wantAsm: []string{
				"\tldy #5",
				"\tlda #$65",
				"\tsta (zpptr),y",
				"\tiny",
				"\tlda #$00",
				"\tsta (zpptr),y",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()
			cmd := &PokeWCommand{}
			line := newLine(tt.line, ctx.Pragma)

			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 !equalAsm(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}

// =============================================================================
// POKEW Case 2 — Byte variable address (zero-page indexed)
// =============================================================================

func TestPokeWByteAddr(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		wantAsm []string
	}{
		{
			name: "literal value, no-space comma",
			line: "POKEW byaddr,101",
			wantAsm: []string{
				"\tldx byaddr",
				"\tlda #$65",
				"\tsta $00,x",
				"\tinx",
				"\tlda #$00",
				"\tsta $00,x",
			},
		},
		{
			name: "word var value, no-space comma",
			line: "POKEW byaddr,wvalvar",
			wantAsm: []string{
				"\tldx byaddr",
				"\tlda wvalvar",
				"\tsta $00,x",
				"\tinx",
				"\tlda wvalvar+1",
				"\tsta $00,x",
			},
		},
		{
			name: "literal value, spaced comma",
			line: "POKEW byaddr , 101",
			wantAsm: []string{
				"\tldx byaddr",
				"\tlda #$65",
				"\tsta $00,x",
				"\tinx",
				"\tlda #$00",
				"\tsta $00,x",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()
			cmd := &PokeWCommand{}
			line := newLine(tt.line, ctx.Pragma)

			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 !equalAsm(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}

// =============================================================================
// POKEW Case 3 — Word variable address (self-modifying code)
// =============================================================================

func TestPokeWSMWordAddr(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		wantAsm []string
	}{
		{
			name: "literal value, no-space comma",
			line: "POKEW waddrvar,101",
			wantAsm: []string{
				"\tlda waddrvar",
				"\tsta _L1+1",
				"\tsta _L2+1",
				"\tlda waddrvar+1",
				"\tsta _L1+2",
				"\tsta _L2+2",
				"\tlda #$65",
				"_L1",
				"\tsta $ffff",
				"\tinc _L2+1",
				"\tbne _L2",
				"\tinc _L2+2",
				"_L2",
				"\tlda #$00",
				"\tsta $ffff",
			},
		},
		{
			name: "word var value, no-space comma",
			line: "POKEW waddrvar,wvalvar",
			wantAsm: []string{
				"\tlda waddrvar",
				"\tsta _L1+1",
				"\tsta _L2+1",
				"\tlda waddrvar+1",
				"\tsta _L1+2",
				"\tsta _L2+2",
				"\tlda wvalvar",
				"_L1",
				"\tsta $ffff",
				"\tinc _L2+1",
				"\tbne _L2",
				"\tinc _L2+2",
				"_L2",
				"\tlda wvalvar+1",
				"\tsta $ffff",
			},
		},
		{
			name: "literal value, spaced comma",
			line: "POKEW waddrvar , 101",
			wantAsm: []string{
				"\tlda waddrvar",
				"\tsta _L1+1",
				"\tsta _L2+1",
				"\tlda waddrvar+1",
				"\tsta _L1+2",
				"\tsta _L2+2",
				"\tlda #$65",
				"_L1",
				"\tsta $ffff",
				"\tinc _L2+1",
				"\tbne _L2",
				"\tinc _L2+2",
				"_L2",
				"\tlda #$00",
				"\tsta $ffff",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()
			cmd := &PokeWCommand{}
			line := newLine(tt.line, ctx.Pragma)

			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 !equalAsm(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}



// =============================================================================
// Error cases
// =============================================================================

func TestPokeErrors(t *testing.T) {
	tests := []struct {
		name    string
		line    string
		wantErr string
	}{
		{
			name:    "offset on non-ZP word pointer",
			line:    "POKE waddrvar[5],10",
			wantErr: "POKE: offset",
		},
		{
			name:    "value out of byte range",
			line:    "POKE $d020,256",
			wantErr: "out of byte range",
		},
		{
			name:    "POKEW with byte variable as value",
			line:    "POKEW $d020,valvar",
			wantErr: "cannot use byte variable",
		},
		{
			name:    "POKEW self-referential (zp pointer == value)",
			line:    "POKEW zpptr,zpptr",
			wantErr: "writing pointer",
		},
		{
			name:    "invalid separator",
			line:    "POKE $d020 WRONG 5",
			wantErr: "must be 'WITH' or ','",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := newCtx()

			if strings.HasPrefix(tt.line, "POKEW") {
				cmd := &PokeWCommand{}
				line := newLine(tt.line, ctx.Pragma)
				err := cmd.Interpret(line, ctx)
				if err == nil {
					t.Fatal("Interpret() expected error but got nil")
				}
				if !strings.Contains(err.Error(), tt.wantErr) {
					t.Errorf("Interpret() error = %q, want containing %q", err.Error(), tt.wantErr)
				}
				return
			}

			cmd := &PokeCommand{}
			line := newLine(tt.line, ctx.Pragma)
			err := cmd.Interpret(line, ctx)
			if err == nil {
				t.Fatal("Interpret() expected error but got nil")
			}
			if !strings.Contains(err.Error(), tt.wantErr) {
				t.Errorf("Interpret() error = %q, want containing %q", err.Error(), tt.wantErr)
			}
		})
	}
}

func TestPokePragmaImmutable(t *testing.T) {
	t.Run("SM byte addr with USE_IMMUTABLE_CODE", func(t *testing.T) {
		pragma := preproc.NewPragma()
		pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1")
		ctx := compiler.NewCompilerContext(pragma)
		ctx.SymbolTable.AddVar("addrvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

		cmd := &PokeCommand{}
		line := preproc.Line{
			Text:           "POKE addrvar,10",
			Kind:           preproc.Source,
			PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
		}

		if err := cmd.Interpret(line, ctx); err != nil {
			t.Fatalf("Interpret() error = %v", err)
		}
		_, err := cmd.Generate(ctx)
		if err == nil {
			t.Fatal("Generate() expected error with USE_IMMUTABLE_CODE")
		}
		if !strings.Contains(err.Error(), "USE_IMMUTABLE_CODE") {
			t.Errorf("Generate() error = %q, want containing USE_IMMUTABLE_CODE", err.Error())
		}
	})

	t.Run("SM word addr with USE_IMMUTABLE_CODE", func(t *testing.T) {
		pragma := preproc.NewPragma()
		pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1")
		ctx := compiler.NewCompilerContext(pragma)
		ctx.SymbolTable.AddVar("waddrvar", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

		cmd := &PokeCommand{}
		line := preproc.Line{
			Text:           "POKE waddrvar,10",
			Kind:           preproc.Source,
			PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
		}

		if err := cmd.Interpret(line, ctx); err != nil {
			t.Fatalf("Interpret() error = %v", err)
		}
		_, err := cmd.Generate(ctx)
		if err == nil {
			t.Fatal("Generate() expected error with USE_IMMUTABLE_CODE")
		}
		if !strings.Contains(err.Error(), "USE_IMMUTABLE_CODE") {
			t.Errorf("Generate() error = %q, want containing USE_IMMUTABLE_CODE", err.Error())
		}
	})

	t.Run("ZP pointer is allowed with USE_IMMUTABLE_CODE", func(t *testing.T) {
		pragma := preproc.NewPragma()
		pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1")
		ctx := compiler.NewCompilerContext(pragma)
		ctx.SymbolTable.AddAbsolute("zpptr", "", compiler.KindWord, 0x80, preproc.Line{Filename: "test.c65", LineNo: 1})

		cmd := &PokeCommand{}
		line := preproc.Line{
			Text:           "POKE zpptr,10",
			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 (ZP pointers should be allowed)", err)
		}
		expected := []string{
			"\tldy #0",
			"\tlda #10",
			"\tsta (zpptr),y",
		}
		if !equalAsm(asm, expected) {
			t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
				strings.Join(asm, "\n"),
				strings.Join(expected, "\n"))
		}
	})

	t.Run("direct addr is allowed with USE_IMMUTABLE_CODE", func(t *testing.T) {
		pragma := preproc.NewPragma()
		pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1")
		ctx := compiler.NewCompilerContext(pragma)

		cmd := &PokeCommand{}
		line := preproc.Line{
			Text:           "POKE $d020,5",
			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 (direct addr should be allowed)", err)
		}
		expected := []string{
			"\tlda #5",
			"\tsta 53280",
		}
		if !equalAsm(asm, expected) {
			t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
				strings.Join(asm, "\n"),
				strings.Join(expected, "\n"))
		}
	})
}
