import { assert } from "./asserts.js";
import { deepPartial, instanceOfAbstractType, parse, testOptional, valita_exports } from "./valita.js";
import { must } from "./must.js";
import { template } from "chalk-template";
import commandLineArgs from "command-line-args";
import commandLineUsage from "command-line-usage";
import { createDefu } from "defu";
import { toKebabCase, toSnakeCase } from "kasi";
import { stripVTControlCharacters } from "node:util";
//#region ../shared/src/options.ts
/**
* Creates a defu instance that overrides arrays instead of merging them.
*/
var defu$1 = createDefu((obj, key, value) => {
	if (!Array.isArray(value)) return;
	obj[key] = value;
	return true;
});
/**
* Converts an Options instance into its corresponding {@link Config} schema.
*/
function configSchema(options, envNamePrefix) {
	function makeObjectType(options, group) {
		return valita_exports.object(Object.fromEntries(Object.entries(options).map(([name, value]) => {
			const addErrorMessage = (t) => {
				const { required } = getRequiredOrDefault(t);
				if (required) {
					const optionName = toSnakeCase(`${envNamePrefix}${group ? group + "_" : ""}${name}`).toUpperCase();
					return t.optional().assert((val) => val !== void 0, `Missing required option ${optionName}`);
				}
				return t;
			};
			if (instanceOfAbstractType(value)) return [name, addErrorMessage(value)];
			const { type } = value;
			if (instanceOfAbstractType(type)) return [name, addErrorMessage(type)];
			return [name, makeObjectType(value, name)];
		})));
	}
	return makeObjectType(options);
}
function getRequiredOrDefault(type) {
	const defaultResult = testOptional(void 0, type);
	return {
		required: !defaultResult.ok,
		defaultValue: defaultResult.ok ? defaultResult.value : void 0
	};
}
function parseOptions(appOptions, opts = {}) {
	return parseOptionsAdvanced(appOptions, opts).config;
}
function parseOptionsAdvanced(appOptions, opts = {}) {
	const { argv = process.argv.slice(2), envNamePrefix = "", description = [], allowUnknown = false, allowPartial = false, env: processEnv = process.env, emitDeprecationWarnings = true, includeDefaults = true, logger = console, exit = process.exit } = opts;
	function addOption(field, option, group) {
		const { type, desc = [], deprecated, alias, hidden } = option;
		const flag = group ? toKebabCase(`${group}-${field}`) : toKebabCase(field);
		const { required, defaultValue } = getRequiredOrDefault(type);
		let multiple = type.name === "array";
		const literals = /* @__PURE__ */ new Set();
		const terminalTypes = /* @__PURE__ */ new Set();
		type.toTerminals(getTerminalTypes);
		function getTerminalTypes(t) {
			switch (t.name) {
				case "undefined":
				case "optional": break;
				case "array":
					multiple = true;
					t.prefix.forEach((t) => t.toTerminals(getTerminalTypes));
					t.rest?.toTerminals(getTerminalTypes);
					t.suffix.forEach((t) => t.toTerminals(getTerminalTypes));
					break;
				case "literal":
					literals.add(String(t.value));
					terminalTypes.add(typeof t.value);
					break;
				default:
					terminalTypes.add(t.name);
					break;
			}
		}
		const env = toSnakeCase(`${envNamePrefix}${flag}`).toUpperCase();
		if (terminalTypes.size > 1) throw new TypeError(`${env} has mixed types ${[...terminalTypes]}`);
		assert(terminalTypes.size === 1, "Expected exactly one terminal type");
		const terminalType = [...terminalTypes][0];
		if (processEnv[env]) if (multiple) envArgv.push(`--${flag}`, ...processEnv[env].split(","));
		else envArgv.push(`--${flag}`, processEnv[env]);
		names.set(flag, {
			field,
			env
		});
		const spec = [(required ? "{italic required}" : defaultValue !== void 0 ? `default: ${JSON.stringify(defaultValue)}` : "optional") + "\n"];
		if (desc) spec.push(...desc);
		const typeLabel = [literals.size ? String(Array.from(literals, (l) => `{underline ${l}}`)) : multiple ? `{underline ${terminalType}[]}` : `{underline ${terminalType}}`, `  ${env} env`];
		const opt = {
			name: flag,
			alias,
			type: valueParser(env, terminalType, logger, emitDeprecationWarnings ? deprecated : void 0),
			multiple,
			group,
			description: spec.join("\n") + "\n",
			typeLabel: typeLabel.join("\n") + "\n",
			hidden: hidden === void 0 ? deprecated !== void 0 : hidden
		};
		optsWithoutDefaults.push(opt);
		optsWithDefaults.push({
			...opt,
			defaultValue
		});
	}
	const names = /* @__PURE__ */ new Map();
	const optsWithDefaults = [];
	const optsWithoutDefaults = [];
	const envArgv = [];
	try {
		for (const [name, val] of Object.entries(appOptions)) {
			const { type } = val;
			if (instanceOfAbstractType(val)) addOption(name, { type: val });
			else if (instanceOfAbstractType(type)) addOption(name, val);
			else {
				const group = name;
				for (const [name, option] of Object.entries(val)) addOption(name, instanceOfAbstractType(option) ? { type: option } : option, group);
			}
		}
		const [defaults, env1, unknown] = parseArgs(optsWithDefaults, argv, names);
		const [fromEnv, env2] = parseArgs(optsWithoutDefaults, envArgv, names);
		const [withoutDefaults, env3] = parseArgs(optsWithoutDefaults, argv, names);
		switch (unknown?.[0]) {
			case void 0: break;
			case "--help":
			case "-h":
				showUsage(optsWithDefaults, description, logger);
				exit(0);
				break;
			default:
				if (!allowUnknown) {
					logger.error?.("Invalid arguments:", unknown);
					showUsage(optsWithDefaults, description, logger);
					exit(0);
				}
				break;
		}
		const parsedArgs = includeDefaults ? defu$1(withoutDefaults, fromEnv, defaults) : defu$1(withoutDefaults, fromEnv);
		const env = includeDefaults ? {
			...env1,
			...env2,
			...env3
		} : {
			...env2,
			...env3
		};
		let schema = configSchema(appOptions, envNamePrefix);
		if (allowPartial || !includeDefaults) schema = deepPartial(schema);
		return {
			config: parse(parsedArgs, schema),
			env,
			...unknown ? { unknown } : {}
		};
	} catch (e) {
		logger.error?.(String(e));
		showUsage(optsWithDefaults, description, logger);
		throw e;
	}
}
function valueParser(optionName, typeName, logger, deprecated) {
	return (input) => {
		if (deprecated) logger.warn?.(template(`\n${optionName} is deprecated:\n` + deprecated.join("\n") + "\n"));
		switch (typeName) {
			case "string": return input;
			case "boolean": return parseBoolean(optionName, input);
			case "number": {
				const val = Number(input);
				if (Number.isNaN(val)) throw new TypeError(`Invalid input for ${optionName}: "${input}"`);
				return val;
			}
			default: throw new TypeError(`${optionName} option has unsupported type ${typeName}`);
		}
	};
}
function parseArgs(optionDefs, argv, names) {
	function normalizeFlagValue(value) {
		return value === null ? true : value;
	}
	const { _all, _none: ungrouped, _unknown: unknown, ...config } = commandLineArgs(optionDefs, {
		argv,
		partial: true
	});
	const result = {};
	const envObj = {};
	function addFlag(flagName, value, group) {
		const { field, env } = must(names.get(flagName));
		const normalized = normalizeFlagValue(value);
		if (group) result[group][field] = normalized;
		else result[field] = normalized;
		envObj[env] = String(normalized);
	}
	for (const [flagName, value] of Object.entries(ungrouped ?? {})) addFlag(flagName, value);
	for (const [name, value] of Object.entries(config)) if (typeof value !== "object" || value === null || Array.isArray(value)) addFlag(name, value);
	else {
		const group = name;
		result[group] = {};
		for (const [flagName, flagValue] of Object.entries(value)) addFlag(flagName, flagValue, group);
	}
	return [
		result,
		envObj,
		unknown
	];
}
function flagToEnv(prefix, flag) {
	return toSnakeCase(prefix + flag).toUpperCase();
}
function parseBoolean(optionName, input) {
	const bool = input.toLowerCase();
	if (["true", "1"].includes(bool)) return true;
	else if (["false", "0"].includes(bool)) return false;
	throw new TypeError(`Invalid input for ${optionName}: "${input}"`);
}
function showUsage(optionList, description = [], logger = console) {
	const hide = [];
	let leftWidth = 35;
	let rightWidth = 70;
	optionList.forEach(({ name, typeLabel, description, hidden }) => {
		if (hidden) hide.push(name);
		const lines = stripVTControlCharacters(template(`${name} ${typeLabel ?? ""}`)).split("\n");
		for (const l of lines) leftWidth = Math.max(leftWidth, l.length + 2);
		const desc = stripVTControlCharacters(template(description ?? "")).split("\n");
		for (const l of desc) rightWidth = Math.max(rightWidth, l.length + 2);
	});
	const sections = [{
		optionList,
		reverseNameOrder: true,
		hide,
		tableOptions: {
			columns: [{
				name: "option",
				width: leftWidth
			}, {
				name: "description",
				width: rightWidth
			}],
			noTrim: true
		}
	}];
	if (description) sections.unshift(...description);
	logger.info?.(commandLineUsage(sections));
}
//#endregion
export { flagToEnv, parseOptions, parseOptionsAdvanced };

//# sourceMappingURL=options.js.map