import type { TSchema } from "@sinclair/typebox";

import { Type } from "@sinclair/typebox";

import type { JsonValue } from "./types";

export const HTTP_METHODS = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] as const;

export const durationSchema = Type.String();

export const httpMethodSchema = Type.Union(
  HTTP_METHODS.map((method) => Type.Literal(method)) as unknown as [TSchema, ...TSchema[]],
);

export const jsonValueSchema = Type.Unsafe<JsonValue>({
  anyOf: [
    { type: "string" },
    { type: "number" },
    { type: "boolean" },
    { type: "null" },
    { items: {}, type: "array" },
    { additionalProperties: {}, type: "object" },
  ],
});

export const primitiveValueSchema = Type.Unsafe<boolean | null | number | string>({
  anyOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }, { type: "null" }],
});

export const sizeSchema = Type.Union([Type.String(), Type.Integer({ minimum: 0 })]);

export const variableValueSchema = Type.Union([Type.String(), Type.Number(), Type.Boolean()]);

export const statusCodePatternSchema = Type.Union([
  Type.Integer({ maximum: 599, minimum: 100 }),
  Type.String({ pattern: "^[1-5]xx$" }),
]);

export const stringMapSchema = Type.Unsafe<Record<string, string>>({
  additionalProperties: { type: "string" },
  type: "object",
});

export function createRawContentExpectationsSchema(): TSchema {
  return Type.Array(
    Type.Object(
      {
        ...matcherProperties(),
        css: Type.Optional(
          Type.Object(
            { attr: Type.Optional(Type.String()), selector: Type.String({ minLength: 1 }), ...matcherProperties() },
            { additionalProperties: false },
          ),
        ),
        json: Type.Optional(
          Type.Object({ path: Type.String(), ...matcherProperties() }, { additionalProperties: false }),
        ),
        xpath: Type.Optional(
          Type.Object({ path: Type.String({ minLength: 1 }), ...matcherProperties() }, { additionalProperties: false }),
        ),
      },
      { additionalProperties: false, minProperties: 1 },
    ),
  );
}

export function createRawKeyedExpectationsSchema(): TSchema {
  return Type.Unsafe<Record<string, unknown>>({
    additionalProperties: createRawValueExpectationSchema(),
    type: "object",
  });
}

export function createRawValueExpectationSchema(): TSchema {
  return Type.Unsafe({
    anyOf: [primitiveValueSchema, Type.Object(matcherProperties(), { additionalProperties: false, minProperties: 1 })],
  });
}

export function createValueMatcherObjectSchema(): TSchema {
  return Type.Object(matcherProperties(), { additionalProperties: false, minProperties: 1 });
}

export function matcherProperties(): Record<string, TSchema> {
  return {
    contains: Type.Optional(Type.String()),
    empty: Type.Optional(Type.Boolean()),
    equals: Type.Optional(jsonValueSchema),
    exists: Type.Optional(Type.Boolean()),
    gt: Type.Optional(Type.Number()),
    gte: Type.Optional(Type.Number()),
    lt: Type.Optional(Type.Number()),
    lte: Type.Optional(Type.Number()),
    regex: Type.Optional(Type.String()),
  };
}
