	import * as THREE from 'https://esm.sh/three@0.160.0';
		import { OrbitControls } from 'https://esm.sh/three@0.160.0/examples/jsm/controls/OrbitControls.js';
		import { TransformControls } from 'https://esm.sh/three@0.160.0/examples/jsm/controls/TransformControls.js';
		import { EffectComposer } from 'https://esm.sh/three@0.160.0/examples/jsm/postprocessing/EffectComposer.js';
		import { RenderPass } from 'https://esm.sh/three@0.160.0/examples/jsm/postprocessing/RenderPass.js';
		import { SSAOPass } from 'https://esm.sh/three@0.160.0/examples/jsm/postprocessing/SSAOPass.js';

		class TextureEditorApp {

constructor() {

		this.scene = new THREE.Scene();
		this.camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
const canvas = document.getElementById('threeCanvas');
this.renderer = new THREE.WebGLRenderer({
	canvas: canvas,
	antialias: true
});
		this.composer = true;
		this.ssaoPass = null;
		this.controls = null;
		this.transformControls = null;
		this.pointLight = null;
		this.raycaster = new THREE.Raycaster();
		this.mouse = new THREE.Vector2();
		this.selectable = [];
		this.draggableMeshes = [];
		this.sceneObjects = [];
		this.selectedObject = null;

		this.materials = [];
		this.selectedMaterialIndex = -1;

		this.init().then(() => {
			this.animate();
		});
	
	}

	async init() {
		this.cacheUI();
		

		await this.loadMaterialsFromStorage();

		this.loadPrimitivesFromStorage();
		this.initCamera();
		this.initRenderer();
		this.initLights();
		this.initControls();
		this.initPostProcessing();
		this.initTransformControls();
		this.initThumbnailPreview();

		this.addEventListeners();
		this.addRoom();
		//this.addBigBox();

		this.initMaterialPreview();
		this.refreshMaterialList();


		this.stabilizeResize();


	}

stabilizeResize(attempts = 10) {
	if (attempts <= 0) return;

	this.onResize();

	requestAnimationFrame(() => this.stabilizeResize(attempts - 1));
}
updatePreviewMaterial(mat) {
	if (this.previewSphere) {
		this.previewSphere.material = mat;
	}
}

initMaterialPreview() {
	this.previewScene     = new THREE.Scene();
	this.previewCamera    = new THREE.PerspectiveCamera(45, 1, 0.1, 10);
	this.previewRenderer  = new THREE.WebGLRenderer({
		canvas: document.getElementById('materialPreview'),
		alpha: true,
		antialias: true
	});
	this.previewRenderer.setSize(200, 200);
	this.previewRenderer.setClearColor(0x000000, 0);


//this.previewRenderer.toneMapping = THREE.ACESFilmicToneMapping;
//this.previewRenderer.toneMappingExposure = 1.3;


	this.previewLight = new THREE.PointLight(0xffffff, 2);
	this.previewLight.position.set(2, 2, 2);
	this.previewScene.add(this.previewLight);



	this.previewSphere = new THREE.Mesh(
		new THREE.SphereGeometry(0.6, 32, 32),
		new THREE.MeshStandardMaterial({ color: 0xffffff })
	);
	this.previewScene.add(this.previewSphere);

	this.previewCamera.position.z = 2;
	this.previewCamera.lookAt(0, 0, 0);
}

async loadTexture(loader, url) {

	const texture = await new Promise((resolve) => {
		loader.load(url, (tex) => {
			// Check if it's a normal map based on filename
			const isNormal = url.toLowerCase().includes('normal');

			if (isNormal) {
				tex.encoding      = THREE.LinearEncoding;
				tex.minFilter     = THREE.LinearMipMapLinearFilter;
				tex.magFilter     = THREE.LinearFilter;
			}

			tex.anisotropy = this.renderer.capabilities.getMaxAnisotropy();

			resolve(tex);
		}, undefined, () => resolve(null));
	});

	return texture;
}

async loadMaterialsFromStorage() {

	const data = localStorage.getItem("materials");

	if (!data) {
		return;
	}

	const list = JSON.parse(data);

	const loader = new THREE.TextureLoader();

	for (const item of list) {

		const material = new THREE.MeshStandardMaterial({
			color: new THREE.Color(item.color),
			metalness: item.metalness,
			roughness: item.roughness,
			displacementScale: item.displacementScale,
			opacity: item.opacity,
			transparent: item.opacity < 1.0
		});

		if (item.mapURL) {
			material.map = await this.loadTexture(loader, item.mapURL);
		}

		if (item.normalMapURL) {
		
	const normalMap = await this.loadTexture(loader, item.normalMapURL);

	if (normalMap) {

		normalMap.wrapS        = THREE.RepeatWrapping;

		normalMap.wrapT        = THREE.RepeatWrapping;

		normalMap.anisotropy   = this.renderer.capabilities.getMaxAnisotropy();

		normalMap.generateMipmaps = true;

		normalMap.minFilter    = THREE.LinearMipMapLinearFilter;

		normalMap.magFilter    = THREE.LinearFilter;

		normalMap.encoding     = THREE.LinearEncoding;

		normalMap.needsUpdate  = true;

		material.normalMap     = normalMap;

		material.normalScale   = new THREE.Vector2( 0.5, 0.5 );

	} else {

		console.warn( "Failed to load normal map:", item.normalMapURL );

	}





		}
if (item.displacementMapURL) {

	const displacementMap = await this.loadTexture(loader, item.displacementMapURL);

	if (displacementMap) {

		displacementMap.wrapS        = THREE.RepeatWrapping;
		displacementMap.wrapT        = THREE.RepeatWrapping;
		displacementMap.anisotropy   = this.renderer.capabilities.getMaxAnisotropy();
		displacementMap.minFilter    = THREE.LinearMipMapLinearFilter;
		displacementMap.magFilter    = THREE.LinearFilter;
		displacementMap.generateMipmaps = true;
		displacementMap.needsUpdate  = true;

		material.displacementMap     = displacementMap;
		material.displacementScale   = item.displacementScale ?? 0.03;

	}

}
		if (item.roughnessMapURL) {
			material.roughnessMap = await this.loadTexture(loader, item.roughnessMapURL);
		}

		if (item.metalnessMapURL) {
			material.metalnessMap = await this.loadTexture(loader, item.metalnessMapURL);
		}

		if (item.alphaMapURL) {
			material.alphaMap = await this.loadTexture(loader, item.alphaMapURL);
			material.transparent = true;
		}

		material.needsUpdate = true;

		this.materials.push({
			name: item.name,
			material: material,
			mapURL: item.mapURL,
			normalMapURL: item.normalMapURL,
			roughnessMapURL: item.roughnessMapURL,
			metalnessMapURL: item.metalnessMapURL,
			alphaMapURL: item.alphaMapURL
		});
	}

	this.refreshMaterialList();
}
saveMaterialsToStorage(mat) {

	this.updatePreviewMaterial(mat);

	const list = this.materials.map(({ name, material, mapURL, normalMapURL, roughnessMapURL, metalnessMapURL, alphaMapURL, displacementMapURL }) => {

		const obj = {

			name:               name,
			color:              "#" + material.color.getHexString(),
			metalness:          material.metalness,
			roughness:          material.roughness,
			displacementScale:  material.displacementScale,
			opacity:            material.opacity,
			mapURL:             mapURL || null,
			normalMapURL:       normalMapURL || null,
			roughnessMapURL:    roughnessMapURL || null,
			metalnessMapURL:    metalnessMapURL || null,
			alphaMapURL:        alphaMapURL || null,
			displacementMapURL: displacementMapURL || null

		};

		return obj;

	});

	console.log("saved material count:", list.length);

	localStorage.setItem("materials", JSON.stringify(list));

}

refreshMaterialList() {
	this.matList.innerHTML = '';

	this.materials.forEach(async (entry, index) => {
		const div = document.createElement('div');
		div.style.padding       = '5px';
		div.style.marginBottom  = '10px';
		div.style.border        = '1px solid #333';
		div.style.background    = '#111';
		div.style.color         = '#fff';
		div.style.cursor        = 'grab';
		div.draggable           = true;
		div.dataset.index       = index;

		const img = document.createElement('img');
		img.width         = 216;
		img.height        = 216;
		img.style.display = 'block';
		img.style.margin  = '0 auto 5px auto';
		img.src           = ''; // set later

		const label = document.createElement('div');
		label.textContent     = entry.name;
		label.style.textAlign = 'center';

		div.appendChild(img);
		div.appendChild(label);

		div.addEventListener('dragstart', (e) => {
			e.dataTransfer.setData('materialIndex', index);
		});

		div.addEventListener('click', () => {
			this.matNameInput.value = entry.name;
			this.colorInput.value   = '#' + entry.material.color.getHexString();
			this.selectedMaterialIndex = index;

			this.updatePreviewMaterial(entry.material);

			if (this.selectedObject) {
				this.selectedObject.material = entry.material;
				this.attachMaterialControls(entry.material);
				this.updateUIFromMaterial(entry.material);
			}
		});

		this.matList.appendChild(div);

		// Render preview AFTER textures are ready
		const dataURL = await this.renderThumbnail(entry.material);
		img.src = dataURL;
	});
}

adjustImageBrightness(img, brightnessFactor = 0.75) {
	return new Promise((resolve) => {
		const canvas = document.createElement('canvas');
		canvas.width = img.width;
		canvas.height = img.height;
		const ctx = canvas.getContext('2d');

		img.onload = () => {
			ctx.drawImage(img, 0, 0);
			const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
			const data = imageData.data;

			for (let i = 0; i < data.length; i += 4) {
				data[i]     *= brightnessFactor; // R
				data[i + 1] *= brightnessFactor; // G
				data[i + 2] *= brightnessFactor; // B
			}

			ctx.putImageData(imageData, 0, 0);
			resolve(canvas.toDataURL());
		};

		img.src = img.src; // trigger reload
	});
}

async waitForTextures(material) {
  const maps = ['map', 'normalMap', 'roughnessMap', 'metalnessMap', 'alphaMap'];
  const promises = [];

  maps.forEach(key => {
    const tex = material[key];

    if (tex) {
      if (tex.image) {
        // If image exists and is complete, resolve immediately
        if (tex.image.complete || (tex.image.readyState && tex.image.readyState === 4)) {
          // already loaded
          return;
        }

        // Listen for load event if image exists but not loaded
        promises.push(
          new Promise(resolve => {
            tex.image.addEventListener('load', () => resolve());
            tex.image.addEventListener('error', () => resolve());
          })
        );
      } else {
        // If no image (likely a DataTexture or procedural), wait for onUpdate callback
        promises.push(
          new Promise(resolve => {
            tex.onUpdate = () => {
              resolve();
              tex.onUpdate = null;
            };
          })
        );
      }
    }
  });

  await Promise.all(promises);
}
initThumbnailPreview() {
	this.thumbRenderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
	this.thumbRenderer.setSize( 256, 256 );
	this.thumbRenderer.setClearColor(0x000000, 0);

	this.thumbScene = new THREE.Scene();

	this.thumbCamera = new THREE.PerspectiveCamera(45, 1, 0.1, 10);
	this.thumbCamera.position.z = 2;
	this.thumbCamera.lookAt(0, 0, 0);

	const light = new THREE.DirectionalLight(0xffffff, 1.5);
	light.position.set(4, 4, 4);
	this.thumbScene.add(light);

	// Add ambient light to soften shadows & prevent complete black areas
	this.thumbScene.add(new THREE.AmbientLight(0xffffff, 0.3));

	this.thumbComposer = new EffectComposer(this.thumbRenderer);
	this.thumbComposer.addPass(new RenderPass(this.thumbScene, this.thumbCamera));

	this.thumbSsaoPass = new SSAOPass(this.thumbScene, this.thumbCamera, 64, 64);
	this.thumbSsaoPass.kernelRadius = 6;
	this.thumbSsaoPass.minDistance = 0.02;
	this.thumbSsaoPass.maxDistance = 0.15;
	this.thumbComposer.addPass(this.thumbSsaoPass);
}

async renderThumbnail(material) {
	// Clone the material to avoid modifying the original
	const mat = material.clone();

	// Ensure all texture maps are loaded
	await this.waitForTextures(mat);

	// Mark material for update after textures are loaded
	mat.needsUpdate = true;

	// Remove old mesh if any
	if (this.thumbMesh) {
		this.thumbScene.remove(this.thumbMesh);
		this.thumbMesh.geometry.dispose();
		this.thumbMesh.material.dispose();
		this.thumbMesh = null;
	}

	// Recreate mesh after textures are loaded and material is valid
	this.thumbMesh = new THREE.Mesh(new THREE.SphereGeometry(0.6, 32, 32), mat);
	this.thumbScene.add(this.thumbMesh);

	// Force material update in renderer
	this.thumbRenderer.outputColorSpace = THREE.SRGBColorSpace;
	this.thumbRenderer.toneMapping = THREE.ACESFilmicToneMapping;
	this.thumbRenderer.toneMappingExposure = 1.3;
	this.thumbRenderer.physicallyCorrectLights = true;



	this.thumbRenderer.clear();

	this.thumbComposer.render();

	// Return snapshot
	const dataURL = this.thumbRenderer.domElement.toDataURL();

	// Remove mesh again if this is purely for snapshot (optional)
	this.thumbScene.remove(this.thumbMesh);
	this.thumbMesh.geometry.dispose();
	this.thumbMesh.material.dispose();
	this.thumbMesh = null;

	return dataURL;
}
promptAddMaterial() {

	const matName = prompt("Enter new material name:");

	if (!matName || matName.trim() === "") return;

	const name = matName.trim();

	const material = new THREE.MeshStandardMaterial({ color: 0xffffff });

	this.materials.push({ name: name, material: material });

	this.selectedMaterialIndex = this.materials.length - 1;

	this.matNameInput.value = name;

	this.updatePreviewMaterial(material);

	this.refreshMaterialList();

	this.saveMaterialsToStorage(material);

}

	cacheUI() {
/*
this.loadMaterialFromFolder("Marble002_2K-JPG", "/textures");

this.loadMaterialFromFolder("Terrazzo015_2K-JPG", "/textures");

this.loadMaterialFromFolder("DiamondPlate001_2K-JPG", "/textures");

this.loadMaterialFromFolder("Marble003_2K-JPG", "/textures");

this.loadMaterialFromFolder("Tiles040_2K-JPG", "/textures");

this.loadMaterialFromFolder("DiamondPlate005B_2K-JPG", "/textures");

this.loadMaterialFromFolder("PavingStones072_2K-JPG", "/textures");

this.loadMaterialFromFolder("Tiles041_2K-JPG", "/textures");

this.loadMaterialFromFolder("DiamondPlate005D_2K-JPG", "/textures");

this.loadMaterialFromFolder("PavingStones075_2K-JPG", "/textures");

this.loadMaterialFromFolder("Tiles044_2K-JPG", "/textures");

this.loadMaterialFromFolder("Gravel022_1K-JPG", "/textures");

this.loadMaterialFromFolder("PavingStones085_2K-JPG.usdc", "/textures");

this.loadMaterialFromFolder("Tiles105_2K-JPG", "/textures");

this.loadMaterialFromFolder("Tiles108_2K-JPG", "/textures");

this.loadMaterialFromFolder("Gravel023_1K-JPG", "/textures");

this.loadMaterialFromFolder("Plastic012A_1K-JPG", "/textures");

this.loadMaterialFromFolder("WoodFloor046_2K-JPG", "/textures");

this.loadMaterialFromFolder("Leather034A_2K-JPG", "/textures");

this.loadMaterialFromFolder("Plastic015A_1K-JPG", "/textures");

this.loadMaterialFromFolder("Leather034C_2K-JPG", "/textures");
*/
	this.matNameInput       = document.getElementById("matName");
	this.saveMaterialBtn    = document.getElementById("saveMaterial"); // existing save button
	this.addMaterialBtn     = document.getElementById("addMaterialBtn"); // new add button
	this.matList            = document.getElementById("materialList");




		this.colorInput = document.getElementById('colorPicker');
		this.roughnessInput = document.getElementById('roughnessSlider');
		this.metalnessInput = document.getElementById('metalnessSlider');
		this.displacementInput = document.getElementById('displacementSlider');
		this.opacityInput = document.getElementById('opacitySlider');

		this.inputs = {
			map: document.getElementById('mapInput'),
			normalMap: document.getElementById('normalInput'),
			roughnessMap: document.getElementById('roughnessInput'),
			metalnessMap: document.getElementById('metalnessInput'),
			alphaMap: document.getElementById('alphaInput')
		};

		this.saveMaterialBtn.addEventListener('click', () => this.addOrUpdateMaterial());

	if (this.addMaterialBtn) {
		this.addMaterialBtn.addEventListener("click", () => this.promptAddMaterial());
	}


		for (const mapType in this.inputs) {


			this.inputs[mapType].addEventListener('change', (e) => {
				if (e.target.files.length > 0) {
					this.applyTexture(mapType, e.target.files[0]);
				}
			});

			this.inputs[mapType].addEventListener('dragover', (e) => {
				e.preventDefault();
			});

			this.inputs[mapType].addEventListener('drop', (e) => {
				e.preventDefault();
				const file = e.dataTransfer.files[0];
				if (file) {
					this.applyTexture(mapType, file);
					this.inputs[mapType].files = e.dataTransfer.files; // Optional: visually update the input
				}
			});



		}

		this.colorInput.addEventListener('change', () => {
			if (this.selectedMaterialIndex >= 0) {
				const mat = this.materials[this.selectedMaterialIndex].material;
				mat.color.set(this.colorInput.value);


				this.refreshMaterialList();
				this.saveMaterialsToStorage(mat); // ✅ save after color change
			}
		});

		this.roughnessInput.addEventListener('change', () => {
			if (this.selectedMaterialIndex >= 0) {
				const mat = this.materials[this.selectedMaterialIndex].material;
				mat.roughness = parseFloat(this.roughnessInput.value);
				this.saveMaterialsToStorage(mat); // ✅ save after change
			}
		});

		this.metalnessInput.addEventListener('change', () => {
			if (this.selectedMaterialIndex >= 0) {
				const mat = this.materials[this.selectedMaterialIndex].material;
				mat.metalness = parseFloat(this.metalnessInput.value);
				this.saveMaterialsToStorage(mat); // ✅ save after change
			}
		});

		this.displacementInput.addEventListener('change', () => {
			if (this.selectedMaterialIndex >= 0) {
				const mat = this.materials[this.selectedMaterialIndex].material;
				mat.displacementScale = parseFloat(this.displacementInput.value);
				this.saveMaterialsToStorage(mat); // ✅ save after change
			}
		});

		this.opacityInput.addEventListener('change', () => {
			if (this.selectedMaterialIndex >= 0) {
				const mat = this.materials[this.selectedMaterialIndex].material;
				mat.opacity = parseFloat(this.opacityInput.value);
				mat.transparent = mat.opacity < 1;
				this.saveMaterialsToStorage(mat); // ✅ save after change
			}
		});



		this.primitiveList = document.getElementById('primitiveList');

		Array.from(this.primitiveList.querySelectorAll('[draggable="true"]')).forEach((el) => {
			el.addEventListener('dragstart', (e) => {
				e.dataTransfer.setData('primitiveType', el.dataset.primitive);
			});
		});


		document.body.addEventListener('dragover', (e) => e.preventDefault());
		document.body.addEventListener('drop', this.onDropMaterial.bind(this));
	}

savePrimitivesToStorage() {
	this.primitivesData = [];
console.log("save transform");
	this.sceneObjects.forEach((obj) => {

		if (!obj.userData.isPrimitive) return;

		const data = {
			type: obj.userData.type,
			position: { x: obj.position.x, y: obj.position.y, z: obj.position.z },
			rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
			scale: { x: obj.scale.x, y: obj.scale.y, z: obj.scale.z },
			materialName: null
		};

		// Find material name from your materials list
		for (let i = 0; i < this.materials.length; i++) {
			if (obj.material === this.materials[i].material) {
				data.materialName = this.materials[i].name;
				break;
			}
		}

		this.primitivesData.push(data);
	});
console.log(this.primitivesData);
	localStorage.setItem('primitives', JSON.stringify(this.primitivesData));
}

loadPrimitivesFromStorage() {
	const saved = localStorage.getItem('primitives');
	if (!saved) return;

	try {
		const list = JSON.parse(saved);

		list.forEach((data) => {

			let geometry = null;

			switch(data.type) {
				case 'cube':
					geometry = new THREE.BoxGeometry(1, 1, 1);
					break;
				case 'sphere':
					geometry = new THREE.SphereGeometry(0.5, 32, 32);
					break;
				case 'cone':
					geometry = new THREE.ConeGeometry(0.5, 1, 32);
					break;
				case 'cone':
					geometry = new THREE.ConeGeometry(0.5, 1, 32);
					break;

				case 'cylinder':
					geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
					break;


				// Add more types if you want
			}

			if (!geometry) return;

			// Find material by name or fallback to default
			let mat = this.materials.find(m => m.name === data.materialName);
			if (mat) {
				mat = mat.material;
			} else {
				mat = new THREE.MeshStandardMaterial({ color: '#888888' });
			}

			const mesh = new THREE.Mesh(geometry, mat);

			mesh.position.set(data.position.x, data.position.y, data.position.z);
			mesh.rotation.set(data.rotation.x, data.rotation.y, data.rotation.z);
			mesh.scale.set(data.scale.x, data.scale.y, data.scale.z);

			mesh.castShadow = true;
			mesh.receiveShadow = true;

			mesh.userData.isPrimitive = true;
			mesh.userData.type = data.type;

			this.scene.add(mesh);
			this.selectable.push(mesh);
			this.sceneObjects.push(mesh);
		});

	} catch (e) {
		console.warn('Failed to load primitives', e);
	}
}

onDropPrimitive(event) {

	event.preventDefault();
console.log("drop primitive");
	const primitiveType = event.dataTransfer.getData('primitiveType');

	if (!primitiveType) return;

	const canvas = this.renderer.domElement;
	const rect   = canvas.getBoundingClientRect();

	this.mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
	this.mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;

				

	this.raycaster.setFromCamera(this.mouse, this.camera);

	// Intersect with floor/room to find drop position
	const intersects = this.raycaster.intersectObjects(this.selectable, true);

	let position = new THREE.Vector3(0, 0, 0);

	if (intersects.length > 0) {

		position.copy(intersects[0].point);

	} else {

		// If no intersection, drop at default height and camera forward direction
		position.set(0, 0, 0);

	}

	let geometry;

	switch (primitiveType) {

		case 'cube':

			geometry = new THREE.BoxGeometry(1, 1, 1);

			break;

		case 'sphere':

			geometry = new THREE.SphereGeometry(0.5, 16, 16);

			break;

		case 'cylinder':

			geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 16);

			break;

		default:

			return;

	}

	const material = new THREE.MeshStandardMaterial({ color: 0x7777ff, metalness: 0.3, roughness: 0.6 });

	const mesh = new THREE.Mesh(geometry, material);

	mesh.userData.isPrimitive = true;

	mesh.userData.type = primitiveType; // or 'sphere', 'cone', etc.

	mesh.position.copy(position);

	mesh.castShadow = true;

	mesh.receiveShadow = true;

	mesh.name = primitiveType;


	this.sceneObjects.push(mesh);

	this.scene.add(mesh);

	this.selectable.push(mesh);

}

	onDropMaterial(event) {
		event.preventDefault();
		const materialIndex = event.dataTransfer.getData('materialIndex');
		if (materialIndex === '') return;
		const index = parseInt(materialIndex);
		if (isNaN(index) || !this.materials[index]) return;


	const canvas = this.renderer.domElement;
	const rect   = canvas.getBoundingClientRect();

	this.mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
	this.mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;

				
		this.raycaster.setFromCamera(this.mouse, this.camera);
		const intersects = this.raycaster.intersectObjects(this.selectable, true);



		if (intersects.length > 0) {
			const obj = intersects[0].object;
			obj.material = this.materials[index].material;
			this.attachMaterialControls(obj.material);

			this.savePrimitivesToStorage();
		}
	}

	addOrUpdateMaterial() {
		const name = this.matNameInput.value.trim();
		const color = this.colorInput.value;
		if (!name) return;

		const hexColor = new THREE.Color(color);
		let mat;

		if (this.selectedMaterialIndex >= 0) {
			mat = this.materials[this.selectedMaterialIndex].material;
			mat.color = hexColor;
			this.materials[this.selectedMaterialIndex].name = name;
		} else {
			mat = new THREE.MeshStandardMaterial({ color: hexColor });
			this.materials.push({ name, material: mat });
		}

		if (this.selectedObject) {
			this.selectedObject.material = mat;
			this.attachMaterialControls(mat);
		}

		this.saveMaterialsToStorage(mat);
		this.matNameInput.value = '';
		this.selectedMaterialIndex = -1;
		this.refreshMaterialList();
	}
applyTexture(mapType, fileOrUrl) {

	const textureLoader = new THREE.TextureLoader();

	if (typeof fileOrUrl === 'string') {
		// Normal URL (e.g. '/tiles/texture.jpg')
		textureLoader.load(fileOrUrl, (texture) => {
			this._applyLoadedTexture(mapType, texture, fileOrUrl);
		});

		return;
	}

	// It's a File - convert to Base64 first
	const reader = new FileReader();

	reader.onload = (event) => {
		const base64url = event.target.result;

		textureLoader.load(base64url, (texture) => {
			this._applyLoadedTexture(mapType, texture, base64url);
		});
	};

	reader.readAsDataURL(fileOrUrl);
}

_applyLoadedTexture(mapType, texture, url) {

	if (this.selectedMaterialIndex < 0) return;

	const matWrapper = this.materials[this.selectedMaterialIndex];

	matWrapper.material[mapType] = texture;

	matWrapper.material.needsUpdate = true;

	matWrapper[mapType + 'URL'] = url;

	this.saveMaterialsToStorage(matWrapper.material);

	// If you want to also update UI sliders etc, call a method here if you have one
}

setTextureOnMaterial(mapType, texture, url) {
	if (this.selectedMaterialIndex < 0) return;

	const matEntry = this.materials[this.selectedMaterialIndex];
	const mat = matEntry.material;

	mat[mapType] = texture;

	mat.needsUpdate = true;

	// Save URL if texture is from a path (null if local file blob)
	if (url) {
		matEntry[mapType + 'URL'] = url;
	} else {
		matEntry[mapType + 'URL'] = null;
	}

	this.saveMaterialsToStorage(mat);

	// If a mesh is selected, update its material reference
	if (this.selectedObject) {
		this.selectedObject.material = mat;
	}
}

updateUIFromMaterial(material) {

	this.colorInput.value = '#' + material.color.getHexString();

	this.roughnessInput.value = material.roughness;

	this.metalnessInput.value = material.metalness;

	this.displacementInput.value = material.displacementScale || 0;

	this.opacityInput.value = material.opacity || 1;

	// If needed, add code to update texture preview thumbnails or inputs too

}

	attachMaterialControls(mat) {
		this.updateUIFromMaterial(mat);
		this.colorInput.oninput = () => {
			mat.color.set(this.colorInput.value);
			mat.needsUpdate = true;
			this.refreshMaterialList();
		};
		this.roughnessInput.oninput = () => {
			mat.roughness = parseFloat(this.roughnessInput.value);
			mat.needsUpdate = true;
		};
		this.metalnessInput.oninput = () => {
			mat.metalness = parseFloat(this.metalnessInput.value);
			mat.needsUpdate = true;
		};
		this.displacementInput.oninput = () => {
			mat.displacementScale = parseFloat(this.displacementInput.value);
			mat.needsUpdate = true;
		};
		this.opacityInput.oninput = () => {
			mat.opacity = parseFloat(this.opacityInput.value);
			mat.transparent = mat.opacity < 1;
			mat.needsUpdate = true;
		};
	}


			initCamera() {
				this.camera.position.set(0, 1.5, 6);
				this.camera.lookAt(0, 0, 0);
			}

			initRenderer() {
				//this.renderer.setSize(window.innerWidth, window.innerHeight);
				this.camera.aspect = window.innerWidth / window.innerHeight;
				this.camera.updateProjectionMatrix();
				//this.renderer.setSize(window.innerWidth, window.innerHeight);


				this.renderer.shadowMap.enabled = true;
				this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
				this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
				this.renderer.toneMappingExposure = 1.3;
				this.renderer.setClearColor(0x151515);
				//document.body.appendChild(this.renderer.domElement);


		
				
			


			}

			initLights() {
				this.pointLight = new THREE.PointLight(0xffddaa, 18, 30, 1.5);
				this.pointLight.position.set(0, 1.5, 3.5);
				this.pointLight.castShadow = true;
				this.pointLight.shadow.mapSize.set(2048, 2048);
				this.pointLight.shadow.radius = 2;
				const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.1), new THREE.MeshBasicMaterial({ color: 0xffddaa }));
				this.pointLight.add(bulb);
				this.scene.add(this.pointLight);
				this.scene.add(new THREE.AmbientLight(0xffffff, 0.6));
				this.scene.add(new THREE.HemisphereLight(0xfff0e0, 0x222244, 0.3));
			}

			initControls() {

				this.controls = new OrbitControls( this.camera, this.renderer.domElement );

				this.controls.enableDamping   = true;
				this.controls.dampingFactor   = 0.05;

				this.controls.zoomSpeed       = 0.02;    // Not too small
				this.controls.minDistance     = 4;
				this.controls.maxDistance     = 10;

				this.controls.enableZoom      = true;
				this.controls.mouseButtons = {
					LEFT: THREE.MOUSE.ROTATE,
					MIDDLE: THREE.MOUSE.DOLLY,
					RIGHT: THREE.MOUSE.PAN
				};

			}

			initPostProcessing() {

				this.composer = new EffectComposer(this.renderer);

				const renderPass = new RenderPass(this.scene, this.camera);
				this.composer.addPass(renderPass);

				this.ssaoPass = new SSAOPass(this.scene, this.camera, window.innerWidth, window.innerHeight);
				this.ssaoPass.kernelRadius = 6;
				this.ssaoPass.minDistance  = 0.02;
				this.ssaoPass.maxDistance  = 0.15;

				this.composer.addPass(this.ssaoPass);

				this.composer.renderTarget1.depthTexture = new THREE.DepthTexture();
				this.composer.renderTarget1.depthTexture.type = THREE.UnsignedShortType;

				this.ssaoPass.depthTexture = this.composer.renderTarget1.depthTexture;
			}

			async loadMaterialFromFolder(name, folderPath) {

				const loader = new THREE.TextureLoader();
const mapPaths = {
	mapURL:             `${folderPath}/${name}/${name}_Color.jpg`,
	normalMapURL:       `${folderPath}/${name}/${name}_NormalGL.jpg`,
	roughnessMapURL:    `${folderPath}/${name}/${name}_Roughness.jpg`,
	displacementMapURL: `${folderPath}/${name}/${name}_Displacement.jpg`,
	alphaMapURL:        `${folderPath}/${name}/${name}_Alpha.jpg`,
	aoMapURL:           `${folderPath}/${name}/${name}_AmbientOcclusion.jpg`
};


				console.log(mapPaths);
				const material = new THREE.MeshStandardMaterial({
					color: 0xffffff,
					metalness: 0.2,
					roughness: 0.6,
					displacementScale: 0.03
				});

for (const [key, url] of Object.entries(mapPaths)) {
	try {
		const texture = await new Promise((resolve) => {
			loader.load(url, resolve, undefined, () => resolve(null));
		});

		if (texture) {
			const mapType = key.replace("URL", ""); // mapURL → map
			material[mapType] = texture;
		}
	} catch (err) {
		console.warn("Failed to load texture:", url);
	}
}

				material.needsUpdate = true;

this.materials.push({
	name: name,
	material: material,
	...mapPaths
});

				this.selectedMaterialIndex = this.materials.length - 1;
				console.log("material", this.materials);
				this.refreshMaterialList();

				this.saveMaterialsToStorage(material);
			}

			initTransformControls() {
				this.transformControls = new TransformControls(this.camera, this.renderer.domElement);
				this.transformControls.addEventListener('dragging-changed', (e) => {
					this.controls.enabled = !e.value;
				});


				this.transformControls.addEventListener('objectChange', () => {
					this.savePrimitivesToStorage();
				});

				this.scene.add(this.transformControls);

				document.getElementById('translateBtn').onclick = () => this.transformControls.setMode('translate');
				document.getElementById('rotateBtn').onclick = () => this.transformControls.setMode('rotate');
				document.getElementById('scaleBtn').onclick = () => this.transformControls.setMode('scale');
				document.getElementById('noneBtn').onclick = () => this.transformControls.detach();
			}

			addEventListeners() {

				window.addEventListener('resize', this.onResize.bind(this));

				window.addEventListener('mousemove', this.onMouseMove.bind(this));

				this.renderer.domElement.addEventListener('click', this.onClick.bind(this));

				this.renderer.domElement.addEventListener('dragover', (e) => e.preventDefault());

				this.renderer.domElement.addEventListener('drop', this.onDropPrimitive.bind(this));

				window.addEventListener('resize', this.onResize.bind(this));

				window.addEventListener('mousemove', this.onMouseMove.bind(this));

				this.renderer.domElement.addEventListener('click', this.onClick.bind(this));
			}

			onResize() {
				this.camera.aspect = window.innerWidth / window.innerHeight;
				this.camera.updateProjectionMatrix();
				//this.renderer.setSize(window.innerWidth, window.innerHeight);

				const canvas       = document.getElementById("threeCanvas");

				const rightPanel       = document.getElementById("rightPanel");
				const pixelRatio   = window.devicePixelRatio || 1;

				const width        = canvas.clientWidth;
				const height       = canvas.clientHeight;
				console.log(canvas, rightPanel, width-rightPanel.clientWidth);

				this.renderer.setSize( width-rightPanel.clientWidth, height, false );
				this.renderer.setPixelRatio( pixelRatio );

				if (this.composer) {
					this.composer.setSize(width-rightPanel.clientWidth, height);
					this.ssaoPass.setSize(width-rightPanel.clientWidth, height);
				}


				if (this.composer) this.composer.setSize(window.innerWidth, window.innerHeight);
			}

			onMouseMove(event) {
	const canvas = this.renderer.domElement;
	const rect   = canvas.getBoundingClientRect();

	this.mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
	this.mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;

	this.raycaster.setFromCamera( this.mouse, this.camera );
				this.pointLight.position.x = this.mouse.x * 3;

				this.pointLight.position.y = this.mouse.y * 1 + 1.5;

			}

			onClick(event) {


	const canvas = this.renderer.domElement;
	const rect   = canvas.getBoundingClientRect();

	this.mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
	this.mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;


				this.raycaster.setFromCamera(this.mouse, this.camera);
				const intersects = this.raycaster.intersectObjects(this.selectable, true);
				if (intersects.length > 0) {
					this.selectedObject = intersects[0].object;
					this.transformControls.attach(this.selectedObject);
				}
			}

			addRoom() {
				const textureLoader = new THREE.TextureLoader();
				const material = new THREE.MeshPhysicalMaterial({
					map: textureLoader.load('tiles/Tiles133A_1K-JPG_Color.jpg'),
					normalMap: textureLoader.load('tiles/Tiles133A_1K-JPG_NormalGL.jpg'),
					roughnessMap: textureLoader.load('tiles/Tiles133A_1K-JPG_Roughness.jpg'),
					displacementMap: textureLoader.load('tiles/Tiles133A_1K-JPG_Displacement.jpg'),
					side: THREE.BackSide,
					metalness: 0.2,
					roughness: 0.6,
					displacementScale: 0.01
				});
				const room = new THREE.Mesh(new THREE.BoxGeometry(16, 14, 16), material);
				room.receiveShadow = true;
				this.scene.add(room);
			}

			addBigBox() {
				const boxMaterial = new THREE.MeshStandardMaterial({
					color: 'rgb(34,139,34)',
					metalness: 0.2,
					roughness: 0.7
				});
				const box = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), boxMaterial);
				box.position.set(1.5, 0, 0);
				box.castShadow = true;
				box.receiveShadow = true;
				this.selectable.push(box);
				this.scene.add(box);
			}

			animate() {
				requestAnimationFrame(this.animate.bind(this));
				this.controls.update();
				if (this.composer) {
					this.composer.render();
				} else {
					this.renderer.render(this.scene, this.camera);
				}

				if (this.previewRenderer && this.previewScene && this.previewCamera) {
					this.previewRenderer.render(this.previewScene, this.previewCamera);
				}

			}
		}

		new TextureEditorApp();