package vocab

import "testing"

// TestLadderProgression walks a card up the Leitner ladder on repeated "good"
// reviews: 1 → 3 → 7 → 16 → 35 days, then geometric growth by ease.
func TestLadderProgression(t *testing.T) {
	s := State{Ease: startEase}
	want := []int{1, 3, 7, 16, 35}
	for i, w := range want {
		s = s.next(GradeGood)
		if s.Interval != w {
			t.Fatalf("rung %d: interval=%d, want %d", i, s.Interval, w)
		}
		if s.Reps != i+1 {
			t.Fatalf("rung %d: reps=%d, want %d", i, s.Reps, i+1)
		}
	}
	// Past the ladder it grows by ease (35 * 2.5 = 87.5 → 88).
	s = s.next(GradeGood)
	if s.Interval != 88 {
		t.Fatalf("post-ladder interval=%d, want 88", s.Interval)
	}
}

// TestAgainStepsBackGently proves a lapse resets to a 1-day interval and counts
// a lapse, but only nudges ease down (never below the floor) — no harsh wipe.
func TestAgainStepsBackGently(t *testing.T) {
	s := State{Reps: 4, Interval: 35, Ease: startEase}
	s = s.next(GradeAgain)
	if s.Interval != 1 {
		t.Fatalf("again interval=%d, want 1", s.Interval)
	}
	if s.Reps != 0 {
		t.Fatalf("again reps=%d, want 0", s.Reps)
	}
	if s.Lapses != 1 {
		t.Fatalf("again lapses=%d, want 1", s.Lapses)
	}
	if s.Ease != startEase+easeAgainDelta {
		t.Fatalf("again ease=%v, want %v", s.Ease, startEase+easeAgainDelta)
	}

	// Ease never drops below the floor no matter how many lapses.
	low := State{Ease: minEase}
	for i := 0; i < 5; i++ {
		low = low.next(GradeAgain)
	}
	if low.Ease < minEase {
		t.Fatalf("ease fell below floor: %v", low.Ease)
	}
}

// TestCapsBoundGrowth proves runaway "easy" grading can't push ease or the
// interval past their ceilings, so a word always resurfaces within a year.
func TestCapsBoundGrowth(t *testing.T) {
	s := State{Ease: startEase}
	for i := 0; i < 40; i++ {
		s = s.next(GradeEasy)
		if s.Ease > maxEase {
			t.Fatalf("step %d: ease %v exceeded cap %v", i, s.Ease, maxEase)
		}
		if s.Interval > maxInterval {
			t.Fatalf("step %d: interval %d exceeded cap %d", i, s.Interval, maxInterval)
		}
	}
	// After enough "easy" reviews it should be pinned at the ceilings.
	if s.Interval != maxInterval {
		t.Fatalf("interval should saturate at %d, got %d", maxInterval, s.Interval)
	}
	if s.Ease != maxEase {
		t.Fatalf("ease should saturate at %v, got %v", maxEase, s.Ease)
	}
}

// TestEasyAdvancesFurther proves "easy" both bumps ease and lands a longer
// interval than a plain "good" at the same rung.
func TestEasyAdvancesFurther(t *testing.T) {
	base := State{Reps: 2, Interval: 7, Ease: startEase}
	good := base.next(GradeGood)
	easy := base.next(GradeEasy)
	if easy.Interval <= good.Interval {
		t.Fatalf("easy interval %d should exceed good interval %d", easy.Interval, good.Interval)
	}
	if easy.Ease <= startEase {
		t.Fatalf("easy should raise ease, got %v", easy.Ease)
	}
}
