// A port of `jsonschema/_legacy_keywords.py`.

///|
/// Ignore siblings of `$ref` if it is present; otherwise return all
/// keywords (upstream's `ignore_ref_siblings`, for `create`'s
/// `applicable_validators`).
pub fn ignore_ref_siblings(schema : Json) -> Array[(String, Json)] {
  match get_key(schema, "$ref") {
    Some(ref_) if !(ref_ is Null) => [("$ref", ref_)]
    _ => default_applicable(schema)
  }
}

///|
fn kw_dependencies_draft3(
  validator : Validator,
  dependencies : Json,
  instance : Json,
  _schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    guard instance is Object(obj) && validator.is_type(instance, "object") else {
      return true
    }
    for entry in object_items(dependencies) {
      let (property, dependency) = entry
      if !obj.contains(property) {
        continue
      }
      if validator.is_type(dependency, "object") {
        if !validator
          .descend(instance, dependency, schema_path=Key(property))
          .run(yield_) {
          return false
        }
      } else if dependency is String(d) &&
        validator.is_type(dependency, "string") {
        if !obj.contains(d) {
          if !yield_(
              err(
                "\{r(dependency)} is a dependency of \{@pycompat.repr_string(property)}",
              ),
            ) {
            return false
          }
        }
      } else {
        for each in py_iter(dependency) {
          let present = match each {
            String(e) => obj.contains(e)
            _ => false
          }
          if !present {
            if !yield_(
                err(
                  "\{r(each)} is a dependency of \{@pycompat.repr_string(property)}",
                ),
              ) {
              return false
            }
          }
        }
      }
    }
    true
  })
}

///|
/// Support for the `dependencies` keyword from pre-draft 2019-09.
fn kw_dependencies_draft4_draft6_draft7(
  validator : Validator,
  dependencies : Json,
  instance : Json,
  _schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    guard instance is Object(obj) && validator.is_type(instance, "object") else {
      return true
    }
    for entry in object_items(dependencies) {
      let (property, dependency) = entry
      if !obj.contains(property) {
        continue
      }
      if validator.is_type(dependency, "array") {
        for each in py_iter(dependency) {
          let present = match each {
            String(e) => obj.contains(e)
            _ => false
          }
          if !present {
            if !yield_(
                err(
                  "\{r(each)} is a dependency of \{@pycompat.repr_string(property)}",
                ),
              ) {
              return false
            }
          }
        }
      } else if !validator
        .descend(instance, dependency, schema_path=Key(property))
        .run(yield_) {
        return false
      }
    }
    true
  })
}

///|
fn kw_disallow_draft3(
  validator : Validator,
  disallow : Json,
  instance : Json,
  _schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    for disallowed in ensure_list(disallow) {
      let schema = Json::object(
        Map::from_array([("type", Json::array([disallowed]))]),
      )
      if validator.evolve(schema~).is_valid(instance) {
        if !yield_(err("\{r(disallowed)} is disallowed for \{r(instance)}")) {
          return false
        }
      }
    }
    true
  })
}

///|
fn kw_extends_draft3(
  validator : Validator,
  extends : Json,
  instance : Json,
  _schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    if validator.is_type(extends, "object") {
      return validator.descend(instance, extends).run(yield_)
    }
    for index, subschema in py_iter(extends) {
      if !validator
        .descend(instance, subschema, schema_path=Index(index))
        .run(yield_) {
        return false
      }
    }
    true
  })
}

///|
fn kw_items_draft3_draft4(
  validator : Validator,
  items : Json,
  instance : Json,
  _schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    guard instance is Array(xs) && validator.is_type(instance, "array") else {
      return true
    }
    if validator.is_type(items, "object") {
      for index, item in xs {
        if !validator.descend(item, items, path=Index(index)).run(yield_) {
          return false
        }
      }
    } else {
      let subschemas = py_iter(items)
      for index, item in xs {
        if index >= subschemas.length() {
          break
        }
        let ok = validator
          .descend(
            item,
            subschemas[index],
            path=Index(index),
            schema_path=Index(index),
          )
          .run(yield_)
        if !ok {
          return false
        }
      }
    }
    true
  })
}

///|
fn kw_additional_items(
  validator : Validator,
  ai : Json,
  instance : Json,
  schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    let items = get_key(schema, "items").unwrap_or(Json::empty_object())
    guard instance is Array(xs) &&
      validator.is_type(instance, "array") &&
      !validator.is_type(items, "object") else {
      return true
    }
    let len_items = match get_key(schema, "items") {
      Some(i) => py_len(i)
      None => 0
    }
    if validator.is_type(ai, "object") {
      for index in len_items.. len_items {
      let (extras, verb) = extras_msg(xs[len_items:])
      return yield_(
        err("Additional items are not allowed (\{extras} \{verb} unexpected)"),
      )
    }
    true
  })
}

///|
fn kw_items_draft6_draft7_draft201909(
  validator : Validator,
  items : Json,
  instance : Json,
  _schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    guard instance is Array(xs) && validator.is_type(instance, "array") else {
      return true
    }
    if validator.is_type(items, "array") {
      let subschemas = py_iter(items)
      for index, item in xs {
        if index >= subschemas.length() {
          break
        }
        let ok = validator
          .descend(
            item,
            subschemas[index],
            path=Index(index),
            schema_path=Index(index),
          )
          .run(yield_)
        if !ok {
          return false
        }
      }
    } else {
      for index, item in xs {
        if !validator.descend(item, items, path=Index(index)).run(yield_) {
          return false
        }
      }
    }
    true
  })
}

///|
fn kw_minimum_draft3_draft4(
  validator : Validator,
  minimum : Json,
  instance : Json,
  schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    if !validator.is_type(instance, "number") {
      return true
    }
    let (failed, cmp) = if truthy(
        get_key(schema, "exclusiveMinimum").unwrap_or(Json::boolean(false)),
      ) {
      (py_le(instance, minimum), "less than or equal to")
    } else {
      (py_lt(instance, minimum), "less than")
    }
    if failed {
      return yield_(
        err("\{r(instance)} is \{cmp} the minimum of \{r(minimum)}"),
      )
    }
    true
  })
}

///|
fn kw_maximum_draft3_draft4(
  validator : Validator,
  maximum : Json,
  instance : Json,
  schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    if !validator.is_type(instance, "number") {
      return true
    }
    let (failed, cmp) = if truthy(
        get_key(schema, "exclusiveMaximum").unwrap_or(Json::boolean(false)),
      ) {
      (py_ge(instance, maximum), "greater than or equal to")
    } else {
      (py_gt(instance, maximum), "greater than")
    }
    if failed {
      return yield_(
        err("\{r(instance)} is \{cmp} the maximum of \{r(maximum)}"),
      )
    }
    true
  })
}

///|
fn kw_properties_draft3(
  validator : Validator,
  properties : Json,
  instance : Json,
  schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    guard instance is Object(obj) && validator.is_type(instance, "object") else {
      return true
    }
    for entry in object_items(properties) {
      let (property, subschema) = entry
      if obj.get(property) is Some(value) {
        let ok = validator
          .descend(
            value,
            subschema,
            path=Key(property),
            schema_path=Key(property),
          )
          .run(yield_)
        if !ok {
          return false
        }
      } else if get_key(subschema, "required") is Some(required) &&
        truthy(required) {
        let error = err(
          "\{@pycompat.repr_string(property)} is a required property",
        )
        if error.validator is None {
          error.validator = Some(Json::string("required"))
        }
        if error.validator_value is None {
          error.validator_value = Some(required)
        }
        if error.instance is None {
          error.instance = Some(instance)
        }
        if error.schema is None {
          error.schema = Some(schema)
        }
        error.path.insert(0, Key(property))
        error.schema_path.push(Key(property))
        error.schema_path.push(Key("required"))
        if !yield_(error) {
          return false
        }
      }
    }
    true
  })
}

///|
fn kw_type_draft3(
  validator : Validator,
  types : Json,
  instance : Json,
  _schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    let types = ensure_list(types)
    let all_errors = []
    for index, t in types {
      if validator.is_type(t, "object") {
        let errors = validator
          .descend(instance, t, schema_path=Index(index))
          .to_array()
        if errors.is_empty() {
          return true
        }
        all_errors.append(errors)
      } else {
        guard t is String(name) else {
          raise PythonError("TypeError: unhashable type: \{type_name(t)}")
        }
        if validator.is_type(instance, name) {
          return true
        }
      }
    }
    let reprs = types.map(t => {
      match t {
        Object({ "name": name, .. }) => r(name)
        _ => r(t)
      }
    })
    yield_(
      ValidationError::new(
        "\{r(instance)} is not of type \{reprs.join(", ")}",
        context=all_errors,
      ),
    )
  })
}

///|
fn kw_contains_draft6_draft7(
  validator : Validator,
  contains : Json,
  instance : Json,
  _schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    guard instance is Array(xs) && validator.is_type(instance, "array") else {
      return true
    }
    let contains_validator = validator.evolve(schema=contains)
    for element in xs {
      if contains_validator.is_valid(element) {
        return true
      }
    }
    yield_(err("None of \{r(instance)} are valid under the given schema"))
  })
}

///|
fn kw_recursive_ref(
  validator : Validator,
  _recursive_ref : Json,
  instance : Json,
  _schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    let resolved = @referencing.lookup_recursive_ref(validator.resolver)
    validator
    .descend(instance, resolved.contents, resolver=resolved.resolver)
    .run(yield_)
  })
}

///|
fn kw_unevaluated_items_draft2019(
  validator : Validator,
  _unevaluated_items : Json,
  instance : Json,
  schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    guard instance is Array(xs) && validator.is_type(instance, "array") else {
      return true
    }
    let evaluated = find_evaluated_item_indexes_by_schema_2019(
      validator, instance, schema,
    )
    let unevaluated = []
    for index, item in xs {
      if !evaluated.contains(index) {
        unevaluated.push(item)
      }
    }
    if unevaluated.length() > 0 {
      let (extras, verb) = extras_msg(unevaluated)
      return yield_(
        err("Unevaluated items are not allowed (\{extras} \{verb} unexpected)"),
      )
    }
    true
  })
}

///|
fn kw_unevaluated_properties_draft2019(
  validator : Validator,
  up : Json,
  instance : Json,
  schema : Json,
) -> Errors {
  Errors::new(fn(yield_) raise {
    guard instance is Object(obj) && validator.is_type(instance, "object") else {
      return true
    }
    let evaluated_keys = find_evaluated_property_keys_by_schema_2019(
      validator, instance, schema,
    )
    unevaluated_properties_errors(validator, up, obj, evaluated_keys, yield_)
  })
}