// 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 fn Surface::add_ref(self : Surface) -> Surface {
  @c.surface_add_ref(self.raw)
  @c.cametallayer_owner_retain(self.layer_owner)
  {
    raw: self.raw,
    layer: self.layer,
    layer_owner: self.layer_owner,
    owner_window_handle: self.owner_window_handle,
    owner_display_handle: self.owner_display_handle,
  }
}

///|
/// Synchronize this surface's retained `CAMetalLayer` with an `NSView*`.
///
/// Call this on macOS after the host view is resized or moves between displays
/// with different backing scale factors.
pub fn Surface::sync_macos_ns_view_layer(
  self : Surface,
  ns_view : @c.OpaquePtr,
) -> Unit raise SurfaceCreateError {
  let status = @c.macos_ns_view_sync_metal_layer(ns_view, self.layer)
  if status != SURFACE_CREATE_STATUS_SUCCESS {
    raise SurfaceCreateError(status)
  }
  ()
}

///|
/// Synchronize this surface's retained `CAMetalLayer` with an `NSView*`
/// address stored as `UInt64`.
pub fn Surface::sync_macos_ns_view_layer_u64(
  self : Surface,
  ns_view : UInt64,
) -> Unit raise SurfaceCreateError {
  self.sync_macos_ns_view_layer(@c.opaque_ptr_from_u64(ns_view))
}

///|
pub fn surface_descriptor_metal_layer_new(
  layer : @c.OpaquePtr,
) -> @c.WGPUSurfaceDescriptorPtr {
  @c.surface_descriptor_metal_layer_new(layer)
}

///|
pub fn surface_descriptor_wayland_new(
  display : @c.OpaquePtr,
  surface : @c.OpaquePtr,
) -> @c.WGPUSurfaceDescriptorPtr {
  @c.surface_descriptor_wayland_new(display, surface)
}

///|
pub fn surface_descriptor_windows_hwnd_new(
  hinstance : @c.OpaquePtr,
  hwnd : @c.OpaquePtr,
) -> @c.WGPUSurfaceDescriptorPtr {
  @c.surface_descriptor_windows_hwnd_new(hinstance, hwnd)
}

///|
pub fn surface_descriptor_xcb_new(
  connection : @c.OpaquePtr,
  window : UInt,
) -> @c.WGPUSurfaceDescriptorPtr {
  @c.surface_descriptor_xcb_new(connection, window)
}

///|
pub fn surface_descriptor_xlib_new(
  display : @c.OpaquePtr,
  window : UInt64,
) -> @c.WGPUSurfaceDescriptorPtr {
  @c.surface_descriptor_xlib_new(display, window)
}

///|
pub fn surface_descriptor_android_native_window_new(
  window : @c.OpaquePtr,
) -> @c.WGPUSurfaceDescriptorPtr {
  @c.surface_descriptor_android_native_window_new(window)
}

///|
pub fn surface_descriptor_swap_chain_panel_new(
  panel_native : @c.OpaquePtr,
) -> @c.WGPUSurfaceDescriptorPtr {
  @c.surface_descriptor_swap_chain_panel_new(panel_native)
}

///|
pub fn surface_descriptor_free(desc : @c.WGPUSurfaceDescriptorPtr) -> Unit {
  @c.surface_descriptor_free(desc)
}

///|
pub fn surface_configuration_ptr_new(
  device : Device,
  config : SurfaceConfiguration,
) -> @c.WGPUSurfaceConfigurationPtr {
  let count = config.view_formats.length()
  if count == 0 {
    @c.surface_configuration_u32_new(
      device.raw,
      config.usage.raw,
      config.format.raw,
      config.width,
      config.height,
      config.present_mode_u32,
      config.alpha_mode_u32,
      config.desired_maximum_frame_latency,
    )
  } else {
    let raw_view_formats : Ref[Array[UInt]] = @ref.new([])
    for i = 0; i < count; i = i + 1 {
      raw_view_formats.val.push(config.view_formats[i].raw)
    }
    let fixed = FixedArray::from_array(raw_view_formats.val[:])
    @c.surface_configuration_u32_with_view_formats_new(
      device.raw,
      config.usage.raw,
      config.format.raw,
      config.width,
      config.height,
      config.present_mode_u32,
      config.alpha_mode_u32,
      config.desired_maximum_frame_latency,
      count.to_uint64(),
      fixed,
    )
  }
}

///|
pub fn surface_configuration_ptr_free(
  config : @c.WGPUSurfaceConfigurationPtr,
) -> Unit {
  @c.surface_configuration_free(config)
}

///|
pub fn Surface::configure_ptr(
  self : Surface,
  config : @c.WGPUSurfaceConfigurationPtr,
) -> Unit {
  @c.wgpuSurfaceConfigure(self.raw, config)
}

///|
pub fn Surface::configure_default(
  self : Surface,
  adapter : Adapter,
  device : Device,
  width : UInt,
  height : UInt,
  usage : TextureUsage,
  desired_maximum_frame_latency? : UInt = SURFACE_DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY,
) -> TextureFormat {
  TextureFormat::from_u32(
    @c.surface_configure_default(
      self.raw,
      adapter.raw,
      device.raw,
      width,
      height,
      usage.raw,
      desired_maximum_frame_latency,
    ),
  )
}

///|
fn pack_alpha_mode_and_desired_maximum_frame_latency_u64(
  alpha_mode_u32 : UInt,
  desired_maximum_frame_latency : UInt,
) -> UInt64 {
  (desired_maximum_frame_latency.to_uint64() << 32) | alpha_mode_u32.to_uint64()
}

///|
pub fn Surface::configure_with(
  self : Surface,
  adapter : Adapter,
  device : Device,
  config : SurfaceConfiguration,
) -> Bool {
  if config.desired_maximum_frame_latency == 0U {
    return false
  }
  let packed_alpha_mode_and_desired_maximum_frame_latency = pack_alpha_mode_and_desired_maximum_frame_latency_u64(
    config.alpha_mode_u32,
    config.desired_maximum_frame_latency,
  )
  let count = config.view_formats.length()
  if count == 0 {
    @c.surface_configure_u32(
      self.raw,
      adapter.raw,
      device.raw,
      config.width,
      config.height,
      config.usage.raw,
      config.format.raw,
      config.present_mode_u32,
      packed_alpha_mode_and_desired_maximum_frame_latency,
    )
  } else {
    let raw_view_formats : Ref[Array[UInt]] = @ref.new([])
    for i = 0; i < count; i = i + 1 {
      raw_view_formats.val.push(config.view_formats[i].raw)
    }
    let fixed = FixedArray::from_array(raw_view_formats.val[:])
    @c.surface_configure_view_formats_u32(
      self.raw,
      adapter.raw,
      device.raw,
      config.width,
      config.height,
      config.usage.raw,
      config.format.raw,
      config.present_mode_u32,
      packed_alpha_mode_and_desired_maximum_frame_latency,
      count.to_uint64(),
      fixed,
    )
  }
}

///|
pub fn Surface::configure_with_or_raise(
  self : Surface,
  adapter : Adapter,
  device : Device,
  config : SurfaceConfiguration,
) -> Unit raise SurfaceConfigureError {
  if config.desired_maximum_frame_latency == 0U {
    raise SurfaceConfigureInvalidDesiredMaximumFrameLatency(
      config.desired_maximum_frame_latency,
    )
  }
  let ok = self.configure_with(adapter, device, config)
  if !ok {
    raise SurfaceConfigureFailed(
      config.width,
      config.height,
      config.usage.raw,
      config.format.raw,
      config.present_mode_u32,
      config.alpha_mode_u32,
      config.desired_maximum_frame_latency,
    )
  }
  ()
}

///|
pub fn Surface::configure_u32(
  self : Surface,
  adapter : Adapter,
  device : Device,
  width : UInt,
  height : UInt,
  usage : TextureUsage,
  format : TextureFormat,
  present_mode_u32 : UInt,
  alpha_mode_u32 : UInt,
  desired_maximum_frame_latency? : UInt = SURFACE_DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY,
) -> Bool {
  let config = SurfaceConfiguration::new(
    width,
    height,
    usage,
    format,
    PresentMode::from_u32(present_mode_u32),
    CompositeAlphaMode::from_u32(alpha_mode_u32),
  ).with_desired_maximum_frame_latency(desired_maximum_frame_latency)
  self.configure_with(adapter, device, config)
}

///|
pub fn Surface::configure_u32_or_raise(
  self : Surface,
  adapter : Adapter,
  device : Device,
  width : UInt,
  height : UInt,
  usage : TextureUsage,
  format : TextureFormat,
  present_mode_u32 : UInt,
  alpha_mode_u32 : UInt,
  desired_maximum_frame_latency? : UInt = SURFACE_DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY,
) -> Unit raise SurfaceConfigureError {
  let config = SurfaceConfiguration::new(
    width,
    height,
    usage,
    format,
    PresentMode::from_u32(present_mode_u32),
    CompositeAlphaMode::from_u32(alpha_mode_u32),
  ).with_desired_maximum_frame_latency(desired_maximum_frame_latency)
  self.configure_with_or_raise(adapter, device, config)
}

///|
pub fn Surface::configure_view_formats_u32(
  self : Surface,
  adapter : Adapter,
  device : Device,
  width : UInt,
  height : UInt,
  usage : TextureUsage,
  format : TextureFormat,
  present_mode_u32 : UInt,
  alpha_mode_u32 : UInt,
  view_formats : Array[TextureFormat],
  desired_maximum_frame_latency? : UInt = SURFACE_DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY,
) -> Bool {
  let config = SurfaceConfiguration::new(
      width,
      height,
      usage,
      format,
      PresentMode::from_u32(present_mode_u32),
      CompositeAlphaMode::from_u32(alpha_mode_u32),
    )
    .with_view_formats(view_formats)
    .with_desired_maximum_frame_latency(desired_maximum_frame_latency)
  self.configure_with(adapter, device, config)
}

///|
pub fn Surface::configure_view_formats_u32_or_raise(
  self : Surface,
  adapter : Adapter,
  device : Device,
  width : UInt,
  height : UInt,
  usage : TextureUsage,
  format : TextureFormat,
  present_mode_u32 : UInt,
  alpha_mode_u32 : UInt,
  view_formats : Array[TextureFormat],
  desired_maximum_frame_latency? : UInt = SURFACE_DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY,
) -> Unit raise SurfaceConfigureError {
  let config = SurfaceConfiguration::new(
      width,
      height,
      usage,
      format,
      PresentMode::from_u32(present_mode_u32),
      CompositeAlphaMode::from_u32(alpha_mode_u32),
    )
    .with_view_formats(view_formats)
    .with_desired_maximum_frame_latency(desired_maximum_frame_latency)
  self.configure_with_or_raise(adapter, device, config)
}

///|
pub fn Surface::get_current_texture(self : Surface) -> SurfaceTexture {
  { raw: @c.surface_texture_acquire(self.raw) }
}

///|
pub fn Surface::get_current_texture_or_raise(
  self : Surface,
) -> SurfaceTexture raise SurfaceTextureError {
  let st = self.get_current_texture()
  let status = st.status()
  if status == SURFACE_GET_CURRENT_TEXTURE_STATUS_SUCCESS_OPTIMAL ||
    status == SURFACE_GET_CURRENT_TEXTURE_STATUS_SUCCESS_SUBOPTIMAL {
    st
  } else {
    st.release()
    raise SurfaceTextureError(status)
  }
}

///|
pub fn Surface::get_current_frame_or_raise(
  self : Surface,
) -> SurfaceFrame raise SurfaceTextureError {
  let st = self.get_current_texture_or_raise()
  let tex = st.take_texture()
  st.release()
  { surface: self.add_ref(), texture: tex }
}

///|
pub fn SurfaceTexture::status(self : SurfaceTexture) -> UInt {
  @c.surface_texture_status(self.raw)
}

///|
pub fn SurfaceTexture::require_success(
  self : SurfaceTexture,
) -> Unit raise SurfaceTextureError {
  let status = self.status()
  if status == SURFACE_GET_CURRENT_TEXTURE_STATUS_SUCCESS_OPTIMAL ||
    status == SURFACE_GET_CURRENT_TEXTURE_STATUS_SUCCESS_SUBOPTIMAL {
    ()
  } else {
    raise SurfaceTextureError(status)
  }
}

///|
pub fn SurfaceTexture::take_texture(self : SurfaceTexture) -> Texture {
  { raw: @c.surface_texture_take_texture(self.raw) }
}

///|
pub fn SurfaceTexture::take_texture_or_raise(
  self : SurfaceTexture,
) -> Texture raise SurfaceTextureError {
  self.require_success()
  self.take_texture()
}

///|
pub fn Surface::present(self : Surface) -> UInt {
  @c.surface_present_u32(self.raw)
}

///|
pub fn Surface::present_or_raise(
  self : Surface,
) -> Unit raise SurfacePresentError {
  let status = self.present()
  if status != STATUS_SUCCESS {
    raise SurfacePresentError(status)
  }
  ()
}

///|
pub fn SurfaceFrame::present(self : SurfaceFrame) -> UInt {
  self.surface.present()
}

///|
pub fn SurfaceFrame::present_or_raise(
  self : SurfaceFrame,
) -> Unit raise SurfacePresentError {
  self.surface.present_or_raise()
}

///|
pub fn SurfaceFrame::release(self : SurfaceFrame) -> Unit {
  self.texture.release()
  self.surface.release()
}

///|
pub fn Surface::unconfigure(self : Surface) -> Unit {
  @c.surface_unconfigure(self.raw)
}

///|
pub fn Surface::capabilities_formats_count_u64(
  self : Surface,
  adapter : Adapter,
) -> UInt64 {
  @c.surface_capabilities_formats_count_u64(self.raw, adapter.raw)
}

///|
pub fn Surface::capabilities_present_modes_count_u64(
  self : Surface,
  adapter : Adapter,
) -> UInt64 {
  @c.surface_capabilities_present_modes_count_u64(self.raw, adapter.raw)
}

///|
pub fn Surface::capabilities_alpha_modes_count_u64(
  self : Surface,
  adapter : Adapter,
) -> UInt64 {
  @c.surface_capabilities_alpha_modes_count_u64(self.raw, adapter.raw)
}

///|
pub fn Surface::capabilities_usages_u64(
  self : Surface,
  adapter : Adapter,
) -> TextureUsage {
  TextureUsage::from_u64(
    @c.surface_capabilities_usages_u64(self.raw, adapter.raw),
  )
}

///|
pub fn Surface::capabilities_format_u32_at(
  self : Surface,
  adapter : Adapter,
  index : UInt64,
) -> TextureFormat {
  TextureFormat::from_u32(
    @c.surface_capabilities_format_u32_at(self.raw, adapter.raw, index),
  )
}

///|
pub fn Surface::capabilities_present_mode_u32_at(
  self : Surface,
  adapter : Adapter,
  index : UInt64,
) -> UInt {
  @c.surface_capabilities_present_mode_u32_at(self.raw, adapter.raw, index)
}

///|
pub fn Surface::capabilities_alpha_mode_u32_at(
  self : Surface,
  adapter : Adapter,
  index : UInt64,
) -> UInt {
  @c.surface_capabilities_alpha_mode_u32_at(self.raw, adapter.raw, index)
}

///|
pub fn Surface::capabilities_formats(
  self : Surface,
  adapter : Adapter,
) -> Array[TextureFormat] {
  let count = self.capabilities_formats_count_u64(adapter)
  let out : Ref[Array[TextureFormat]] = @ref.new([])
  for i = 0; i < count.to_int(); i = i + 1 {
    out.val.push(self.capabilities_format_u32_at(adapter, i.to_uint64()))
  }
  out.val
}

///|
pub fn Surface::capabilities_present_modes(
  self : Surface,
  adapter : Adapter,
) -> Array[UInt] {
  let count = self.capabilities_present_modes_count_u64(adapter)
  let out : Ref[Array[UInt]] = @ref.new([])
  for i = 0; i < count.to_int(); i = i + 1 {
    out.val.push(self.capabilities_present_mode_u32_at(adapter, i.to_uint64()))
  }
  out.val
}

///|
pub fn Surface::capabilities_alpha_modes(
  self : Surface,
  adapter : Adapter,
) -> Array[UInt] {
  let count = self.capabilities_alpha_modes_count_u64(adapter)
  let out : Ref[Array[UInt]] = @ref.new([])
  for i = 0; i < count.to_int(); i = i + 1 {
    out.val.push(self.capabilities_alpha_mode_u32_at(adapter, i.to_uint64()))
  }
  out.val
}

///|
pub fn Surface::configure_best_effort(
  self : Surface,
  adapter : Adapter,
  device : Device,
  width : UInt,
  height : UInt,
  usage : TextureUsage,
  prefer_srgb? : Bool = true,
  vsync? : Bool = true,
  desired_maximum_frame_latency? : UInt = SURFACE_DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY,
) -> Bool {
  let formats = self.capabilities_formats(adapter)
  let present_modes = self.capabilities_present_modes(adapter)
  let alpha_modes = self.capabilities_alpha_modes(adapter)
  let format_prefs = if prefer_srgb {
    [
      TextureFormat::from_u32(TEXTURE_FORMAT_BGRA8_UNORM_SRGB),
      TextureFormat::from_u32(TEXTURE_FORMAT_RGBA8_UNORM_SRGB),
      TextureFormat::from_u32(TEXTURE_FORMAT_BGRA8_UNORM),
      TextureFormat::from_u32(TEXTURE_FORMAT_RGBA8_UNORM),
    ]
  } else {
    [
      TextureFormat::from_u32(TEXTURE_FORMAT_BGRA8_UNORM),
      TextureFormat::from_u32(TEXTURE_FORMAT_RGBA8_UNORM),
      TextureFormat::from_u32(TEXTURE_FORMAT_BGRA8_UNORM_SRGB),
      TextureFormat::from_u32(TEXTURE_FORMAT_RGBA8_UNORM_SRGB),
    ]
  }
  let fmt = pick_first_supported_texture_format(formats, format_prefs)
  let pm_prefs = if vsync {
    [PRESENT_MODE_FIFO, PRESENT_MODE_FIFO_RELAXED]
  } else {
    [PRESENT_MODE_MAILBOX, PRESENT_MODE_IMMEDIATE, PRESENT_MODE_FIFO]
  }
  let pm = pick_first_supported_u32(present_modes, pm_prefs)
  let am_prefs = [
    COMPOSITE_ALPHA_MODE_AUTO,
    COMPOSITE_ALPHA_MODE_OPAQUE,
    COMPOSITE_ALPHA_MODE_PREMULTIPLIED,
    COMPOSITE_ALPHA_MODE_UNPREMULTIPLIED,
    COMPOSITE_ALPHA_MODE_INHERIT,
  ]
  let am = pick_first_supported_u32(alpha_modes, am_prefs)
  let supported_usage = self.capabilities_usages_u64(adapter)
  let actual_usage = if (usage.raw & supported_usage.raw) == 0UL {
    supported_usage.raw
  } else {
    usage.raw & supported_usage.raw
  }
  self.configure_u32(
    adapter,
    device,
    width,
    height,
    TextureUsage::from_u64(actual_usage),
    fmt,
    pm,
    am,
    desired_maximum_frame_latency~,
  )
}

///|
pub fn SurfaceTexture::release(self : SurfaceTexture) -> Unit {
  @c.surface_texture_free(self.raw)
}

///|
pub fn Surface::release(self : Surface) -> Unit {
  @c.surface_release_safe(self.raw)
  @c.cametallayer_owner_release(self.layer_owner)
  self.owner_window_handle = None
  self.owner_display_handle = None
}

///|
pub fn Surface::set_label(self : Surface, label : String) -> Unit {
  let bytes = utf8_bytes(label)
  @c.surface_set_label_utf8(self.raw, bytes, bytes.length().to_uint64())
}

///|
pub fn Surface::raw_handle(self : Surface) -> @c.WGPUSurface {
  self.raw
}

///|
pub fn Surface::metal_layer_handle(self : Surface) -> @c.OpaquePtr {
  self.layer
}