// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
pub(all) struct MeshHandle(UInt) derive(Eq, Debug, Hash)

///|
pub(all) struct MaterialHandle(UInt) derive(Eq, Debug, Hash)

///|
pub(all) struct ImageHandle(UInt) derive(Eq, Debug, Hash)

///|
pub(all) struct SceneHandle(UInt) derive(Eq, Debug, Hash)

///|
pub(all) struct CubemapHandle(UInt) derive(Eq, Debug, Hash)

///|
pub(all) enum CameraProjection3D {
  Perspective
  Orthographic
} derive(Eq, Debug)

///|
pub(all) enum AlphaMode3D {
  Opaque
  Mask
  Blend
} derive(Eq, Debug)

///|
pub(all) enum TextureWrap3D {
  ClampToEdge
  Repeat
  MirroredRepeat
} derive(Eq, Debug)

///|
pub(all) enum TextureFilter3D {
  Nearest
  Linear
} derive(Eq, Debug)

///|
pub(all) struct TextureSampler3D {
  wrap_u : TextureWrap3D
  wrap_v : TextureWrap3D
  min_filter : TextureFilter3D
  mag_filter : TextureFilter3D
  mipmap_filter : TextureFilter3D?
} derive(Eq, Debug)

///|
pub(all) struct TextureTransform3D {
  offset : @math.Vec2
  scale : @math.Vec2
  rotation : Double
} derive(Eq, Debug)

///|
pub(all) struct TextureBinding3D {
  image : ImageHandle
  sampler : TextureSampler3D
  texcoord_set : Int
  transform : TextureTransform3D
} derive(Eq, Debug)

///|
pub(all) struct Tangent3D {
  x : Double
  y : Double
  z : Double
  w : Double
} derive(Eq, Debug)

///|
pub(all) enum TriangleTopology3D {
  TriangleList
  TriangleStrip
  TriangleFan
} derive(Eq, Debug)

///|
pub(all) struct TriangleMesh3D {
  positions : Array[@math.Vec3]
  indices : Array[Int]?
  topology : TriangleTopology3D
  uv_sets : Array[Array[@math.Vec2]]
  normals : Array[@math.Vec3]?
  tangents : Array[Tangent3D]?
  colors : Array[@render.Color]?
} derive(Eq, Debug)

///|
pub(all) enum MeshPrimitive3D {
  Cube(@math.Vec3)
  Sphere(Double)
  Cylinder(Double, Double, Double, Int)
  Plane(@math.Vec3)
  Triangles(TriangleMesh3D)
} derive(Eq, Debug)

///|
pub(all) struct MeshAsset {
  primitive : MeshPrimitive3D
} derive(Eq, Debug)

///|
pub(all) struct ImageAsset {
  path : String
} derive(Eq, Debug)

///|
pub(all) struct CubemapFaces3D {
  positive_x : ImageHandle
  negative_x : ImageHandle
  positive_y : ImageHandle
  negative_y : ImageHandle
  positive_z : ImageHandle
  negative_z : ImageHandle
} derive(Eq, Debug)

///|
pub(all) enum CubemapAsset3D {
  SixFaces(CubemapFaces3D)
} derive(Eq, Debug)

///|
pub(all) struct Skybox3D {
  cubemap : CubemapHandle
  brightness : Double
  rotation : @math.Quat
} derive(Eq, Debug)

///|
pub(all) struct StandardMaterial3D {
  base_color : @render.Color
  base_color_texture : TextureBinding3D?
  normal_texture : TextureBinding3D?
  normal_scale : Double
  emissive_texture : TextureBinding3D?
  emissive_color : @render.Color
  metallic_roughness_texture : TextureBinding3D?
  occlusion_texture : TextureBinding3D?
  occlusion_strength : Double
  metallic : Double
  roughness : Double
  alpha_mode : AlphaMode3D
  alpha_cutoff : Double
  double_sided : Bool
  unlit : Bool
}

///|
pub(all) struct DirectionalLight3D {
  direction : @math.Vec3
  color : @render.Color
  intensity : Double
  shadows : Bool
  shadow_depth_bias : Double
  shadow_normal_bias : Double
  cascade_shadow_config : CascadeShadowConfig3D
}

///|
pub(all) struct PointLight3D {
  color : @render.Color
  intensity : Double
  range : Double
  shadows : Bool
  shadow_depth_bias : Double
  shadow_normal_bias : Double
  shadow_map_near_z : Double
}

///|
pub(all) struct FramePointLight3D {
  position : @math.Vec3
  color : @render.Color
  intensity : Double
  range : Double
  shadows : Bool
  shadow_depth_bias : Double
  shadow_normal_bias : Double
  shadow_map_near_z : Double
}

///|
pub(all) struct FrameSpotLight3D {
  position : @math.Vec3
  direction : @math.Vec3
  color : @render.Color
  intensity : Double
  range : Double
  shadows : Bool
  shadow_depth_bias : Double
  shadow_normal_bias : Double
  shadow_map_near_z : Double
  inner_angle : Double
  outer_angle : Double
}

///|
pub(all) struct SpotLight3D {
  direction : @math.Vec3
  color : @render.Color
  intensity : Double
  range : Double
  shadows : Bool
  shadow_depth_bias : Double
  shadow_normal_bias : Double
  shadow_map_near_z : Double
  inner_angle : Double
  outer_angle : Double
}

///|
pub(all) struct AmbientLight3D {
  color : @render.Color
  intensity : Double
}

///|
pub(all) struct DirectionalLightShadowMap3D {
  size : Int
} derive(Eq, Debug)

///|
pub(all) struct PointLightShadowMap3D {
  size : Int
} derive(Eq, Debug)

///|
pub(all) struct CascadeShadowConfigBuilder3D {
  num_cascades : Int
  minimum_distance : Double
  maximum_distance : Double
  first_cascade_far_bound : Double
  overlap_proportion : Double
} derive(Eq, Debug)

///|
pub(all) struct CascadeShadowConfig3D {
  bounds : Array[Double]
  minimum_distance : Double
  overlap_proportion : Double
} derive(Debug)

///|
pub(all) struct FrameTransform3D {
  translation : @math.Vec3
  rotation : @math.Quat
  scale : @math.Vec3
} derive(Eq, Debug)

///|
pub(all) struct FrameCamera3D {
  position : @math.Vec3
  target : @math.Vec3
  up : @math.Vec3
  fov_y : Double
  projection : CameraProjection3D
  orthographic_size : @math.Vec2?
  near : Double
  far : Double
  clear_color : @render.Color
}

///|
pub(all) struct RenderItem3D {
  entity_id : UInt?
  transform : FrameTransform3D
  mesh : MeshHandle
  material : MaterialHandle
  cast_shadows : Bool
  receive_shadows : Bool
}

///|
pub(all) struct RenderLine3D {
  start : @math.Vec3
  end : @math.Vec3
  color : @render.Color
} derive(Eq, Debug)

///|
pub(all) struct RenderFrame3D {
  mut camera : FrameCamera3D?
  mut clear_color : @render.Color
  mut skybox : Skybox3D?
  mut items : Array[RenderItem3D]
  mut lines : Array[RenderLine3D]
  mut directional_lights : Array[DirectionalLight3D]
  mut point_lights : Array[FramePointLight3D]
  mut spot_lights : Array[FrameSpotLight3D]
  mut ambient_light : AmbientLight3D?
  mut directional_shadow_map_size : Int
  mut point_shadow_map_size : Int
}

///|
pub fn default_texture_sampler3d() -> TextureSampler3D {
  {
    wrap_u: Repeat,
    wrap_v: Repeat,
    min_filter: Linear,
    mag_filter: Linear,
    mipmap_filter: Some(Linear),
  }
}

///|
pub fn default_texture_transform3d() -> TextureTransform3D {
  { offset: @math.Vec2::zero(), scale: Vec2(1.0, 1.0), rotation: 0.0 }
}

///|
pub fn apply_texture_transform3d(
  transform : TextureTransform3D,
  uv : @math.Vec2,
) -> @math.Vec2 {
  let scaled = uv * transform.scale
  let sin_rotation = @cmath.sin(transform.rotation)
  let cos_rotation = @cmath.cos(transform.rotation)
  Vec2(
    scaled[X] * cos_rotation - scaled[Y] * sin_rotation + transform.offset[X],
    scaled[X] * sin_rotation + scaled[Y] * cos_rotation + transform.offset[Y],
  )
}

///|
pub fn texture_binding3d(
  image : ImageHandle,
  texcoord_set? : Int = 0,
  sampler? : TextureSampler3D = default_texture_sampler3d(),
  transform? : TextureTransform3D = default_texture_transform3d(),
) -> TextureBinding3D {
  { image, sampler, texcoord_set, transform }
}

///|
pub fn cubemap_faces3d(
  positive_x : ImageHandle,
  negative_x : ImageHandle,
  positive_y : ImageHandle,
  negative_y : ImageHandle,
  positive_z : ImageHandle,
  negative_z : ImageHandle,
) -> CubemapFaces3D {
  { positive_x, negative_x, positive_y, negative_y, positive_z, negative_z }
}

///|
pub fn cubemap_asset_six_faces(faces : CubemapFaces3D) -> CubemapAsset3D {
  SixFaces(faces)
}

///|
pub fn skybox3d(
  cubemap : CubemapHandle,
  brightness? : Double = 1.0,
  rotation? : @math.Quat = @math.Quat::identity(),
) -> Skybox3D {
  {
    cubemap,
    brightness: if brightness < 0.0 {
      0.0
    } else {
      brightness
    },
    rotation,
  }
}

///|
pub fn empty_render_frame3d() -> RenderFrame3D {
  {
    camera: None,
    clear_color: { r: 0U, g: 0U, b: 0U, a: 1.0 },
    skybox: None,
    items: [],
    lines: [],
    directional_lights: [],
    point_lights: [],
    spot_lights: [],
    ambient_light: None,
    directional_shadow_map_size: default_directional_light_shadow_map3d().size,
    point_shadow_map_size: default_point_light_shadow_map3d().size,
  }
}

///|
pub fn default_directional_light_shadow_map3d() -> DirectionalLightShadowMap3D {
  { size: 2048 }
}

///|
pub fn default_point_light_shadow_map3d() -> PointLightShadowMap3D {
  { size: 1024 }
}

///|
pub fn default_cascade_shadow_config_builder3d() -> CascadeShadowConfigBuilder3D {
  {
    num_cascades: 4,
    minimum_distance: 0.1,
    maximum_distance: 150.0,
    first_cascade_far_bound: 10.0,
    overlap_proportion: 0.2,
  }
}

///|
pub fn build_cascade_shadow_config3d(
  builder : CascadeShadowConfigBuilder3D,
) -> CascadeShadowConfig3D {
  let num_cascades = if builder.num_cascades < 1 {
    1
  } else {
    builder.num_cascades
  }
  let minimum_distance = if builder.minimum_distance < 0.0 {
    0.0
  } else {
    builder.minimum_distance
  }
  let maximum_distance = if builder.maximum_distance <= minimum_distance {
    minimum_distance + 1.0
  } else {
    builder.maximum_distance
  }
  let first_cascade_far_bound = if builder.first_cascade_far_bound <=
    minimum_distance {
    minimum_distance +
    (maximum_distance - minimum_distance) / num_cascades.to_double()
  } else if builder.first_cascade_far_bound >= maximum_distance {
    maximum_distance
  } else {
    builder.first_cascade_far_bound
  }
  let overlap_proportion = if builder.overlap_proportion < 0.0 {
    0.0
  } else if builder.overlap_proportion > 1.0 {
    1.0
  } else {
    builder.overlap_proportion
  }
  let bounds : Array[Double] = []
  if num_cascades == 1 {
    bounds.push(maximum_distance)
  } else {
    let base = @cmath.pow(
      maximum_distance / first_cascade_far_bound,
      1.0 / (num_cascades - 1).to_double(),
    )
    for index in 0.. CascadeShadowConfig3D {
  build_cascade_shadow_config3d(default_cascade_shadow_config_builder3d())
}

///|
pub fn default_directional_light3d() -> DirectionalLight3D {
  {
    direction: Vec3(0.0, 0.0, -1.0),
    color: { r: 255U, g: 255U, b: 255U, a: 1.0 },
    intensity: 1.0,
    shadows: false,
    // Match Bevy's current defaults for directional shadow bias tuning.
    shadow_depth_bias: 0.02,
    shadow_normal_bias: 1.8,
    cascade_shadow_config: default_cascade_shadow_config3d(),
  }
}

///|
pub fn default_point_light3d() -> PointLight3D {
  {
    color: { r: 255U, g: 255U, b: 255U, a: 1.0 },
    intensity: 1.0,
    range: 0.0,
    shadows: false,
    // Match Bevy's current defaults for point shadow bias tuning.
    shadow_depth_bias: 0.08,
    shadow_normal_bias: 0.6,
    shadow_map_near_z: 0.1,
  }
}

///|
pub fn default_spot_light3d() -> SpotLight3D {
  {
    direction: Vec3(0.0, 0.0, -1.0),
    color: { r: 255U, g: 255U, b: 255U, a: 1.0 },
    intensity: 1.0,
    range: 0.0,
    shadows: false,
    // Match Bevy's current defaults for spot shadow bias tuning.
    shadow_depth_bias: 0.02,
    shadow_normal_bias: 1.8,
    shadow_map_near_z: 0.1,
    inner_angle: 0.0,
    outer_angle: 0.7853981633974483,
  }
}

///|
pub fn default_standard_material3d() -> StandardMaterial3D {
  {
    base_color: { r: 255U, g: 255U, b: 255U, a: 1.0 },
    base_color_texture: None,
    normal_texture: None,
    normal_scale: 1.0,
    emissive_texture: None,
    emissive_color: { r: 0U, g: 0U, b: 0U, a: 1.0 },
    metallic_roughness_texture: None,
    occlusion_texture: None,
    occlusion_strength: 1.0,
    metallic: 0.0,
    roughness: 1.0,
    alpha_mode: Opaque,
    alpha_cutoff: 0.5,
    double_sided: false,
    unlit: false,
  }
}