package optimizer

import (
	"strings"
	"testing"
)

func lines(s ...string) []string { return s }

func TestPassLoadElimination(t *testing.T) {
	tests := []struct {
		name     string
		input    []string
		expected int // expected number of lines after optimization
	}{
		{
			name:     "redundant same variable",
			input:    lines("\tlda x", "\tsta y", "\tlda x"),
			expected: 2, // third lda removed
		},
		{
			name:     "not redundant - store to same variable",
			input:    lines("\tlda x", "\tsta x", "\tlda x"),
			expected: 3, // X was stored, so A is stale
		},
		{
			name:     "not redundant - arithmetic in between",
			input:    lines("\tlda x", "\tadc #1", "\tlda x"),
			expected: 3, // A was modified
		},
		{
			name:     "redundant - label resets state",
			input:    lines("\tlda x", "label", "\tlda x"),
			expected: 3, // label resets state, second lda not redundant
		},
		{
			name:     "x register redundant",
			input:    lines("\tldx counter", "\tldy counter", "\tldx counter"),
			expected: 2, // third ldx is redundant (X still holds counter)
		},
		{
			name:     "immediate loads not affected",
			input:    lines("\tlda #5", "\tsta x", "\tlda #5"),
			expected: 3, // handled by imm pass
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parsed := parseLines(tt.input)
			result := passLoadElimination(parsed, nil)
			cleaned := stripOptMarkers(result)
			if got := len(cleaned); got != tt.expected {
				t.Errorf("got %d lines, want %d\ninput: %v\noutput: %v", got, tt.expected, tt.input, linesToString(cleaned))
			}
		})
	}
}

func TestPassImmElimination(t *testing.T) {
	tests := []struct {
		name     string
		input    []string
		expected int
	}{
		{
			name:     "redundant same immediate",
			input:    lines("\tlda #$05", "\tsta y", "\tlda #$05"),
			expected: 2, // third removed
		},
		{
			name:     "different immediate values",
			input:    lines("\tlda #5", "\tsta y", "\tlda #3"),
			expected: 3, // different values
		},
		{
			name:     "arithmetic invalidates",
			input:    lines("\tlda #5", "\tadc #1", "\tlda #5"),
			expected: 3, // adc modified A
		},
		{
			name:     "x and y independent",
			input:    lines("\tldx #$00", "\tldy #$00", "\tldx #$00"),
			expected: 2, // first ldx removed? No, let me check: ldx #0 then ldy #0, X still 0, so third redundant
			// Actually: ldx#0(X=0), ldy#0(Y=0,X=0), ldx#0(X=0,redundant)
			// Result: ldx#0, ldy#0 → 2 lines
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parsed := parseLines(tt.input)
			result := passImmElimination(parsed)
			cleaned := stripOptMarkers(result)
			if got := len(cleaned); got != tt.expected {
				t.Errorf("got %d lines, want %d\ninput: %v\noutput: %v", got, tt.expected, tt.input, linesToString(cleaned))
			}
		})
	}
}

func TestPassJmpNext(t *testing.T) {
	tests := []struct {
		name     string
		input    []string
		expected int
	}{
		{
			name:     "jmp to next label",
			input:    lines("\tjmp _L1", "_L1"),
			expected: 1, // jmp removed
		},
		{
			name:     "jmp not to next label",
			input:    lines("\tjmp _L1", "\tlda x", "_L1"),
			expected: 3, // not immediately followed by label
		},
		{
			name:     "label different from jmp target",
			input:    lines("\tjmp _L1", "_L2"),
			expected: 2, // different labels
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parsed := parseLines(tt.input)
			result := passJmpNext(parsed)
			cleaned := stripOptMarkers(result)
			if got := len(cleaned); got != tt.expected {
				t.Errorf("got %d lines, want %d\ninput: %v\noutput: %v", got, tt.expected, tt.input, linesToString(cleaned))
			}
		})
	}
}

func TestPassSelfAssignment(t *testing.T) {
	tests := []struct {
		name     string
		input    []string
		expected int
	}{
		{
			name:     "self assignment",
			input:    lines("\tlda x", "\tsta x"),
			expected: 0, // both removed
		},
		{
			name:     "not self - different variables",
			input:    lines("\tlda x", "\tsta y"),
			expected: 2, // different
		},
		{
			name:     "not self - immediate load",
			input:    lines("\tlda #$05", "\tsta #$05"),
			expected: 2, // immediate + immediate doesn't match
		},
		{
			name:     "not self - SM code pattern (+ offset)",
			input:    lines("\tlda x", "\tsta _L1+1"),
			expected: 2, // SM code pattern
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parsed := parseLines(tt.input)
			result := passSelfAssignment(parsed)
			cleaned := stripOptMarkers(result)
			if got := len(cleaned); got != tt.expected {
				t.Errorf("got %d lines, want %d\ninput: %v\noutput: %v", got, tt.expected, tt.input, linesToString(cleaned))
			}
		})
	}
}

func TestPassLoadIO(t *testing.T) {
	cfg := &Config{}
	cfg.IOMap[0xD012] = true // raster line register — changes constantly

	t.Run("IO load not eliminated", func(t *testing.T) {
		parsed := parseLines(lines("\tlda $D012", "\tlda $D012"))
		result := passLoadElimination(parsed, cfg)
		cleaned := stripOptMarkers(result)
		if len(cleaned) != 2 {
			t.Errorf("expected 2 lines (IO skip), got %d", len(cleaned))
		}
	})

	t.Run("non-IO load eliminated", func(t *testing.T) {
		parsed := parseLines(lines("\tlda $C000", "\tlda $C000"))
		result := passLoadElimination(parsed, cfg)
		cleaned := stripOptMarkers(result)
		if len(cleaned) != 1 {
			t.Errorf("expected 1 line (non-IO), got %d", len(cleaned))
		}
	})

	t.Run("variable name not caught by IO", func(t *testing.T) {
		parsed := parseLines(lines("\tlda RASTER_LINE", "\tlda RASTER_LINE"))
		result := passLoadElimination(parsed, cfg)
		cleaned := stripOptMarkers(result)
		if len(cleaned) != 1 {
			t.Errorf("expected 1 line (var not IO), got %d", len(cleaned))
		}
	})
}

func TestPassLoadNoCommentReset(t *testing.T) {
	// Comments should NOT reset register state
	parsed := parseLines(lines(
		"\tlda b",
		"\tsta a",
		"; comment",
		"\tlda b", // should be redundant
	))
	result := passLoadElimination(parsed, nil)
	if len(result) != 3 {
		t.Errorf("expected 3 lines (comment kept, lda b removed), got %d:\n%v", len(result), linesToString(result))
	}
}

func TestOptimizeIntegration(t *testing.T) {
	input := lines(
		"; a = b",
		"; @@OPT:LINEAR:LET",
		"\tlda b",
		"\tsta a",
		"; second use of b (redundant load)",
		"; @@OPT:LINEAR:LET",
		"\tlda b",
		"\tsta c",
	)

	cfg := &Config{EnableLoad: true}
	output, _ := Optimize(input, cfg)

	// 2 source comments + 3 asm lines (lda b removed) = 5
	if len(output) != 5 {
		t.Errorf("expected 5 lines, got %d:\n%v", len(output), output)
	}
}

func TestOptimizeWithDebug(t *testing.T) {
	input := lines(
		"\tlda x",
		"; @@OPT:LINEAR:LET",
		"\tlda x",
		"\tsta y",
	)

	cfg := &Config{EnableLoad: true, Debug: true}
	output, _ := Optimize(input, cfg)

	// Header + 2 kept lines + 1 removed annotation = 4
	if len(output) != 4 {
		t.Errorf("expected 4 lines, got %d:\n%v", len(output), output)
	}
	if !containsPrefix(output, "; --- peephole:") {
		t.Errorf("expected peephole summary line:\n%v", output)
	}
	if !containsSubstring(output, "[removed]") {
		t.Errorf("expected [removed] annotation:\n%v", output)
	}
}

func containsPrefix(lines []string, prefix string) bool {
	for _, l := range lines {
		if len(l) >= len(prefix) && l[:len(prefix)] == prefix {
			return true
		}
	}
	return false
}

func containsSubstring(lines []string, sub string) bool {
	for _, l := range lines {
		for i := 0; i <= len(l)-len(sub); i++ {
			if l[i:i+len(sub)] == sub {
				return true
			}
		}
	}
	return false
}

func TestPassStoreReload(t *testing.T) {
	cfg := &Config{}

	tests := []struct {
		name     string
		input    []string
		expected int
	}{
		{
			name:     "sta x then lda x",
			input:    lines("\tsta x", "\tlda x"),
			expected: 1,
		},
		{
			name:     "different variables",
			input:    lines("\tsta x", "\tlda y"),
			expected: 2,
		},
		{
			name:     "ldy between sta and lda",
			input:    lines("\tsta x", "\tldy #0", "\tlda x"),
			expected: 2,
		},
		{
			name:     "adc between — modifies A",
			input:    lines("\tsta x", "\tadc #1", "\tlda x"),
			expected: 3,
		},
		{
			name:     "label between — barrier",
			input:    lines("\tsta x", "label", "\tlda x"),
			expected: 3,
		},
		{
			name:     "different registers",
			input:    lines("\tsta x", "\tldx x"),
			expected: 2,
		},
		{
			name:     "comment between",
			input:    lines("\tsta x", "; source line", "\tlda x"),
			expected: 2,
		},
		{
			name:     "comment and ldy between",
			input:    lines("\tsta x", "; POKE addr", "\tldy #5", "\tlda x"),
			expected: 3,
		},
		{
			name:     "SM code — operand with +",
			input:    lines("\tsta _L1+1", "\tlda _L1+1"),
			expected: 2,
		},
		{
			name:     "indexed store — operand with (",
			input:    lines("\tsta (zp),y", "\tlda (zp),y"),
			expected: 2,
		},
		{
			name:     "indexed store — operand with ,",
			input:    lines("\tsta $D020,x", "\tlda $D020,x"),
			expected: 2,
		},
		{
			name:     "different operand offsets",
			input:    lines("\tsta x", "\tlda x+1"),
			expected: 2,
		},
		{
			name:     "inx between — does not modify A",
			input:    lines("\tsta x", "\tinx", "\tlda x"),
			expected: 2,
		},
		{
			name:     "dex between — does not modify A",
			input:    lines("\tsta x", "\tdex", "\tlda x"),
			expected: 2,
		},
		{
			name:     "iny between — does not modify A",
			input:    lines("\tsta x", "\tiny", "\tlda x"),
			expected: 2,
		},
		{
			name:     "dey between — does not modify A",
			input:    lines("\tsta x", "\tdey", "\tlda x"),
			expected: 2,
		},
		{
			name:     "tax between — does not modify A",
			input:    lines("\tsta x", "\ttax", "\tlda x"),
			expected: 2,
		},
		{
			name:     "tay between — does not modify A",
			input:    lines("\tsta x", "\ttay", "\tlda x"),
			expected: 2,
		},
		{
			name:     "txa between — modifies A, keep lda",
			input:    lines("\tsta x", "\ttxa", "\tlda x"),
			expected: 3,
		},
		{
			name:     "tya between — modifies A, keep lda",
			input:    lines("\tsta x", "\ttya", "\tlda x"),
			expected: 3,
		},
		{
			name:     "pla between — modifies A, keep lda",
			input:    lines("\tsta x", "\tpla", "\tlda x"),
			expected: 3,
		},
		{
			name:     "multiple stores — pick the last sta",
			input:    lines("\tsta x", "\tsta y", "\tlda x"),
			expected: 2, // sta x kept, sta y kept, lda x removed
		},
		{
			name:     "@@OPT marker between — skip, lda removed",
			input:    lines("\tsta x", "; @@OPT:LINEAR:LET", "\tlda x"),
			expected: 1, // @@OPT stripped by stripOptMarkers
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parsed := parseLines(tt.input)
			result := passStoreReload(parsed, cfg)
			cleaned := stripOptMarkers(result)
			if got := len(cleaned); got != tt.expected {
				t.Errorf("got %d lines, want %d\ninput: %v\noutput: %v", got, tt.expected, tt.input, linesToString(cleaned))
			}
		})
	}
}

func TestPassStoreReloadIO(t *testing.T) {
	t.Run("c64 border color", func(t *testing.T) {
		cfg := &Config{}
		cfg.IOMap[0xD020] = true
		parsed := parseLines(lines("\tsta $D020", "\tlda $D020"))
		result := passStoreReload(parsed, cfg)
		cleaned := stripOptMarkers(result)
		if len(cleaned) != 2 {
			t.Errorf("expected 2 lines (I/O skip), got %d", len(cleaned))
		}
	})

	t.Run("non-IO address", func(t *testing.T) {
		cfg := &Config{}
		cfg.IOMap[0xD020] = true
		parsed := parseLines(lines("\tsta $C000", "\tlda $C000"))
		result := passStoreReload(parsed, cfg)
		cleaned := stripOptMarkers(result)
		if len(cleaned) != 1 {
			t.Errorf("expected 1 line (non-IO), got %d", len(cleaned))
		}
	})

	t.Run("decimal address in IO range", func(t *testing.T) {
		cfg := &Config{}
		cfg.IOMap[0xD020] = true
		// Decimal 53280 = $D020, but IOMap uses hex lookup
		// The pass checks $ prefix only, decimal addresses won't be caught by IOMap
		parsed := parseLines(lines("\tsta 53280", "\tlda 53280"))
		result := passStoreReload(parsed, cfg)
		cleaned := stripOptMarkers(result)
		if len(cleaned) != 1 {
			t.Errorf("expected 1 line (decimal not caught by I/O), got %d", len(cleaned))
		}
	})

	t.Run("variable name in IO region", func(t *testing.T) {
		cfg := &Config{}
		cfg.IOMap[0xD020] = true
		// Variable names like VIC_BORDER aren't checked against IOMap
		parsed := parseLines(lines("\tsta BORDER_COLOR", "\tlda BORDER_COLOR"))
		result := passStoreReload(parsed, cfg)
		cleaned := stripOptMarkers(result)
		if len(cleaned) != 1 {
			t.Errorf("expected 1 line (var name not I/O), got %d", len(cleaned))
		}
	})

	t.Run("decimal address not in IO range", func(t *testing.T) {
		cfg := &Config{}
		cfg.IOMap[0xD020] = true
		parsed := parseLines(lines("\tsta 49152", "\tlda 49152"))
		result := passStoreReload(parsed, cfg)
		cleaned := stripOptMarkers(result)
		if len(cleaned) != 1 {
			t.Errorf("expected 1 line (decimal not IO), got %d", len(cleaned))
		}
	})

	t.Run("IOMap from AddIORegions", func(t *testing.T) {
		cfg := &Config{}
		cfg.AddIORegions([]IORegion{{Start: 0xD000, End: 0xDFFF}})
		parsed := parseLines(lines("\tsta $D020", "\tlda $D020"))
		result := passStoreReload(parsed, cfg)
		cleaned := stripOptMarkers(result)
		if len(cleaned) != 2 {
			t.Errorf("expected 2 lines (I/O via AddIORegions), got %d", len(cleaned))
		}
	})

	t.Run("multiple IO regions", func(t *testing.T) {
		cfg := &Config{}
		cfg.AddIORegions([]IORegion{
			{Start: 0xD000, End: 0xDFFF},
			{Start: 0xDC00, End: 0xDC0F},
		})
		tests := []struct{
			addr string
			expect int
		}{
			{"$D020", 2},
			{"$DC00", 2},
			{"$DC0F", 2},
			{"$C000", 1},
			{"$E000", 1},
		}
		for _, tt := range tests {
			parsed := parseLines(lines("\tsta " + tt.addr, "\tlda " + tt.addr))
			result := passStoreReload(parsed, cfg)
			cleaned := stripOptMarkers(result)
			if len(cleaned) != tt.expect {
				t.Errorf("addr %s: expected %d lines, got %d", tt.addr, tt.expect, len(cleaned))
			}
		}
	})
}

func TestPassRegDead_DeadStore(t *testing.T) {
	input := []string{
		"\tlda #$05",
		"\tsta myFunc_temp",
		"\tsta $d020",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"myFunc_temp": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if !dissolved["myFunc_temp"] {
		t.Error("expected myFunc_temp to be dissolved")
	}

	joined := strings.Join(output, "\n")
	if strings.Contains(joined, "myFunc_temp") {
		t.Errorf("expected sta myFunc_temp to be removed, got:\n%s", joined)
	}
}

func TestPassRegDead_KeptStoreWithLoad(t *testing.T) {
	input := []string{
		"\tlda #$05",
		"\tsta myFunc_temp",
		"\tlda myFunc_temp",
		"\tsta $d020",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"myFunc_temp": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if dissolved["myFunc_temp"] {
		t.Error("expected myFunc_temp NOT to be dissolved")
	}

	joined := strings.Join(output, "\n")
	if !strings.Contains(joined, "myFunc_temp") {
		t.Errorf("expected sta myFunc_temp to be kept, got:\n%s", joined)
	}
}

func TestPassRegDead_KeptStoreAtLabel(t *testing.T) {
	// sta regVar before a label — no lda regVar anywhere → globally dead
	input := []string{
		"\tlda #$05",
		"\tsta myFunc_temp",
		"myskip:",
		"\tnop",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"myFunc_temp": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if !dissolved["myFunc_temp"] {
		t.Error("expected myFunc_temp to be dissolved — globally dead")
	}

	joined := strings.Join(output, "\n")
	if strings.Contains(joined, "sta myFunc_temp") {
		t.Errorf("expected sta myFunc_temp to be removed (globally dead), got:\n%s", joined)
	}
}

func TestPassRegDead_NonRegisterVarUnchanged(t *testing.T) {
	input := []string{
		"\tlda #$05",
		"\tsta normalVar",
		"\tsta $d020",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{},
	}

	output, _ := Optimize(input, cfg)

	joined := strings.Join(output, "\n")
	if !strings.Contains(joined, "sta normalVar") {
		t.Errorf("expected sta normalVar to be kept (not a register var), got:\n%s", joined)
	}
}

func TestPassRegDead_DeadStoreBeforeRts(t *testing.T) {
	input := []string{
		"\tlda #$05",
		"\tsta myFunc_temp",
		"\tsta $d020",
		"\trts",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"myFunc_temp": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if !dissolved["myFunc_temp"] {
		t.Error("expected myFunc_temp to be dissolved")
	}

	joined := strings.Join(output, "\n")
	if strings.Contains(joined, "sta myFunc_temp") {
		t.Errorf("expected sta myFunc_temp to be removed (dead before rts), got:\n%s", joined)
	}
}

func TestPassRegDead_StoreNeededAfterAClobber(t *testing.T) {
	// sta regVar; lda other → A clobbered, but regVar is reloaded later → keep store
	input := []string{
		"\tlda 53281",
		"\tsta spill_me_bg",
		"\tlda 53282",
		"\tsta spill_me_mc1",
		"\tlda spill_me_bg",
		"\tsta 53282",
		"\trts",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"spill_me_bg":  true,
			"spill_me_mc1": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if dissolved["spill_me_bg"] {
		t.Error("spill_me_bg should NOT be dissolved — its value is reloaded")
	}

	joined := strings.Join(output, "\n")
	if !strings.Contains(joined, "sta spill_me_bg") {
		t.Errorf("expected sta spill_me_bg to be kept (reloaded after A clobber), got:\n%s", joined)
	}
}

func TestPassRegDead_DeadStoreNeverReloaded(t *testing.T) {
	// sta → and (A clobbered) → POKE (uses A directly) → rts. Store dead.
	input := []string{
		"\tlda 56576",
		"\tsta dissolve_me_temp",
		"\tand #$fc",
		"\tsta dissolve_me_temp",
		"\tsta 56576",
		"\trts",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"dissolve_me_temp": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if !dissolved["dissolve_me_temp"] {
		t.Error("dissolve_me_temp should be dissolved — never reloaded after either store")
	}

	joined := strings.Join(output, "\n")
	if strings.Contains(joined, "sta dissolve_me_temp") {
		t.Errorf("expected all sta dissolve_me_temp to be removed, got:\n%s", joined)
	}
}

func TestPassRegDead_GloballyDeadBeforeJsr(t *testing.T) {
	// sta regVar; jsr foo; no lda regVar anywhere → globally dead
	// jsr does not protect a store with zero readers.
	input := []string{
		"\tlda 53281",
		"\tsta call_val",
		"\tjsr helper",
		"\trts",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"call_val": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if !dissolved["call_val"] {
		t.Error("call_val should be dissolved — globally dead")
	}

	joined := strings.Join(output, "\n")
	if strings.Contains(joined, "sta call_val") {
		t.Errorf("expected sta call_val to be removed (globally dead), got:\n%s", joined)
	}
}

func TestPassRegDead_JsrProtectsStoreWhenReloaded(t *testing.T) {
	// sta regVar; jsr foo; lda regVar → store kept
	// lda exists (pre-scan passes), but jsr blocks local scan before reaching it.
	// The callee may modify A, so the reload after jsr is genuine.
	input := []string{
		"\tlda 53281",
		"\tsta call_val",
		"\tjsr helper",
		"\tlda call_val",
		"\tsta 53280",
		"\trts",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"call_val": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if dissolved["call_val"] {
		t.Error("call_val should NOT be dissolved — lda exists, jsr protects store")
	}

	joined := strings.Join(output, "\n")
	if !strings.Contains(joined, "sta call_val") {
		t.Errorf("expected sta call_val to be kept (jsr before reload), got:\n%s", joined)
	}
}

func TestPassRegDead_InitValueFlowsThrough(t *testing.T) {
	// BYTE REGISTER x = 42; POKE $d020, x → value flows through A, never touches RAM
	input := []string{
		"\tlda #$2a",
		"\tsta test_x",
		"\tlda test_x",
		"\tsta 53280",
		"\trts",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		EnableStoreLoad:    true,
		RegisterVars: map[string]bool{
			"test_x": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if !dissolved["test_x"] {
		t.Error("test_x should be dissolved — init value flows through A to POKE")
	}

	joined := strings.Join(output, "\n")
	if strings.Contains(joined, "test_x") {
		t.Errorf("expected no reference to test_x in output, got:\n%s", joined)
	}
	if !strings.Contains(joined, "lda #$2a") {
		t.Error("init value lda #$2a should survive")
	}
}

func TestPassRegDead_GloballyDeadBeforeLabel(t *testing.T) {
	// sta regVar before a label, no lda regVar anywhere → globally dead
	// Typical copy-loop pattern: the value flows through A, never reloaded.
	input := []string{
		"_LOOPSTART",
		"\tlda (src),y",
		"\tsta loop_val",
		"\tsta (dst),y",
		"\tbne _SKIP",
		"_SKIP:",
		"\tjmp _LOOPSTART",
		"\trts",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"loop_val": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if !dissolved["loop_val"] {
		t.Error("loop_val should be dissolved — globally dead (no lda anywhere)")
	}

	joined := strings.Join(output, "\n")
	if strings.Contains(joined, "sta loop_val") {
		t.Errorf("expected sta loop_val to be removed (globally dead), got:\n%s", joined)
	}
}

func TestPassRegDead_NotGloballyDeadWithLda(t *testing.T) {
	// sta regVar before a label, but lda regVar exists elsewhere → not globally dead
	// Falls through to local scan. With jsr before the lda, store is kept.
	input := []string{
		"\tsta spill_me_bg",
		"\tlda 53282",
		"_SKIP:",
		"\tjsr helper",
		"\tlda spill_me_bg",
		"\tsta 53282",
		"\trts",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"spill_me_bg": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if dissolved["spill_me_bg"] {
		t.Error("spill_me_bg should NOT be dissolved — has a real lda")
	}

	joined := strings.Join(output, "\n")
	if !strings.Contains(joined, "sta spill_me_bg") {
		t.Errorf("expected sta spill_me_bg to be kept, got:\n%s", joined)
	}
}

func TestPassRegDead_ReadModifyWrite(t *testing.T) {
	tests := []struct {
		name     string
		opcode   string
		lines    []string
	}{
		{
			name:   "dec reads stored value",
			opcode: "dec",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tdec rmw_var",
				"\trts",
			},
		},
		{
			name:   "inc reads stored value",
			opcode: "inc",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tinc rmw_var",
				"\trts",
			},
		},
		{
			name:   "adc reads stored value",
			opcode: "adc",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tclc",
				"\tadc rmw_var",
				"\trts",
			},
		},
		{
			name:   "sbc reads stored value",
			opcode: "sbc",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tsec",
				"\tsbc rmw_var",
				"\trts",
			},
		},
		{
			name:   "and reads stored value",
			opcode: "and",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tand rmw_var",
				"\trts",
			},
		},
		{
			name:   "ora reads stored value",
			opcode: "ora",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tora rmw_var",
				"\trts",
			},
		},
		{
			name:   "eor reads stored value",
			opcode: "eor",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\teor rmw_var",
				"\trts",
			},
		},
		{
			name:   "cmp reads stored value",
			opcode: "cmp",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tcmp rmw_var",
				"\trts",
			},
		},
		{
			name:   "ldx reads stored value",
			opcode: "ldx",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tldx rmw_var",
				"\trts",
			},
		},
		{
			name:   "ldy reads stored value",
			opcode: "ldy",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tldy rmw_var",
				"\trts",
			},
		},
		{
			name:   "asl reads stored value",
			opcode: "asl",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tasl rmw_var",
				"\trts",
			},
		},
		{
			name:   "lsr reads stored value",
			opcode: "lsr",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tlsr rmw_var",
				"\trts",
			},
		},
		{
			name:   "rol reads stored value",
			opcode: "rol",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\trol rmw_var",
				"\trts",
			},
		},
		{
			name:   "ror reads stored value",
			opcode: "ror",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tror rmw_var",
				"\trts",
			},
		},
		{
			name:   "bit reads stored value",
			opcode: "bit",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tbit rmw_var",
				"\trts",
			},
		},
		{
			name:   "cpx reads stored value",
			opcode: "cpx",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tcpx rmw_var",
				"\trts",
			},
		},
		{
			name:   "cpy reads stored value",
			opcode: "cpy",
			lines: []string{
				"\tlda #$05",
				"\tsta rmw_var",
				"\tcpy rmw_var",
				"\trts",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cfg := &Config{
				EnableRegisterVars: true,
				RegisterVars: map[string]bool{
					"rmw_var": true,
				},
			}

			output, dissolved := Optimize(tt.lines, cfg)

			if dissolved["rmw_var"] {
				t.Errorf("rmw_var should NOT be dissolved — %s reads its value", tt.opcode)
			}

			joined := strings.Join(output, "\n")
			if !strings.Contains(joined, "sta rmw_var") {
				t.Errorf("expected sta rmw_var to be kept (followed by %s), got:\n%s", tt.opcode, joined)
			}
		})
	}
}

func TestPassRegDead_JmpDoesNotKillStore(t *testing.T) {
	// sta regVar; ...(then body)...; jmp _END; _ELSE:; ...; _END:; ldy regVar
	// The jmp in the THEN body should NOT kill the store because
	// the jump target _END reaches code that reads regVar.
	input := []string{
		"\tldy #0",
		"\tlda (zp),y",
		"\tsta if_val",
		"\tlda test_i",
		"\tcmp #$06",
		"\tbne _I1",
		"\tlda (zp),y",
		"\tsta other",
		"\tjmp _I2",
		"_I1",
		"\tldy #1",
		"\tlda (zp),y",
		"\tsta other",
		"_I2",
		"\tldy if_val",
		"\tlda (zp),y",
		"\trts",
	}

	cfg := &Config{
		EnableRegisterVars: true,
		RegisterVars: map[string]bool{
			"if_val": true,
		},
	}

	output, dissolved := Optimize(input, cfg)

	if dissolved["if_val"] {
		t.Error("if_val should NOT be dissolved — ldy if_val reads its value")
	}

	joined := strings.Join(output, "\n")
	if !strings.Contains(joined, "sta if_val") {
		t.Errorf("expected sta if_val to be kept (jmp in THEN should not kill it), got:\n%s", joined)
	}
}
