import {
  clearJSONValues,
  createEmptyJSONStructure,
  expandStringifiedJSON,
  filterFlatJSONEntries,
  flattenObject,
  formatContentAsString,
  formatFlatJSONValue,
  hasStringifiedJSON,
  isJSONObjectString,
  isPlainObject,
  jsonStringToFlatObject,
  safelyJSONStringify,
  safelyParseJSONObjectString,
  safelyParseJSONString,
  toFlatJSONEntries,
  unnestSingleStringValue,
} from "../jsonUtils";

describe("isJSONObjectString", () => {
  it("detects invalid JSON and JSON that's not objects", () => {
    expect(isJSONObjectString("")).toEqual(false);
    expect(isJSONObjectString("123")).toEqual(false);
    expect(isJSONObjectString("[]")).toEqual(false);
    expect(isJSONObjectString("[1, 2, 3]")).toEqual(false);
    expect(isJSONObjectString("{")).toEqual(false);
    expect(isJSONObjectString("{]")).toEqual(false);
    expect(isJSONObjectString("{1: 2}")).toEqual(false);
    expect(isJSONObjectString("{1, 2}")).toEqual(false);
    expect(isJSONObjectString("{1: 2, 3}")).toEqual(false);
  });
  it("detects valid JSON objects", () => {
    expect(isJSONObjectString("{}")).toEqual(true);
    expect(isJSONObjectString('{"a": 1}')).toEqual(true);
    expect(isJSONObjectString('{"a": "b"}')).toEqual(true);
    expect(isJSONObjectString('{"a": {"b": 1}}')).toEqual(true);
    expect(isJSONObjectString('{"a": [1, 2, 3]}')).toEqual(true);
  });
});

describe("flattenObject", () => {
  describe("basic flattening", () => {
    it("returns empty object for empty input", () => {
      expect(flattenObject({ obj: {} })).toEqual({});
    });

    it("flattens simple objects with no nesting", () => {
      expect(flattenObject({ obj: { a: 1, b: 2 } })).toEqual({ a: 1, b: 2 });
    });

    it("flattens single-level nested objects", () => {
      expect(flattenObject({ obj: { a: { b: 1 } } })).toEqual({ "a.b": 1 });
    });

    it("flattens deeply nested objects", () => {
      expect(flattenObject({ obj: { a: { b: { c: { d: 1 } } } } })).toEqual({
        "a.b.c.d": 1,
      });
    });

    it("flattens objects with multiple branches", () => {
      expect(
        flattenObject({
          obj: {
            a: { b: 1 },
            c: { d: 2 },
            e: 3,
          },
        })
      ).toEqual({
        "a.b": 1,
        "c.d": 2,
        e: 3,
      });
    });

    it("handles various primitive types as values", () => {
      expect(
        flattenObject({
          obj: {
            num: 42,
            str: "hello",
            bool: true,
            falseBool: false,
            zero: 0,
          },
        })
      ).toEqual({
        num: 42,
        str: "hello",
        bool: true,
        falseBool: false,
        zero: 0,
      });
    });
  });

  describe("separator parameter", () => {
    it("uses default dot separator", () => {
      expect(flattenObject({ obj: { a: { b: 1 } } })).toEqual({ "a.b": 1 });
    });

    it("uses custom separator when provided", () => {
      expect(flattenObject({ obj: { a: { b: 1 } }, separator: "/" })).toEqual({
        "a/b": 1,
      });
    });

    it("uses underscore separator", () => {
      expect(flattenObject({ obj: { a: { b: 1 } }, separator: "_" })).toEqual({
        a_b: 1,
      });
    });

    it("uses multi-character separator", () => {
      expect(flattenObject({ obj: { a: { b: 1 } }, separator: "->" })).toEqual({
        "a->b": 1,
      });
    });

    it("applies separator throughout deep nesting", () => {
      expect(
        flattenObject({
          obj: { a: { b: { c: { d: 1 } } } },
          separator: "::",
        })
      ).toEqual({
        "a::b::c::d": 1,
      });
    });
  });

  describe("parentKey parameter", () => {
    it("uses no parent key by default", () => {
      expect(flattenObject({ obj: { a: 1 } })).toEqual({ a: 1 });
    });

    it("prepends parent key to all keys", () => {
      expect(flattenObject({ obj: { a: 1, b: 2 }, parentKey: "root" })).toEqual(
        {
          "root.a": 1,
          "root.b": 2,
        }
      );
    });

    it("combines parent key with nested objects", () => {
      expect(
        flattenObject({ obj: { a: { b: 1 } }, parentKey: "prefix" })
      ).toEqual({
        "prefix.a.b": 1,
      });
    });

    it("uses parent key with custom separator", () => {
      expect(
        flattenObject({
          obj: { a: { b: 1 } },
          parentKey: "root",
          separator: "/",
        })
      ).toEqual({
        "root/a/b": 1,
      });
    });
  });

  describe("keepNonTerminalValues parameter", () => {
    it("excludes non-terminal values by default", () => {
      const result = flattenObject({ obj: { a: { b: 1 } } });
      expect(result).toEqual({ "a.b": 1 });
      expect(result).not.toHaveProperty("a");
    });

    it("keeps non-terminal values when enabled", () => {
      const result = flattenObject({
        obj: { a: { b: 1 } },
        keepNonTerminalValues: true,
      });
      expect(result).toEqual({
        a: { b: 1 },
        "a.b": 1,
      });
    });

    it("keeps all intermediate values in deep nesting", () => {
      const result = flattenObject({
        obj: { a: { b: { c: 1 } } },
        keepNonTerminalValues: true,
      });
      expect(result).toEqual({
        a: { b: { c: 1 } },
        "a.b": { c: 1 },
        "a.b.c": 1,
      });
    });

    it("keeps non-terminal values for multiple branches", () => {
      const result = flattenObject({
        obj: {
          x: { y: 1 },
          a: { b: { c: 2 } },
        },
        keepNonTerminalValues: true,
      });
      expect(result).toEqual({
        x: { y: 1 },
        "x.y": 1,
        a: { b: { c: 2 } },
        "a.b": { c: 2 },
        "a.b.c": 2,
      });
    });

    it("keeps non-terminal array values when enabled", () => {
      const result = flattenObject({
        obj: { items: [1, 2] },
        keepNonTerminalValues: true,
      });
      expect(result["items"]).toEqual([1, 2]);
      expect(result["items.0"]).toBe(1);
      expect(result["items.1"]).toBe(2);
    });

    it("combines keepNonTerminalValues with custom separator", () => {
      const result = flattenObject({
        obj: { a: { b: 1 } },
        keepNonTerminalValues: true,
        separator: "/",
      });
      expect(result).toEqual({
        a: { b: 1 },
        "a/b": 1,
      });
    });
  });

  describe("formatIndices parameter", () => {
    it("uses dot notation for array indices by default", () => {
      expect(flattenObject({ obj: { items: [1, 2, 3] } })).toEqual({
        "items.0": 1,
        "items.1": 2,
        "items.2": 3,
      });
    });

    it("uses bracket notation when formatIndices is true", () => {
      expect(
        flattenObject({ obj: { items: [1, 2, 3] }, formatIndices: true })
      ).toEqual({
        "items[0]": 1,
        "items[1]": 2,
        "items[2]": 3,
      });
    });

    it("formats indices in nested arrays", () => {
      expect(
        flattenObject({
          obj: { outer: { inner: ["a", "b"] } },
          formatIndices: true,
        })
      ).toEqual({
        "outer.inner[0]": "a",
        "outer.inner[1]": "b",
      });
    });

    it("formats indices in array of objects", () => {
      expect(
        flattenObject({
          obj: { users: [{ name: "Alice" }, { name: "Bob" }] },
          formatIndices: true,
        })
      ).toEqual({
        "users[0].name": "Alice",
        "users[1].name": "Bob",
      });
    });

    it("handles nested arrays with formatIndices", () => {
      expect(
        flattenObject({
          obj: {
            matrix: [
              [1, 2],
              [3, 4],
            ],
          },
          formatIndices: true,
        })
      ).toEqual({
        "matrix[0][0]": 1,
        "matrix[0][1]": 2,
        "matrix[1][0]": 3,
        "matrix[1][1]": 4,
      });
    });

    it("combines formatIndices with custom separator", () => {
      expect(
        flattenObject({
          obj: { data: { items: [1, 2] } },
          formatIndices: true,
          separator: "/",
        })
      ).toEqual({
        "data/items[0]": 1,
        "data/items[1]": 2,
      });
    });

    it("combines formatIndices with keepNonTerminalValues", () => {
      const result = flattenObject({
        obj: { items: [{ a: 1 }] },
        formatIndices: true,
        keepNonTerminalValues: true,
      });
      expect(result).toEqual({
        items: [{ a: 1 }],
        "items[0]": { a: 1 },
        "items[0].a": 1,
      });
    });

    it("handles empty arrays", () => {
      expect(
        flattenObject({ obj: { empty: [] }, formatIndices: true })
      ).toEqual({});
    });

    it("handles top-level array", () => {
      expect(
        flattenObject({
          obj: ["first", "second"],
          formatIndices: true,
        })
      ).toEqual({
        "[0]": "first",
        "[1]": "second",
      });
    });
  });

  describe("complex combinations", () => {
    it("combines all parameters together", () => {
      const result = flattenObject({
        obj: { data: [{ value: 1 }] },
        parentKey: "root",
        separator: "/",
        keepNonTerminalValues: true,
        formatIndices: true,
      });
      expect(result).toEqual({
        "root/data": [{ value: 1 }],
        "root/data[0]": { value: 1 },
        "root/data[0]/value": 1,
      });
    });

    it("handles mixed objects and arrays deeply nested", () => {
      const result = flattenObject({
        obj: {
          config: {
            servers: [
              { host: "localhost", ports: [8080, 8443] },
              { host: "remote", ports: [9090] },
            ],
          },
        },
        formatIndices: true,
      });
      expect(result).toEqual({
        "config.servers[0].host": "localhost",
        "config.servers[0].ports[0]": 8080,
        "config.servers[0].ports[1]": 8443,
        "config.servers[1].host": "remote",
        "config.servers[1].ports[0]": 9090,
      });
    });
  });

  describe("edge cases", () => {
    it("handles null values as terminal values", () => {
      // Note: null is typeof "object" but should be treated as terminal
      // Looking at the implementation, null will be treated as a terminal value
      // because the check is `value && typeof value === "object"`
      expect(flattenObject({ obj: { a: null } })).toEqual({ a: null });
    });

    it("handles objects with numeric string keys", () => {
      expect(flattenObject({ obj: { "0": "zero", "1": "one" } })).toEqual({
        "0": "zero",
        "1": "one",
      });
    });

    it("handles objects with special characters in keys", () => {
      expect(
        flattenObject({ obj: { "key.with.dots": { nested: 1 } } })
      ).toEqual({
        "key.with.dots.nested": 1,
      });
    });

    it("handles empty nested objects", () => {
      expect(flattenObject({ obj: { a: {} } })).toEqual({});
    });

    it("handles empty nested objects with keepNonTerminalValues", () => {
      expect(
        flattenObject({ obj: { a: {} }, keepNonTerminalValues: true })
      ).toEqual({
        a: {},
      });
    });
  });
});

describe("jsonStringToFlatObject", () => {
  it("flattens objects", () => {
    expect(jsonStringToFlatObject("{}")).toEqual({});
    expect(jsonStringToFlatObject('{"a": 1}')).toEqual({ a: 1 });
    expect(jsonStringToFlatObject('{"a": {"b": 1}}')).toEqual({ "a.b": 1 });
    expect(jsonStringToFlatObject('{"a": {"b": {"c": 1}}}')).toEqual({
      "a.b.c": 1,
    });
    expect(jsonStringToFlatObject('{"a": {"b": 1}, "c": 2}')).toEqual({
      "a.b": 1,
      c: 2,
    });
  });
});

describe("formatContentAsString", () => {
  it("should return unknown json content as a string", () => {
    const content = "Hello, world!";
    expect(formatContentAsString(content)).toBe('"Hello, world!"');
    const content2 = ".123";
    expect(formatContentAsString(content2)).toBe('".123"');
    const content3 = "True";
    expect(formatContentAsString(content3)).toBe('"True"');
    const content4 = "False";
    expect(formatContentAsString(content4)).toBe('"False"');
    const content5 = "Null";
    expect(formatContentAsString(content5)).toBe('"Null"');
    const content6 = "a";
    expect(formatContentAsString(content6)).toBe('"a"');
    const content7 = "u";
    expect(formatContentAsString(content7)).toBe('"u"');
  });

  it("should return the content as a stringified JSON with pretty printing if it is an object", () => {
    const content = { foo: "bar" };
    expect(formatContentAsString(content)).toBe(
      JSON.stringify(content, null, 2)
    );
  });

  it("should return the content as a string if it is a number", () => {
    const content = 123;
    expect(formatContentAsString(content)).toBe("123");
    const content2 = 123.456;
    expect(formatContentAsString(content2)).toBe("123.456");
    const content3 = -123.456;
    expect(formatContentAsString(content3)).toBe("-123.456");
    const content4 = 0;
    expect(formatContentAsString(content4)).toBe("0");
    const content6 = 0.5;
    expect(formatContentAsString(content6)).toBe("0.5");
  });

  it("should return the content as a string if it is a boolean", () => {
    const content = true;
    expect(formatContentAsString(content)).toBe("true");
    const content2 = false;
    expect(formatContentAsString(content2)).toBe("false");
  });

  it("should return the content as a string if it is null", () => {
    const content = null;
    expect(formatContentAsString(content)).toBe("null");
  });

  it("should return the content as a string if it is an array", () => {
    const content = [1, "2", 3, { foo: "bar" }];
    expect(formatContentAsString(content)).toBe(
      `[
  1,
  "2",
  3,
  {
    "foo": "bar"
  }
]`
    );
  });

  it("should handle double quoted strings", () => {
    const content = `"\\"Hello, world!\\""`;
    expect(formatContentAsString(content)).toBe(`"Hello, world!"`);
  });

  it("should return 'undefined' when content is undefined", () => {
    expect(formatContentAsString(undefined)).toBe("undefined");
    expect(formatContentAsString(undefined, { unquotePlainString: true })).toBe(
      "undefined"
    );
  });
});
type SafelyJSONStringifyFixture = {
  name: string;
  input: unknown;
  expected: string | undefined;
};

const safelyJSONStringifyFixtures: SafelyJSONStringifyFixture[] = [
  // Nullish values return undefined
  {
    name: "returns undefined for null",
    input: null,
    expected: undefined,
  },
  {
    name: "returns undefined for undefined",
    input: undefined,
    expected: undefined,
  },

  // Primitive values
  {
    name: "stringifies strings",
    input: "hello",
    expected: '"hello"',
  },
  {
    name: "stringifies empty strings",
    input: "",
    expected: '""',
  },
  {
    name: "stringifies numbers",
    input: 42,
    expected: "42",
  },
  {
    name: "stringifies zero",
    input: 0,
    expected: "0",
  },
  {
    name: "stringifies negative numbers",
    input: -3.14,
    expected: "-3.14",
  },
  {
    name: "stringifies true",
    input: true,
    expected: "true",
  },
  {
    name: "stringifies false",
    input: false,
    expected: "false",
  },

  // Objects
  {
    name: "stringifies empty objects",
    input: {},
    expected: "{}",
  },
  {
    name: "stringifies simple objects",
    input: { a: 1, b: "two" },
    expected: '{"a":1,"b":"two"}',
  },
  {
    name: "stringifies nested objects",
    input: { outer: { inner: { deep: "value" } } },
    expected: '{"outer":{"inner":{"deep":"value"}}}',
  },
  {
    name: "stringifies objects with null values",
    input: { a: null, b: "value" },
    expected: '{"a":null,"b":"value"}',
  },

  // Arrays
  {
    name: "stringifies empty arrays",
    input: [],
    expected: "[]",
  },
  {
    name: "stringifies arrays of primitives",
    input: [1, 2, 3],
    expected: "[1,2,3]",
  },
  {
    name: "stringifies arrays of objects",
    input: [{ id: 1 }, { id: 2 }],
    expected: '[{"id":1},{"id":2}]',
  },
  {
    name: "stringifies mixed arrays",
    input: [1, "two", true, null, { a: 1 }],
    expected: '[1,"two",true,null,{"a":1}]',
  },

  // Edge cases that JSON.stringify handles
  {
    name: "stringifies NaN as null",
    input: NaN,
    expected: "null",
  },
  {
    name: "stringifies Infinity as null",
    input: Infinity,
    expected: "null",
  },
  {
    name: "stringifies negative Infinity as null",
    input: -Infinity,
    expected: "null",
  },
];

describe("safelyJSONStringify", () => {
  describe.each(safelyJSONStringifyFixtures)("$name", ({ input, expected }) => {
    it("stringifies correctly", () => {
      expect(safelyJSONStringify(input)).toEqual(expected);
    });
  });

  describe("error handling", () => {
    it("returns undefined for circular references", () => {
      const circular: Record<string, unknown> = { a: 1 };
      circular.self = circular;
      expect(safelyJSONStringify(circular)).toBeUndefined();
    });

    it("returns undefined for BigInt values", () => {
      // BigInt cannot be serialized by JSON.stringify
      expect(safelyJSONStringify(BigInt(123))).toBeUndefined();
    });

    it("returns undefined for objects with BigInt properties", () => {
      expect(safelyJSONStringify({ value: BigInt(456) })).toBeUndefined();
    });
  });
});

type SafelyParseJSONStringFixture = {
  name: string;
  input: string;
  expected: unknown;
};

const safelyParseJSONStringFixtures: SafelyParseJSONStringFixture[] = [
  // Empty and whitespace strings return undefined
  {
    name: "returns undefined for empty string",
    input: "",
    expected: undefined,
  },
  {
    name: "returns undefined for whitespace-only string",
    input: "   ",
    expected: undefined,
  },
  {
    name: "returns undefined for tabs and newlines only",
    input: "\t\n\r",
    expected: undefined,
  },

  // Invalid JSON returns undefined
  {
    name: "returns undefined for invalid JSON",
    input: "invalid",
    expected: undefined,
  },
  {
    name: "returns undefined for unclosed brace",
    input: "{",
    expected: undefined,
  },
  {
    name: "returns undefined for malformed object",
    input: "{a: 1}",
    expected: undefined,
  },

  // Valid JSON objects
  {
    name: "parses empty object",
    input: "{}",
    expected: {},
  },
  {
    name: "parses simple object",
    input: '{"a": 1, "b": "two"}',
    expected: { a: 1, b: "two" },
  },
  {
    name: "parses nested object",
    input: '{"outer": {"inner": "value"}}',
    expected: { outer: { inner: "value" } },
  },

  // Valid JSON arrays
  {
    name: "parses empty array",
    input: "[]",
    expected: [],
  },
  {
    name: "parses array of numbers",
    input: "[1, 2, 3]",
    expected: [1, 2, 3],
  },
  {
    name: "parses array of objects",
    input: '[{"id": 1}, {"id": 2}]',
    expected: [{ id: 1 }, { id: 2 }],
  },

  // Valid JSON primitives
  {
    name: "parses number",
    input: "42",
    expected: 42,
  },
  {
    name: "parses negative number",
    input: "-3.14",
    expected: -3.14,
  },
  {
    name: "parses string",
    input: '"hello"',
    expected: "hello",
  },
  {
    name: "parses true",
    input: "true",
    expected: true,
  },
  {
    name: "parses false",
    input: "false",
    expected: false,
  },
  {
    name: "parses null",
    input: "null",
    expected: null,
  },

  // Edge cases
  {
    name: "parses JSON with leading/trailing whitespace",
    input: '  {"a": 1}  ',
    expected: { a: 1 },
  },
];

describe("safelyParseJSONString", () => {
  describe.each(safelyParseJSONStringFixtures)(
    "$name",
    ({ input, expected }) => {
      it("parses correctly", () => {
        expect(safelyParseJSONString(input)).toEqual(expected);
      });
    }
  );
});

type SafelyParseJSONObjectStringFixture = {
  name: string;
  input: string;
  expected: object | undefined;
};

const safelyParseJSONObjectStringFixtures: SafelyParseJSONObjectStringFixture[] =
  [
    // Empty and whitespace strings return undefined
    {
      name: "returns undefined for empty string",
      input: "",
      expected: undefined,
    },
    {
      name: "returns undefined for whitespace-only string",
      input: "   ",
      expected: undefined,
    },

    // Invalid JSON returns undefined
    {
      name: "returns undefined for invalid JSON",
      input: "invalid",
      expected: undefined,
    },
    {
      name: "returns undefined for malformed object",
      input: "{a: 1}",
      expected: undefined,
    },

    // Primitives return undefined (stricter than safelyParseJSONString)
    {
      name: "returns undefined for number",
      input: "42",
      expected: undefined,
    },
    {
      name: "returns undefined for string",
      input: '"hello"',
      expected: undefined,
    },
    {
      name: "returns undefined for boolean true",
      input: "true",
      expected: undefined,
    },
    {
      name: "returns undefined for boolean false",
      input: "false",
      expected: undefined,
    },
    {
      name: "returns undefined for null",
      input: "null",
      expected: undefined,
    },

    // Valid JSON objects
    {
      name: "parses empty object",
      input: "{}",
      expected: {},
    },
    {
      name: "parses simple object",
      input: '{"a": 1, "b": "two"}',
      expected: { a: 1, b: "two" },
    },
    {
      name: "parses nested object",
      input: '{"outer": {"inner": "value"}}',
      expected: { outer: { inner: "value" } },
    },
    {
      name: "parses object with null values",
      input: '{"a": null, "b": 1}',
      expected: { a: null, b: 1 },
    },

    // Valid JSON arrays (arrays are objects in JS)
    {
      name: "parses empty array",
      input: "[]",
      expected: [],
    },
    {
      name: "parses array of numbers",
      input: "[1, 2, 3]",
      expected: [1, 2, 3],
    },
    {
      name: "parses array of objects",
      input: '[{"id": 1}, {"id": 2}]',
      expected: [{ id: 1 }, { id: 2 }],
    },
    {
      name: "parses mixed array",
      input: '[1, "two", null, {"a": 1}]',
      expected: [1, "two", null, { a: 1 }],
    },

    // Edge cases
    {
      name: "parses JSON with leading/trailing whitespace",
      input: '  {"a": 1}  ',
      expected: { a: 1 },
    },
  ];

describe("safelyParseJSONObjectString", () => {
  describe.each(safelyParseJSONObjectStringFixtures)(
    "$name",
    ({ input, expected }) => {
      it("parses correctly", () => {
        expect(safelyParseJSONObjectString(input)).toEqual(expected);
      });
    }
  );
});

describe("unnestSingleStringValue", () => {
  describe("returns unnested value when object has single string key with string value", () => {
    it("unnests simple wrapped response", () => {
      const result = unnestSingleStringValue({ response: "Hello world" });
      expect(result).toEqual({ value: "Hello world", wasUnnested: true });
    });

    it("unnests with any key name", () => {
      const result = unnestSingleStringValue({ output: "Some output" });
      expect(result).toEqual({ value: "Some output", wasUnnested: true });
    });

    it("unnests empty string value", () => {
      const result = unnestSingleStringValue({ data: "" });
      expect(result).toEqual({ value: "", wasUnnested: true });
    });
  });

  describe("returns original value (wasUnnested: false) for non-unnestable values", () => {
    it("does not unnest object with multiple keys", () => {
      const input = { a: "1", b: "2" };
      const result = unnestSingleStringValue(input);
      expect(result).toEqual({ value: input, wasUnnested: false });
    });

    it("does not unnest object where value is not a string", () => {
      const input = { data: { nested: true } };
      const result = unnestSingleStringValue(input);
      expect(result).toEqual({ value: input, wasUnnested: false });
    });

    it("does not unnest object with number value", () => {
      const input = { count: 42 };
      const result = unnestSingleStringValue(input);
      expect(result).toEqual({ value: input, wasUnnested: false });
    });

    it("does not unnest object with array value", () => {
      const input = { items: ["a", "b", "c"] };
      const result = unnestSingleStringValue(input);
      expect(result).toEqual({ value: input, wasUnnested: false });
    });

    it("does not unnest object with null value", () => {
      const input = { data: null };
      const result = unnestSingleStringValue(input);
      expect(result).toEqual({ value: input, wasUnnested: false });
    });

    it("does not unnest empty object", () => {
      const input = {};
      const result = unnestSingleStringValue(input);
      expect(result).toEqual({ value: input, wasUnnested: false });
    });
  });

  describe("returns original value for non-object types", () => {
    it("returns string as-is", () => {
      const result = unnestSingleStringValue("plain string");
      expect(result).toEqual({ value: "plain string", wasUnnested: false });
    });

    it("returns number as-is", () => {
      const result = unnestSingleStringValue(42);
      expect(result).toEqual({ value: 42, wasUnnested: false });
    });

    it("returns boolean as-is", () => {
      const result = unnestSingleStringValue(true);
      expect(result).toEqual({ value: true, wasUnnested: false });
    });

    it("returns null as-is", () => {
      const result = unnestSingleStringValue(null);
      expect(result).toEqual({ value: null, wasUnnested: false });
    });

    it("returns undefined as-is", () => {
      const result = unnestSingleStringValue(undefined);
      expect(result).toEqual({ value: undefined, wasUnnested: false });
    });

    it("returns array as-is", () => {
      const input = [1, 2, 3];
      const result = unnestSingleStringValue(input);
      expect(result).toEqual({ value: input, wasUnnested: false });
    });
  });
});

describe("clearJSONValues", () => {
  describe("primitive values", () => {
    it("clears string to empty string", () => {
      expect(clearJSONValues("hello")).toBe("");
    });

    it("preserves numbers", () => {
      expect(clearJSONValues(42)).toBe(42);
      expect(clearJSONValues(0)).toBe(0);
      expect(clearJSONValues(-3.14)).toBe(-3.14);
    });

    it("preserves booleans", () => {
      expect(clearJSONValues(true)).toBe(true);
      expect(clearJSONValues(false)).toBe(false);
    });

    it("converts null to empty string", () => {
      expect(clearJSONValues(null)).toBe("");
    });

    it("converts undefined to empty string", () => {
      expect(clearJSONValues(undefined)).toBe("");
    });
  });

  describe("objects", () => {
    it("clears string values in objects", () => {
      expect(clearJSONValues({ name: "John" })).toEqual({ name: "" });
    });

    it("preserves numbers in objects", () => {
      expect(clearJSONValues({ age: 30 })).toEqual({ age: 30 });
    });

    it("preserves booleans in objects", () => {
      expect(clearJSONValues({ active: true })).toEqual({ active: true });
    });

    it("handles mixed value types", () => {
      expect(
        clearJSONValues({
          name: "John",
          age: 30,
          active: true,
          email: "test@example.com",
        })
      ).toEqual({
        name: "",
        age: 30,
        active: true,
        email: "",
      });
    });

    it("handles nested objects", () => {
      expect(
        clearJSONValues({
          user: {
            name: "John",
            address: {
              city: "NYC",
              zip: 10001,
            },
          },
        })
      ).toEqual({
        user: {
          name: "",
          address: {
            city: "",
            zip: 10001,
          },
        },
      });
    });

    it("returns empty object for empty object", () => {
      expect(clearJSONValues({})).toEqual({});
    });
  });

  describe("arrays", () => {
    it("clears string values in arrays", () => {
      expect(clearJSONValues(["a", "b", "c"])).toEqual(["", "", ""]);
    });

    it("preserves numbers in arrays", () => {
      expect(clearJSONValues([1, 2, 3])).toEqual([1, 2, 3]);
    });

    it("handles mixed value types in arrays", () => {
      expect(clearJSONValues(["hello", 42, true])).toEqual(["", 42, true]);
    });

    it("returns empty array for empty array", () => {
      expect(clearJSONValues([])).toEqual([]);
    });

    it("handles arrays of objects", () => {
      expect(
        clearJSONValues([
          { name: "John", age: 30 },
          { name: "Jane", age: 25 },
        ])
      ).toEqual([
        { name: "", age: 30 },
        { name: "", age: 25 },
      ]);
    });

    it("handles nested arrays", () => {
      expect(
        clearJSONValues([
          ["a", "b"],
          ["c", "d"],
        ])
      ).toEqual([
        ["", ""],
        ["", ""],
      ]);
    });
  });

  describe("complex structures", () => {
    it("handles deeply nested structures", () => {
      expect(
        clearJSONValues({
          level1: {
            level2: {
              level3: {
                value: "deep",
                count: 100,
              },
            },
          },
        })
      ).toEqual({
        level1: {
          level2: {
            level3: {
              value: "",
              count: 100,
            },
          },
        },
      });
    });

    it("handles objects with array values", () => {
      expect(
        clearJSONValues({
          items: ["item1", "item2"],
          counts: [1, 2, 3],
        })
      ).toEqual({
        items: ["", ""],
        counts: [1, 2, 3],
      });
    });

    it("handles arrays with nested objects", () => {
      expect(
        clearJSONValues({
          users: [
            { name: "John", scores: [85, 90] },
            { name: "Jane", scores: [95, 88] },
          ],
        })
      ).toEqual({
        users: [
          { name: "", scores: [85, 90] },
          { name: "", scores: [95, 88] },
        ],
      });
    });
  });
});

describe("createEmptyJSONStructure", () => {
  describe("valid JSON strings", () => {
    it("creates empty structure from simple object", () => {
      const input = '{"name": "John", "age": 30}';
      const result = createEmptyJSONStructure(input);
      expect(JSON.parse(result)).toEqual({ name: "", age: 30 });
    });

    it("creates empty structure from nested object", () => {
      const input = '{"user": {"name": "John", "email": "test@example.com"}}';
      const result = createEmptyJSONStructure(input);
      expect(JSON.parse(result)).toEqual({ user: { name: "", email: "" } });
    });

    it("creates empty structure from object with array", () => {
      const input = '{"items": ["a", "b", "c"]}';
      const result = createEmptyJSONStructure(input);
      expect(JSON.parse(result)).toEqual({ items: ["", "", ""] });
    });

    it("preserves numbers and booleans", () => {
      const input = '{"count": 42, "active": true, "label": "test"}';
      const result = createEmptyJSONStructure(input);
      expect(JSON.parse(result)).toEqual({
        count: 42,
        active: true,
        label: "",
      });
    });

    it("returns formatted JSON with 2-space indentation", () => {
      const input = '{"a":"b"}';
      const result = createEmptyJSONStructure(input);
      expect(result).toBe('{\n  "a": ""\n}');
    });

    it("handles empty object", () => {
      const input = "{}";
      const result = createEmptyJSONStructure(input);
      expect(result).toBe("{}");
    });

    it("handles array at root", () => {
      const input = '["a", "b"]';
      const result = createEmptyJSONStructure(input);
      expect(JSON.parse(result)).toEqual(["", ""]);
    });
  });

  describe("invalid JSON strings", () => {
    it("returns default empty object for invalid JSON", () => {
      expect(createEmptyJSONStructure("invalid")).toBe("{\n  \n}");
    });

    it("returns default empty object for unclosed brace", () => {
      expect(createEmptyJSONStructure("{")).toBe("{\n  \n}");
    });

    it("returns default empty object for empty string", () => {
      expect(createEmptyJSONStructure("")).toBe("{\n  \n}");
    });

    it("returns default empty object for malformed JSON", () => {
      expect(createEmptyJSONStructure("{a: 1}")).toBe("{\n  \n}");
    });
  });
});

// =============================================================================
// Collapse Utilities Tests
// These utilities are used for "collapsing" top-level keys during dataset upload,
// promoting their immediate children to become top-level keys.
// =============================================================================

describe("isPlainObject", () => {
  it("returns true for plain objects", () => {
    expect(isPlainObject({})).toBe(true);
    expect(isPlainObject({ a: 1 })).toBe(true);
    expect(isPlainObject({ a: { b: 2 } })).toBe(true);
  });

  it("returns false for arrays", () => {
    expect(isPlainObject([])).toBe(false);
    expect(isPlainObject([1, 2, 3])).toBe(false);
  });

  it("returns false for null", () => {
    expect(isPlainObject(null)).toBe(false);
  });

  it("returns false for primitives", () => {
    expect(isPlainObject("string")).toBe(false);
    expect(isPlainObject(123)).toBe(false);
    expect(isPlainObject(true)).toBe(false);
    expect(isPlainObject(undefined)).toBe(false);
  });
});

describe("expandStringifiedJSON", () => {
  it("parses stringified objects", () => {
    expect(expandStringifiedJSON('{"a": 1}')).toEqual({ a: 1 });
  });

  it("parses objects with string values", () => {
    expect(expandStringifiedJSON('{"a": "b"}')).toEqual({ a: "b" });
  });

  it("parses stringified arrays", () => {
    expect(expandStringifiedJSON("[1, 2, 3]")).toEqual([1, 2, 3]);
  });

  it("parses arrays of objects", () => {
    expect(expandStringifiedJSON('[{"a": 1}, {"b": 2}]')).toEqual([
      { a: 1 },
      { b: 2 },
    ]);
  });

  it("parses nested JSON in objects", () => {
    expect(expandStringifiedJSON({ nested: '{"a": 1}' })).toEqual({
      nested: { a: 1 },
    });
  });

  it("parses nested JSON in arrays", () => {
    expect(expandStringifiedJSON(['{"a": 1}', '{"b": 2}'])).toEqual([
      { a: 1 },
      { b: 2 },
    ]);
  });

  it("parses deeply nested structures", () => {
    expect(
      expandStringifiedJSON({
        key1: '{"a": 1}',
        key2: { nested: '{"b": 2}' },
      })
    ).toEqual({
      key1: { a: 1 },
      key2: { nested: { b: 2 } },
    });
  });

  it("handles mixed structures", () => {
    expect(
      expandStringifiedJSON({
        stringified: '{"a": 1}',
        array: ['{"b": 2}', 3],
        nested: { deep: '["x", "y"]' },
      })
    ).toEqual({
      stringified: { a: 1 },
      array: [{ b: 2 }, 3],
      nested: { deep: ["x", "y"] },
    });
  });

  it("preserves non-JSON strings", () => {
    expect(expandStringifiedJSON("not json")).toBe("not json");
    expect(expandStringifiedJSON("123")).toBe("123");
    expect(expandStringifiedJSON("true")).toBe("true");
  });

  it("preserves primitive values", () => {
    expect(expandStringifiedJSON(123)).toBe(123);
    expect(expandStringifiedJSON(true)).toBe(true);
    expect(expandStringifiedJSON(null)).toBe(null);
  });

  it("preserves already parsed objects", () => {
    const input = [{ a: 1 }, { b: 2 }];
    expect(expandStringifiedJSON(input)).toEqual(input);
  });
});

describe("hasStringifiedJSON", () => {
  describe("returns true for", () => {
    it("stringified objects", () => {
      expect(hasStringifiedJSON('{"a": 1}')).toBe(true);
    });

    it("nested stringified objects", () => {
      expect(hasStringifiedJSON('{"a": {"b": 1}}')).toBe(true);
    });

    it("stringified arrays", () => {
      expect(hasStringifiedJSON("[1, 2, 3]")).toBe(true);
    });

    it("stringified arrays of objects", () => {
      expect(hasStringifiedJSON('[{"a": 1}]')).toBe(true);
    });

    it("arrays containing JSON strings", () => {
      expect(hasStringifiedJSON(['{"a": 1}'])).toBe(true);
    });

    it("arrays with mixed JSON strings", () => {
      expect(hasStringifiedJSON([1, 2, '{"a": 1}'])).toBe(true);
    });

    it("objects with JSON string values", () => {
      expect(hasStringifiedJSON({ key: '{"a": 1}' })).toBe(true);
    });

    it("deeply nested JSON strings", () => {
      expect(hasStringifiedJSON({ nested: { deep: '{"a": 1}' } })).toBe(true);
    });
  });

  describe("returns false for", () => {
    it("non-JSON strings", () => {
      expect(hasStringifiedJSON("plain string")).toBe(false);
      expect(hasStringifiedJSON("123")).toBe(false);
      expect(hasStringifiedJSON("true")).toBe(false);
      expect(hasStringifiedJSON("null")).toBe(false);
    });

    it("primitive values", () => {
      expect(hasStringifiedJSON(123)).toBe(false);
      expect(hasStringifiedJSON(true)).toBe(false);
      expect(hasStringifiedJSON(null)).toBe(false);
    });

    it("objects without stringified JSON", () => {
      expect(hasStringifiedJSON({ a: 1 })).toBe(false);
      expect(hasStringifiedJSON({ a: { b: 1 } })).toBe(false);
    });

    it("arrays without stringified JSON", () => {
      expect(hasStringifiedJSON([1, 2, 3])).toBe(false);
      expect(hasStringifiedJSON([{ a: 1 }])).toBe(false);
    });

    it("invalid or malformed JSON strings", () => {
      expect(hasStringifiedJSON("{invalid}")).toBe(false);
      expect(hasStringifiedJSON("[1, 2,")).toBe(false);
    });
  });
});

describe("toFlatJSONEntries", () => {
  it("flattens nested objects with a dot separator", () => {
    expect(toFlatJSONEntries({ value: { a: { b: 1 } } })).toEqual([
      { key: "a.b", value: 1 },
    ]);
  });

  it("addresses array items with bracket notation", () => {
    expect(toFlatJSONEntries({ value: { a: [1, 2] } })).toEqual([
      { key: "a[0]", value: 1 },
      { key: "a[1]", value: 2 },
    ]);
  });

  it("flattens objects nested in arrays", () => {
    expect(
      toFlatJSONEntries({ value: { messages: [{ role: "user" }] } })
    ).toEqual([{ key: "messages[0].role", value: "user" }]);
  });

  it("preserves document order", () => {
    expect(
      toFlatJSONEntries({ value: { z: 1, a: 2 } }).map((entry) => entry.key)
    ).toEqual(["z", "a"]);
  });

  it("keeps empty objects and arrays as leaves", () => {
    expect(toFlatJSONEntries({ value: { a: {}, b: [] } })).toEqual([
      { key: "a", value: {} },
      { key: "b", value: [] },
    ]);
  });

  it("keeps null values", () => {
    expect(toFlatJSONEntries({ value: { a: null } })).toEqual([
      { key: "a", value: null },
    ]);
  });

  it("gives colliding keys an entry each rather than dropping one", () => {
    // a recorded key holding the separator flattens onto a nested one; the
    // caller keeps the list so the copy control cannot lose a row
    expect(toFlatJSONEntries({ value: { "a.b": 1, a: { b: 2 } } })).toEqual([
      { key: "a.b", value: 1 },
      { key: "a.b", value: 2 },
    ]);
  });

  it("flattens a root array", () => {
    expect(toFlatJSONEntries({ value: [1, 2] })).toEqual([
      { key: "[0]", value: 1 },
      { key: "[1]", value: 2 },
    ]);
  });

  it("returns no entries for a root that is not a container", () => {
    expect(toFlatJSONEntries({ value: 1 })).toEqual([]);
    expect(toFlatJSONEntries({ value: "a" })).toEqual([]);
    expect(toFlatJSONEntries({ value: null })).toEqual([]);
    expect(toFlatJSONEntries({ value: {} })).toEqual([]);
  });

  it("composes with expandStringifiedJSON to flatten stringified JSON", () => {
    expect(
      toFlatJSONEntries({
        value: expandStringifiedJSON({
          input: '{"messages": [{"role": "user"}]}',
        }),
      })
    ).toEqual([{ key: "input.messages[0].role", value: "user" }]);
  });

  describe("dot index notation", () => {
    it("addresses array items with a dotted index", () => {
      expect(
        toFlatJSONEntries({
          value: { input: { value: { messages: [{}, { content: "hi" }] } } },
          indexNotation: "dot",
        })
      ).toEqual([
        { key: "input.value.messages.0", value: {} },
        { key: "input.value.messages.1.content", value: "hi" },
      ]);
    });

    it("addresses nested arrays with a dotted index", () => {
      expect(
        toFlatJSONEntries({
          value: { a: [[1]] },
          indexNotation: "dot",
        })
      ).toEqual([{ key: "a.0.0", value: 1 }]);
    });

    it("flattens a root array without a leading separator", () => {
      expect(
        toFlatJSONEntries({ value: [1, 2], indexNotation: "dot" })
      ).toEqual([
        { key: "0", value: 1 },
        { key: "1", value: 2 },
      ]);
    });

    it("separates a nested index from its parent key", () => {
      expect(
        toFlatJSONEntries({ value: { a: [1] }, indexNotation: "dot" })
      ).toEqual([{ key: "a.0", value: 1 }]);
    });
  });
});

describe("formatFlatJSONValue", () => {
  it("shows strings without JSON quoting", () => {
    expect(formatFlatJSONValue("hello")).toBe("hello");
  });

  it("JSON encodes everything else", () => {
    expect(formatFlatJSONValue(1)).toBe("1");
    expect(formatFlatJSONValue(true)).toBe("true");
    expect(formatFlatJSONValue(null)).toBe("null");
    expect(formatFlatJSONValue({})).toBe("{}");
    expect(formatFlatJSONValue([])).toBe("[]");
  });
});

describe("filterFlatJSONEntries", () => {
  const entries = [
    { key: "llm.model_name", value: "gpt-4" },
    { key: "llm.token_count.total", value: 42 },
    { key: "output.value", value: "Hello" },
  ];

  it("returns every entry for an empty query", () => {
    expect(filterFlatJSONEntries({ entries, query: "" })).toEqual(entries);
    expect(filterFlatJSONEntries({ entries, query: "   " })).toEqual(entries);
  });

  it("matches on the key", () => {
    expect(filterFlatJSONEntries({ entries, query: "token" })).toEqual([
      entries[1],
    ]);
  });

  it("matches on the formatted value", () => {
    expect(filterFlatJSONEntries({ entries, query: "42" })).toEqual([
      entries[1],
    ]);
  });

  it("ignores case", () => {
    expect(filterFlatJSONEntries({ entries, query: "HELLO" })).toEqual([
      entries[2],
    ]);
  });

  it("returns nothing when nothing matches", () => {
    expect(filterFlatJSONEntries({ entries, query: "nope" })).toEqual([]);
  });
});
