@tool
extends Resource
class_name DialogicStyle

## A style represents a collection of layers and settings.
## A style can inherit from another style.


@export var name := "Style":
	get:
		if name.is_empty():
			return "Unkown Style"
		return name

@export var inherits: DialogicStyle = null

## Stores the layer order
@export var layer_list: Array[String] = []
## Stores the layer infos
@export var layer_info := {
	"" : DialogicStyleLayer.new()
}




func _init(_name := "") -> void:
	if not _name.is_empty():
		name = _name



#region BASE METHODS
# These methods are local, meaning they do NOT take inheritance into account.


## Returns the amount of layers (the base layer is not included).
func get_layer_count() -> int:
	return layer_list.size()


## Returns the index of the layer with [param id] in the layer list.
## Returns -1 for the base layer (id=="") which is not in the layer list.
func get_layer_index(id:String) -> int:
	return layer_list.find(id)


## Returns `true` if [param id] is a valid id for a layer.
func has_layer(id:String) -> bool:
	return id in layer_info or id == ""


## Returns `true` if [param index] is a valid index for a layer.
func has_layer_index(index:int) -> bool:
	return index < layer_list.size()


## Returns the id of the layer at [param index].
func get_layer_id_at_index(index:int) -> String:
	if index == -1:
		return ""
	if has_layer_index(index):
		return layer_list[index]
	return ""


func get_layer_info(id:String) -> Dictionary:
	var info := {"id": id, "path": "", "overrides": {}}

	if has_layer(id):
		var layer_resource: DialogicStyleLayer = layer_info[id]

		if layer_resource.scene != null:
			info.path = layer_resource.scene.resource_path
		elif id == "":
			info.path = DialogicStylesUtil.get_default_layout_base().resource_path

		info.overrides = layer_resource.overrides.duplicate()

	return info

#endregion


#region MODIFICATION METHODS
# These methods modify the layers of this style.


## Returns a new layer id not yet in use.
func get_new_layer_id() -> String:
	var i := 16
	while String.num_int64(i, 16) in layer_info:
		i += 1
	return String.num_int64(i, 16)


## Adds a layer with the given scene and overrides.
## Returns the new layers id.
func add_layer(scene:String, overrides:Dictionary = {}, id:= "##") -> String:
	if id == "##":
		id = get_new_layer_id()
	layer_info[id] = DialogicStyleLayer.new(scene, overrides)
	layer_list.append(id)
	changed.emit()
	return id


## Deletes the layer with the given id.
## Deleting the base layer is not allowed.
func delete_layer(id:String) -> void:
	if not has_layer(id) or id == "":
		return

	layer_info.erase(id)
	layer_list.erase(id)

	changed.emit()


## Moves the layer at [param from_index] to [param to_index].
func move_layer(from_index:int, to_index:int) -> void:
	if not has_layer_index(from_index) or not has_layer_index(to_index-1):
		return

	var id: String = layer_list.pop_at(from_index)
	layer_list.insert(to_index, id)

	changed.emit()


## Changes the scene property of the DialogicStyleLayer resource at [param layer_id].
func set_layer_scene(layer_id:String, scene:String) -> void:
	if not has_layer(layer_id):
		return
	layer_info[layer_id].scene = load(scene)
	changed.emit()


func set_layer_overrides(layer_id:String, overrides:Dictionary) -> void:
	if not has_layer(layer_id):
		return

	layer_info[layer_id].overrides = overrides
	changed.emit()


## Changes an override of the DialogicStyleLayer resource at [param layer_id].
func set_layer_setting(layer_id:String, setting:String, value:Variant) -> void:
	if not has_layer(layer_id):
		return

	layer_info[layer_id].overrides[setting] = value
	changed.emit()


## Resets (removes) an override of the DialogicStyleLayer resource at [param layer_id].
func remove_layer_setting(layer_id:String, setting:String) -> void:
	if not has_layer(layer_id):
		return

	layer_info[layer_id].overrides.erase(setting)
	changed.emit()

#
#endregion


#region INHERITANCE METHODS
# These methods are what you should usually use to get info about this style.


## Returns `true` if this style is inheriting from another style.
func inherits_anything() -> bool:
	return inherits != null


## Returns the base style of this style.
func get_inheritance_root() -> DialogicStyle:
	if not inherits_anything():
		return self

	var style: DialogicStyle = self
	while style.inherits_anything():
		style = style.inherits

	return style


## This merges some [param layer_info] with it's param ancestors layer info.
func merge_layer_infos(new_layer_info:Dictionary, ancestor_info:Dictionary) -> Dictionary:
	var combined := new_layer_info.duplicate(true)

	combined.path = ancestor_info.path
	combined.overrides.merge(ancestor_info.overrides)

	return combined


## Returns the layer info of the layer at [param id] taking into account inherited info.
## If [param inherited_only] is `true`, the local info is not included.
func get_layer_inherited_info(id:String, inherited_only := false) -> Dictionary:
	var style := self
	var info := {"id": id, "path": "", "overrides": {}}

	if not inherited_only:
		info = get_layer_info(id)

	while style.inherits_anything():
		style = style.inherits
		info = merge_layer_infos(info, style.get_layer_info(id))

	return info


## Returns the layer list of the root style.
func get_layer_inherited_list() -> Array:
	var list := layer_list

	if inherits_anything():
		list = get_inheritance_root().layer_list

	return list


## Applies inherited info to the local layers.
## Then removes inheritance.
func realize_inheritance() -> void:
	layer_list = get_layer_inherited_list()

	var new_layer_info := {}
	for id in layer_info:
		var info := get_layer_inherited_info(id)
		new_layer_info[id] = DialogicStyleLayer.new(info.get("path", ""), info.get("overrides", {}))

	layer_info = new_layer_info
	inherits = null
	changed.emit()


#endregion

## Creates a fresh new style with the same settings.
func clone() -> DialogicStyle:
	var style := DialogicStyle.new()
	style.name = name
	style.inherits = inherits

	var base_info := get_layer_info("")
	style.set_layer_scene("", base_info.path)
	style.set_layer_overrides("", base_info.overrides)

	for id in layer_list:
		var info := get_layer_info(id)
		style.add_layer(info.path, info.overrides, id)

	return style


## Starts preloading all the scenes used by this style.
func prepare() -> void:
	for id in layer_info:
		if layer_info[id].scene:
			ResourceLoader.load_threaded_request(layer_info[id].scene.resource_path)


#region UPDATE OLD STYLES
# TODO deprecated when going into beta

# TODO  Deprecated, only for Styles before alpha 16!
@export var base_scene: PackedScene = null
# TODO Deprecated, only for Styles before alpha 16!
@export var base_overrides := {}
# TODO Deprecated, only for Styles before alpha 16!
@export var layers: Array[DialogicStyleLayer] = []

func update_from_pre_alpha16() -> void:
	if not layers.is_empty():
		var idx := 0
		for layer in layers:
			var id := "##"
			if inherits_anything():
				id = get_layer_inherited_list()[idx]
			if layer.scene:
				add_layer(layer.scene.resource_path, layer.overrides, id)
			else:
				add_layer("", layer.overrides, id)
			idx += 1
		layers.clear()

	if not base_scene == null:
		set_layer_scene("", base_scene.resource_path)
		base_scene = null
	if not base_overrides.is_empty():
		set_layer_overrides("", base_overrides)
		base_overrides.clear()


#endregion
