// notifier.cpp - Implementació del singleton de notificacions toast

#include "core/system/notifier.hpp"

#include "core/defaults.hpp"
#include "core/rendering/gpu/gpu_frame_renderer.hpp"
#include "core/utils/easing.hpp"

namespace System {

    namespace {
        // Alias d'àmbit local per accedir compactament als defaults del notifier.
        namespace Cfg = Defaults::Notifier;

        // Conversió color SDL → float [0,1].
        constexpr auto toUnit(Uint8 v) -> float {
            return static_cast<float>(v) / 255.0F;
        }

        // Color del fons: variant fosca del text (0.25× RGB) amb alpha 0.65.
        struct UnitRGBA {
            float r;
            float g;
            float b;
            float a;
        };

        constexpr auto textColorFloat(SDL_Color c) -> UnitRGBA {
            return UnitRGBA{.r = toUnit(c.r), .g = toUnit(c.g), .b = toUnit(c.b), .a = toUnit(c.a)};
        }

        constexpr auto bgColorFloat(SDL_Color c) -> UnitRGBA {
            constexpr float DARKEN = 0.25F;
            constexpr float BG_ALPHA = 0.65F;
            return UnitRGBA{
                .r = toUnit(c.r) * DARKEN,
                .g = toUnit(c.g) * DARKEN,
                .b = toUnit(c.b) * DARKEN,
                .a = BG_ALPHA};
        }
    }  // namespace

    std::unique_ptr<Notifier> Notifier::instance;

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

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

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

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

    void Notifier::notify(const std::string& text, SDL_Color text_color, float duration_s) {
        current_text_ = text;
        current_color_ = text_color;
        hold_remaining_s_ = duration_s;
        current_is_exit_ = false;

        const float TEXT_W = Graphics::VectorText::getTextWidth(text, Cfg::TEXT_SCALE, Cfg::TEXT_SPACING);
        const float TEXT_H = Graphics::VectorText::getTextHeight(Cfg::TEXT_SCALE);

        box_w_ = TEXT_W + (Cfg::PADDING_H * 2.0F);
        box_h_ = TEXT_H + (Cfg::PADDING_V * 2.0F);
        text_x_ = (Cfg::CANVAS_WIDTH - TEXT_W) * 0.5F;

        y_on_ = Cfg::MARGIN_TOP;
        y_off_ = -(box_h_ + Cfg::BORDER_THICKNESS);

        // Si ja es veu, reseteja el slide-in des de la posició actual perquè
        // la transició sembli continua. Si està amagat, arrenc des de fora.
        if (status_ == Status::HIDDEN) {
            y_current_ = y_off_;
        }
        status_ = Status::ENTERING;
        slide_elapsed_s_ = 0.0F;
        text_scale_ = Cfg::TEXT_SCALE;
    }

    void Notifier::notifyInfo(const std::string& text) { notify(text, Cfg::COLOR_INFO, Cfg::DURATION_INFO); }
    void Notifier::notifyWarn(const std::string& text) { notify(text, Cfg::COLOR_WARN, Cfg::DURATION_WARN); }
    void Notifier::notifyExit(const std::string& text) {
        notify(text, Cfg::COLOR_EXIT, Cfg::DURATION_EXIT);
        current_is_exit_ = true;  // notify() ho ha posat a false; restaurem.
    }

    void Notifier::update(float delta_time) {
        switch (status_) {
            case Status::ENTERING: {
                slide_elapsed_s_ += delta_time;
                if (slide_elapsed_s_ >= Cfg::SLIDE_DURATION_S) {
                    y_current_ = y_on_;
                    status_ = Status::HOLDING;
                    slide_elapsed_s_ = 0.0F;
                } else {
                    const float T = slide_elapsed_s_ / Cfg::SLIDE_DURATION_S;
                    const float K = Utils::Easing::outCubic(T);
                    y_current_ = y_off_ + ((y_on_ - y_off_) * K);
                }
                break;
            }
            case Status::HOLDING: {
                hold_remaining_s_ -= delta_time;
                if (hold_remaining_s_ <= 0.0F) {
                    status_ = Status::EXITING;
                    slide_elapsed_s_ = 0.0F;
                }
                break;
            }
            case Status::EXITING: {
                slide_elapsed_s_ += delta_time;
                if (slide_elapsed_s_ >= Cfg::SLIDE_DURATION_S) {
                    y_current_ = y_off_;
                    status_ = Status::HIDDEN;
                } else {
                    const float T = slide_elapsed_s_ / Cfg::SLIDE_DURATION_S;
                    const float K = Utils::Easing::inCubic(T);
                    y_current_ = y_on_ + ((y_off_ - y_on_) * K);
                }
                break;
            }
            case Status::HIDDEN:
            default:
                break;
        }
    }

    void Notifier::draw() const {
        if (status_ == Status::HIDDEN) {
            return;
        }

        const float BOX_X = (Cfg::CANVAS_WIDTH - box_w_) * 0.5F;
        const float BOX_Y = y_current_;
        const UnitRGBA TC = textColorFloat(current_color_);
        const UnitRGBA BG = bgColorFloat(current_color_);

        auto* gpu = renderer_;

        // 1. Fons semitransparent.
        gpu->pushRect(BOX_X, BOX_Y, box_w_, box_h_, BG.r, BG.g, BG.b, BG.a);

        // 2. Bordes (4 línies amb el color del text).
        const float X1 = BOX_X;
        const float Y1 = BOX_Y;
        const float X2 = BOX_X + box_w_;
        const float Y2 = BOX_Y + box_h_;
        gpu->pushLine(X1, Y1, X2, Y1, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a);  // top
        gpu->pushLine(X1, Y2, X2, Y2, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a);  // bottom
        gpu->pushLine(X1, Y1, X1, Y2, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a);  // left
        gpu->pushLine(X2, Y1, X2, Y2, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a);  // right

        // 3. Text centrat dins la caixa, amb color explícit (l'alpha != 0
        // li diu al renderShape que no agafe l'oscil·lador global de color).
        const float TEXT_Y = BOX_Y + Cfg::PADDING_V;
        text_.render(current_text_,
            Vec2{.x = text_x_, .y = TEXT_Y},
            text_scale_,
            Cfg::TEXT_SPACING,
            Cfg::BORDER_BRIGHTNESS,
            current_color_);
    }

    auto Notifier::isActiveWindow() const -> bool {
        return status_ == Status::ENTERING || status_ == Status::HOLDING;
    }

    auto Notifier::isExitPromptActive() const -> bool {
        return isActiveWindow() && current_is_exit_;
    }

}  // namespace System
