import { pathToFileURL } from "node:url";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import crypto from "node:crypto";
//#region scripts/migrate-legacy-sessions.mjs
const LEGACY_TS_RE = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/;
function parseLegacyTimestamp(value, fallbackMs) {
	if (typeof value !== "string") return fallbackMs;
	const m = LEGACY_TS_RE.exec(value.trim());
	if (!m) return fallbackMs;
	const [, y, mo, d, h, mi, s, ms] = m;
	const ts = Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s), ms ? Number(ms.padEnd(3, "0")) : 0);
	if (!Number.isFinite(ts)) return fallbackMs;
	const back = new Date(ts);
	if (back.getUTCMonth() !== Number(mo) - 1 || back.getUTCDate() !== Number(d)) return fallbackMs;
	return ts;
}
function atomicWriteJson(filePath, value) {
	const tmpPath = `${filePath}.tmp`;
	fs.mkdirSync(path.dirname(filePath), { recursive: true });
	fs.writeFileSync(tmpPath, JSON.stringify(value, null, 2));
	fs.renameSync(tmpPath, filePath);
}
function readJsonIfExists(filePath) {
	if (!fs.existsSync(filePath)) return void 0;
	return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
function buildStoreEntry({ sessionId, sessionFile, firstMsgMs, lastMsgMs, label }) {
	return {
		sessionId,
		sessionFile,
		sessionStartedAt: firstMsgMs,
		lastInteractionAt: lastMsgMs,
		updatedAt: lastMsgMs,
		chatType: "direct",
		systemSent: true,
		abortedLastRun: false,
		origin: {
			provider: "legacy-import",
			surface: "legacy",
			chatType: "direct"
		},
		deliveryContext: { channel: "webchat" },
		lastChannel: "webchat",
		label
	};
}
function mergeSessionsStore({ storePath, additions, force = false }) {
	const next = { ...readJsonIfExists(storePath) ?? {} };
	let added = 0;
	let skipped = 0;
	for (const [k, v] of Object.entries(additions)) {
		if (!force && Object.prototype.hasOwnProperty.call(next, k)) {
			skipped++;
			continue;
		}
		next[k] = v;
		added++;
	}
	atomicWriteJson(storePath, next);
	return {
		added,
		skipped
	};
}
function loadManifest(manifestPath) {
	if (!fs.existsSync(manifestPath)) return {
		version: 1,
		imported: {}
	};
	let raw;
	try {
		raw = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
	} catch {
		raw = null;
	}
	if (raw && typeof raw === "object" && raw.version === 1 && raw.imported && typeof raw.imported === "object") return raw;
	const err = /* @__PURE__ */ new Error(`legacy-import-manifest at ${manifestPath} is corrupt`);
	err.code = "LEGACY_MANIFEST_CORRUPT";
	throw err;
}
function recordImport({ manifestPath, sourcePath, record }) {
	const current = loadManifest(manifestPath);
	current.imported[sourcePath] = record;
	atomicWriteJson(manifestPath, current);
}
const HEARTBEAT_PREFIXES = [
	"你现在处于心跳巡检模式",
	"# 心跳巡检",
	"# 引导模式"
];
function loadChats(chatsJsonPath) {
	const raw = readJsonIfExists(chatsJsonPath);
	if (!raw || !Array.isArray(raw.chats)) return [];
	return raw.chats.map((c) => ({
		sessionId: String(c.session_id),
		name: typeof c.name === "string" ? c.name : "",
		channel: typeof c.channel === "string" ? c.channel : "",
		createdAtMs: Date.parse(c.created_at),
		updatedAtMs: Date.parse(c.updated_at)
	})).filter((c) => Number.isFinite(c.createdAtMs) && Number.isFinite(c.updatedAtMs)).sort((a, b) => a.createdAtMs - b.createdAtMs);
}
function isHeartbeatMessage(msg) {
	if (msg?.role !== "user") return false;
	const trimmed = (msg.text ?? "").replace(/^\s+/, "");
	return HEARTBEAT_PREFIXES.some((p) => trimmed.startsWith(p));
}
function loadSessionMessages({ sessionsSourceDir, sessionId, fallbackMs }) {
	const filePath = path.join(sessionsSourceDir, `default_${sessionId}.json`);
	if (!fs.existsSync(filePath)) return [];
	let doc;
	try {
		doc = JSON.parse(fs.readFileSync(filePath, "utf8"));
	} catch {
		return [];
	}
	const groups = doc?.agent?.memory?.content;
	if (!Array.isArray(groups)) return [];
	const out = [];
	let monotonic = fallbackMs;
	for (const group of groups) {
		if (!Array.isArray(group)) continue;
		for (const m of group) {
			if (!m || m.role !== "user" && m.role !== "assistant") continue;
			const text = (Array.isArray(m.content) ? m.content : []).filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n\n");
			if (!text.trim()) continue;
			const tsMs = parseLegacyTimestamp(m.timestamp, monotonic);
			monotonic = tsMs + 1;
			out.push({
				role: m.role,
				text,
				tsMs
			});
		}
	}
	return out;
}
function filterHeartbeats(messages) {
	const kept = [];
	let dropNextAssistant = false;
	for (const m of messages) {
		if (dropNextAssistant && m.role === "assistant") {
			dropNextAssistant = false;
			continue;
		}
		dropNextAssistant = false;
		if (isHeartbeatMessage(m)) {
			dropNextAssistant = true;
			continue;
		}
		kept.push(m);
	}
	return kept;
}
const CHAT_ALLOWED_ROLES = new Set(["user", "assistant"]);
function convertChatToEvents({ chat, sessionId, messages, cwd, randomHex }) {
	const events = [];
	const headerTs = messages.length > 0 ? messages[0].tsMs : chat.createdAtMs;
	events.push({
		type: "session",
		version: 3,
		id: sessionId,
		timestamp: new Date(headerTs).toISOString(),
		cwd
	});
	let prevId = sessionId;
	let firstMsgMs = Number.POSITIVE_INFINITY;
	let lastMsgMs = Number.NEGATIVE_INFINITY;
	for (const m of messages) {
		if (!CHAT_ALLOWED_ROLES.has(m.role)) continue;
		const id = randomHex();
		events.push({
			type: "message",
			id,
			parentId: prevId,
			timestamp: new Date(m.tsMs).toISOString(),
			message: {
				role: m.role,
				content: [{
					type: "text",
					text: m.text
				}],
				timestamp: m.tsMs
			}
		});
		prevId = id;
		if (m.tsMs < firstMsgMs) firstMsgMs = m.tsMs;
		if (m.tsMs > lastMsgMs) lastMsgMs = m.tsMs;
	}
	if (firstMsgMs === Number.POSITIVE_INFINITY) {
		firstMsgMs = chat.createdAtMs;
		lastMsgMs = chat.createdAtMs;
	}
	return {
		events,
		messageCount: events.length - 1,
		firstMsgMs,
		lastMsgMs
	};
}
function parseArgs(argv) {
	const out = {
		workspace: null,
		agent: "main",
		stateDir: null,
		dryRun: false,
		force: false,
		verbose: false
	};
	for (let i = 0; i < argv.length; i++) {
		const a = argv[i];
		switch (a) {
			case "--workspace":
				out.workspace = argv[++i];
				break;
			case "--agent":
				out.agent = argv[++i];
				break;
			case "--state-dir":
				out.stateDir = argv[++i];
				break;
			case "--dry-run":
				out.dryRun = true;
				break;
			case "--force":
				out.force = true;
				break;
			case "--verbose":
				out.verbose = true;
				break;
			default: throw new Error(`unknown arg: ${a}`);
		}
	}
	return out;
}
function resolveStateDir(env, override) {
	if (override) return override;
	const explicit = env.OPENCLAW_STATE_DIR;
	if (explicit) return explicit;
	const home = env.HOME || env.USERPROFILE || os.homedir();
	return path.join(home, ".sealseek");
}
async function runCli({ argv, env }) {
	const args = parseArgs(argv);
	const stateDir = resolveStateDir(env, args.stateDir);
	const workspace = args.workspace ?? path.join(stateDir, "workspaces", "default");
	const chatsPath = path.join(workspace, "chats.json");
	const sessionsSourceDir = path.join(workspace, "sessions");
	const sessionsDir = path.join(stateDir, "agents", args.agent, "sessions");
	const storePath = path.join(sessionsDir, "sessions.json");
	const manifestPath = path.join(sessionsDir, ".legacy-import-manifest.json");
	if (!fs.existsSync(chatsPath)) {
		console.error(`chats.json not found at ${chatsPath}`);
		return 1;
	}
	if (!fs.existsSync(sessionsSourceDir)) {
		console.error(`sessions source directory not found at ${sessionsSourceDir}`);
		return 1;
	}
	const chats = loadChats(chatsPath);
	const chatsJsonMtime = fs.statSync(chatsPath).mtimeMs;
	let manifest;
	try {
		manifest = loadManifest(manifestPath);
	} catch (err) {
		if (err.code !== "LEGACY_MANIFEST_CORRUPT" || !args.force) {
			console.error(err.message + (err.code === "LEGACY_MANIFEST_CORRUPT" ? " (rerun with --force to overwrite)" : ""));
			return 1;
		}
		console.error(`${err.message} — proceeding because --force is set`);
		manifest = {
			version: 1,
			imported: {}
		};
	}
	const additions = {};
	const newManifestRecords = [];
	let imported = 0;
	let skipped = 0;
	let empty = 0;
	let failed = 0;
	for (const chat of chats) {
		const existing = manifest.imported[chat.sessionId];
		if (!args.force && existing && existing.chatsJsonMtime === chatsJsonMtime) {
			if (args.verbose) console.log(`skip ${chat.sessionId} (already imported)`);
			skipped++;
			continue;
		}
		const msgs = filterHeartbeats(loadSessionMessages({
			sessionsSourceDir,
			sessionId: chat.sessionId,
			fallbackMs: chat.createdAtMs
		}));
		if (msgs.length === 0) {
			if (args.verbose) console.log(`empty ${chat.sessionId} (${chat.name || "<no name>"})`);
			empty++;
			continue;
		}
		const sessionId = crypto.randomUUID();
		const sessionFile = path.join(sessionsDir, `${sessionId}.jsonl`);
		let result;
		try {
			result = convertChatToEvents({
				chat,
				sessionId,
				messages: msgs,
				cwd: path.join(stateDir, "workspace"),
				randomHex: () => crypto.randomBytes(4).toString("hex")
			});
		} catch (err) {
			console.error(`fail ${chat.sessionId}: ${err.message}`);
			failed++;
			continue;
		}
		if (!args.dryRun) {
			fs.mkdirSync(sessionsDir, { recursive: true });
			const tmpPath = `${sessionFile}.tmp`;
			fs.writeFileSync(tmpPath, result.events.map((e) => JSON.stringify(e)).join("\n") + "\n");
			fs.renameSync(tmpPath, sessionFile);
		}
		additions[`agent:${args.agent}:legacy-${chat.sessionId}`] = buildStoreEntry({
			sessionId,
			sessionFile,
			firstMsgMs: result.firstMsgMs,
			lastMsgMs: result.lastMsgMs,
			label: chat.name || "(legacy chat)"
		});
		newManifestRecords.push({
			sessionId: chat.sessionId,
			record: {
				newSessionId: sessionId,
				sessionKey: `legacy-${chat.sessionId}`,
				importedAt: Date.now(),
				chatsJsonMtime,
				messageCount: result.messageCount
			}
		});
		imported++;
		if (args.verbose) console.log(`import ${chat.sessionId} -> ${sessionId} (${result.messageCount} msgs)`);
	}
	if (!args.dryRun && imported > 0) {
		mergeSessionsStore({
			storePath,
			additions,
			force: args.force
		});
		for (const r of newManifestRecords) recordImport({
			manifestPath,
			sourcePath: r.sessionId,
			record: r.record
		});
	}
	console.log(`chats=${chats.length} imported=${imported} skipped=${skipped} empty=${empty} failed=${failed} dry=${args.dryRun ? "true" : "false"}`);
	return 0;
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) runCli({
	argv: process.argv.slice(2),
	env: process.env
}).then((code) => process.exit(code ?? 0)).catch((err) => {
	console.error(err?.stack ?? String(err));
	process.exit(1);
});
//#endregion
export { runCli };
