class_name MeshLibraryBuilder
extends RefCounted

# Builds a MeshLibrary at runtime for the 3D dungeon demo. No art assets —
# BoxMesh / PlaneMesh primitives with tinted StandardMaterial3D. Each tile
# ID here matches a terrain enum the renderer cares about.

# Tile IDs (exposed as constants so demo_3d.gd can pass them to
# GridMap.set_cell_item).
const TILE_FLOOR       := 0
const TILE_WALL        := 1
const TILE_DOOR        := 2
const TILE_CORRIDOR    := 3
const TILE_WATER       := 4
const TILE_LAVA        := 5
const TILE_BRIMSTONE   := 6
const TILE_BRIDGE      := 7
const TILE_STAIRS_UP   := 8
const TILE_STAIRS_DOWN := 9

# Floors and liquid surfaces are thin slabs centered at the cell origin.
# Walls are tall enough that the player (camera ~2.6u) can't see over them.
# Doors are chest-height so they read as entry markers without blocking view
# along corridors — can raise to full height once we have swinging doors.
const CELL_SIZE := 1.0
const WALL_HEIGHT := 3.0
const FLOOR_THICKNESS := 0.05
const LIQUID_THICKNESS := 0.05
const DOOR_HEIGHT := 0.8
const BRIDGE_THICKNESS := 0.1
const STAIRS_HEIGHT := 0.7

static func build(with_collision: bool = true) -> MeshLibrary:
	var lib := MeshLibrary.new()
	_add_slab(lib, TILE_FLOOR,     "Floor",     _mat(Color(0.64, 0.54, 0.42)), FLOOR_THICKNESS, 0.0)
	_add_cube(lib, TILE_WALL,      "Wall",      _mat(Color(0.28, 0.28, 0.32)), WALL_HEIGHT, WALL_HEIGHT * 0.5)
	_add_cube(lib, TILE_DOOR,      "Door",      _mat(Color(0.72, 0.36, 0.20)), DOOR_HEIGHT, DOOR_HEIGHT * 0.5)
	_add_slab(lib, TILE_CORRIDOR,  "Corridor",  _mat(Color(0.44, 0.37, 0.29)), FLOOR_THICKNESS, 0.0)
	_add_slab(lib, TILE_WATER,     "Water",     _mat_trans(Color(0.18, 0.42, 0.78, 0.75)), LIQUID_THICKNESS, -0.05)
	_add_slab(lib, TILE_LAVA,      "Lava",      _mat_emissive(Color(0.90, 0.30, 0.12), Color(1.00, 0.45, 0.15), 1.5), LIQUID_THICKNESS, -0.05)
	_add_slab(lib, TILE_BRIMSTONE, "Brimstone", _mat_emissive(Color(0.72, 0.50, 0.22), Color(0.95, 0.65, 0.25), 0.8), LIQUID_THICKNESS, -0.05)
	_add_slab(lib, TILE_BRIDGE,    "Bridge",    _mat(Color(0.55, 0.38, 0.22)), BRIDGE_THICKNESS, 0.0)
	_add_ramp(lib, TILE_STAIRS_UP,   "StairsUp",   _mat(Color(0.74, 0.88, 0.94)), true)
	_add_ramp(lib, TILE_STAIRS_DOWN, "StairsDown", _mat(Color(0.20, 0.44, 0.80)), false)

	if with_collision:
		_attach_collision(lib)
	return lib

# Bake the library to a .tres so the CLI and runtime can share the same asset
# instead of rebuilding it each time a scene loads.
static func save_resource(path: String, with_collision: bool = true) -> Error:
	return ResourceSaver.save(build(with_collision), path)

# --- collision shapes ---

# Every tile except chasm (which has no tile at all) gets a box collider
# sized to match its mesh. Chasms therefore let the player fall through
# cleanly onto the level below. Stairs are treated as solid blocks; walking
# onto them works well enough with gravity + move_and_slide.
static func _attach_collision(lib: MeshLibrary) -> void:
	# Shape, y_offset pairs, in the same geometry as the meshes above.
	var floor_shape := _box_shape(Vector3(CELL_SIZE, FLOOR_THICKNESS, CELL_SIZE))
	var wall_shape := _box_shape(Vector3(CELL_SIZE, WALL_HEIGHT, CELL_SIZE))
	var door_shape := _box_shape(Vector3(CELL_SIZE, DOOR_HEIGHT, CELL_SIZE))
	var liquid_shape := _box_shape(Vector3(CELL_SIZE, LIQUID_THICKNESS, CELL_SIZE))
	var bridge_shape := _box_shape(Vector3(CELL_SIZE, BRIDGE_THICKNESS, CELL_SIZE))
	var stairs_shape := _box_shape(Vector3(CELL_SIZE, STAIRS_HEIGHT, CELL_SIZE))

	_set_shape(lib, TILE_FLOOR,     floor_shape,  FLOOR_THICKNESS * 0.5)
	_set_shape(lib, TILE_WALL,      wall_shape,   WALL_HEIGHT * 0.5)
	_set_shape(lib, TILE_DOOR,      door_shape,   DOOR_HEIGHT * 0.5)
	_set_shape(lib, TILE_CORRIDOR,  floor_shape,  FLOOR_THICKNESS * 0.5)
	_set_shape(lib, TILE_WATER,     liquid_shape, -0.05 + LIQUID_THICKNESS * 0.5)
	_set_shape(lib, TILE_LAVA,      liquid_shape, -0.05 + LIQUID_THICKNESS * 0.5)
	_set_shape(lib, TILE_BRIMSTONE, liquid_shape, -0.05 + LIQUID_THICKNESS * 0.5)
	_set_shape(lib, TILE_BRIDGE,    bridge_shape, BRIDGE_THICKNESS * 0.5)
	_set_shape(lib, TILE_STAIRS_UP,   stairs_shape, STAIRS_HEIGHT * 0.5)
	_set_shape(lib, TILE_STAIRS_DOWN, stairs_shape, STAIRS_HEIGHT * 0.5)

static func _box_shape(size: Vector3) -> BoxShape3D:
	var b := BoxShape3D.new()
	b.size = size
	return b

static func _set_shape(lib: MeshLibrary, id: int, shape: Shape3D, y: float) -> void:
	var t := Transform3D.IDENTITY
	t.origin = Vector3(0, y, 0)
	lib.set_item_shapes(id, [shape, t])

# --- material helpers ---

static func _mat(base: Color) -> StandardMaterial3D:
	var m := StandardMaterial3D.new()
	m.albedo_color = base
	m.roughness = 0.8
	m.metallic = 0.0
	return m

static func _mat_trans(base: Color) -> StandardMaterial3D:
	var m := _mat(base)
	m.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
	return m

static func _mat_emissive(base: Color, emis: Color, strength: float) -> StandardMaterial3D:
	var m := _mat(base)
	m.emission_enabled = true
	m.emission = emis
	m.emission_energy_multiplier = strength
	return m

# --- mesh builders ---

# A flat slab filling the cell's XZ footprint, with the given thickness and
# base at y_base (the slab extends UP from y_base by `thickness`).
static func _add_slab(lib: MeshLibrary, id: int, name: String,
					  mat: StandardMaterial3D,
					  thickness: float, y_base: float) -> void:
	var mesh := BoxMesh.new()
	mesh.size = Vector3(CELL_SIZE, thickness, CELL_SIZE)
	mesh.material = mat
	var t := Transform3D.IDENTITY
	t.origin = Vector3(0, y_base + thickness * 0.5, 0)
	_install(lib, id, name, mesh, t)

# A cube centered on the cell. y_center is the Y of the cube's center.
static func _add_cube(lib: MeshLibrary, id: int, name: String,
					  mat: StandardMaterial3D,
					  height: float, y_center: float) -> void:
	var mesh := BoxMesh.new()
	mesh.size = Vector3(CELL_SIZE, height, CELL_SIZE)
	mesh.material = mat
	var t := Transform3D.IDENTITY
	t.origin = Vector3(0, y_center, 0)
	_install(lib, id, name, mesh, t)

# Simple visual ramp using two cubes at different heights. Not geometrically
# correct stairs — just enough to read as "stairs" from flight.
# going_up=true means the tall step is at +Z; false means the tall step is
# at -Z (visually going down).
static func _add_ramp(lib: MeshLibrary, id: int, name: String,
					  mat: StandardMaterial3D, going_up: bool) -> void:
	# GridMap.set_mesh expects a single Mesh. Fake the steps by stacking two
	# BoxMeshes into an ArrayMesh via SurfaceTool.
	var st := SurfaceTool.new()
	st.begin(Mesh.PRIMITIVE_TRIANGLES)
	st.set_material(mat)

	var tall := STAIRS_HEIGHT
	var short := STAIRS_HEIGHT * 0.35
	var half := CELL_SIZE * 0.25

	# Step 1: back (taller going up, shorter going down).
	var z_back := half if going_up else -half
	_append_box(st, Vector3(0, (tall if going_up else short) * 0.5, z_back),
				Vector3(CELL_SIZE, (tall if going_up else short), CELL_SIZE * 0.5))
	# Step 2: front.
	var z_front := -half if going_up else half
	_append_box(st, Vector3(0, (short if going_up else tall) * 0.5, z_front),
				Vector3(CELL_SIZE, (short if going_up else tall), CELL_SIZE * 0.5))

	var arr := st.commit()
	_install(lib, id, name, arr, Transform3D.IDENTITY)

static func _append_box(st: SurfaceTool, center: Vector3, size: Vector3) -> void:
	var h := size * 0.5
	var c := center
	var v := [
		c + Vector3(-h.x, -h.y, -h.z),  # 0
		c + Vector3( h.x, -h.y, -h.z),  # 1
		c + Vector3( h.x,  h.y, -h.z),  # 2
		c + Vector3(-h.x,  h.y, -h.z),  # 3
		c + Vector3(-h.x, -h.y,  h.z),  # 4
		c + Vector3( h.x, -h.y,  h.z),  # 5
		c + Vector3( h.x,  h.y,  h.z),  # 6
		c + Vector3(-h.x,  h.y,  h.z),  # 7
	]
	# 6 faces, each 2 tris, wound CCW with outward normals.
	var faces := [
		[0, 1, 2, 3, Vector3(0, 0, -1)],
		[5, 4, 7, 6, Vector3(0, 0,  1)],
		[1, 5, 6, 2, Vector3( 1, 0, 0)],
		[4, 0, 3, 7, Vector3(-1, 0, 0)],
		[3, 2, 6, 7, Vector3(0,  1, 0)],
		[4, 5, 1, 0, Vector3(0, -1, 0)],
	]
	for f in faces:
		var n: Vector3 = f[4]
		for i in [0, 1, 2, 0, 2, 3]:
			st.set_normal(n)
			st.add_vertex(v[f[i]])

# Register a mesh + transform with the library under the given ID.
static func _install(lib: MeshLibrary, id: int, name: String,
					 mesh: Mesh, t: Transform3D) -> void:
	lib.create_item(id)
	lib.set_item_name(id, name)
	lib.set_item_mesh(id, mesh)
	lib.set_item_mesh_transform(id, t)
