import fss from "node:fs";
import path from "node:path";
import { debugLog, userError } from "../../engine/core/logger.js";
import { isSoloforgeStateBypassCommand } from "../../engine/contracts/control_plane_contract.js";
import { isSameOrDescendantPath } from "../../engine/core/path_scope_utils.js";
import { resolveProjectPath, denyPreToolUse, evaluateImplementationWriteGate, evaluateArtifactStructure } from "./check_write.js";
import { detectLazyPatterns } from "../../engine/contracts/lazy_pattern_detector.js";
import { errorMessage, safeJsonParse } from "../../engine/core/helpers.js";
import { resolveProjectConfig } from "../../knowledge/loader.js";
const args = process.argv.slice(3);
export async function cmdPostBash() {
    debugLog("CLI: 执行 cmdPostBash");
    // [ENG-11] TODO: Bash PostToolUse hook 需要文件变更检测基础设施
    // 当前为空操作占位符 — 未来实现: 跟踪命令结果，检测构建/测试失败
    process.exit(0);
}
export function extractBashCommand(rawInput) {
    const trimmed = rawInput.trim();
    if (!trimmed)
        return "";
    try {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- MCP SDK 桥接/动态 JSON 解析，字段由运行时决定
        const parsed = safeJsonParse(trimmed);
        const input = parsed?.tool_input || parsed?.toolInput || parsed?.input || parsed;
        return String(input?.command || input?.cmd || parsed?.command || "");
    }
    catch {
        return trimmed;
    }
}
export function unquoteShellToken(token) {
    if (!token)
        return undefined;
    const trimmed = token.trim();
    if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
        return trimmed.slice(1, -1);
    }
    return trimmed;
}
export function addShellPathToken(targets, token) {
    const value = unquoteShellToken(token);
    if (!value || value === "-" || value.startsWith("$"))
        return;
    targets.add(value.replace(/[),;]+$/, ""));
}
export function addSourcePathTokens(targets, commandText) {
    const pathTokenPattern = /"([^"]+\.(?:java|kt|kts|scala|ts|tsx|js|jsx|mjs|cjs|py|go|rs|php|rb|cs|swift|vue|svelte|sql|xml|ya?ml|properties))"|'([^']+\.(?:java|kt|kts|scala|ts|tsx|js|jsx|mjs|cjs|py|go|rs|php|rb|cs|swift|vue|svelte|sql|xml|ya?ml|properties))'|([^\s'";&|()<>]+\.(?:java|kt|kts|scala|ts|tsx|js|jsx|mjs|cjs|py|go|rs|php|rb|cs|swift|vue|svelte|sql|xml|ya?ml|properties))/gi;
    for (const match of commandText.matchAll(pathTokenPattern)) {
        addShellPathToken(targets, match[1] || match[2] || match[3]);
    }
}
export function extractBashWriteTargets(commandText) {
    const command = commandText.replace(/\s+/g, " ").trim();
    const targets = new Set();
    const redirectionPattern = /(?:^|[^<])>>?\s*(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g;
    for (const match of command.matchAll(redirectionPattern)) {
        addShellPathToken(targets, match[1] || match[2] || match[3]);
    }
    const teePattern = /(?:^|[\s;&|()])tee\s+(?:-[A-Za-z]+\s+)*(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g;
    for (const match of command.matchAll(teePattern)) {
        addShellPathToken(targets, match[1] || match[2] || match[3]);
    }
    const writeApiPattern = /(?:writeFileSync|writeFile|write_text)\s*\(\s*["']([^"']+)["']/g;
    for (const match of command.matchAll(writeApiPattern)) {
        addShellPathToken(targets, match[1]);
    }
    // [ENG-12] 补充 fs.promises.writeFile 写入检测
    const fsPromisesWritePattern = /fs\.promises\.writeFile\s*\(\s*["']([^"']+)["']/g;
    for (const match of command.matchAll(fsPromisesWritePattern)) {
        addShellPathToken(targets, match[1]);
    }
    const pythonOpenWritePattern = /open\s*\(\s*["']([^"']+)["']\s*,\s*["'][^"']*[wa+][^"']*["']/g;
    for (const match of command.matchAll(pythonOpenWritePattern)) {
        addShellPathToken(targets, match[1]);
    }
    const pathlibWritePattern = /(?:Path|pathlib\.Path)\s*\(\s*["']([^"']+)["']\s*\)\.write_text/g;
    for (const match of command.matchAll(pathlibWritePattern)) {
        addShellPathToken(targets, match[1]);
    }
    // [ENG-12] 补充 heredoc 写入检测: cat <<EOF > file / cat <<EOF >> file
    const heredocPattern = /cat\s*<<.*?\s*>>?\s*(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/gi;
    for (const match of command.matchAll(heredocPattern)) {
        addShellPathToken(targets, match[1] || match[2] || match[3]);
    }
    // [ENG-12] 补充 curl -o / wget -O 下载写入检测
    const downloadPattern = /(?:curl|wget)\s+(?:[^;&|]*?\s)?-(?:o|O)\s+(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g;
    for (const match of command.matchAll(downloadPattern)) {
        addShellPathToken(targets, match[1] || match[2] || match[3]);
    }
    // [ENG-12] 补充 dd of= 写入检测
    const ddPattern = /dd\s+[^;&|]*?of=(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g;
    for (const match of command.matchAll(ddPattern)) {
        addShellPathToken(targets, match[1] || match[2] || match[3]);
    }
    const mutatingCommandPattern = /(?:^|[\s;&|()])(?:rm|mv|cp|touch|chmod|chown)\b/i;
    const inPlaceEditPattern = /(?:^|[\s;&|()])(?:(?:g?sed)\s+[^;&|]*\s-i\b|perl\s+[^;&|]*\s-pi\b)/i;
    if (mutatingCommandPattern.test(command) || inPlaceEditPattern.test(command)) {
        addSourcePathTokens(targets, command);
    }
    return [...targets];
}
/**
 * 从 Bash 命令文本中提取 heredoc 写入内容。
 * 匹配 cat << 'DELIM' > file / cat <<DELIM>> file 等常见模式。
 * @returns heredoc 内容字符串，无法提取时返回 undefined
 */
export function extractHeredocContent(commandText, _targetFile) {
    // 匹配 heredoc 起始标记：<< 'DELIM' 或 << DELIM 或 <<-DELIM
    const heredocStartPattern = /<<-?\s*['"]?(\w+)['"]?/g;
    let startMatch;
    while ((startMatch = heredocStartPattern.exec(commandText)) !== null) {
        const delimiter = startMatch[1];
        // heredoc 内容从标记行下一行开始，到只包含 delimiter 的行结束
        const afterStart = commandText.substring(startMatch.index + startMatch[0].length);
        const newlineIdx = afterStart.indexOf("\n");
        if (newlineIdx === -1)
            continue;
        const contentStart = newlineIdx + 1;
        const remaining = afterStart.substring(contentStart);
        const endIdx = remaining.search(new RegExp(`^${delimiter}\\s*$`, "m"));
        if (endIdx === -1)
            continue;
        return remaining.substring(0, endIdx);
    }
    return undefined;
}
export function resolveProjectRelativeTarget(projectPath, targetPath) {
    const rawTargetFilePath = path.isAbsolute(targetPath) ? path.normalize(targetPath) : path.resolve(projectPath, targetPath);
    const projectRealPath = fss.realpathSync(projectPath);
    const targetFilePath = isSameOrDescendantPath(rawTargetFilePath, projectPath, { caseSensitive: false })
        ? path.join(projectRealPath, path.relative(projectPath, rawTargetFilePath))
        : rawTargetFilePath;
    const relativeFilePath = path.relative(projectRealPath, targetFilePath).replaceAll(path.sep, "/");
    if (relativeFilePath === "" || relativeFilePath.startsWith("../") || path.isAbsolute(relativeFilePath))
        return undefined;
    return relativeFilePath;
}
export async function cmdCheckBash() {
    debugLog("CLI: 执行 cmdCheckBash");
    try {
        const chunks = [];
        for await (const chunk of process.stdin) {
            chunks.push(chunk);
        }
        const stdin = Buffer.concat(chunks).toString();
        const commandText = extractBashCommand(stdin || args.join(" "));
        if (isSoloforgeStateBypassCommand(commandText)) {
            denyPreToolUse("禁止通过 Bash 直接修改 .soloforge/state；请使用 SoloForge MCP 工具，例如 sf_status action=archive_stale confirm=true。");
            process.exit(0);
        }
        const projectPath = resolveProjectPath();
        const { config } = await resolveProjectConfig(projectPath);
        // 收集收窄路径（空 = 黑名单模式，不收窄）
        const allowedPaths = [
            ...(config.scope.backend ?? []),
            ...(config.scope.frontend ?? []),
            ...config.repos.flatMap((r) => r.scope ?? []),
        ].map((p) => path.resolve(projectPath, p));
        const { checkScope } = await import("../../engine/pipeline/scope_controller.js");
        for (const target of extractBashWriteTargets(commandText)) {
            const relativeTarget = resolveProjectRelativeTarget(projectPath, target);
            if (!relativeTarget)
                continue;
            // 黑名单 + 收窄校验（与 check_write 统一）
            const absoluteTarget = path.resolve(projectPath, relativeTarget);
            const scopeResult = await checkScope(absoluteTarget, allowedPaths, undefined, relativeTarget);
            if (scopeResult.severity === "blocked") {
                denyPreToolUse(scopeResult.reason || `Bash 写入 ${relativeTarget} 被阻止`);
                process.exit(0);
            }
            // 实现前门禁（保持不变）
            const implementationGateReason = await evaluateImplementationWriteGate(projectPath, relativeTarget);
            if (implementationGateReason) {
                denyPreToolUse(`禁止通过 Bash 直接写业务实现文件 ${relativeTarget}；${implementationGateReason}`);
                process.exit(0);
            }
            // L4: 产物结构校验（读取已有文件内容进行校验）
            if (relativeTarget.endsWith(".md") && fss.existsSync(absoluteTarget)) {
                try {
                    const existingContent = fss.readFileSync(absoluteTarget, "utf-8");
                    const artifactResult = evaluateArtifactStructure(relativeTarget, existingContent, projectPath);
                    if (artifactResult.severity === "blocked") {
                        denyPreToolUse(artifactResult.reason || "产物结构校验失败");
                        process.exit(0);
                    }
                }
                catch {
                    // 读取失败时不阻断
                }
            }
            // L5: .ts 文件偷懒模式检测（从 heredoc 内容或已写入文件检测）
            if (/\.ts$/.test(relativeTarget) && !/\.d\.ts$/.test(relativeTarget)) {
                let tsContent;
                // 优先从 heredoc 命令文本中提取内容
                const heredocContent = extractHeredocContent(commandText, relativeTarget);
                if (heredocContent) {
                    tsContent = heredocContent;
                }
                else if (fss.existsSync(absoluteTarget)) {
                    // 回退到读取已写入文件
                    try {
                        tsContent = fss.readFileSync(absoluteTarget, "utf-8");
                    }
                    catch {
                        // 读取失败时跳过
                    }
                }
                if (tsContent) {
                    const lazyFindings = detectLazyPatterns(tsContent, relativeTarget);
                    const hardFails = lazyFindings.filter((f) => f.severity === "hard_fail");
                    if (hardFails.length > 0) {
                        denyPreToolUse(`Bash 写入代码质量拦截: ${hardFails.length} 个偷懒模式\n` +
                            hardFails.map((f) => `  L${f.line_number} [${f.pattern}] ${f.message}: ${f.evidence}`).join("\n"));
                        process.exit(0);
                    }
                }
            }
        }
        process.exit(0);
    }
    catch (e) {
        userError(`SoloForge Bash 检查错误: ${errorMessage(e)}`);
        process.exit(1);
    }
}
//# sourceMappingURL=check_bash.js.map