// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import { z } from 'zod';
import assert from 'node:assert/strict';
import {
  parseLoose,
  parsePartial,
  parseStrict,
  parseUnknown,
  SchemaParseError,
} from '../../util/schemas.std.ts';

describe('schemas', () => {
  const schema = z.object({ prop: z.literal('value') });

  it('rejects invalid inputs', () => {
    function assertThrows(fn: () => void) {
      assert.throws(fn, SchemaParseError);
    }

    const input = { prop: 42 };
    // @ts-expect-error: not unknown
    assertThrows(() => parseUnknown(schema, input));
    // @ts-expect-error: invalid type
    assertThrows(() => parseStrict(schema, input));
    assertThrows(() => parseLoose(schema, input));
    // @ts-expect-error: invalid type
    assertThrows(() => parsePartial(schema, input));
  });

  it('accepts valid inputs', () => {
    const valid = { prop: 'value' };

    function assertShape(value: { prop: 'value' }) {
      assert.deepEqual(value, valid);
    }

    // unknown
    {
      const input = valid as unknown;
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      // @ts-expect-error: not loose
      assertShape(parseLoose(schema, input));
      // @ts-expect-error: not partial
      assertShape(parsePartial(schema, input));
    }
    // any
    {
      // oxlint-disable-next-line typescript/no-explicit-any
      const input = valid as unknown as any;
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      // @ts-expect-error: not loose
      assertShape(parseLoose(schema, input));
      // @ts-expect-error: not partial
      assertShape(parsePartial(schema, input));
    }
    // {}
    {
      // oxlint-disable-next-line typescript/ban-types, typescript/no-empty-object-type
      const input = valid as unknown as {};
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      // @ts-expect-error: not loose
      assertShape(parseLoose(schema, input));
      // @ts-expect-error: not partial
      assertShape(parsePartial(schema, input));
    }
    // never
    {
      const input = valid as unknown as never;
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      // @ts-expect-error: not loose
      assertShape(parseLoose(schema, input));
      // @ts-expect-error: not partial
      assertShape(parsePartial(schema, input));
    }
    // { prop: "value" }
    {
      const input = valid as { prop: 'value' };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      assertShape(parseStrict(schema, input));
      assertShape(parseLoose(schema, input));
      assertShape(parsePartial(schema, input));
    }
    // { prop?: "value" }
    {
      const input = valid as { prop?: 'value' };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      // @ts-expect-error: not loose
      assertShape(parseLoose(schema, input));
      assertShape(parsePartial(schema, input));
    }
    // { prop: "value" | void }
    {
      const input = valid as { prop: 'value' | void };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      assertShape(parseLoose(schema, input));
      assertShape(parsePartial(schema, input));
    }
    // { prop: "value" | undefined }
    {
      const input = valid as { prop: 'value' | undefined };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      assertShape(parseLoose(schema, input));
      assertShape(parsePartial(schema, input));
    }
    // { prop: "value" | null }
    {
      const input = valid as { prop: 'value' | null };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      assertShape(parseLoose(schema, input));
      assertShape(parsePartial(schema, input));
    }

    // { prop: string }
    {
      const input = valid as { prop: string };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      assertShape(parseLoose(schema, input));
      // @ts-expect-error: not partial
      assertShape(parsePartial(schema, input));
    }
    // { prop?: string }
    {
      const input = valid as { prop?: string };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      // @ts-expect-error: not loose
      assertShape(parseLoose(schema, input));
      // @ts-expect-error: not partial
      assertShape(parsePartial(schema, input));
    }
    // { prop: void }
    {
      const input = valid as unknown as { prop: void };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      assertShape(parseLoose(schema, input));
      assertShape(parsePartial(schema, input));
    }
    // { prop: undefined }
    {
      const input = valid as unknown as { prop: undefined };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      assertShape(parseLoose(schema, input));
      assertShape(parsePartial(schema, input));
    }
    // { prop: null }
    {
      const input = valid as unknown as { prop: null };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      assertShape(parseLoose(schema, input));
      assertShape(parsePartial(schema, input));
    }
    // { prop: unknown }
    {
      const input = valid as { prop: unknown };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      assertShape(parseLoose(schema, input));
      // @ts-expect-error: not partial
      assertShape(parsePartial(schema, input));
    }
    // { prop: any }
    {
      // oxlint-disable-next-line typescript/no-explicit-any
      const input = valid as { prop: any };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // (ideally not allowed)
      assertShape(parseStrict(schema, input));
      // (ideally not allowed)
      assertShape(parseLoose(schema, input));
      // (ideally not allowed)
      assertShape(parsePartial(schema, input));
    }
    // { prop: {} }
    {
      // oxlint-disable-next-line typescript/ban-types, typescript/no-empty-object-type
      const input = valid as { prop: {} };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // @ts-expect-error: not strict
      assertShape(parseStrict(schema, input));
      // (ideally not allowed)
      assertShape(parseLoose(schema, input));
      // @ts-expect-error: not partial
      assertShape(parsePartial(schema, input));
    }
    // { prop: never }
    {
      const input = valid as { prop: never };
      // @ts-expect-error: not unknown
      assertShape(parseUnknown(schema, input));
      // (ideally not allowed)
      assertShape(parseStrict(schema, input));
      // (ideally not allowed)
      assertShape(parseLoose(schema, input));
      // (ideally not allowed)
      assertShape(parsePartial(schema, input));
    }
  });
});
