import { join } from "node:path";
import * as fs from "node:fs";
import { globSync } from "tinyglobby";
import { minimatch } from "minimatch";
import { load } from "js-yaml";
import { isUndefined } from "lodash-es";
import { type PackageJson } from "pkg-types";
import { type PnpmWorkspace } from "@ruan-cat/utils";

// 注意 整个 commitlint-config 包都是使用 cjs 的语法，所以需要使用 node-cjs 的语法
import { printList, isMonorepoProject } from "@ruan-cat/utils/node-cjs";

import { createPackagescopes } from "./utils.ts";
import { commonScopes } from "./common-scopes.ts";

import { execSync } from "node:child_process";
import consola from "consola";

/**
 * 解析 git status --porcelain 输出，提取暂存区文件路径
 * @description
 * 1. 按行分割输出
 * 2. 过滤空行
 * 3. 只保留暂存区文件（第一个字符不是空格且不是?）
 * 4. 提取文件路径（从第3个字符开始）
 * @param gitStatusOutput - git status --porcelain 命令的输出
 * @returns 暂存区文件路径数组
 */
export function parseGitStatusOutput(gitStatusOutput: string): string[] {
	return gitStatusOutput
		.split("\n")
		.filter((line) => line.length > 0)
		.filter((line) => {
			// git status --porcelain 格式：XY filename
			// X: 索引状态（暂存区），Y: 工作目录状态
			// 只处理暂存区的文件（第一个字符不是空格且不是?）
			const indexStatus = line[0];
			return indexStatus !== " " && indexStatus !== "?";
		})
		.map((line) => {
			// git status --porcelain 格式：XY filename
			// 从第3个字符开始是文件名，但需要去掉可能的前导空格
			let filePath = line.substring(2).trim();
			return filePath;
		})
		.filter((filePath) => filePath.length > 0);
}

/**
 * 获取包路径到范围值的映射关系
 */
function getPackagePathToScopeMapping(): Map<string, string> {
	const mapping = new Map<string, string>();

	// 判断是否是 monorepo 项目
	if (!isMonorepoProject()) {
		// 如果不是 monorepo，不添加默认映射，依赖 glob 匹配来确定范围
		return mapping;
	}

	// 读取 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;
	});

	// 根据每个模式匹配相应的目录
	filteredPkgPatterns.forEach((pkgPattern) => {
		const globPattern = `${pkgPattern}/package.json`;
		const matchedPaths = globSync(globPattern, {
			cwd: process.cwd(),
			ignore: ["**/node_modules/**"],
		});

		matchedPaths.forEach((relativePkgPath) => {
			const fullPkgJsonPath = join(process.cwd(), relativePkgPath);
			if (fs.existsSync(fullPkgJsonPath)) {
				// 防御性检查1: 验证文件路径确实以 package.json 结尾
				if (!relativePkgPath.endsWith("package.json")) {
					consola.warn(`跳过非 package.json 文件: ${relativePkgPath}`);
					return;
				}

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

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

					const pkgJson = <PackageJson>JSON.parse(fileContent);
					const scope = createPackagescopes(pkgJson);

					// 获取包的目录路径（移除 package.json）
					const packageRelativePath = relativePkgPath.replace(/[/\\]package\.json$/, "").replace(/\\/g, "/");

					mapping.set(packageRelativePath, scope);
				} catch (error) {
					// 防御性检查3: 捕获 JSON.parse 错误
					consola.error(`解析 package.json 失败: ${relativePkgPath}`, error);
				}
			}
		});
	});

	// 不再默认添加根目录映射，root 范围应该通过 glob 匹配来确定

	return mapping;
}

/**
 * 根据 git 状态，获取默认的提交范围
 * @description
 * 1. 从 getPackagesNameAndDescription 获取所有包信息
 * 2. 从 git status --porcelain 获取修改的文件路径
 * 3. 匹配被修改的包范围，返回这些范围
 * @see https://cz-git.qbb.sh/zh/recipes/default-scope
 * @returns 返回被修改的包范围数组，如果只有一个则返回字符串
 */
export function getDefaultScope(): string | string[] | undefined {
	try {
		// 1. 获取包路径到范围的映射
		const pathToScopeMapping = getPackagePathToScopeMapping();
		// consola.warn("pathToScopeMapping", pathToScopeMapping);

		// 2. 获取 git 修改的文件列表
		const gitStatusOutput = execSync("git status --porcelain || true").toString();
		// printList({
		// 	title: (files) => `输出 git status --porcelain || true 命令的输出:`,
		// 	stringList: gitStatusOutput.split("\n"),
		// });

		if (!gitStatusOutput) {
			consola.info("没有检测到文件修改");
			return undefined;
		}

		// 3. 解析修改的文件路径
		const modifiedFiles = parseGitStatusOutput(gitStatusOutput);
		// 输出修改的文件列表
		printList({
			title: (files) => `输出 ${files.length} 个暂存区文件路径:`,
			stringList: modifiedFiles,
		});

		// 4. 匹配文件路径到包范围
		const affectedScopes = new Set<string>();

		modifiedFiles.forEach((filePath) => {
			let matchedScope: string | undefined = undefined; // 不设置默认值，只有真正匹配时才设置
			let maxMatchLength = 0;

			// 找到最长匹配的包路径
			for (const [packagePath, scope] of pathToScopeMapping.entries()) {
				if (packagePath === "") {
					// 空路径代表根目录，优先级最低
					continue;
				}

				// 检查文件是否在这个包目录下
				const normalizedPackagePath = packagePath.replace(/\\/g, "/");
				const normalizedFilePath = filePath.replace(/\\/g, "/");

				if (
					normalizedFilePath.startsWith(normalizedPackagePath + "/") ||
					normalizedFilePath === normalizedPackagePath
				) {
					// 选择最长匹配的路径（最具体的包）
					if (packagePath.length > maxMatchLength) {
						maxMatchLength = packagePath.length;
						matchedScope = scope;
					}
				}
			}

			// 只有当真正匹配到包路径时才添加范围
			if (matchedScope !== undefined) {
				affectedScopes.add(matchedScope);
			}

			// 新增：基于 commonScopes 的 glob 匹配
			const normalizedFilePath = filePath.replace(/\\/g, "/");
			commonScopes.forEach((scopeItem) => {
				// 检查是否存在 glob 字段
				if (scopeItem.glob && scopeItem.glob.length > 0) {
					// 遍历每个 glob 模式
					scopeItem.glob.forEach((globPattern) => {
						// 使用 minimatch 进行 glob 匹配
						if (minimatch(normalizedFilePath, globPattern)) {
							// 匹配成功，添加该范围的 value 到集合中
							affectedScopes.add(scopeItem.value);
						}
					});
				}
			});
		});

		const scopesArray = Array.from(affectedScopes);

		// 5. 返回结果
		if (scopesArray.length === 0) {
			consola.info("本次修改没有影响任何包范围");
			return undefined;
		} else if (scopesArray.length === 1) {
			return scopesArray[0];
		} else {
			// 输出影响的包范围
			printList({
				title: "影响的包范围:",
				stringList: scopesArray,
			});
			return scopesArray;
		}
	} catch (error) {
		consola.error("获取默认范围时出错:", error);
		return undefined;
	}
}
