class_name SimulationStateRecord
extends RefCounted

const SCHEMA_NAME := "the_steward.simulation"
const SCHEMA_VERSION := 3
const LEGACY_SCHEMA_VERSION := 1
const PREVIOUS_SCHEMA_VERSION := 2

var simulation: Dictionary
var village: VillageStateRecord
var npcs: Array[NPCStateRecord] = []
var resources: Array[ResourceStateRecord] = []
var storages: Array[StorageStateRecord] = []
var economic_events: Array[EconomicEventRecord] = []


func to_dictionary() -> Dictionary:
	var npc_data: Array[Dictionary] = []
	for npc_record in npcs:
		npc_data.append(npc_record.to_dictionary())

	var resource_data: Array[Dictionary] = []
	for resource_record in resources:
		resource_data.append(resource_record.to_dictionary())
	var storage_data: Array[Dictionary] = []
	for storage_record in storages:
		storage_data.append(storage_record.to_dictionary())
	var event_data: Array[Dictionary] = []
	for event_record in economic_events:
		event_data.append(event_record.to_dictionary())

	return {
		"schema": SCHEMA_NAME,
		"schema_version": SCHEMA_VERSION,
		"simulation": simulation.duplicate(true),
		"village": village.to_dictionary(),
		"npcs": npc_data,
		"resources": resource_data,
		"storages": storage_data,
		"economic_events": event_data
	}


func to_json() -> String:
	return JSON.stringify(to_dictionary())


static func from_json(json_text: String) -> SimulationStateRecord:
	var parsed = JSON.parse_string(json_text)
	if not parsed is Dictionary:
		return null
	return from_dictionary(parsed)


static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
	if record_data.get("schema", "") != SCHEMA_NAME:
		return null
	var version := int(record_data.get("schema_version", -1))
	if version in [LEGACY_SCHEMA_VERSION, PREVIOUS_SCHEMA_VERSION]:
		record_data = _migrate_legacy(record_data, version)
	elif version != SCHEMA_VERSION:
		return null
	if not record_data.has_all(
		["simulation", "village", "npcs", "resources", "storages", "economic_events"]
	):
		return null

	var simulation_data = record_data["simulation"]
	if not simulation_data is Dictionary:
		return null
	if not simulation_data.has_all(
		[
			"seed",
			"tick_interval",
			"tick_count",
			"clock_accumulator",
			"clock_elapsed_ticks",
			"wander_random_streams",
			"next_event_id"
		]
	):
		return null
	var wander_streams = simulation_data["wander_random_streams"]
	if not wander_streams is Array:
		return null
	for stream_data in wander_streams:
		if not stream_data is Dictionary:
			return null
		if not stream_data.has_all(["npc_id", "seed", "state"]):
			return null

	var village_data = record_data["village"]
	if not village_data is Dictionary:
		return null
	var village_record := VillageStateRecord.from_dictionary(village_data)
	if village_record == null:
		return null

	var npc_data = record_data["npcs"]
	var resource_data = record_data["resources"]
	var storage_data = record_data["storages"]
	var event_data = record_data["economic_events"]
	if (
		not npc_data is Array
		or not resource_data is Array
		or not storage_data is Array
		or not event_data is Array
	):
		return null

	var record := SimulationStateRecord.new()
	record.simulation = simulation_data.duplicate(true)
	record.village = village_record
	var npc_ids := {}

	for item in npc_data:
		if not item is Dictionary:
			return null
		var npc_record := NPCStateRecord.from_dictionary(item)
		if npc_record == null:
			return null
		var npc_id := int(npc_record.data["id"])
		if npc_ids.has(npc_id):
			return null
		npc_ids[npc_id] = true
		record.npcs.append(npc_record)

	var resource_ids := {}
	for item in resource_data:
		if not item is Dictionary:
			return null
		var resource_record := ResourceStateRecord.from_dictionary(item)
		if resource_record == null:
			return null
		var resource_id := String(resource_record.data["node_id"])
		if resource_ids.has(resource_id):
			return null
		resource_ids[resource_id] = true
		record.resources.append(resource_record)

	var storage_ids := {}
	for item in storage_data:
		if not item is Dictionary:
			return null
		var storage_record := StorageStateRecord.from_dictionary(item)
		if storage_record == null:
			return null
		var storage_id := storage_record.get_storage_id()
		if storage_ids.has(storage_id):
			return null
		storage_ids[storage_id] = true
		record.storages.append(storage_record)

	var event_ids := {}
	var highest_event_id := -1
	for item in event_data:
		if not item is Dictionary:
			return null
		var event_record := EconomicEventRecord.from_dictionary(item)
		if event_record == null:
			return null
		var event_id := int(event_record.data["event_id"])
		if event_ids.has(event_id):
			return null
		event_ids[event_id] = true
		highest_event_id = maxi(highest_event_id, event_id)
		record.economic_events.append(event_record)
	if int(record.simulation["next_event_id"]) <= highest_event_id:
		return null

	return record


static func _migrate_legacy(legacy_data: Dictionary, version: int) -> Dictionary:
	var migrated := legacy_data.duplicate(true)
	migrated["schema_version"] = SCHEMA_VERSION
	if version == LEGACY_SCHEMA_VERSION:
		var village_data: Dictionary = legacy_data.get("village", {})
		var initial_food := float(village_data.get("food", 0.0))
		migrated["storages"] = [
			(
				StorageStateRecord
				. create(
					SimulationIds.STORAGE_VILLAGE_PANTRY,
					{String(SimulationIds.RESOURCE_FOOD): initial_food}
				)
				. to_dictionary()
			)
		]
	migrated["economic_events"] = []
	var simulation_data: Dictionary = migrated.get("simulation", {})
	simulation_data["next_event_id"] = 0
	migrated["simulation"] = simulation_data
	return migrated
