class_name M2BuildQueue
extends RefCounted

## Owns pending M2 build-job records and FIFO tile keys. Stale FIFO keys are
## intentional: erasing a job never removes its queued key.

const JOB_SCRIPT := preload("res://src/render/m2/m2_build_job.gd")

var _jobs_by_tile_key: Dictionary = {}
var _queued_tile_keys: Array[String] = []


## Enqueues a typed job. Duplicate tile keys replace the keyed job and append
## another FIFO entry, matching raw Dictionary assignment plus Array append.
func enqueue(tile_key: String, root: Node3D, groups: Dictionary) -> bool:
	if tile_key.is_empty() or root == null or groups.is_empty():
		return false
	_jobs_by_tile_key[tile_key] = JOB_SCRIPT.new(tile_key, root, groups)
	_queued_tile_keys.append(tile_key)
	return true


## Returns whether at least one FIFO key, including a stale key, is pending.
func has_pending() -> bool:
	return not _queued_tile_keys.is_empty()


## Returns the front FIFO tile key, or an empty String when the queue is empty.
func front_key() -> String:
	if _queued_tile_keys.is_empty():
		return ""
	return _queued_tile_keys.front()


## Pops and returns the front FIFO key, or an empty String when already empty.
func pop_front() -> String:
	if _queued_tile_keys.is_empty():
		return ""
	return _queued_tile_keys.pop_front()


## Moves the front FIFO key to the tail and reports whether a key existed.
func rotate_front() -> bool:
	if _queued_tile_keys.is_empty():
		return false
	_queued_tile_keys.append(_queued_tile_keys.pop_front())
	return true


## Returns whether a tile key still has a current job record.
func has_job(tile_key: String) -> bool:
	return _jobs_by_tile_key.has(tile_key)


## Returns the current job as its engine base type, or null for a stale key.
func job_for(tile_key: String) -> RefCounted:
	return _jobs_by_tile_key.get(tile_key, null) as RefCounted


## Returns the borrowed root for a current job, or null for a stale key.
func root_for(tile_key: String) -> Node3D:
	var job := job_for(tile_key)
	if job == null:
		return null
	return job.call("root") as Node3D


## Returns the exact grouped-transform Dictionary, or an empty Dictionary.
func groups_for(tile_key: String) -> Dictionary:
	var job := job_for(tile_key)
	if job == null:
		return {}
	return job.call("groups") as Dictionary


## Returns the borrowed group-key snapshot, or an empty Array for a stale key.
func group_keys_for(tile_key: String) -> Array:
	var job := job_for(tile_key)
	if job == null:
		return []
	return job.call("group_keys") as Array


## Returns the current model-path group cursor, or zero for a stale key.
func group_index_for(tile_key: String) -> int:
	var job := job_for(tile_key)
	if job == null:
		return 0
	return int(job.call("group_index"))


## Returns the current in-group transform offset, or zero for a stale key.
func transform_offset_for(tile_key: String) -> int:
	var job := job_for(tile_key)
	if job == null:
		return 0
	return int(job.call("transform_offset"))


## Returns the current batch serial, or zero for a stale key.
func batch_serial_for(tile_key: String) -> int:
	var job := job_for(tile_key)
	if job == null:
		return 0
	return int(job.call("batch_serial"))


## Atomically adopts all progress values for a current job.
func adopt_progress(
	tile_key: String,
	next_group_index: int,
	next_transform_offset: int,
	next_batch_serial: int
) -> bool:
	var job := job_for(tile_key)
	if job == null:
		return false
	job.call(
		"adopt_progress",
		next_group_index,
		next_transform_offset,
		next_batch_serial
	)
	return true


## Erases only the keyed job and intentionally leaves every FIFO entry stale.
## Returns whether an active record existed. Engine objects are never freed.
func erase_job(tile_key: String) -> bool:
	var had_job := _jobs_by_tile_key.has(tile_key)
	_jobs_by_tile_key.erase(tile_key)
	return had_job


## Returns a detached snapshot of active job keys for loader-owned cleanup.
func job_keys() -> Array:
	return _jobs_by_tile_key.keys()


## Releases all job/key references without freeing referenced engine objects.
func clear() -> void:
	_jobs_by_tile_key.clear()
	_queued_tile_keys.clear()


## Returns FIFO entry count, including duplicate and stale keys.
func pending_count() -> int:
	return _queued_tile_keys.size()


## Returns active keyed job count, excluding stale FIFO keys.
func active_job_count() -> int:
	return _jobs_by_tile_key.size()


## Returns detached FIFO order and tile-key-sorted scalar job diagnostics.
func diagnostic_snapshot() -> Dictionary:
	var tile_keys: Array = _jobs_by_tile_key.keys()
	tile_keys.sort()
	var jobs: Array[Dictionary] = []
	for tile_key_variant in tile_keys:
		var tile_key := String(tile_key_variant)
		var job := job_for(tile_key)
		if job != null:
			jobs.append(job.call("diagnostic_snapshot") as Dictionary)
	return {
		"queued_tile_keys": _queued_tile_keys.duplicate(),
		"jobs": jobs,
	}
