'use strict';

/* ────────────────────────────────────────────────────────────────
   WebGPU Render Pipeline — Modern GPU-accelerated rendering
   
   A full WebGPU rendering pipeline for the game engine with:
   - Forward+ rendering with clustered light culling
   - Material system (PBR, unlit, sprite, custom)
   - Multi-pass pipeline (depth pre-pass, color, post-process)
   - Instanced rendering and indirect draw
   - HDR + tone mapping
   - GPU-driven culling
──────────────────────────────────────────────────────────────── */
/** Standard vertex layout: position(3) + normal(3) + uv(2) + tangent(4) */
const STANDARD_VERTEX_LAYOUT = {
    arrayStride: 48, // 12 floats * 4 bytes
    stepMode: 'vertex',
    attributes: [
        { name: 'position', format: 'float32x3', offset: 0, shaderLocation: 0 },
        { name: 'normal', format: 'float32x3', offset: 12, shaderLocation: 1 },
        { name: 'uv', format: 'float32x2', offset: 24, shaderLocation: 2 },
        { name: 'tangent', format: 'float32x4', offset: 32, shaderLocation: 3 },
    ],
};
/** Instance data layout: model matrix (4x4) + color (4) */
const INSTANCE_LAYOUT = {
    arrayStride: 80, // 20 floats * 4 bytes
    stepMode: 'instance',
    attributes: [
        { name: 'modelCol0', format: 'float32x4', offset: 0, shaderLocation: 4 },
        { name: 'modelCol1', format: 'float32x4', offset: 16, shaderLocation: 5 },
        { name: 'modelCol2', format: 'float32x4', offset: 32, shaderLocation: 6 },
        { name: 'modelCol3', format: 'float32x4', offset: 48, shaderLocation: 7 },
        { name: 'instanceColor', format: 'float32x4', offset: 64, shaderLocation: 8 },
    ],
};
const DEFAULT_PBR = {
    type: 'pbr',
    albedo: [1, 1, 1, 1],
    metallic: 0,
    roughness: 0.5,
    normalScale: 1,
    occlusionStrength: 1,
    emissive: [0, 0, 0],
    emissiveIntensity: 1,
    alphaMode: 'opaque',
    alphaCutoff: 0.5,
    doubleSided: false,
};
const DEFAULT_RENDER_CONFIG = {
    canvasFormat: 'bgra8unorm',
    sampleCount: 4,
    maxLights: 256,
    clusteredLighting: {
        tileCountX: 16,
        tileCountY: 9,
        sliceCount: 24,
        maxLightsPerCluster: 64,
    },
    shadows: {
        enabled: true,
        mapSize: 2048,
        cascadeCount: 4,
        cascadeSplitLambda: 0.5,
        bias: 0.001,
        normalBias: 0.02,
        pcfRadius: 2,
    },
    postProcess: {
        enabled: true,
        effects: [],
        hdrEnabled: true,
        toneMapping: 'aces',
        exposure: 1,
        gamma: 2.2,
    },
    maxInstances: 10000,
    depthPrepassEnabled: true,
    frustumCulling: true,
    occlusionCulling: false,
    sortTransparents: true,
};
/* ══════════════════════════════════════════════════════════════
   WGSL SHADER SOURCES
   ══════════════════════════════════════════════════════════════ */
const DEPTH_PREPASS_WGSL = /* wgsl */ `
struct Uniforms {
  viewProj: mat4x4<f32>,
}
@group(0) @binding(0) var<uniform> u: Uniforms;

struct VertexInput {
  @location(0) position: vec3<f32>,
  @location(4) modelCol0: vec4<f32>,
  @location(5) modelCol1: vec4<f32>,
  @location(6) modelCol2: vec4<f32>,
  @location(7) modelCol3: vec4<f32>,
}

@vertex fn vs_main(input: VertexInput) -> @builtin(position) vec4<f32> {
  let model = mat4x4<f32>(input.modelCol0, input.modelCol1, input.modelCol2, input.modelCol3);
  return u.viewProj * model * vec4<f32>(input.position, 1.0);
}
`;
const PBR_FORWARD_WGSL = /* wgsl */ `
struct CameraUniforms {
  viewProj: mat4x4<f32>,
  cameraPos: vec3<f32>,
  exposure: f32,
}

struct MaterialUniforms {
  albedo: vec4<f32>,
  emissive: vec3<f32>,
  metallic: f32,
  roughness: f32,
  normalScale: f32,
  occlusionStrength: f32,
  emissiveIntensity: f32,
}

struct LightData {
  position: vec3<f32>,
  range: f32,
  color: vec3<f32>,
  intensity: f32,
  direction: vec3<f32>,
  lightType: u32, // 0=dir, 1=point, 2=spot
  innerCone: f32,
  outerCone: f32,
  _pad: vec2<f32>,
}

struct LightBuffer {
  count: u32,
  _pad: vec3<u32>,
  lights: array<LightData, 256>,
}

@group(0) @binding(0) var<uniform> camera: CameraUniforms;
@group(0) @binding(1) var<uniform> material: MaterialUniforms;
@group(0) @binding(2) var<storage, read> lightBuf: LightBuffer;

struct VertexOutput {
  @builtin(position) position: vec4<f32>,
  @location(0) worldPos: vec3<f32>,
  @location(1) normal: vec3<f32>,
  @location(2) uv: vec2<f32>,
}

struct VertexInput {
  @location(0) position: vec3<f32>,
  @location(1) normal: vec3<f32>,
  @location(2) uv: vec2<f32>,
  @location(4) modelCol0: vec4<f32>,
  @location(5) modelCol1: vec4<f32>,
  @location(6) modelCol2: vec4<f32>,
  @location(7) modelCol3: vec4<f32>,
  @location(8) instanceColor: vec4<f32>,
}

@vertex fn vs_main(input: VertexInput) -> VertexOutput {
  let model = mat4x4<f32>(input.modelCol0, input.modelCol1, input.modelCol2, input.modelCol3);
  let worldPos = (model * vec4<f32>(input.position, 1.0)).xyz;
  let normalMat = mat3x3<f32>(model[0].xyz, model[1].xyz, model[2].xyz);
  var out: VertexOutput;
  out.position = camera.viewProj * vec4<f32>(worldPos, 1.0);
  out.worldPos = worldPos;
  out.normal = normalize(normalMat * input.normal);
  out.uv = input.uv;
  return out;
}

// Schlick Fresnel
fn fresnelSchlick(cosTheta: f32, F0: vec3<f32>) -> vec3<f32> {
  return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

// GGX Normal Distribution
fn distributionGGX(N: vec3<f32>, H: vec3<f32>, roughness: f32) -> f32 {
  let a = roughness * roughness;
  let a2 = a * a;
  let NdotH = max(dot(N, H), 0.0);
  let denom = (NdotH * NdotH * (a2 - 1.0) + 1.0);
  return a2 / (3.14159265 * denom * denom);
}

// Geometry Smith
fn geometrySmith(N: vec3<f32>, V: vec3<f32>, L: vec3<f32>, roughness: f32) -> f32 {
  let r = roughness + 1.0;
  let k = (r * r) / 8.0;
  let NdotV = max(dot(N, V), 0.0);
  let NdotL = max(dot(N, L), 0.0);
  let ggx1 = NdotV / (NdotV * (1.0 - k) + k);
  let ggx2 = NdotL / (NdotL * (1.0 - k) + k);
  return ggx1 * ggx2;
}

// ACES tone mapping
fn acesToneMap(color: vec3<f32>) -> vec3<f32> {
  let a = 2.51;
  let b = 0.03;
  let c = 2.43;
  let d = 0.59;
  let e = 0.14;
  return clamp((color * (a * color + b)) / (color * (c * color + d) + e), vec3<f32>(0.0), vec3<f32>(1.0));
}

@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
  let albedo = material.albedo.rgb;
  let N = normalize(input.normal);
  let V = normalize(camera.cameraPos - input.worldPos);
  let F0 = mix(vec3<f32>(0.04), albedo, material.metallic);

  var Lo = vec3<f32>(0.0);
  for (var i = 0u; i < lightBuf.count; i++) {
    let light = lightBuf.lights[i];
    var L: vec3<f32>;
    var attenuation: f32 = 1.0;

    if (light.lightType == 0u) {
      L = normalize(-light.direction);
    } else {
      let diff = light.position - input.worldPos;
      let dist = length(diff);
      L = diff / dist;
      if (light.range > 0.0) {
        attenuation = max(1.0 - (dist / light.range), 0.0);
        attenuation *= attenuation;
      }
      if (light.lightType == 2u) {
        let theta = dot(L, normalize(-light.direction));
        let epsilon = light.innerCone - light.outerCone;
        attenuation *= clamp((theta - light.outerCone) / epsilon, 0.0, 1.0);
      }
    }

    let H = normalize(V + L);
    let NDF = distributionGGX(N, H, material.roughness);
    let G = geometrySmith(N, V, L, material.roughness);
    let F = fresnelSchlick(max(dot(H, V), 0.0), F0);

    let kd = (1.0 - F) * (1.0 - material.metallic);
    let NdotL = max(dot(N, L), 0.0);
    let numerator = NDF * G * F;
    let denominator = max(4.0 * max(dot(N, V), 0.0) * NdotL, 0.001);
    let specular = numerator / denominator;

    let radiance = light.color * light.intensity * attenuation;
    Lo += (kd * albedo / 3.14159265 + specular) * radiance * NdotL;
  }

  let ambient = vec3<f32>(0.03) * albedo;
  let emissive = material.emissive * material.emissiveIntensity;
  var color = ambient + Lo + emissive;

  // Tone mapping
  color = acesToneMap(color * camera.exposure);
  // Gamma correction
  color = pow(color, vec3<f32>(1.0 / 2.2));

  return vec4<f32>(color, material.albedo.a);
}
`;
const BLOOM_EXTRACT_WGSL = /* wgsl */ `
@group(0) @binding(0) var inputTex: texture_2d<f32>;
@group(0) @binding(1) var inputSampler: sampler;

struct BloomParams {
  threshold: f32,
  softKnee: f32,
}
@group(0) @binding(2) var<uniform> params: BloomParams;

struct VertexOutput {
  @builtin(position) position: vec4<f32>,
  @location(0) uv: vec2<f32>,
}

@vertex fn vs_fullscreen(@builtin(vertex_index) idx: u32) -> VertexOutput {
  var pos = array<vec2<f32>, 3>(vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));
  var uv = array<vec2<f32>, 3>(vec2(0.0, 1.0), vec2(2.0, 1.0), vec2(0.0, -1.0));
  var out: VertexOutput;
  out.position = vec4<f32>(pos[idx], 0.0, 1.0);
  out.uv = uv[idx];
  return out;
}

@fragment fn fs_extract(input: VertexOutput) -> @location(0) vec4<f32> {
  let color = textureSample(inputTex, inputSampler, input.uv);
  let brightness = dot(color.rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
  let knee = params.threshold * params.softKnee;
  let soft = brightness - params.threshold + knee;
  let contribution = max(0.0, soft * soft / (4.0 * knee + 0.00001));
  let w = max(contribution, select(0.0, 1.0, brightness > params.threshold));
  return vec4<f32>(color.rgb * w, 1.0);
}
`;
/** Track GPU resource allocations */
class GPUResourceTracker {
    constructor() {
        this.resources = new Map();
        this.totalBytes = 0;
    }
    register(id, type, sizeBytes, label = '') {
        this.resources.set(id, { type, sizeBytes, label });
        this.totalBytes += sizeBytes;
    }
    release(id) {
        const r = this.resources.get(id);
        if (r) {
            this.totalBytes -= r.sizeBytes;
            this.resources.delete(id);
        }
    }
    getStats() {
        var _a;
        const byType = {};
        for (const r of this.resources.values()) {
            byType[r.type] = ((_a = byType[r.type]) !== null && _a !== void 0 ? _a : 0) + r.sizeBytes;
        }
        for (const k of Object.keys(byType))
            byType[k] = byType[k] / (1024 * 1024);
        return { count: this.resources.size, totalMB: this.totalBytes / (1024 * 1024), byType };
    }
    clear() {
        this.resources.clear();
        this.totalBytes = 0;
    }
}
/** Collects batches, sorts, and generates draw calls per pass */
class RenderPipelineManager {
    constructor(config = {}) {
        this.passes = [];
        this.batches = [];
        this.lights = [];
        this.stats = {
            drawCalls: 0, triangles: 0, instances: 0,
            textureMegabytes: 0, bufferMegabytes: 0,
            pipelineSwitches: 0, frameTimeMs: 0, gpuTimeMs: 0,
        };
        this.resourceTracker = new GPUResourceTracker();
        this.config = { ...DEFAULT_RENDER_CONFIG, ...config };
        this.initDefaultPasses();
    }
    initDefaultPasses() {
        this.passes = [
            { type: 'depth-prepass', label: 'Depth Pre-pass', enabled: this.config.depthPrepassEnabled, order: 0 },
            { type: 'shadow', label: 'Shadow Map', enabled: this.config.shadows.enabled, order: 1 },
            { type: 'color', label: 'Forward Color', enabled: true, order: 2 },
            { type: 'transparent', label: 'Transparent', enabled: true, order: 3 },
            { type: 'post-process', label: 'Post-Process', enabled: this.config.postProcess.enabled, order: 4 },
            { type: 'ui-overlay', label: 'UI Overlay', enabled: true, order: 5 },
        ];
    }
    getConfig() {
        return this.config;
    }
    updateConfig(patch) {
        this.config = { ...this.config, ...patch };
        this.initDefaultPasses();
    }
    submitBatch(batch) {
        this.batches.push(batch);
    }
    submitLight(light) {
        if (this.lights.length < this.config.maxLights) {
            this.lights.push(light);
        }
    }
    clearFrame() {
        this.batches = [];
        this.lights = [];
        this.stats = {
            drawCalls: 0, triangles: 0, instances: 0,
            textureMegabytes: 0, bufferMegabytes: 0,
            pipelineSwitches: 0, frameTimeMs: 0, gpuTimeMs: 0,
        };
    }
    /** Sort and organize batches for rendering */
    preparePasses() {
        const result = new Map();
        for (const pass of this.passes.filter(p => p.enabled).sort((a, b) => a.order - b.order)) {
            const passBatches = [];
            for (const batch of this.batches) {
                if (pass.type === 'depth-prepass' && batch.material.type !== 'sprite') {
                    passBatches.push(batch);
                }
                else if (pass.type === 'shadow' && batch.castShadow) {
                    passBatches.push(batch);
                }
                else if (pass.type === 'color' && this.isOpaque(batch)) {
                    passBatches.push(batch);
                }
                else if (pass.type === 'transparent' && !this.isOpaque(batch)) {
                    passBatches.push(batch);
                }
            }
            // Sort opaques front-to-back, transparents back-to-front
            if (pass.type === 'color') {
                passBatches.sort((a, b) => a.layer - b.layer);
            }
            else if (pass.type === 'transparent') {
                passBatches.sort((a, b) => b.layer - a.layer);
            }
            result.set(pass.type, passBatches);
        }
        return result;
    }
    isOpaque(batch) {
        const m = batch.material;
        if (m.type === 'pbr' || m.type === 'unlit')
            return m.alphaMode === 'opaque';
        if (m.type === 'sprite')
            return false;
        return true;
    }
    /** Generate draw statistics */
    computeStats() {
        var _a, _b;
        let drawCalls = 0;
        let triangles = 0;
        let instances = 0;
        for (const batch of this.batches) {
            drawCalls++;
            const indexCount = batch.indexData.length;
            const inst = batch.instanceCount || 1;
            triangles += (indexCount / 3) * inst;
            instances += inst;
        }
        this.stats.drawCalls = drawCalls;
        this.stats.triangles = triangles;
        this.stats.instances = instances;
        const resStats = this.resourceTracker.getStats();
        this.stats.textureMegabytes = (_a = resStats.byType['texture']) !== null && _a !== void 0 ? _a : 0;
        this.stats.bufferMegabytes = (_b = resStats.byType['buffer']) !== null && _b !== void 0 ? _b : 0;
        return { ...this.stats };
    }
    getLights() {
        return this.lights;
    }
    getPasses() {
        return this.passes;
    }
    getResourceTracker() {
        return this.resourceTracker;
    }
}
/** High-level WebGPU renderer that manages device, pipelines, and frame submission */
class WebGPURenderer {
    constructor(config = {}) {
        this.state = {
            initialized: false,
            supported: false,
            adapterInfo: '',
            deviceLimits: {},
        };
        this.frameCallback = null;
        this.pipeline = new RenderPipelineManager(config);
        this.state.supported = typeof navigator !== 'undefined' && 'gpu' in navigator;
    }
    /** Check WebGPU support */
    isSupported() {
        return this.state.supported;
    }
    /** Initialize the WebGPU device */
    async initialize() {
        if (!this.state.supported || typeof navigator === 'undefined')
            return false;
        try {
            const adapter = await navigator.gpu.requestAdapter({
                powerPreference: this.pipeline.getConfig().canvasFormat === 'bgra8unorm'
                    ? 'high-performance' : 'low-power',
            });
            if (!adapter)
                return false;
            const device = await adapter.requestDevice({
                requiredLimits: {
                    maxStorageBuffersPerShaderStage: 4,
                    maxStorageBufferBindingSize: 256 * 1024 * 1024,
                },
            });
            this.state.initialized = true;
            this.state.adapterInfo = 'WebGPU adapter ready';
            device.destroy();
            return true;
        }
        catch (_a) {
            return false;
        }
    }
    /** Submit a renderable batch */
    submit(batch) {
        this.pipeline.submitBatch(batch);
    }
    /** Submit a light */
    addLight(light) {
        this.pipeline.submitLight(light);
    }
    /** Execute full render frame */
    renderFrame() {
        this.pipeline.preparePasses();
        const stats = this.pipeline.computeStats();
        if (this.frameCallback)
            this.frameCallback(stats);
        this.pipeline.clearFrame();
        return stats;
    }
    /** Register frame callback */
    onFrame(cb) {
        this.frameCallback = cb;
    }
    getState() {
        return this.state;
    }
    getPipeline() {
        return this.pipeline;
    }
    /** Get available shader sources for debug/customization */
    getShaderSources() {
        return {
            depthPrepass: DEPTH_PREPASS_WGSL,
            pbrForward: PBR_FORWARD_WGSL,
            bloomExtract: BLOOM_EXTRACT_WGSL,
        };
    }
    destroy() {
        this.pipeline.getResourceTracker().clear();
        this.state.initialized = false;
        this.frameCallback = null;
    }
}
const BLOOM_DEFAULTS = {
    type: 'bloom',
    enabled: true,
    intensity: 0.5,
    params: { threshold: 1.0, softKnee: 0.5, radius: 4, iterations: 5 },
};
const FXAA_DEFAULTS = {
    type: 'fxaa',
    enabled: true,
    intensity: 1,
    params: { subpixelQuality: 0.75, edgeThreshold: 0.166, edgeThresholdMin: 0.0833 },
};
const SSAO_DEFAULTS = {
    type: 'ssao',
    enabled: false,
    intensity: 0.5,
    params: { radius: 0.5, bias: 0.025, kernelSize: 32 },
};
const DOF_DEFAULTS = {
    type: 'dof',
    enabled: false,
    intensity: 1,
    params: { focusDistance: 10, aperture: 0.05, maxBlur: 5 },
};
const COLOR_GRADING_DEFAULTS = {
    type: 'color-grading',
    enabled: false,
    intensity: 1,
    params: { temperature: 0, tint: 0, saturation: 1, contrast: 1, brightness: 0 },
};
/** Manage ordered post-process stack */
class PostProcessStack {
    constructor() {
        this.passes = [];
    }
    add(pass) {
        this.passes.push(pass);
    }
    remove(type) {
        this.passes = this.passes.filter(p => p.type !== type);
    }
    setEnabled(type, enabled) {
        const p = this.passes.find(pp => pp.type === type);
        if (p)
            p.enabled = enabled;
    }
    setIntensity(type, intensity) {
        const p = this.passes.find(pp => pp.type === type);
        if (p)
            p.intensity = Math.max(0, Math.min(1, intensity));
    }
    getActive() {
        return this.passes.filter(p => p.enabled);
    }
    getAll() {
        return this.passes;
    }
    reorder(order) {
        const ordered = [];
        for (const type of order) {
            const p = this.passes.find(pp => pp.type === type);
            if (p)
                ordered.push(p);
        }
        // Append any that weren't in the order list
        for (const p of this.passes) {
            if (!ordered.includes(p))
                ordered.push(p);
        }
        this.passes = ordered;
    }
    clear() {
        this.passes = [];
    }
}
const QUALITY_PRESET_CONFIGS = {
    low: {
        sampleCount: 1,
        shadows: { ...DEFAULT_RENDER_CONFIG.shadows, enabled: false },
        postProcess: { ...DEFAULT_RENDER_CONFIG.postProcess, hdrEnabled: false, effects: [] },
        depthPrepassEnabled: false,
        occlusionCulling: false,
    },
    medium: {
        sampleCount: 1,
        shadows: { ...DEFAULT_RENDER_CONFIG.shadows, mapSize: 1024, cascadeCount: 2 },
        postProcess: { ...DEFAULT_RENDER_CONFIG.postProcess, effects: [FXAA_DEFAULTS] },
        depthPrepassEnabled: false,
    },
    high: {
        sampleCount: 4,
        shadows: { ...DEFAULT_RENDER_CONFIG.shadows, mapSize: 2048, cascadeCount: 4 },
        postProcess: { ...DEFAULT_RENDER_CONFIG.postProcess, effects: [FXAA_DEFAULTS, BLOOM_DEFAULTS] },
        depthPrepassEnabled: true,
    },
    ultra: {
        sampleCount: 4,
        shadows: { ...DEFAULT_RENDER_CONFIG.shadows, mapSize: 4096, cascadeCount: 4, pcfRadius: 3 },
        postProcess: {
            ...DEFAULT_RENDER_CONFIG.postProcess,
            effects: [FXAA_DEFAULTS, BLOOM_DEFAULTS, SSAO_DEFAULTS, DOF_DEFAULTS, COLOR_GRADING_DEFAULTS],
        },
        depthPrepassEnabled: true,
        occlusionCulling: true,
    },
};
function applyQualityPreset(preset) {
    return { ...DEFAULT_RENDER_CONFIG, ...QUALITY_PRESET_CONFIGS[preset] };
}

exports.BLOOM_DEFAULTS = BLOOM_DEFAULTS;
exports.COLOR_GRADING_DEFAULTS = COLOR_GRADING_DEFAULTS;
exports.DEFAULT_PBR = DEFAULT_PBR;
exports.DEFAULT_RENDER_CONFIG = DEFAULT_RENDER_CONFIG;
exports.DOF_DEFAULTS = DOF_DEFAULTS;
exports.FXAA_DEFAULTS = FXAA_DEFAULTS;
exports.GPUResourceTracker = GPUResourceTracker;
exports.INSTANCE_LAYOUT = INSTANCE_LAYOUT;
exports.PostProcessStack = PostProcessStack;
exports.QUALITY_PRESET_CONFIGS = QUALITY_PRESET_CONFIGS;
exports.RenderPipelineManager = RenderPipelineManager;
exports.SSAO_DEFAULTS = SSAO_DEFAULTS;
exports.STANDARD_VERTEX_LAYOUT = STANDARD_VERTEX_LAYOUT;
exports.WebGPURenderer = WebGPURenderer;
exports.applyQualityPreset = applyQualityPreset;
//# sourceMappingURL=WebGPURenderPipeline.js.map
