/**
 * create-emdash
 *
 * Interactive CLI for creating new EmDash projects
 *
 * Usage: npm create emdash@latest
 */

import { exec } from "node:child_process";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { promisify } from "node:util";

const execAsync = promisify(exec);

import * as p from "@clack/prompts";
import { downloadTemplate } from "giget";
import pc from "picocolors";

const PROJECT_NAME_PATTERN = /^[a-z0-9-]+$/;

const GITHUB_REPO = "emdash-cms/templates";

type PackageManager = "pnpm" | "npm" | "yarn" | "bun";

/** Detect which package manager invoked us, or fall back to npm */
function detectPackageManager(): PackageManager {
	const agent = process.env.npm_config_user_agent ?? "";
	if (agent.startsWith("pnpm")) return "pnpm";
	if (agent.startsWith("yarn")) return "yarn";
	if (agent.startsWith("bun")) return "bun";
	return "npm";
}

type Platform = "node" | "cloudflare";

interface TemplateConfig {
	name: string;
	description: string;
	/** Directory name in the templates repo */
	dir: string;
}

const NODE_TEMPLATES = {
	blog: {
		name: "Blog",
		description: "A blog with posts, pages, and authors",
		dir: "blog",
	},
	starter: {
		name: "Starter",
		description: "A general-purpose starter with posts and pages",
		dir: "starter",
	},
	marketing: {
		name: "Marketing",
		description: "A marketing site with landing pages and CTAs",
		dir: "marketing",
	},
	portfolio: {
		name: "Portfolio",
		description: "A portfolio site with projects and case studies",
		dir: "portfolio",
	},
	blank: {
		name: "Blank",
		description: "A minimal starter with no content or styling",
		dir: "blank",
	},
} as const satisfies Record<string, TemplateConfig>;

const CLOUDFLARE_TEMPLATES = {
	blog: {
		name: "Blog",
		description: "A blog with posts, pages, and authors",
		dir: "blog-cloudflare",
	},
	starter: {
		name: "Starter",
		description: "A general-purpose starter with posts and pages",
		dir: "starter-cloudflare",
	},
	marketing: {
		name: "Marketing",
		description: "A marketing site with landing pages and CTAs",
		dir: "marketing-cloudflare",
	},
	portfolio: {
		name: "Portfolio",
		description: "A portfolio site with projects and case studies",
		dir: "portfolio-cloudflare",
	},
} as const satisfies Record<string, TemplateConfig>;

type NodeTemplate = keyof typeof NODE_TEMPLATES;
type CloudflareTemplate = keyof typeof CLOUDFLARE_TEMPLATES;

/** Build select options from a config object, preserving literal key types */
function selectOptions<K extends string>(
	obj: Readonly<Record<K, Readonly<{ name: string; description: string }>>>,
): { value: K; label: string; hint: string }[] {
	const keys: K[] = Object.keys(obj).filter((k): k is K => k in obj);
	return keys.map((key) => ({
		value: key,
		label: obj[key].name,
		hint: obj[key].description,
	}));
}

async function selectTemplate(platform: Platform): Promise<TemplateConfig> {
	if (platform === "node") {
		const key = await p.select<NodeTemplate>({
			message: "Which template?",
			options: selectOptions(NODE_TEMPLATES),
			initialValue: "blog",
		});
		if (p.isCancel(key)) {
			p.cancel("Operation cancelled.");
			process.exit(0);
		}
		return NODE_TEMPLATES[key];
	}
	const key = await p.select<CloudflareTemplate>({
		message: "Which template?",
		options: selectOptions(CLOUDFLARE_TEMPLATES),
		initialValue: "blog",
	});
	if (p.isCancel(key)) {
		p.cancel("Operation cancelled.");
		process.exit(0);
	}
	return CLOUDFLARE_TEMPLATES[key];
}

async function main() {
	console.clear();

	console.log(`\n  ${pc.bold(pc.cyan("— E M D A S H —"))}\n`);
	p.intro("Create a new EmDash project");

	const projectName = await p.text({
		message: "Project name?",
		placeholder: "my-site",
		defaultValue: "my-site",
		validate: (value) => {
			if (!value) return "Project name is required";
			if (!PROJECT_NAME_PATTERN.test(value))
				return "Project name can only contain lowercase letters, numbers, and hyphens";
			return undefined;
		},
	});

	if (p.isCancel(projectName)) {
		p.cancel("Operation cancelled.");
		process.exit(0);
	}

	const projectDir = resolve(process.cwd(), projectName);

	if (existsSync(projectDir)) {
		const overwrite = await p.confirm({
			message: `Directory ${projectName} already exists. Overwrite?`,
			initialValue: false,
		});

		if (p.isCancel(overwrite) || !overwrite) {
			p.cancel("Operation cancelled.");
			process.exit(0);
		}
	}

	// Step 1: pick platform
	const platform = await p.select<Platform>({
		message: "Where will you deploy?",
		options: [
			{
				value: "cloudflare",
				label: "Cloudflare Workers",
				hint: "D1 + R2",
			},
			{
				value: "node",
				label: "Node.js",
				hint: "SQLite + local file storage",
			},
		],
		initialValue: "cloudflare",
	});

	if (p.isCancel(platform)) {
		p.cancel("Operation cancelled.");
		process.exit(0);
	}

	// Step 2: pick template
	const templateConfig = await selectTemplate(platform);

	// Step 3: pick package manager
	const detectedPm = detectPackageManager();
	const pm = await p.select<PackageManager>({
		message: "Which package manager?",
		options: [
			{ value: "pnpm", label: "pnpm" },
			{ value: "npm", label: "npm" },
			{ value: "yarn", label: "yarn" },
			{ value: "bun", label: "bun" },
		],
		initialValue: detectedPm,
	});

	if (p.isCancel(pm)) {
		p.cancel("Operation cancelled.");
		process.exit(0);
	}

	// Step 4: install dependencies?
	const shouldInstall = await p.confirm({
		message: "Install dependencies?",
		initialValue: true,
	});

	if (p.isCancel(shouldInstall)) {
		p.cancel("Operation cancelled.");
		process.exit(0);
	}

	const installCmd = `${pm} install`;
	const runCmd = (script: string) => (pm === "npm" ? `npm run ${script}` : `${pm} ${script}`);

	const s = p.spinner();
	s.start("Creating project...");

	try {
		await downloadTemplate(`github:${GITHUB_REPO}/${templateConfig.dir}`, {
			dir: projectDir,
			force: true,
		});

		// Set project name in package.json
		const pkgPath = resolve(projectDir, "package.json");
		if (existsSync(pkgPath)) {
			const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
			pkg.name = projectName;

			// Add emdash config if template has seed data
			const seedPath = resolve(projectDir, "seed", "seed.json");
			if (existsSync(seedPath)) {
				pkg.emdash = {
					label: templateConfig.name,
					seed: "seed/seed.json",
				};
			}

			writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
		}

		s.stop("Project created!");

		if (shouldInstall) {
			s.start(`Installing dependencies with ${pc.cyan(pm)}...`);
			try {
				await execAsync(installCmd, { cwd: projectDir });
				s.stop("Dependencies installed!");
			} catch {
				s.stop("Failed to install dependencies");
				p.log.warn(`Run ${pc.cyan(`cd ${projectName} && ${installCmd}`)} manually`);
			}
		}

		const steps = [`cd ${projectName}`];
		if (!shouldInstall) steps.push(installCmd);
		steps.push(runCmd("dev"));

		p.note(steps.join("\n"), "Next steps");

		p.outro(`${pc.green("Done!")} Your EmDash project is ready at ${pc.cyan(projectName)}`);
	} catch (error) {
		s.stop("Failed to create project");
		p.log.error(error instanceof Error ? error.message : String(error));
		process.exit(1);
	}
}

main().catch((error) => {
	console.error(error);
	process.exit(1);
});
