class_name AdtWaterLoadPipelineState
extends RefCounted

## Owns ADT water request, active-task and worker-result bookkeeping. The caller
## owns WorkerThreadPool operations, ADT parsing, permits and scene finalization.

var _pending_requests: Array[Dictionary] = []
var _queued_tile_keys: Dictionary = {}
var _active_task_ids_by_tile_key: Dictionary = {}
var _result_mutex := Mutex.new()
var _results: Array[Dictionary] = []


## Appends one tile/path request in FIFO order. Invalid, queued or active tile
## keys are rejected without mutation.
func enqueue_request(tile_key: String, adt_path: String) -> bool:
	if tile_key.is_empty() or adt_path.is_empty():
		return false
	if _queued_tile_keys.has(tile_key) or _active_task_ids_by_tile_key.has(tile_key):
		return false
	_pending_requests.append({
		"key": tile_key,
		"path": adt_path,
	})
	_queued_tile_keys[tile_key] = true
	return true


## Removes and returns the oldest request, also releasing its queued-key marker.
## An empty Dictionary means no pending request exists.
func take_next_request() -> Dictionary:
	if _pending_requests.is_empty():
		return {}
	var request: Dictionary = _pending_requests.pop_front()
	_queued_tile_keys.erase(String(request.get("key", "")))
	return request


## Removes all pending requests for one tile without affecting active work.
## Returns the number of removed FIFO entries.
func cancel_pending_requests(tile_key: String) -> int:
	if tile_key.is_empty():
		return 0
	_queued_tile_keys.erase(tile_key)
	var removed_count := 0
	for request_index in range(_pending_requests.size() - 1, -1, -1):
		var request: Dictionary = _pending_requests[request_index]
		if String(request.get("key", "")) != tile_key:
			continue
		_pending_requests.remove_at(request_index)
		removed_count += 1
	return removed_count


## Records one caller-started worker task. The opaque task ID is retained until
## the caller waits for and completes it.
func remember_active_task(tile_key: String, task_id: int) -> bool:
	if tile_key.is_empty() or _active_task_ids_by_tile_key.has(tile_key):
		return false
	_active_task_ids_by_tile_key[tile_key] = task_id
	return true


## Returns whether a worker task is active for the tile.
func has_active_task(tile_key: String) -> bool:
	return not tile_key.is_empty() and _active_task_ids_by_tile_key.has(tile_key)


## Returns the opaque active task ID, or -1 for an unknown tile.
func active_task_id_for(tile_key: String) -> int:
	return int(_active_task_ids_by_tile_key.get(tile_key, -1))


## Removes and returns one completed task ID, or -1 for an unknown tile.
func complete_active_task(tile_key: String) -> int:
	if tile_key.is_empty() or not _active_task_ids_by_tile_key.has(tile_key):
		return -1
	var task_id := int(_active_task_ids_by_tile_key[tile_key])
	_active_task_ids_by_tile_key.erase(tile_key)
	return task_id


## Returns a detached snapshot of opaque task IDs for orderly shutdown waiting.
func active_task_ids_snapshot() -> Array:
	return _active_task_ids_by_tile_key.values().duplicate()


## Publishes one parsed ADT result from a worker thread. Payload identity is
## retained; the caller must not mutate it after publication.
func publish_result(tile_key: String, adt_path: String, water_data: Dictionary) -> bool:
	if tile_key.is_empty() or adt_path.is_empty():
		return false
	_result_mutex.lock()
	_results.append({
		"key": tile_key,
		"path": adt_path,
		"data": water_data,
	})
	_result_mutex.unlock()
	return true


## Returns whether a worker result is currently ready.
func has_result() -> bool:
	_result_mutex.lock()
	var result_available := not _results.is_empty()
	_result_mutex.unlock()
	return result_available


## Removes and returns the oldest worker result. An empty Dictionary means no
## result was ready. Intended for the serialized renderer main thread.
func pop_result() -> Dictionary:
	_result_mutex.lock()
	var result: Dictionary = {}
	if not _results.is_empty():
		result = _results.pop_front()
	_result_mutex.unlock()
	return result


## Returns pending plus active load count for existing renderer metrics.
func total_load_count() -> int:
	return _pending_requests.size() + _active_task_ids_by_tile_key.size()


## Returns the pending request count.
func pending_request_count() -> int:
	return _pending_requests.size()


## Returns the active worker-task count.
func active_task_count() -> int:
	return _active_task_ids_by_tile_key.size()


## Clears bookkeeping and queued results without interrupting worker tasks.
## Orderly shutdown must wait the task-ID snapshot before calling this method.
func clear() -> void:
	_pending_requests.clear()
	_queued_tile_keys.clear()
	_active_task_ids_by_tile_key.clear()
	_result_mutex.lock()
	_results.clear()
	_result_mutex.unlock()


## Returns detached FIFO/task/result scalar diagnostics without water payloads.
func diagnostic_snapshot() -> Dictionary:
	var pending_requests: Array[Dictionary] = []
	for request in _pending_requests:
		pending_requests.append(request.duplicate())
	var active_tile_keys: Array = _active_task_ids_by_tile_key.keys()
	active_tile_keys.sort()
	_result_mutex.lock()
	var result_records: Array[Dictionary] = []
	for result in _results:
		result_records.append({
			"key": String(result.get("key", "")),
			"path": String(result.get("path", "")),
		})
	_result_mutex.unlock()
	return {
		"pending_requests": pending_requests,
		"active_tile_keys": active_tile_keys.duplicate(),
		"result_records": result_records,
	}
