// =====================================================================
//  InputSystem — guided tour of Crafter::Input
// =====================================================================
//
//  Run this with a window focused. The console narrates each event as
//  it fires; the on-screen window only exists so the OS routes keyboard
//  and mouse input to us. Plug a gamepad in (Xbox / DualShock /
//  Switch Pro) and it will be picked up live.
//
//  What this demo demonstrates, in order:
//
//   1. Three runtime layers stacked end-to-end:
//        - Crafter::Key(CrafterKeys::X)   compile-time abstract → raw
//                                         platform KeyCode
//        - Window::onRawKeyDown            raw keyboard channel for UI /
//                                         text input (NOT used for game
//                                         input — actions handle that)
//        - Crafter::Input::Map             named actions → bindings →
//                                         events
//        - Crafter::Gamepad                hot-plug, button + axis state,
//                                         rumble
//
//   2. All four action types: Button, Axis1D, Vector2, with multiple
//      bindings per action (any-of semantics — press Space OR press
//      gamepad-A; both trigger Jump).
//
//   3. Composite bindings: WASDBind (4 keys → Vector2),
//      GamepadStickBind (analog stick → Vector2), MouseDeltaBind
//      (mouse movement → Vector2).
//
//   4. Runtime rebinding: press R to enter rebind mode; the next key /
//      mouse button / gamepad button you press becomes the new Jump
//      binding. The captured input does NOT fire Jump that frame.
//
//   5. Serialization: bindings are round-tripped to text strings at
//      startup. Use these as the on-disk format for your settings file.
//
//   6. Hot-plug: connect / disconnect a gamepad while running. The
//      Map's polling-based evaluation seamlessly switches over —
//      bindings to a vanished gamepad become inert until reconnect.
//
//  Controls:
//     Space / Gamepad A           → Jump
//     WASD / Left stick           → Move
//     Mouse-Left / Right Trigger  → Fire
//     Mouse delta / Right stick   → Look
//     Mouse wheel                 → Zoom (Axis1D)
//     R                           → Start rebinding "Jump" — press
//                                   any key/button next to assign it
//     Esc                         → Quit
//
//  Output is intentionally chatty so you can SEE the system working.
//  In a real game, replace the print statements with your gameplay
//  code — that's it.
// =====================================================================

import Crafter.Graphics;
import Crafter.Event;        // EventListener<>: Crafter.Graphics imports
import Crafter.Math;         // Vector<float,2>:  it transitively, but
                             // C++20 module-reachability rules require
                             // us to name them explicitly to instantiate
                             // the templates in this TU.
import std;
using namespace Crafter;
using namespace Crafter::Input;

int main() {
    Device::Initialize();
    Window window(640, 360, "Crafter Input — give the window focus");

    // =================================================================
    //  Build the action map.
    //
    //  AddAction returns a reference; we keep it so we can subscribe.
    //  The map owns the actions (vector<unique_ptr>); references stay
    //  valid for the map's lifetime.
    // =================================================================
    Map map;
    Action& jump  = map.AddAction("Jump",  ActionType::Button);
    Action& move  = map.AddAction("Move",  ActionType::Vector2);
    Action& fire  = map.AddAction("Fire",  ActionType::Button);
    Action& look  = map.AddAction("Look",  ActionType::Vector2);
    Action& zoom  = map.AddAction("Zoom",  ActionType::Axis1D);
    Action& reset = map.AddAction("Reset", ActionType::Button);
    Action& quit  = map.AddAction("Quit",  ActionType::Button);

    // =================================================================
    //  Default bindings.
    //
    //  Key(CrafterKeys::X) is `consteval` — these literals fold to
    //  per-platform scancodes at compile time, no runtime translation.
    //  Multiple bindings per action are OR'd: pressing EITHER triggers
    //  the action.
    //
    //  gamepadId == 0 is a sentinel here meaning "first connected
    //  gamepad". For multi-player you'd save the stable id from a
    //  specific Device and use that.
    // =================================================================
    jump.bindings = {
        KeyBind{ Key(CrafterKeys::Space) },
        GamepadButtonBind{ 0, Gamepad::Button::South },
    };
    move.bindings = {
        WASDBind{
            Key(CrafterKeys::W), Key(CrafterKeys::S),
            Key(CrafterKeys::A), Key(CrafterKeys::D),
        },
        GamepadStickBind{ 0, Gamepad::Stick::Left },
    };
    fire.bindings = {
        MouseButtonBind{ 0 },                                       // left click
        GamepadAxisBind{ 0, Gamepad::Axis::RightTrigger, false },   // analog R2
    };
    look.bindings = {
        MouseDeltaBind{ 0.01f },                                    // scale down
        GamepadStickBind{ 0, Gamepad::Stick::Right },
    };
    zoom.bindings = {
        MouseScrollBind{},
    };
    reset.bindings = { KeyBind{ Key(CrafterKeys::R) } };
    quit.bindings  = { KeyBind{ Key(CrafterKeys::Escape) } };

    // =================================================================
    //  Serialization round-trip — call sites can save these strings to
    //  disk and load them back into Action::bindings later.
    //
    //  IMPORTANT: KeyCode values are platform-specific (Win32 scancodes
    //  vs. Wayland kernel keycodes). A saved file from one platform
    //  does NOT load on the other. Store your config under platform-
    //  specific paths.
    // =================================================================
    std::println("─── Default bindings (serialized form) ───");
    for (auto& a : map.actions) {
        for (auto& b : a->bindings) {
            std::string s = BindingToString(b);
            // Parse it back to prove the format roundtrips.
            auto parsed = BindingFromString(s);
            std::println("  {:<6}  {}  (roundtrip OK: {})",
                         a->name, s, parsed.has_value());
        }
    }
    std::println("");

    // =================================================================
    //  Subscribe to action events.
    //
    //  EventListeners use the same RAII pattern as the rest of the
    //  library — declare them in scope, they unsubscribe when they go
    //  out of scope. Capturing references to the Actions is safe
    //  because the Map (and therefore its actions) outlive these
    //  listeners (both are in main()'s scope).
    // =================================================================
    EventListener<void> jumpPerf(&jump.onPerformed,
        [] { std::println("[Jump]   performed"); });
    EventListener<void> jumpCanc(&jump.onCanceled,
        [] { std::println("[Jump]   canceled"); });

    EventListener<Vector<float,2>> moveChanged(&move.onVector2Changed,
        [](Vector<float,2> v) {
            std::println("[Move]   ({:+.2f}, {:+.2f})", v.x, v.y);
        });

    EventListener<void> firePerf(&fire.onPerformed,
        [] { std::println("[Fire]   performed"); });
    EventListener<void> fireCanc(&fire.onCanceled,
        [] { std::println("[Fire]   canceled"); });

    EventListener<Vector<float,2>> lookChanged(&look.onVector2Changed,
        [](Vector<float,2> v) {
            // Mouse delta fires every frame the mouse moves; throttle
            // by checking magnitude so the console doesn't drown.
            if (std::abs(v.x) + std::abs(v.y) > 0.01f) {
                std::println("[Look]   ({:+.3f}, {:+.3f})", v.x, v.y);
            }
        });

    EventListener<float> zoomChanged(&zoom.onValueChanged,
        [](float v) { std::println("[Zoom]   delta {:+.0f}", v); });

    EventListener<void> quitPerf(&quit.onPerformed,
        [&] {
            std::println("[Quit]   shutting down");
            window.open = false;
        });

    // =================================================================
    //  Rebinding flow.
    //
    //  Pressing R fires Reset → we call StartRebind for the Jump
    //  action. While rebinding, the next qualifying input does NOT
    //  fire Jump (or any other action that uses the same input). It
    //  routes to the onCaptured callback exactly once.
    //
    //  CaptureMask::Any accepts keyboard | mouse | gamepad. Narrow it
    //  (Keyboard, Gamepad, etc.) to restrict what counts as a valid
    //  binding for a given action.
    // =================================================================
    EventListener<void> resetPerf(&reset.onPerformed, [&] {
        std::println("[Rebind] Press any key / button to bind \"Jump\" "
                     "(Esc to cancel)…");
        map.StartRebind(jump, CaptureMask::Any, [&](Binding captured) {
            jump.bindings.clear();
            jump.bindings.push_back(captured);
            std::println("[Rebind] Jump → {}", BindingToString(captured));
        });
    });

    // =================================================================
    //  Gamepad lifecycle observers.
    //
    //  Gamepad::Tick() is called automatically inside Window::StartSync
    //  every frame, so these events fire on the main thread alongside
    //  every other event. Hot-plug a controller while running and you
    //  should see the connect line below.
    // =================================================================
    EventListener<Gamepad::Device*> gpAdded(&Gamepad::onConnected,
        [](Gamepad::Device* d) {
            std::println("[Gamepad] connected: id={} name=\"{}\"",
                         d->id, d->name);
        });
    EventListener<Gamepad::Device*> gpRemoved(&Gamepad::onDisconnected,
        [](Gamepad::Device* d) {
            std::println("[Gamepad] disconnected: id={}", d->id);
        });

    // =================================================================
    //  Connect the map to the window. Map polls window state + gamepad
    //  state every Tick; we trigger Tick from onBeforeUpdate so it
    //  runs after the OS event pump (and Gamepad::Tick) for that frame.
    // =================================================================
    map.Attach(window);
    EventListener<void> tick(&window.onBeforeUpdate, [&] { map.Tick(); });

    std::println("─── Ready. Focus the window and try the controls. ───");
    window.Render();
    window.StartSync();

    std::println("Done.");
    return 0;
}
