extends Node3D
class_name ChunkManager

const CHUNK_SIZE: int = 16

@export var render_distance: int = 2
@export var world_seed: int = 12345

var chunks: Dictionary = {}

func _ready() -> void:
	_load_initial_chunks()

func _load_initial_chunks() -> void:
	for cx: int in range(-render_distance, render_distance + 1):
		for cy: int in range(-render_distance, render_distance + 1):
			for cz: int in range(-render_distance, render_distance + 1):
				_load_chunk(Vector3i(cx, cy, cz))

func update_player_position(pos: Vector3) -> void:
	var player_chunk: Vector3i = world_to_chunk_coord(pos)

	var to_unload: Array[Vector3i] = []
	for coord: Vector3i in chunks.keys():
		var dist: Vector3i = coord - player_chunk
		if abs(dist.x) > render_distance or abs(dist.y) > render_distance or abs(dist.z) > render_distance:
			to_unload.append(coord)

	for coord: Vector3i in to_unload:
		_unload_chunk(coord)

	for cx: int in range(player_chunk.x - render_distance, player_chunk.x + render_distance + 1):
		for cy: int in range(player_chunk.y - render_distance, player_chunk.y + render_distance + 1):
			for cz: int in range(player_chunk.z - render_distance, player_chunk.z + render_distance + 1):
				var coord: Vector3i = Vector3i(cx, cy, cz)
				if not chunks.has(coord):
					_load_chunk(coord)

func _regen_border_neighbors(chunk_coord: Vector3i, local: Vector3i) -> void:
	# Regen adjacent chunk only if block is on the border face
	var offsets: Array[Vector3i] = []
	if local.x == 0:
		offsets.append(Vector3i(-1, 0, 0))
	elif local.x == CHUNK_SIZE - 1:
		offsets.append(Vector3i(1, 0, 0))
	if local.y == 0:
		offsets.append(Vector3i(0, -1, 0))
	elif local.y == CHUNK_SIZE - 1:
		offsets.append(Vector3i(0, 1, 0))
	if local.z == 0:
		offsets.append(Vector3i(0, 0, -1))
	elif local.z == CHUNK_SIZE - 1:
		offsets.append(Vector3i(0, 0, 1))
	for offset: Vector3i in offsets:
		var neighbor: Vector3i = chunk_coord + offset
		if chunks.has(neighbor):
			chunks[neighbor].generate_mesh()

func break_block(world_pos: Vector3) -> int:
	var chunk_coord: Vector3i = world_to_chunk_coord(world_pos)
	if not chunks.has(chunk_coord):
		return 0
	var chunk: Chunk = chunks[chunk_coord]
	var local: Vector3i = world_to_local(world_pos)
	var old_id: int = chunk.get_block(local.x, local.y, local.z)
	if old_id == BlockDatabase.BlockType.AIR:
		return 0
	chunk.set_block(local.x, local.y, local.z, BlockDatabase.BlockType.AIR)
	chunk.generate_mesh()
	_regen_border_neighbors(chunk_coord, local)
	return old_id

func place_block(world_pos: Vector3, block_id: int) -> bool:
	var chunk_coord: Vector3i = world_to_chunk_coord(world_pos)
	if not chunks.has(chunk_coord):
		return false
	var chunk: Chunk = chunks[chunk_coord]
	var local: Vector3i = world_to_local(world_pos)
	var current: int = chunk.get_block(local.x, local.y, local.z)
	if current != BlockDatabase.BlockType.AIR and current != BlockDatabase.BlockType.WATER:
		return false
	chunk.set_block(local.x, local.y, local.z, block_id)
	chunk.generate_mesh()
	_regen_border_neighbors(chunk_coord, local)
	return true

func get_block(world_pos: Vector3) -> int:
	var chunk_coord: Vector3i = world_to_chunk_coord(world_pos)
	if not chunks.has(chunk_coord):
		return BlockDatabase.BlockType.AIR
	var chunk: Chunk = chunks[chunk_coord]
	var local: Vector3i = world_to_local(world_pos)
	return chunk.get_block(local.x, local.y, local.z)

func world_to_chunk_coord(pos: Vector3) -> Vector3i:
	return Vector3i(
		int(floor(pos.x / float(CHUNK_SIZE))),
		int(floor(pos.y / float(CHUNK_SIZE))),
		int(floor(pos.z / float(CHUNK_SIZE)))
	)

func chunk_to_world(coord: Vector3i) -> Vector3:
	return Vector3(
		float(coord.x * CHUNK_SIZE),
		float(coord.y * CHUNK_SIZE),
		float(coord.z * CHUNK_SIZE)
	)

func world_to_local(pos: Vector3) -> Vector3i:
	var ix: int = int(floor(pos.x))
	var iy: int = int(floor(pos.y))
	var iz: int = int(floor(pos.z))
	return Vector3i(
		((ix % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE,
		((iy % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE,
		((iz % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE
	)

func _load_chunk(coord: Vector3i) -> void:
	var data: PackedInt32Array = WorldGenerator.generate_chunk(coord.x, coord.y, coord.z, world_seed)
	var chunk: Chunk = Chunk.new()
	add_child(chunk)
	chunk.init_blocks(data)
	chunk.position = chunk_to_world(coord)
	chunk.generate_mesh()
	chunks[coord] = chunk

func _unload_chunk(coord: Vector3i) -> void:
	if chunks.has(coord):
		var chunk: Chunk = chunks[coord]
		chunk.queue_free()
		chunks.erase(coord)
