// 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 Instance::create() -> Instance raise WgpuError {
  require_native()
  { raw: @c.create_instance() }
}

///|
pub fn Instance::create_ptr(
  descriptor : @c.WGPUInstanceDescriptorPtr,
) -> Instance raise WgpuError {
  require_native()
  { raw: @c.wgpuCreateInstance(descriptor) }
}

///|
/// Create an instance with explicit native extras.
///
/// `dxc_path` is currently reserved and behaves as a documented no-op because
/// published upstream binaries do not share a stable `WGPUInstanceExtras`
/// layout for that field yet.
pub fn Instance::create_with_extras_u32(
  backends_u64? : UInt64 = INSTANCE_BACKEND_ALL,
  flags_u32? : UInt = INSTANCE_FLAG_DEFAULT,
  dx12_shader_compiler_u32? : UInt = DX12_COMPILER_UNDEFINED,
  gles3_minor_version_u32? : UInt = GLES3_MINOR_VERSION_AUTOMATIC,
  gl_fence_behaviour_u32? : UInt = GL_FENCE_BEHAVIOUR_NORMAL,
  dxc_max_shader_model_u32? : UInt = DXC_MAX_SHADER_MODEL_V6_0,
  dx12_presentation_system_u32? : UInt = DX12_SWAPCHAIN_KIND_UNDEFINED,
  dxc_path? : String = "",
) -> Instance raise WgpuError {
  require_native()
  let dxc_path_utf8 = utf8_bytes(dxc_path)
  {
    raw: @c.create_instance_with_extras_utf8(
      backends_u64,
      flags_u32,
      dx12_shader_compiler_u32,
      dxc_path_utf8,
      dxc_path_utf8.length().to_uint64(),
      gles3_minor_version_u32,
      gl_fence_behaviour_u32,
      dxc_max_shader_model_u32,
      dx12_presentation_system_u32,
    ),
  }
}

///|
pub fn Instance::request_adapter_sync(
  self : Instance,
) -> Adapter raise WgpuError {
  require_native()

  // Primary path: enumerate adapters (wgpu-native extra API).
  // In some CI environments, requestAdapter may report "Success" but still
  // return a null adapter handle.
  if native_has_symbol("wgpuInstanceEnumerateAdapters") {
    let raw = if platform_is_linux() {
      let a0 = @c.instance_enumerate_adapter_first_vulkan(self.raw)
      if !@c.adapter_is_null(a0) {
        a0
      } else {
        @c.instance_enumerate_adapter_first_gl(self.raw)
      }
    } else if platform_is_windows() {
      let a0 = @c.instance_enumerate_adapter_first_vulkan(self.raw)
      if !@c.adapter_is_null(a0) {
        a0
      } else {
        let a1 = @c.instance_enumerate_adapter_first_dx12(self.raw)
        if !@c.adapter_is_null(a1) {
          a1
        } else {
          let a2 = @c.instance_enumerate_adapter_first_gl(self.raw)
          if !@c.adapter_is_null(a2) {
            a2
          } else {
            @c.instance_enumerate_adapter_first_dx11(self.raw)
          }
        }
      }
    } else if platform_is_macos() {
      @c.instance_enumerate_adapter_first_metal(self.raw)
    } else {
      @c.instance_enumerate_adapter_first_primary(self.raw)
    }
    if !@c.adapter_is_null(raw) {
      return { raw, }
    }
  }
  require_native_symbol("wgpuInstanceRequestAdapter")
  require_native_symbol("wgpuInstanceProcessEvents")

  // Best-effort strategy: the C API requires featureLevel to be specified, and
  // CI environments may only expose software adapters. Try a small matrix of
  // request options before giving up.
  let backend = if platform_is_linux() {
    BACKEND_TYPE_VULKAN
  } else if platform_is_windows() {
    BACKEND_TYPE_D3_D12
  } else if platform_is_macos() {
    BACKEND_TYPE_METAL
  } else {
    BACKEND_TYPE_UNDEFINED
  }
  fn try_request(
    feature_level_u32 : UInt,
    force_fallback_adapter : Bool,
    backend_type_u32 : UInt,
  ) -> @c.Adapter {
    @c.instance_request_adapter_sync_options_u32(
      self.raw,
      feature_level_u32,
      POWER_PREFERENCE_UNDEFINED,
      force_fallback_adapter,
      backend_type_u32,
      @c.null_surface(),
    )
  }

  let attempts : Array[(UInt, Bool, UInt)] = [
    // Prefer the default backend selection first.
    (FEATURE_LEVEL_CORE, false, BACKEND_TYPE_UNDEFINED),
    (FEATURE_LEVEL_CORE, false, backend),
    // Then allow fallback adapters.
    (FEATURE_LEVEL_CORE, true, BACKEND_TYPE_UNDEFINED),
    (FEATURE_LEVEL_CORE, true, backend),
    // Compatibility profile can be necessary for some software adapters.
    (FEATURE_LEVEL_COMPATIBILITY, false, BACKEND_TYPE_UNDEFINED),
    (FEATURE_LEVEL_COMPATIBILITY, false, backend),
    (FEATURE_LEVEL_COMPATIBILITY, true, BACKEND_TYPE_UNDEFINED),
    (FEATURE_LEVEL_COMPATIBILITY, true, backend),
  ]
  for attempt in attempts {
    let (feature_level_u32, force_fallback_adapter, backend_type_u32) = attempt
    let raw = try_request(
      feature_level_u32, force_fallback_adapter, backend_type_u32,
    )
    if !@c.adapter_is_null(raw) {
      return { raw, }
    }
  }

  // Preserve the last observed status from the C helper.
  raise WgpuRequestAdapterFailed(
    @c.instance_request_adapter_sync_last_status_u32(),
  )
}

///|
pub fn Instance::request_adapter_sync_ptr(
  self : Instance,
  options : @c.WGPURequestAdapterOptionsPtr,
) -> Adapter raise WgpuError {
  require_native_symbol("wgpuInstanceRequestAdapter")
  require_native_symbol("wgpuInstanceProcessEvents")
  let raw = @c.instance_request_adapter_sync_ptr(self.raw, options)
  if @c.adapter_is_null(raw) {
    raise WgpuRequestAdapterFailed(
      @c.instance_request_adapter_sync_last_status_u32(),
    )
  }
  { raw, }
}

///|
pub fn Instance::request_adapter_sync_options_u32(
  self : Instance,
  feature_level_u32? : UInt = FEATURE_LEVEL_CORE,
  power_preference_u32? : UInt = POWER_PREFERENCE_UNDEFINED,
  force_fallback_adapter? : Bool = false,
  backend_type_u32? : UInt = BACKEND_TYPE_UNDEFINED,
) -> Adapter raise WgpuError {
  require_native()
  require_native_symbol("wgpuInstanceRequestAdapter")
  require_native_symbol("wgpuInstanceProcessEvents")
  let raw = @c.instance_request_adapter_sync_options_u32(
    self.raw,
    feature_level_u32,
    power_preference_u32,
    force_fallback_adapter,
    backend_type_u32,
    @c.null_surface(),
  )
  if @c.adapter_is_null(raw) {
    raise WgpuRequestAdapterFailed(
      @c.instance_request_adapter_sync_last_status_u32(),
    )
  }
  { raw, }
}

///|
pub fn Instance::request_adapter_sync_options_surface_u32(
  self : Instance,
  surface : Surface,
  feature_level_u32? : UInt = FEATURE_LEVEL_CORE,
  power_preference_u32? : UInt = POWER_PREFERENCE_UNDEFINED,
  force_fallback_adapter? : Bool = false,
  backend_type_u32? : UInt = BACKEND_TYPE_UNDEFINED,
) -> Adapter raise WgpuError {
  require_native()
  require_native_symbol("wgpuInstanceRequestAdapter")
  require_native_symbol("wgpuInstanceProcessEvents")
  let raw = @c.instance_request_adapter_sync_options_u32(
    self.raw,
    feature_level_u32,
    power_preference_u32,
    force_fallback_adapter,
    backend_type_u32,
    surface.raw,
  )
  if @c.adapter_is_null(raw) {
    raise WgpuRequestAdapterFailed(
      @c.instance_request_adapter_sync_last_status_u32(),
    )
  }
  { raw, }
}

///|
pub fn Instance::request_adapter_future_id_u64(
  self : Instance,
  options? : @c.WGPURequestAdapterOptionsPtr = @c.null_request_adapter_options_ptr(),
) -> UInt64 {
  if !native_available() || !native_supported() {
    return 0UL
  }
  if !native_has_symbol("wgpuInstanceRequestAdapter") {
    return 0UL
  }
  @c.instance_request_adapter_future_id_u64(self.raw, options)
}

///|
pub fn Instance::request_adapter_async_status_u32(
  self : Instance,
  future_id : UInt64,
) -> UInt {
  let _ = self
  @c.instance_request_adapter_async_status_u32(future_id)
}

///|
pub fn Instance::request_adapter_async_adapter(
  self : Instance,
  future_id : UInt64,
) -> Adapter {
  let _ = self
  { raw: @c.instance_request_adapter_async_adapter(future_id) }
}

///|
pub fn Instance::request_adapter_async_message(
  self : Instance,
  future_id : UInt64,
) -> String {
  let _ = self
  let len = @c.instance_request_adapter_async_message_utf8_len(future_id)
  if len == 0UL {
    ""
  } else {
    let out = Bytes::new(len.to_int())
    let ok = @c.instance_request_adapter_async_message_utf8(future_id, out, len)
    if ok {
      @utf8.decode_lossy(out[:], ignore_bom=true)
    } else {
      ""
    }
  }
}

///|
pub fn Instance::request_adapter_async_clear(
  self : Instance,
  future_id : UInt64,
) -> Unit {
  let _ = self
  @c.instance_request_adapter_async_clear(future_id)
}

///|
pub fn Instance::request_adapter_async_take_or_raise(
  self : Instance,
  future_id : UInt64,
) -> Adapter raise WgpuError {
  let status = self.request_adapter_async_status_u32(future_id)
  let adapter = self.request_adapter_async_adapter(future_id)
  self.request_adapter_async_clear(future_id)
  if status == REQUEST_ADAPTER_STATUS_SUCCESS &&
    !@c.adapter_is_null(adapter.raw) {
    adapter
  } else {
    raise WgpuRequestAdapterFailed(status)
  }
}

///|
pub fn Instance::process_events(self : Instance) -> Unit {
  @c.instance_process_events(self.raw)
}

///|
pub fn Instance::wait_any_one(
  self : Instance,
  future_id : UInt64,
  timeout_ns? : UInt64 = 0UL,
) -> WaitAnyResult {
  let packed = @c.instance_wait_any_one_packed_u64(
    self.raw,
    future_id,
    timeout_ns,
  )
  let status = (packed & 0xFFFF_FFFFUL).to_int().reinterpret_as_uint()
  let completed = packed >> 32 != 0UL
  { status, completed }
}

///|
pub fn Instance::wait_any_one_or_raise(
  self : Instance,
  future_id : UInt64,
  timeout_ns? : UInt64 = 0UL,
) -> WaitAnyResult raise WaitAnyError {
  let result = self.wait_any_one(future_id, timeout_ns~)
  if result.status != WAIT_STATUS_SUCCESS {
    raise WaitAnyError(result.status)
  }
  result
}

///|
pub fn Instance::enumerate_adapters_count_metal(self : Instance) -> UInt64 {
  @c.instance_enumerate_adapters_count_metal(self.raw)
}

///|
pub fn Instance::enumerate_adapters_count_vulkan(self : Instance) -> UInt64 {
  @c.instance_enumerate_adapters_count_vulkan(self.raw)
}

///|
pub fn Instance::wgsl_language_features_count_u64(self : Instance) -> UInt64 {
  @c.instance_wgsl_language_features_count_u64(self.raw)
}

///|
pub fn Instance::generate_report(self : Instance) -> GlobalReport {
  { raw: @c.instance_generate_report_new(self.raw) }
}

///|
pub fn Instance::create_surface_metal_layer(self : Instance) -> Surface {
  let layer = @c.cametallayer_new()
  let surface = @c.instance_create_surface_metal_layer(self.raw, layer)
  {
    raw: surface,
    layer,
    layer_owner: @c.cametallayer_owner_new(layer),
    owner_window_handle: None,
    owner_display_handle: None,
  }
}

///|
/// Create a macOS Metal surface from an `NSView*`.
///
/// The view remains host-owned. The returned `Surface` retains the attached
/// `CAMetalLayer` and releases that retain in `Surface::release`.
pub fn Instance::create_surface_macos_ns_view(
  self : Instance,
  ns_view : @c.OpaquePtr,
) -> Surface raise SurfaceCreateError {
  let layer = @c.macos_ns_view_prepare_metal_layer(ns_view)
  if @c.opaque_ptr_is_null(layer) {
    raise SurfaceCreateError(@c.macos_surface_last_status_u32())
  }
  let surface = @c.instance_create_surface_metal_layer(self.raw, layer)
  if @c.surface_is_null(surface) {
    @c.cametallayer_release(layer)
    raise SurfaceCreateError(SURFACE_CREATE_STATUS_CREATE_SURFACE_FAILED)
  }
  {
    raw: surface,
    layer,
    layer_owner: @c.cametallayer_owner_new(layer),
    owner_window_handle: None,
    owner_display_handle: None,
  }
}

///|
/// Create a macOS Metal surface from an `NSView*` address stored as `UInt64`.
///
/// This compatibility entry is intended for window packages that expose native
/// view pointers as integer handles. The value must be an `NSView*` address.
pub fn Instance::create_surface_macos_ns_view_u64(
  self : Instance,
  ns_view : UInt64,
) -> Surface raise SurfaceCreateError {
  self.create_surface_macos_ns_view(@c.opaque_ptr_from_u64(ns_view))
}

///|
/// Create a surface from Wayland handles (`wl_display*`, `wl_surface*`).
///
/// This is intended for Linux + Vulkan setups. The pointers are passed through
/// as opaque handles (you manage their lifetime on the host side).
pub fn Instance::create_surface_wayland(
  self : Instance,
  display : @c.OpaquePtr,
  surface : @c.OpaquePtr,
) -> Surface {
  {
    raw: @c.instance_create_surface_wayland(self.raw, display, surface),
    layer: @c.null_opaque_ptr(),
    layer_owner: @c.null_opaque_ptr(),
    owner_window_handle: None,
    owner_display_handle: None,
  }
}

///|
/// Create a surface from XCB handles (`xcb_connection_t*`, `xcb_window_t`).
pub fn Instance::create_surface_xcb(
  self : Instance,
  connection : @c.OpaquePtr,
  window : UInt,
) -> Surface {
  {
    raw: @c.instance_create_surface_xcb(self.raw, connection, window),
    layer: @c.null_opaque_ptr(),
    layer_owner: @c.null_opaque_ptr(),
    owner_window_handle: None,
    owner_display_handle: None,
  }
}

///|
/// Create a surface from Xlib handles (`Display*`, `Window`).
pub fn Instance::create_surface_xlib(
  self : Instance,
  display : @c.OpaquePtr,
  window : UInt64,
) -> Surface {
  {
    raw: @c.instance_create_surface_xlib(self.raw, display, window),
    layer: @c.null_opaque_ptr(),
    layer_owner: @c.null_opaque_ptr(),
    owner_window_handle: None,
    owner_display_handle: None,
  }
}

///|
/// Create a surface from Android handle (`ANativeWindow*`).
pub fn Instance::create_surface_android_native_window(
  self : Instance,
  window : @c.OpaquePtr,
) -> Surface {
  {
    raw: @c.instance_create_surface_android_native_window(self.raw, window),
    layer: @c.null_opaque_ptr(),
    layer_owner: @c.null_opaque_ptr(),
    owner_window_handle: None,
    owner_display_handle: None,
  }
}

///|
/// Create a surface from Windows XAML SwapChainPanel native handle.
pub fn Instance::create_surface_swap_chain_panel(
  self : Instance,
  panel_native : @c.OpaquePtr,
) -> Surface {
  {
    raw: @c.instance_create_surface_swap_chain_panel(self.raw, panel_native),
    layer: @c.null_opaque_ptr(),
    layer_owner: @c.null_opaque_ptr(),
    owner_window_handle: None,
    owner_display_handle: None,
  }
}

///|
/// Create a surface from Windows handles (`HINSTANCE`, `HWND`).
///
/// The pointers are passed through as opaque handles (you manage their lifetime
/// on the host side).
pub fn Instance::create_surface_windows_hwnd(
  self : Instance,
  hinstance : @c.OpaquePtr,
  hwnd : @c.OpaquePtr,
) -> Surface {
  {
    raw: @c.instance_create_surface_windows_hwnd(self.raw, hinstance, hwnd),
    layer: @c.null_opaque_ptr(),
    layer_owner: @c.null_opaque_ptr(),
    owner_window_handle: None,
    owner_display_handle: None,
  }
}

///|
pub fn GlobalReport::surfaces_num_allocated(self : GlobalReport) -> UInt64 {
  @c.global_report_surfaces_num_allocated(self.raw)
}

///|
pub fn GlobalReport::surfaces_num_kept_from_user(self : GlobalReport) -> UInt64 {
  @c.global_report_surfaces_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::surfaces_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_surfaces_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::surfaces_element_size(self : GlobalReport) -> UInt64 {
  @c.global_report_surfaces_element_size(self.raw)
}

///|
pub fn GlobalReport::hub_devices_num_allocated(self : GlobalReport) -> UInt64 {
  @c.global_report_hub_devices_num_allocated(self.raw)
}

///|
pub fn GlobalReport::hub_devices_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_devices_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_devices_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_devices_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_devices_element_size(self : GlobalReport) -> UInt64 {
  @c.global_report_hub_devices_element_size(self.raw)
}

///|
pub fn GlobalReport::hub_queues_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_queues_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_queues_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_queues_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_buffers_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_buffers_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_buffers_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_buffers_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_textures_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_textures_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_textures_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_textures_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_texture_views_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_texture_views_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_texture_views_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_texture_views_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_samplers_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_samplers_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_samplers_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_samplers_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_shader_modules_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_shader_modules_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_shader_modules_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_shader_modules_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_pipeline_layouts_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_pipeline_layouts_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_pipeline_layouts_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_pipeline_layouts_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_bind_group_layouts_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_bind_group_layouts_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_bind_group_layouts_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_bind_group_layouts_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_bind_groups_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_bind_groups_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_bind_groups_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_bind_groups_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_command_buffers_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_command_buffers_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_command_buffers_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_command_buffers_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_render_pipelines_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_render_pipelines_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_render_pipelines_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_render_pipelines_num_released_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_compute_pipelines_num_kept_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_compute_pipelines_num_kept_from_user(self.raw)
}

///|
pub fn GlobalReport::hub_compute_pipelines_num_released_from_user(
  self : GlobalReport,
) -> UInt64 {
  @c.global_report_hub_compute_pipelines_num_released_from_user(self.raw)
}

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

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

///|
pub fn Instance::add_ref(self : Instance) -> Instance {
  @c.wgpuInstanceAddRef(self.raw)
  { raw: self.raw }
}

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