class_name OBJLoader
extends RefCounted

#Non si vedevano i materiali e le texture dei vagoni
#Il problema era l'inversione della coordinata V nelle UV, perché:
#OBJ/Blender: V=0 è in alto
#Godot/OpenGL: V=0 è in basso

static func load_obj(path: String) -> ArrayMesh:
	var file = FileAccess.open(path, FileAccess.READ)
	if not file:
		push_error("Impossibile aprire il file: " + path)
		return null
	
	# Directory base per i file relativi (mtl, texture)
	var base_dir = path.get_base_dir()
	
	# Dati geometrici globali
	var vertices: Array[Vector3] = []
	var normals: Array[Vector3] = []
	var uvs: Array[Vector2] = []
	
	# Materiali caricati dal file .mtl
	var materials: Dictionary = {}
	
	# Mesh per ogni materiale: { "nome_materiale": { "v": [], "n": [], "uv": [] } }
	var surfaces: Dictionary = {}
	var current_material: String = "__default__"
	surfaces[current_material] = {"v": [], "n": [], "uv": []}
	
	while not file.eof_reached():
		var line = file.get_line().strip_edges()
		
		if line.begins_with("#") or line.is_empty():
			continue
		
		var parts = line.split(" ", false)
		if parts.is_empty():
			continue
		
		match parts[0]:
			"mtllib":  # File materiale
				var mtl_path = base_dir.path_join(parts[1])
				materials = _load_mtl(mtl_path)
			
			"usemtl":  # Usa materiale
				current_material = parts[1] if parts.size() > 1 else "__default__"
				if not surfaces.has(current_material):
					surfaces[current_material] = {"v": [], "n": [], "uv": []}
			
			"v":  # Vertice
				vertices.append(Vector3(
					float(parts[1]),
					float(parts[2]),
					float(parts[3])
				))
			
			"vn":  # Normale
				normals.append(Vector3(
					float(parts[1]),
					float(parts[2]),
					float(parts[3])
				))
			
			"vt":  # UV
				uvs.append(Vector2(
					float(parts[1]),
					float(parts[2]) if parts.size() > 2 else 0.0
				))
			
			"f":  # Faccia
				var face_vertices: Array = []
				for i in range(1, parts.size()):
					face_vertices.append(_parse_face_vertex(parts[i], vertices, normals, uvs))
				
				# Triangola e aggiungi alla surface corrente
				var surf = surfaces[current_material]
				for i in range(1, face_vertices.size() - 1):
					_add_vertex(face_vertices[0], surf["v"], surf["n"], surf["uv"])
					_add_vertex(face_vertices[i], surf["v"], surf["n"], surf["uv"])
					_add_vertex(face_vertices[i + 1], surf["v"], surf["n"], surf["uv"])
	
	file.close()
	return _create_mesh(surfaces, materials)


static func _load_mtl(path: String) -> Dictionary:
	var materials: Dictionary = {}
	var file = FileAccess.open(path, FileAccess.READ)
	
	if not file:
		push_warning("File MTL non trovato: " + path)
		return materials
	
	var current_mat: String = ""
	var base_dir = path.get_base_dir()
	
	while not file.eof_reached():
		var line = file.get_line().strip_edges()
		
		if line.begins_with("#") or line.is_empty():
			continue
		
		var parts = line.split(" ", false)
		if parts.is_empty():
			continue
		
		match parts[0]:
			"newmtl":  # Nuovo materiale
				current_mat = parts[1]
				materials[current_mat] = {
					"albedo": Color.WHITE,
					"metallic": 0.0,
					"roughness": 1.0,
					"emission": Color.BLACK,
					"alpha": 1.0,
					"texture": "",
					"normal_map": "",
					"roughness_map": "",
					"metallic_map": ""
				}
			
			"Kd":  # Colore diffuso (albedo)
				if current_mat != "":
					materials[current_mat]["albedo"] = Color(
						float(parts[1]),
						float(parts[2]),
						float(parts[3])
					)
			
			"Ks":  # Colore speculare → approssima metallic
				if current_mat != "":
					var spec = (float(parts[1]) + float(parts[2]) + float(parts[3])) / 3.0
					materials[current_mat]["metallic"] = spec
			
			"Ns":  # Esponente speculare → approssima roughness
				if current_mat != "":
					# Ns va da 0 a 1000 tipicamente, invertiamo per roughness
					var ns = float(parts[1])
					materials[current_mat]["roughness"] = 1.0 - clampf(ns / 1000.0, 0.0, 1.0)
			
			"Ke":  # Emissione
				if current_mat != "":
					materials[current_mat]["emission"] = Color(
						float(parts[1]),
						float(parts[2]),
						float(parts[3])
					)
			
			"d":  # Opacità
				if current_mat != "":
					materials[current_mat]["alpha"] = float(parts[1])
			
			"Tr":  # Trasparenza (inverso di d)
				if current_mat != "":
					materials[current_mat]["alpha"] = 1.0 - float(parts[1])
			
			"map_Kd":  # Texture diffusa
				if current_mat != "":
					var tex_path = base_dir.path_join(parts[1])
					materials[current_mat]["texture"] = tex_path
			
			"map_Bump", "bump", "map_bump":  # Normal map
				if current_mat != "":
					# Gestisce anche "-bm 1.0 nomefile.png"
					var tex_path = parts[parts.size() - 1]
					materials[current_mat]["normal_map"] = base_dir.path_join(tex_path)
			
			"map_Pr", "map_Ns":  # Roughness map
				if current_mat != "":
					materials[current_mat]["roughness_map"] = base_dir.path_join(parts[1])
			
			"map_Pm":  # Metallic map
				if current_mat != "":
					materials[current_mat]["metallic_map"] = base_dir.path_join(parts[1])
	
	file.close()
	return materials


static func _parse_face_vertex(data: String, vertices: Array, normals: Array, uvs: Array) -> Dictionary:
	var parts = data.split("/")
	var result = {"v": null, "vt": null, "vn": null}
	
	# Indice vertice (obbligatorio)
	var v_idx = int(parts[0])
	if v_idx < 0:
		v_idx = vertices.size() + v_idx
	else:
		v_idx -= 1
	result["v"] = vertices[v_idx]
	
	# Indice UV (opzionale)
	if parts.size() > 1 and not parts[1].is_empty():
		var vt_idx = int(parts[1])
		if vt_idx < 0:
			vt_idx = uvs.size() + vt_idx
		else:
			vt_idx -= 1
		var uv = uvs[vt_idx]
		# INVERTI LA COORDINATA V
		result["vt"] = Vector2(uv.x, 1.0 - uv.y)
	
	# Indice normale (opzionale)
	if parts.size() > 2 and not parts[2].is_empty():
		var vn_idx = int(parts[2])
		if vn_idx < 0:
			vn_idx = normals.size() + vn_idx
		else:
			vn_idx -= 1
		result["vn"] = normals[vn_idx]
	
	return result
	

static func _add_vertex(data: Dictionary, mesh_v: Array, mesh_n: Array, mesh_uv: Array):
	mesh_v.append(data["v"])
	if data["vn"]:
		mesh_n.append(data["vn"])
	if data["vt"]:
		mesh_uv.append(data["vt"])


static func _create_mesh(surfaces: Dictionary, materials: Dictionary) -> ArrayMesh:
	var mesh = ArrayMesh.new()
	
	for mat_name in surfaces.keys():
		var surf = surfaces[mat_name]
		var verts: Array = surf["v"]
		var norms: Array = surf["n"]
		var uv: Array = surf["uv"]
		
		if verts.is_empty():
			continue
		
		var arrays = []
		arrays.resize(Mesh.ARRAY_MAX)
		
		arrays[Mesh.ARRAY_VERTEX] = PackedVector3Array(verts)
		
		if not norms.is_empty() and norms.size() == verts.size():
			arrays[Mesh.ARRAY_NORMAL] = PackedVector3Array(norms)
		
		if not uv.is_empty() and uv.size() == verts.size():
			arrays[Mesh.ARRAY_TEX_UV] = PackedVector2Array(uv)
		
		mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
		
		var material = _create_material(mat_name, materials)
		mesh.surface_set_material(mesh.get_surface_count() - 1, material)
	
	return mesh


static func _create_material(mat_name: String, materials: Dictionary) -> StandardMaterial3D:
	var mat = StandardMaterial3D.new()
	
	# DEBUG: Disabilita culling e rendi visibile senza luce
	mat.cull_mode = BaseMaterial3D.CULL_DISABLED
	mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED  # Ignora illuminazione
	
	if not materials.has(mat_name):
		mat.albedo_color = Color(0.8, 0.8, 0.8)
		return mat
	
	var data: Dictionary = materials[mat_name]
	
	mat.albedo_color = data["albedo"]
	mat.metallic = data["metallic"]
	mat.roughness = data["roughness"]
	
	if data["emission"] != Color.BLACK:
		mat.emission_enabled = true
		mat.emission = data["emission"]
	
	if data["alpha"] < 1.0:
		mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
		mat.albedo_color.a = data["alpha"]
	
	if data["texture"] != "":
		var tex = _load_texture(data["texture"])
		if tex:
			mat.albedo_texture = tex
	
	return mat


static func _load_texture(path: String) -> Texture2D:
	
	# Metodo 1: Risorsa importata da Godot (per res://)
	if ResourceLoader.exists(path):
		var res = ResourceLoader.load(path)
		if res is Texture2D:
			return res
		elif res is Image:
			return ImageTexture.create_from_image(res)
	
	# Metodo 2: Prova il percorso senza spazi
	var clean_path = path.replace(" ", "%20")
	if clean_path != path and ResourceLoader.exists(clean_path):
		var res = ResourceLoader.load(clean_path)
		if res is Texture2D:
			return res
	
	# Metodo 3: File raw (per user:// o path assoluti)
	if FileAccess.file_exists(path):
		var file = FileAccess.open(path, FileAccess.READ)
		if not file:
			push_warning("Impossibile aprire: " + path)
			return null
		
		var buffer = file.get_buffer(file.get_length())
		file.close()
		
		var image = Image.new()
		var error: Error
		
		var ext = path.get_extension().to_lower()
		match ext:
			"png":
				error = image.load_png_from_buffer(buffer)
			"jpg", "jpeg":
				error = image.load_jpg_from_buffer(buffer)
			"webp":
				error = image.load_webp_from_buffer(buffer)
			"bmp":
				error = image.load_bmp_from_buffer(buffer)
			"tga":
				error = image.load_tga_from_buffer(buffer)
			_:
				push_warning("Formato non supportato: " + ext)
				return null
		
		if error != OK:
			push_warning("Errore caricamento: " + str(error))
			return null
		
		return ImageTexture.create_from_image(image)
	
	push_warning("Texture NON trovata: " + path)
	return null
