///|
extern "js" fn webgpu_begin_3d(
  position_x : Double,
  position_y : Double,
  position_z : Double,
  target_x : Double,
  target_y : Double,
  target_z : Double,
  up_x : Double,
  up_y : Double,
  up_z : Double,
  fovy : Double,
  near_z : Double,
  far_z : Double,
  orthographic : Double,
  orthographic_width : Double,
  orthographic_height : Double,
) -> Unit =
  #| (px, py, pz, tx, ty, tz, ux, uy, uz, fovy, nearZ, farZ, orthographic, orthoWidth, orthoHeight) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.begin3d(px, py, pz, tx, ty, tz, ux, uy, uz, fovy, nearZ, farZ, orthographic, orthoWidth, orthoHeight);
  #|   }
  #| }

///|
extern "js" fn webgpu_end_3d() -> Unit =
  #| () => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.end3d();
  #|   }
  #| }

///|
extern "js" fn webgpu_set_lighting_3d(
  directional_data : Array[Double],
  point_data : Array[Double],
  spot_data : Array[Double],
  ambient_r : Double,
  ambient_g : Double,
  ambient_b : Double,
  directional_shadow_map_size : Double,
  point_shadow_map_size : Double,
) -> Unit =
  #| (directionalData, pointData, spotData, ar, ag, ab, directionalShadowMapSize, pointShadowMapSize) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (!rt) return;
  #|   const clamp01 = (x) => Math.max(0, Math.min(1, Number.isFinite(x) ? x : 0));
  #|   const clampSigned = (x) => Math.max(-1, Math.min(1, Number.isFinite(x) ? x : 0));
  #|   const safeDirectional = Array.isArray(directionalData) ? directionalData : [];
  #|   const safePoint = Array.isArray(pointData) ? pointData : [];
  #|   const safeSpot = Array.isArray(spotData) ? spotData : [];
  #|   const safeDirectionalShadowMapSize = Math.max(1, Math.round(Number(directionalShadowMapSize) || 2048));
  #|   const safePointShadowMapSize = Math.max(1, Math.round(Number(pointShadowMapSize) || 1024));
  #|   const directionalLights = [];
  #|   for (let i = 0; i + 16 < safeDirectional.length && directionalLights.length < 4; i += 17) {
  #|     let dx = Number(safeDirectional[i]) || 0;
  #|     let dy = Number(safeDirectional[i + 1]) || -1;
  #|     let dz = Number(safeDirectional[i + 2]) || 0;
  #|     const len = Math.hypot(dx, dy, dz);
  #|     if (len <= 1e-8) {
  #|       dx = 0;
  #|       dy = -1;
  #|       dz = 0;
  #|     } else {
  #|       dx /= len;
  #|       dy /= len;
  #|       dz /= len;
  #|     }
  #|     directionalLights.push({
  #|       direction: [dx, dy, dz],
  #|       intensity: Math.max(0, Number(safeDirectional[i + 3]) || 0),
  #|       color: [
  #|         clamp01(safeDirectional[i + 4]),
  #|         clamp01(safeDirectional[i + 5]),
  #|         clamp01(safeDirectional[i + 6]),
  #|       ],
  #|       shadows: Number(safeDirectional[i + 7]) >= 0.5,
  #|       depthBias: Math.max(0, Number(safeDirectional[i + 8]) || 0),
  #|       normalBias: Math.max(0, Number(safeDirectional[i + 9]) || 0),
  #|       cascadeConfig: (() => {
  #|         const cascadeCount = Math.max(1, Math.min(4, Math.round(Number(safeDirectional[i + 10]) || 1)));
  #|         const minimumDistance = Math.max(0, Number(safeDirectional[i + 11]) || 0);
  #|         const overlapProportion = clamp01(safeDirectional[i + 12]);
  #|         const bounds = [];
  #|         let fallbackBound = minimumDistance + 1.0;
  #|         for (let boundIndex = 0; boundIndex < 4; boundIndex += 1) {
  #|           const rawBound = Number(safeDirectional[i + 13 + boundIndex]);
  #|           const bound = Number.isFinite(rawBound) ? rawBound : fallbackBound;
  #|           fallbackBound = bound;
  #|           if (boundIndex < cascadeCount) {
  #|             bounds.push(bound);
  #|           }
  #|         }
  #|         return { minimumDistance, overlapProportion, bounds };
  #|       })(),
  #|     });
  #|   }
  #|   const pointLights = [];
  #|   for (let i = 0; i + 11 < safePoint.length && pointLights.length < 8; i += 12) {
  #|     pointLights.push({
  #|       position: [
  #|         Number(safePoint[i]) || 0,
  #|         Number(safePoint[i + 1]) || 0,
  #|         Number(safePoint[i + 2]) || 0,
  #|       ],
  #|       intensity: Math.max(0, Number(safePoint[i + 3]) || 0),
  #|       color: [
  #|         clamp01(safePoint[i + 4]),
  #|         clamp01(safePoint[i + 5]),
  #|         clamp01(safePoint[i + 6]),
  #|       ],
  #|       range: Math.max(0.0001, Number(safePoint[i + 7]) || 0.0001),
  #|       shadows: Number(safePoint[i + 8]) >= 0.5,
  #|       depthBias: Math.max(0, Number(safePoint[i + 9]) || 0),
  #|       normalBias: Math.max(0, Number(safePoint[i + 10]) || 0),
  #|       nearZ: Math.max(0.0001, Number(safePoint[i + 11]) || 0.0001),
  #|     });
  #|   }
  #|   const spotLights = [];
  #|   for (let i = 0; i + 16 < safeSpot.length && spotLights.length < 4; i += 17) {
  #|     let dx = Number(safeSpot[i + 8]) || 0;
  #|     let dy = Number(safeSpot[i + 9]) || -1;
  #|     let dz = Number(safeSpot[i + 10]) || 0;
  #|     const dlen = Math.hypot(dx, dy, dz);
  #|     if (dlen <= 1e-8) {
  #|       dx = 0;
  #|       dy = -1;
  #|       dz = 0;
  #|     } else {
  #|       dx /= dlen;
  #|       dy /= dlen;
  #|       dz /= dlen;
  #|     }
  #|     const innerCos = clampSigned(safeSpot[i + 11]);
  #|     const outerCos = clampSigned(safeSpot[i + 12]);
  #|     spotLights.push({
  #|       position: [
  #|         Number(safeSpot[i]) || 0,
  #|         Number(safeSpot[i + 1]) || 0,
  #|         Number(safeSpot[i + 2]) || 0,
  #|       ],
  #|       intensity: Math.max(0, Number(safeSpot[i + 3]) || 0),
  #|       color: [
  #|         clamp01(safeSpot[i + 4]),
  #|         clamp01(safeSpot[i + 5]),
  #|         clamp01(safeSpot[i + 6]),
  #|       ],
  #|       range: Math.max(0.0001, Number(safeSpot[i + 7]) || 0.0001),
  #|       direction: [dx, dy, dz],
  #|       innerCos,
  #|       outerCos,
  #|       innerAngle: Math.acos(innerCos),
  #|       outerAngle: Math.acos(outerCos),
  #|       shadows: Number(safeSpot[i + 13]) >= 0.5,
  #|       depthBias: Math.max(0, Number(safeSpot[i + 14]) || 0),
  #|       normalBias: Math.max(0, Number(safeSpot[i + 15]) || 0),
  #|       nearZ: Math.max(0.0001, Number(safeSpot[i + 16]) || 0.0001),
  #|     });
  #|   }
  #|   rt.light3d = {
  #|     directionalLights,
  #|     pointLights,
  #|     spotLights,
  #|     ambient: [
  #|       clamp01(ar),
  #|       clamp01(ag),
  #|       clamp01(ab),
  #|     ],
  #|     directionalShadowMapSize: safeDirectionalShadowMapSize,
  #|     pointShadowMapSize: safePointShadowMapSize,
  #|   };
  #| }

///|
extern "js" fn webgpu_draw_cube_3d(
  center_x : Double,
  center_y : Double,
  center_z : Double,
  size_x : Double,
  size_y : Double,
  size_z : Double,
  rotation_x : Double,
  rotation_y : Double,
  rotation_z : Double,
  rotation_w : Double,
  r : Double,
  g : Double,
  b : Double,
  a : Double,
  emissive_r : Double,
  emissive_g : Double,
  emissive_b : Double,
  alpha_mode : Double,
  alpha_cutoff : Double,
  unlit : Bool,
  cast_shadows : Bool,
  receive_shadows : Bool,
) -> Unit =
  #| (cx, cy, cz, sx, sy, sz, qx, qy, qz, qw, r, g, b, a, emissiveR, emissiveG, emissiveB, alphaMode, alphaCutoff, unlit, castShadows, receiveShadows) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.pushCube3d(cx, cy, cz, sx, sy, sz, qx, qy, qz, qw, r, g, b, a, emissiveR, emissiveG, emissiveB, alphaMode, alphaCutoff, unlit, castShadows, receiveShadows);
  #|   }
  #| }

///|
extern "js" fn webgpu_draw_sphere_3d(
  center_x : Double,
  center_y : Double,
  center_z : Double,
  radius : Double,
  rotation_x : Double,
  rotation_y : Double,
  rotation_z : Double,
  rotation_w : Double,
  r : Double,
  g : Double,
  b : Double,
  a : Double,
  emissive_r : Double,
  emissive_g : Double,
  emissive_b : Double,
  alpha_mode : Double,
  alpha_cutoff : Double,
  unlit : Bool,
  cast_shadows : Bool,
  receive_shadows : Bool,
) -> Unit =
  #| (cx, cy, cz, radius, qx, qy, qz, qw, r, g, b, a, emissiveR, emissiveG, emissiveB, alphaMode, alphaCutoff, unlit, castShadows, receiveShadows) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.pushSphere3d(cx, cy, cz, radius, qx, qy, qz, qw, r, g, b, a, emissiveR, emissiveG, emissiveB, alphaMode, alphaCutoff, unlit, castShadows, receiveShadows);
  #|   }
  #| }

///|
extern "js" fn webgpu_draw_cylinder_3d(
  center_x : Double,
  center_y : Double,
  center_z : Double,
  radius_top : Double,
  radius_bottom : Double,
  height : Double,
  slices : Int,
  rotation_x : Double,
  rotation_y : Double,
  rotation_z : Double,
  rotation_w : Double,
  r : Double,
  g : Double,
  b : Double,
  a : Double,
  emissive_r : Double,
  emissive_g : Double,
  emissive_b : Double,
  alpha_mode : Double,
  alpha_cutoff : Double,
  unlit : Bool,
  cast_shadows : Bool,
  receive_shadows : Bool,
) -> Unit =
  #| (cx, cy, cz, rtTop, rtBottom, h, slices, qx, qy, qz, qw, r, g, b, a, emissiveR, emissiveG, emissiveB, alphaMode, alphaCutoff, unlit, castShadows, receiveShadows) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.pushCylinder3d(cx, cy, cz, rtTop, rtBottom, h, slices, qx, qy, qz, qw, r, g, b, a, emissiveR, emissiveG, emissiveB, alphaMode, alphaCutoff, unlit, castShadows, receiveShadows);
  #|   }
  #| }

///|
extern "js" fn webgpu_draw_colored_triangles_3d(
  vertices : Array[Double],
  translation_x : Double,
  translation_y : Double,
  translation_z : Double,
  rotation_x : Double,
  rotation_y : Double,
  rotation_z : Double,
  rotation_w : Double,
  double_sided : Bool,
  cast_shadows : Bool,
  receive_shadows : Bool,
) -> Unit =
  #| (vertices, tx, ty, tz, qx, qy, qz, qw, doubleSided, castShadows, receiveShadows) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.pushColoredTriangles3d(vertices, tx, ty, tz, qx, qy, qz, qw, doubleSided, castShadows, receiveShadows);
  #|   }
  #| }

///|
extern "js" fn webgpu_draw_textured_triangles_3d(
  base_path : String,
  emissive_path : String,
  metallic_roughness_path : String,
  occlusion_path : String,
  normal_path : String,
  base_sampler_code : Int,
  emissive_sampler_code : Int,
  metallic_roughness_sampler_code : Int,
  occlusion_sampler_code : Int,
  normal_sampler_code : Int,
  vertices : Array[Double],
  translation_x : Double,
  translation_y : Double,
  translation_z : Double,
  rotation_x : Double,
  rotation_y : Double,
  rotation_z : Double,
  rotation_w : Double,
  double_sided : Bool,
  cast_shadows : Bool,
  receive_shadows : Bool,
) -> Unit =
  #| (basePath, emissivePath, metallicRoughnessPath, occlusionPath, normalPath, baseSamplerCode, emissiveSamplerCode, metallicRoughnessSamplerCode, occlusionSamplerCode, normalSamplerCode, vertices, tx, ty, tz, qx, qy, qz, qw, doubleSided, castShadows, receiveShadows) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.pushTexturedTriangles3d(basePath, emissivePath, metallicRoughnessPath, occlusionPath, normalPath, baseSamplerCode, emissiveSamplerCode, metallicRoughnessSamplerCode, occlusionSamplerCode, normalSamplerCode, vertices, tx, ty, tz, qx, qy, qz, qw, doubleSided, castShadows, receiveShadows);
  #|   }
  #| }

///|
pub extern "js" fn Audio::new(path : String) -> Audio =
  #| (path) => {
  #|   const init = globalThis.__selene_init_audio_element || (
  #|     globalThis.__selene_init_audio_element = (audio) => {
  #|       if (audio.__seleneAudioInitialized) {
  #|         return audio;
  #|       }
  #|       audio.__seleneAudioInitialized = true;
  #|       audio.preload = 'auto';
  #|       audio.__selenePlayState = 0;
  #|       audio.__selenePlayAttemptEpoch = -1;
  #|       audio.addEventListener('playing', () => {
  #|         audio.__selenePlayState = 1;
  #|       });
  #|       audio.addEventListener('pause', () => {
  #|         if (!audio.ended) {
  #|           audio.__selenePlayState = 0;
  #|         }
  #|       });
  #|       audio.addEventListener('error', () => {
  #|         audio.__selenePlayState = 3;
  #|       });
  #|       return audio;
  #|     }
  #|   );
  #|   return init(new Audio(path));
  #| }