import { expect, test, vi } from "vitest";

const myEqual = vi.defineHelper((a: any, b: any) => {
  expect(a).toEqual(b);
});

const myEqualAsync = vi.defineHelper(async (a: any, b: any) => {
  await new Promise((r) => setTimeout(r, 1));
  expect(a).toEqual(b);
});

const myEqualSoft = vi.defineHelper((a: any, b: any) => {
  expect.soft(a).toEqual(b);
});

const myEqualSoftAsync = vi.defineHelper(async (a: any, b: any) => {
  await new Promise((r) => setTimeout(r, 1));
  expect.soft(a).toEqual(b);
});

test("sync", () => {
  myEqual("sync", "x");
});

test("async", async () => {
  await myEqualAsync("async", "x");
});

test("soft", () => {
  myEqualSoft("soft", "x");
});

test("soft async", async () => {
  await myEqualSoftAsync("soft async", "x");
});

// Nested helpers: outermost marker wins
const innerHelper = vi.defineHelper((a: any, b: any) => {
  expect(a).toEqual(b);
});
const outerHelper = vi.defineHelper((a: any, b: any) => {
  innerHelper(a, b);
});

test("nested", () => {
  outerHelper("nested", "x");
});

// Helper that passes
test("pass sync", () => {
  myEqual(1, 1);
});

test("pass async", async () => {
  await myEqualAsync(1, 1);
});

// Helper returning value
const myAdd = vi.defineHelper((a: number, b: number) => {
  return a + b;
});
const myAddAsync = vi.defineHelper(async (a: number, b: number) => {
  await new Promise((r) => setTimeout(r, 1));
  return a + b;
});

test("return sync", () => {
  expect(myAdd(1, 2)).toBe(3);
});

test("return async", async () => {
  expect(await myAddAsync(1, 2)).toBe(3);
});

// Multiple soft errors in one test
test("multiple soft", () => {
  myEqualSoft("first", "x");
  myEqualSoft("second", "y");
});

// Custom error in helper
const throwCustom = vi.defineHelper(() => {
  throw new Error("custom error from helper");
});

test("custom error", () => {
  throwCustom();
});

// non-helper wrapper calling a helper: stack should include the wrapper
function assertEqualValues(a: any, b: any) {
  myEqual(a, b);
}

test("non-helper wrapper", () => {
  assertEqualValues("wrapper", "x");
});

// printConsoleTrace also hides internal stacks
const myHelperWithLogs = vi.defineHelper(() => {
  console.error("[test-myHelperWithLogs]");
});

test("helper with logs", () => {
  myHelperWithLogs();
});

const helperObject = {
  prop: 1234,
  assertEqualProp: vi.defineHelper(function (this: any, actual: any) {
    expect(actual).toEqual(this.prop);
  }),
  assertEqualPropAsync: vi.defineHelper(async function (this: any, actual: any) {
    await new Promise((r) => setTimeout(r, 1));
    expect(actual).toEqual(this.prop);
  }),
}

test("helper with context pass", () => {
  helperObject.assertEqualProp(1234);
});

test("helper with context fail", () => {
  helperObject.assertEqualProp(4321);
});

test("async helper with context pass", async () => {
  await helperObject.assertEqualPropAsync(1234);
});

test("async helper with context fail", async () => {
  await helperObject.assertEqualPropAsync(4321);
});

const checkWithWaitFor = vi.defineHelper(async () => {
  await vi.waitFor(() => {
    expect(1).toBe(2);
  }, { timeout: 20, interval: 10 });
});

const checkWithWaitUntil = vi.defineHelper(async () => {
  await vi.waitUntil(() => {
    expect(1).toBe(2);
  }, { timeout: 20, interval: 10 });
});

const throwPlainAsyncError = vi.defineHelper(async () => {
  await new Promise((_resolve, reject) => {
    setTimeout(() => {
      reject(new Error("async error from helper"));
    }, 1);
  });
});

test("waitFor keeps helper callsite", async () => {
  await checkWithWaitFor();
});

test("waitUntil keeps helper callsite", async () => {
  await checkWithWaitUntil();
});

test("plain async error", async () => {
  await throwPlainAsyncError();
});
