namespace splat;

using Godot;

/// <summary>
/// Registers and abstracts device-agnostic input actions (per the
/// controller-first design pillar) and tracks the currently selected build
/// tool. Both gamepad and keyboard/mouse map onto the same InputMap actions.
/// </summary>
public partial class InputManager : Node
{
    public enum Tool
    {
        Select,
        PlaceBuilding,
        PlaceRoad,
        Demolish,
        Inspect
    }

    public Tool CurrentTool { get; private set; } = Tool.Select;

    /// <summary>Emitted when the active tool changes.</summary>
    [Signal] public delegate void ToolChangedEventHandler(Tool tool);

    /// <summary>Emitted when the player asks to open or close the build menu.</summary>
    [Signal] public delegate void BuildMenuRequestedEventHandler();

    /// <summary>Emitted when the player backs out of a menu or active tool.</summary>
    [Signal] public delegate void CancelRequestedEventHandler();

    private static readonly string[] CameraActions =
    {
        "cam_pan_left", "cam_pan_right", "cam_pan_forward", "cam_pan_back",
        "cam_rotate_left", "cam_rotate_right", "cam_rotate_up", "cam_rotate_down",
        "cam_zoom_in", "cam_zoom_out"
    };

    public override void _Ready()
    {
        RegisterCameraActions();
        RegisterUiActions();
    }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (@event.IsActionPressed("ui_build_menu"))
            EmitSignal(SignalName.BuildMenuRequested);
        else if (@event.IsActionPressed("ui_place_road"))
            SetTool(CurrentTool == Tool.PlaceRoad ? Tool.Select : Tool.PlaceRoad);
        else if (@event.IsActionPressed("ui_demolish"))
            SetTool(Tool.Demolish);
        else if (@event.IsActionPressed("ui_inspect"))
            SetTool(Tool.Inspect);
        else if (@event.IsActionPressed("placement_cancel"))
            EmitSignal(SignalName.CancelRequested);
    }

    public void SetTool(Tool tool)
    {
        if (tool == CurrentTool)
            return;
        CurrentTool = tool;
        EmitSignal(SignalName.ToolChanged, (int)tool);
    }

    private static void RegisterCameraActions()
    {
        foreach (var action in CameraActions)
            AddActionIfMissing(action);

        Bind("cam_pan_left", JoyAxis.LeftX, -1f);
        Bind("cam_pan_right", JoyAxis.LeftX, 1f);
        Bind("cam_pan_forward", JoyAxis.LeftY, -1f);
        Bind("cam_pan_back", JoyAxis.LeftY, 1f);
        Bind("cam_rotate_left", JoyAxis.RightX, -1f);
        Bind("cam_rotate_right", JoyAxis.RightX, 1f);
        Bind("cam_rotate_up", JoyAxis.RightY, -1f);
        Bind("cam_rotate_down", JoyAxis.RightY, 1f);
        Bind("cam_zoom_in", JoyButton.RightShoulder);
        Bind("cam_zoom_out", JoyButton.LeftShoulder);

        // Keyboard/mouse alternates.
        AddKey("cam_pan_left", Key.A);
        AddKey("cam_pan_right", Key.D);
        AddKey("cam_pan_forward", Key.W);
        AddKey("cam_pan_back", Key.S);
        AddKey("cam_zoom_in", Key.Equal);
        AddKey("cam_zoom_out", Key.Minus);
    }

    private static void RegisterUiActions()
    {
        AddActionIfMissing("ui_build_menu");
        AddActionIfMissing("ui_place_road");
        AddActionIfMissing("ui_demolish");
        AddActionIfMissing("ui_inspect");
        AddActionIfMissing("placement_confirm");
        AddActionIfMissing("placement_cancel");
        AddActionIfMissing("game_cycle_speed");

        AddKey("ui_build_menu", Key.Tab);
        AddKey("ui_place_road", Key.R);
        AddKey("ui_demolish", Key.Delete);
        AddKey("ui_inspect", Key.I);
        AddKey("placement_confirm", Key.Enter);
        AddKey("placement_cancel", Key.Escape);
        AddKey("game_cycle_speed", Key.Space);

        // Controller-first bindings: face buttons own the primary build loop.
        Bind("ui_build_menu", JoyButton.Y);
        Bind("ui_place_road", JoyButton.DpadRight);
        Bind("ui_demolish", JoyButton.X);
        Bind("ui_inspect", JoyButton.DpadUp);
        Bind("placement_confirm", JoyButton.A);
        Bind("placement_cancel", JoyButton.B);
        Bind("game_cycle_speed", JoyButton.Start);
    }

    private static void AddActionIfMissing(string action)
    {
        if (!InputMap.HasAction(action))
            InputMap.AddAction(action);
    }

    private static void Bind(string action, JoyAxis axis, float sign)
    {
        var ev = new InputEventJoypadMotion { Axis = axis, AxisValue = sign };
        AddEventIfMissing(action, ev);
    }

    private static void Bind(string action, JoyButton button)
    {
        var ev = new InputEventJoypadButton { ButtonIndex = button };
        AddEventIfMissing(action, ev);
    }

    private static void AddKey(string action, Key key)
    {
        var ev = new InputEventKey { Keycode = key };
        AddEventIfMissing(action, ev);
    }

    private static void AddEventIfMissing(string action, InputEvent inputEvent)
    {
        if (!InputMap.ActionHasEvent(action, inputEvent))
            InputMap.ActionAddEvent(action, inputEvent);
    }
}
