#!/bin/sh
# HarmonyOS 硬链接(link(2))补丁 —— 垫片方案(自包含)
#
# 背景:HarmonyOS 的 f2fs 配置拒绝硬链接(实测 link() 返回 EACCES/EPERM),而
# DeepSeek Harness(dsh)多处用 link() 做"原子创建且不覆盖已存在目标"的文件发布。
#
# 用法:
#   sh reapply-hardlink-patches.sh          打补丁(默认)
#   sh reapply-hardlink-patches.sh --restore  还原(撤销补丁)
#
# 打补丁方案(自包含,不依赖外部文件):
#   1) 把 link() 兼容垫片(见下方 SHIM heredoc)写入 dsh 依赖树;
#   2) 对每个调用点:已含垫片补丁(我们的代码)则跳过;带旧方案内联补丁
#      (证明 .bak 与当前文件是同一版本)才从 .bak 还原,否则绝不还原——
#      避免 dsh 升级后过期 .bak 把旧版本代码覆盖到新文件上;再重新生成 .bak;
#   3) 用正则把该文件 fs/promises import 中的 `link` 移除(只关心 link 这一个名字,
#      不关心同行导入了哪些其他包名),换成垫片 import;
#   4) 垫片行为:先系统 link,失败(EACCES/EPERM)则排他复制 copyFile(COPYFILE_EXCL),
#      返回值/错误码与系统 link 一致(成功 undefined / 目标已存在 EEXIST / 源缺失 ENOENT)。
#
# 还原方案:只要目标文件含我们的补丁(垫片 import 或旧内联补丁)就从 .bak 还原;
# 三个调用点都不再引用垫片时删除垫片。幂等。
set -e

RESTORE=0
case "${1:-}" in
	--restore) RESTORE=1 ;;
	"") ;;
	*) echo "用法: $0 [--restore]"; exit 2 ;;
esac

NODE=/storage/Users/currentUser/.harmonybrew/bin/node
DSH_ROOT="/storage/Users/currentUser/.npm-global/lib/node_modules/@deepseek-ai/dsh"
SHIM_DST="$DSH_ROOT/node_modules/@deepseek-ai/harmonyos-link-shim.mjs"
SHIM_IMP='import { link } from "../../harmonyos-link-shim.mjs";'
SP="$DSH_ROOT/node_modules/@deepseek-ai/dsh-session-persistence-jsonl/lib/index.js"
FS="$DSH_ROOT/node_modules/@deepseek-ai/dsh-fs-local/lib/index.js"
ATT="$DSH_ROOT/node_modules/@deepseek-ai/dsh-attachment-local/lib/index.js"

[ -d "$DSH_ROOT/node_modules/@deepseek-ai" ] || { echo "未找到 dsh 依赖目录 $DSH_ROOT"; exit 1; }

patch_call_site() {
	# $1 = 描述, $2 = 文件路径
	echo "==== $1 ===="
	[ -f "$2" ] || { echo "未找到 $2,跳过"; return 0; }

	# 1) 已含我们的垫片补丁 → 跳过(检测"我们的代码"标记)
	if grep -q 'harmonyos-link-shim.mjs' "$2"; then
		echo "  [skip] 已含垫片补丁,跳过"
		return 0
	fi

	# 2) 带旧方案的内联补丁 → 说明该文件是我们旧版打过补丁的样子,.bak 必与它是同一
	#    版本,从 .bak 还原以清除旧补丁。否则(全新 / dsh 升级后的文件,不含我们的
	#    任何代码)绝不还原 .bak,避免过期 .bak 把旧版本代码覆盖到新文件上。
	if grep -q 'try { await link(tmp, finalPath); } catch' "$2"; then
		if [ -f "$2.bak" ]; then
			cp "$2.bak" "$2"
			echo "  已从 .bak 还原(清除旧内联补丁): $2.bak"
		else
			echo "  [WARN] 带旧内联补丁但无 .bak,基于当前内容直接打补丁"
		fi
	fi

	cp "$2" "$2.bak"
	"$NODE" - "$2" "$SHIM_IMP" <<'NODE'
const fs = require("fs");
const f = process.argv[2];
const shimImp = process.argv[3];
let s = fs.readFileSync(f, "utf8");
let changed = false;

// 1) 正则:从 node:fs/promises 的 import 中移除 `link`(不关心同行的其他包名)
const reImport = /import \{([^}]*)\} from "node:fs\/promises";/;
const m = reImport.exec(s);
if (m) {
	const names = m[1].split(",").map((x) => x.trim());
	if (names.includes("link")) {
		const cleaned = names.filter((x) => x !== "link").join(", ");
		s = s.replace(reImport, `import { ${cleaned} } from "node:fs/promises";`);
		changed = true;
		console.log("  [ok] 正则移除 fs/promises import 中的 link");
	} else {
		console.log("  [skip] fs/promises import 已不含 link");
	}
} else {
	console.error("  [FAIL] 未找到 node:fs/promises import 行"); process.exit(1);
}

// 2) 在 fs/promises import 行后引入垫片
if (!s.includes(shimImp)) {
	const reInsert = /(import \{[^}]*\} from "node:fs\/promises";\n)/;
	if (!reInsert.test(s)) { console.error("  [FAIL] 插入点未找到"); process.exit(1); }
	s = s.replace(reInsert, `$1${shimImp}\n`);
	changed = true;
	console.log("  [ok] 引入垫片 import");
} else {
	console.log("  [skip] 垫片已引入");
}

if (changed) fs.writeFileSync(f, s);
console.log("  完成");
NODE
}

restore_call_site() {
	# $1 = 描述, $2 = 文件路径
	echo "==== $1 ===="
	[ -f "$2" ] || { echo "未找到 $2,跳过"; return 0; }
	# 仅当文件含我们的补丁(垫片 import 或旧内联补丁)才还原;否则不动(全新/升级文件)
	if grep -q 'harmonyos-link-shim.mjs\|try { await link(tmp, finalPath); } catch' "$2"; then
		if [ -f "$2.bak" ]; then
			cp "$2.bak" "$2"
			echo "  已还原: $2.bak -> $2"
		else
			echo "  [WARN] 文件含我们的补丁但无 .bak,无法自动还原,请手动处理"
		fi
	else
		echo "  [skip] 不含我们的补丁,无需还原"
	fi
}

if [ "$RESTORE" -eq 1 ]; then
	restore_call_site "1/3  dsh-session-persistence-jsonl(session 日志)" "$SP"
	restore_call_site "2/3  dsh-fs-local(write 工具)" "$FS"
	restore_call_site "3/3  dsh-attachment-local(附件对象)" "$ATT"
	# 三个调用点都不再引用垫片时,删除垫片
	STILL=0
	for f in "$SP" "$FS" "$ATT"; do
		[ -f "$f" ] && grep -q 'harmonyos-link-shim.mjs' "$f" && STILL=1
	done
	if [ "$STILL" -eq 0 ] && [ -f "$SHIM_DST" ]; then
		rm -f "$SHIM_DST"
		echo "已移除垫片: $SHIM_DST"
	elif [ "$STILL" -eq 1 ]; then
		echo "[WARN] 仍有调用点引用垫片,保留: $SHIM_DST"
	fi
	echo
	echo "已还原。重启 dsh web 生效。"
	exit 0
fi

echo "==== 0/4  写入 link 垫片 ===="
cat > "$SHIM_DST" <<'SHIM'
import { constants } from "node:fs";
import { copyFile, link as systemLink } from "node:fs/promises";

export async function link(src, dest) {
	try {
		return await systemLink(src, dest);
	} catch (error) {
		if (error.code !== "EACCES" && error.code !== "EPERM") throw error;
		return await copyFile(src, dest, constants.COPYFILE_EXCL);
	}
}

export * from "node:fs/promises";
SHIM
echo "  已写入: $SHIM_DST"

patch_call_site "1/3  dsh-session-persistence-jsonl(session 日志)" "$SP"
patch_call_site "2/3  dsh-fs-local(write 工具)" "$FS"
patch_call_site "3/3  dsh-attachment-local(附件对象)" "$ATT"

echo
echo "垫片方案就绪。重启 dsh web 生效。"
echo "dsh 升级后重跑本脚本即可;各文件备份为 <文件>.bak;还原用 --restore"