// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use std::borrow::Cow;
use std::cell::RefCell;

use super::error::WebGpuResult;

#[derive(Debug, thiserror::Error)]
pub enum RenderPassError {
  #[error(transparent)]
  Resource(deno_core::error::AnyError),
  #[error("size must be larger than 0")]
  InvalidSize,
}

pub(crate) struct WebGpuRenderPass(
  pub(crate) RefCell<wgpu_core::command::RenderPass>,
);
impl Resource for WebGpuRenderPass {
  fn name(&self) -> Cow<str> {
    "webGPURenderPass".into()
  }
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RenderPassSetViewportArgs {
  render_pass_rid: ResourceId,
  x: f32,
  y: f32,
  width: f32,
  height: f32,
  min_depth: f32,
  max_depth: f32,
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_viewport(
  state: &mut OpState,
  #[serde] args: RenderPassSetViewportArgs,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(args.render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_set_viewport(
    &mut render_pass_resource.0.borrow_mut(),
    args.x,
    args.y,
    args.width,
    args.height,
    args.min_depth,
    args.max_depth,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_scissor_rect(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  x: u32,
  y: u32,
  width: u32,
  height: u32,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_set_scissor_rect(
    &mut render_pass_resource.0.borrow_mut(),
    x,
    y,
    width,
    height,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_blend_constant(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  #[serde] color: wgpu_types::Color,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_set_blend_constant(
    &mut render_pass_resource.0.borrow_mut(),
    &color,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_stencil_reference(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  reference: u32,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_set_stencil_reference(
    &mut render_pass_resource.0.borrow_mut(),
    reference,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_begin_occlusion_query(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  query_index: u32,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_begin_occlusion_query(
    &mut render_pass_resource.0.borrow_mut(),
    query_index,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_end_occlusion_query(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_end_occlusion_query(
    &mut render_pass_resource.0.borrow_mut(),
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_execute_bundles(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  #[serde] bundles: Vec<u32>,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let bundles = bundles
    .iter()
    .map(|rid| {
      let render_bundle_resource =
        state
          .resource_table
          .get::<super::bundle::WebGpuRenderBundle>(*rid)?;
      Ok(render_bundle_resource.1)
    })
    .collect::<Result<Vec<_>, deno_core::error::AnyError>>()?;

  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_execute_bundles(
    &mut render_pass_resource.0.borrow_mut(),
    &bundles,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_end(
  state: &mut OpState,
  #[smi] command_encoder_rid: ResourceId,
  #[smi] render_pass_rid: ResourceId,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let command_encoder_resource = state
    .resource_table
    .get::<super::command_encoder::WebGpuCommandEncoder>(
    command_encoder_rid,
  )?;
  let command_encoder = command_encoder_resource.1;
  let render_pass_resource = state
    .resource_table
    .take::<WebGpuRenderPass>(render_pass_rid)?;
  let render_pass = &render_pass_resource.0.borrow();
  let instance = state.borrow::<super::Instance>();

  gfx_ok!(command_encoder => instance.command_encoder_run_render_pass(command_encoder, render_pass))
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_bind_group(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  index: u32,
  bind_group: u32,
  #[buffer] dynamic_offsets_data: &[u32],
  #[number] dynamic_offsets_data_start: usize,
  #[number] dynamic_offsets_data_length: usize,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let bind_group_resource =
    state
      .resource_table
      .get::<super::binding::WebGpuBindGroup>(bind_group)?;
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  let start = dynamic_offsets_data_start;
  let len = dynamic_offsets_data_length;

  // Assert that length and start are both in bounds
  assert!(start <= dynamic_offsets_data.len());
  assert!(len <= dynamic_offsets_data.len() - start);

  let dynamic_offsets_data: &[u32] = &dynamic_offsets_data[start..start + len];

  wgpu_core::command::render_commands::wgpu_render_pass_set_bind_group(
    &mut render_pass_resource.0.borrow_mut(),
    index,
    bind_group_resource.1,
    dynamic_offsets_data,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_push_debug_group(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  #[string] group_label: &str,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_push_debug_group(
    &mut render_pass_resource.0.borrow_mut(),
    group_label,
    0, // wgpu#975
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_pop_debug_group(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_pop_debug_group(
    &mut render_pass_resource.0.borrow_mut(),
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_insert_debug_marker(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  #[string] marker_label: &str,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_insert_debug_marker(
    &mut render_pass_resource.0.borrow_mut(),
    marker_label,
    0, // wgpu#975
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_pipeline(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  pipeline: u32,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pipeline_resource =
    state
      .resource_table
      .get::<super::pipeline::WebGpuRenderPipeline>(pipeline)?;
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_set_pipeline(
    &mut render_pass_resource.0.borrow_mut(),
    render_pipeline_resource.1,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_index_buffer(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  buffer: u32,
  #[serde] index_format: wgpu_types::IndexFormat,
  #[number] offset: u64,
  #[number] size: Option<u64>,
) -> Result<WebGpuResult, RenderPassError> {
  let buffer_resource = state
    .resource_table
    .get::<super::buffer::WebGpuBuffer>(buffer)
    .map_err(RenderPassError::Resource)?;
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)
    .map_err(RenderPassError::Resource)?;

  let size = if let Some(size) = size {
    Some(std::num::NonZeroU64::new(size).ok_or(RenderPassError::InvalidSize)?)
  } else {
    None
  };

  render_pass_resource.0.borrow_mut().set_index_buffer(
    buffer_resource.1,
    index_format,
    offset,
    size,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_vertex_buffer(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  slot: u32,
  buffer: u32,
  #[number] offset: u64,
  #[number] size: Option<u64>,
) -> Result<WebGpuResult, RenderPassError> {
  let buffer_resource = state
    .resource_table
    .get::<super::buffer::WebGpuBuffer>(buffer)
    .map_err(RenderPassError::Resource)?;
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)
    .map_err(RenderPassError::Resource)?;

  let size = if let Some(size) = size {
    Some(std::num::NonZeroU64::new(size).ok_or(RenderPassError::InvalidSize)?)
  } else {
    None
  };

  wgpu_core::command::render_commands::wgpu_render_pass_set_vertex_buffer(
    &mut render_pass_resource.0.borrow_mut(),
    slot,
    buffer_resource.1,
    offset,
    size,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_draw(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  vertex_count: u32,
  instance_count: u32,
  first_vertex: u32,
  first_instance: u32,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_draw(
    &mut render_pass_resource.0.borrow_mut(),
    vertex_count,
    instance_count,
    first_vertex,
    first_instance,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_draw_indexed(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  index_count: u32,
  instance_count: u32,
  first_index: u32,
  base_vertex: i32,
  first_instance: u32,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_draw_indexed(
    &mut render_pass_resource.0.borrow_mut(),
    index_count,
    instance_count,
    first_index,
    base_vertex,
    first_instance,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_draw_indirect(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  indirect_buffer: u32,
  #[number] indirect_offset: u64,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let buffer_resource = state
    .resource_table
    .get::<super::buffer::WebGpuBuffer>(indirect_buffer)?;
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_draw_indirect(
    &mut render_pass_resource.0.borrow_mut(),
    buffer_resource.1,
    indirect_offset,
  );

  Ok(WebGpuResult::empty())
}

#[op2]
#[serde]
pub fn op_webgpu_render_pass_draw_indexed_indirect(
  state: &mut OpState,
  #[smi] render_pass_rid: ResourceId,
  indirect_buffer: u32,
  #[number] indirect_offset: u64,
) -> Result<WebGpuResult, deno_core::error::AnyError> {
  let buffer_resource = state
    .resource_table
    .get::<super::buffer::WebGpuBuffer>(indirect_buffer)?;
  let render_pass_resource = state
    .resource_table
    .get::<WebGpuRenderPass>(render_pass_rid)?;

  wgpu_core::command::render_commands::wgpu_render_pass_draw_indexed_indirect(
    &mut render_pass_resource.0.borrow_mut(),
    buffer_resource.1,
    indirect_offset,
  );

  Ok(WebGpuResult::empty())
}