import { uniformTypeToNameMapWGSL, UNUSED_UNIFORM_NAME, UNIFORMTYPE_FLOAT, BINDGROUP_MESH_UB, SHADERSTAGE_VERTEX, SHADERSTAGE_FRAGMENT, BINDGROUP_MESH, bindGroupNames, uniformTypeToNameWGSL, SAMPLETYPE_DEPTH, semanticToLocation, TYPE_FLOAT32, TYPE_FLOAT16, TEXTUREDIMENSION_CUBE_ARRAY, TEXTUREDIMENSION_CUBE, TEXTUREDIMENSION_2D_ARRAY, TEXTUREDIMENSION_2D, SAMPLETYPE_UNFILTERABLE_FLOAT, SAMPLETYPE_FLOAT, TEXTUREDIMENSION_3D, TEXTUREDIMENSION_1D, SAMPLETYPE_INT, SAMPLETYPE_UINT, TYPE_INT8, TYPE_INT16, TYPE_INT32 } from '../constants.js';
import { UniformFormat, UniformBufferFormat } from '../uniform-buffer-format.js';
import { BindTextureFormat, BindStorageBufferFormat, BindGroupFormat } from '../bind-group-format.js';

const KEYWORD = /^[ \t]*(attribute|varying|uniform)[\t ]+/gm;
const KEYWORD_LINE = /^[ \t]*(attribute|varying|uniform)[ \t]*([^;]+)(;+)/gm;
const KEYWORD_RESOURCE = /^[ \t]*var\s*(?:(<storage,[^>]*>)\s*([\w\d_]+)\s*:\s*(.*?)\s*;|(<(?!storage,)[^>]*>)?\s*([\w\d_]+)\s*:\s*(texture_.*|storage_texture_.*|storage\w.*|external_texture|sampler(?:_comparison)?)\s*;)\s*$/gm;
const VARYING = /(?:@interpolate\([^)]*\)\s*)?([\w]+)\s*:/;
const MARKER = '@@@';
const ENTRY_FUNCTION = /(@vertex|@fragment)\s*fn\s+\w+\s*\(\s*(\w+)\s*:[\s\S]*?\{/;
const textureBaseInfo = {
		'texture_1d': {
				viewDimension: TEXTUREDIMENSION_1D,
				baseSampleType: SAMPLETYPE_FLOAT
		},
		'texture_2d': {
				viewDimension: TEXTUREDIMENSION_2D,
				baseSampleType: SAMPLETYPE_FLOAT
		},
		'texture_2d_array': {
				viewDimension: TEXTUREDIMENSION_2D_ARRAY,
				baseSampleType: SAMPLETYPE_FLOAT
		},
		'texture_3d': {
				viewDimension: TEXTUREDIMENSION_3D,
				baseSampleType: SAMPLETYPE_FLOAT
		},
		'texture_cube': {
				viewDimension: TEXTUREDIMENSION_CUBE,
				baseSampleType: SAMPLETYPE_FLOAT
		},
		'texture_cube_array': {
				viewDimension: TEXTUREDIMENSION_CUBE_ARRAY,
				baseSampleType: SAMPLETYPE_FLOAT
		},
		'texture_multisampled_2d': {
				viewDimension: TEXTUREDIMENSION_2D,
				baseSampleType: SAMPLETYPE_FLOAT
		},
		'texture_depth_2d': {
				viewDimension: TEXTUREDIMENSION_2D,
				baseSampleType: SAMPLETYPE_DEPTH
		},
		'texture_depth_2d_array': {
				viewDimension: TEXTUREDIMENSION_2D_ARRAY,
				baseSampleType: SAMPLETYPE_DEPTH
		},
		'texture_depth_cube': {
				viewDimension: TEXTUREDIMENSION_CUBE,
				baseSampleType: SAMPLETYPE_DEPTH
		},
		'texture_depth_cube_array': {
				viewDimension: TEXTUREDIMENSION_CUBE_ARRAY,
				baseSampleType: SAMPLETYPE_DEPTH
		},
		'texture_external': {
				viewDimension: TEXTUREDIMENSION_2D,
				baseSampleType: SAMPLETYPE_UNFILTERABLE_FLOAT
		}
};
const getTextureInfo = (baseType, componentType)=>{
		const baseInfo = textureBaseInfo[baseType];
		let finalSampleType = baseInfo.baseSampleType;
		if (baseInfo.baseSampleType === SAMPLETYPE_FLOAT && baseType !== 'texture_multisampled_2d') {
				switch(componentType){
						case 'u32':
								finalSampleType = SAMPLETYPE_UINT;
								break;
						case 'i32':
								finalSampleType = SAMPLETYPE_INT;
								break;
						case 'f32':
								finalSampleType = SAMPLETYPE_FLOAT;
								break;
						case 'uff':
								finalSampleType = SAMPLETYPE_UNFILTERABLE_FLOAT;
								break;
				}
		}
		return {
				viewDimension: baseInfo.viewDimension,
				sampleType: finalSampleType
		};
};
const getTextureDeclarationType = (viewDimension, sampleType)=>{
		if (sampleType === SAMPLETYPE_DEPTH) {
				switch(viewDimension){
						case TEXTUREDIMENSION_2D:
								return 'texture_depth_2d';
						case TEXTUREDIMENSION_2D_ARRAY:
								return 'texture_depth_2d_array';
						case TEXTUREDIMENSION_CUBE:
								return 'texture_depth_cube';
						case TEXTUREDIMENSION_CUBE_ARRAY:
								return 'texture_depth_cube_array';
				}
		}
		let baseTypeString;
		switch(viewDimension){
				case TEXTUREDIMENSION_1D:
						baseTypeString = 'texture_1d';
						break;
				case TEXTUREDIMENSION_2D:
						baseTypeString = 'texture_2d';
						break;
				case TEXTUREDIMENSION_2D_ARRAY:
						baseTypeString = 'texture_2d_array';
						break;
				case TEXTUREDIMENSION_3D:
						baseTypeString = 'texture_3d';
						break;
				case TEXTUREDIMENSION_CUBE:
						baseTypeString = 'texture_cube';
						break;
				case TEXTUREDIMENSION_CUBE_ARRAY:
						baseTypeString = 'texture_cube_array';
						break;
		}
		let coreFormatString;
		switch(sampleType){
				case SAMPLETYPE_FLOAT:
				case SAMPLETYPE_UNFILTERABLE_FLOAT:
						coreFormatString = 'f32';
						break;
				case SAMPLETYPE_UINT:
						coreFormatString = 'u32';
						break;
				case SAMPLETYPE_INT:
						coreFormatString = 'i32';
						break;
		}
		return `${baseTypeString}<${coreFormatString}>`;
};
const wrappedArrayTypes = {
		'f32': 'WrappedF32',
		'i32': 'WrappedI32',
		'u32': 'WrappedU32',
		'vec2f': 'WrappedVec2F',
		'vec2i': 'WrappedVec2I',
		'vec2u': 'WrappedVec2U'
};
const splitToWords = (line)=>{
		line = line.replace(/\s+/g, ' ').trim();
		return line.split(/[\s:]+/);
};
const UNIFORM_ARRAY_REGEX = /array<([^,]+),\s*([^>]+)>/;
class UniformLine {
		constructor(line, shader){
				this.ubName = null;
				this.arraySize = 0;
				this.line = line;
				const parts = splitToWords(line);
				if (parts.length < 2) {
						shader.failed = true;
						return;
				}
				this.name = parts[0];
				this.type = parts.slice(1).join(' ');
				if (this.type.includes('array<')) {
						const match = UNIFORM_ARRAY_REGEX.exec(this.type);
						this.type = match[1].trim();
						this.arraySize = Number(match[2]);
						if (isNaN(this.arraySize)) {
								shader.failed = true;
						}
				}
		}
}
const TEXTURE_REGEX = /^\s*var\s+(\w+)\s*:\s*(texture_\w+)(?:<(\w+)>)?;\s*$/;
const STORAGE_TEXTURE_REGEX = /^\s*var\s+([\w\d_]+)\s*:\s*(texture_storage_2d|texture_storage_2d_array)<([\w\d_]+),\s*(\w+)>\s*;\s*$/;
const STORAGE_BUFFER_REGEX = /^\s*var\s*<storage,\s*(read|write)?>\s*([\w\d_]+)\s*:\s*(.*)\s*;\s*$/;
const EXTERNAL_TEXTURE_REGEX = /^\s*var\s+([\w\d_]+)\s*:\s*texture_external;\s*$/;
const SAMPLER_REGEX = /^\s*var\s+([\w\d_]+)\s*:\s*(sampler|sampler_comparison)\s*;\s*$/;
class ResourceLine {
		constructor(line, shader){
				this.originalLine = line;
				this.line = line;
				this.isTexture = false;
				this.isSampler = false;
				this.isStorageTexture = false;
				this.isStorageBuffer = false;
				this.isExternalTexture = false;
				this.type = '';
				this.matchedElements = [];
				const textureMatch = this.line.match(TEXTURE_REGEX);
				if (textureMatch) {
						this.name = textureMatch[1];
						this.type = textureMatch[2];
						this.textureFormat = textureMatch[3];
						this.isTexture = true;
						this.matchedElements.push(...textureMatch);
						const info = getTextureInfo(this.type, this.textureFormat);
						this.textureDimension = info.viewDimension;
						this.sampleType = info.sampleType;
				}
				const storageTextureMatch = this.line.match(STORAGE_TEXTURE_REGEX);
				if (storageTextureMatch) {
						this.isStorageTexture = true;
						this.name = storageTextureMatch[1];
						this.textureType = storageTextureMatch[2];
						this.format = storageTextureMatch[3];
						this.access = storageTextureMatch[4];
						this.matchedElements.push(...storageTextureMatch);
				}
				const storageBufferMatch = this.line.match(STORAGE_BUFFER_REGEX);
				if (storageBufferMatch) {
						this.isStorageBuffer = true;
						this.accessMode = storageBufferMatch[1] || 'none';
						this.name = storageBufferMatch[2];
						this.type = storageBufferMatch[3];
						this.matchedElements.push(...storageBufferMatch);
				}
				const externalTextureMatch = this.line.match(EXTERNAL_TEXTURE_REGEX);
				if (externalTextureMatch) {
						this.name = externalTextureMatch[1];
						this.isExternalTexture = true;
						this.matchedElements.push(...storageBufferMatch);
				}
				const samplerMatch = this.line.match(SAMPLER_REGEX);
				if (samplerMatch) {
						this.name = samplerMatch[1];
						this.samplerType = samplerMatch[2];
						this.isSampler = true;
						this.matchedElements.push(...samplerMatch);
				}
				if (this.matchedElements.length === 0) {
						shader.failed = true;
				}
		}
		equals(other) {
				if (this.name !== other.name) return false;
				if (this.type !== other.type) return false;
				if (this.isTexture !== other.isTexture) return false;
				if (this.isSampler !== other.isSampler) return false;
				if (this.isStorageTexture !== other.isStorageTexture) return false;
				if (this.isStorageBuffer !== other.isStorageBuffer) return false;
				if (this.isExternalTexture !== other.isExternalTexture) return false;
				if (this.textureFormat !== other.textureFormat) return false;
				if (this.textureDimension !== other.textureDimension) return false;
				if (this.sampleType !== other.sampleType) return false;
				if (this.textureType !== other.textureType) return false;
				if (this.format !== other.format) return false;
				if (this.access !== other.access) return false;
				if (this.accessMode !== other.accessMode) return false;
				if (this.samplerType !== other.samplerType) return false;
				return true;
		}
}
class WebgpuShaderProcessorWGSL {
		static run(device, shaderDefinition, shader) {
				const varyingMap = new Map();
				const vertexExtracted = WebgpuShaderProcessorWGSL.extract(shaderDefinition.vshader);
				const fragmentExtracted = WebgpuShaderProcessorWGSL.extract(shaderDefinition.fshader);
				const attributesMap = new Map();
				const attributesBlock = WebgpuShaderProcessorWGSL.processAttributes(vertexExtracted.attributes, shaderDefinition.attributes, attributesMap, shaderDefinition.processingOptions, shader);
				const vertexVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(vertexExtracted.varyings, varyingMap, true);
				const fragmentVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(fragmentExtracted.varyings, varyingMap, false);
				const concatUniforms = vertexExtracted.uniforms.concat(fragmentExtracted.uniforms);
				const uniforms = Array.from(new Set(concatUniforms));
				const parsedUniforms = uniforms.map((line)=>new UniformLine(line, shader));
				const uniformsData = WebgpuShaderProcessorWGSL.processUniforms(device, parsedUniforms, shaderDefinition.processingOptions, shader);
				vertexExtracted.src = WebgpuShaderProcessorWGSL.renameUniformAccess(vertexExtracted.src, parsedUniforms);
				fragmentExtracted.src = WebgpuShaderProcessorWGSL.renameUniformAccess(fragmentExtracted.src, parsedUniforms);
				const parsedResources = WebgpuShaderProcessorWGSL.mergeResources(vertexExtracted.resources, fragmentExtracted.resources, shader);
				const resourcesData = WebgpuShaderProcessorWGSL.processResources(device, parsedResources, shaderDefinition.processingOptions, shader);
				const fOutput = WebgpuShaderProcessorWGSL.generateFragmentOutputStruct(fragmentExtracted.src, device.maxColorAttachments);
				vertexExtracted.src = WebgpuShaderProcessorWGSL.copyInputs(vertexExtracted.src, shader);
				fragmentExtracted.src = WebgpuShaderProcessorWGSL.copyInputs(fragmentExtracted.src, shader);
				const vBlock = `${attributesBlock}\n${vertexVaryingsBlock}\n${uniformsData.code}\n${resourcesData.code}\n`;
				const vshader = vertexExtracted.src.replace(MARKER, vBlock);
				const fBlock = `${fragmentVaryingsBlock}\n${fOutput}\n${uniformsData.code}\n${resourcesData.code}\n`;
				const fshader = fragmentExtracted.src.replace(MARKER, fBlock);
				return {
						vshader: vshader,
						fshader: fshader,
						attributes: attributesMap,
						meshUniformBufferFormat: uniformsData.meshUniformBufferFormat,
						meshBindGroupFormat: resourcesData.meshBindGroupFormat
				};
		}
		static extract(src) {
				const attributes = [];
				const varyings = [];
				const uniforms = [];
				const resources = [];
				let replacement = `${MARKER}\n`;
				let match;
				while((match = KEYWORD.exec(src)) !== null){
						const keyword = match[1];
						KEYWORD_LINE.lastIndex = match.index;
						const lineMatch = KEYWORD_LINE.exec(src);
						if (keyword === 'attribute') {
								attributes.push(lineMatch[2]);
						} else if (keyword === 'varying') {
								varyings.push(lineMatch[2]);
						} else if (keyword === 'uniform') {
								uniforms.push(lineMatch[2]);
						}
						src = WebgpuShaderProcessorWGSL.cutOut(src, match.index, KEYWORD_LINE.lastIndex, replacement);
						KEYWORD.lastIndex = match.index + replacement.length;
						replacement = '';
				}
				while((match = KEYWORD_RESOURCE.exec(src)) !== null){
						resources.push(match[0]);
						src = WebgpuShaderProcessorWGSL.cutOut(src, match.index, KEYWORD_RESOURCE.lastIndex, replacement);
						KEYWORD_RESOURCE.lastIndex = match.index + replacement.length;
						replacement = '';
				}
				return {
						src,
						attributes,
						varyings,
						uniforms,
						resources
				};
		}
		static processUniforms(device, uniforms, processingOptions, shader) {
				const meshUniforms = [];
				uniforms.forEach((uniform)=>{
						if (!processingOptions.hasUniform(uniform.name)) {
								uniform.ubName = 'ub_mesh_ub';
								const uniformType = uniformTypeToNameMapWGSL.get(uniform.type);
								const uniformFormat = new UniformFormat(uniform.name, uniformType, uniform.arraySize);
								meshUniforms.push(uniformFormat);
						} else {
								uniform.ubName = 'ub_view';
						}
				});
				if (meshUniforms.length === 0) {
						meshUniforms.push(new UniformFormat(UNUSED_UNIFORM_NAME, UNIFORMTYPE_FLOAT));
				}
				const meshUniformBufferFormat = new UniformBufferFormat(device, meshUniforms);
				let code = '';
				processingOptions.uniformFormats.forEach((format, bindGroupIndex)=>{
						if (format) {
								code += WebgpuShaderProcessorWGSL.getUniformShaderDeclaration(format, bindGroupIndex, 0);
						}
				});
				if (meshUniformBufferFormat) {
						code += WebgpuShaderProcessorWGSL.getUniformShaderDeclaration(meshUniformBufferFormat, BINDGROUP_MESH_UB, 0);
				}
				return {
						code,
						meshUniformBufferFormat
				};
		}
		static renameUniformAccess(source, uniforms) {
				uniforms.forEach((uniform)=>{
						const srcName = `uniform.${uniform.name}`;
						const dstName = `${uniform.ubName}.${uniform.name}`;
						const regex = new RegExp(`\\b${srcName}\\b`, 'g');
						source = source.replace(regex, dstName);
				});
				return source;
		}
		static mergeResources(vertex, fragment, shader) {
				const resources = vertex.map((line)=>new ResourceLine(line, shader));
				const fragmentResources = fragment.map((line)=>new ResourceLine(line, shader));
				fragmentResources.forEach((fragmentResource)=>{
						const existing = resources.find((resource)=>resource.name === fragmentResource.name);
						if (existing) {
								if (!existing.equals(fragmentResource)) {
										shader.failed = true;
								}
						} else {
								resources.push(fragmentResource);
						}
				});
				return resources;
		}
		static processResources(device, resources, processingOptions, shader) {
				const textureFormats = [];
				for(let i = 0; i < resources.length; i++){
						const resource = resources[i];
						if (resource.isTexture) {
								const sampler = resources[i + 1];
								const hasSampler = sampler?.isSampler;
								const sampleType = resource.sampleType;
								const dimension = resource.textureDimension;
								textureFormats.push(new BindTextureFormat(resource.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, dimension, sampleType, hasSampler, hasSampler ? sampler.name : null));
								if (hasSampler) i++;
						}
						if (resource.isStorageBuffer) {
								const readOnly = resource.accessMode !== 'read_write';
								const bufferFormat = new BindStorageBufferFormat(resource.name, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT, readOnly);
								bufferFormat.format = resource.type;
								textureFormats.push(bufferFormat);
						}
				}
				const meshBindGroupFormat = new BindGroupFormat(device, textureFormats);
				let code = '';
				processingOptions.bindGroupFormats.forEach((format, bindGroupIndex)=>{
						if (format) {
								code += WebgpuShaderProcessorWGSL.getTextureShaderDeclaration(format, bindGroupIndex, 1);
						}
				});
				code += WebgpuShaderProcessorWGSL.getTextureShaderDeclaration(meshBindGroupFormat, BINDGROUP_MESH, 0);
				return {
						code,
						meshBindGroupFormat
				};
		}
		static getUniformShaderDeclaration(ubFormat, bindGroup, bindIndex) {
				const name = bindGroupNames[bindGroup];
				const structName = `struct_ub_${name}`;
				let code = `struct ${structName} {\n`;
				ubFormat.uniforms.forEach((uniform)=>{
						let typeString = uniformTypeToNameWGSL[uniform.type][0];
						if (uniform.count > 0) {
								if (wrappedArrayTypes.hasOwnProperty(typeString)) {
										typeString = wrappedArrayTypes[typeString];
								}
								code += `    ${uniform.shortName}: array<${typeString}, ${uniform.count}>,\n`;
						} else {
								code += `    ${uniform.shortName}: ${typeString},\n`;
						}
				});
				code += '};\n';
				code += `@group(${bindGroup}) @binding(${bindIndex}) var<uniform> ub_${name} : ${structName};\n\n`;
				return code;
		}
		static getTextureShaderDeclaration(format, bindGroup, startBindIndex) {
				let code = '';
				let bindIndex = startBindIndex;
				format.textureFormats.forEach((format)=>{
						const textureTypeName = getTextureDeclarationType(format.textureDimension, format.sampleType);
						code += `@group(${bindGroup}) @binding(${bindIndex}) var ${format.name}: ${textureTypeName};\n`;
						bindIndex++;
						if (format.hasSampler) {
								const samplerName = format.sampleType === SAMPLETYPE_DEPTH ? 'sampler_comparison' : 'sampler';
								code += `@group(${bindGroup}) @binding(${bindIndex}) var ${format.samplerName}: ${samplerName};\n`;
								bindIndex++;
						}
				});
				format.storageBufferFormats.forEach((format)=>{
						const access = format.readOnly ? 'read' : 'read_write';
						code += `@group(${bindGroup}) @binding(${bindIndex}) var<storage, ${access}> ${format.name} : ${format.format};\n`;
						bindIndex++;
				});
				return code;
		}
		static processVaryings(varyingLines, varyingMap, isVertex) {
				let block = '';
				let blockPrivates = '';
				let blockCopy = '';
				varyingLines.forEach((line, index)=>{
						const match = line.match(VARYING);
						if (match) {
								const name = match[1];
								if (isVertex) {
										varyingMap.set(name, index);
								} else {
										index = varyingMap.get(name);
								}
								block += `    @location(${index}) ${line},\n`;
								if (!isVertex) {
										blockPrivates += `    var<private> ${line};\n`;
										blockCopy += `    ${name} = input.${name};\n`;
								}
						}
				});
				if (isVertex) {
						block += '    @builtin(position) position : vec4f,\n';
				} else {
						block += '    @builtin(position) position : vec4f,\n';
						block += '    @builtin(front_facing) frontFacing : bool,\n';
						block += '    @builtin(sample_index) sampleIndex : u32\n';
				}
				const fragmentGlobals = isVertex ? '' : `
						var<private> pcPosition: vec4f;
						var<private> pcFrontFacing: bool;
						var<private> pcSampleIndex: u32;
						${blockPrivates}
						
						// function to copy inputs (varyings) to private global variables
						fn _pcCopyInputs(input: FragmentInput) {
								${blockCopy}
								pcPosition = input.position;
								pcFrontFacing = input.frontFacing;
								pcSampleIndex = input.sampleIndex;
						}
				`;
				const structName = isVertex ? 'VertexOutput' : 'FragmentInput';
				return `
						struct ${structName} {
								${block}
						};
						${fragmentGlobals}
				`;
		}
		static generateFragmentOutputStruct(src, numRenderTargets) {
				let structCode = 'struct FragmentOutput {\n';
				for(let i = 0; i < numRenderTargets; i++){
						structCode += `    @location(${i}) color${i > 0 ? i : ''} : pcOutType${i},\n`;
				}
				const needsFragDepth = src.search(/\.fragDepth\s*=/) !== -1;
				if (needsFragDepth) {
						structCode += '    @builtin(frag_depth) fragDepth : f32\n';
				}
				return `${structCode}};\n`;
		}
		static floatAttributeToInt(type, signed) {
				const longToShortMap = {
						'f32': 'f32',
						'vec2<f32>': 'vec2f',
						'vec3<f32>': 'vec3f',
						'vec4<f32>': 'vec4f'
				};
				const shortType = longToShortMap[type] || type;
				const floatToIntShort = {
						'f32': signed ? 'i32' : 'u32',
						'vec2f': signed ? 'vec2i' : 'vec2u',
						'vec3f': signed ? 'vec3i' : 'vec3u',
						'vec4f': signed ? 'vec4i' : 'vec4u'
				};
				return floatToIntShort[shortType] || null;
		}
		static processAttributes(attributeLines, shaderDefinitionAttributes = {}, attributesMap, processingOptions, shader) {
				let blockAttributes = '';
				let blockPrivates = '';
				let blockCopy = '';
				attributeLines.forEach((line)=>{
						const words = splitToWords(line);
						const name = words[0];
						let type = words[1];
						const originalType = type;
						if (shaderDefinitionAttributes.hasOwnProperty(name)) {
								const semantic = shaderDefinitionAttributes[name];
								const location = semanticToLocation[semantic];
								attributesMap.set(location, name);
								const element = processingOptions.getVertexElement(semantic);
								if (element) {
										const dataType = element.dataType;
										if (dataType !== TYPE_FLOAT32 && dataType !== TYPE_FLOAT16 && !element.normalize && !element.asInt) {
												const isSignedType = dataType === TYPE_INT8 || dataType === TYPE_INT16 || dataType === TYPE_INT32;
												type = WebgpuShaderProcessorWGSL.floatAttributeToInt(type, isSignedType);
										}
								}
								blockAttributes += `    @location(${location}) ${name}: ${type},\n`;
								blockPrivates += `    var<private> ${line};\n`;
								blockCopy += `    ${name} = ${originalType}(input.${name});\n`;
						}
				});
				return `
						struct VertexInput {
								${blockAttributes}
								@builtin(vertex_index) vertexIndex : u32,       // built-in vertex index
								@builtin(instance_index) instanceIndex : u32    // built-in instance index
						};

						${blockPrivates}
						var<private> pcVertexIndex: u32;
						var<private> pcInstanceIndex: u32;

						fn _pcCopyInputs(input: VertexInput) {
								${blockCopy}
								pcVertexIndex = input.vertexIndex;
								pcInstanceIndex = input.instanceIndex;
						}
				`;
		}
		static copyInputs(src, shader) {
				const match = src.match(ENTRY_FUNCTION);
				if (!match || !match[2]) {
						return src;
				}
				const inputName = match[2];
				const braceIndex = match.index + match[0].length - 1;
				const beginning = src.slice(0, braceIndex + 1);
				const end = src.slice(braceIndex + 1);
				const lineToInject = `\n    _pcCopyInputs(${inputName});`;
				return beginning + lineToInject + end;
		}
		static cutOut(src, start, end, replacement) {
				return src.substring(0, start) + replacement + src.substring(end);
		}
}

export { WebgpuShaderProcessorWGSL };
