import path from 'path';
import fs from 'fs';
import inquirer from 'inquirer';
import chalk from 'chalk';
import boxen from 'boxen';
import Table from 'cli-table3';

import {
	log,
	readJSON,
	writeJSON,
	getCurrentTag,
	resolveTag,
	getTasksForTag,
	setTasksForTag,
	findProjectRoot,
	truncate
} from '../utils.js';
import { displayBanner, getStatusWithColor } from '../ui.js';
import findNextTask from './find-next-task.js';

/**
 * Create a new tag context
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} tagName - Name of the new tag to create
 * @param {Object} options - Options object
 * @param {boolean} [options.copyFromCurrent=false] - Whether to copy tasks from current tag
 * @param {string} [options.copyFromTag] - Specific tag to copy tasks from
 * @param {string} [options.description] - Optional description for the tag
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with tag creation details
 */
async function createTag(
	tasksPath,
	tagName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { copyFromCurrent = false, copyFromTag, description } = options;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate tag name
		if (!tagName || typeof tagName !== 'string') {
			throw new Error('Tag name is required and must be a string');
		}

		// Validate tag name format (alphanumeric, hyphens, underscores only)
		if (!/^[a-zA-Z0-9_-]+$/.test(tagName)) {
			throw new Error(
				'Tag name can only contain letters, numbers, hyphens, and underscores'
			);
		}

		// Reserved tag names
		const reservedNames = ['master', 'main', 'default'];
		if (reservedNames.includes(tagName.toLowerCase())) {
			throw new Error(`"${tagName}" is a reserved tag name`);
		}

		logFn.info(`Creating new tag: ${tagName}`);

		// Read current tasks data
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use raw tagged data for tag operations - ensure we get the actual tagged structure
		let rawData;
		if (data._rawTaggedData) {
			// If we have _rawTaggedData, use it (this is the clean tagged structure)
			rawData = data._rawTaggedData;
		} else if (data.tasks && !data.master) {
			// This is legacy format - create a master tag structure
			rawData = {
				master: {
					tasks: data.tasks,
					metadata: data.metadata || {
						created: new Date().toISOString(),
						updated: new Date().toISOString(),
						description: 'Tasks live here by default'
					}
				}
			};
		} else {
			// This is already in tagged format, use it directly but exclude internal fields
			rawData = {};
			for (const [key, value] of Object.entries(data)) {
				if (key !== '_rawTaggedData' && key !== 'tag') {
					rawData[key] = value;
				}
			}
		}

		// Check if tag already exists
		if (rawData[tagName]) {
			throw new Error(`Tag "${tagName}" already exists`);
		}

		// Determine source for copying tasks (only if explicitly requested)
		let sourceTasks = [];
		if (copyFromCurrent || copyFromTag) {
			const sourceTag = copyFromTag || getCurrentTag(projectRoot);
			sourceTasks = getTasksForTag(rawData, sourceTag);

			if (copyFromTag && sourceTasks.length === 0) {
				logFn.warn(`Source tag "${copyFromTag}" not found or has no tasks`);
			}

			logFn.info(`Copying ${sourceTasks.length} tasks from tag "${sourceTag}"`);
		} else {
			logFn.info('Creating empty tag (no tasks copied)');
		}

		// Create the new tag structure in raw data
		rawData[tagName] = {
			tasks: [...sourceTasks], // Create a copy of the tasks array
			metadata: {
				created: new Date().toISOString(),
				updated: new Date().toISOString(),
				description:
					description || `Tag created on ${new Date().toLocaleDateString()}`
			}
		};

		// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
		const cleanData = {};
		for (const [key, value] of Object.entries(rawData)) {
			if (key !== '_rawTaggedData') {
				cleanData[key] = value;
			}
		}

		// Write the clean data back to file with proper context to avoid tag corruption
		writeJSON(tasksPath, cleanData, projectRoot);

		logFn.success(`Successfully created tag "${tagName}"`);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				tagName,
				created: true,
				tasksCopied: sourceTasks.length,
				sourceTag:
					copyFromCurrent || copyFromTag
						? copyFromTag || getCurrentTag(projectRoot)
						: null,
				description:
					description || `Tag created on ${new Date().toLocaleDateString()}`
			};
		}

		// For text output, display success message
		if (outputFormat === 'text') {
			console.log(
				boxen(
					chalk.green.bold('✓ Tag Created Successfully') +
						`\n\nTag Name: ${chalk.cyan(tagName)}` +
						`\nTasks Copied: ${chalk.yellow(sourceTasks.length)}` +
						(copyFromCurrent || copyFromTag
							? `\nSource Tag: ${chalk.cyan(copyFromTag || getCurrentTag(projectRoot))}`
							: '') +
						(description ? `\nDescription: ${chalk.gray(description)}` : ''),
					{
						padding: 1,
						borderColor: 'green',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);
		}

		return {
			tagName,
			created: true,
			tasksCopied: sourceTasks.length,
			sourceTag:
				copyFromCurrent || copyFromTag
					? copyFromTag || getCurrentTag(projectRoot)
					: null,
			description:
				description || `Tag created on ${new Date().toLocaleDateString()}`
		};
	} catch (error) {
		logFn.error(`Error creating tag: ${error.message}`);
		throw error;
	}
}

/**
 * Delete an existing tag
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} tagName - Name of the tag to delete
 * @param {Object} options - Options object
 * @param {boolean} [options.yes=false] - Skip confirmation prompts
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with deletion details
 */
async function deleteTag(
	tasksPath,
	tagName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { yes = false } = options;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate tag name
		if (!tagName || typeof tagName !== 'string') {
			throw new Error('Tag name is required and must be a string');
		}

		// Prevent deletion of master tag
		if (tagName === 'master') {
			throw new Error('Cannot delete the "master" tag');
		}

		logFn.info(`Deleting tag: ${tagName}`);

		// Read current tasks data
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use raw tagged data for tag operations - ensure we get the actual tagged structure
		let rawData;
		if (data._rawTaggedData) {
			// If we have _rawTaggedData, use it (this is the clean tagged structure)
			rawData = data._rawTaggedData;
		} else if (data.tasks && !data.master) {
			// This is legacy format - create a master tag structure
			rawData = {
				master: {
					tasks: data.tasks,
					metadata: data.metadata || {
						created: new Date().toISOString(),
						updated: new Date().toISOString(),
						description: 'Tasks live here by default'
					}
				}
			};
		} else {
			// This is already in tagged format, use it directly but exclude internal fields
			rawData = {};
			for (const [key, value] of Object.entries(data)) {
				if (key !== '_rawTaggedData' && key !== 'tag') {
					rawData[key] = value;
				}
			}
		}

		// Check if tag exists
		if (!rawData[tagName]) {
			throw new Error(`Tag "${tagName}" does not exist`);
		}

		// Get current tag to check if we're deleting the active tag
		const currentTag = getCurrentTag(projectRoot);
		const isCurrentTag = currentTag === tagName;

		// Get task count for confirmation
		const tasks = getTasksForTag(rawData, tagName);
		const taskCount = tasks.length;

		// If not forced and has tasks, require confirmation (for CLI)
		if (!yes && taskCount > 0 && outputFormat === 'text') {
			console.log(
				boxen(
					chalk.yellow.bold('⚠ WARNING: Tag Deletion') +
						`\n\nYou are about to delete tag "${chalk.cyan(tagName)}"` +
						`\nThis will permanently delete ${chalk.red.bold(taskCount)} tasks` +
						'\n\nThis action cannot be undone!',
					{
						padding: 1,
						borderColor: 'yellow',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);

			// First confirmation
			const firstConfirm = await inquirer.prompt([
				{
					type: 'confirm',
					name: 'proceed',
					message: `Are you sure you want to delete tag "${tagName}" and its ${taskCount} tasks?`,
					default: false
				}
			]);

			if (!firstConfirm.proceed) {
				logFn.info('Tag deletion cancelled by user');
				throw new Error('Tag deletion cancelled');
			}

			// Second confirmation (double-check)
			const secondConfirm = await inquirer.prompt([
				{
					type: 'input',
					name: 'tagNameConfirm',
					message: `To confirm deletion, please type the tag name "${tagName}":`,
					validate: (input) => {
						if (input === tagName) {
							return true;
						}
						return `Please type exactly "${tagName}" to confirm deletion`;
					}
				}
			]);

			if (secondConfirm.tagNameConfirm !== tagName) {
				logFn.info('Tag deletion cancelled - incorrect tag name confirmation');
				throw new Error('Tag deletion cancelled');
			}

			logFn.info('Double confirmation received, proceeding with deletion...');
		}

		// Delete the tag
		delete rawData[tagName];

		// If we're deleting the current tag, switch to master
		if (isCurrentTag) {
			await switchCurrentTag(projectRoot, 'master');
			logFn.info('Switched current tag to "master"');
		}

		// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
		const cleanData = {};
		for (const [key, value] of Object.entries(rawData)) {
			if (key !== '_rawTaggedData') {
				cleanData[key] = value;
			}
		}

		// Write the clean data back to file with proper context to avoid tag corruption
		writeJSON(tasksPath, cleanData, projectRoot);

		logFn.success(`Successfully deleted tag "${tagName}"`);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				tagName,
				deleted: true,
				tasksDeleted: taskCount,
				wasCurrentTag: isCurrentTag,
				switchedToMaster: isCurrentTag
			};
		}

		// For text output, display success message
		if (outputFormat === 'text') {
			console.log(
				boxen(
					chalk.red.bold('✓ Tag Deleted Successfully') +
						`\n\nTag Name: ${chalk.cyan(tagName)}` +
						`\nTasks Deleted: ${chalk.yellow(taskCount)}` +
						(isCurrentTag
							? `\n${chalk.yellow('⚠ Switched current tag to "master"')}`
							: ''),
					{
						padding: 1,
						borderColor: 'red',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);
		}

		return {
			tagName,
			deleted: true,
			tasksDeleted: taskCount,
			wasCurrentTag: isCurrentTag,
			switchedToMaster: isCurrentTag
		};
	} catch (error) {
		logFn.error(`Error deleting tag: ${error.message}`);
		throw error;
	}
}

/**
 * Enhance existing tags with metadata if they don't have it
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {Object} rawData - The raw tagged data
 * @param {Object} context - Context object
 * @returns {Promise<boolean>} True if any tags were enhanced
 */
async function enhanceTagsWithMetadata(tasksPath, rawData, context = {}) {
	let enhanced = false;

	try {
		// Get file stats for creation date fallback
		let fileCreatedDate;
		try {
			const stats = fs.statSync(tasksPath);
			fileCreatedDate =
				stats.birthtime < stats.mtime ? stats.birthtime : stats.mtime;
		} catch (error) {
			fileCreatedDate = new Date();
		}

		for (const [tagName, tagData] of Object.entries(rawData)) {
			// Skip non-tag properties
			if (
				tagName === 'tasks' ||
				tagName === 'tag' ||
				tagName === '_rawTaggedData' ||
				!tagData ||
				typeof tagData !== 'object' ||
				!Array.isArray(tagData.tasks)
			) {
				continue;
			}

			// Check if tag needs metadata enhancement
			if (!tagData.metadata) {
				tagData.metadata = {};
				enhanced = true;
			}

			// Add missing metadata fields
			if (!tagData.metadata.created) {
				tagData.metadata.created = fileCreatedDate.toISOString();
				enhanced = true;
			}

			if (!tagData.metadata.description) {
				if (tagName === 'master') {
					tagData.metadata.description = 'Tasks live here by default';
				} else {
					tagData.metadata.description = `Tag created on ${new Date(tagData.metadata.created).toLocaleDateString()}`;
				}
				enhanced = true;
			}

			// Add updated field if missing (set to created date initially)
			if (!tagData.metadata.updated) {
				tagData.metadata.updated = tagData.metadata.created;
				enhanced = true;
			}
		}

		// If we enhanced any tags, write the data back
		if (enhanced) {
			// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
			const cleanData = {};
			for (const [key, value] of Object.entries(rawData)) {
				if (key !== '_rawTaggedData') {
					cleanData[key] = value;
				}
			}
			writeJSON(tasksPath, cleanData, context.projectRoot);
		}
	} catch (error) {
		// Don't throw - just log and continue
		const logFn = context.mcpLog || {
			warn: (...args) => log('warn', ...args)
		};
		logFn.warn(`Could not enhance tag metadata: ${error.message}`);
	}

	return enhanced;
}

/**
 * List all available tags with metadata
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {Object} options - Options object
 * @param {boolean} [options.showTaskCounts=true] - Whether to show task counts
 * @param {boolean} [options.showMetadata=false] - Whether to show metadata
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with tags list
 */
async function tags(
	tasksPath,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { showTaskCounts = true, showMetadata = false } = options;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		logFn.info('Listing available tags');

		// Read current tasks data
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Get current tag
		const currentTag = getCurrentTag(projectRoot);

		// Use raw tagged data if available, otherwise use the data directly
		const rawData = data._rawTaggedData || data;

		// Enhance existing tags with metadata if they don't have it
		await enhanceTagsWithMetadata(tasksPath, rawData, context);

		// Extract all tags
		const tagList = [];
		for (const [tagName, tagData] of Object.entries(rawData)) {
			// Skip non-tag properties (like legacy 'tasks' array, 'tag', '_rawTaggedData')
			if (
				tagName === 'tasks' ||
				tagName === 'tag' ||
				tagName === '_rawTaggedData' ||
				!tagData ||
				typeof tagData !== 'object' ||
				!Array.isArray(tagData.tasks)
			) {
				continue;
			}

			const tasks = tagData.tasks || [];
			const metadata = tagData.metadata || {};

			tagList.push({
				name: tagName,
				isCurrent: tagName === currentTag,
				completedTasks: tasks.filter(
					(t) => t.status === 'done' || t.status === 'completed'
				).length,
				tasks: tasks || [],
				created: metadata.created || 'Unknown',
				description: metadata.description || 'No description'
			});
		}

		// Sort tags: current tag first, then alphabetically
		tagList.sort((a, b) => {
			if (a.isCurrent) return -1;
			if (b.isCurrent) return 1;
			return a.name.localeCompare(b.name);
		});

		logFn.success(`Found ${tagList.length} tags`);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				tags: tagList,
				currentTag,
				totalTags: tagList.length
			};
		}

		// For text output, display formatted table
		if (outputFormat === 'text') {
			if (tagList.length === 0) {
				console.log(
					boxen(chalk.yellow('No tags found'), {
						padding: 1,
						borderColor: 'yellow',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					})
				);
				return { tags: [], currentTag, totalTags: 0 };
			}

			// Create table headers based on options
			const headers = [chalk.cyan.bold('Tag Name')];
			if (showTaskCounts) {
				headers.push(chalk.cyan.bold('Tasks'));
				headers.push(chalk.cyan.bold('Completed'));
			}
			if (showMetadata) {
				headers.push(chalk.cyan.bold('Created'));
				headers.push(chalk.cyan.bold('Description'));
			}

			// Calculate dynamic column widths based on terminal width
			const terminalWidth = Math.max(process.stdout.columns || 120, 80);
			const usableWidth = Math.floor(terminalWidth * 0.95);

			let colWidths;
			if (showMetadata) {
				// With metadata: Tag Name, Tasks, Completed, Created, Description
				const widths = [0.25, 0.1, 0.12, 0.15, 0.38];
				colWidths = widths.map((w, i) =>
					Math.max(Math.floor(usableWidth * w), i === 0 ? 15 : 8)
				);
			} else {
				// Without metadata: Tag Name, Tasks, Completed
				const widths = [0.7, 0.15, 0.15];
				colWidths = widths.map((w, i) =>
					Math.max(Math.floor(usableWidth * w), i === 0 ? 20 : 10)
				);
			}

			const table = new Table({
				head: headers,
				colWidths: colWidths,
				wordWrap: true
			});

			// Add rows
			tagList.forEach((tag) => {
				const row = [];

				// Tag name with current indicator
				const tagDisplay = tag.isCurrent
					? `${chalk.green('●')} ${chalk.green.bold(tag.name)} ${chalk.gray('(current)')}`
					: `  ${tag.name}`;
				row.push(tagDisplay);

				if (showTaskCounts) {
					row.push(chalk.white(tag.tasks.length.toString()));
					row.push(chalk.green(tag.completedTasks.toString()));
				}

				if (showMetadata) {
					const createdDate =
						tag.created !== 'Unknown'
							? new Date(tag.created).toLocaleDateString()
							: 'Unknown';
					row.push(chalk.gray(createdDate));
					row.push(chalk.gray(truncate(tag.description, 50)));
				}

				table.push(row);
			});

			// console.log(
			// 	boxen(
			// 		chalk.white.bold('Available Tags') +
			// 			`\n\nCurrent Tag: ${chalk.green.bold(currentTag)}`,
			// 		{
			// 			padding: { top: 0, bottom: 1, left: 1, right: 1 },
			// 			borderColor: 'blue',
			// 			borderStyle: 'round',
			// 			margin: { top: 1, bottom: 0 }
			// 		}
			// 	)
			// );

			console.log(table.toString());
		}

		return {
			tags: tagList,
			currentTag,
			totalTags: tagList.length
		};
	} catch (error) {
		logFn.error(`Error listing tags: ${error.message}`);
		throw error;
	}
}

/**
 * Switch to a different tag context
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} tagName - Name of the tag to switch to
 * @param {Object} options - Options object
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with switch details
 */
async function useTag(
	tasksPath,
	tagName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate tag name
		if (!tagName || typeof tagName !== 'string') {
			throw new Error('Tag name is required and must be a string');
		}

		logFn.info(`Switching to tag: ${tagName}`);

		// Read current tasks data to verify tag exists
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use raw tagged data to check if tag exists
		const rawData = data._rawTaggedData || data;

		// Check if tag exists
		if (!rawData[tagName]) {
			throw new Error(`Tag "${tagName}" does not exist`);
		}

		// Get current tag
		const previousTag = getCurrentTag(projectRoot);

		// Switch to the new tag
		await switchCurrentTag(projectRoot, tagName);

		// Get task count for the new tag - read tasks specifically for this tag
		const tagData = readJSON(tasksPath, projectRoot, tagName);
		const tasks = tagData ? tagData.tasks || [] : [];
		const taskCount = tasks.length;

		// Find the next task to work on in this tag
		const nextTask = findNextTask(tasks);

		logFn.success(`Successfully switched to tag "${tagName}"`);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				previousTag,
				currentTag: tagName,
				switched: true,
				taskCount,
				nextTask
			};
		}

		// For text output, display success message
		if (outputFormat === 'text') {
			let nextTaskInfo = '';
			if (nextTask) {
				nextTaskInfo = `\nNext Task: ${chalk.cyan(`#${nextTask.id}`)} - ${chalk.white(nextTask.title)}`;
			} else {
				nextTaskInfo = `\nNext Task: ${chalk.gray('No eligible tasks available')}`;
			}

			console.log(
				boxen(
					chalk.green.bold('✓ Tag Switched Successfully') +
						`\n\nPrevious Tag: ${chalk.cyan(previousTag)}` +
						`\nCurrent Tag: ${chalk.green.bold(tagName)}` +
						`\nAvailable Tasks: ${chalk.yellow(taskCount)}` +
						nextTaskInfo,
					{
						padding: 1,
						borderColor: 'green',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);
		}

		return {
			previousTag,
			currentTag: tagName,
			switched: true,
			taskCount,
			nextTask
		};
	} catch (error) {
		logFn.error(`Error switching tag: ${error.message}`);
		throw error;
	}
}

/**
 * Rename an existing tag
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} oldName - Current name of the tag
 * @param {string} newName - New name for the tag
 * @param {Object} options - Options object
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with rename details
 */
async function renameTag(
	tasksPath,
	oldName,
	newName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate parameters
		if (!oldName || typeof oldName !== 'string') {
			throw new Error('Old tag name is required and must be a string');
		}
		if (!newName || typeof newName !== 'string') {
			throw new Error('New tag name is required and must be a string');
		}

		// Validate new tag name format
		if (!/^[a-zA-Z0-9_-]+$/.test(newName)) {
			throw new Error(
				'New tag name can only contain letters, numbers, hyphens, and underscores'
			);
		}

		// Prevent renaming master tag
		if (oldName === 'master') {
			throw new Error('Cannot rename the "master" tag');
		}

		// Reserved tag names
		const reservedNames = ['master', 'main', 'default'];
		if (reservedNames.includes(newName.toLowerCase())) {
			throw new Error(`"${newName}" is a reserved tag name`);
		}

		logFn.info(`Renaming tag from "${oldName}" to "${newName}"`);

		// Read current tasks data
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use raw tagged data for tag operations
		const rawData = data._rawTaggedData || data;

		// Check if old tag exists
		if (!rawData[oldName]) {
			throw new Error(`Tag "${oldName}" does not exist`);
		}

		// Check if new tag name already exists
		if (rawData[newName]) {
			throw new Error(`Tag "${newName}" already exists`);
		}

		// Get current tag to check if we're renaming the active tag
		const currentTag = getCurrentTag(projectRoot);
		const isCurrentTag = currentTag === oldName;

		// Rename the tag by copying data and deleting old
		rawData[newName] = { ...rawData[oldName] };

		// Update metadata if it exists
		if (rawData[newName].metadata) {
			rawData[newName].metadata.renamed = {
				from: oldName,
				date: new Date().toISOString()
			};
		}

		delete rawData[oldName];

		// If we're renaming the current tag, update the current tag reference
		if (isCurrentTag) {
			await switchCurrentTag(projectRoot, newName);
			logFn.info(`Updated current tag reference to "${newName}"`);
		}

		// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
		const cleanData = {};
		for (const [key, value] of Object.entries(rawData)) {
			if (key !== '_rawTaggedData') {
				cleanData[key] = value;
			}
		}

		// Write the clean data back to file with proper context to avoid tag corruption
		writeJSON(tasksPath, cleanData, projectRoot);

		// Get task count
		const tasks = getTasksForTag(rawData, newName);
		const taskCount = tasks.length;

		logFn.success(`Successfully renamed tag from "${oldName}" to "${newName}"`);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				oldName,
				newName,
				renamed: true,
				taskCount,
				wasCurrentTag: isCurrentTag,
				isCurrentTag: isCurrentTag
			};
		}

		// For text output, display success message
		if (outputFormat === 'text') {
			console.log(
				boxen(
					chalk.green.bold('✓ Tag Renamed Successfully') +
						`\n\nOld Name: ${chalk.cyan(oldName)}` +
						`\nNew Name: ${chalk.green.bold(newName)}` +
						`\nTasks: ${chalk.yellow(taskCount)}` +
						(isCurrentTag ? `\n${chalk.green('✓ Current tag updated')}` : ''),
					{
						padding: 1,
						borderColor: 'green',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);
		}

		return {
			oldName,
			newName,
			renamed: true,
			taskCount,
			wasCurrentTag: isCurrentTag,
			isCurrentTag: isCurrentTag
		};
	} catch (error) {
		logFn.error(`Error renaming tag: ${error.message}`);
		throw error;
	}
}

/**
 * Copy an existing tag to create a new tag with the same tasks
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} sourceName - Name of the source tag to copy from
 * @param {string} targetName - Name of the new tag to create
 * @param {Object} options - Options object
 * @param {string} [options.description] - Optional description for the new tag
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with copy details
 */
async function copyTag(
	tasksPath,
	sourceName,
	targetName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { description } = options;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate parameters
		if (!sourceName || typeof sourceName !== 'string') {
			throw new Error('Source tag name is required and must be a string');
		}
		if (!targetName || typeof targetName !== 'string') {
			throw new Error('Target tag name is required and must be a string');
		}

		// Validate target tag name format
		if (!/^[a-zA-Z0-9_-]+$/.test(targetName)) {
			throw new Error(
				'Target tag name can only contain letters, numbers, hyphens, and underscores'
			);
		}

		// Reserved tag names
		const reservedNames = ['master', 'main', 'default'];
		if (reservedNames.includes(targetName.toLowerCase())) {
			throw new Error(`"${targetName}" is a reserved tag name`);
		}

		logFn.info(`Copying tag from "${sourceName}" to "${targetName}"`);

		// Read current tasks data
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use raw tagged data for tag operations
		const rawData = data._rawTaggedData || data;

		// Check if source tag exists
		if (!rawData[sourceName]) {
			throw new Error(`Source tag "${sourceName}" does not exist`);
		}

		// Check if target tag already exists
		if (rawData[targetName]) {
			throw new Error(`Target tag "${targetName}" already exists`);
		}

		// Get source tasks
		const sourceTasks = getTasksForTag(rawData, sourceName);

		// Create deep copy of the source tag data
		rawData[targetName] = {
			tasks: JSON.parse(JSON.stringify(sourceTasks)), // Deep copy tasks
			metadata: {
				created: new Date().toISOString(),
				updated: new Date().toISOString(),
				description:
					description ||
					`Copy of "${sourceName}" created on ${new Date().toLocaleDateString()}`,
				copiedFrom: {
					tag: sourceName,
					date: new Date().toISOString()
				}
			}
		};

		// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
		const cleanData = {};
		for (const [key, value] of Object.entries(rawData)) {
			if (key !== '_rawTaggedData') {
				cleanData[key] = value;
			}
		}

		// Write the clean data back to file with proper context to avoid tag corruption
		writeJSON(tasksPath, cleanData, projectRoot);

		logFn.success(
			`Successfully copied tag from "${sourceName}" to "${targetName}"`
		);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				sourceName,
				targetName,
				copied: true,
				description:
					description ||
					`Copy of "${sourceName}" created on ${new Date().toLocaleDateString()}`
			};
		}

		// For text output, display success message
		if (outputFormat === 'text') {
			console.log(
				boxen(
					chalk.green.bold('✓ Tag Copied Successfully') +
						`\n\nSource Tag: ${chalk.cyan(sourceName)}` +
						`\nTarget Tag: ${chalk.green.bold(targetName)}` +
						`\nTasks Copied: ${chalk.yellow(sourceTasks.length)}` +
						(description ? `\nDescription: ${chalk.gray(description)}` : ''),
					{
						padding: 1,
						borderColor: 'green',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);
		}

		return {
			sourceName,
			targetName,
			copied: true,
			description:
				description ||
				`Copy of "${sourceName}" created on ${new Date().toLocaleDateString()}`
		};
	} catch (error) {
		logFn.error(`Error copying tag: ${error.message}`);
		throw error;
	}
}

/**
 * Helper function to switch the current tag in state.json
 * @param {string} projectRoot - Project root directory
 * @param {string} tagName - Name of the tag to switch to
 * @returns {Promise<void>}
 */
async function switchCurrentTag(projectRoot, tagName) {
	try {
		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');

		// Read current state or create default
		let state = {};
		if (fs.existsSync(statePath)) {
			const rawState = fs.readFileSync(statePath, 'utf8');
			state = JSON.parse(rawState);
		}

		// Update current tag and timestamp
		state.currentTag = tagName;
		state.lastSwitched = new Date().toISOString();

		// Ensure other required state properties exist
		if (!state.branchTagMapping) {
			state.branchTagMapping = {};
		}
		if (state.migrationNoticeShown === undefined) {
			state.migrationNoticeShown = false;
		}

		// Write updated state
		fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
	} catch (error) {
		log('warn', `Could not update current tag in state.json: ${error.message}`);
		// Don't throw - this is not critical for tag operations
	}
}

/**
 * Update branch-tag mapping in state.json
 * @param {string} projectRoot - Project root directory
 * @param {string} branchName - Git branch name
 * @param {string} tagName - Tag name to map to
 * @returns {Promise<void>}
 */
async function updateBranchTagMapping(projectRoot, branchName, tagName) {
	try {
		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');

		// Read current state or create default
		let state = {};
		if (fs.existsSync(statePath)) {
			const rawState = fs.readFileSync(statePath, 'utf8');
			state = JSON.parse(rawState);
		}

		// Ensure branchTagMapping exists
		if (!state.branchTagMapping) {
			state.branchTagMapping = {};
		}

		// Update the mapping
		state.branchTagMapping[branchName] = tagName;

		// Write updated state
		fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
	} catch (error) {
		log('warn', `Could not update branch-tag mapping: ${error.message}`);
		// Don't throw - this is not critical for tag operations
	}
}

/**
 * Get tag name for a git branch from state.json mapping
 * @param {string} projectRoot - Project root directory
 * @param {string} branchName - Git branch name
 * @returns {Promise<string|null>} Mapped tag name or null if not found
 */
async function getTagForBranch(projectRoot, branchName) {
	try {
		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');

		if (!fs.existsSync(statePath)) {
			return null;
		}

		const rawState = fs.readFileSync(statePath, 'utf8');
		const state = JSON.parse(rawState);

		return state.branchTagMapping?.[branchName] || null;
	} catch (error) {
		return null;
	}
}

/**
 * Create a tag from a git branch name
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} branchName - Git branch name to create tag from
 * @param {Object} options - Options object
 * @param {boolean} [options.copyFromCurrent] - Copy tasks from current tag
 * @param {string} [options.copyFromTag] - Copy tasks from specific tag
 * @param {string} [options.description] - Custom description for the tag
 * @param {boolean} [options.autoSwitch] - Automatically switch to the new tag
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with creation details
 */
async function createTagFromBranch(
	tasksPath,
	branchName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { copyFromCurrent, copyFromTag, description, autoSwitch } = options;

	// Import git utilities
	const { sanitizeBranchNameForTag, isValidBranchForTag } = await import(
		'../utils/git-utils.js'
	);

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate branch name
		if (!branchName || typeof branchName !== 'string') {
			throw new Error('Branch name is required and must be a string');
		}

		// Check if branch name is valid for tag creation
		if (!isValidBranchForTag(branchName)) {
			throw new Error(
				`Branch "${branchName}" cannot be converted to a valid tag name`
			);
		}

		// Sanitize branch name to create tag name
		const tagName = sanitizeBranchNameForTag(branchName);

		logFn.info(`Creating tag "${tagName}" from git branch "${branchName}"`);

		// Create the tag using existing createTag function
		const createResult = await createTag(
			tasksPath,
			tagName,
			{
				copyFromCurrent,
				copyFromTag,
				description:
					description || `Tag created from git branch "${branchName}"`
			},
			context,
			outputFormat
		);

		// Update branch-tag mapping
		await updateBranchTagMapping(projectRoot, branchName, tagName);
		logFn.info(`Updated branch-tag mapping: ${branchName} -> ${tagName}`);

		// Auto-switch to the new tag if requested
		if (autoSwitch) {
			await switchCurrentTag(projectRoot, tagName);
			logFn.info(`Automatically switched to tag "${tagName}"`);
		}

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				...createResult,
				branchName,
				tagName,
				mappingUpdated: true,
				autoSwitched: autoSwitch || false
			};
		}

		// For text output, the createTag function already handles display
		return {
			branchName,
			tagName,
			created: true,
			mappingUpdated: true,
			autoSwitched: autoSwitch || false
		};
	} catch (error) {
		logFn.error(`Error creating tag from branch: ${error.message}`);
		throw error;
	}
}

/**
 * Automatically switch tag based on current git branch
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {Object} options - Options object
 * @param {boolean} [options.createIfMissing] - Create tag if it doesn't exist
 * @param {boolean} [options.copyFromCurrent] - Copy tasks when creating new tag
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with switch details
 */
async function autoSwitchTagForBranch(
	tasksPath,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { createIfMissing, copyFromCurrent } = options;

	// Import git utilities
	const {
		getCurrentBranch,
		isGitRepository,
		sanitizeBranchNameForTag,
		isValidBranchForTag
	} = await import('../utils/git-utils.js');

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Check if we're in a git repository
		if (!(await isGitRepository(projectRoot))) {
			logFn.warn('Not in a git repository, cannot auto-switch tags');
			return { switched: false, reason: 'not_git_repo' };
		}

		// Get current git branch
		const currentBranch = await getCurrentBranch(projectRoot);
		if (!currentBranch) {
			logFn.warn('Could not determine current git branch');
			return { switched: false, reason: 'no_current_branch' };
		}

		logFn.info(`Current git branch: ${currentBranch}`);

		// Check if branch is valid for tag creation
		if (!isValidBranchForTag(currentBranch)) {
			logFn.info(`Branch "${currentBranch}" is not suitable for tag creation`);
			return {
				switched: false,
				reason: 'invalid_branch_for_tag',
				branchName: currentBranch
			};
		}

		// Check if there's already a mapping for this branch
		let tagName = await getTagForBranch(projectRoot, currentBranch);

		if (!tagName) {
			// No mapping exists, create tag name from branch
			tagName = sanitizeBranchNameForTag(currentBranch);
		}

		// Check if tag exists
		const data = readJSON(tasksPath, projectRoot);
		const rawData = data._rawTaggedData || data;
		const tagExists = rawData[tagName];

		if (!tagExists && createIfMissing) {
			// Create the tag from branch
			logFn.info(`Creating new tag "${tagName}" for branch "${currentBranch}"`);

			const createResult = await createTagFromBranch(
				tasksPath,
				currentBranch,
				{
					copyFromCurrent,
					autoSwitch: true
				},
				context,
				outputFormat
			);

			return {
				switched: true,
				created: true,
				branchName: currentBranch,
				tagName,
				...createResult
			};
		} else if (tagExists) {
			// Tag exists, switch to it
			logFn.info(
				`Switching to existing tag "${tagName}" for branch "${currentBranch}"`
			);

			const switchResult = await useTag(
				tasksPath,
				tagName,
				{},
				context,
				outputFormat
			);

			// Update mapping if it didn't exist
			if (!(await getTagForBranch(projectRoot, currentBranch))) {
				await updateBranchTagMapping(projectRoot, currentBranch, tagName);
			}

			return {
				switched: true,
				created: false,
				branchName: currentBranch,
				tagName,
				...switchResult
			};
		} else {
			// Tag doesn't exist and createIfMissing is false
			logFn.warn(
				`Tag "${tagName}" for branch "${currentBranch}" does not exist`
			);
			return {
				switched: false,
				reason: 'tag_not_found',
				branchName: currentBranch,
				tagName
			};
		}
	} catch (error) {
		logFn.error(`Error in auto-switch tag for branch: ${error.message}`);
		throw error;
	}
}

/**
 * Check git workflow configuration and perform auto-switch if enabled
 * @param {string} projectRoot - Project root directory
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {Object} context - Context object
 * @returns {Promise<Object|null>} Switch result or null if not enabled
 */
async function checkAndAutoSwitchTag(projectRoot, tasksPath, context = {}) {
	try {
		// Read configuration
		const configPath = path.join(projectRoot, '.taskmaster', 'config.json');
		if (!fs.existsSync(configPath)) {
			return null;
		}

		const rawConfig = fs.readFileSync(configPath, 'utf8');
		const config = JSON.parse(rawConfig);

		// Git workflow has been removed - return null to disable auto-switching
		return null;

		// Perform auto-switch
		return await autoSwitchTagForBranch(
			tasksPath,
			{ createIfMissing: true, copyFromCurrent: false },
			context,
			'json'
		);
	} catch (error) {
		// Silently fail - this is not critical
		return null;
	}
}

// Export all tag management functions
export {
	createTag,
	deleteTag,
	tags,
	useTag,
	renameTag,
	copyTag,
	switchCurrentTag,
	updateBranchTagMapping,
	getTagForBranch,
	createTagFromBranch,
	autoSwitchTagForBranch,
	checkAndAutoSwitchTag
};
