import { styleText } from 'node:util';
import type { Validate } from '@clack/core';
import { settings, TextPrompt } from '@clack/core';
import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js';

/**
 * Options for the {@link text} prompt
 */
export interface TextOptions extends CommonOptions {
	/**
	 * The prompt message or question shown to the user above the input.
	 */
	message: string;

	/**
	 * A visual hint shown when the field has no content.
	 */
	placeholder?: string;

	/**
	 * A fallback value returned when the user provides nothing (empty input).
	 */
	defaultValue?: string;

	/**
	 * The starting value shown when the prompt first renders.
	 * Users can edit this value before submitting.
	 */
	initialValue?: string;

	/**
	 * A function or a [Standard Schema](https://github.com/standard-schema/standard-schema)
	 * that validates user input. If a custom function is given, you should return a `string` or `Error`
	 * to show as a validation error, or `undefined` to accept the result.
	 */
	validate?: Validate<string>;
}

/**
 * The text prompt accepts a single line of text.
 *
 * @see https://bomb.sh/docs/clack/packages/prompts/#text-input
 *
 * @example
 * ```ts
 * import { text } from '@clack/prompts';
 *
 * const name = await text({
 *   message: 'What is your name?',
 *   placeholder: 'John Doe',
 *   validate: (value) => {
 *     if (!value || value.length < 2) return 'Name must be at least 2 characters';
 *     return undefined;
 *   },
 * });
 * ```
 */
export const text = (opts: TextOptions) => {
	return new TextPrompt({
		validate: opts.validate,
		placeholder: opts.placeholder,
		defaultValue: opts.defaultValue,
		initialValue: opts.initialValue,
		output: opts.output,
		signal: opts.signal,
		input: opts.input,
		render() {
			const hasGuide = opts?.withGuide ?? settings.withGuide;
			const titlePrefix = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${symbol(this.state)}  `;
			const title = `${titlePrefix}${opts.message}\n`;
			const placeholder = opts.placeholder
				? styleText('inverse', opts.placeholder[0]) + styleText('dim', opts.placeholder.slice(1))
				: styleText(['inverse', 'hidden'], '_');
			const userInput = !this.userInput ? placeholder : this.userInputWithCursor;
			const value = this.value ?? '';

			switch (this.state) {
				case 'validating': {
					const validatePrefix = hasGuide ? `${styleText('cyan', S_BAR)}  ` : '';
					const validatePrefixEnd = hasGuide ? styleText('cyan', S_BAR_END) : '';
					const userInputText = styleText('dim', userInput);
					const validateText = styleText('dim', 'Validating...');
					return `${title}${validatePrefix}${userInputText}\n${validatePrefixEnd}  ${validateText}\n`;
				}
				case 'error': {
					const errorText = this.error ? `  ${styleText('yellow', this.error)}` : '';
					const errorPrefix = hasGuide ? `${styleText('yellow', S_BAR)}  ` : '';
					const errorPrefixEnd = hasGuide ? styleText('yellow', S_BAR_END) : '';
					return `${title.trim()}\n${errorPrefix}${userInput}\n${errorPrefixEnd}${errorText}\n`;
				}
				case 'submit': {
					const valueText = value ? `  ${styleText('dim', value)}` : '';
					const submitPrefix = hasGuide ? styleText('gray', S_BAR) : '';
					return `${title}${submitPrefix}${valueText}`;
				}
				case 'cancel': {
					const valueText = value ? `  ${styleText(['strikethrough', 'dim'], value)}` : '';
					const cancelPrefix = hasGuide ? styleText('gray', S_BAR) : '';
					return `${title}${cancelPrefix}${valueText}${value.trim() ? `\n${cancelPrefix}` : ''}`;
				}
				default: {
					const defaultPrefix = hasGuide ? `${styleText('cyan', S_BAR)}  ` : '';
					const defaultPrefixEnd = hasGuide ? styleText('cyan', S_BAR_END) : '';
					return `${title}${defaultPrefix}${userInput}\n${defaultPrefixEnd}\n`;
				}
			}
		},
	}).prompt() as Promise<string | symbol>;
};
