import { SHADERLANGUAGE_WGSL, SHADERLANGUAGE_GLSL, SEMANTIC_BLENDWEIGHT, SEMANTIC_BLENDINDICES, SEMANTIC_COLOR, SEMANTIC_TEXCOORD7, SEMANTIC_TEXCOORD6, SEMANTIC_TEXCOORD5, SEMANTIC_TEXCOORD4, SEMANTIC_TEXCOORD3, SEMANTIC_TEXCOORD2, SEMANTIC_TEXCOORD1, SEMANTIC_TEXCOORD0, SEMANTIC_TANGENT, SEMANTIC_NORMAL, SEMANTIC_POSITION, primitiveGlslToWgslTypeMap } from './constants.js';
import gles3PS from './shader-chunks/frag/gles3.js';
import gles3VS from './shader-chunks/vert/gles3.js';
import webgpuPS$1 from './shader-chunks/frag/webgpu.js';
import webgpuVS$1 from './shader-chunks/vert/webgpu.js';
import webgpuPS from './shader-chunks/frag/webgpu-wgsl.js';
import webgpuVS from './shader-chunks/vert/webgpu-wgsl.js';
import sharedGLSL from './shader-chunks/frag/shared.js';
import sharedWGSL from './shader-chunks/frag/shared-wgsl.js';

const _attrib2Semantic = {
		vertex_position: SEMANTIC_POSITION,
		vertex_normal: SEMANTIC_NORMAL,
		vertex_tangent: SEMANTIC_TANGENT,
		vertex_texCoord0: SEMANTIC_TEXCOORD0,
		vertex_texCoord1: SEMANTIC_TEXCOORD1,
		vertex_texCoord2: SEMANTIC_TEXCOORD2,
		vertex_texCoord3: SEMANTIC_TEXCOORD3,
		vertex_texCoord4: SEMANTIC_TEXCOORD4,
		vertex_texCoord5: SEMANTIC_TEXCOORD5,
		vertex_texCoord6: SEMANTIC_TEXCOORD6,
		vertex_texCoord7: SEMANTIC_TEXCOORD7,
		vertex_color: SEMANTIC_COLOR,
		vertex_boneIndices: SEMANTIC_BLENDINDICES,
		vertex_boneWeights: SEMANTIC_BLENDWEIGHT
};
class ShaderDefinitionUtils {
		static createDefinition(device, options) {
				const normalizedOutputTypes = (options)=>{
						let fragmentOutputTypes = options.fragmentOutputTypes ?? 'vec4';
						if (!Array.isArray(fragmentOutputTypes)) {
								fragmentOutputTypes = [
										fragmentOutputTypes
								];
						}
						return fragmentOutputTypes;
				};
				const getDefines = (gpu, gl2, isVertex, options)=>{
						const deviceIntro = device.isWebGPU ? gpu : gl2;
						let attachmentsDefine = '';
						if (!isVertex) {
								const fragmentOutputTypes = normalizedOutputTypes(options);
								for(let i = 0; i < device.maxColorAttachments; i++){
										attachmentsDefine += `#define COLOR_ATTACHMENT_${i}\n`;
										const outType = fragmentOutputTypes[i] ?? 'vec4';
										attachmentsDefine += `#define outType_${i} ${outType}\n`;
								}
						}
						return attachmentsDefine + deviceIntro;
				};
				const getDefinesWgsl = (isVertex, options)=>{
						let attachmentsDefine = '';
						if (!isVertex) {
								const fragmentOutputTypes = normalizedOutputTypes(options);
								for(let i = 0; i < device.maxColorAttachments; i++){
										const glslOutType = fragmentOutputTypes[i] ?? 'vec4';
										const wgslOutType = primitiveGlslToWgslTypeMap.get(glslOutType);
										attachmentsDefine += `alias pcOutType${i} = ${wgslOutType};\n`;
								}
						}
						return attachmentsDefine;
				};
				const name = options.name ?? 'Untitled';
				let vertCode;
				let fragCode;
				const vertexDefinesCode = ShaderDefinitionUtils.getDefinesCode(device, options.vertexDefines);
				const fragmentDefinesCode = ShaderDefinitionUtils.getDefinesCode(device, options.fragmentDefines);
				const wgsl = options.shaderLanguage === SHADERLANGUAGE_WGSL;
				if (wgsl) {
						vertCode = `
								${getDefinesWgsl(true, options)}
								${webgpuVS}
								${sharedWGSL}
								${vertexDefinesCode}
								${options.vertexCode}
						`;
						fragCode = `
								${getDefinesWgsl(false, options)}
								${webgpuPS}
								${sharedWGSL}
								${fragmentDefinesCode}
								${options.fragmentCode}
						`;
				} else {
						vertCode = `${ShaderDefinitionUtils.versionCode(device) + getDefines(webgpuVS$1, gles3VS, true, options) + vertexDefinesCode + ShaderDefinitionUtils.precisionCode(device)}
								${sharedGLSL}
								${ShaderDefinitionUtils.getShaderNameCode(name)}
								${options.vertexCode}`;
						fragCode = `${(options.fragmentPreamble || '') + ShaderDefinitionUtils.versionCode(device) + getDefines(webgpuPS$1, gles3PS, false, options) + fragmentDefinesCode + ShaderDefinitionUtils.precisionCode(device)}
								${sharedGLSL}
								${ShaderDefinitionUtils.getShaderNameCode(name)}
								${options.fragmentCode}`;
				}
				return {
						name: name,
						shaderLanguage: options.shaderLanguage ?? SHADERLANGUAGE_GLSL,
						attributes: options.attributes,
						vshader: vertCode,
						vincludes: options.vertexIncludes,
						fincludes: options.fragmentIncludes,
						fshader: fragCode,
						useTransformFeedback: options.useTransformFeedback,
						meshUniformBufferFormat: options.meshUniformBufferFormat,
						meshBindGroupFormat: options.meshBindGroupFormat
				};
		}
		static getDefinesCode(device, defines) {
				let code = '';
				device.capsDefines.forEach((value, key)=>{
						code += `#define ${key} ${value}\n`;
				});
				code += '\n';
				defines?.forEach((value, key)=>{
						code += `#define ${key} ${value}\n`;
				});
				code += '\n';
				return code;
		}
		static getShaderNameCode(name) {
				return `#define SHADER_NAME ${name}\n`;
		}
		static versionCode(device) {
				return device.isWebGPU ? '#version 450\n' : '#version 300 es\n';
		}
		static precisionCode(device, forcePrecision) {
				if (forcePrecision && forcePrecision !== 'highp' && forcePrecision !== 'mediump' && forcePrecision !== 'lowp') {
						forcePrecision = null;
				}
				if (forcePrecision) {
						if (forcePrecision === 'highp' && device.maxPrecision !== 'highp') {
								forcePrecision = 'mediump';
						}
						if (forcePrecision === 'mediump' && device.maxPrecision === 'lowp') {
								forcePrecision = 'lowp';
						}
				}
				const precision = forcePrecision ? forcePrecision : device.precision;
				const code = `
						precision ${precision} float;
						precision ${precision} int;
						precision ${precision} usampler2D;
						precision ${precision} isampler2D;
						precision ${precision} sampler2DShadow;
						precision ${precision} samplerCubeShadow;
						precision ${precision} sampler2DArray;
				`;
				return code;
		}
		static collectAttributes(vsCode) {
				const attribs = {};
				let attrs = 0;
				let found = vsCode.indexOf('attribute');
				while(found >= 0){
						if (found > 0 && vsCode[found - 1] === '/') break;
						let ignore = false;
						if (found > 0) {
								let startOfLine = vsCode.lastIndexOf('\n', found);
								startOfLine = startOfLine !== -1 ? startOfLine + 1 : 0;
								const lineStartString = vsCode.substring(startOfLine, found);
								if (lineStartString.includes('#')) {
										ignore = true;
								}
						}
						if (!ignore) {
								const endOfLine = vsCode.indexOf(';', found);
								const startOfAttribName = vsCode.lastIndexOf(' ', endOfLine);
								const attribName = vsCode.substring(startOfAttribName + 1, endOfLine);
								if (attribs[attribName]) ; else {
										const semantic = _attrib2Semantic[attribName];
										if (semantic !== undefined) {
												attribs[attribName] = semantic;
										} else {
												attribs[attribName] = `ATTR${attrs}`;
												attrs++;
										}
								}
						}
						found = vsCode.indexOf('attribute', found + 1);
				}
				return attribs;
		}
}

export { ShaderDefinitionUtils };
