import { uint8ArrayToBase64, base64ToUint8Array, Vector3 } from '@zephyr3d/base';
import { getEngine } from '../../../app/api.js';
import '../../../scene/scene.js';
import '../../../values.js';
import '../../../render/render_queue.js';
import '../../../render/primitive.js';
import '@zephyr3d/device';
import '../../../material/shader/helper.js';
import '../../../material/lambert.js';
import '../../../material/blinn.js';
import '../../../material/unlit.js';
import '../../../material/particle.js';
import '../../../material/meshmaterial.js';
import '../../../material/grassmaterial.js';
import '../../../material/terrain-cm.js';
import '../../../material/pbrmr.js';
import '../../../material/pbrsg.js';
import '../../../material/pbrblueprint.js';
import '../../../material/sprite.js';
import '../../../material/sprite_std.js';
import { defineProps } from '../types.js';
import '../../blueprint/material/texture.js';
import '../../../render/rendergraph/forward_plus_builder.js';
import '../../../render/sky.js';
import '../../../render/clipmap.js';
import '../../../shaders/atmosphere.js';
import { GraphNode } from '../../../scene/graph_node.js';
import { BoundingBox } from '../../bounding_volume.js';
import '../../../shadow/shadowmapper.js';
import { Mesh } from '../../../scene/mesh.js';
import '../../../scene/basesprite.js';
import '../../../scene/water.js';
import '../../../scene/scene_node.js';
import '../../../scene/particlesys.js';
import '../../../scene/batchgroup.js';
import '../../../scene/terrain-cm/terrain-cm.js';
import '../../../scene/terrain-cm/grass.js';
import '../../../scene/raycast_visitor.js';
import { meshInstanceClsMap } from './common.js';
import { JSONData } from '../json.js';

/** @internal */ function getMeshClass() {
    return {
        ctor: Mesh,
        name: 'Mesh',
        parent: GraphNode,
        noTitle: true,
        createFunc (ctx) {
            const node = new Mesh(ctx.scene);
            node.parent = ctx;
            return {
                obj: node
            };
        },
        getProps () {
            return defineProps([
                {
                    name: 'CastShadow',
                    description: 'If true, the mesh can cast shadows',
                    type: 'bool',
                    default: false,
                    get (value) {
                        value.bool[0] = this.castShadow;
                    },
                    set (value) {
                        this.castShadow = value.bool[0];
                    }
                },
                {
                    name: 'SkinnedBoundingInfo',
                    description: 'Serialized skinned bounding data for the mesh',
                    type: 'string',
                    isHidden () {
                        return true;
                    },
                    get (value) {
                        if (this.skinnedBoundingInfo) {
                            const arr = new Float32Array(24 + 24 + 6 * 3);
                            arr.set(this.skinnedBoundingInfo.boundingVertexBlendIndices, 0);
                            arr.set(this.skinnedBoundingInfo.boundingVertexJointWeights, 24);
                            for(let i = 0; i < 6; i++){
                                arr.set(this.skinnedBoundingInfo.boundingVertices[i], 24 + 24 + i * 3);
                            }
                            value.str[0] = uint8ArrayToBase64(new Uint8Array(arr.buffer));
                        } else {
                            value.str[0] = '';
                        }
                    },
                    set (value) {
                        if (value.str[0]) {
                            const buf = new Float32Array(base64ToUint8Array(value.str[0]).buffer);
                            const boundingVertexBlendIndices = buf.subarray(0, 24);
                            const boundingVertexJointWeights = buf.subarray(24, 48);
                            const boundingVertices = [];
                            for(let i = 0; i < 6; i++){
                                boundingVertices.push(new Vector3(buf.subarray(48 + i * 3, 48 + (i + 1) * 3)));
                            }
                            this.setSkinnedBoundingInfo({
                                boundingVertexBlendIndices,
                                boundingVertexJointWeights,
                                boundingVertices,
                                boundingBox: new BoundingBox()
                            });
                        } else {
                            this.setSkinnedBoundingInfo(null);
                        }
                    }
                },
                {
                    name: 'Skeleton',
                    description: 'Persistent ID of the skeleton bound to this mesh',
                    type: 'string',
                    isHidden () {
                        return false;
                    },
                    get (value) {
                        value.str[0] = this.skeletonName;
                    },
                    set (value) {
                        this.skeletonName = value.str[0];
                    }
                },
                {
                    name: 'MorphData',
                    description: 'Serialized morph-target vertex data',
                    type: 'string',
                    isHidden () {
                        return true;
                    },
                    async get (value) {
                        const morphData = this.getMorphData();
                        if (morphData) {
                            const buffer = new ArrayBuffer(4 + 4 + 4 * 4 * morphData.width * morphData.height);
                            const dataView = new DataView(buffer);
                            dataView.setUint32(0, morphData.width, true);
                            dataView.setUint32(4, morphData.height, true);
                            new Float32Array(buffer, 8, 4 * morphData.width * morphData.height).set(morphData.data);
                            value.str[0] = uint8ArrayToBase64(new Uint8Array(buffer));
                        } else {
                            value.str[0] = '';
                        }
                    },
                    set (value) {
                        if (value.str[0]) {
                            const data = base64ToUint8Array(value.str[0]);
                            const dataView = new DataView(data.buffer);
                            const width = dataView.getUint32(0, true);
                            const height = dataView.getUint32(4, true);
                            const pixels = new Float32Array(data.buffer, 8, 4 * width * height);
                            this.setMorphData({
                                width,
                                height,
                                data: pixels
                            });
                        } else {
                            this.setMorphData(null);
                        }
                    }
                },
                {
                    name: 'MorphTargets',
                    description: 'Named morph-target weights for this mesh',
                    type: 'object',
                    options: {
                        objectTypes: [
                            JSONData
                        ]
                    },
                    isPersistent () {
                        return false;
                    },
                    isHidden () {
                        return !this.getMorphInfo();
                    },
                    get (value) {
                        const morphInfo = this.getMorphInfo();
                        const numTargets = morphInfo.data[3];
                        const data = {};
                        for(let i = 0; i < numTargets; i++){
                            const name = Object.keys(morphInfo.names).find((key)=>morphInfo.names[key] === i);
                            if (name) {
                                Object.defineProperty(data, name, {
                                    enumerable: true,
                                    configurable: true,
                                    get: ()=>this.getMorphWeight(name),
                                    set: (v)=>this.setMorphWeight(name, v)
                                });
                            }
                        }
                        value.object[0] = new JSONData(null, data);
                    }
                },
                {
                    name: 'MorphInfo',
                    description: 'Serialized morph-target layout and metadata',
                    type: 'string',
                    isHidden () {
                        return true;
                    },
                    get (value) {
                        const morphInfo = this.getMorphInfo();
                        if (morphInfo) {
                            const data = new Uint8Array(morphInfo.data.buffer, morphInfo.data.byteOffset, morphInfo.data.byteLength);
                            value.str[0] = JSON.stringify({
                                data: uint8ArrayToBase64(data),
                                names: morphInfo.names
                            });
                        } else {
                            value.str[0] = '';
                        }
                    },
                    set (value) {
                        if (value.str[0]) {
                            try {
                                const info = JSON.parse(value.str[0]);
                                const data = new Float32Array(base64ToUint8Array(info.data).buffer);
                                const names = info.names;
                                this.setMorphInfo({
                                    data,
                                    names
                                });
                            } catch  {
                                const data = new Float32Array(base64ToUint8Array(value.str[0]).buffer);
                                const names = {};
                                for(let i = 0; i < data[3]; i++){
                                    names[`Target${i}`] = i;
                                }
                                this.setMorphInfo({
                                    data,
                                    names
                                });
                            }
                        } else {
                            this.setMorphInfo(null);
                        }
                    }
                },
                {
                    name: 'MorphBoundingBox',
                    description: 'Serialized bounding box for morphed geometry',
                    type: 'string',
                    isHidden () {
                        return true;
                    },
                    get (value) {
                        value.str[0] = '';
                        if (this.getMorphData()) {
                            const box = this.getAnimatedBoundingBox();
                            if (box) {
                                const data = new Float32Array([
                                    ...box.minPoint,
                                    ...box.maxPoint
                                ]);
                                value.str[0] = uint8ArrayToBase64(new Uint8Array(data.buffer));
                            }
                        }
                    },
                    set (value) {
                        if (value.str[0]) {
                            const data = new Float32Array(base64ToUint8Array(value.str[0]).buffer);
                            const bbox = new BoundingBox();
                            bbox.minPoint.setXYZ(data[0], data[1], data[2]);
                            bbox.maxPoint.setXYZ(data[3], data[4], data[5]);
                            this.setAnimatedBoundingBox(bbox);
                        } else {
                            this.setAnimatedBoundingBox(null);
                        }
                    }
                },
                {
                    name: 'Primitive',
                    description: 'Primitive object of this mesh',
                    type: 'object',
                    options: {
                        mimeTypes: [
                            'application/vnd.zephyr3d.mesh+json'
                        ]
                    },
                    get (value) {
                        value.str[0] = this.primitive ? getEngine().resourceManager.getAssetId(this.primitive) ?? '' : '';
                    },
                    async set (value) {
                        if (value?.str[0]) {
                            const primitive = await getEngine().resourceManager.fetchPrimitive(value.str[0]);
                            if (primitive) {
                                this.primitive = primitive;
                            } else {
                                console.error(`Primitive not found: ${value.str[0]}`);
                            }
                        }
                    }
                },
                {
                    name: 'Material',
                    description: 'Material object of this mesh',
                    type: 'object',
                    options: {
                        mimeTypes: [
                            'application/vnd.zephyr3d.material+json'
                        ]
                    },
                    get (value) {
                        const m = this.material?.coreMaterial;
                        value.str[0] = getEngine().resourceManager.getAssetId(m) ?? '';
                    },
                    async set (value) {
                        if (value?.str[0]) {
                            const material = await getEngine().resourceManager.fetchMaterial(value.str[0]);
                            if (material) {
                                this.material = material;
                            } else {
                                console.error(`Material not found: ${value.str[0]}`);
                            }
                        }
                    }
                },
                {
                    name: 'Geometry Instance',
                    description: 'If true, geometry instancing is enabled for this mesh',
                    type: 'bool',
                    get (value) {
                        value.bool[0] = !!this.material?.$isInstance;
                    },
                    set (value) {
                        this.material = value.bool[0] ? this.material?.createInstance() ?? null : this.material?.coreMaterial ?? null;
                    }
                },
                {
                    name: 'MaterialInstanceUniforms',
                    description: 'Per-instance material uniform properties',
                    type: 'object',
                    phase: 1,
                    options: {
                        objectTypes: []
                    },
                    isHidden () {
                        return !!this.material && !this.material?.$isInstance;
                    },
                    isNullable () {
                        return true;
                    },
                    get (value) {
                        const C = this.material?.$isInstance ? meshInstanceClsMap.get(this.material.coreMaterial.constructor) : null;
                        value.object[0] = C ? new C.C(this.material) : null;
                    },
                    set (value) {
                        if (value.object[0]) {
                            this.material = value.object[0]?.material;
                        }
                    }
                }
            ]);
        }
    };
}

export { getMeshClass };
//# sourceMappingURL=mesh.js.map
