// 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.

///|
/// ComputePipelineDescriptor builder.
///
/// The builder is MoonBit-managed. If you call `finish_descriptor_ptr`, it
/// transfers the underlying descriptor out of the builder, and you must free
/// the returned pointer via `@c.compute_pipeline_descriptor_free(desc)`.
pub fn ComputePipelineDescBuilder::new(
  shader_module : ShaderModule,
  layout? : PipelineLayout = PipelineLayout::{ raw: @c.null_pipeline_layout() },
) -> ComputePipelineDescBuilder raise ComputePipelineDescError {
  let raw = @c.compute_pipeline_desc_builder_new(layout.raw, shader_module.raw)
  if @c.compute_pipeline_desc_builder_is_null(raw) {
    raise ComputePipelineDescError(COMPUTE_PIPELINE_DESC_ERR_OOM, 0U, 0U)
  }
  ComputePipelineDescBuilder::{ raw, }
}

///|
pub fn ComputePipelineDescBuilder::set_entry_point(
  self : ComputePipelineDescBuilder,
  entry_point : String,
) -> Unit raise ComputePipelineDescError {
  let entry_bytes = utf8_bytes(entry_point)
  let err = @c.compute_pipeline_desc_builder_set_entry_point_utf8(
    self.raw,
    entry_bytes,
    entry_bytes.length().to_uint64(),
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn ComputePipelineDescBuilder::finish_descriptor_ptr(
  self : ComputePipelineDescBuilder,
) -> @c.WGPUComputePipelineDescriptorPtr raise ComputePipelineDescError {
  let desc = @c.compute_pipeline_desc_builder_take_descriptor(self.raw)
  if @c.compute_pipeline_descriptor_ptr_is_null(desc) {
    raise ComputePipelineDescError(
      COMPUTE_PIPELINE_DESC_ERR_NULL_DESCRIPTOR,
      0U,
      0U,
    )
  }
  desc
}

///|
pub fn ComputePipelineDescBuilder::create_pipeline(
  self : ComputePipelineDescBuilder,
  device : Device,
) -> ComputePipeline raise ComputePipelineDescError {
  let desc = self.finish_descriptor_ptr()
  let pipeline = device.create_compute_pipeline_ptr(desc)
  @c.compute_pipeline_descriptor_free(desc)
  pipeline
}

///|
pub fn ComputePipelineDescBuilder::free(
  self : ComputePipelineDescBuilder,
) -> Unit {
  @c.compute_pipeline_desc_builder_free(self.raw)
}

///|
pub fn ComputePipelineDescBuilder::last_error(
  self : ComputePipelineDescBuilder,
) -> ComputePipelineDescError {
  let code = @c.compute_pipeline_desc_builder_last_error_u32(self.raw)
  let args = @c.compute_pipeline_desc_builder_last_error_args_u64(self.raw)
  let a = (args >> 32).to_uint()
  let b = (args & 0xFFFFFFFFUL).to_uint()
  ComputePipelineDescError(code, a, b)
}

///|
pub fn ComputePipelineDescError::message(
  self : ComputePipelineDescError,
) -> String {
  let ComputePipelineDescError(code, _, _) = self
  if code == COMPUTE_PIPELINE_DESC_ERR_NULL_DESCRIPTOR {
    "descriptor is NULL"
  } else if code == COMPUTE_PIPELINE_DESC_ERR_ENTRY_EMPTY {
    "entry point is empty"
  } else if code == COMPUTE_PIPELINE_DESC_ERR_ENTRY_TOO_LONG {
    "entry point too long"
  } else if code == COMPUTE_PIPELINE_DESC_ERR_OOM {
    "allocation failed"
  } else {
    "unknown error"
  }
}

///|
pub const COMPUTE_PIPELINE_DESC_ERR_NULL_DESCRIPTOR : UInt = 1U

///|
pub const COMPUTE_PIPELINE_DESC_ERR_ENTRY_EMPTY : UInt = 2U

///|
pub const COMPUTE_PIPELINE_DESC_ERR_ENTRY_TOO_LONG : UInt = 3U

///|
pub const COMPUTE_PIPELINE_DESC_ERR_OOM : UInt = 4U

///|
/// RenderPipelineDescriptor arena builder (MVP).
///
/// The builder is MoonBit-managed. If you call `finish_descriptor_ptr`, it
/// transfers the underlying descriptor allocation out of the builder, and you
/// must free the returned pointer via `@c.render_pipeline_descriptor_free(desc)`.
pub fn RenderPipelineDescBuilder::new(
  shader_module : ShaderModule,
  layout? : PipelineLayout = PipelineLayout::{ raw: @c.null_pipeline_layout() },
) -> RenderPipelineDescBuilder raise RenderPipelineDescError {
  let raw = @c.render_pipeline_desc_builder_new(layout.raw, shader_module.raw)
  if @c.render_pipeline_desc_builder_is_null(raw) {
    raise RenderPipelineDescError(RENDER_PIPELINE_DESC_ERR_OOM, 0U, 0U)
  }
  RenderPipelineDescBuilder::{ raw, }
}

///|
pub fn RenderPipelineDescBuilder::set_entry_points(
  self : RenderPipelineDescBuilder,
  vs_entry : String,
  fs_entry : String,
) -> Unit raise RenderPipelineDescError {
  let vs_bytes = utf8_bytes(vs_entry)
  let fs_bytes = utf8_bytes(fs_entry)
  let err = @c.render_pipeline_desc_builder_set_entry_points_utf8(
    self.raw,
    vs_bytes,
    vs_bytes.length().to_uint64(),
    fs_bytes,
    fs_bytes.length().to_uint64(),
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_color_target_format(
  self : RenderPipelineDescBuilder,
  format : TextureFormat,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_color_target_format(
    self.raw,
    format.raw,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_color_target_count(
  self : RenderPipelineDescBuilder,
  count_u32 : UInt,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_color_target_count(
    self.raw,
    count_u32,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_color_target_format_at(
  self : RenderPipelineDescBuilder,
  index_u32 : UInt,
  format : TextureFormat,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_color_target_format_at(
    self.raw,
    index_u32,
    format.raw,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_color_target_write_mask(
  self : RenderPipelineDescBuilder,
  write_mask_u64 : UInt64,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_color_target_write_mask(
    self.raw,
    write_mask_u64,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_color_target_write_mask_at(
  self : RenderPipelineDescBuilder,
  index_u32 : UInt,
  write_mask_u64 : UInt64,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_color_target_write_mask_at(
    self.raw,
    index_u32,
    write_mask_u64,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_blend_components(
  self : RenderPipelineDescBuilder,
  color_src_factor_u32 : UInt,
  color_dst_factor_u32 : UInt,
  color_operation_u32 : UInt,
  alpha_src_factor_u32 : UInt,
  alpha_dst_factor_u32 : UInt,
  alpha_operation_u32 : UInt,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_blend_components(
    self.raw,
    color_src_factor_u32,
    color_dst_factor_u32,
    color_operation_u32,
    alpha_src_factor_u32,
    alpha_dst_factor_u32,
    alpha_operation_u32,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::enable_alpha_blend(
  self : RenderPipelineDescBuilder,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_enable_alpha_blend(self.raw)
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::disable_blend(
  self : RenderPipelineDescBuilder,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_disable_blend(self.raw)
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_vertex_buffer_layout(
  self : RenderPipelineDescBuilder,
  array_stride : UInt64,
  step_mode_u32 : UInt,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_vertex_buffer_layout(
    self.raw,
    array_stride,
    step_mode_u32,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::add_vertex_buffer_layout(
  self : RenderPipelineDescBuilder,
  array_stride : UInt64,
  step_mode_u32 : UInt,
) -> UInt raise RenderPipelineDescError {
  let idx = @c.render_pipeline_desc_builder_add_vertex_buffer_layout(
    self.raw,
    array_stride,
    step_mode_u32,
  )
  let err = @c.render_pipeline_desc_builder_last_error_u32(self.raw)
  if err != 0U {
    raise self.last_error()
  }
  idx
}

///|
pub fn RenderPipelineDescBuilder::add_vertex_attribute(
  self : RenderPipelineDescBuilder,
  format_u32 : UInt,
  offset : UInt64,
  shader_location : UInt,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_add_vertex_attribute(
    self.raw,
    format_u32,
    offset,
    shader_location,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_topology_u32(
  self : RenderPipelineDescBuilder,
  topology_u32 : UInt,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_topology(self.raw, topology_u32)
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_polygon_mode_u32(
  self : RenderPipelineDescBuilder,
  polygon_mode_u32 : UInt,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_polygon_mode(
    self.raw,
    polygon_mode_u32,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_conservative_rasterization(
  self : RenderPipelineDescBuilder,
  conservative : Bool,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_conservative(
    self.raw,
    conservative,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::clear_primitive_extras(
  self : RenderPipelineDescBuilder,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_clear_primitive_extras(self.raw)
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::set_depth_stencil(
  self : RenderPipelineDescBuilder,
  depth_format : TextureFormat,
  depth_write_enabled? : Bool = true,
  depth_compare_u32? : UInt = COMPARE_FUNCTION_LESS,
) -> Unit raise RenderPipelineDescError {
  let err = @c.render_pipeline_desc_builder_set_depth_stencil(
    self.raw,
    depth_format.raw,
    depth_write_enabled,
    depth_compare_u32,
  )
  if err != 0U {
    raise self.last_error()
  }
  ()
}

///|
pub fn RenderPipelineDescBuilder::finish_descriptor_ptr(
  self : RenderPipelineDescBuilder,
) -> @c.WGPURenderPipelineDescriptorPtr raise RenderPipelineDescError {
  let desc = @c.render_pipeline_desc_builder_finish(self.raw)
  if @c.render_pipeline_descriptor_ptr_is_null(desc) {
    raise self.last_error()
  }
  desc
}

///|
pub fn RenderPipelineDescBuilder::create_pipeline(
  self : RenderPipelineDescBuilder,
  device : Device,
) -> RenderPipeline raise RenderPipelineDescError {
  let desc = self.finish_descriptor_ptr()
  let pipeline = device.create_render_pipeline_ptr(desc)
  @c.render_pipeline_descriptor_free(desc)
  pipeline
}

///|
pub fn RenderPipelineDescBuilder::free(
  self : RenderPipelineDescBuilder,
) -> Unit {
  @c.render_pipeline_desc_builder_free(self.raw)
}

///|
pub fn RenderPipelineDescBuilder::last_error(
  self : RenderPipelineDescBuilder,
) -> RenderPipelineDescError {
  let code = @c.render_pipeline_desc_builder_last_error_u32(self.raw)
  let args = @c.render_pipeline_desc_builder_last_error_args_u64(self.raw)
  let a = (args >> 32).to_uint()
  let b = (args & 0xFFFFFFFFUL).to_uint()
  RenderPipelineDescError(code, a, b)
}

///|
pub fn RenderPipelineDescError::message(
  self : RenderPipelineDescError,
) -> String {
  let RenderPipelineDescError(code, _, _) = self
  if code == RENDER_PIPELINE_DESC_ERR_NULL_BUILDER {
    "builder is NULL"
  } else if code == RENDER_PIPELINE_DESC_ERR_ENTRY_EMPTY {
    "entry point is empty"
  } else if code == RENDER_PIPELINE_DESC_ERR_ENTRY_TOO_LONG {
    "entry point too long"
  } else if code == RENDER_PIPELINE_DESC_ERR_COLOR_TARGET_COUNT_ZERO {
    "color target count must be >= 1"
  } else if code == RENDER_PIPELINE_DESC_ERR_COLOR_TARGET_COUNT_EXCEEDS_MAX {
    "color target count exceeds max"
  } else if code == RENDER_PIPELINE_DESC_ERR_COLOR_TARGET_INDEX_OOB {
    "color target index out of range"
  } else if code == RENDER_PIPELINE_DESC_ERR_NO_VERTEX_BUFFER_LAYOUT {
    "no vertex buffer layout is set"
  } else if code == RENDER_PIPELINE_DESC_ERR_VERTEX_BUFFER_LAYOUT_EXCEEDS_MAX {
    "vertex buffer layout count exceeds max"
  } else if code == RENDER_PIPELINE_DESC_ERR_VERTEX_ATTRIBUTE_EXCEEDS_MAX {
    "vertex attribute count exceeds max"
  } else if code == RENDER_PIPELINE_DESC_ERR_INTERNAL {
    "internal error"
  } else if code == RENDER_PIPELINE_DESC_ERR_OOM {
    "allocation failed"
  } else {
    "unknown error"
  }
}

///|
pub const RENDER_PIPELINE_DESC_ERR_NULL_BUILDER : UInt = 1U

///|
pub const RENDER_PIPELINE_DESC_ERR_ENTRY_EMPTY : UInt = 2U

///|
pub const RENDER_PIPELINE_DESC_ERR_ENTRY_TOO_LONG : UInt = 3U

///|
pub const RENDER_PIPELINE_DESC_ERR_COLOR_TARGET_COUNT_ZERO : UInt = 4U

///|
pub const RENDER_PIPELINE_DESC_ERR_COLOR_TARGET_COUNT_EXCEEDS_MAX : UInt = 5U

///|
pub const RENDER_PIPELINE_DESC_ERR_COLOR_TARGET_INDEX_OOB : UInt = 6U

///|
pub const RENDER_PIPELINE_DESC_ERR_NO_VERTEX_BUFFER_LAYOUT : UInt = 7U

///|
pub const RENDER_PIPELINE_DESC_ERR_VERTEX_BUFFER_LAYOUT_EXCEEDS_MAX : UInt = 8U

///|
pub const RENDER_PIPELINE_DESC_ERR_VERTEX_ATTRIBUTE_EXCEEDS_MAX : UInt = 9U

///|
pub const RENDER_PIPELINE_DESC_ERR_INTERNAL : UInt = 10U

///|
pub const RENDER_PIPELINE_DESC_ERR_OOM : UInt = 11U