/**
 * tool-call-card.tsx preview helpers: previewArguments keeps the real arguments (what heads
 * the approval row), headerSubtitle surfaces the model-written `description` argument for
 * the command/subagent tools and the shortened file path for the file tools, and
 * pendingFilePayload decodes the file-tool arguments so a pending approval shows the actual
 * rewrite. All must tolerate incomplete mid-stream JSON; headerSubtitle additionally holds a
 * still-streaming field back until its closing quote so the header never jitters (#137).
 */
import { describe, expect, it } from "vitest";
import {
  headerSubtitle,
  pendingFilePayload,
  previewArguments,
  shortenPath,
} from "../src/features/chat/tool-call-card";

describe("previewArguments", () => {
  it("renders exec_command as $ <cmd>", () => {
    expect(previewArguments("exec_command", '{"cmd":"ls -la"}')).toBe("$ ls -la");
    // Mid-stream (incomplete JSON) still extracts the cmd prefix.
    expect(previewArguments("exec_command", '{"cmd":"echo h')).toBe("$ echo h");
  });

  it("renders the file tools by their shortened file_path", () => {
    expect(previewArguments("read_file", '{"file_path":"src/app.py","offset":3}')).toBe(
      "src/app.py",
    );
    expect(
      previewArguments("edit_file", '{"file_path":"a.txt","old_string":"x","new_string":"y"}'),
    ).toBe("a.txt");
    expect(previewArguments("write_file", '{"file_path":"packages/core/src/state/out.ts"}')).toBe(
      "…/state/out.ts",
    );
  });

  it("keeps the real command even when a description argument is present (approval fidelity)", () => {
    expect(
      previewArguments("exec_command", '{"cmd":"rm -rf build","description":"Clean caches"}'),
    ).toBe("$ rm -rf build");
  });

  it("falls back to the single-line raw arguments for other tools", () => {
    expect(previewArguments("search", '{"q": "a\n b"}')).toBe('{"q": "a b"}');
  });
});

describe("shortenPath", () => {
  it("keeps at most one parent directory plus the filename", () => {
    expect(shortenPath("file.ts")).toBe("file.ts");
    expect(shortenPath("src/file.ts")).toBe("src/file.ts");
    expect(shortenPath("/etc/hosts")).toBe("/etc/hosts");
    expect(shortenPath("packages/core/src/state/default-config.ts")).toBe(
      "…/state/default-config.ts",
    );
  });
});

describe("headerSubtitle", () => {
  it("shows the description for the command/subagent tools when present", () => {
    expect(
      headerSubtitle("exec_command", '{"cmd":"ls","description":"List workspace files"}'),
    ).toBe("List workspace files");
    expect(
      headerSubtitle("run_subagent", '{"prompt":"p","description":"Delegating research"}'),
    ).toBe("Delegating research");
    expect(
      headerSubtitle("input_command", '{"process_id":"proc-1","description":"Poll the build"}'),
    ).toBe("Poll the build");
    expect(
      headerSubtitle("input_subagent", '{"subagent_id":"s-1","description":"Follow up"}'),
    ).toBe("Follow up");
  });

  it("is null when the description is absent or empty (call_description off = the model never sends it)", () => {
    expect(headerSubtitle("exec_command", '{"cmd":"ls"}')).toBeNull();
    expect(headerSubtitle("exec_command", '{"cmd":"ls","description":""}')).toBeNull();
  });

  it("shows the shortened file path for the file tools and nothing for others", () => {
    expect(headerSubtitle("read_file", '{"file_path":"src/app.py"}')).toBe("src/app.py");
    expect(headerSubtitle("edit_file", '{"file_path":"packages/web/src/a.tsx"}')).toBe(
      "…/src/a.tsx",
    );
    expect(headerSubtitle("write_file", '{"file_path":"out.md"}')).toBe("out.md");
    expect(headerSubtitle("search", '{"q":"x"}')).toBeNull();
  });

  it("folds a multi-line description to one line", () => {
    expect(headerSubtitle("exec_command", '{"cmd":"ls","description":"one\\ntwo"}')).toBe(
      "one two",
    );
  });

  it("holds a still-streaming description back until its closing quote (#137)", () => {
    expect(headerSubtitle("exec_command", '{"description":"Read the con', false)).toBeNull();
    // Closing quote arrived: renders even though later arguments are still streaming.
    expect(
      headerSubtitle("exec_command", '{"description":"Read the config","cmd":"cat co', false),
    ).toBe("Read the config");
  });

  it("holds a still-streaming file path back (shortenPath would rewrite non-monotonically)", () => {
    expect(headerSubtitle("read_file", '{"file_path":"/home/us', false)).toBeNull();
    expect(headerSubtitle("write_file", '{"file_path":"/a/b/c.txt","content":"xx', false)).toBe(
      "…/b/c.txt",
    );
  });

  it("renders whatever is there once the arguments settled, even unterminated (aborted call)", () => {
    expect(headerSubtitle("exec_command", '{"description":"half', true)).toBe("half");
    expect(headerSubtitle("read_file", '{"file_path":"/a/b/c.txt', true)).toBe("…/b/c.txt");
  });

  it("prefers a complete description over the file path when a file tool carries one (user-enabled schema)", () => {
    expect(
      headerSubtitle("read_file", '{"description":"Check the config","file_path":"/a/b/c.ts"}'),
    ).toBe("Check the config");
    // Description still streaming: nothing renders yet — no path-then-description swap.
    expect(headerSubtitle("read_file", '{"description":"Check the co', false)).toBeNull();
    // Empty description falls back to the path.
    expect(headerSubtitle("read_file", '{"description":"","file_path":"/a/b/c.ts"}')).toBe(
      "…/b/c.ts",
    );
  });
});

describe("pendingFilePayload", () => {
  it("decodes the edit_file rewrite so the approval block shows it", () => {
    const payload = pendingFilePayload(
      "edit_file",
      JSON.stringify({ file_path: "src/app.py", old_string: "a\nb", new_string: "a\nc" }),
    );
    expect(payload).toBe("file_path: src/app.py\nold_string:\na\nb\nnew_string:\na\nc");
  });

  it("decodes write_file content and read_file window arguments", () => {
    expect(pendingFilePayload("write_file", '{"file_path":"out.md","content":"hello"}')).toBe(
      "file_path: out.md\ncontent: hello",
    );
    expect(pendingFilePayload("read_file", '{"file_path":"a.txt","offset":3,"limit":5}')).toBe(
      "file_path: a.txt\noffset: 3\nlimit: 5",
    );
  });

  it("returns null for non-file tools and incomplete JSON", () => {
    expect(pendingFilePayload("exec_command", '{"cmd":"ls"}')).toBeNull();
    expect(pendingFilePayload("edit_file", '{"file_path":"a.txt","old_str')).toBeNull();
  });
});
