import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { inspect } from "node:util";
import { cancel, isCancel } from "@clack/prompts";
import { DEFAULT_AGENT_WORKSPACE_DIR, ensureAgentWorkspace } from "../agents/workspace.js";
import { CONFIG_PATH } from "../config/config.js";
import { resolveSessionTranscriptsDirForAgent } from "../config/sessions.js";
import { callGateway } from "../gateway/call.js";
import { normalizeControlUiBasePath } from "../gateway/control-ui-shared.js";
import { isSafeExecutableValue } from "../infra/exec-safety.js";
import { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js";
import { isWSL } from "../infra/wsl.js";
import { runCommandWithTimeout } from "../process/exec.js";
import { stylePromptTitle } from "../terminal/prompt-style.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
import { CONFIG_DIR, resolveUserPath, shortenHomeInString, shortenHomePath, sleep, } from "../utils.js";
import { VERSION } from "../version.js";
export function guardCancel(value, runtime) {
    if (isCancel(value)) {
        cancel(stylePromptTitle("设置已取消。") ?? "设置已取消。");
        runtime.exit(0);
    }
    return value;
}
export function summarizeExistingConfig(config) {
    const rows = [];
    const defaults = config.agents?.defaults;
    if (defaults?.workspace)
        rows.push(shortenHomeInString(`workspace: ${defaults.workspace}`));
    if (defaults?.model) {
        const model = typeof defaults.model === "string" ? defaults.model : defaults.model.primary;
        if (model)
            rows.push(shortenHomeInString(`model: ${model}`));
    }
    if (config.gateway?.mode)
        rows.push(shortenHomeInString(`gateway.mode: ${config.gateway.mode}`));
    if (typeof config.gateway?.port === "number") {
        rows.push(shortenHomeInString(`gateway.port: ${config.gateway.port}`));
    }
    if (config.gateway?.bind)
        rows.push(shortenHomeInString(`gateway.bind: ${config.gateway.bind}`));
    if (config.gateway?.remote?.url) {
        rows.push(shortenHomeInString(`gateway.remote.url: ${config.gateway.remote.url}`));
    }
    if (config.skills?.install?.nodeManager) {
        rows.push(shortenHomeInString(`skills.nodeManager: ${config.skills.install.nodeManager}`));
    }
    return rows.length ? rows.join("\n") : "未检测到关键设置。";
}
export function randomToken() {
    return crypto.randomBytes(24).toString("hex");
}
export function normalizeGatewayTokenInput(value) {
    if (typeof value !== "string")
        return "";
    return value.trim();
}
export function printWizardHeader(runtime) {
    const header = [
        "░████░█░░░░░█████░█░░░█░███░░████░░████░░▀█▀",
        "█░░░░░█░░░░░█░░░█░█░█░█░█░░█░█░░░█░█░░░█░░█░",
        "█░░░░░█░░░░░█████░█░█░█░█░░█░████░░█░░░█░░█░",
        "█░░░░░█░░░░░█░░░█░█░█░█░█░░█░█░░█░░█░░░█░░█░",
        "░████░█████░█░░░█░░█░█░░███░░████░░░███░░░█░",
        "            🦞 每日新鲜 🦞",
    ].join("\n");
    runtime.log(header);
}
export function applyWizardMetadata(cfg, params) {
    const commit = process.env.GIT_COMMIT?.trim() || process.env.GIT_SHA?.trim() || undefined;
    return {
        ...cfg,
        wizard: {
            ...cfg.wizard,
            lastRunAt: new Date().toISOString(),
            lastRunVersion: VERSION,
            lastRunCommit: commit,
            lastRunCommand: params.command,
            lastRunMode: params.mode,
        },
    };
}
export async function resolveBrowserOpenCommand() {
    const platform = process.platform;
    const hasDisplay = Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
    const isSsh = Boolean(process.env.SSH_CLIENT) ||
        Boolean(process.env.SSH_TTY) ||
        Boolean(process.env.SSH_CONNECTION);
    if (isSsh && !hasDisplay && platform !== "win32") {
        return { argv: null, reason: "ssh-无显示器" };
    }
    if (platform === "win32") {
        return {
            argv: ["cmd", "/c", "start", ""],
            command: "cmd",
            quoteUrl: true,
        };
    }
    if (platform === "darwin") {
        const hasOpen = await detectBinary("open");
        return hasOpen ? { argv: ["open"], command: "open" } : { argv: null, reason: "缺少-open" };
    }
    if (platform === "linux") {
        const wsl = await isWSL();
        if (!hasDisplay && !wsl) {
            return { argv: null, reason: "无显示器" };
        }
        if (wsl) {
            const hasWslview = await detectBinary("wslview");
            if (hasWslview)
                return { argv: ["wslview"], command: "wslview" };
            if (!hasDisplay)
                return { argv: null, reason: "wsl-无-wslview" };
        }
        const hasXdgOpen = await detectBinary("xdg-open");
        return hasXdgOpen
            ? { argv: ["xdg-open"], command: "xdg-open" }
            : { argv: null, reason: "缺少-xdg-open" };
    }
    return { argv: null, reason: "不支持的平台" };
}
export async function detectBrowserOpenSupport() {
    const resolved = await resolveBrowserOpenCommand();
    if (!resolved.argv)
        return { ok: false, reason: resolved.reason };
    return { ok: true, command: resolved.command };
}
export function formatControlUiSshHint(params) {
    const basePath = normalizeControlUiBasePath(params.basePath);
    const uiPath = basePath ? `${basePath}/` : "/";
    const localUrl = `http://localhost:${params.port}${uiPath}`;
    const tokenParam = params.token ? `?token=${encodeURIComponent(params.token)}` : "";
    const authedUrl = params.token ? `${localUrl}${tokenParam}` : undefined;
    const sshTarget = resolveSshTargetHint();
    return [
        "未检测到图形界面。从您的计算机打开：",
        `ssh -N -L ${params.port}:127.0.0.1:${params.port} ${sshTarget}`,
        "然后打开：",
        localUrl,
        authedUrl,
        "文档：",
        "https://docs.clawd.bot/gateway/remote",
        "https://docs.clawd.bot/web/control-ui",
    ]
        .filter(Boolean)
        .join("\n");
}
function resolveSshTargetHint() {
    const user = process.env.USER || process.env.LOGNAME || "user";
    const conn = process.env.SSH_CONNECTION?.trim().split(/\s+/);
    const host = conn?.[2] ?? "<host>";
    return `${user}@${host}`;
}
export async function openUrl(url) {
    if (shouldSkipBrowserOpenInTests())
        return false;
    const resolved = await resolveBrowserOpenCommand();
    if (!resolved.argv)
        return false;
    const quoteUrl = resolved.quoteUrl === true;
    const command = [...resolved.argv];
    if (quoteUrl) {
        if (command.at(-1) === "") {
            // Preserve the empty title token for `start` when using verbatim args.
            command[command.length - 1] = '""';
        }
        command.push(`"${url}"`);
    }
    else {
        command.push(url);
    }
    try {
        await runCommandWithTimeout(command, {
            timeoutMs: 5_000,
            windowsVerbatimArguments: quoteUrl,
        });
        return true;
    }
    catch {
        // 忽略；我们仍会打印URL以便手动打开
        return false;
    }
}
export async function openUrlInBackground(url) {
    if (shouldSkipBrowserOpenInTests())
        return false;
    if (process.platform !== "darwin")
        return false;
    const resolved = await resolveBrowserOpenCommand();
    if (!resolved.argv || resolved.command !== "open")
        return false;
    const command = ["open", "-g", url];
    try {
        await runCommandWithTimeout(command, { timeoutMs: 5_000 });
        return true;
    }
    catch {
        return false;
    }
}
export async function ensureWorkspaceAndSessions(workspaceDir, runtime, options) {
    const ws = await ensureAgentWorkspace({
        dir: workspaceDir,
        ensureBootstrapFiles: !options?.skipBootstrap,
    });
    runtime.log(`工作区正常：${shortenHomePath(ws.dir)}`);
    const sessionsDir = resolveSessionTranscriptsDirForAgent(options?.agentId);
    await fs.mkdir(sessionsDir, { recursive: true });
    runtime.log(`会话正常：${shortenHomePath(sessionsDir)}`);
}
export function resolveNodeManagerOptions() {
    return [
        { value: "npm", label: "npm" },
        { value: "pnpm", label: "pnpm" },
        { value: "bun", label: "bun" },
    ];
}
export async function moveToTrash(pathname, runtime) {
    if (!pathname)
        return;
    try {
        await fs.access(pathname);
    }
    catch {
        return;
    }
    try {
        await runCommandWithTimeout(["trash", pathname], { timeoutMs: 5000 });
        runtime.log(`已移至废纸篓：${shortenHomePath(pathname)}`);
    }
    catch {
        runtime.log(`移至废纸篓失败（手动删除）：${shortenHomePath(pathname)}`);
    }
}
export async function handleReset(scope, workspaceDir, runtime) {
    await moveToTrash(CONFIG_PATH, runtime);
    if (scope === "config")
        return;
    await moveToTrash(path.join(CONFIG_DIR, "credentials"), runtime);
    await moveToTrash(resolveSessionTranscriptsDirForAgent(), runtime);
    if (scope === "full") {
        await moveToTrash(workspaceDir, runtime);
    }
}
export async function detectBinary(name) {
    if (!name?.trim())
        return false;
    if (!isSafeExecutableValue(name))
        return false;
    const resolved = name.startsWith("~") ? resolveUserPath(name) : name;
    if (path.isAbsolute(resolved) ||
        resolved.startsWith(".") ||
        resolved.includes("/") ||
        resolved.includes("\\")) {
        try {
            await fs.access(resolved);
            return true;
        }
        catch {
            return false;
        }
    }
    const command = process.platform === "win32" ? ["where", name] : ["/usr/bin/env", "which", name];
    try {
        const result = await runCommandWithTimeout(command, { timeoutMs: 2000 });
        return result.code === 0 && result.stdout.trim().length > 0;
    }
    catch {
        return false;
    }
}
function shouldSkipBrowserOpenInTests() {
    if (process.env.VITEST)
        return true;
    return process.env.NODE_ENV === "test";
}
export async function probeGatewayReachable(params) {
    const url = params.url.trim();
    const timeoutMs = params.timeoutMs ?? 1500;
    try {
        await callGateway({
            url,
            token: params.token,
            password: params.password,
            method: "health",
            timeoutMs,
            clientName: GATEWAY_CLIENT_NAMES.PROBE,
            mode: GATEWAY_CLIENT_MODES.PROBE,
        });
        return { ok: true };
    }
    catch (err) {
        return { ok: false, detail: summarizeError(err) };
    }
}
export async function waitForGatewayReachable(params) {
    const deadlineMs = params.deadlineMs ?? 15_000;
    const pollMs = params.pollMs ?? 400;
    const probeTimeoutMs = params.probeTimeoutMs ?? 1500;
    const startedAt = Date.now();
    let lastDetail;
    while (Date.now() - startedAt < deadlineMs) {
        const probe = await probeGatewayReachable({
            url: params.url,
            token: params.token,
            password: params.password,
            timeoutMs: probeTimeoutMs,
        });
        if (probe.ok)
            return probe;
        lastDetail = probe.detail;
        await sleep(pollMs);
    }
    return { ok: false, detail: lastDetail };
}
function summarizeError(err) {
    let raw = "unknown error";
    if (err instanceof Error) {
        raw = err.message || raw;
    }
    else if (typeof err === "string") {
        raw = err || raw;
    }
    else if (err !== undefined) {
        raw = inspect(err, { depth: 2 });
    }
    const line = raw
        .split("\n")
        .map((s) => s.trim())
        .find(Boolean) ?? raw;
    return line.length > 120 ? `${line.slice(0, 119)}…` : line;
}
export const DEFAULT_WORKSPACE = DEFAULT_AGENT_WORKSPACE_DIR;
export function resolveControlUiLinks(params) {
    const port = params.port;
    const bind = params.bind ?? "loopback";
    const customBindHost = params.customBindHost?.trim();
    const tailnetIPv4 = pickPrimaryTailnetIPv4();
    const host = (() => {
        if (bind === "custom" && customBindHost && isValidIPv4(customBindHost)) {
            return customBindHost;
        }
        if (bind === "tailnet" && tailnetIPv4)
            return tailnetIPv4 ?? "127.0.0.1";
        return "127.0.0.1";
    })();
    const basePath = normalizeControlUiBasePath(params.basePath);
    const uiPath = basePath ? `${basePath}/` : "/";
    const wsPath = basePath ? basePath : "";
    return {
        httpUrl: `http://${host}:${port}${uiPath}`,
        wsUrl: `ws://${host}:${port}${wsPath}`,
    };
}
function isValidIPv4(host) {
    const parts = host.split(".");
    if (parts.length !== 4)
        return false;
    return parts.every((part) => {
        const n = Number.parseInt(part, 10);
        return !Number.isNaN(n) && n >= 0 && n <= 255 && part === String(n);
    });
}
