using Godot;
using Mr.BrickAdventures.Autoloads;

namespace Mr.BrickAdventures.scripts.UI;

public partial class InputSettings : Control
{
    [Export] public VBoxContainer ActionsContainer { get; set; }
    [Export] public Button ResetButton { get; set; }
    [Export] public Control InputSettingsControl { get; set; }
    [Export] public PackedScene InputButtonScene { get; set; }

    // When adding new game actions, update this list to make them remappable in settings.
    private static readonly string[] RemappableActions =
        ["left", "right", "jump", "down", "attack", "pause", "show_marketplace"];

    private UIManager UIManager => UIManager.Instance;
    private ConfigFileHandler ConfigFileHandler => ConfigFileHandler.Instance;

    private string _rebindingAction;
    private Button _rebindingButton;
    private Label _rebindingLabel;

    public override void _Ready()
    {
        PopulateActions();
        ResetButton.FocusMode = FocusModeEnum.All;
        ResetButton.Pressed += OnResetPressed;

        if (InputDeviceManager.Instance != null)
            InputDeviceManager.Instance.DeviceChanged += OnDeviceChanged;
        InputSettingsControl.VisibilityChanged += OnVisibilityChanged;
    }

    public override void _ExitTree()
    {
        if (InputDeviceManager.Instance != null)
            InputDeviceManager.Instance.DeviceChanged -= OnDeviceChanged;
        InputSettingsControl.VisibilityChanged -= OnVisibilityChanged;
    }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (!IsVisibleInTree()) return;

        if (_rebindingAction != null)
        {
            if (@event is InputEventKey keyEvent && keyEvent.Pressed && !keyEvent.Echo)
            {
                GetViewport().SetInputAsHandled();
                TryBind(_rebindingAction, _rebindingButton, _rebindingLabel, keyEvent);
            }
            else if (@event.IsActionReleased("ui_cancel"))
            {
                GetViewport().SetInputAsHandled();
                CancelRebind();
            }
            return;
        }

        if (!@event.IsActionReleased("ui_cancel")) return;
        if (!UIManager.IsScreenOnTop(InputSettingsControl)) return;
        UIManager.PopScreen();
    }

    private void PopulateActions()
    {
        foreach (Node child in ActionsContainer.GetChildren())
            child.QueueFree();

        foreach (var action in RemappableActions)
        {
            var row = InputButtonScene.Instantiate<Button>();
            ActionsContainer.AddChild(row);

            var labelAction = row.GetNode<Label>("MarginContainer/HBoxContainer/LabelAction");
            var labelInput = row.GetNode<Label>("MarginContainer/HBoxContainer/LabelInput");

            labelAction.Text = HumanizeAction(action);
            labelInput.Text = GetKeyName(action);

            row.FocusMode = FocusModeEnum.All;

            var capturedAction = action;
            var capturedLabel = labelInput;
            var capturedRow = row;
            row.Pressed += () => OnRowPressed(capturedAction, capturedRow, capturedLabel);
        }
    }

    private void OnRowPressed(string action, Button row, Label labelInput)
    {
        if (_rebindingAction != null)
            CancelRebind();

        _rebindingAction = action;
        _rebindingButton = row;
        _rebindingLabel = labelInput;
        labelInput.Text = "Press a key...";
    }

    private void TryBind(string action, Button row, Label labelInput, InputEventKey keyEvent)
    {
        var newKey = keyEvent.PhysicalKeycode;

        // Conflict check
        foreach (var other in RemappableActions)
        {
            if (other == action) continue;
            var existing = GetCurrentKey(other);
            if (existing == newKey)
            {
                labelInput.Text = $"Already used by {HumanizeAction(other)}!";
                _rebindingAction = null;
                _rebindingButton = null;
                _rebindingLabel = null;
                var capturedAction = action;
                var capturedLabel = labelInput;
                GetTree().CreateTimer(2.0).Timeout += () =>
                {
                    if (IsInstanceValid(capturedLabel))
                        capturedLabel.Text = GetKeyName(capturedAction);
                };
                return;
            }
        }

        // Apply
        foreach (var ev in InputMap.ActionGetEvents(action))
        {
            if (ev is InputEventKey)
                InputMap.ActionEraseEvent(action, ev);
        }
        InputMap.ActionAddEvent(action, new InputEventKey { PhysicalKeycode = keyEvent.PhysicalKeycode });

        // Persist
        SettingsManager.Instance.SaveInputSettings();

        labelInput.Text = OS.GetKeycodeString(newKey);

        _rebindingAction = null;
        _rebindingButton = null;
        _rebindingLabel = null;
    }

    private void CancelRebind()
    {
        if (_rebindingLabel != null)
            _rebindingLabel.Text = GetKeyName(_rebindingAction);
        _rebindingAction = null;
        _rebindingButton = null;
        _rebindingLabel = null;
    }

    private void OnResetPressed()
    {
        InputMap.LoadFromProjectSettings();
        ConfigFileHandler.SettingsConfig.EraseSection("input_settings");
        ConfigFileHandler.SettingsConfig.Save(ConfigFileHandler.SettingsPath);
        PopulateActions();
    }

    private void OnVisibilityChanged()
    {
        if (InputSettingsControl.IsVisibleInTree() && InputDeviceManager.Instance?.IsPointerless == true)
            GrabFirstFocus();
    }

    private void OnDeviceChanged(int device)
    {
        var d = (InputDeviceManager.InputDevice)device;
        if (d != InputDeviceManager.InputDevice.Mouse && InputSettingsControl.IsVisibleInTree())
            GrabFirstFocus();
    }

    private void GrabFirstFocus()
    {
        var children = ActionsContainer.GetChildren();
        if (children.Count > 0 && children[0] is Button btn)
            btn.GrabFocus();
    }

    private static Key GetCurrentKey(string action)
    {
        foreach (var ev in InputMap.ActionGetEvents(action))
        {
            if (ev is InputEventKey key)
                return key.PhysicalKeycode;
        }
        return Key.None;
    }

    private static string GetKeyName(string action)
    {
        var key = GetCurrentKey(action);
        return key == Key.None ? "---" : OS.GetKeycodeString(key);
    }

    private static string HumanizeAction(string action)
        => action.Replace("_", " ").ToUpper();
}
