///|
/// 将 JSON 对象解析为完整 GltfDocument
///
/// # Example
/// ```mbt check
/// test "parse minimal gltf" {
///   let json_str =
///     #|{"asset":{"version":"2.0"},"scenes":[],"nodes":[],"meshes":[],
///     #|"materials":[],"textures":[],"images":[],"samplers":[],
///     #|"accessors":[],"bufferViews":[],"buffers":[],"animations":[],"skins":[]}
///   let doc = @gltf.parse_gltf_string(json_str)
///   inspect(doc.version, content="2.0")
/// }
/// ```
pub fn parse_gltf_string(json_str : String) -> GltfDocument raise ParseError {
  let json = @json.parse(json_str) catch {
    err => raise ParseError::InvalidJson(msg="\{err}")
  }
  match json {
    Json::Object(root) => parse_gltf_object(root)
    _ => raise ParseError::InvalidJson(msg="root must be a JSON object")
  }
}

///|
/// 从 JSON 根对象解析 GltfDocument
pub fn parse_gltf_object(
  root : Map[String, Json],
) -> GltfDocument raise ParseError {
  // asset 对象
  let version = match root.get("asset") {
    Some(Json::Object(asset)) => get_string(asset, "version")
    Some(_) =>
      raise ParseError::InvalidFieldType(field="asset", expected="object")
    None => raise ParseError::MissingField(field="asset")
  }
  let generator = match root.get("asset") {
    Some(Json::Object(asset)) => get_string_opt(asset, "generator")
    _ => None
  }
  let copyright = match root.get("asset") {
    Some(Json::Object(asset)) => get_string_opt(asset, "copyright")
    _ => None
  }
  let default_scene = get_int_opt(root, "scene")
  let scenes = parse_scenes(root)
  let nodes = parse_nodes(root)
  let meshes = parse_meshes(root)
  let materials = parse_materials(root)
  let textures = parse_textures(root)
  let images = parse_images(root)
  let samplers = parse_samplers(root)
  let accessors = parse_accessors(root)
  let buffer_views = parse_buffer_views(root)
  let buffers = parse_buffers(root)
  let animations = parse_animations(root)
  let skins = parse_skins(root)
  {
    version,
    generator,
    copyright,
    scenes,
    nodes,
    meshes,
    materials,
    textures,
    images,
    samplers,
    accessors,
    buffer_views,
    buffers,
    animations,
    skins,
    default_scene,
  }
}

///|
fn parse_scenes(root : Map[String, Json]) -> Array[GltfScene] raise ParseError {
  let arr = get_obj_array(root, "scenes")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let nodes = get_int_array(o, "nodes")
    { name, nodes }
  })
}

///|
fn parse_nodes(root : Map[String, Json]) -> Array[GltfNode] raise ParseError {
  let arr = get_obj_array(root, "nodes")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let children = get_int_array(o, "children")
    let mesh = get_int_opt(o, "mesh")
    let skin = get_int_opt(o, "skin")
    let camera = get_int_opt(o, "camera")
    let translation = match o.get("translation") {
      Some(Json::Array(arr)) => Some(parse_vec3(arr))
      _ => None
    }
    let rotation = match o.get("rotation") {
      Some(Json::Array(arr)) => Some(parse_quat(arr))
      _ => None
    }
    let scale = match o.get("scale") {
      Some(Json::Array(arr)) => Some(parse_vec3(arr))
      _ => None
    }
    { name, children, mesh, skin, camera, translation, rotation, scale }
  })
}

///|
fn parse_meshes(root : Map[String, Json]) -> Array[GltfMesh] raise ParseError {
  let arr = get_obj_array(root, "meshes")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let prims_raw = get_obj_array(o, "primitives")
    let primitives = prims_raw.map(p => {
      let attrs_raw = match p.get("attributes") {
        Some(Json::Object(a)) => a
        _ => {}
      }
      let attributes : Map[String, Int] = Map([])
      for k, v in attrs_raw {
        match v {
          Json::Number(n, ..) => attributes[k] = n.to_int()
          _ => ()
        }
      }
      let indices = get_int_opt(p, "indices")
      let material = get_int_opt(p, "material")
      let mode = match get_int_opt(p, "mode") {
        Some(code) => parse_primitive_mode(code)
        None => Triangles
      }
      { attributes, indices, material, mode }
    })
    { name, primitives }
  })
}

///|
fn parse_materials(
  root : Map[String, Json],
) -> Array[GltfMaterial] raise ParseError {
  let arr = get_obj_array(root, "materials")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let pbr_metallic_roughness = match o.get("pbrMetallicRoughness") {
      Some(Json::Object(pbr)) => parse_pbr(pbr)
      _ =>
        {
          base_color_factor: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
          base_color_texture: None,
          metallic_factor: 1.0,
          roughness_factor: 1.0,
          metallic_roughness_texture: None,
        }
    }
    let normal_texture = match o.get("normalTexture") {
      Some(Json::Object(t)) => Some(parse_texture_info(t))
      _ => None
    }
    let occlusion_texture = match o.get("occlusionTexture") {
      Some(Json::Object(t)) => Some(parse_texture_info(t))
      _ => None
    }
    let emissive_texture = match o.get("emissiveTexture") {
      Some(Json::Object(t)) => Some(parse_texture_info(t))
      _ => None
    }
    let emissive_factor = match o.get("emissiveFactor") {
      Some(Json::Array(arr)) => parse_vec3(arr)
      _ => { x: 0.0, y: 0.0, z: 0.0 }
    }
    let alpha_mode = match get_string_opt(o, "alphaMode") {
      Some(s) => parse_alpha_mode(s)
      None => Opaque
    }
    let alpha_cutoff = get_double(o, "alphaCutoff", default=0.5)
    let double_sided = get_bool(o, "doubleSided", default=false)
    {
      name,
      pbr_metallic_roughness,
      normal_texture,
      occlusion_texture,
      emissive_texture,
      emissive_factor,
      alpha_mode,
      alpha_cutoff,
      double_sided,
    }
  })
}

///|
fn parse_textures(
  root : Map[String, Json],
) -> Array[GltfTexture] raise ParseError {
  let arr = get_obj_array(root, "textures")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let sampler = get_int_opt(o, "sampler")
    let source = get_int_opt(o, "source")
    { name, sampler, source }
  })
}

///|
fn parse_images(root : Map[String, Json]) -> Array[GltfImage] raise ParseError {
  let arr = get_obj_array(root, "images")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let uri = get_string_opt(o, "uri")
    let mime_type = get_string_opt(o, "mimeType")
    let buffer_view = get_int_opt(o, "bufferView")
    { name, uri, mime_type, buffer_view }
  })
}

///|
fn parse_samplers(
  root : Map[String, Json],
) -> Array[GltfSampler] raise ParseError {
  let arr = get_obj_array(root, "samplers")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let mag_filter = match get_int_opt(o, "magFilter") {
      Some(code) => Some(parse_filter_mode(code))
      None => None
    }
    let min_filter = match get_int_opt(o, "minFilter") {
      Some(code) => Some(parse_filter_mode(code))
      None => None
    }
    let wrap_s = match get_int_opt(o, "wrapS") {
      Some(code) => parse_wrap_mode(code)
      None => Repeat
    }
    let wrap_t = match get_int_opt(o, "wrapT") {
      Some(code) => parse_wrap_mode(code)
      None => Repeat
    }
    { name, mag_filter, min_filter, wrap_s, wrap_t }
  })
}

///|
fn parse_accessors(
  root : Map[String, Json],
) -> Array[GltfAccessor] raise ParseError {
  let arr = get_obj_array(root, "accessors")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let buffer_view = get_int_opt(o, "bufferView")
    let byte_offset = match get_int_opt(o, "byteOffset") {
      Some(n) => n
      None => 0
    }
    let component_type = parse_component_type(get_int(o, "componentType"))
    let normalized = get_bool(o, "normalized", default=false)
    let count = get_int(o, "count")
    let accessor_type = parse_accessor_type(get_string(o, "type"))
    let max_values = get_number_array(o, "max")
    let min_values = get_number_array(o, "min")
    {
      name,
      buffer_view,
      byte_offset,
      component_type,
      normalized,
      count,
      accessor_type,
      max_values,
      min_values,
    }
  })
}

///|
fn parse_buffer_views(
  root : Map[String, Json],
) -> Array[GltfBufferView] raise ParseError {
  let arr = get_obj_array(root, "bufferViews")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let buffer = get_int(o, "buffer")
    let byte_offset = match get_int_opt(o, "byteOffset") {
      Some(n) => n
      None => 0
    }
    let byte_length = get_int(o, "byteLength")
    let target = get_int_opt(o, "target")
    let byte_stride = get_int_opt(o, "byteStride")
    { name, buffer, byte_offset, byte_length, target, byte_stride }
  })
}

///|
fn parse_buffers(
  root : Map[String, Json],
) -> Array[GltfBuffer] raise ParseError {
  let arr = get_obj_array(root, "buffers")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let byte_length = get_int(o, "byteLength")
    let uri = get_string_opt(o, "uri")
    { name, byte_length, uri }
  })
}

///|
fn parse_animations(
  root : Map[String, Json],
) -> Array[GltfAnimation] raise ParseError {
  let arr = get_obj_array(root, "animations")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let channels_raw = get_obj_array(o, "channels")
    let channels = channels_raw.map(c => {
      let sampler = get_int(c, "sampler")
      let target = match c.get("target") {
        Some(Json::Object(t)) => {
          let node = get_int_opt(t, "node")
          let path = parse_animation_path(get_string(t, "path"))
          { node, path }
        }
        _ => raise ParseError::MissingField(field="channel.target")
      }
      { sampler, target }
    })
    let samplers_raw = get_obj_array(o, "samplers")
    let samplers = samplers_raw.map(s => {
      let input = get_int(s, "input")
      let output = get_int(s, "output")
      let interpolation = match get_string_opt(s, "interpolation") {
        Some(v) => parse_interpolation(v)
        None => Linear
      }
      { input, output, interpolation }
    })
    { name, channels, samplers }
  })
}

///|
fn parse_skins(root : Map[String, Json]) -> Array[GltfSkin] raise ParseError {
  let arr = get_obj_array(root, "skins")
  arr.map(o => {
    let name = get_string_opt(o, "name")
    let skeleton = get_int_opt(o, "skeleton")
    let joints = get_int_array(o, "joints")
    let inverse_bind_matrices = get_int_opt(o, "inverseBindMatrices")
    { name, skeleton, joints, inverse_bind_matrices }
  })
}