package compiler

import "testing"

func TestLabelStack_PushCreatesUniqueLabels(t *testing.T) {
	stack := NewLabelStack("test")

	lbl1 := stack.Push()
	lbl2 := stack.Push()
	lbl3 := stack.Push()

	if lbl1 != "test1" {
		t.Errorf("expected test1, got %s", lbl1)
	}
	if lbl2 != "test2" {
		t.Errorf("expected test2, got %s", lbl2)
	}
	if lbl3 != "test3" {
		t.Errorf("expected test3, got %s", lbl3)
	}
}

func TestLabelStack_PeekDoesNotRemove(t *testing.T) {
	stack := NewLabelStack("peek")

	stack.Push()
	lbl := stack.Push()

	peeked, err := stack.Peek()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if peeked != lbl {
		t.Errorf("expected %s, got %s", lbl, peeked)
	}

	if stack.Size() != 2 {
		t.Errorf("expected size 2 after peek, got %d", stack.Size())
	}
}

func TestLabelStack_PopRemoves(t *testing.T) {
	stack := NewLabelStack("pop")

	stack.Push()
	lbl := stack.Push()

	popped, err := stack.Pop()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if popped != lbl {
		t.Errorf("expected %s, got %s", lbl, popped)
	}

	if stack.Size() != 1 {
		t.Errorf("expected size 1 after pop, got %d", stack.Size())
	}
}

func TestLabelStack_PeekEmptyReturnsError(t *testing.T) {
	stack := NewLabelStack("empty")

	_, err := stack.Peek()
	if err == nil {
		t.Error("expected error on peek of empty stack")
	}
}

func TestLabelStack_PopEmptyReturnsError(t *testing.T) {
	stack := NewLabelStack("empty")

	_, err := stack.Pop()
	if err == nil {
		t.Error("expected error on pop of empty stack")
	}
}

func TestLabelStack_IsEmpty(t *testing.T) {
	stack := NewLabelStack("check")

	if !stack.IsEmpty() {
		t.Error("new stack should be empty")
	}

	stack.Push()
	if stack.IsEmpty() {
		t.Error("stack with item should not be empty")
	}

	stack.Pop()
	if !stack.IsEmpty() {
		t.Error("stack after pop should be empty")
	}
}

func TestLabelStack_CounterNeverResets(t *testing.T) {
	stack := NewLabelStack("counter")

	lbl1 := stack.Push()
	lbl2 := stack.Push()
	stack.Pop()
	stack.Pop()
	lbl3 := stack.Push()

	if lbl1 != "counter1" || lbl2 != "counter2" || lbl3 != "counter3" {
		t.Errorf("counter reset detected: %s, %s, %s", lbl1, lbl2, lbl3)
	}
}

func TestLabelStack_WhileWendPattern(t *testing.T) {
	whileStack := NewLabelStack("whilelbl")
	wendStack := NewLabelStack("wendlbl")

	// WHILE
	whileLbl := whileStack.Push()
	wendLbl := wendStack.Push()

	if whileLbl != "whilelbl1" {
		t.Errorf("expected whilelbl1, got %s", whileLbl)
	}
	if wendLbl != "wendlbl1" {
		t.Errorf("expected wendlbl1, got %s", wendLbl)
	}

	// WEND
	w, err := whileStack.Pop()
	if err != nil || w != whileLbl {
		t.Errorf("failed to pop while label")
	}

	wd, err := wendStack.Pop()
	if err != nil || wd != wendLbl {
		t.Errorf("failed to pop wend label")
	}
}

func TestLabelStack_IfElseEndifPattern(t *testing.T) {
	ifStack := NewLabelStack("iflbl")

	// IF
	ifLbl1 := ifStack.Push()

	// ELSE - peek the if label, then push new one
	peeked, err := ifStack.Peek()
	if err != nil || peeked != ifLbl1 {
		t.Errorf("failed to peek if label")
	}

	// Commit (pop) the first label
	popped, err := ifStack.Pop()
	if err != nil || popped != ifLbl1 {
		t.Errorf("failed to pop first if label")
	}

	// Push new label for ENDIF
	ifLbl2 := ifStack.Push()

	// ENDIF
	endifLbl, err := ifStack.Pop()
	if err != nil || endifLbl != ifLbl2 {
		t.Errorf("failed to pop endif label")
	}

	if ifLbl1 != "iflbl1" || ifLbl2 != "iflbl2" {
		t.Errorf("unexpected label names: %s, %s", ifLbl1, ifLbl2)
	}
}

func TestLabelStack_NestedIfPattern(t *testing.T) {
	ifStack := NewLabelStack("iflbl")

	// Outer IF
	outerIf := ifStack.Push()

	// Inner IF
	innerIf := ifStack.Push()

	// Inner ENDIF
	innerEnd, _ := ifStack.Pop()
	if innerEnd != innerIf {
		t.Errorf("inner endif mismatch")
	}

	// Outer ENDIF
	outerEnd, _ := ifStack.Pop()
	if outerEnd != outerIf {
		t.Errorf("outer endif mismatch")
	}

	if outerIf != "iflbl1" || innerIf != "iflbl2" {
		t.Errorf("nested labels incorrect: %s, %s", outerIf, innerIf)
	}
}

func TestLabelStack_BreakPattern(t *testing.T) {
	wendStack := NewLabelStack("wendlbl")

	// WHILE
	wendLbl := wendStack.Push()

	// BREAK - needs to peek wend label without popping
	breakTarget, err := wendStack.Peek()
	if err != nil || breakTarget != wendLbl {
		t.Errorf("failed to peek for break")
	}

	// Stack should still have the label
	if wendStack.Size() != 1 {
		t.Error("peek should not modify stack")
	}

	// WEND - now pop it
	wend, _ := wendStack.Pop()
	if wend != wendLbl {
		t.Errorf("wend label mismatch")
	}
}

func TestLabelStack_MultipleSeparateStacks(t *testing.T) {
	whileStack := NewLabelStack("whilelbl")
	ifStack := NewLabelStack("iflbl")
	generalStack := NewLabelStack("general")

	w := whileStack.Push()
	i := ifStack.Push()
	g := generalStack.Push()

	if w != "whilelbl1" || i != "iflbl1" || g != "general1" {
		t.Errorf("separate stacks interfering: %s, %s, %s", w, i, g)
	}
}
