import fs from 'node:fs/promises';
import path from 'node:path';
import { MCPORTER_VERSION } from '../../runtime.js';
import { buildToolDoc } from '../list-detail-helpers.js';
import { markExecutable } from './fs-helpers.js';
import { buildEmbeddedSchemaMap } from './tools.js';
export async function writeTemplate(input) {
    const resolvedOutput = input.outputPath
        ? path.resolve(input.outputPath)
        : path.resolve(process.cwd(), `${input.serverName}.ts`);
    await fs.mkdir(path.dirname(resolvedOutput), { recursive: true });
    await fs.writeFile(resolvedOutput, renderTemplate(input), 'utf8');
    await markExecutable(resolvedOutput);
    return resolvedOutput;
}
export async function readPackageMetadata() {
    const packageJsonPath = path.resolve(process.cwd(), 'package.json');
    try {
        const buffer = await fs.readFile(packageJsonPath, 'utf8');
        const pkg = JSON.parse(buffer);
        return {
            name: pkg.name ?? 'mcporter',
            version: pkg.version ?? '0.0.0',
        };
    }
    catch (error) {
        if (error.code !== 'ENOENT') {
            throw error;
        }
        return { name: 'mcporter', version: MCPORTER_VERSION };
    }
}
export function renderTemplate({ runtimeKind, timeoutMs, definition, serverName, tools, generator, metadata, }) {
    const imports = [
        "import { Command } from 'commander';",
        "import { createRuntime, createServerProxy } from 'mcporter';",
        "import { createCallResult } from 'mcporter';",
    ].join('\n');
    const embedded = JSON.stringify(definition, (_key, value) => (value instanceof URL ? value.toString() : value), 2);
    const generatorHeader = `Generated by ${generator.name}@${generator.version} — https://github.com/steipete/mcporter`;
    const toolDocs = tools.map((tool) => ({
        tool,
        doc: buildToolDoc({
            serverName,
            toolName: tool.tool.name,
            description: tool.tool.description,
            outputSchema: tool.tool.outputSchema,
            options: tool.options,
            requiredOnly: true,
            colorize: false,
            flagExtras: [{ text: '--raw <json>' }],
        }),
    }));
    const renderedTools = toolDocs.map((entry) => ({
        ...renderToolCommand(entry.tool, timeoutMs, serverName, entry.doc),
        doc: entry.doc,
        tool: entry.tool,
    }));
    const toolHelp = renderedTools.map((entry) => ({
        name: entry.commandName,
        description: entry.tool.tool.description ?? '',
        usage: entry.doc.flagUsage ? `${entry.commandName} ${entry.doc.flagUsage}` : undefined,
        flags: entry.doc.flagUsage ?? '',
    }));
    const generatorHeaderLiteral = JSON.stringify(generatorHeader);
    const toolHelpLiteral = JSON.stringify(toolHelp, undefined, 2);
    const embeddedSchemas = JSON.stringify(buildEmbeddedSchemaMap(tools), undefined, 2);
    const embeddedMetadata = JSON.stringify(metadata, undefined, 2);
    const toolBlocks = renderedTools.map((entry) => entry.block).join('\n\n');
    const signatureMap = Object.fromEntries(renderedTools.map((entry) => [entry.commandName, entry.tsSignature]));
    const signatureMapLiteral = JSON.stringify(signatureMap, undefined, 2);
    const generatedHeaderComment = `// @generated by ${generator.name}@${generator.version} on ${metadata.generatedAt}. DO NOT EDIT.`;
    return `#!/usr/bin/env ${runtimeKind === 'bun' ? 'bun' : 'node'}
${generatedHeaderComment}
${imports}

const embeddedServer = ${embedded} as const;
const embeddedSchemas = ${embeddedSchemas} as const;
const embeddedName = ${JSON.stringify(serverName)};
const embeddedDescription = ${JSON.stringify(definition.description ?? `Standalone CLI for the ${serverName} MCP server.`)};
const generatorInfo = ${generatorHeaderLiteral};
const generatorTools = ${toolHelpLiteral} as const;
const embeddedMetadata = ${embeddedMetadata} as const;
const artifactKind = determineArtifactKind();
const program = new Command();
program.name(embeddedName);
program.description(embeddedDescription);
program.option('-t, --timeout <ms>', 'Call timeout in milliseconds', (value) => parseInt(value, 10), ${timeoutMs});
program.option('-o, --output <format>', 'Output format: text|markdown|json|raw', 'text');
const commandSignatures: Record<string, string> = ${signatureMapLiteral};
program.configureHelp({
\tcommandTerm(cmd) {
\t\tconst term = cmd.name();
\t\treturn commandSignatures[term] ?? cmd.name();
\t},
});
program.showSuggestionAfterError(true);

${toolBlocks}

program
\t.command('__mcporter_inspect', { hidden: true })
\t.description('Internal metadata printer for mcporter inspect-cli.')
\t.action(() => {
\t\tconst payload = buildMetadataPayload();
\t\tconsole.log(JSON.stringify(payload, null, 2));
\t});

configureToolCommandHelps();

const FORCE_COLOR = process.env.FORCE_COLOR?.toLowerCase();
const forceDisableColor = FORCE_COLOR === '0' || FORCE_COLOR === 'false';
const forceEnableColor = FORCE_COLOR === '1' || FORCE_COLOR === 'true' || FORCE_COLOR === '2' || FORCE_COLOR === '3';
const hasNoColor = process.env.NO_COLOR !== undefined;
const stdoutStream = process.stdout as NodeJS.WriteStream | undefined;
const supportsAnsiColor = !hasNoColor && (forceEnableColor || (!forceDisableColor && Boolean(stdoutStream?.isTTY)));

const tint = {
	bold(text: string): string {
		return supportsAnsiColor ? '\u001B[1m' + text + '\u001B[0m' : text;
	},
	dim(text: string): string {
		return supportsAnsiColor ? '\u001B[90m' + text + '\u001B[0m' : text;
	},
	extraDim(text: string): string {
		return supportsAnsiColor ? '\u001B[38;5;244m' + text + '\u001B[0m' : text;
	},
};

function configureGeneratedCommandHelp(command: Command): void {
	command.configureHelp({
		commandUsage(target) {
			const usage = (target.name() + ' ' + target.usage()).trim() || target.name();
			return supportsAnsiColor ? tint.bold(usage) : usage;
		},
		optionTerm(option) {
			const term = option.flags ?? '';
			return supportsAnsiColor ? tint.bold(term) : term;
		},
		optionDescription(option) {
			const description = option.description ?? '';
			return supportsAnsiColor ? tint.extraDim(description) : description;
		},
	});
}

function configureToolCommandHelps(): void {
	program.commands.forEach((cmd) => {
		if (cmd.name() === '__mcporter_inspect') {
			return;
		}
		configureGeneratedCommandHelp(cmd);
	});
}

function renderStandaloneHelp(): string {
	const colorfulTitle = tint.bold(embeddedName) + ' ' + tint.dim('— ' + embeddedDescription);
	const plainTitle = embeddedName + ' — ' + embeddedDescription;
	const title = supportsAnsiColor ? colorfulTitle : plainTitle;
	const lines = [title, '', 'Usage: ' + embeddedName + ' <command> [options]', ''];
	if (generatorTools) {
		lines.push(formatEmbeddedTools());
	}
	lines.push('', formatGlobalFlags(), '', formatQuickStart());
	if (generatorInfo) {
		lines.push('', tint.extraDim(generatorInfo));
	}
	return lines.join('\\n');
}

program.helpInformation = () => renderStandaloneHelp();

function formatEmbeddedTools(): string {
	const header = supportsAnsiColor ? tint.bold('Embedded tools') : 'Embedded tools';
	if (!generatorTools.length) {
		return header;
	}
	const lines = [header];
	generatorTools.forEach((entry) => {
		const renderedDesc = entry.description
			? supportsAnsiColor
				? tint.extraDim(entry.description)
				: entry.description
			: undefined;
		const base = renderedDesc ? entry.name + ' - ' + renderedDesc : entry.name;
		lines.push('  ' + base);
		if (entry.flags) {
			const renderedFlags = supportsAnsiColor ? tint.extraDim(entry.flags) : entry.flags;
			lines.push('    ' + renderedFlags);
		}
		lines.push('');
	});
	if (lines[lines.length - 1] === '') {
		lines.pop();
	}
	return lines.join('\\n');
}

function formatGlobalFlags(): string {
	const header = supportsAnsiColor ? tint.bold('Global flags') : 'Global flags';
	const entries = [
		['-t, --timeout <ms>', 'Call timeout in milliseconds'],
		['-o, --output <format>', 'text | markdown | json | raw (default text)'],
	];
	const formatted = entries.map(([flag, summary]) => '  ' + flag.padEnd(28) + summary);
	return [header, ...formatted].join('\\n');
}

function formatQuickStart(): string {
  const header = supportsAnsiColor ? tint.bold('Quick start') : 'Quick start';
  const examples = quickStartExamples();
  if (!examples.length) {
    return header;
  }
  const formatted = examples.map(([cmd, note]) => '  ' + cmd + '\\n    ' + tint.dim('# ' + note));
  return [header, ...formatted].join('\\n');
}

function quickStartExamples(): Array<[string, string]> {
  const examples: Array<[string, string]> = [];
  const commandMap = new Map<string, string>();
  program.commands.forEach((cmd) => {
    const name = cmd.name();
    if (name !== '__mcporter_inspect') {
      commandMap.set(name, name);
    }
  });
  const embedded = Array.isArray(generatorTools) ? generatorTools : [];
  for (const entry of embedded.slice(0, 3)) {
    const commandName = commandMap.get(entry.name) ?? entry.name;
    const flags = entry.flags ? ' ' + entry.flags.replace(/<[^>]+>/g, '<value>') : '';
    examples.push([embeddedName + ' ' + commandName + flags, 'invoke ' + commandName]);
  }
  if (!examples.length) {
    examples.push([embeddedName + ' <tool> --key value', 'invoke a tool with flags']);
  }
  return examples;
}

function printResult(result: unknown, format: string) {
\tconst wrapped = createCallResult(result);
\tswitch (format) {
\t\tcase 'json': {
\t\t\tconst json = wrapped.json();
\t\t\tif (json) {
\t\t\t\tconsole.log(JSON.stringify(json, null, 2));
\t\t\t\treturn;
\t\t\t}
\t\t\tbreak;
\t\t}
\t\tcase 'markdown': {
\t\t\tconst markdown = wrapped.markdown();
\t\t\tif (markdown) {
\t\t\t\tconsole.log(markdown);
\t\t\t\treturn;
\t\t\t}
\t\t\tbreak;
\t\t}
\t\tcase 'raw': {
\t\t\tconsole.log(JSON.stringify(wrapped.raw, null, 2));
\t\t\treturn;
\t\t}
\t}
\tconst text = wrapped.text();
\tif (text) {
\t\tconsole.log(text);
\t} else {
\t\tconsole.log(JSON.stringify(wrapped.raw, null, 2));
\t}
}

function normalizeEmbeddedServer(server: typeof embeddedServer) {
\tconst base = { ...server } as Record<string, unknown>;
\tif ((server.command as any).kind === 'http') {
\t\tconst urlRaw = (server.command as any).url;
\t\tconst urlValue = typeof urlRaw === 'string' ? urlRaw : String(urlRaw);
\t\treturn {
\t\t\t...base,
\t\t\tcommand: {
\t\t\t\t...(server.command as Record<string, unknown>),
\t\t\t\turl: new URL(urlValue),
\t\t\t},
\t\t};
\t}
\tif ((server.command as any).kind === 'stdio') {
\t\treturn {
\t\t\t...base,
\t\t\tcommand: {
\t\t\t\t...(server.command as Record<string, unknown>),
\t\t\t\targs: [ ...((server.command as any).args ?? []) ],
\t\t\t},
\t\t};
\t}
\treturn base;
}

function determineArtifactKind(): 'template' | 'bundle' | 'binary' {
\tconst scriptPath = typeof process !== 'undefined' && Array.isArray(process.argv) ? process.argv[1] ?? '' : '';
\tif (scriptPath.endsWith('.ts')) {
\t\treturn 'template';
\t}
\tif (scriptPath.endsWith('.js')) {
\t\treturn 'bundle';
\t}
\treturn 'binary';
}

function resolveArtifactPath(): string {
\tif (typeof process !== 'undefined' && Array.isArray(process.argv) && process.argv.length > 1) {
\t\tconst script = process.argv[1];
\t\tif (script) {
\t\t\treturn script;
\t\t}
\t}
\treturn embeddedMetadata.artifact.path;
}

function buildMetadataPayload() {
\tconst invocation = { ...embeddedMetadata.invocation };
\tconst path = resolveArtifactPath();
\tif (artifactKind === 'template' && path) {
\t\tinvocation.outputPath = invocation.outputPath ?? path;
\t} else if (artifactKind === 'bundle' && path) {
\t\tinvocation.bundle = invocation.bundle ?? path;
\t} else if (artifactKind === 'binary' && path) {
\t\tinvocation.compile = invocation.compile ?? path;
\t}
\treturn {
\t\t...embeddedMetadata,
\t\tartifact: {
\t\t\tpath,
\t\t\tkind: artifactKind,
\t\t},
\t\tinvocation,
\t};
}

async function ensureRuntime(): Promise<Awaited<ReturnType<typeof createRuntime>>> {
	return await createRuntime({
		servers: [normalizeEmbeddedServer(embeddedServer)],
	});
}

async function invokeWithTimeout<T>(call: Promise<T>, timeout: number): Promise<T> {
\tif (!Number.isFinite(timeout) || timeout <= 0) {
\t\treturn await call;
\t}
\tlet timer: ReturnType<typeof setTimeout> | undefined;
\ttry {
\t\treturn await Promise.race([
\t\t\tcall,
\t\t\tnew Promise<never>((_, reject) => {
\t\t\t\ttimer = setTimeout(() => {
\t\t\t\t\treject(new Error('Call timed out after ' + timeout + 'ms.'));
\t\t\t\t}, timeout);
\t\t\t}),
\t\t]);
\t} finally {
\t\tif (timer) {
\t\t\tclearTimeout(timer);
\t\t}
\t}
}

async function runCli(): Promise<void> {
\tconst args = process.argv.slice(2);
\tif (args.length === 0) {
\t\tprogram.outputHelp();
\t\treturn;
\t}
\tawait program.parseAsync(process.argv);
}

if (process.env.MCPORTER_DISABLE_AUTORUN !== '1') {
\trunCli().catch((error) => {
\t\tconst message = error instanceof Error ? error.message : String(error);
\t\tconsole.error(message);
\t\tprocess.exit(1);
\t});
}
`;
}
export function renderToolCommand(tool, defaultTimeout, serverName, existingDoc) {
    const commandName = tool.tool.name.replace(/[^a-zA-Z0-9-]/g, '-');
    const description = tool.tool.description ?? `Invoke the ${tool.tool.name} tool.`;
    const doc = existingDoc ??
        buildToolDoc({
            serverName,
            toolName: tool.tool.name,
            description: tool.tool.description,
            outputSchema: tool.tool.outputSchema,
            options: tool.options,
            requiredOnly: true,
            colorize: false,
            flagExtras: [{ text: '--raw <json>' }],
        });
    const buildArgs = tool.options
        .map((option) => {
        const source = `cmdOpts.${option.property}`;
        return `if (${source} !== undefined) args.${option.property} = ${source};`;
    })
        .join('\n\t\t');
    const flagUsage = doc.flagUsage;
    const optionLines = doc.optionDocs.map((entry) => renderOption(entry)).join('\n');
    const summary = flagUsage ? `${commandName} ${flagUsage}` : commandName;
    const signature = summary;
    const usageSnippet = flagUsage ? `.usage(${JSON.stringify(flagUsage)})\n` : '';
    const tsSignature = doc.tsSignature;
    const exampleText = doc.examples[0];
    const exampleSnippet = exampleText
        ? `\n\t.addHelpText('after', () => '\\nExample:\\n  ' + ${JSON.stringify(exampleText)})`
        : '';
    const optionalSnippet = doc.optionalSummary
        ? `\n\t.addHelpText('afterAll', () => '\\n' + ${JSON.stringify(doc.optionalSummary)} + '\\n')`
        : '';
    const aliasSnippet = tool.tool.name !== commandName ? `\n\t.alias(${JSON.stringify(tool.tool.name)})` : '';
    const block = `program
\t.command(${JSON.stringify(commandName)})
\t.summary(${JSON.stringify(summary)})
\t.description(${JSON.stringify(description)})
${usageSnippet ? `\t${usageSnippet}` : ''}\t.option('--raw <json>', 'Provide raw JSON arguments to the tool, bypassing flag parsing.')
${optionLines ? `\n${optionLines}` : ''}
${aliasSnippet ? `\t${aliasSnippet}` : ''}\t.action(async (cmdOpts) => {
\t\tconst globalOptions = program.opts();
\t\tconst runtime = await ensureRuntime();
\t\tconst serverName = embeddedName;
\t\tconst proxy = createServerProxy(runtime, serverName, {
\t\t\tinitialSchemas: embeddedSchemas,
\t\t});
\t\ttry {
\t\t\tconst args = cmdOpts.raw ? JSON.parse(cmdOpts.raw) : ({} as Record<string, unknown>);
\t\t\t${buildArgs}
\t\t\tconst call = (proxy.${tool.methodName} as any)(args);
\t\t\tconst result = await invokeWithTimeout(call, globalOptions.timeout || ${defaultTimeout});
\t\t\tprintResult(result, globalOptions.output ?? 'text');
\t\t} finally {
\t\t\tawait runtime.close(serverName).catch(() => {});
\t\t}
\t})${exampleSnippet}${optionalSnippet};`;
    return { block, commandName, signature, tsSignature };
}
function renderOption(optionDoc) {
    const parser = optionParser(optionDoc.option);
    const method = optionDoc.option.required ? '.requiredOption' : '.option';
    return `\t${method}(${JSON.stringify(optionDoc.flagLabel)}, ${JSON.stringify(optionDoc.description)}${parser ? `, ${parser}` : ''})`;
}
function optionParser(option) {
    switch (option.type) {
        case 'number':
            return '(value) => parseFloat(value)';
        case 'boolean':
            return "(value) => value !== 'false'";
        case 'array':
            return "(value) => value.split(',')";
        default:
            return undefined;
    }
}
//# sourceMappingURL=template.js.map