@tool
extends Node

## Creates Godot Theme resources from a structured specification.
## Replaces the verbose 100+ line execute_script pattern.


func register(server: Node) -> void:
	server.register_handler("create_theme", _create_theme)


func _create_theme(args: Dictionary) -> Dictionary:
	var save_path: String = args.get("path", "res://theme.tres")

	if not save_path.begins_with("res://"):
		save_path = "res://" + save_path
	if not save_path.ends_with(".tres"):
		save_path += ".tres"

	var theme := Theme.new()
	var errors: PackedStringArray = []

	# --- Colors ---
	var colors: Dictionary = args.get("colors", {})
	for color_name in colors:
		var mapping: Dictionary = colors[color_name]
		for component in mapping:
			var parsed := _parse_color(mapping[component])
			if parsed == null:
				errors.append("Invalid color '%s' for %s/%s" % [str(mapping[component]), color_name, component])
				continue
			theme.set_color(color_name, component, parsed)

	# --- Fonts ---
	var fonts: Dictionary = args.get("fonts", {})
	for font_name in fonts:
		var mapping: Dictionary = fonts[font_name]
		for component in mapping:
			var font_spec = mapping[component]
			var font_res = _load_font(font_spec)
			if font_res == null:
				errors.append("Cannot load font '%s' for %s/%s" % [str(font_spec), font_name, component])
				continue
			theme.set_font(font_name, component, font_res)

	# --- Font sizes ---
	var font_sizes: Dictionary = args.get("font_sizes", {})
	for size_name in font_sizes:
		var mapping: Dictionary = font_sizes[size_name]
		for component in mapping:
			theme.set_font_size(size_name, component, int(mapping[component]))

	# --- Constants ---
	var constants: Dictionary = args.get("constants", {})
	for const_name in constants:
		var mapping: Dictionary = constants[const_name]
		for component in mapping:
			theme.set_constant(const_name, component, int(mapping[component]))

	# --- Styleboxes ---
	var styleboxes: Array = args.get("styleboxes", [])
	for spec in styleboxes:
		var result := _create_stylebox(spec)
		if result["stylebox"] == null:
			errors.append(result.get("error", "Unknown stylebox error"))
			continue
		var component: String = spec.get("component", "")
		var variant: String = spec.get("variant", "")
		if component.is_empty() or variant.is_empty():
			errors.append("Stylebox missing 'component' or 'variant'")
			continue
		theme.set_stylebox(variant, component, result["stylebox"])

	# --- Save ---
	var dir_path := save_path.get_base_dir()
	if not DirAccess.dir_exists_absolute(dir_path):
		var err := DirAccess.make_dir_recursive_absolute(dir_path)
		if err != OK:
			return {"ok": false, "error": "Cannot create directory: %s (error %d)" % [dir_path, err]}

	var err := ResourceSaver.save(theme, save_path)
	if err != OK:
		return {"ok": false, "error": "Failed to save theme to %s (error %d)" % [save_path, err]}

	EditorInterface.get_resource_filesystem().scan()

	# --- Apply global ---
	var apply_global: bool = args.get("apply_global", false)
	if apply_global:
		ProjectSettings.set_setting("gui/theme/custom", save_path)
		ProjectSettings.save()

	var result_msg := "Theme created: %s" % save_path
	if apply_global:
		result_msg += " (applied as global theme)"
	if errors.size() > 0:
		result_msg += "\nWarnings:\n- " + "\n- ".join(errors)

	return {"ok": true, "type": "text", "result": result_msg}


# ------------------------------------------------------------------
# Color parsing — hex, rgba array, named, "transparent"
# ------------------------------------------------------------------

func _parse_color(value) -> Variant:
	if value is String:
		var s: String = value.strip_edges()
		if s == "transparent":
			return Color.TRANSPARENT
		# Hex string (with or without #)
		if s.begins_with("#") or (s.length() >= 6 and s.is_valid_hex_number(true)):
			return Color(s)
		# Try as named color
		if Color.html_is_valid(s):
			return Color.html(s)
		# Try as hex without #
		if Color.html_is_valid("#" + s):
			return Color.html("#" + s)
		return null
	if value is Array:
		if value.size() >= 3:
			var a: float = float(value[3]) if value.size() > 3 else 1.0
			return Color(float(value[0]), float(value[1]), float(value[2]), a)
		return null
	return null


# ------------------------------------------------------------------
# Font loading — path string or FontVariation spec
# ------------------------------------------------------------------

func _load_font(spec) -> Font:
	if spec is String:
		# Direct path to .ttf/.otf
		var path: String = spec
		if not path.begins_with("res://"):
			path = "res://" + path
		if not ResourceLoader.exists(path):
			return null
		return load(path) as Font

	if spec is Dictionary:
		# FontVariation: {"base": "res://font.ttf", "weight": 700, "size": 16}
		var base_path: String = spec.get("base", "")
		if base_path.is_empty():
			return null
		if not base_path.begins_with("res://"):
			base_path = "res://" + base_path
		if not ResourceLoader.exists(base_path):
			return null
		var base_font = load(base_path) as Font
		if base_font == null:
			return null

		var variation := FontVariation.new()
		variation.base_font = base_font

		if spec.has("weight"):
			variation.variation_embolden = (float(spec["weight"]) - 400.0) / 300.0

		if spec.has("italic") and bool(spec["italic"]):
			variation.variation_transform = Transform2D(Vector2(1, 0.2), Vector2(0, 1), Vector2.ZERO)

		if spec.has("spacing_top"):
			variation.spacing_top = int(spec["spacing_top"])
		if spec.has("spacing_bottom"):
			variation.spacing_bottom = int(spec["spacing_bottom"])

		return variation

	return null


# ------------------------------------------------------------------
# Stylebox creation from spec dict
# ------------------------------------------------------------------

func _create_stylebox(spec: Dictionary) -> Dictionary:
	var style := StyleBoxFlat.new()

	# Background color
	if spec.has("bg_color"):
		var c := _parse_color(spec["bg_color"])
		if c != null:
			style.bg_color = c

	# Corner radius — uniform int or [tl, tr, bl, br] array
	if spec.has("corner_radius"):
		var cr = spec["corner_radius"]
		if cr is Array and cr.size() >= 4:
			style.corner_radius_top_left = int(cr[0])
			style.corner_radius_top_right = int(cr[1])
			style.corner_radius_bottom_left = int(cr[2])
			style.corner_radius_bottom_right = int(cr[3])
		else:
			var r := int(cr)
			style.corner_radius_top_left = r
			style.corner_radius_top_right = r
			style.corner_radius_bottom_left = r
			style.corner_radius_bottom_right = r

	# Border color + width
	if spec.has("border_color"):
		var c := _parse_color(spec["border_color"])
		if c != null:
			style.border_color = c
	if spec.has("border_width"):
		var bw = spec["border_width"]
		if bw is Array and bw.size() >= 4:
			style.border_width_left = int(bw[0])
			style.border_width_top = int(bw[1])
			style.border_width_right = int(bw[2])
			style.border_width_bottom = int(bw[3])
		else:
			var w := int(bw)
			style.border_width_left = w
			style.border_width_top = w
			style.border_width_right = w
			style.border_width_bottom = w

	# Content margin — uniform int, [h, v], or [l, t, r, b]
	if spec.has("content_margin"):
		var cm = spec["content_margin"]
		if cm is Array:
			if cm.size() >= 4:
				style.content_margin_left = int(cm[0])
				style.content_margin_top = int(cm[1])
				style.content_margin_right = int(cm[2])
				style.content_margin_bottom = int(cm[3])
			elif cm.size() >= 2:
				style.content_margin_left = int(cm[0])
				style.content_margin_right = int(cm[0])
				style.content_margin_top = int(cm[1])
				style.content_margin_bottom = int(cm[1])
		else:
			var m := int(cm)
			style.content_margin_left = m
			style.content_margin_top = m
			style.content_margin_right = m
			style.content_margin_bottom = m

	# Shadow
	if spec.has("shadow_color"):
		var c := _parse_color(spec["shadow_color"])
		if c != null:
			style.shadow_color = c
	if spec.has("shadow_size"):
		style.shadow_size = int(spec["shadow_size"])
	if spec.has("shadow_offset"):
		var so = spec["shadow_offset"]
		if so is Array and so.size() >= 2:
			style.shadow_offset = Vector2(float(so[0]), float(so[1]))

	# Expand margins
	if spec.has("expand_margin"):
		var em = spec["expand_margin"]
		if em is Array and em.size() >= 4:
			style.expand_margin_left = float(em[0])
			style.expand_margin_top = float(em[1])
			style.expand_margin_right = float(em[2])
			style.expand_margin_bottom = float(em[3])
		else:
			var m := float(em)
			style.expand_margin_left = m
			style.expand_margin_top = m
			style.expand_margin_right = m
			style.expand_margin_bottom = m

	# Anti-aliasing
	if spec.has("anti_aliasing"):
		style.anti_aliasing = bool(spec["anti_aliasing"])

	# Skew
	if spec.has("skew"):
		var sk = spec["skew"]
		if sk is Array and sk.size() >= 2:
			style.skew = Vector2(float(sk[0]), float(sk[1]))

	return {"stylebox": style}
