import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";

test("successful validation", () => {
  const testTuple = z.tuple([z.string(), z.number()]);
  expectTypeOf<typeof testTuple._output>().toEqualTypeOf<[string, number]>();

  const val = testTuple.parse(["asdf", 1234]);
  expect(val).toEqual(val);

  const r1 = testTuple.safeParse(["asdf", "asdf"]);
  expect(r1.success).toEqual(false);
  expect(r1.error!).toMatchInlineSnapshot(`
    [ZodError: [
      {
        "expected": "number",
        "code": "invalid_type",
        "path": [
          1
        ],
        "message": "Invalid input: expected number, received string"
      }
    ]]
  `);

  const r2 = testTuple.safeParse(["asdf", 1234, true]);
  expect(r2.success).toEqual(false);
  expect(r2.error!).toMatchInlineSnapshot(`
    [ZodError: [
      {
        "code": "too_big",
        "maximum": 2,
        "origin": "array",
        "path": [],
        "message": "Too big: expected array to have <2 items"
      }
    ]]
  `);

  const r3 = testTuple.safeParse({});
  expect(r3.success).toEqual(false);
  expect(r3.error!).toMatchInlineSnapshot(`
    [ZodError: [
      {
        "expected": "tuple",
        "code": "invalid_type",
        "path": [],
        "message": "Invalid input: expected tuple, received object"
      }
    ]]
  `);
});

test("async validation", async () => {
  const testTuple = z
    .tuple([z.string().refine(async () => true), z.number().refine(async () => true)])
    .refine(async () => true);
  expectTypeOf<typeof testTuple._output>().toEqualTypeOf<[string, number]>();

  const val = await testTuple.parseAsync(["asdf", 1234]);
  expect(val).toEqual(val);

  const r1 = await testTuple.safeParseAsync(["asdf", "asdf"]);
  expect(r1.success).toEqual(false);
  expect(r1.error!).toMatchInlineSnapshot(`
    [ZodError: [
      {
        "expected": "number",
        "code": "invalid_type",
        "path": [
          1
        ],
        "message": "Invalid input: expected number, received string"
      }
    ]]
  `);

  const r2 = await testTuple.safeParseAsync(["asdf", 1234, true]);
  expect(r2.success).toEqual(false);
  expect(r2.error!).toMatchInlineSnapshot(`
    [ZodError: [
      {
        "code": "too_big",
        "maximum": 2,
        "origin": "array",
        "path": [],
        "message": "Too big: expected array to have <2 items"
      }
    ]]
  `);

  const r3 = await testTuple.safeParseAsync({});
  expect(r3.success).toEqual(false);
  expect(r3.error!).toMatchInlineSnapshot(`
    [ZodError: [
      {
        "expected": "tuple",
        "code": "invalid_type",
        "path": [],
        "message": "Invalid input: expected tuple, received object"
      }
    ]]
  `);
});

test("tuple with optional elements", () => {
  const myTuple = z.tuple([z.string(), z.number().optional(), z.string().optional()]).rest(z.boolean());
  expectTypeOf<typeof myTuple._output>().toEqualTypeOf<[string, number?, string?, ...boolean[]]>();

  const goodData = [["asdf"], ["asdf", 1234], ["asdf", 1234, "asdf"], ["asdf", 1234, "asdf", true, false, true]];
  for (const data of goodData) {
    expect(myTuple.parse(data)).toEqual(data);
  }

  const badData = [
    ["asdf", "asdf"],
    ["asdf", 1234, "asdf", "asdf"],
    ["asdf", 1234, "asdf", true, false, "asdf"],
  ];
  for (const data of badData) {
    expect(() => myTuple.parse(data)).toThrow();
  }
});

test("tuple with optional elements followed by required", () => {
  const myTuple = z.tuple([z.string(), z.number().optional(), z.string()]).rest(z.boolean());
  expectTypeOf<typeof myTuple._output>().toEqualTypeOf<[string, number | undefined, string, ...boolean[]]>();

  const goodData = [
    ["asdf", 1234, "asdf"],
    ["asdf", 1234, "asdf", true, false, true],
  ];
  for (const data of goodData) {
    expect(myTuple.parse(data)).toEqual(data);
  }

  const badData = [
    ["asdf"],
    ["asdf", 1234],
    ["asdf", 1234, "asdf", "asdf"],
    ["asdf", 1234, "asdf", true, false, "asdf"],
  ];
  for (const data of badData) {
    expect(() => myTuple.parse(data)).toThrow();
  }
});

test("tuple with all optional elements", () => {
  const allOptionalTuple = z.tuple([z.string().optional(), z.number().optional(), z.boolean().optional()]);
  expectTypeOf<typeof allOptionalTuple._output>().toEqualTypeOf<[string?, number?, boolean?]>();

  // Empty array should be valid (all items optional)
  expect(allOptionalTuple.parse([])).toEqual([]);

  // Partial arrays should be valid
  expect(allOptionalTuple.parse(["hello"])).toEqual(["hello"]);
  expect(allOptionalTuple.parse(["hello", 42])).toEqual(["hello", 42]);

  // Full array should be valid
  expect(allOptionalTuple.parse(["hello", 42, true])).toEqual(["hello", 42, true]);

  // Array that's too long should fail
  expect(() => allOptionalTuple.parse(["hello", 42, true, "extra"])).toThrow();
});

test("tuple with rest schema", () => {
  const myTuple = z.tuple([z.string(), z.number()]).rest(z.boolean());
  expect(myTuple.parse(["asdf", 1234, true, false, true])).toEqual(["asdf", 1234, true, false, true]);

  expect(myTuple.parse(["asdf", 1234])).toEqual(["asdf", 1234]);

  expect(() => myTuple.parse(["asdf", 1234, "asdf"])).toThrow();
  type t1 = z.output<typeof myTuple>;

  expectTypeOf<t1>().toEqualTypeOf<[string, number, ...boolean[]]>();
});

test("sparse array input", () => {
  const schema = z.tuple([z.string(), z.number()]);
  expect(() => schema.parse(new Array(2))).toThrow();
});
