package compiler

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

func TestAddConstStr_NonCompressable(t *testing.T) {
	h := NewConstantStringHandler()
	ps := preproc.PragmaSet{}

	label1 := h.AddConstStr("lbl1", "hello", false, ps)
	label2 := h.AddConstStr("lbl2", "hello", false, ps)

	if label1 != "lbl1" {
		t.Errorf("Expected label1 = lbl1, got %s", label1)
	}
	if label2 != "lbl2" {
		t.Errorf("Expected label2 = lbl2, got %s", label2)
	}
	if len(h.strings) != 2 {
		t.Errorf("Expected 2 strings, got %d", len(h.strings))
	}
}

func TestAddConstStr_CompressableDedupe(t *testing.T) {
	h := NewConstantStringHandler()
	ps := preproc.PragmaSet{}

	label1 := h.AddConstStr("lbl1", "hello", true, ps)
	label2 := h.AddConstStr("lbl2", "hello", true, ps)

	if label1 != "lbl1" {
		t.Errorf("Expected label1 = lbl1, got %s", label1)
	}
	if label2 != "lbl1" {
		t.Errorf("Expected label2 = lbl1 (deduped), got %s", label2)
	}
	if len(h.strings) != 1 {
		t.Errorf("Expected 1 string after dedup, got %d", len(h.strings))
	}
}

func TestAddConstStr_CompressableNoDedupe(t *testing.T) {
	h := NewConstantStringHandler()
	ps := preproc.PragmaSet{}

	label1 := h.AddConstStr("lbl1", "hello", true, ps)
	label2 := h.AddConstStr("lbl2", "world", true, ps)

	if label1 != "lbl1" {
		t.Errorf("Expected label1 = lbl1, got %s", label1)
	}
	if label2 != "lbl2" {
		t.Errorf("Expected label2 = lbl2, got %s", label2)
	}
	if len(h.strings) != 2 {
		t.Errorf("Expected 2 strings (different values), got %d", len(h.strings))
	}
}

func TestGenerateConstStrDecls_Normal(t *testing.T) {
	h := NewConstantStringHandler()
	ps := preproc.PragmaSet{}

	h.AddConstStr("str1", "\"hello\"", false, ps)

	result := h.GenerateConstStrDecls()

	expected := []string{
		"str1",
		" !raw \"hello\"",
		" !8 0",
	}

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

	for i, exp := range expected {
		if result[i] != exp {
			t.Errorf("Line %d: expected %q, got %q", i, exp, result[i])
		}
	}
}

func TestGenerateConstStrDecls_CBM(t *testing.T) {
	h := NewConstantStringHandler()
	ps := mockPragmaSet(map[string]string{"_P_USE_CBM_STRINGS": "1"})

	h.AddConstStr("str1", "\"hello\"", false, ps)

	result := h.GenerateConstStrDecls()

	expected := []string{
		"str1",
		" !pet \"hello\"",
		" !8 0",
	}

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

	for i, exp := range expected {
		if result[i] != exp {
			t.Errorf("Line %d: expected %q, got %q", i, exp, result[i])
		}
	}
}

func TestGenerateConstStrDecls_CBM_DisabledWithZero(t *testing.T) {
	h := NewConstantStringHandler()
	ps := mockPragmaSet(map[string]string{"_P_USE_CBM_STRINGS": "0"})

	h.AddConstStr("str1", "\"hello\"", false, ps)

	result := h.GenerateConstStrDecls()

	expected := []string{
		"str1",
		" !raw \"hello\"",
		" !8 0",
	}

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

	for i, exp := range expected {
		if result[i] != exp {
			t.Errorf("Line %d: expected %q, got %q", i, exp, result[i])
		}
	}
}

func TestGenerateConstStrDecls_MixedPragmas(t *testing.T) {
	h := NewConstantStringHandler()
	psNormal := preproc.PragmaSet{}
	psCBM := mockPragmaSet(map[string]string{"_P_USE_CBM_STRINGS": "1"})

	h.AddConstStr("str1", "\"hello\"", false, psNormal)
	h.AddConstStr("str2", "\"world\"", false, psCBM)

	result := h.GenerateConstStrDecls()

	expected := []string{
		"str1",
		" !raw \"hello\"",
		" !8 0",
		"str2",
		" !pet \"world\"",
		" !8 0",
	}

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

	for i, exp := range expected {
		if result[i] != exp {
			t.Errorf("Line %d: expected %q, got %q", i, exp, result[i])
		}
	}
}

func TestGenerateConstStrDecls_Order(t *testing.T) {
	h := NewConstantStringHandler()
	ps := preproc.PragmaSet{}

	h.AddConstStr("str1", "\"first\"", false, ps)
	h.AddConstStr("str2", "\"second\"", false, ps)
	h.AddConstStr("str3", "\"third\"", false, ps)

	result := h.GenerateConstStrDecls()

	if len(result) != 9 {
		t.Fatalf("Expected 9 lines, got %d", len(result))
	}

	if result[0] != "str1" || result[3] != "str2" || result[6] != "str3" {
		t.Errorf("Labels not in correct order")
	}
}

func TestAddConstStr_CompressOnlyDedupesSameValue(t *testing.T) {
	h := NewConstantStringHandler()
	ps := preproc.PragmaSet{}

	h.AddConstStr("lbl1", "hello", true, ps)
	h.AddConstStr("lbl2", "hello", true, ps)
	h.AddConstStr("lbl3", "world", true, ps)
	h.AddConstStr("lbl4", "hello", true, ps)

	if len(h.strings) != 2 {
		t.Errorf("Expected 2 unique strings, got %d", len(h.strings))
	}

	if len(h.compressMap) != 2 {
		t.Errorf("Expected 2 entries in compress map, got %d", len(h.compressMap))
	}

	if h.compressMap["hello"] != "lbl1" {
		t.Errorf("Expected hello -> lbl1, got %s", h.compressMap["hello"])
	}
	if h.compressMap["world"] != "lbl3" {
		t.Errorf("Expected world -> lbl3, got %s", h.compressMap["world"])
	}
}

func mockPragmaSet(m map[string]string) preproc.PragmaSet {
	return preproc.NewPragmaSet(m)
}
