import { fromObject } from '@nativescript/core';
import { GPUBuffer } from './GPUBuffer';
import { GPUTexture } from './GPUTexture';
import { adapter_, native_ } from './Constants';
import { GPUShaderModule } from './GPUShaderModule';
import { GPUQueue } from './GPUQueue';
import { GPUPipelineLayout } from './GPUPipelineLayout';
import { parseVertexFormat } from './Utils';
import { GPURenderPipeline } from './GPURenderPipeline';
import { GPUCommandEncoder } from './GPUCommandEncoder';
import { GPUDeviceLostInfo, GPUInternalError, GPUOutOfMemoryError, GPUValidationError } from './Errors';
import { GPUBindGroup } from './GPUBindGroup';
import { GPUBindGroupLayout } from './GPUBindGroupLayout';
import { GPUTextureView } from './GPUTextureView';
import { GPUSampler } from './GPUSampler';
import { GPUExternalTexture } from './GPUExternalTexture';
import { GPUComputePipeline } from './GPUComputePipeline';
import { GPUQuerySet } from './GPUQuerySet';
import { GPURenderBundleEncoder } from './GPURenderBundleEncoder';
import { GPUSupportedFeatures } from './GPUAdapter';
function fixup_shader_code(code) {
    if (!code.includes('#storage')) {
        if (code.includes('atomic_storage'))
            code = '#storage atomic_storage array<atomic<i32>>\n\n' + code;
        if (code.includes('float_storage'))
            code = '#storage float_storage array<vec4<f32>>\n\n' + code;
    }
    code = code.replace(/@stage\(compute\)/g, '@compute');
    code = code.replace(/^type /gm, 'alias ');
    code = code.replace(/^let /gm, 'const ');
    code = code.replace(/alias\s+bool([2-4])\s*=\s*vec\1<\s*bool\s*>\s*;/gm, '');
    code = code.replace(/alias\s+float([2-4])x([2-4])\s*=\s*mat\1x\2<\s*f32\s*>\s*;/gm, '');
    // patch switch issue
    code = code.replace(/case\s+(\d+)\s*:\s*{/g, 'case $1u: {');
    return code;
}
// Doing so because :D
export class EventTarget {
    addEventListener(event, handler, options = {}) {
        const { capture, once } = options;
        if (capture) {
            //   debug("Bubble propagation is not supported");
        }
        if (once) {
            const oldHandler = handler;
            const self = this;
            handler = (...args) => {
                oldHandler.call(null, ...args);
                self.removeEventListener(event, handler);
            };
        }
        let emitter;
        if (global.isAndroid) {
            emitter = this._emitter?.get?.();
        }
        if (global.isIOS) {
            emitter = this._emitter?.deref?.();
        }
        if (emitter !== null && emitter !== undefined) {
            emitter.addEventListener(event, handler, this);
        }
    }
    removeEventListener(event, handler) {
        let emitter;
        if (global.isAndroid) {
            emitter = this._emitter?.get?.();
        }
        if (global.isIOS) {
            emitter = this._emitter?.deref?.();
        }
        if (emitter !== null && emitter !== undefined) {
            emitter.removeEventListener(event, handler);
        }
    }
    dispatchEvent(event) {
        let emitter;
        if (global.isAndroid) {
            emitter = this._emitter?.get?.();
        }
        if (global.isIOS) {
            emitter = this._emitter?.deref?.();
        }
        if (emitter !== null && emitter !== undefined) {
            emitter.notify({ ...event, eventName: event.type, object: emitter });
        }
    }
}
export class GPUDevice extends EventTarget {
    constructor() {
        super();
        this._observerable = fromObject({});
        this._emitter = new WeakRef(this._observerable);
    }
    _uncapturederror(type, message) {
        let emitter;
        if (__ANDROID__) {
            emitter = this._emitter?.get();
        }
        else {
            emitter = this._emitter?.deref();
        }
        let error;
        switch (type) {
            case 1:
                // lost
                // noop
                break;
            case 2:
                // oom
                error = new GPUOutOfMemoryError();
                break;
            case 3:
                // validation
                error = new GPUValidationError(message);
                break;
            case 4:
                // internal
                error = new GPUInternalError();
                break;
        }
        if (emitter) {
            const has = emitter.hasListeners('uncapturederror');
            if (has) {
                emitter.notify({
                    eventName: 'uncapturederror',
                    object: fromObject({}),
                    error,
                });
            }
            else {
                console.error(error);
            }
        }
        else {
            console.error(error);
        }
    }
    static fromNative(device, adapter) {
        if (device) {
            const ret = new GPUDevice();
            device.setuncapturederror(ret._uncapturederror.bind(this));
            ret[native_] = device;
            ret[adapter_] = adapter;
            ret._lost = new Promise((resolve, reject) => {
                ret[native_].lost
                    .then((info) => {
                    const ret = new GPUDeviceLostInfo();
                    ret[native_] = info;
                    resolve(ret);
                })
                    .catch((error) => {
                    reject(error);
                });
            });
            return ret;
        }
        return null;
    }
    get label() {
        return this[native_]?.label ?? '';
    }
    get lost() {
        return this._lost;
    }
    get native() {
        return this[native_];
    }
    get limits() {
        return this.native.limits;
    }
    get features() {
        return new GPUSupportedFeatures(this.native.features);
    }
    destroy() {
        this.native.destroy();
    }
    createBindGroup(descriptor) {
        const desc = {
            layout: descriptor.layout[native_],
            entries: [],
        };
        desc.label = descriptor?.label;
        if (Array.isArray(descriptor.entries)) {
            for (const entry of descriptor.entries) {
                if (entry.resource instanceof GPUTextureView) {
                    desc.entries.push({
                        binding: entry.binding,
                        resource: entry.resource[native_],
                    });
                }
                else if (entry.resource instanceof GPUSampler) {
                    desc.entries.push({
                        binding: entry.binding,
                        resource: entry.resource[native_],
                    });
                }
                else if (entry.resource instanceof GPUExternalTexture) {
                    desc.entries.push({
                        binding: entry.binding,
                        resource: entry.resource[native_],
                    });
                }
                else if (entry?.resource?.buffer && entry?.resource?.buffer instanceof GPUBuffer) {
                    desc.entries.push({
                        binding: entry.binding,
                        resource: {
                            buffer: entry.resource.buffer[native_],
                            offset: entry.resource.offset ?? 0,
                            size: entry.resource.size ?? entry.resource.buffer.size - entry.resource.offset,
                        },
                    });
                }
            }
        }
        const group = this.native.createBindGroup(desc);
        if (group) {
            return GPUBindGroup.fromNative(group);
        }
        return undefined;
    }
    createBindGroupLayout(descriptor) {
        const groupLayout = this.native.createBindGroupLayout(descriptor);
        if (groupLayout) {
            return GPUBindGroupLayout.fromNative(groupLayout);
        }
        return undefined;
    }
    createBuffer(descriptor) {
        const buffer = this.native.createBuffer(descriptor);
        if (buffer) {
            return GPUBuffer.fromNative(buffer, descriptor.mappedAtCreation);
        }
        return undefined;
    }
    createCommandEncoder(descriptor) {
        const encoder = this.native.createCommandEncoder(descriptor);
        return GPUCommandEncoder.fromNative(encoder);
    }
    createComputePipeline(descriptor) {
        const desc = {
            compute: descriptor.compute.module[native_],
            layout: 'auto',
        };
        if (descriptor.layout instanceof GPUPipelineLayout) {
            desc.layout = descriptor.layout[native_];
        }
        return GPUComputePipeline.fromNative(this.native.createComputePipeline(desc));
    }
    createComputePipelineAsync(descriptor) {
        return new Promise((resolve, reject) => {
            if (descriptor.layout instanceof GPUPipelineLayout) {
                descriptor.layout = descriptor.layout[native_];
            }
            descriptor.compute.module = descriptor.compute.module[native_];
            this.native.createComputePipelineAsync(descriptor, (error, pipeline) => {
                if (error) {
                    reject(error.error);
                }
                else {
                    resolve(GPUComputePipeline.fromNative(pipeline));
                }
            });
        });
    }
    createPipelineLayout(descriptor) {
        const desc = {
            label: descriptor.label,
            bindGroupLayouts: descriptor.bindGroupLayouts.map((layout) => {
                return layout[native_];
            }),
        };
        return GPUPipelineLayout.fromNative(this.native.createPipelineLayout(desc));
    }
    createQuerySet(descriptor) {
        const query = this.native.createQuerySet(descriptor);
        return GPUQuerySet.fromNative(query);
    }
    createRenderBundleEncoder(descriptor) {
        return GPURenderBundleEncoder.fromNative(this.native.createRenderBundleEncoder(descriptor));
    }
    createRenderPipeline(descriptor) {
        const desc = {
            vertex: {
                module: descriptor.vertex.module[native_],
            },
            layout: 'auto',
        };
        const vertex = descriptor.vertex;
        const buffers = vertex?.buffers;
        if (Array.isArray(buffers)) {
            desc.vertex.buffers = buffers.map((buffer) => {
                buffer.attributes = buffer.attributes.map((attr) => {
                    attr.format = parseVertexFormat(attr.format);
                    return attr;
                });
                return buffer;
            });
        }
        const fragment = descriptor.fragment;
        if (fragment) {
            handleUnsupportedPlatformFormat(desc.fragment, 'createRenderPipeline');
            desc.fragment = {
                module: fragment.module[native_],
                targets: fragment.targets,
                constants: fragment.constants,
                entryPoint: fragment.entryPoint,
            };
        }
        if (descriptor.depthStencil) {
            desc.depthStencil = descriptor.depthStencil;
        }
        if (descriptor.multisample) {
            desc.multisample = descriptor.multisample;
        }
        if (descriptor.primitive) {
            desc.primitive = descriptor.primitive;
        }
        if (descriptor.layout instanceof GPUPipelineLayout) {
            desc.layout = descriptor.layout[native_];
        }
        return GPURenderPipeline.fromNative(this[native_].createRenderPipeline(desc));
    }
    createRenderPipelineAsync(descriptor) {
        return new Promise((resolve, reject) => {
            const vertex = descriptor.vertex;
            vertex.module = vertex.module[native_];
            const buffers = vertex?.buffers;
            if (Array.isArray(buffers)) {
                vertex.buffers = buffers.map((buffer) => {
                    buffer.attributes = buffer.attributes.map((attr) => {
                        attr.format = parseVertexFormat(attr.format);
                        return attr;
                    });
                    return buffer;
                });
            }
            const fragment = descriptor.fragment;
            if (fragment) {
                handleUnsupportedPlatformFormat(fragment, 'createRenderPipelineAsync');
                fragment.module = fragment.module[native_];
            }
            const layout = descriptor.layout;
            if (layout instanceof GPUPipelineLayout) {
                descriptor.layout = descriptor.layout[native_];
            }
            else {
            }
            this[native_].createRenderPipelineAsync(descriptor, (error, pipeline) => {
                if (error) {
                    reject(error.error);
                }
                else {
                    resolve(GPURenderPipeline.fromNative(pipeline));
                }
            });
        });
    }
    createSampler(descriptor) {
        return GPUSampler.fromNative(this.native.createSampler(descriptor));
    }
    createShaderModule(desc) {
        desc.code = fixup_shader_code(desc.code);
        const module = this.native.createShaderModule(desc);
        if (module) {
            return GPUShaderModule.fromNative(module);
        }
        return undefined;
    }
    createTexture(descriptor) {
        const sizeIsArray = Array.isArray(descriptor.size);
        const opts = {
            label: descriptor?.label,
            mipLevelCount: descriptor?.mipLevelCount ?? 1,
            sampleCount: descriptor?.sampleCount ?? 1,
            dimension: descriptor?.dimension ?? '2d',
            format: descriptor?.format,
            usage: descriptor?.usage,
            viewFormats: descriptor?.viewFormats ?? [],
            width: sizeIsArray ? descriptor.size[0] : descriptor.size?.width,
            height: sizeIsArray ? descriptor.size[1] ?? 1 : descriptor.size?.height ?? 1,
            depthOrArrayLayers: sizeIsArray ? descriptor.size[2] ?? 1 : descriptor.size?.depthOrArrayLayers ?? 1,
        };
        let hasBrga = false;
        if (__ANDROID__) {
            switch (opts.format) {
                case 'bgra8unorm':
                    opts.format = 'rgba8unorm';
                    hasBrga = true;
                    break;
                case 'bgra8unorm-srgb':
                    opts.format = 'rgba8unorm-srgb';
                    hasBrga = true;
                    break;
            }
            if (hasBrga) {
                console.warn(`GPUDevice:createTexture using unsupported brga format falling back to rgba counterpart.`);
            }
        }
        return GPUTexture.fromNative(this.native.createTexture(opts));
    }
    popErrorScope() {
        return new Promise((resolve, reject) => {
            this.native.popErrorScope((type, message) => {
                switch (type) {
                    case 0:
                        resolve(null);
                        break;
                    case 1:
                        // should not reach
                        // throw internal error
                        resolve(new GPUInternalError());
                        break;
                    case 2:
                        resolve(new GPUOutOfMemoryError());
                        break;
                    case 3:
                        resolve(new GPUValidationError(message));
                        break;
                    case 4:
                        resolve(new GPUInternalError());
                        break;
                }
            });
        });
    }
    pushErrorScope(filter) {
        this.native.pushErrorScope(filter);
    }
    get queue() {
        if (!this._queue) {
            this._queue = GPUQueue.fromNative(this[native_].queue);
        }
        return this._queue;
    }
}
function handleUnsupportedPlatformFormat(fragment, method) {
    // falls back to platform supported format ... maybe this can be removed once frameworks use the getPreferredCanvasFormat
    if (__ANDROID__) {
        let hasBrga = false;
        fragment.targets = fragment.targets.map((target) => {
            switch (target.format) {
                case 'bgra8unorm':
                    target.format = 'rgba8unorm';
                    hasBrga = true;
                    break;
                case 'bgra8unorm-srgb':
                    target.format = 'rgba8unorm-srgb';
                    hasBrga = true;
                    break;
            }
            return target;
        });
        if (hasBrga) {
            console.warn(`GPUDevice:${method} using unsupported brga format falling back to rgba counterpart.`);
        }
    }
}
//# sourceMappingURL=GPUDevice.js.map