///|
/// OneOf schema
pub struct OneOfSchema {
  schemas : Array[JsonSchema]
  parsed_nullable : JsonSchema?
  parsed_enumerable : Array[Enumerable]?
} derive(Debug, Eq)

///|
impl Validatable for OneOfSchema with fn validate(
  self,
  value,
  resolver~,
  json_path~,
  schema_path~,
) {
  let errors : Array[ValidationError] = []
  let mut passed = 0
  for idx, schema in self.schemas {
    let sub_errors = schema.validate(
      value,
      json_path~,
      schema_path=schema_path.key("oneOf").index(idx),
      resolver~,
    )
    if sub_errors.length() == 0 {
      passed += 1
    } else {
      errors.append(sub_errors)
    }
  }
  if passed == 1 {
    []
  } else {
    errors.push(
      ValidationError::new(
        value,
        json_path,
        schema_path.key("oneOf"),
        "Value does not match exactly one schema in oneOf",
      ),
    )
    errors
  }
}

///|
pub(all) struct Enumerable {
  // variant tag
  tag : String
  // positional types
  positionals : Array[JsonSchema]?
  // named parameters
  params : Map[String, JsonSchema]?
} derive(Debug, Eq)

///|
pub fn Enumerable::new(
  tag : String,
  positionals : Array[JsonSchema]?,
  // should be Object with properties
  params : Map[String, JsonSchema]?,
) -> Enumerable {
  { tag, positionals, params }
}

///|
pub fn Enumerable::parse_enumerable(schema : OneOfSchema) -> Array[Enumerable]? {
  // guard schema is JsonSchema::OneOf(schemas) else { return None }
  let parsed : Array[Enumerable] = []
  for schema in schema.schemas {
    match schema {
      // {"const": "A"}
      Const(s) if s.value is String(tag_value) =>
        parsed.push(Enumerable::new(tag_value, None, None))
      // [{"const": "A"}, { "type": "array", .. }]
      Array(
        {
          prefix_items: Some([JsonSchema::Const({ value: String(tag_value) })]),
          ..,
        }
      ) => parsed.push(Enumerable::new(tag_value, None, None))
      // [{"const": "A"}, ..]
      Array(
        {
          prefix_items: Some(
            [Const({ value: String(tag_value) }), .. child_schemas]
          ),
          ..,
        }
      ) => {
        let matched = match child_schemas.to_owned() {
          [] => { tag: tag_value, positionals: None, params: None }
          // [.. positionals, Object(properties=Some(params), ..)] =>
          [.. positionals, Object(v)] =>
            {
              tag: tag_value,
              positionals: positionals.to_owned() |> Some,
              params: v.properties,
            }
          [.. positionals] =>
            {
              tag: tag_value,
              positionals: positionals.to_owned() |> Some,
              params: None,
            }
        }
        parsed.push(matched)
      }
      _ => ()
    }
  }
  return Some(parsed)
  // }
  // None
}

///|
/// detect nullable enumerable by [ , {"type": "null"}]
pub fn OneOfSchema::parse_nullable(schema : OneOfSchema) -> JsonSchema? {
  match schema.schemas {
    [s, JsonSchema::Null(_)] | [JsonSchema::Null(_), s] => return Some(s)
    _ => return None
  }
}

///|
test "parse enumerable" {
  let enumerable : Json = {
    "oneOf": [
      { "const": "Single" },
      {
        "type": "array",
        "prefixItems": [
          { "const": "A" },
          { "type": "integer" },
          { "type": "string" },
        ],
      },
      // B(Number)
      {
        "type": "array",
        "prefixItems": [{ "const": "B" }, { "type": "number" }],
      },
      // C(~a: Int)
      {
        "type": "array",
        "prefixItems": [{ "const": "C" }, { "type": "integer" }],
      },
    ],
  }
  let schema : JsonSchema = @json.from_json(enumerable)
  assert_true(schema is JsonSchema::OneOf(_))
  guard schema is JsonSchema::OneOf(schema) else {
    raise fail("Failed to parse OneOfSchema")
  }
  guard Enumerable::parse_enumerable(schema) is Some(enum_values) else {
    raise fail("Failed to parse enumerable")
  }
  debug_inspect(
    enum_values,
    content=(
      #|[
      #|  { tag: "Single", positionals: None, params: None },
      #|  {
      #|    tag: "A",
      #|    positionals: Some(
      #|      [
      #|        Integer(
      #|          {
      #|            minimum: None,
      #|            maximum: None,
      #|            exclusive_minimum: None,
      #|            exclusive_maximum: None,
      #|          },
      #|        ),
      #|        String({ min_length: None, max_length: None, enum_: None }),
      #|      ],
      #|    ),
      #|    params: None,
      #|  },
      #|  {
      #|    tag: "B",
      #|    positionals: Some(
      #|      [
      #|        Number(
      #|          {
      #|            minimum: None,
      #|            maximum: None,
      #|            exclusive_minimum: None,
      #|            exclusive_maximum: None,
      #|          },
      #|        ),
      #|      ],
      #|    ),
      #|    params: None,
      #|  },
      #|  {
      #|    tag: "C",
      #|    positionals: Some(
      #|      [
      #|        Integer(
      #|          {
      #|            minimum: None,
      #|            maximum: None,
      #|            exclusive_minimum: None,
      #|            exclusive_maximum: None,
      #|          },
      #|        ),
      #|      ],
      #|    ),
      #|    params: None,
      #|  },
      #|]
    ),
  )
}

///|
test "validate enum" {
  let b = Builder::new()
  let reschema = b.one_of([
    b.array(prefix_items=[b.const_("A"), b.integer(), b.string()]),
    b.array(prefix_items=[b.const_("B"), b.number()]),
    b.array(prefix_items=[b.const_("C"), b.integer()]),
    b.const_("D"),
    b.array(prefix_items=[
      b.const_("Paramed"),
      b.object(properties={ "a": b.integer() }, required=["a"]),
    ]),
  ])
  let resolver = build_resolver({})
  // @test_utils::expect_ok(reschema.validate("D"), "D")
  reschema.validate(
    "Not match",
    resolver~,
    json_path=JsonPointer::Root,
    schema_path=JsonPointer::Root,
  )
  |> assert_not_eq([])
  reschema.validate(
    "D",
    resolver~,
    json_path=JsonPointer::Root,
    schema_path=JsonPointer::Root,
  )
  |> assert_eq([])
  reschema.validate(
    ["A", 1, "str"],
    resolver~,
    json_path=JsonPointer::Root,
    schema_path=JsonPointer::Root,
  )
  |> assert_eq([])
  reschema.validate(
    ["B", 3.14],
    resolver~,
    json_path=JsonPointer::Root,
    schema_path=JsonPointer::Root,
  )
  |> assert_eq([])
  // reschema.validate(["A", 1, "str"]) |> assert_ok
  // reschema.validate(["B", 3.14]) |> assert_ok
  // reschema.validate(["B", "err"]) |> assert_err
  // reschema.validate(["C", 42]) |> assert_ok
  // reschema.validate("D") |> assert_ok
  // reschema.validate("NotExist") |> assert_err
  // reschema.validate(["Paramed", { "a": 10 }]) |> assert_ok
  // reschema.validate(["Paramed", { "a": "not a number" }]) |> assert_err
}