@tool
extends Control
class_name FormValidator

signal control_validated(control: Control, passed: bool, messages: PackedStringArray)

class ValidatorInfo extends RefCounted:
	var control: Control
	var validator: Validator

@export var auto_validate: bool = false
@export var validation_method: Validation.Method = Validation.Method.BATCH:
	set(value):
		validation_method = value
		_update_validation_methods()

var _control_validator_map: Dictionary[Control, Validator] = {}
var _control_messages_map: Dictionary[Control, PackedStringArray] = {}


func _ready() -> void:
	Validation.validator_added.connect(_on_validator_added)
	Validation.validator_removed.connect(_on_validator_removed)
	_find_validators(self)


func get_messages() -> Dictionary:
	return _control_messages_map


func get_messages_for_control(control: Control) -> PackedStringArray:
	if not _control_messages_map.has(control):
		return PackedStringArray()
	return _control_messages_map[control]


func validate() -> bool:
	_control_messages_map.clear()
	var list: Array[ValidatorInfo] = _get_validator_info_list()
	var valid: bool = true
	for info: ValidatorInfo in list:
		if info.validator.skip_validation:
			continue
		var control: Control = info.control as Control
		if control == null:
			Log.error("FormValidator: ValidatorInfo.control is not a Control: %s" % [info.control])
			continue
		var passed: bool = info.validator.validate(control)
		var messages: PackedStringArray = info.validator.get_messages()
		if not passed:
			_control_messages_map[control] = messages
		control_validated.emit(control, passed, messages)
		valid = valid and passed
		if not valid and validation_method == Validation.Method.IMMEDIATE:
			return valid
	return valid



func _get_validator_info_list() -> Array[ValidatorInfo]:
	var list: Array[ValidatorInfo] = []
	for controlKey: Control in _control_validator_map.keys():
		var control: Control = controlKey as Control
		if control == null:
			Log.error("FormValidator: _control_validator_map key is not a Control")
			continue
		var validator: Validator = _control_validator_map[control]
		if not validator:
			continue
		var info: ValidatorInfo = ValidatorInfo.new()
		info.control = control
		info.validator = validator
		list.append(info)
	list.sort_custom(func (a: ValidatorInfo, b: ValidatorInfo): 
		return a.validator.validation_order < b.validator.validation_order
	)
	return list


func _update_validation_methods() -> void:
	var validators: Array = _control_validator_map.values()
	for item in validators:
		var validator: Validator = item as Validator
		if not validator:
			continue
		validator.validation_method = validation_method


func _find_validators(node: Node) -> void:
	for child in node.get_children():
		var control_validator: ControlValidator = child as ControlValidator
		if control_validator:
			control_validator._on_validator_added()
		_find_validators(child)


func _auto_validate(control: Control) -> void:
	if not auto_validate:
		return
	var validator: Validator = _control_validator_map[control] as Validator
	if not validator:
		return
	var passed: bool = validator.validate(control)
	control_validated.emit(control, passed, validator.get_messages())


func _on_validator_added(control: Control, validator: Validator) -> void:
	if not control:
		return
	_control_validator_map[control] = validator
	if not control.focus_exited.is_connected(_on_control_focus_exited):
		control.focus_exited.connect(_on_control_focus_exited.bind(control))
	# Special case to handle range-type controls that don't respond to focus 
	# events for editing.
	if control.has_signal("value_changed"):
		if not control.value_changed.is_connected(_on_control_value_changed):
			control.value_changed.connect(_on_control_value_changed.bind(control))


func _on_validator_removed(control: Control) -> void:
	_control_validator_map.erase(control)
	if control.focus_exited.is_connected(_on_control_focus_exited):
		control.focus_exited.disconnect(_on_control_focus_exited)
	if control.has_signal("value_changed"):
		if control.value_changed.is_connected(_on_control_value_changed):
			control.value_changed.disconnect(_on_control_value_changed)


func _on_control_focus_exited(control: Control) -> void:
	_auto_validate(control)


func _on_control_value_changed(value: float, control: Control) -> void:
	_auto_validate(control)
