/**
 * `penguin update`'s pure pieces: install-kind detection over the three real layouts, package-
 * manager identification for global installs, version normalisation/comparison, the installer
 * argv/env built for each combination of install dir and bundled runtime, and the decision the
 * command makes before it touches anything (planUpdate) plus its confirmation gate.
 *
 * No real network and no filesystem mutation — every I/O helper takes its inputs as arguments.
 */
import { describe, expect, it } from "vitest";
import { Command } from "commander";
import {
  buildInstallerInvocation,
  compareVersions,
  confirmationMode,
  configuredInstallerCandidate,
  detectInstall,
  detectPackageManager,
  downloadInstaller,
  globalInstallCommand,
  installerCandidates,
  installerUrl,
  normalizeVersion,
  normalizeHttpsBaseUrl,
  parseDownloadSource,
  parseOssLatestManifest,
  planUpdate,
  registerUpdateCommand,
  resolveRelease,
} from "../src/commands/update.js";
import { getMessages } from "../src/i18n.js";

// POSIX-only: the fixtures are POSIX install layouts and detectInstall normalizes through
// path.* (backslashes on Windows) — where in-place `penguin update` is refused anyway
// (the documented Windows upgrade path is re-running install.ps1).
describe.skipIf(process.platform === "win32")(
  "detectInstall (how this CLI was installed, from its own real path)",
  () => {
    it("tarball: <installDir>/lib/dist/index.js, the layout install.sh unpacks", () => {
      expect(detectInstall("/home/me/.penguin/lib/dist/index.js")).toEqual({
        kind: "tarball",
        installDir: "/home/me/.penguin",
      });
    });

    it("tarball: a non-default PENGUIN_INSTALL_DIR is read off the path, not the environment", () => {
      expect(detectInstall("/opt/tools/penguin/lib/dist/index.js")).toEqual({
        kind: "tarball",
        installDir: "/opt/tools/penguin",
      });
    });

    it("npm global: npm's own prefix layout", () => {
      expect(
        detectInstall("/usr/local/lib/node_modules/@prismshadow/penguin-cli/dist/index.js"),
      ).toEqual({ kind: "npm", globalRoot: "/usr/local/lib/node_modules" });
    });

    it("npm global: pnpm's global store, through the .pnpm virtual dir", () => {
      const p =
        "/home/me/.local/share/pnpm/global/5/node_modules/.pnpm/@prismshadow+penguin-cli@0.1.1/node_modules/@prismshadow/penguin-cli/dist/index.js";
      const info = detectInstall(p);
      expect(info.kind).toBe("npm");
      expect(info.globalRoot).toContain(".pnpm");
    });

    it("source checkout: the built dist inside the monorepo", () => {
      expect(detectInstall("/home/me/code/penguin-harness/packages/cli/dist/index.js")).toEqual({
        kind: "source",
      });
    });

    it("source checkout: tsx running src directly", () => {
      expect(detectInstall("/home/me/code/penguin-harness/packages/cli/src/index.ts")).toEqual({
        kind: "source",
      });
    });

    it("a checkout wins over the tarball shape, so a repo under a lib/ dir is never mistaken for an install", () => {
      expect(detectInstall("/srv/lib/penguin-harness/packages/cli/dist/index.js")).toEqual({
        kind: "source",
      });
    });

    it("anything else is unknown rather than guessed", () => {
      expect(detectInstall("/random/place/index.js").kind).toBe("unknown");
      expect(detectInstall("/home/me/.penguin/bin/penguin").kind).toBe("unknown");
    });
  },
);

describe("detectPackageManager (which manager owns a global node_modules root)", () => {
  it("pnpm: the global store or the .pnpm virtual dir", () => {
    expect(detectPackageManager("/home/me/.local/share/pnpm/global/5/node_modules")).toBe("pnpm");
    expect(
      detectPackageManager("/home/me/.local/share/pnpm/global/5/node_modules/.pnpm/x/node_modules"),
    ).toBe("pnpm");
  });
  it("npm: the usual prefixes", () => {
    expect(detectPackageManager("/usr/local/lib/node_modules")).toBe("npm");
    expect(detectPackageManager("/home/me/.npm-global/lib/node_modules")).toBe("npm");
  });
  it("yarn and bun", () => {
    expect(detectPackageManager("/home/me/.config/yarn/global/node_modules")).toBe("yarn");
    expect(detectPackageManager("/home/me/.bun/install/global/node_modules")).toBe("bun");
  });
  it("returns null when unrecognizable, so the caller prints a command instead of guessing", () => {
    expect(detectPackageManager("/weird/place")).toBeNull();
    expect(detectPackageManager("")).toBeNull();
  });
});

describe("globalInstallCommand", () => {
  it("uses each manager's own global-install spelling", () => {
    expect(globalInstallCommand("pnpm", "0.1.2")).toEqual({
      command: "pnpm",
      args: ["add", "-g", "@prismshadow/penguin-cli@0.1.2"],
    });
    expect(globalInstallCommand("npm", "0.1.2")).toEqual({
      command: "npm",
      args: ["install", "-g", "@prismshadow/penguin-cli@0.1.2"],
    });
    expect(globalInstallCommand("yarn", "0.1.2")).toEqual({
      command: "yarn",
      args: ["global", "add", "@prismshadow/penguin-cli@0.1.2"],
    });
    expect(globalInstallCommand("bun", "0.1.2")).toEqual({
      command: "bun",
      args: ["add", "-g", "@prismshadow/penguin-cli@0.1.2"],
    });
  });
});

describe("normalizeVersion / compareVersions", () => {
  it("accepts both v-prefixed and bare tags", () => {
    expect(normalizeVersion("v0.1.2")).toBe("0.1.2");
    expect(normalizeVersion("0.1.2")).toBe("0.1.2");
    expect(normalizeVersion("  V0.1.2 ")).toBe("0.1.2");
  });
  it("equal versions compare equal regardless of the v prefix", () => {
    expect(compareVersions("v0.1.1", "0.1.1")).toBe(0);
  });
  it("orders newer above older, including across component widths", () => {
    expect(compareVersions("0.1.2", "0.1.1")).toBe(1);
    expect(compareVersions("0.1.1", "0.1.2")).toBe(-1);
    expect(compareVersions("0.2.0", "0.1.9")).toBe(1);
    expect(compareVersions("1.0.0", "0.99.99")).toBe(1);
    expect(compareVersions("0.1.10", "0.1.9")).toBe(1);
  });
  it("treats a missing component as 0", () => {
    expect(compareVersions("1", "1.0.0")).toBe(0);
    expect(compareVersions("1.1", "1.0.9")).toBe(1);
  });
  it("a malformed tag can never look like an available upgrade", () => {
    expect(compareVersions("not-a-version", "0.1.1")).toBe(-1);
    expect(compareVersions("", "0.1.1")).toBe(-1);
  });
  it("reads each component as far as it is numeric, which is what the docblock promises", () => {
    // parseInt semantics, pinned so the comment and the behaviour cannot drift apart again:
    // leading digits win, and a component with none counts as 0.
    expect(compareVersions("0.1.2abc", "0.1.2")).toBe(0);
    expect(compareVersions("0.1.x", "0.1.0")).toBe(0);
    expect(compareVersions("0.1.x", "0.1.1")).toBe(-1);
  });
  it("suffixes are invisible, so a pre-release compares equal to its release", () => {
    // Documented limitation rather than a bug: this project publishes plain vX.Y.Z tags only.
    expect(compareVersions("0.1.2-rc1", "0.1.2")).toBe(0);
    expect(compareVersions("0.1.2-rc1", "0.1.1")).toBe(1);
  });
});

describe("installerUrl", () => {
  it("resolves the latest release when no version is pinned", () => {
    expect(installerUrl()).toBe(
      "https://github.com/Prism-Shadow/penguin-harness/releases/latest/download/install.sh",
    );
  });
  it("pins a tag, normalising the v prefix", () => {
    const expected =
      "https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.1.2/install.sh";
    expect(installerUrl("0.1.2")).toBe(expected);
    expect(installerUrl("v0.1.2")).toBe(expected);
  });
});

describe("release source selection", () => {
  const ossOrigin = "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com";
  const githubApi = "https://api.github.com/repos/Prism-Shadow/penguin-harness/releases/latest";
  const manifest = {
    schemaVersion: 1,
    tag: "v0.2.1",
    version: "0.2.1",
    releaseBaseUrl: `${ossOrigin}/releases/v0.2.1`,
  };
  const t = getMessages("en");

  it("accepts the same auto/oss/github environment contract as the installers", () => {
    expect(parseDownloadSource(undefined)).toBe("auto");
    expect(parseDownloadSource("auto")).toBe("auto");
    expect(parseDownloadSource("oss")).toBe("oss");
    expect(parseDownloadSource("github")).toBe("github");
    expect(parseDownloadSource("OSS")).toBeNull();
    expect(parseDownloadSource("mirror")).toBeNull();
  });

  it("accepts only absolute HTTPS mirror bases and removes trailing slashes", () => {
    expect(normalizeHttpsBaseUrl("https://mirror.example/releases/v0.2.1/")).toBe(
      "https://mirror.example/releases/v0.2.1",
    );
    expect(normalizeHttpsBaseUrl("http://mirror.example/releases/v0.2.1")).toBeNull();
    expect(normalizeHttpsBaseUrl("/releases/v0.2.1")).toBeNull();
    expect(normalizeHttpsBaseUrl(undefined)).toBeNull();
  });

  it("an explicit mirror is one strict installer candidate with its configured fallback", () => {
    expect(
      configuredInstallerCandidate(
        "https://mirror.example/releases/v0.2.1",
        "https://backup.example/releases/v0.2.1",
      ),
    ).toEqual({
      source: "configured",
      baseUrl: "https://mirror.example/releases/v0.2.1",
      url: "https://mirror.example/releases/v0.2.1/install.sh",
      fallbackBaseUrl: "https://backup.example/releases/v0.2.1",
    });
  });

  it("validates latest.json's schema, tag, and fixed OSS release base", () => {
    expect(parseOssLatestManifest(manifest)).toEqual({
      version: "0.2.1",
      tag: "v0.2.1",
      discoveredFrom: "oss",
    });
    expect(parseOssLatestManifest({ ...manifest, schemaVersion: 2 })).toBeNull();
    expect(parseOssLatestManifest({ ...manifest, tag: "../bad" })).toBeNull();
    expect(
      parseOssLatestManifest({ ...manifest, releaseBaseUrl: "https://example.com/v0.2.1" }),
    ).toBeNull();
  });

  it("auto discovers latest through OSS without touching GitHub when metadata is valid", async () => {
    const calls: string[] = [];
    const fetcher = async (url: string) => {
      calls.push(url);
      return new Response(JSON.stringify(manifest), { status: 200 });
    };
    await expect(resolveRelease("auto", undefined, t, fetcher)).resolves.toMatchObject({
      tag: "v0.2.1",
      discoveredFrom: "oss",
    });
    expect(calls).toEqual([`${ossOrigin}/latest.json`]);
  });

  it("auto falls back to GitHub discovery when OSS metadata is unavailable", async () => {
    const calls: string[] = [];
    const fetcher = async (url: string) => {
      calls.push(url);
      if (url === `${ossOrigin}/latest.json`) return new Response("unavailable", { status: 503 });
      return new Response(JSON.stringify({ tag_name: "v0.2.2" }), { status: 200 });
    };
    await expect(resolveRelease("auto", undefined, t, fetcher)).resolves.toEqual({
      version: "0.2.2",
      tag: "v0.2.2",
      discoveredFrom: "github",
    });
    expect(calls).toEqual([`${ossOrigin}/latest.json`, githubApi]);
  });

  it("forced oss is strict, while forced github skips OSS", async () => {
    const ossCalls: string[] = [];
    const unavailable = async (url: string) => {
      ossCalls.push(url);
      return new Response("unavailable", { status: 503 });
    };
    await expect(resolveRelease("oss", undefined, t, unavailable)).rejects.toThrow(
      t.update.ossUnavailable(),
    );
    expect(ossCalls).toEqual([`${ossOrigin}/latest.json`]);

    const githubCalls: string[] = [];
    const github = async (url: string) => {
      githubCalls.push(url);
      return new Response(JSON.stringify({ tag_name: "v0.2.2" }), { status: 200 });
    };
    await expect(resolveRelease("github", undefined, t, github)).resolves.toMatchObject({
      tag: "v0.2.2",
      discoveredFrom: "github",
    });
    expect(githubCalls).toEqual([githubApi]);
  });

  it("a requested release skips discovery and produces same-tag source candidates", async () => {
    let fetched = false;
    const shouldNotFetch = async () => {
      fetched = true;
      throw new Error("unexpected fetch");
    };
    const release = await resolveRelease("auto", "0.2.0", t, shouldNotFetch);
    expect(fetched).toBe(false);
    expect(installerCandidates("auto", release)).toEqual([
      {
        source: "oss",
        baseUrl: `${ossOrigin}/releases/v0.2.0`,
        url: `${ossOrigin}/releases/v0.2.0/install.sh`,
        fallbackBaseUrl: "https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.2.0",
      },
      {
        source: "github",
        baseUrl: "https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.2.0",
        url: "https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.2.0/install.sh",
      },
    ]);
  });

  it("auto stays on GitHub when OSS latest discovery failed", () => {
    const release = {
      version: "0.2.2",
      tag: "v0.2.2",
      discoveredFrom: "github" as const,
    };
    expect(installerCandidates("auto", release).map((candidate) => candidate.source)).toEqual([
      "github",
    ]);
    expect(installerCandidates("github", release).map((candidate) => candidate.source)).toEqual([
      "github",
    ]);
    expect(installerCandidates("oss", release).map((candidate) => candidate.source)).toEqual([
      "oss",
    ]);
  });

  it("installer transport failure falls back to the matching GitHub tag", async () => {
    const release = parseOssLatestManifest(manifest);
    expect(release).not.toBeNull();
    const candidates = installerCandidates("auto", release!);
    const calls: string[] = [];
    const fetcher = async (url: string) => {
      calls.push(url);
      return url.includes("aliyuncs.com")
        ? new Response("unavailable", { status: 503 })
        : new Response("#!/bin/sh\n", { status: 200 });
    };
    const downloaded = await downloadInstaller(candidates, fetcher);
    expect(downloaded?.candidate.source).toBe("github");
    expect(downloaded?.script).toBe("#!/bin/sh\n");
    expect(calls).toEqual(candidates.map((candidate) => candidate.url));
  });
});

describe("buildInstallerInvocation (preserves the shape of the install being upgraded)", () => {
  const base = {
    scriptPath: "/tmp/penguin-install-1.sh",
    defaultInstallDir: "/home/me/.penguin",
  };

  it("default dir + bundled runtime: no flags, no env", () => {
    expect(
      buildInstallerInvocation({
        ...base,
        installDir: "/home/me/.penguin",
        hasBundledNode: true,
      }),
    ).toEqual({ args: ["/tmp/penguin-install-1.sh"], env: {} });
  });

  it("no bundled node/ means the universal package: pass --universal so the runtime is not silently added", () => {
    expect(
      buildInstallerInvocation({
        ...base,
        installDir: "/home/me/.penguin",
        hasBundledNode: false,
      }),
    ).toEqual({ args: ["/tmp/penguin-install-1.sh", "--universal"], env: {} });
  });

  it("a non-default install dir is passed through, or the upgrade would relocate the install", () => {
    expect(
      buildInstallerInvocation({
        ...base,
        installDir: "/opt/penguin",
        hasBundledNode: true,
      }),
    ).toEqual({
      args: ["/tmp/penguin-install-1.sh"],
      env: { PENGUIN_INSTALL_DIR: "/opt/penguin" },
    });
  });

  it("a pinned version becomes PENGUIN_VERSION, always v-prefixed for the release tag", () => {
    expect(
      buildInstallerInvocation({
        ...base,
        installDir: "/home/me/.penguin",
        hasBundledNode: true,
        version: "0.1.2",
      }).env,
    ).toEqual({ PENGUIN_VERSION: "v0.1.2" });
    expect(
      buildInstallerInvocation({
        ...base,
        installDir: "/home/me/.penguin",
        hasBundledNode: true,
        version: "v0.1.2",
      }).env,
    ).toEqual({ PENGUIN_VERSION: "v0.1.2" });
  });

  it("all three at once: custom dir, universal package, pinned version", () => {
    expect(
      buildInstallerInvocation({
        ...base,
        installDir: "/opt/penguin",
        hasBundledNode: false,
        version: "v0.2.0",
      }),
    ).toEqual({
      args: ["/tmp/penguin-install-1.sh", "--universal"],
      env: { PENGUIN_INSTALL_DIR: "/opt/penguin", PENGUIN_VERSION: "v0.2.0" },
    });
  });

  it("pins the selected payload source and same-version fallback for the child installer", () => {
    expect(
      buildInstallerInvocation({
        ...base,
        installDir: "/home/me/.penguin",
        hasBundledNode: true,
        version: "0.2.0",
        downloadBaseUrl:
          "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.2.0",
        downloadFallbackBaseUrl:
          "https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.2.0",
      }).env,
    ).toEqual({
      PENGUIN_VERSION: "v0.2.0",
      PENGUIN_DOWNLOAD_BASE_URL:
        "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.2.0",
      PENGUIN_DOWNLOAD_FALLBACK_BASE_URL:
        "https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.2.0",
    });
  });

  it("explicitly clears an inherited fallback when the selected source has none", () => {
    expect(
      buildInstallerInvocation({
        ...base,
        installDir: "/home/me/.penguin",
        hasBundledNode: true,
        version: "0.2.0",
        downloadBaseUrl: "https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.2.0",
        downloadFallbackBaseUrl: "",
      }).env,
    ).toEqual({
      PENGUIN_VERSION: "v0.2.0",
      PENGUIN_DOWNLOAD_BASE_URL:
        "https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.2.0",
      PENGUIN_DOWNLOAD_FALLBACK_BASE_URL: "",
    });
  });

  it("localizes the source list and connector in installer download failures", () => {
    expect(getMessages("en").update.installerFetchFailed(["oss", "github"])).toBe(
      "Could not download the installer from the OSS mirror or GitHub. Check your network and retry.",
    );
    expect(getMessages("zh").update.installerFetchFailed(["oss"])).toBe(
      "无法从 OSS 镜像下载安装脚本。请检查网络后重试。",
    );
    expect(getMessages("zh").update.installerFetchFailed(["github"])).toBe(
      "无法从 GitHub 下载安装脚本。请检查网络后重试。",
    );
    expect(getMessages("zh").update.installerFetchFailed(["oss", "github"])).toBe(
      "无法从 OSS 镜像或 GitHub 下载安装脚本。请检查网络后重试。",
    );
    expect(getMessages("zh").update.installerFetchFailed(["configured"])).toBe(
      "无法从配置的镜像下载安装脚本。请检查网络后重试。",
    );
  });
});

describe("planUpdate (what the command decides before it touches anything)", () => {
  const base = {
    current: "0.1.1",
    target: "0.1.2",
    modulePath: "/home/me/.penguin/lib/dist/index.js",
    platform: "linux",
    defaultInstallDir: "/home/me/.penguin",
  };
  const tarball = { kind: "tarball", installDir: "/home/me/.penguin" } as const;
  const npmGlobal = { kind: "npm", globalRoot: "/usr/local/lib/node_modules" } as const;

  it("--check reports the comparison and never upgrades, even when one is available", () => {
    expect(planUpdate({ ...base, check: true, install: tarball })).toEqual({
      action: "report",
      current: "0.1.1",
      target: "0.1.2",
      comparison: 1,
    });
  });

  it("--check reports a downgrade and an up-to-date install without upgrading either", () => {
    expect(planUpdate({ ...base, target: "0.1.0", check: true, install: tarball })).toMatchObject({
      action: "report",
      comparison: -1,
    });
    expect(planUpdate({ ...base, target: "0.1.1", check: true, install: tarball })).toMatchObject({
      action: "report",
      comparison: 0,
    });
  });

  it("--check reports even from a source checkout, where an upgrade would be refused", () => {
    // Reporting changes nothing, so the layout must not gate it.
    expect(planUpdate({ ...base, check: true, install: { kind: "source" } }).action).toBe("report");
  });

  it("an install already on the target exits without touching anything", () => {
    expect(planUpdate({ ...base, target: "0.1.1", install: tarball })).toEqual({
      action: "up-to-date",
      current: "0.1.1",
    });
    // Decided before the layout matters: even an unknown install is simply up to date.
    expect(planUpdate({ ...base, target: "v0.1.1", install: { kind: "unknown" } }).action).toBe(
      "up-to-date",
    );
  });

  it("a source checkout is refused, because overwriting a working tree destroys work", () => {
    expect(planUpdate({ ...base, install: { kind: "source" } })).toEqual({
      action: "refuse",
      reason: "source",
    });
  });

  it("an unrecognised layout is refused and names the path it was running from", () => {
    expect(
      planUpdate({ ...base, modulePath: "/random/place/index.js", install: { kind: "unknown" } }),
    ).toEqual({
      action: "refuse",
      reason: "unknown-install",
      modulePath: "/random/place/index.js",
    });
  });

  it("a global install upgrades through its own manager", () => {
    expect(planUpdate({ ...base, install: npmGlobal })).toEqual({
      action: "npm",
      manager: "npm",
      command: "npm",
      args: ["install", "-g", "@prismshadow/penguin-cli@0.1.2"],
    });
  });

  it("a global install with an unidentifiable manager is refused rather than guessed", () => {
    expect(planUpdate({ ...base, install: { kind: "npm", globalRoot: "/weird/place" } })).toEqual({
      action: "refuse",
      reason: "unknown-manager",
      globalRoot: "/weird/place",
      target: "0.1.2",
    });
  });

  it("a tarball install re-runs the installer at the dir it was detected in", () => {
    expect(planUpdate({ ...base, install: tarball })).toEqual({
      action: "tarball",
      installDir: "/home/me/.penguin",
    });
    expect(
      planUpdate({ ...base, install: { kind: "tarball", installDir: "/opt/penguin" } }),
    ).toEqual({ action: "tarball", installDir: "/opt/penguin" });
    // No installDir on the info (shouldn't happen, but the fallback is the default install dir).
    expect(planUpdate({ ...base, install: { kind: "tarball" } })).toEqual({
      action: "tarball",
      installDir: "/home/me/.penguin",
    });
  });

  it("Windows is refused on the tarball path: the installer is a POSIX shell script", () => {
    expect(planUpdate({ ...base, platform: "win32", install: tarball })).toEqual({
      action: "refuse",
      reason: "windows-installer",
    });
  });

  it("Windows is refused on the npm path too, handing over the exact command to run", () => {
    // spawn() cannot run a .cmd shim without a shell, so this must not reach the spawn and
    // fail with a generic message.
    expect(planUpdate({ ...base, platform: "win32", install: npmGlobal })).toEqual({
      action: "refuse",
      reason: "windows-global",
      command: "npm install -g @prismshadow/penguin-cli@0.1.2",
    });
  });

  it("every refusal has a message in both languages", () => {
    const plans = [
      planUpdate({ ...base, install: { kind: "source" } }),
      planUpdate({ ...base, install: { kind: "unknown" } }),
      planUpdate({ ...base, install: { kind: "npm", globalRoot: "/weird/place" } }),
      planUpdate({ ...base, platform: "win32", install: tarball }),
      planUpdate({ ...base, platform: "win32", install: npmGlobal }),
    ];
    for (const lang of ["en", "zh"] as const) {
      const t = getMessages(lang);
      expect(plans.map((p) => p.action)).toEqual(Array(plans.length).fill("refuse"));
      expect(t.update.sourceCheckout()).toBeTruthy();
      expect(t.update.unknownInstall("/x")).toContain("/x");
      expect(t.update.npmUnknownManager("/weird/place", "0.1.2")).toContain("/weird/place");
      expect(t.update.windowsUnsupported()).toBeTruthy();
      // The Windows global-install message is only useful if it carries the command verbatim.
      expect(t.update.windowsGlobalInstall("pnpm add -g pkg@1")).toContain("pnpm add -g pkg@1");
    }
  });
});

describe("confirmationMode (the gate in front of every upgrade)", () => {
  it("--yes proceeds without a prompt, interactive or not", () => {
    expect(confirmationMode(true, true)).toBe("proceed");
    expect(confirmationMode(true, false)).toBe("proceed");
  });
  it("a non-TTY without --yes demands --yes instead of hanging on a prompt nobody can answer", () => {
    expect(confirmationMode(undefined, false)).toBe("needs-yes");
    expect(confirmationMode(false, false)).toBe("needs-yes");
    expect(getMessages("en").update.needsYes()).toContain("--yes");
    expect(getMessages("zh").update.needsYes()).toContain("--yes");
  });
  it("a terminal without --yes gets the interactive prompt", () => {
    expect(confirmationMode(undefined, true)).toBe("prompt");
  });
});

describe("command registration", () => {
  it("registers `update` with its three options in both languages", () => {
    for (const lang of ["en", "zh"] as const) {
      const program = new Command();
      registerUpdateCommand(program, getMessages(lang));
      const cmd = program.commands.find((c) => c.name() === "update");
      expect(cmd).toBeDefined();
      const flags = cmd?.options.map((o) => o.flags) ?? [];
      expect(flags).toContain("--check");
      expect(flags).toContain("--release <tag>");
      expect(flags).toContain("-y, --yes");
      expect(cmd?.description()).toBeTruthy();
    }
  });
});
