package optimizer

import (
	"testing"
	"c65gm/internal/preproc"
)

func TestNewConfigWithAll(t *testing.T) {
	ps := preproc.NewPragmaSet(map[string]string{
		"_P_OPT_ALL":   "1",
		"_P_OPT_DEBUG": "1",
	})
	cfg := NewConfig(ps)
	if !cfg.Any() {
		t.Error("expected Any()=true")
	}
	if !cfg.EnableLoad {
		t.Error("expected EnableLoad=true from _P_OPT_ALL")
	}
	if !cfg.EnableImm {
		t.Error("expected EnableImm=true from _P_OPT_ALL")
	}
	if !cfg.Debug {
		t.Error("expected Debug=true")
	}
}

func TestParseExcludeRange(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		wantOk  bool
		wantS   uint16
		wantE   uint16
	}{
		{"hex", "0xD000:0xDFFF", true, 0xD000, 0xDFFF},
		{"hex lowercase", "0xd000:0xdfff", true, 0xD000, 0xDFFF},
		{"hex mixed case", "0XD000:0XdFfF", true, 0xD000, 0xDFFF},
		{"decimal", "53248:57343", true, 53248, 57343},
		{"single address", "0xD020:0xD020", true, 0xD020, 0xD020},
		{"invalid range", "0xFFFF:0x0000", false, 0, 0},
		{"bad format", "garbage", false, 0, 0},
		{"bad hex", "0xGGGG:0x0000", false, 0, 0},
		{"too many colons", "0xD000:0xDFFF:extra", false, 0, 0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, err := ParseExcludeRange(tt.input)
			if tt.wantOk {
				if err != nil {
					t.Errorf("unexpected error: %v", err)
				}
				if r.Start != tt.wantS || r.End != tt.wantE {
					t.Errorf("got %04X:%04X, want %04X:%04X", r.Start, r.End, tt.wantS, tt.wantE)
				}
			} else {
				if err == nil {
					t.Errorf("expected error, got %04X:%04X", r.Start, r.End)
				}
			}
		})
	}
}

func TestAddIORegions(t *testing.T) {
	cfg := &Config{}
	cfg.AddIORegions([]IORegion{
		{Start: 0xD000, End: 0xDFFF},
	})

	if !cfg.IOMap[0xD000] {
		t.Error("expected 0xD000 to be marked I/O")
	}
	if !cfg.IOMap[0xDFFF] {
		t.Error("expected 0xDFFF to be marked I/O")
	}
	if cfg.IOMap[0xCFFF] {
		t.Error("expected 0xCFFF to NOT be marked I/O")
	}
	if cfg.IOMap[0xE000] {
		t.Error("expected 0xE000 to NOT be marked I/O")
	}
}

func TestAddIORegionsMultiple(t *testing.T) {
	cfg := &Config{}
	cfg.AddIORegions([]IORegion{
		{Start: 0xD000, End: 0xDFFF},
		{Start: 0xDC00, End: 0xDC0F}, // overlaps — still correct
	})

	if !cfg.IOMap[0xD000] {
		t.Error("expected 0xD000 I/O")
	}
	if !cfg.IOMap[0xDC00] {
		t.Error("expected 0xDC00 I/O")
	}
	if !cfg.IOMap[0xDC0F] {
		t.Error("expected 0xDC0F I/O")
	}
}

func TestBuildIOMap(t *testing.T) {
	p := preproc.NewPragma()
	p.AddPragma("_P_OPT_IO", "$D000 $DFFF")

	// Last pragma set should have _P_OPT_IO
	ps := p.GetPragmaSetByIndex(p.GetCurrentPragmaSetIndex())
	cfg := NewConfig(ps)
	cfg.BuildIOMap(p)

	if !cfg.IOMap[0xD000] {
		t.Error("expected $D000 I/O from pragma")
	}
	if !cfg.IOMap[0xDFFF] {
		t.Error("expected $DFFF I/O from pragma")
	}
}

func TestBuildIOMapMultiple(t *testing.T) {
	// Simulate two _P_OPT_IO pragmas
	p := preproc.NewPragma()
	p.AddPragma("_P_OPT_IO", "$D000 $DFFF")
	p.AddPragma("_P_OPT_IO", "$DC00 $DC0F")
	p.AddPragma("_P_SOMETHING_ELSE", "1")

	// BuildIOMap scans ALL sets, finding _P_OPT_IO at index 1 and 2
	ps := p.GetPragmaSetByIndex(p.GetCurrentPragmaSetIndex())
	cfg := NewConfig(ps)
	cfg.BuildIOMap(p)

	if !cfg.IOMap[0xD000] {
		t.Error("expected $D000 I/O")
	}
	if !cfg.IOMap[0xDC00] {
		t.Error("expected $DC00 I/O")
	}
}

func TestBuildIOMapEmpty(t *testing.T) {
	p := preproc.NewPragma()
	ps := p.GetPragmaSetByIndex(p.GetCurrentPragmaSetIndex())
	cfg := NewConfig(ps)
	cfg.BuildIOMap(p)

	for _, addr := range []uint16{0, 0xD000, 0xFFFF} {
		if cfg.IOMap[addr] {
			t.Errorf("address %04X should not be I/O", addr)
		}
	}
}

func TestNewConfigPragmasAccumulate(t *testing.T) {
	// Pragma sets COPY previous values — so last set has ALL pragmas
	p := preproc.NewPragma()
	p.AddPragma("_P_OPT_ALL", "1")
	p.AddPragma("_P_OPT_DEBUG", "1")
	p.AddPragma("_P_SOMETHING_ELSE", "1")

	// Last set should have all three
	ps := p.GetPragmaSetByIndex(p.GetCurrentPragmaSetIndex())
	if v := ps.GetPragma("_P_OPT_ALL"); v != "1" {
		t.Errorf("expected _P_OPT_ALL=1, got %q", v)
	}
	if v := ps.GetPragma("_P_OPT_DEBUG"); v != "1" {
		t.Errorf("expected _P_OPT_DEBUG=1, got %q", v)
	}
	if v := ps.GetPragma("_P_SOMETHING_ELSE"); v != "1" {
		t.Errorf("expected _P_SOMETHING_ELSE=1, got %q", v)
	}

	cfg := NewConfig(ps)
	if !cfg.Any() {
		t.Error("expected Any()=true")
	}
	if !cfg.Debug {
		t.Error("expected Debug=true")
	}
}
