[content] src/utilities.ts (typescript)
import { tmpdir } from 'node:os'

interface DefaultConfiguration {
	[key: string]: (typeof defaultConfiguration)[keyof typeof defaultConfiguration]
}

const defaultConfiguration = contributes.configuration.properties

export const outputChannel = vscode.window.createOutputChannel('crowd-code')

/**
 * Retrieves the configuration object for the 'crowdCode' extension.
 *
 * @returns The configuration object for the 'crowdCode' extension.
 */
export function getConfig() {
	return vscode.workspace.getConfiguration('crowdCode')
}

/**
 * Creates a directory at the specified path if it does not already exist.
 *
 * @param path - The path of the directory to create.
 * @returns Void.
 */
export async function createPath(path: string) {
	// If the setting is enabled and the path is inside the workspace, add it to .gitignore
	if (
		getConfig().get<boolean>('export.addToGitignore') &&
		getConfig().get<string>('export.exportPath')?.startsWith('${workspaceFolder}')
	) {
		await addToGitignore()
	}

	if (!fs.existsSync(path)) {
		fs.mkdirSync(path)
	}
}

/**
 * Retrieves the export path for the crowd-code extension, handling various scenarios such as:
 * - If no export path is specified, it prompts the user to reset to default or open the settings.
 * - If the export path starts with '${workspaceFolder}', it replaces it with the actual workspace path.
 * - If the export path does not exist and the 'export.createPathOutsideWorkspace' setting is false, it prompts the user to reset to default or open the settings.
 * - It trims, normalizes, and updates the export path in the extension settings.
 *
 * @returns The normalized and updated export path, or `undefined` if an error occurred.
 */
export function getExportPath(): string | undefined {
	const exportPath = getConfig().get<string>('export.exportPath')
	let outputExportPath = exportPath
	const resetToDefaultMessage = 'Reset to default'
	const openSettingsMessage = 'Open Settings'
	const cancelMessage = 'Cancel'

	/**
	 * Handles the user's selection when prompted to reset the export path to the default or open the settings.
	 *
	 * @param selection - The user's selection, which can be 'Reset to default', 'Open Settings', or 'Cancel'.
	 * @returns Void.
	 */
	function handleSelection(selection: string | undefined) {
		if (selection === resetToDefaultMessage) {
			getConfig().update('export.exportPath', undefined, vscode.ConfigurationTarget.Global)
		}
		if (selection === 'Open Settings') {
			vscode.commands.executeCommand(
				'workbench.action.openSettings',
				'crowdCode.export.exportPath'
			)
		}
	}
	if (outputExportPath?.startsWith('${TMPDIR}')) {
		const exportDir = tmpdir() ?? "/tmp"
		outputExportPath = outputExportPath.replace('${TMPDIR}', exportDir)
		try {
			if (!fs.existsSync(outputExportPath)) {
				fs.mkdirSync(outputExportPath, { recursive: true })
			}
		} catch (err) {
			const errorMessage = vscode.l10n.t('Failed to create export path: {0}', String(err));
			vscode.window.showErrorMessage(errorMessage);
			logToOutput(errorMessage, 'error');
			return
		}
	}

	if (!outputExportPath) {
		const exportPathNotFoundMessage = 'No export path specified'
		vscode.window
			.showErrorMessage(
				exportPathNotFoundMessage,
				resetToDefaultMessage,
				openSettingsMessage,
				cancelMessage
			)
			.then(selection => handleSelection(selection))
		logToOutput(exportPathNotFoundMessage, 'error')
		return
	}

	if (outputExportPath?.startsWith('${workspaceFolder}')) {
		const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath
		if (!workspacePath) {
			const errorMessage = 'No workspace folder found'
			vscode.window.showErrorMessage(errorMessage)
			logToOutput(errorMessage, 'error')
			return
		}
		outputExportPath = outputExportPath.replace('${workspaceFolder}', workspacePath)
		createPath(outputExportPath)
	} else {
		if (
			!fs.existsSync(outputExportPath) &&
			getConfig().get<boolean>('export.createPathOutsideWorkspace', false) === false
		) {
			const exportPathNotFoundMessage = 'Export path does not exist'
			vscode.window
				.showErrorMessage(
					exportPathNotFoundMessage,
					resetToDefaultMessage,
					openSettingsMessage,
					cancelMessage
				)
				// deepcode ignore PromiseNotCaughtGeneral: catch method not available
				.then(selection => handleSelection(selection))
			logToOutput(exportPathNotFoundMessage, 'error')
			return
		}
		createPath(outputExportPath)
	}

	outputExportPath = outputExportPath.trim()
	outputExportPath = outputExportPath.replaceAll('\\', '/')
	if (!outputExportPath.endsWith('/')) {
		outputExportPath += '/'
	}