// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

// A keyword this library has never heard of, defined outside it and put in force
// by the document's own metaschema. Run it with
// `moon run src/examples/vocabularies`.

///|
/// `x-units`: a number is a whole number of units, and the keyword says how many
/// decimal places a unit has — so `2` admits `1.25` and refuses `1.234`.
struct Units {}

///|
/// Ten to the power of a small non-negative integer.
fn scale_of(places : Double) -> Double {
  let mut scale = 1.0
  for _ in 0.. {
      let scaled = number * scale_of(places)
      // A binary float carries a little error: 1.234 is 1233.9999999999998 in
      // thousandths, and refusing that would be an artefact of the arithmetic.
      let nearest = scaled.round()
      let slack = if scaled.abs() > 1.0 { scaled.abs() } else { 1.0 }
      if (scaled - nearest).abs() <= 1.0e-9 * slack {
        Accept
      } else {
        Refuse(why="not a whole number of units")
      }
    }
    _ => Accept
  }
}

///|
pub extend Units with @moonschema.Keyword::{name, judge}

///|
/// The vocabulary that holds it.
struct Measures {}

///|
pub impl @moonschema.Vocabulary for Measures with fn uri(_self) {
  "https://example.test/vocab/measures"
}

///|
pub impl @moonschema.Vocabulary for Measures with fn keywords(_self) {
  let units : &@moonschema.Keyword = Units::{ }
  [units]
}

///|
pub extend Measures with @moonschema.Vocabulary::{uri, keywords}

///|
fn main raise {
  // A metaschema is a document like any other: this one says it is written
  // against 2020-12 and that it wants the measures vocabulary.
  let metaschema = @json.parse(
    (
      #|{ "$id": "https://example.test/meta",
      #|  "$schema": "https://json-schema.org/draft/2020-12/schema",
      #|  "$vocabulary": {
      #|    "https://json-schema.org/draft/2020-12/vocab/validation": true,
      #|    "https://example.test/vocab/measures": true } }
    ),
  )
  let remotes = { "https://example.test/meta": metaschema }
  let registry = @moonschema.Registry::new()
  registry.add(Measures::{ })

  let document = @json.parse(
    (
      #|{ "$schema": "https://example.test/meta",
      #|  "type": "number", "x-units": 2 }
    ),
  )
  let schema = @moonschema.Schema::new(document, remotes~, registry~)
  // Three places is a whole number of hundredths; 1.234 is not.
  for text in ["1.25", "1.234"] {
    let instance = @json.parse(text)
    println("\{text}  \{schema.valid(instance)}")
    for fault in schema.faults(instance) {
      println("  " + fault.to_string())
    }
  }

  // Without the vocabulary, the same document is one this cannot read: it asks
  // for something required and nobody holds it.
  let alone = try {
    @moonschema.Schema::new(document, remotes~) |> ignore
    "read"
  } catch {
    @moonschema.Broken(at~, why~) => at + ": " + why
    _ => "refused"
  }
  println("\nwithout the vocabulary: \{alone}")
}