/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
Catcrafts.net

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3.0 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*/
export module Crafter.Graphics:Input;
import std;
import Crafter.Math;
import Crafter.Event;
import :Keys;
import :Gamepad;
import :Window;

// Named-action input mapping with rebindable bindings. Game code declares
// actions ("Jump", "Move", "Fire"), attaches binding sources (keys, mouse
// buttons, gamepad sticks, WASD-as-Vector2, etc.), and subscribes to
// `onPerformed` / `onCanceled` / `onValueChanged` events. Bindings can be
// rebound at runtime via `StartRebind` — the next qualifying input
// becomes the new binding.
//
// Polling-based evaluation: `Map::Tick()` (called once per frame from
// game code, AFTER `Gamepad::Tick()`) reads the polled state of the
// attached window + connected gamepads and diffs against the action's
// previous state. No per-device event listeners — works regardless of
// gamepad connect/disconnect timing.
//
// Bindings store raw platform key codes (the same `KeyCode` domain as
// `Window::onRawKeyDown`). Source code uses `Key(CrafterKeys::Space)`
// from :Keys to obtain cross-platform default codes at compile time.
// Binding files serialized with `BindingToString` are platform-specific
// (a Win32 save won't load on Wayland) — document this for callers.

export namespace Crafter::Input {

    enum class ActionType : std::uint8_t {
        Button,    // digital — pressed/released
        Axis1D,    // -1..1 (or 0..1 for triggers / scroll)
        Vector2,   // 2D — sticks, WASD, mouse delta
    };

    // ─── Binding alternatives ───────────────────────────────────────
    struct KeyBind            { KeyCode code; };
    struct MouseButtonBind    { std::uint8_t button; };     // 0 = left, 1 = right
    struct MouseScrollBind    { };                          // Axis1D, accumulator drained per tick
    struct MouseDeltaBind     { float scale = 1.0f; };      // Vector2
    struct GamepadButtonBind  { std::uint32_t gamepadId; Gamepad::Button button; };
    struct GamepadAxisBind    { std::uint32_t gamepadId; Gamepad::Axis axis; bool invert = false; };
    struct GamepadStickBind   { std::uint32_t gamepadId; Gamepad::Stick stick; };   // Vector2
    struct WASDBind {
        KeyCode up;
        KeyCode down;
        KeyCode left;
        KeyCode right;
    };

    using Binding = std::variant<
        KeyBind, MouseButtonBind, MouseScrollBind, MouseDeltaBind,
        GamepadButtonBind, GamepadAxisBind, GamepadStickBind, WASDBind
    >;

    struct Action {
        std::string  name;
        ActionType   type;
        std::vector<Binding> bindings;
        // Analog deadzone — bindings below this magnitude read as zero.
        // Applied per-axis to GamepadAxisBind, radially to
        // GamepadStickBind. Doesn't apply to Button (digital).
        float        deadzone = 0.15f;

        // Polled state. Read freely from the listener callback or
        // anywhere else; updated by `Map::Tick()`.
        bool             pressed = false;
        float            value   = 0.0f;
        Vector<float, 2> vector2{};

        // Events fired during `Map::Tick()` when the polled state crosses
        // an edge or changes value.
        Event<void>             onPerformed;       // pressed (Button) / non-zero edge (axis)
        Event<void>             onCanceled;        // released (Button) / back to zero
        Event<float>            onValueChanged;    // Axis1D — value
        Event<Vector<float,2>>  onVector2Changed;  // Vector2 — vector2
    };

    enum class CaptureMask : std::uint8_t {
        Keyboard = 1,
        Mouse    = 2,
        Gamepad  = 4,
        Any      = 0xFF,
    };
    constexpr CaptureMask operator|(CaptureMask a, CaptureMask b) {
        return (CaptureMask)((std::uint8_t)a | (std::uint8_t)b);
    }
    constexpr bool HasFlag(CaptureMask m, CaptureMask f) {
        return ((std::uint8_t)m & (std::uint8_t)f) != 0;
    }

    struct Map {
        std::vector<std::unique_ptr<Action>> actions;

        Action& AddAction(std::string name, ActionType type);
        Action* Find(std::string_view name);

        // Subscribe internally to the window's mouse-scroll event so a
        // single scroll between ticks isn't lost. All other state is
        // polled from `Window` / `Gamepad`. Detaching releases that one
        // listener; the polled-state reads automatically stop.
        void Attach(Window& window);
        void Detach();

        // Apply current input state to all actions. Fire edge / value
        // events. Call once per frame AFTER `Gamepad::Tick()`. Safe to
        // call when not attached (no-op).
        void Tick();

        // Capture the next qualifying input as a binding. While capture
        // is active, that input does NOT dispatch to actions — it only
        // routes to `onCaptured`. Capturing replaces nothing in the
        // action's bindings; the callback decides whether to assign,
        // append, or discard. Cancel via `StopRebind`.
        void StartRebind(Action& action, CaptureMask mask,
                         std::function<void(Binding)> onCaptured);
        void StopRebind();
        bool IsRebinding() const;

        // ─── Internals (exposed because we're a POD-ish struct) ──────
        Window* window = nullptr;
        // Mouse scroll is the one event-driven source; everything else
        // is polled. The listener feeds into `scrollAccumulator`,
        // drained on the next Tick.
        std::unique_ptr<EventListener<std::uint32_t>> scrollListener;
        std::int32_t scrollAccumulator = 0;

        // Mouse delta is computed from window.currentMousePos vs the
        // position observed last tick. nullopt before first tick.
        std::optional<Vector<float, 2>> lastMousePos;

        // Rebind state.
        struct RebindState {
            Action* action;
            CaptureMask mask;
            std::function<void(Binding)> onCaptured;
            // Snapshot of all input at rebind-start, so we only fire on
            // a fresh down-edge (not on a button held since before).
            std::unordered_set<KeyCode>   keysHeldAtStart;
            bool mouseLeftHeldAtStart  = false;
            bool mouseRightHeldAtStart = false;
            std::unordered_map<std::uint32_t, std::array<bool, (std::size_t)Gamepad::Button::Max>> gamepadButtonsAtStart;
        };
        std::optional<RebindState> rebind;
    };

    // ─── Serialization ──────────────────────────────────────────────
    // Single-binding text round-trip. Caller composes binding files
    // however they like (JSON / INI / line-per-binding). Formats:
    //   "key:<hex>"             — KeyBind
    //   "mb:<0|1>"              — MouseButtonBind
    //   "mscroll"               — MouseScrollBind
    //   "mdelta:<scale>"        — MouseDeltaBind
    //   "gpb:<id>:<button>"     — GamepadButtonBind
    //   "gpa:<id>:<axis>:<inv>" — GamepadAxisBind (inv = 0/1)
    //   "gps:<id>:<stick>"      — GamepadStickBind
    //   "wasd:<u>:<d>:<l>:<r>"  — WASDBind, four hex KeyCodes
    // KeyCode hex values are platform-specific; see :Keys.
    std::string  BindingToString(const Binding&);
    std::optional<Binding> BindingFromString(std::string_view);
}
