///|
/// Broad grouping used by generated documentation and editor integrations.
pub(all) enum FunctionCategory {
  Collection
  String
  Number
  Conversion
  Predicate
} derive(Eq, Debug, ToJson, FromJson)

///|
/// Machine-readable description of a function built into MoonRule.
///
/// Keeping this metadata in the library lets command-line tools and web
/// editors provide completion without maintaining a second function list.
pub(all) struct FunctionSpec {
  name : String
  category : FunctionCategory
  min_arguments : Int
  max_arguments : Int
  signature : String
  summary : String
} derive(Eq, Debug, ToJson, FromJson)

///|
fn function_spec(
  name : String,
  category : FunctionCategory,
  arguments : Int,
  signature : String,
  summary : String,
) -> FunctionSpec {
  {
    name,
    category,
    min_arguments: arguments,
    max_arguments: arguments,
    signature,
    summary,
  }
}

///|
fn variadic_function_spec(
  name : String,
  category : FunctionCategory,
  min_arguments : Int,
  max_arguments : Int,
  signature : String,
  summary : String,
) -> FunctionSpec {
  { name, category, min_arguments, max_arguments, signature, summary }
}

///|
/// Return the stable catalog of functions supported by the evaluator.
pub fn builtin_functions() -> Array[FunctionSpec] {
  [
    function_spec(
      "len",
      Collection,
      1,
      "len(value)",
      "Return the character, array, or object length.",
    ),
    function_spec(
      "contains",
      Collection,
      2,
      "contains(container, value)",
      "Test string, array, or object-key membership.",
    ),
    function_spec(
      "has",
      Collection,
      2,
      "has(object, key)",
      "Test whether an object contains a key.",
    ),
    function_spec(
      "get",
      Collection,
      3,
      "get(object, key, default)",
      "Read an object key and return a default when it is absent.",
    ),
    function_spec(
      "starts_with",
      String,
      2,
      "starts_with(text, prefix)",
      "Test whether text starts with a prefix.",
    ),
    function_spec(
      "ends_with",
      String,
      2,
      "ends_with(text, suffix)",
      "Test whether text ends with a suffix.",
    ),
    function_spec(
      "lower",
      String,
      1,
      "lower(text)",
      "Convert text to lowercase.",
    ),
    function_spec(
      "upper",
      String,
      1,
      "upper(text)",
      "Convert text to uppercase.",
    ),
    function_spec(
      "matches",
      String,
      2,
      "matches(text, pattern)",
      "Test text with a regular expression.",
    ),
    function_spec(
      "is_date",
      Predicate,
      1,
      "is_date(text)",
      "Validate an ISO 8601 calendar date in YYYY-MM-DD form.",
    ),
    function_spec(
      "is_datetime",
      Predicate,
      1,
      "is_datetime(text)",
      "Validate an RFC 3339 timestamp with seconds and a timezone.",
    ),
    function_spec(
      "is_ipv4",
      Predicate,
      1,
      "is_ipv4(text)",
      "Validate a canonical dotted-decimal IPv4 address.",
    ),
    function_spec(
      "is_email",
      Predicate,
      1,
      "is_email(text)",
      "Validate a conservative ASCII email-address syntax.",
    ),
    function_spec(
      "is_uuid",
      Predicate,
      1,
      "is_uuid(text)",
      "Validate the canonical 8-4-4-4-12 UUID text form.",
    ),
    function_spec(
      "is_semver",
      Predicate,
      1,
      "is_semver(text)",
      "Validate a Semantic Versioning 2.0.0 version string.",
    ),
    function_spec(
      "abs",
      Number,
      1,
      "abs(number)",
      "Return the absolute value of a number.",
    ),
    function_spec(
      "min",
      Number,
      2,
      "min(left, right)",
      "Return the smaller of two numbers.",
    ),
    function_spec(
      "max",
      Number,
      2,
      "max(left, right)",
      "Return the larger of two numbers.",
    ),
    function_spec(
      "clamp",
      Number,
      3,
      "clamp(value, minimum, maximum)",
      "Constrain a number to an inclusive range.",
    ),
    function_spec(
      "sum",
      Number,
      1,
      "sum(numbers)",
      "Add all numbers in an array.",
    ),
    function_spec(
      "type_of",
      Conversion,
      1,
      "type_of(value)",
      "Return the MoonRule type name for a value.",
    ),
    variadic_function_spec(
      "coalesce",
      Conversion,
      1,
      32,
      "coalesce(first, ...fallbacks)",
      "Return the first value that is not null.",
    ),
    function_spec(
      "exists",
      Predicate,
      1,
      "exists(value)",
      "Test whether a value is not null.",
    ),
    function_spec(
      "all",
      Predicate,
      1,
      "all(booleans)",
      "Test whether every array item is true.",
    ),
    function_spec(
      "any",
      Predicate,
      1,
      "any(booleans)",
      "Test whether at least one array item is true.",
    ),
  ]
}

///|
/// Look up one built-in function by its exact rule-language name.
pub fn find_builtin_function(name : String) -> FunctionSpec? {
  for specification in builtin_functions() {
    if specification.name == name {
      return Some(specification)
    }
  }
  None
}

///|
/// Return true when an argument count is accepted by a function specification.
pub fn FunctionSpec::accepts_arity(
  self : FunctionSpec,
  arguments : Int,
) -> Bool {
  arguments >= self.min_arguments && arguments <= self.max_arguments
}

///|
/// Render a concise argument-count description for diagnostics.
pub fn FunctionSpec::arity_description(self : FunctionSpec) -> String {
  if self.min_arguments == self.max_arguments {
    "\{self.min_arguments}"
  } else {
    "\{self.min_arguments}..\{self.max_arguments}"
  }
}

///|
/// Serialize the function catalog for documentation generators and IDEs.
pub fn builtin_functions_json(indent? : Int = 2) -> String {
  builtin_functions().to_json().stringify(indent~)
}