import { commitTypes } from "./commit-types.ts";
import { join } from "node:path";
import * as fs from "node:fs";
import { globSync } from "tinyglobby";
import { load } from "js-yaml";
import { isUndefined } from "lodash";
import { pathChange, isMonorepoProject } from "@ruan-cat/utils/node-cjs";
import { type PackageJson } from "pkg-types";
import { type PnpmWorkspace } from "@ruan-cat/utils";
import { type ScopesType } from "cz-git";
import consola from "consola";

export type ScopesTypeItem = Exclude<ScopesType[number], string>;

export const defScopes: ScopesTypeItem[] = [
	{
		name: "root | 根目录",
		value: "root",
	},
	{
		name: "utils | 工具包",
		value: "utils",
	},
	{
		name: "demo | 测试项目",
		value: "demo",
	},
];

/**
 * 创建标签名称
 */
function createLabelName(packageJson: PackageJson) {
	const { name, description } = packageJson;
	const noneDesc = `该依赖包没有描述。`;
	const desc = isUndefined(description) ? noneDesc : description;
	return `${name ?? "bug：极端情况，这个包没有配置name名称"}    >>|>>    ${desc}`;
}

/**
 * 创建包范围取值
 */
export function createPackagescopes(packageJson: PackageJson) {
	const { name } = packageJson;
	const names = name?.split("/");
	/**
	 * 含有业务名称的包名
	 * @description
	 * 如果拆分的数组长度大于1 说明包是具有前缀的。取用后面的名称。
	 *
	 * 否则包名就是单纯的字符串，直接取用即可。
	 */
	// @ts-ignore 默认 name 名称总是存在的 不做undefined校验
	const packageNameWithBusiness = names?.length > 1 ? names?.[1] : names?.[0];
	return `${packageNameWithBusiness}`;
}

/**
 * 根据 pnpm-workspace.yaml 配置的monorepo有意义的包名，获取包名和包描述
 * @description
 * 根据 pnpm-workspace.yaml 配置的monorepo有意义的包名，获取包名和包描述
 */
export function getPackagesNameAndDescription() {
	// 判断是否是 monorepo 项目
	if (!isMonorepoProject()) {
		return defScopes;
	}

	// 读取 pnpm-workspace.yaml 文件
	const workspaceConfigPath = join(process.cwd(), "pnpm-workspace.yaml");
	const workspaceFile = fs.readFileSync(workspaceConfigPath, "utf8");
	const workspaceConfig = <PnpmWorkspace>load(workspaceFile);

	/**
	 * packages配置 包的匹配语法
	 * @description
	 * 此时已经通过 isMonorepoProject() 验证，packages 一定存在且有效
	 */
	const pkgPatterns = workspaceConfig.packages!;

	/**
	 * 过滤后的包匹配模式
	 * @description
	 * 过滤掉 negation patterns（以 ! 开头）和空字符串
	 * negation patterns 由 pnpm 自身处理，不应传递给 glob 工具
	 */
	const filteredPkgPatterns = pkgPatterns.filter((pattern) => {
		if (pattern.startsWith("!")) return false; // 排除 negation patterns
		if (pattern.trim() === "") return false; // 排除空字符串
		return true;
	});

	/**
	 * 全部的 package.json 文件路径
	 */
	let pkgPaths: string[] = [];

	// 根据每个模式匹配相应的目录
	filteredPkgPatterns.map((pkgPattern) => {
		// 在进程运行的根目录下，执行匹配。 一般来说是项目的根目录
		const matchedPath = pathChange(join(process.cwd(), pkgPattern, "package.json"));

		const matchedPaths = globSync(matchedPath, {
			ignore: ["**/node_modules/**"],
		});

		// 找到包路径，就按照顺序逐个填充准备
		pkgPaths = pkgPaths.concat(...matchedPaths);
		return matchedPaths;
	});

	// console.log("pkgPaths :>> ", pkgPaths);

	const czGitScopesType = pkgPaths
		.map(function (pkgJsonPath) {
			// 如果确实存在该文件，就处理。否则不管了。
			if (fs.existsSync(pkgJsonPath)) {
				// 防御性检查1: 验证文件路径确实以 package.json 结尾
				if (!pkgJsonPath.endsWith("package.json")) {
					consola.warn(`跳过非 package.json 文件: ${pkgJsonPath}`);
					return null;
				}

				try {
					// 读取文件内容
					const fileContent = fs.readFileSync(pkgJsonPath, "utf-8");

					// 防御性检查2: 验证文件内容以 { 开头，确认为有效 JSON
					const trimmedContent = fileContent.trim();
					if (!trimmedContent.startsWith("{")) {
						consola.warn(`跳过无效的 JSON 文件（内容不以 { 开头）: ${pkgJsonPath}`);
						return null;
					}

					/**
					 * 包配置文件数据
					 */
					const pkgJson = <PackageJson>JSON.parse(fileContent);
					return <ScopesTypeItem>{
						// 标签名称 对外展示的标签名称
						name: createLabelName(pkgJson),
						// 取值
						value: createPackagescopes(pkgJson),
					};
				} catch (error) {
					// 防御性检查3: 捕获 JSON.parse 错误
					consola.error(`解析 package.json 失败: ${pkgJsonPath}`, error);
					return null;
				}
			}

			consola.warn(`文件不存在: ${pkgJsonPath}`);
			return null;
		})
		.filter((item): item is ScopesTypeItem => item !== null);

	// console.log("czGitScopesType :>> ", czGitScopesType);

	return czGitScopesType;
}

/**
 * 计算字符串的显示宽度（考虑中文、emoji 等宽字符）
 * @description
 * 使用更精确的方式计算字符显示宽度：
 * - Emoji 和特殊符号通常占 2 个显示宽度
 * - 中文字符占 2 个显示宽度
 * - ASCII 字符占 1 个显示宽度
 * @param str 要计算的字符串
 * @returns 字符串的显示宽度
 */
function getDisplayWidth(str: string): number {
	let width = 0;
	// 使用正则表达式匹配不同类型的字符
	const chars = Array.from(str);

	for (const char of chars) {
		const code = char.codePointAt(0) || 0;

		// Emoji 和特殊符号（占 2 宽度）
		if (
			(code >= 0x1f300 && code <= 0x1f9ff) || // Emoji 主要范围
			(code >= 0x2600 && code <= 0x26ff) || // 杂项符号
			(code >= 0x2700 && code <= 0x27bf) || // 装饰符号
			(code >= 0xfe00 && code <= 0xfe0f) || // 变体选择器
			(code >= 0x1f000 && code <= 0x1f02f) || // 麻将牌
			(code >= 0x1f0a0 && code <= 0x1f0ff) || // 扑克牌
			(code >= 0x1f100 && code <= 0x1f64f) || // 符号和图形
			(code >= 0x1f680 && code <= 0x1f6ff) || // 交通和地图
			(code >= 0x1f900 && code <= 0x1f9ff) || // 补充符号和图形
			// 中文字符（占 2 宽度）
			(code >= 0x4e00 && code <= 0x9fff) || // CJK 统一汉字
			(code >= 0x3000 && code <= 0x303f) || // CJK 符号和标点
			(code >= 0xff00 && code <= 0xffef) // 全角字符
		) {
			width += 2;
		} else {
			// ASCII 和其他字符（占 1 宽度）
			width += 1;
		}
	}

	return width;
}

/**
 * 将 commitTypes 转换为 cz-git 格式
 * @description
 * 将内部定义的 CommitType 数组转换为 cz-git 库所需的格式，
 *
 * 实现四层对齐机制，确保提交类型选择界面的美观性：
 * 1. emoji 图标靠左对齐（最前面，固定占3个字符宽度）
 * 2. type 提交类型字段对齐
 * 3. description 描述字段对齐
 * 4. longDescription 长描述字段的竖线 `|` 对齐
 * @returns 包含 value 和 name 字段的对象数组，用于 cz-git 配置
 */
export function convertCommitTypesToCzGitFormat() {
	// ===== 第一步：计算各字段的最大宽度 =====

	// 为 emoji 设置固定宽度（大多数 emoji 显示为2个字符宽度，加1个空格=3）
	const EMOJI_FIXED_WIDTH = 3;

	// 计算每个 type 的长度
	const maxTypeLength = Math.max(...commitTypes.map((commitType) => commitType.type.length));

	// 计算每个 description 的显示宽度
	const maxDescriptionWidth = Math.max(...commitTypes.map((commitType) => getDisplayWidth(commitType.description)));

	// ===== 第二步：为每个提交类型生成对齐后的格式 =====

	return commitTypes.map((commitType) => {
		// 【第1层对齐】emoji 固定占 3 个字符位置
		const emojiPart = commitType.emoji + " ".repeat(Math.max(0, EMOJI_FIXED_WIDTH - getDisplayWidth(commitType.emoji)));

		// 【第2层对齐】type 后面添加空格使冒号对齐
		const typeSpaces = " ".repeat(maxTypeLength - commitType.type.length);

		// 【第3层对齐】description 后面添加空格使竖线对齐
		const descWidth = getDisplayWidth(commitType.description);
		const descSpaces = " ".repeat(maxDescriptionWidth - descWidth);

		// ===== 第三步：构建最终的对齐文本 =====
		// 格式说明：
		// emoji<固定3字符> type:<空格对齐> description<空格对齐> | longDescription
		//   ↑                ↑    ↑            ↑            ↑
		// 靠左固定宽       第2层  冒号后      第3层        第4层

		const name = commitType.longDescription
			? `${emojiPart}${commitType.type}:${typeSpaces} ${commitType.description}${descSpaces} | ${commitType.longDescription}`
			: `${emojiPart}${commitType.type}:${typeSpaces} ${commitType.description}`;

		return {
			value: `${commitType.emoji} ${commitType.type}`,
			name,
		};
	});
}

/**
 * 获取所有提交类型
 * @description
 * 获取所有提交类型，用于 commitlint 的 `type-enum` 规则
 * @returns 返回提交类型数组
 */
export function getTypes() {
	return commitTypes.map((commitType) => commitType.type);
}

/**
 * 获取所有提交范围
 * @description
 * 获取所有提交范围，用于 commitlint 的 `scope-enum` 规则
 * @returns 返回提交范围数组
 */
export function getScopes() {
	return getPackagesNameAndDescription().map(
		(scope) =>
			// 优先从 value 取值，如果 value 不存在，则从 name 取值。 value 才是包名，才是 scope 提交范围。
			scope.value ?? scope.name,
	);
}
