/**
 * Terminal Typing Animation Engine
 * 
 * Handles the character-by-character typing animation for terminal lines.
 */

import type { ParsedLine, DisplayedLine } from './types';
import type { TerminalSettings } from '$lib/config';
import { calculateTypeSpeed } from '$lib';

export interface TypingState {
	displayedLines: DisplayedLine[];
	currentLineIndex: number;
	isTyping: boolean;
	isComplete: boolean;
	skipRequested: boolean;
	selectedIndex: number;
}

export interface TypingOptions {
	parsedLines: ParsedLine[];
	speedMultiplier: number;
	terminalSettings: TerminalSettings;
	buttonIndices: number[];
	interactive: boolean;
	autoscroll: boolean;
	getBodyElement: () => HTMLDivElement | undefined;
	getState: () => TypingState;
	setState: (updates: Partial<TypingState>) => void;
	onComplete?: () => void;
	scrollToHash: () => void;
}

function sleep(ms: number): Promise<void> {
	return new Promise(resolve => setTimeout(resolve, ms));
}

/**
 * Scroll terminal body to bottom
 */
function scrollToBottom(bodyElement: HTMLDivElement | undefined, autoscroll: boolean) {
	if (!autoscroll || !bodyElement) return;
	bodyElement.scrollTo({
		top: bodyElement.scrollHeight,
		behavior: 'smooth'
	});
}

/**
 * Run the typing animation for all lines
 */
export async function runTypingAnimation(options: TypingOptions): Promise<void> {
	const {
		parsedLines,
		speedMultiplier,
		terminalSettings,
		buttonIndices,
		interactive,
		autoscroll,
		getBodyElement,
		getState,
		setState,
		onComplete,
		scrollToHash
	} = options;

	if (parsedLines.length === 0) return;

	setState({ isTyping: true });

	// Apply speed multiplier to start delay
	const startDelayMs = speedMultiplier === 0 ? 0 : terminalSettings.startDelay * speedMultiplier;
	await sleep(startDelayMs);
	if (getState().skipRequested) return;

	for (let i = 0; i < parsedLines.length; i++) {
		const state = getState();
		if (state.skipRequested) return;

		const parsed = parsedLines[i];
		const line = parsed.line;
		const plainLength = parsed.plainText.length;

		// Add new line to displayed lines
		const currentDisplayed = [...state.displayedLines, { parsed, charIndex: 0, complete: false, showImage: false }];
		setState({ displayedLines: currentDisplayed, currentLineIndex: i });

		if (line.delay && speedMultiplier > 0) {
			await sleep(line.delay * speedMultiplier);
			if (getState().skipRequested) return;
		}

		const bodyElement = getBodyElement();

		// Handle different line types
		if (line.type === 'image') {
			await sleep(speedMultiplier === 0 ? 0 : 100 * speedMultiplier);
			if (getState().skipRequested) return;
			updateDisplayedLine(i, { parsed, charIndex: plainLength, complete: true, showImage: true }, getState, setState);
			scrollToBottom(bodyElement, autoscroll);
		} else if (line.type === 'blank' || line.type === 'divider') {
			updateDisplayedLine(i, { parsed, charIndex: plainLength, complete: true, showImage: false }, getState, setState);
		} else if (line.type === 'button') {
			// Buttons appear instantly
			updateDisplayedLine(i, { parsed, charIndex: plainLength, complete: true, showImage: false }, getState, setState);
			scrollToBottom(bodyElement, autoscroll);
		} else if (speedMultiplier === 0) {
			// Instant mode - no typing animation
			updateDisplayedLine(i, { parsed, charIndex: plainLength, complete: true, showImage: false }, getState, setState);
			if (i % 5 === 0) scrollToBottom(bodyElement, autoscroll);
		} else if (line.type === 'header') {
			const typeSpeed = calculateTypeSpeed(plainLength, speedMultiplier * 0.25);
			for (let j = 0; j <= plainLength; j++) {
				if (getState().skipRequested) return;
				updateDisplayedLine(i, {
					parsed,
					charIndex: j,
					complete: j === plainLength,
					showImage: false
				}, getState, setState);
				if (j < plainLength) await sleep(typeSpeed);
			}
			scrollToBottom(bodyElement, autoscroll);
		} else {
			const typeSpeed = calculateTypeSpeed(plainLength, speedMultiplier);

			for (let j = 0; j <= plainLength; j++) {
				if (getState().skipRequested) return;
				updateDisplayedLine(i, {
					parsed,
					charIndex: j,
					complete: j === plainLength,
					showImage: false
				}, getState, setState);

				if (j % 10 === 0) scrollToBottom(bodyElement, autoscroll);

				if (j < plainLength) {
					await sleep(typeSpeed);
				}
			}
		}

		if (getState().skipRequested) return;

		// Mark line as complete
		const finalState = getState();
		const updatedLines = [...finalState.displayedLines];
		if (updatedLines[i]) {
			updatedLines[i].complete = true;
			setState({ displayedLines: updatedLines });
		}

		scrollToBottom(bodyElement, autoscroll);

		if (i < parsedLines.length - 1 && speedMultiplier > 0) {
			await sleep(terminalSettings.lineDelay * speedMultiplier);
		}
	}

	setState({ isTyping: false, isComplete: true });

	if (interactive && buttonIndices.length > 0) {
		setState({ selectedIndex: buttonIndices[0] });
	}

	// Scroll to hash anchor if present in URL
	scrollToHash();

	onComplete?.();
}

/**
 * Update a single displayed line at index
 */
function updateDisplayedLine(
	index: number,
	update: DisplayedLine,
	getState: () => TypingState,
	setState: (updates: Partial<TypingState>) => void
) {
	const state = getState();
	const updatedLines = [...state.displayedLines];
	updatedLines[index] = update;
	setState({ displayedLines: updatedLines });
}

/**
 * Skip animation and show all content instantly
 */
export function skipTypingAnimation(
	parsedLines: ParsedLine[],
	buttonIndices: number[],
	interactive: boolean,
	getState: () => TypingState,
	setState: (updates: Partial<TypingState>) => void,
	getBodyElement: () => HTMLDivElement | undefined,
	autoscroll: boolean,
	onComplete?: () => void
): void {
	const state = getState();
	if (!state.isTyping) return;

	const displayedLines = parsedLines.map(parsed => ({
		parsed,
		charIndex: parsed.plainText.length,
		complete: true,
		showImage: parsed.line.type === 'image'
	}));

	setState({
		skipRequested: true,
		displayedLines,
		isTyping: false,
		isComplete: true,
		currentLineIndex: parsedLines.length - 1,
		selectedIndex: interactive && buttonIndices.length > 0 ? buttonIndices[0] : -1
	});

	scrollToBottom(getBodyElement(), autoscroll);
	onComplete?.();
}
