import { describe, it, expect } from "vitest";
import {
	generateLetters,
	createExerciseState,
	createWordExerciseState,
	getCurrentWordIndex,
	handleKeyPress,
	getCurrentLetter,
	isExerciseComplete,
} from "./typing";
import { getKeysUpToLevel } from "./levels";

describe("generateLetters", () => {
	it("returns 8-12 letters for level 1", () => {
		for (let i = 0; i < 20; i++) {
			const letters = generateLetters(1);
			expect(letters.length).toBeGreaterThanOrEqual(8);
			expect(letters.length).toBeLessThanOrEqual(12);
		}
	});

	it("only uses keys from level 1 key set (f, j, space)", () => {
		const validKeys = ["f", "j", " "];
		for (let i = 0; i < 20; i++) {
			const letters = generateLetters(1);
			for (const letter of letters) {
				expect(validKeys).toContain(letter);
			}
		}
	});

	it("only uses keys from levels 1-3 key set for level 3", () => {
		const validKeys = getKeysUpToLevel(3);
		for (let i = 0; i < 20; i++) {
			const letters = generateLetters(3);
			for (const letter of letters) {
				expect(validKeys).toContain(letter);
			}
		}
	});
});

describe("createExerciseState", () => {
	it("sets initial interval to 3000ms", () => {
		const state = createExerciseState(1);
		expect(state.intervalMs).toBe(3000);
	});

	it("starts at letter index 0 with 0 correct, 0 errors, 0 streak", () => {
		const state = createExerciseState(1);
		expect(state.currentLetterIndex).toBe(0);
		expect(state.totalCorrect).toBe(0);
		expect(state.totalErrors).toBe(0);
		expect(state.correctStreak).toBe(0);
	});
});

describe("handleKeyPress", () => {
	it("increments currentLetterIndex and totalCorrect on correct key", () => {
		const state = createExerciseState(1);
		const expected = state.letters[0]!;
		const result = handleKeyPress(state, expected);
		expect(result.correct).toBe(true);
		expect(state.currentLetterIndex).toBe(1);
		expect(state.totalCorrect).toBe(1);
	});

	it("does NOT increment currentLetterIndex on wrong key, increments totalErrors", () => {
		const state = createExerciseState(1);
		// Pick a key that is definitely wrong
		const expected = state.letters[0]!;
		const wrongKey = expected === "f" ? "j" : "f";
		const result = handleKeyPress(state, wrongKey);
		expect(result.correct).toBe(false);
		expect(state.currentLetterIndex).toBe(0);
		expect(state.totalErrors).toBe(1);
	});

	it("increments correctStreak on correct key", () => {
		const state = createExerciseState(1);
		const expected = state.letters[0]!;
		handleKeyPress(state, expected);
		expect(state.correctStreak).toBe(1);
	});

	it("resets correctStreak to 0 on error", () => {
		const state = createExerciseState(1);
		// Get one correct to build streak
		handleKeyPress(state, state.letters[0]!);
		expect(state.correctStreak).toBe(1);
		// Now wrong key
		const expected = state.letters[1]!;
		const wrongKey = expected === "f" ? "j" : "f";
		handleKeyPress(state, wrongKey);
		expect(state.correctStreak).toBe(0);
	});

	it("decreases interval by 200ms after 2 correct in sequence", () => {
		const state = createExerciseState(1);
		expect(state.intervalMs).toBe(3000);
		handleKeyPress(state, state.letters[0]!);
		handleKeyPress(state, state.letters[1]!);
		expect(state.intervalMs).toBe(2800);
	});

	it("increases interval by 500ms after 1 error", () => {
		const state = createExerciseState(1);
		// Force an error
		const expected = state.letters[0]!;
		const wrongKey = expected === "f" ? "j" : "f";
		handleKeyPress(state, wrongKey);
		expect(state.intervalMs).toBe(3500);
	});

	it("interval never goes below 1500ms", () => {
		const state = createExerciseState(1);
		// Force interval down to minimum
		state.intervalMs = 1600;
		state.correctStreak = 1;
		handleKeyPress(state, state.letters[0]!);
		// After 2 correct streak, -200ms: 1600 -> 1500 (clamped)
		expect(state.intervalMs).toBe(1500);
		// Try again: force streak
		state.correctStreak = 1;
		handleKeyPress(state, state.letters[1]!);
		// Should stay at 1500
		expect(state.intervalMs).toBe(1500);
	});

	it("interval never goes above 4000ms", () => {
		const state = createExerciseState(1);
		state.intervalMs = 3800;
		const expected = state.letters[0]!;
		const wrongKey = expected === "f" ? "j" : "f";
		handleKeyPress(state, wrongKey);
		// 3800 + 500 = 4300, clamped to 4000
		expect(state.intervalMs).toBe(4000);
	});

	it("exercise is complete when currentLetterIndex equals letters.length", () => {
		const state = createExerciseState(1);
		// Type all letters correctly
		let lastResult = { correct: false, exerciseComplete: false, newInterval: 0 };
		for (let i = 0; i < state.letters.length; i++) {
			lastResult = handleKeyPress(state, state.letters[i]!);
		}
		expect(lastResult.exerciseComplete).toBe(true);
		expect(isExerciseComplete(state)).toBe(true);
	});
});

describe("getCurrentLetter", () => {
	it("returns the current letter", () => {
		const state = createExerciseState(1);
		expect(getCurrentLetter(state)).toBe(state.letters[0]);
	});

	it("returns null when exercise is complete", () => {
		const state = createExerciseState(1);
		state.currentLetterIndex = state.letters.length;
		expect(getCurrentLetter(state)).toBeNull();
	});
});

describe("isExerciseComplete", () => {
	it("returns false when exercise is in progress", () => {
		const state = createExerciseState(1);
		expect(isExerciseComplete(state)).toBe(false);
	});

	it("returns true when all letters are typed", () => {
		const state = createExerciseState(1);
		state.currentLetterIndex = state.letters.length;
		expect(isExerciseComplete(state)).toBe(true);
	});
});

describe("createExerciseState backwards compatibility", () => {
	it("sets mode to letters with no words/wordBoundaries", () => {
		const state = createExerciseState(1);
		expect(state.mode).toBe("letters");
		expect(state.words).toBeUndefined();
		expect(state.wordBoundaries).toBeUndefined();
	});
});

describe("createWordExerciseState", () => {
	it("creates state with letters from words joined", () => {
		const state = createWordExerciseState(["eis", "die"]);
		expect(state.letters).toEqual(["e", "i", "s", "d", "i", "e"]);
	});

	it("stores words and wordBoundaries in state", () => {
		const state = createWordExerciseState(["eis", "die"]);
		expect(state.words).toEqual(["eis", "die"]);
		expect(state.wordBoundaries).toEqual([0, 3]);
	});

	it("sets mode to words", () => {
		const state = createWordExerciseState(["eis"]);
		expect(state.mode).toBe("words");
	});

	it("sets intervalMs to 0 for word mode", () => {
		const state = createWordExerciseState(["eis"]);
		expect(state.intervalMs).toBe(0);
	});

	it("starts with 0 correct, 0 errors, 0 streak, index 0", () => {
		const state = createWordExerciseState(["eis"]);
		expect(state.currentLetterIndex).toBe(0);
		expect(state.totalCorrect).toBe(0);
		expect(state.totalErrors).toBe(0);
		expect(state.correctStreak).toBe(0);
	});
});

describe("handleKeyPress with word-mode state", () => {
	it("works the same way for word-mode state", () => {
		const state = createWordExerciseState(["eis"]);
		const result1 = handleKeyPress(state, "e");
		expect(result1.correct).toBe(true);
		expect(state.currentLetterIndex).toBe(1);

		const result2 = handleKeyPress(state, "x");
		expect(result2.correct).toBe(false);
		expect(state.currentLetterIndex).toBe(1);

		handleKeyPress(state, "i");
		const result3 = handleKeyPress(state, "s");
		expect(result3.exerciseComplete).toBe(true);
	});
});

describe("getCurrentWordIndex", () => {
	it("returns which word the letter belongs to", () => {
		const state = createWordExerciseState(["eis", "die"]);
		// Index 0 -> word 0 ("eis")
		state.currentLetterIndex = 0;
		expect(getCurrentWordIndex(state)).toBe(0);
		// Index 2 -> word 0 ("eis")
		state.currentLetterIndex = 2;
		expect(getCurrentWordIndex(state)).toBe(0);
		// Index 3 -> word 1 ("die")
		state.currentLetterIndex = 3;
		expect(getCurrentWordIndex(state)).toBe(1);
		// Index 5 -> word 1 ("die")
		state.currentLetterIndex = 5;
		expect(getCurrentWordIndex(state)).toBe(1);
	});

	it("returns 0 for letters-mode state (no boundaries)", () => {
		const state = createExerciseState(1);
		expect(getCurrentWordIndex(state)).toBe(0);
	});
});
