/**
 * SM-2 Spaced Repetition Algorithm Implementation
 * Based on the SuperMemo 2 algorithm
 * https://www.supermemo.com/en/archives1990-2015/english/ol/sm2
 */

export interface SM2Result {
	interval: number; // Days until next review
	repetitions: number; // Number of consecutive correct responses
	easeFactor: number; // Difficulty factor (min 1.3)
	nextReviewDate: Date;
}

export interface ReviewQuality {
	quality: 0 | 1 | 2 | 3 | 4 | 5; // 0-2: incorrect, 3-5: correct
}

/**
 * Calculate next review using SM-2 algorithm
 *
 * @param quality - Quality of recall (0-5)
 *   5 - perfect response
 *   4 - correct response after hesitation
 *   3 - correct response recalled with serious difficulty
 *   2 - incorrect response; where the correct one seemed easy to recall
 *   1 - incorrect response; the correct one remembered
 *   0 - complete blackout
 * @param repetitions - Number of consecutive correct responses
 * @param previousInterval - Previous interval in days
 * @param previousEaseFactor - Previous ease factor (min 1.3)
 */
export function calculateSM2(
	quality: number,
	repetitions: number,
	previousInterval: number,
	previousEaseFactor: number
): SM2Result {
	let interval = previousInterval;
	let easeFactor = previousEaseFactor;
	let reps = repetitions;

	// Ensure quality is within bounds
	quality = Math.max(0, Math.min(5, quality));

	// Calculate new ease factor
	// EF' = EF + (0.1 - (5 - q) * (0.08 + (5 - q) * 0.02))
	easeFactor = easeFactor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02));

	// Ease factor should not be less than 1.3
	easeFactor = Math.max(1.3, easeFactor);

	if (quality < 3) {
		// Incorrect response - reset
		reps = 0;
		interval = 1; // Review again tomorrow
	} else {
		// Correct response
		if (reps === 0) {
			// First correct response
			interval = 1;
		} else if (reps === 1) {
			// Second correct response
			interval = 6;
		} else {
			// Subsequent correct responses
			interval = Math.round(interval * easeFactor);
		}
		reps += 1;
	}

	// Calculate next review date
	const nextReviewDate = new Date();
	nextReviewDate.setDate(nextReviewDate.getDate() + interval);
	nextReviewDate.setHours(4, 0, 0, 0); // Set to 4 AM for next day reviews

	return {
		interval,
		repetitions: reps,
		easeFactor,
		nextReviewDate,
	};
}

/**
 * Convert difficulty rating to SM-2 quality score
 */
export function difficultyToQuality(difficulty: 'easy' | 'medium' | 'hard' | 'again'): number {
	switch (difficulty) {
		case 'easy':
			return 5; // Perfect recall
		case 'medium':
			return 4; // Good recall with hesitation
		case 'hard':
			return 3; // Difficult but correct
		case 'again':
			return 1; // Incorrect, need to review again
		default:
			return 3;
	}
}

/**
 * Calculate review statistics
 */
export function calculateReviewStats(
	totalReviews: number,
	correctReviews: number,
	easeFactor: number
): {
	accuracy: number;
	difficulty: 'easy' | 'medium' | 'hard';
	masteryLevel: number;
} {
	const accuracy = totalReviews > 0 ? (correctReviews / totalReviews) * 100 : 0;

	// Determine difficulty based on ease factor
	let difficulty: 'easy' | 'medium' | 'hard';
	if (easeFactor >= 2.5) {
		difficulty = 'easy';
	} else if (easeFactor >= 2.0) {
		difficulty = 'medium';
	} else {
		difficulty = 'hard';
	}

	// Calculate mastery level (0-100)
	// Based on accuracy and ease factor
	const masteryLevel = Math.min(
		100,
		Math.round(accuracy * 0.6 + ((easeFactor - 1.3) / (2.8 - 1.3)) * 40)
	);

	return {
		accuracy,
		difficulty,
		masteryLevel,
	};
}

/**
 * Determine if a card is due for review
 */
export function isCardDue(nextReviewDate: Date | string): boolean {
	const reviewDate = typeof nextReviewDate === 'string' ? new Date(nextReviewDate) : nextReviewDate;

	const now = new Date();
	now.setHours(23, 59, 59, 999); // End of today

	return reviewDate <= now;
}

/**
 * Get cards organized by review status
 */
export interface ReviewQueue {
	new: string[]; // Card IDs that are new
	learning: string[]; // Cards being learned (interval < 10 days)
	review: string[]; // Cards due for review
	total: number;
}

export function organizeReviewQueue(
	cards: {
		id: string;
		status: 'new' | 'learning' | 'review' | 'relearning';
		next_review_date: string;
		interval: number;
	}[]
): ReviewQueue {
	const queue: ReviewQueue = {
		new: [],
		learning: [],
		review: [],
		total: 0,
	};

	const now = new Date();

	cards.forEach((card) => {
		if (card.status === 'new') {
			queue.new.push(card.id);
		} else if (card.interval < 10 || card.status === 'learning' || card.status === 'relearning') {
			if (isCardDue(card.next_review_date)) {
				queue.learning.push(card.id);
			}
		} else if (isCardDue(card.next_review_date)) {
			queue.review.push(card.id);
		}
	});

	queue.total = queue.new.length + queue.learning.length + queue.review.length;

	return queue;
}

/**
 * Format interval for display
 */
export function formatInterval(days: number): string {
	if (days === 0) {
		return 'Heute';
	} else if (days === 1) {
		return 'Morgen';
	} else if (days < 7) {
		return `In ${days} Tagen`;
	} else if (days < 30) {
		const weeks = Math.floor(days / 7);
		return weeks === 1 ? 'In 1 Woche' : `In ${weeks} Wochen`;
	} else if (days < 365) {
		const months = Math.floor(days / 30);
		return months === 1 ? 'In 1 Monat' : `In ${months} Monaten`;
	} else {
		const years = Math.floor(days / 365);
		return years === 1 ? 'In 1 Jahr' : `In ${years} Jahren`;
	}
}
