extends CanvasLayer


@onready var balloon: ColorRect = $Balloon
@onready var margin: MarginContainer = $Balloon/Margin
@onready var character_label: RichTextLabel = $Balloon/Margin/VBox/CharacterLabel
@onready var dialogue_label := $Balloon/Margin/VBox/DialogueLabel
@onready var responses_menu: VBoxContainer = $Balloon/Margin/VBox/Responses
@onready var response_template: RichTextLabel = %ResponseTemplate

## The dialogue resource
var resource: DialogueResource

## Temporary game states
var temporary_game_states: Array = []

## See if we are waiting for the player
var is_waiting_for_input: bool = false

## See if we are running a long mutation and should hide the balloon
var will_hide_balloon: bool = false

## The current line
var dialogue_line: DialogueLine:
    set(next_dialogue_line):
        is_waiting_for_input = false

        if not next_dialogue_line:
            queue_free()
            return

        # Remove any previous responses
        for child in responses_menu.get_children():
            responses_menu.remove_child(child)
            child.queue_free()

        dialogue_line = next_dialogue_line

        character_label.visible = not dialogue_line.character.is_empty()
        character_label.text = tr(dialogue_line.character, "dialogue")

        dialogue_label.modulate.a = 0
        dialogue_label.custom_minimum_size.x = dialogue_label.get_parent().size.x - 1
        dialogue_label.dialogue_line = dialogue_line

        # Show any responses we have
        responses_menu.modulate.a = 0
        if dialogue_line.responses.size() > 0:
            for response in dialogue_line.responses:
                # Duplicate the template so we can grab the fonts, sizing, etc
                var item: RichTextLabel = response_template.duplicate(0)
                item.name = "Response%d" % responses_menu.get_child_count()
                if not response.is_allowed:
                    item.name = String(item.name) + "Disallowed"
                    item.modulate.a = 0.4
                item.text = response.text
                item.show()
                responses_menu.add_child(item)

        # Show our balloon
        balloon.show()
        will_hide_balloon = false

        dialogue_label.modulate.a = 1
        if not dialogue_line.text.is_empty():
            dialogue_label.type_out()
            await dialogue_label.finished_typing

        # Wait for input
        if dialogue_line.responses.size() > 0:
            responses_menu.modulate.a = 1
            configure_menu()
        elif dialogue_line.time != null:
            var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float()
            await get_tree().create_timer(time).timeout
            next(dialogue_line.next_id)
        else:
            is_waiting_for_input = true
            balloon.focus_mode = Control.FOCUS_ALL
            balloon.grab_focus()
    get:
        return dialogue_line


func _ready() -> void:
    response_template.hide()
    balloon.hide()
    balloon.custom_minimum_size.x = balloon.get_viewport_rect().size.x

    Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated)


func _unhandled_input(_event: InputEvent) -> void:
    # Only the balloon is allowed to handle input while it's showing
    get_viewport().set_input_as_handled()


## Start some dialogue
func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void:
    temporary_game_states = extra_game_states
    is_waiting_for_input = false
    resource = dialogue_resource

    self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states)


## Go to the next line
func next(next_id: String) -> void:
    self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states)


### Helpers


# Set up keyboard movement and signals for the response menu
func configure_menu() -> void:
    balloon.focus_mode = Control.FOCUS_NONE

    var items = get_responses()
    for i in items.size():
        var item: Control = items[i]

        item.focus_mode = Control.FOCUS_ALL

        item.focus_neighbor_left = item.get_path()
        item.focus_neighbor_right = item.get_path()

        if i == 0:
            item.focus_neighbor_top = item.get_path()
            item.focus_previous = item.get_path()
        else:
            item.focus_neighbor_top = items[i - 1].get_path()
            item.focus_previous = items[i - 1].get_path()

        if i == items.size() - 1:
            item.focus_neighbor_bottom = item.get_path()
            item.focus_next = item.get_path()
        else:
            item.focus_neighbor_bottom = items[i + 1].get_path()
            item.focus_next = items[i + 1].get_path()

        item.mouse_entered.connect(_on_response_mouse_entered.bind(item))
        item.gui_input.connect(_on_response_gui_input.bind(item))

    items[0].grab_focus()


# Get a list of enabled items
func get_responses() -> Array:
    var items: Array = []
    for child in responses_menu.get_children():
        if "Disallowed" in child.name: continue
        items.append(child)

    return items


func handle_resize() -> void:
    if not is_instance_valid(margin):
        call_deferred("handle_resize")
        return

    balloon.custom_minimum_size.y = margin.size.y
    # Force a resize on only the height
    balloon.size.y = 0
    var viewport_size = balloon.get_viewport_rect().size
    balloon.global_position = Vector2((viewport_size.x - balloon.size.x) * 0.5, viewport_size.y - balloon.size.y)


### Signals


func _on_mutated(_mutation: Dictionary) -> void:
    is_waiting_for_input = false
    will_hide_balloon = true
    get_tree().create_timer(0.1).timeout.connect(func():
        if will_hide_balloon:
            will_hide_balloon = false
            balloon.hide()
    )


func _on_response_mouse_entered(item: Control) -> void:
    if "Disallowed" in item.name: return

    item.grab_focus()


func _on_response_gui_input(event: InputEvent, item: Control) -> void:
    if "Disallowed" in item.name: return

    if event is InputEventMouseButton and event.is_pressed() and event.button_index == 1:
        next(dialogue_line.responses[item.get_index()].next_id)
    elif event.is_action_pressed("ui_accept") and item in get_responses():
        next(dialogue_line.responses[item.get_index()].next_id)


func _on_balloon_gui_input(event: InputEvent) -> void:
    if not is_waiting_for_input: return
    if dialogue_line.responses.size() > 0: return

    # When there are no response options the balloon itself is the clickable thing
    get_viewport().set_input_as_handled()

    if event is InputEventMouseButton and event.is_pressed() and event.button_index == 1:
        next(dialogue_line.next_id)
    elif event.is_action_pressed("ui_accept") and get_viewport().gui_get_focus_owner() == balloon:
        next(dialogue_line.next_id)


func _on_margin_resized() -> void:
    handle_resize()
