// define_inputs.cpp - Implementacio de l'overlay modal de redefinicio
// © 2026 JailDesigner

#include "core/input/define_inputs.hpp"

#include <format>
#include <string>
#include <vector>

#include "core/audio/audio.hpp"
#include "core/defaults/service_menu.hpp"
#include "core/input/input.hpp"
#include "core/locale/locale.hpp"
#include "core/types.hpp"
#include "game/config_yaml.hpp"

namespace {

    constexpr float CANVAS_W = 1280.0F;
    constexpr float CANVAS_H = 720.0F;

    // Llindar de trigger per a edge-detect L2/R2 com a boto virtual.
    constexpr Sint16 TRIGGER_THRESHOLD = 16384;

    // Codis virtuals per als triggers (consistents amb input_types.cpp).
    constexpr int TRIGGER_L2_VIRTUAL = 100;
    constexpr int TRIGGER_R2_VIRTUAL = 101;

    // Durada del missatge de confirmacio abans de tancar-se.
    constexpr float COMPLETE_DISPLAY_S = 1.5F;

    // Llindar dpad als axis sticks: no es captura per evitar conflicte amb el
    // moviment LEFT/RIGHT/UP/DOWN (que es presuposen no redefinibles al mando).
    constexpr Sint16 STICK_THRESHOLD = 16384;

    // Crida pushRect amb un SDL_Color (les components s'escalen a [0..1]).
    void fillRect(Rendering::Renderer* renderer, float x, float y, float w, float h, SDL_Color color) {
        renderer->pushRect(x, y, w, h, static_cast<float>(color.r) / 255.0F, static_cast<float>(color.g) / 255.0F, static_cast<float>(color.b) / 255.0F, static_cast<float>(color.a) / 255.0F);
    }

    auto titleKey(System::DefineInputs::Mode mode, System::DefineInputs::Player player) -> std::string {
        const bool IS_KB = (mode == System::DefineInputs::Mode::KEYBOARD);
        const bool IS_P1 = (player == System::DefineInputs::Player::P1);
        if (IS_KB && IS_P1) {
            return "define.title_keyboard_p1";
        }
        if (IS_KB) {
            return "define.title_keyboard_p2";
        }
        if (IS_P1) {
            return "define.title_gamepad_p1";
        }
        return "define.title_gamepad_p2";
    }

    // Scancodes que MAI capturem com a binding (reservats per a navegacio o
    // global hotkeys). Tornen true → handleEvent les deixa passar al pipeline
    // global perque facin la seua feina (ESC obre el prompt d'eixida, F1-F12
    // son hotkeys de sistema, RETURN/BACKSPACE/TAB son navegacio).
    auto isReservedScancode(SDL_Scancode sc) -> bool {
        if (sc == SDL_SCANCODE_ESCAPE) {
            return true;
        }
        if (sc >= SDL_SCANCODE_F1 && sc <= SDL_SCANCODE_F12) {
            return true;
        }
        if (sc == SDL_SCANCODE_RETURN || sc == SDL_SCANCODE_KP_ENTER) {
            return true;
        }
        if (sc == SDL_SCANCODE_BACKSPACE || sc == SDL_SCANCODE_TAB) {
            return true;
        }
        return false;
    }

    // Conversio sense pèrdua de SDL_Scancode → int per a comparacions
    // homogenies dins de sequence_ (que guarda codis de tots dos modes).
    auto scancodeToInt(SDL_Scancode sc) -> int {
        return static_cast<int>(sc);
    }

}  // namespace

namespace System {

    std::unique_ptr<DefineInputs> DefineInputs::instance;

    void DefineInputs::init(Rendering::Renderer* renderer) {
        if (!instance) {
            instance = std::unique_ptr<DefineInputs>(new DefineInputs(renderer));
        }
    }

    void DefineInputs::destroy() { instance.reset(); }

    auto DefineInputs::get() -> DefineInputs* { return instance.get(); }

    DefineInputs::DefineInputs(Rendering::Renderer* renderer)
        : renderer_(renderer),
          text_(renderer) {}

    auto DefineInputs::isActive() const -> bool {
        return phase_ != Phase::INACTIVE;
    }

    auto DefineInputs::begin(Mode mode, Player player) -> bool {
        if (mode == Mode::GAMEPAD) {
            // Requereix un pad assignat al jugador.
            const auto* input = Input::get();
            if (input == nullptr) {
                return false;
            }
            const int IDX = (player == Player::P1) ? 0 : 1;
            if (input->getPlayerGamepad(IDX) == nullptr) {
                return false;
            }
        }

        mode_ = mode;
        player_ = player;
        index_ = 0;
        complete_timer_s_ = 0.0F;
        l2_was_pressed_ = false;
        r2_was_pressed_ = false;

        buildSequence();
        phase_ = Phase::CAPTURING;
        return true;
    }

    void DefineInputs::cancel() {
        phase_ = Phase::INACTIVE;
        sequence_.clear();
        index_ = 0;
        complete_timer_s_ = 0.0F;
    }

    void DefineInputs::buildSequence() {
        sequence_.clear();
        if (mode_ == Mode::KEYBOARD) {
            // Teclat: LEFT, RIGHT, FIRE (SHOOT), ACCELERATE (THRUST)
            sequence_.push_back({.action_label_key = "define.action.left", .action = InputAction::LEFT, .captured = -1});
            sequence_.push_back({.action_label_key = "define.action.right", .action = InputAction::RIGHT, .captured = -1});
            sequence_.push_back({.action_label_key = "define.action.fire", .action = InputAction::SHOOT, .captured = -1});
            sequence_.push_back({.action_label_key = "define.action.accelerate", .action = InputAction::THRUST, .captured = -1});
        } else {
            // Mando: FIRE, ACCELERATE, START, MENU
            sequence_.push_back({.action_label_key = "define.action.fire", .action = InputAction::SHOOT, .captured = -1});
            sequence_.push_back({.action_label_key = "define.action.accelerate", .action = InputAction::THRUST, .captured = -1});
            sequence_.push_back({.action_label_key = "define.action.start", .action = InputAction::START, .captured = -1});
            sequence_.push_back({.action_label_key = "define.action.menu", .action = InputAction::MENU, .captured = -1});
        }
    }

    auto DefineInputs::isInUse(int code) const -> bool {
        for (const auto& s : sequence_) {
            if (s.captured == code) {
                return true;
            }
        }
        return false;
    }

    void DefineInputs::captureAndAdvance(int code) {
        if (index_ >= sequence_.size()) {
            return;
        }
        if (isInUse(code)) {
            return;  // Duplicat dins de la sessio: rebutgem silenciosament
        }
        sequence_[index_].captured = code;
        ++index_;
        if (auto* audio = Audio::get(); audio != nullptr) {
            audio->playSound(Defaults::ServiceMenu::ACCEPT_SOUND, Audio::Group::INTERFACE);
        }
        if (index_ >= sequence_.size()) {
            persistAndComplete();
        }
    }

    void DefineInputs::persistAndComplete() {
        auto& cfg = (player_ == Player::P1)
            ? ConfigYaml::engine_config.player1
            : ConfigYaml::engine_config.player2;

        if (mode_ == Mode::KEYBOARD) {
            for (const Step& s : sequence_) {
                switch (s.action) {
                    case InputAction::LEFT:
                        cfg.keyboard.key_left = static_cast<SDL_Scancode>(s.captured);
                        break;
                    case InputAction::RIGHT:
                        cfg.keyboard.key_right = static_cast<SDL_Scancode>(s.captured);
                        break;
                    case InputAction::SHOOT:
                        cfg.keyboard.key_shoot = static_cast<SDL_Scancode>(s.captured);
                        break;
                    case InputAction::THRUST:
                        cfg.keyboard.key_thrust = static_cast<SDL_Scancode>(s.captured);
                        break;
                    default:
                        break;  // START / MENU no es redefineixen al teclat
                }
            }
        } else {
            for (const Step& s : sequence_) {
                switch (s.action) {
                    case InputAction::SHOOT:
                        cfg.gamepad.button_shoot = s.captured;
                        break;
                    case InputAction::THRUST:
                        cfg.gamepad.button_thrust = s.captured;
                        break;
                    case InputAction::START:
                        cfg.gamepad.button_start = s.captured;
                        break;
                    case InputAction::MENU:
                        cfg.gamepad.button_menu = s.captured;
                        break;
                    default:
                        break;  // LEFT / RIGHT no es redefineixen al mando
                }
            }
        }

        // Aplicar canvis al runtime de l'Input i persistir a disc.
        if (auto* input = Input::get(); input != nullptr) {
            if (player_ == Player::P1) {
                input->applyPlayer1Bindings(ConfigYaml::engine_config.player1);
            } else {
                input->applyPlayer2Bindings(ConfigYaml::engine_config.player2);
            }
        }
        ConfigYaml::saveToFile();

        phase_ = Phase::COMPLETE;
        complete_timer_s_ = COMPLETE_DISPLAY_S;
    }

    void DefineInputs::update(float delta_time) {
        if (phase_ != Phase::COMPLETE) {
            return;
        }
        complete_timer_s_ -= delta_time;
        if (complete_timer_s_ <= 0.0F) {
            cancel();
        }
    }

    void DefineInputs::processTrigger(int virtual_button, bool& was_pressed, bool now) {
        if (now && !was_pressed) {
            captureAndAdvance(virtual_button);
        }
        was_pressed = now;
    }

    auto DefineInputs::handleKeyboardEvent(const SDL_Event& event) -> bool {
        if (event.type != SDL_EVENT_KEY_DOWN) {
            return true;  // Empassem la resta sense fer res
        }
        const SDL_Scancode SC = event.key.scancode;
        if (isReservedScancode(SC)) {
            // ESC, F1-F12, RETURN, BACKSPACE, TAB es deixen passar al pipeline
            // global (ESC obre el prompt d'eixida; F1-F12 hotkeys, etc.).
            return false;
        }
        captureAndAdvance(scancodeToInt(SC));
        return true;
    }

    auto DefineInputs::handleGamepadEvent(const SDL_Event& event) -> bool {
        // KEY_DOWN no es per al rebind de mando: deixem que el global el
        // gestioni (ex. ESC → prompt d'eixida, F12 → tanca menu, etc.).
        if (event.type == SDL_EVENT_KEY_DOWN) {
            return false;
        }

        // Filtrar events al pad del jugador actiu.
        const auto* input = Input::get();
        if (input == nullptr) {
            return true;
        }
        const int IDX = (player_ == Player::P1) ? 0 : 1;
        auto pad = input->getPlayerGamepad(IDX);
        if (!pad) {
            return true;
        }

        if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
            if (event.gbutton.which != pad->instance_id) {
                return true;
            }
            captureAndAdvance(static_cast<int>(event.gbutton.button));
            return true;
        }

        if (event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
            if (event.gaxis.which != pad->instance_id) {
                return true;
            }
            const auto AXIS = static_cast<SDL_GamepadAxis>(event.gaxis.axis);
            const Sint16 VAL = event.gaxis.value;
            if (AXIS == SDL_GAMEPAD_AXIS_LEFT_TRIGGER) {
                processTrigger(TRIGGER_L2_VIRTUAL, l2_was_pressed_, VAL >= TRIGGER_THRESHOLD);
            } else if (AXIS == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
                processTrigger(TRIGGER_R2_VIRTUAL, r2_was_pressed_, VAL >= TRIGGER_THRESHOLD);
            }
            // Sticks LEFTX/LEFTY/RIGHTX/RIGHTY: ignorats (no son redefinibles).
            (void)STICK_THRESHOLD;
            return true;
        }

        return true;
    }

    auto DefineInputs::handleEvent(const SDL_Event& event) -> bool {
        if (phase_ == Phase::INACTIVE) {
            return false;
        }
        // SDL_EVENT_QUIT i WINDOW_CLOSE_REQUESTED han de poder tancar la
        // finestra encara que el modal estiga obert; els passem al pipeline.
        if (event.type == SDL_EVENT_QUIT ||
            event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
            return false;
        }
        if (phase_ == Phase::COMPLETE) {
            // Mentre mostrem el missatge OK, empassem la resta d'events sense
            // capturar perque l'usuari no puga avançar accions sense voler.
            return true;
        }
        if (mode_ == Mode::KEYBOARD) {
            return handleKeyboardEvent(event);
        }
        return handleGamepadEvent(event);
    }

    void DefineInputs::draw() const {
        if (phase_ == Phase::INACTIVE) {
            return;
        }

        using namespace Defaults::ServiceMenu;

        // Caixa centrada, dimensions fixes (no depen del contingut a redefinir).
        constexpr float BOX_W = 560.0F;
        constexpr float BOX_H = 280.0F;
        const float BOX_X = (CANVAS_W - BOX_W) * 0.5F;
        const float BOX_Y = (CANVAS_H - BOX_H) * 0.5F;

        // Fons + brackets als 4 cantons (estil HUD del menu de servei).
        fillRect(renderer_, BOX_X, BOX_Y, BOX_W, BOX_H, BG_COLOR);

        const auto T = static_cast<float>(CORNER_THICKNESS);
        const auto AH = static_cast<float>(CORNER_ARM_H);
        const auto AV = static_cast<float>(CORNER_ARM_V);
        fillRect(renderer_, BOX_X, BOX_Y, AH, T, CORNER_COLOR);
        fillRect(renderer_, BOX_X, BOX_Y, T, AV, CORNER_COLOR);
        fillRect(renderer_, BOX_X + BOX_W - AH, BOX_Y, AH, T, CORNER_COLOR);
        fillRect(renderer_, BOX_X + BOX_W - T, BOX_Y, T, AV, CORNER_COLOR);
        fillRect(renderer_, BOX_X, BOX_Y + BOX_H - T, AH, T, CORNER_COLOR);
        fillRect(renderer_, BOX_X, BOX_Y + BOX_H - AV, T, AV, CORNER_COLOR);
        fillRect(renderer_, BOX_X + BOX_W - AH, BOX_Y + BOX_H - T, AH, T, CORNER_COLOR);
        fillRect(renderer_, BOX_X + BOX_W - T, BOX_Y + BOX_H - AV, T, AV, CORNER_COLOR);

        const std::string TITLE = Locale::get().text(titleKey(mode_, player_));
        const float TITLE_W = Graphics::VectorText::getTextWidth(TITLE, TITLE_SCALE, TEXT_SPACING);
        const float TITLE_X = BOX_X + ((BOX_W - TITLE_W) * 0.5F);
        const float TITLE_Y = BOX_Y + 26.0F;
        text_.render(TITLE, Vec2{.x = TITLE_X, .y = TITLE_Y}, TITLE_SCALE, TEXT_SPACING, 1.0F, TITLE_COLOR);

        if (phase_ == Phase::COMPLETE) {
            const std::string OK = Locale::get().text("define.complete");
            constexpr float OK_SCALE = 0.7F;
            const float OK_W = Graphics::VectorText::getTextWidth(OK, OK_SCALE, TEXT_SPACING);
            const float OK_X = BOX_X + ((BOX_W - OK_W) * 0.5F);
            const float OK_Y = BOX_Y + (BOX_H * 0.5F) - 10.0F;
            constexpr SDL_Color OK_COLOR{.r = 120, .g = 255, .b = 140, .a = 255};
            text_.render(OK, Vec2{.x = OK_X, .y = OK_Y}, OK_SCALE, TEXT_SPACING, 1.0F, OK_COLOR);
            return;
        }

        // Instruccio (premeu tecla / boto) + accio actual + progres.
        const std::string PROMPT = Locale::get().text(
            mode_ == Mode::KEYBOARD ? "define.press_key" : "define.press_button");
        const float PROMPT_W = Graphics::VectorText::getTextWidth(PROMPT, ITEM_SCALE, TEXT_SPACING);
        const float PROMPT_X = BOX_X + ((BOX_W - PROMPT_W) * 0.5F);
        const float PROMPT_Y = BOX_Y + 86.0F;
        text_.render(PROMPT, Vec2{.x = PROMPT_X, .y = PROMPT_Y}, ITEM_SCALE, TEXT_SPACING, 1.0F, SUBTITLE_COLOR);

        if (index_ < sequence_.size()) {
            const std::string ACTION = Locale::get().text(sequence_[index_].action_label_key);
            constexpr float ACTION_SCALE = 0.9F;
            const float ACTION_W = Graphics::VectorText::getTextWidth(ACTION, ACTION_SCALE, TEXT_SPACING);
            const float ACTION_X = BOX_X + ((BOX_W - ACTION_W) * 0.5F);
            const float ACTION_Y = BOX_Y + 130.0F;
            text_.render(ACTION, Vec2{.x = ACTION_X, .y = ACTION_Y}, ACTION_SCALE, TEXT_SPACING, 1.0F, CURSOR_COLOR);
        }

        const std::string PROGRESS = std::format("{}/{}", index_ + 1, sequence_.size());
        constexpr float PROG_SCALE = 0.4F;
        const float PROG_W = Graphics::VectorText::getTextWidth(PROGRESS, PROG_SCALE, TEXT_SPACING);
        const float PROG_X = BOX_X + ((BOX_W - PROG_W) * 0.5F);
        const float PROG_Y = BOX_Y + 200.0F;
        text_.render(PROGRESS, Vec2{.x = PROG_X, .y = PROG_Y}, PROG_SCALE, TEXT_SPACING, 1.0F, LABEL_COLOR);
    }

}  // namespace System
