import { maybeStopWatch } from '@gitlens/utils/stopwatch.js';
import { iterateByDelimiter } from '@gitlens/utils/string.js';
import type { ParsedRebaseTodo, RebaseTodoAction, RebaseTodoEntry, RebaseTodoInfo } from '../models/rebase.js';

const rebaseActionsMap = new Map<string, RebaseTodoAction>([
	// Standard commit commands
	['p', 'pick'],
	['pick', 'pick'],
	['r', 'reword'],
	['reword', 'reword'],
	['e', 'edit'],
	['edit', 'edit'],
	['s', 'squash'],
	['squash', 'squash'],
	['f', 'fixup'],
	['fixup', 'fixup'],
	['d', 'drop'],
	['drop', 'drop'],
	// Special commands
	['b', 'break'],
	['break', 'break'],
	['x', 'exec'],
	['exec', 'exec'],
	['l', 'label'],
	['label', 'label'],
	['t', 'reset'],
	['reset', 'reset'],
	['m', 'merge'],
	['merge', 'merge'],
	['u', 'update-ref'],
	['update-ref', 'update-ref'],
	// No-op command (auto-generated by Git for empty rebases)
	['noop', 'noop'],
]);

const rebaseHeaderRegex = /^\s?#\s?Rebase\s([0-9a-f]+)(?:\.\.([0-9a-f]+))?\sonto\s([0-9a-f]+)\s.*$/im;
const mergeCommentSplitRegex = /\s*#\s*/;

// Optimized: Single regex that matches all command types
// Groups: (action)(sha?)(message/command/ref?)(merge-sha?)(merge-message?)
const commandRegex =
	/^(p|pick|r|reword|e|edit|s|squash|f|fixup|d|drop|b|break|x|exec|l|label|t|reset|m|merge|u|update-ref|noop)(?:\s+(?:(-[Cc])\s+)?([0-9a-f]+|\S+))?(?:\s+(?:#\s*)?(.*))?$/;

/**
 * Parses a git-rebase-todo file content into structured entries
 *
 * Supports all Git rebase commands:
 * - Commit commands: pick, reword, edit, squash, fixup (with -c/-C flags), drop
 * - Control commands: break, exec, noop
 * - Label commands: label, reset, merge (with -c/-C flags)
 * - Ref commands: update-ref
 *
 * @param data The content of the git-rebase-todo file
 * @returns Parsed rebase todo entries with line numbers and optional rebase info
 */
export function parseRebaseTodo(data: string | undefined): ParsedRebaseTodo {
	using sw = maybeStopWatch('Git.parseRebaseTodo', { log: { onlyExit: true, level: 'debug' } });

	if (!data) {
		sw?.stop({ suffix: ' no data' });
		return { entries: [] };
	}

	// Try to extract rebase info from header comment
	let info: RebaseTodoInfo | undefined;
	const headerMatch = rebaseHeaderRegex.exec(data);
	if (headerMatch) {
		const [, from, to, onto] = headerMatch;
		info = { from: from || undefined, to: to || undefined, onto: onto };
	}

	const entries: RebaseTodoEntry[] = [];

	let count = -1;
	for (let line of iterateByDelimiter(data, '\n')) {
		count++;
		line = line.trim();

		// Skip comments and blank lines
		if (!line || line.startsWith('#')) continue;

		const match = commandRegex.exec(line);
		if (!match) continue;

		const [, actionStr, mergeFlag, arg1, arg2] = match;
		const action = rebaseActionsMap.get(actionStr);
		if (!action) continue;

		// Build entry based on action type
		let entry: RebaseTodoEntry;

		switch (action) {
			case 'pick':
			case 'reword':
			case 'edit':
			case 'squash':
			case 'drop':
				// Format: action sha message
				entry = { line: count, action: action, sha: arg1, message: arg2 || '' };
				break;

			case 'fixup':
				// Format: fixup [-c|-C] sha message
				if (mergeFlag) {
					entry = { line: count, action: action, sha: arg1, message: arg2 || '', flag: mergeFlag };
				} else {
					entry = { line: count, action: action, sha: arg1, message: arg2 || '' };
				}
				break;

			case 'break':
			case 'noop':
				// Format: break or noop (no arguments)
				entry = { line: count, action: action };
				break;

			case 'exec': {
				// Format: exec command
				// Extract command directly from line to preserve # characters in shell commands
				const prefixLen = line.startsWith('x ') ? 2 : 5; // 'x ' or 'exec '
				entry = { line: count, action: action, command: line.slice(prefixLen) };
				break;
			}

			case 'label':
			case 'reset':
			case 'update-ref':
				// Format: label ref, reset ref, or update-ref ref
				entry = { line: count, action: action, ref: arg1 };
				break;

			case 'merge': {
				// Format: merge [-C sha] ref [# message]
				if (mergeFlag && arg1 && arg2) {
					// merge -C sha ref [# message]
					const parts = arg2.split(mergeCommentSplitRegex);
					entry = {
						line: count,
						action: action,
						sha: arg1,
						ref: parts[0],
						message: parts[1],
						flag: mergeFlag,
					};
				} else if (arg1) {
					// merge ref [# message]
					const parts = arg1.split(mergeCommentSplitRegex);
					entry = {
						line: count,
						action: action,
						ref: parts[0],
						message: parts[1],
					};
				} else {
					continue;
				}
				break;
			}
			default:
				continue;
		}

		entries.push(entry);
	}

	sw?.stop({ suffix: ` parsed ${entries.length} entries` });

	return { entries: entries, info: info };
}
