import { _isEmail, _isUrl, _isUuid, _isInt, _isPositive, _isNegative, _min, _max, _minLength, _maxLength, _matches } from "agency-lang/stdlib-lib/validators.js"
import { _isJsonValue } from "agency-lang/stdlib-lib/jsonValue.js"

/** @module
  @summary Validate values and describe constraints in tool schemas.
  Validators and format helpers for constrained types. Pair a validator with
  `@validate(...)` to check values at runtime, spread the pre-baked
  `@jsonSchema(...)` format fragments into your own schemas, or reach for the
  ready-made type aliases (`Email`, `URLString`, `UUIDString`, …) that combine
  both. Each validator takes a value and returns a `Result`.

  Single-argument validators plug straight in. Parameterized ones bind their
  config via partial application:

  ```ts
  import { isEmail, min, max } from "std::validation"

  @validate(isEmail)
  type Email = string

  @validate(min.partial(n: 0), max.partial(n: 150))
  type Age = number
  ```

  Validators only run on types annotated with `!` (the bang operator). See the
  [type validation guide](../guide/type-validation.md) for full details.
*/

/**
Use for a basic input check. This does not verify that the mailbox exists or
implement every email-address syntax rule.
*/
export def isEmail(value: string): Result {
  """
  Check for an email-shaped string with a name, an @ sign, and a dotted domain.
  """
  return _isEmail(value)
}

/**
Use for a basic URL check. This does not validate the hostname or fetch the URL.
*/
export def isUrl(value: string): Result {
  """
  Check for an http:// or https:// prefix followed by non-whitespace text.
  """
  return _isUrl(value)
}

export def isUuid(value: string): Result {
  """
  Returns success if value is a canonical UUID string
  (8-4-4-4-12 hex), failure otherwise.
  """
  return _isUuid(value)
}

export def isInt(value: number): Result {
  """
  Returns success if value is an integer (no fractional component),
  failure otherwise.
  """
  return _isInt(value)
}

export def isPositive(value: number): Result {
  """
  Returns success if value > 0, failure otherwise.
  """
  return _isPositive(value)
}

export def isNegative(value: number): Result {
  """
  Returns success if value < 0, failure otherwise.
  """
  return _isNegative(value)
}

/** Bind `n` with partial application before use in `@validate(...)`, e.g. `@validate(min.partial(n: 0))`. */
export def min(n: number, value: number): Result {
  """
  Returns success if `value >= n`, failure otherwise.

  @param n - The inclusive minimum allowed value.
  """
  return _min(n, value)
}

/** Bind `n` with partial application before use in `@validate(...)`, e.g. `@validate(max.partial(n: 150))`. */
export def max(n: number, value: number): Result {
  """
  Returns success if `value <= n`, failure otherwise.

  @param n - The inclusive maximum allowed value.
  """
  return _max(n, value)
}

/** Bind `n` with partial application before use in `@validate(...)`, e.g. `@validate(minLength.partial(n: 3))`. */
export def minLength(n: number, value: string): Result {
  """
  Returns success if `value.length >= n`, failure otherwise.

  @param n - The inclusive minimum allowed length.
  """
  return _minLength(n, value)
}

/** Bind `n` with partial application before use in `@validate(...)`, e.g. `@validate(maxLength.partial(n: 80))`. */
export def maxLength(n: number, value: string): Result {
  """
  Returns success if `value.length <= n`, failure otherwise.

  @param n - The inclusive maximum allowed length.
  """
  return _maxLength(n, value)
}

/** Bind `pattern` with partial application before use in `@validate(...)`, e.g. `@validate(matches.partial(pattern: "^[A-Z]"))`. */
export def matches(pattern: string, value: string): Result {
  """
  Returns success if `value` matches the regular expression `pattern`, failure otherwise.

  @param pattern - The regular expression source to match against.
  """
  return _matches(pattern, value)
}

/** JSON Schema email format annotation for a string. */
export static const emailFormat = { format: "email" }

/** `{ format: "uri" }` — RFC 3986 URI. */
export static const urlFormat = { format: "uri" }

/** `{ format: "uuid" }` — canonical 8-4-4-4-12 UUID. */
export static const uuidFormat = { format: "uuid" }

/** `{ format: "date-time" }` — ISO 8601 date-time. */
export static const dateTimeFormat = { format: "date-time" }

/** `{ format: "date" }` — ISO 8601 date (no time). */
export static const dateFormat = { format: "date" }

/** `{ format: "ipv4" }` — dotted-quad IPv4 address. */
export static const ipv4Format = { format: "ipv4" }

/** `{ format: "ipv6" }` — IPv6 address. */
export static const ipv6Format = { format: "ipv6" }

/** An email-shaped string with a name, an @ sign, and a dotted domain. */
@validate(isEmail)
@jsonSchema({ ...emailFormat })
export type Email = string

// Named `URLString` so it does not shadow JavaScript's global `URL` constructor.
/** A string with an http:// or https:// prefix and no whitespace. */
@validate(isUrl)
@jsonSchema({ ...urlFormat })
export type URLString = string

/** A UUID in 8-4-4-4-12 hexadecimal form. */
@validate(isUuid)
@jsonSchema({ ...uuidFormat })
export type UUIDString = string

/** A number that must lie within the inclusive range `[low, high]`.
    Use as `NumberInRange(0, 100)` etc. Both bounds substitute into the
    `@validate(...)` and `@jsonSchema(...)` tags at the use site. */
@validate(min.partial(n: low), max.partial(n: high))
@jsonSchema({ minimum: low, maximum: high })
export type NumberInRange(low: number, high: number) = number

/** A string whose length is in the inclusive range `[min, max]`. */
@validate(minLength.partial(n: min), maxLength.partial(n: max))
@jsonSchema({ minLength: min, maxLength: max })
export type StringWithLength(min: number, max: number) = string

/** A string that matches the given regular expression `pat`. */
@validate(matches.partial(pattern: pat))
@jsonSchema({ pattern: pat })
export type MatchesPattern(pat: string) = string

/** An array whose length is in the inclusive range `[min, max]`. */
@jsonSchema({ minItems: min, maxItems: max })
export type BoundedArray<T>(min: number, max: number) = T[]

export def isJsonValue(value: any): Result {
  """
  Returns success if value is a plain JSON tree that round-trips through
  JSON serialization unchanged, failure (naming the offending path)
  otherwise.
  """
  return _isJsonValue(value)
}

export def isJsonObject(value: any): Result {
  """
  Returns success if value is a plain JSON-serializable object (non-null,
  non-array), failure otherwise.
  """
  if (value is object) {
    return isJsonValue(value)
  }
  return failure("expected a plain JSON object")
}

/** A value that round-trips through JSON unchanged: null, booleans, strings,
    finite numbers, arrays of Json, and plain objects of Json. Class
    instances (Date, Map, ...), functions, NaN/Infinity, and cyclic
    structures are rejected. */
@validate(isJsonValue)
export type Json = any

/** A plain JSON-serializable object: non-null, non-array, and every value
    reachable from it is Json. */
@validate(isJsonObject)
export type JsonObject = object
