#include "game/scenes/intro_scene.hpp"

#include "core/jail/jdraw8.hpp"
#include "core/jail/jinput.hpp"
#include "game/info.hpp"
#include "game/scenes/scene_utils.hpp"

namespace {

    // Timings idèntics als del vell `doIntro()`: el Jg::setUpdateTicks(1000)
    // inicial, (100) per a les 3 primeres lletres (J, A, I), (200) per a
    // "JAIL" i el seu clear, (100) per a les 4 lletres centrals
    // (G, A, M, E) i (200) per a la resta fins al cicle de paleta.
    constexpr int INITIAL_MS = 1000;
    constexpr int PALETTE_CYCLE_STEP_MS = 20;
    constexpr int PALETTE_CYCLE_STEPS = 256;
    constexpr int FINAL_WAIT_MS = 200;

    // Un pas del revelat. Dos blits configurables (cos del wordmark + avió)
    // més una variant per al wordmark sencer i un flag de ClearScreen previ.
    struct RevealStep {
        int duration_ms;
        int body_w;     // amplada del blit body (43,78) ← (43,155, body_w, 45); 0 si s'usa wordmark
        int plane_x;    // x del blit de l'avió (274,155, 27×45); -1 = no avió
        bool clear;     // fa ClearScreen(0) abans dels blits
        bool wordmark;  // usa drawWordmark() en lloc del blit body (wordmark complet)
    };

    constexpr RevealStep REVEAL_STEPS[] = {
        {.duration_ms = 100, .body_w = 27, .plane_x = 68, .clear = false, .wordmark = false},    // J
        {.duration_ms = 100, .body_w = 53, .plane_x = 96, .clear = false, .wordmark = false},    // JA
        {.duration_ms = 100, .body_w = 66, .plane_x = 109, .clear = false, .wordmark = false},   // JAI
        {.duration_ms = 200, .body_w = 92, .plane_x = 136, .clear = false, .wordmark = false},   // JAIL
        {.duration_ms = 200, .body_w = 92, .plane_x = -1, .clear = true, .wordmark = false},     // JAIL (clear, sense avió — parpelleig)
        {.duration_ms = 100, .body_w = 118, .plane_x = 160, .clear = false, .wordmark = false},  // JAILG
        {.duration_ms = 100, .body_w = 145, .plane_x = 188, .clear = false, .wordmark = false},  // JAILGA
        {.duration_ms = 100, .body_w = 178, .plane_x = 221, .clear = false, .wordmark = false},  // JAILGAM
        {.duration_ms = 100, .body_w = 205, .plane_x = 248, .clear = false, .wordmark = false},  // JAILGAME
        {.duration_ms = 200, .body_w = 0, .plane_x = 274, .clear = false, .wordmark = true},     // JAILGAMES (wordmark complet) + avió
        {.duration_ms = 200, .body_w = 0, .plane_x = -1, .clear = true, .wordmark = true},       // JAILGAMES (clear, sense avió)
        {.duration_ms = 200, .body_w = 0, .plane_x = 274, .clear = false, .wordmark = true},     // JAILGAMES + avió (sense clear)
        {.duration_ms = 200, .body_w = 0, .plane_x = -1, .clear = true, .wordmark = true},       // JAILGAMES (clear, sense avió)
        {.duration_ms = 200, .body_w = 0, .plane_x = 274, .clear = false, .wordmark = true},     // JAILGAMES + avió (sense clear)
        {.duration_ms = 200, .body_w = 0, .plane_x = -1, .clear = true, .wordmark = true},       // JAILGAMES (clear, sense avió)
    };
    constexpr int REVEAL_COUNT = sizeof(REVEAL_STEPS) / sizeof(REVEAL_STEPS[0]);

    // Branca `!use_new_logo` del drawIntroWordmark de modulesequence.cpp:
    // blit únic del wordmark "JAILGAMES" complet (231×45 al destí 43,78).
    // IntroScene només s'activa quan use_new_logo == false, així que la
    // branca use_new_logo d'aquell helper aquí no es necessita.
    void drawWordmark(const Uint8* gfx) {
        Jd8::blit(43, 78, gfx, 43, 155, 231, 45);
    }

}  // namespace

namespace Scenes {

    IntroScene::IntroScene() = default;

    // No alliberem `pal_`: Jd8::setScreenPalette n'ha pres ownership i el
    // proper SetScreenPalette / FadeToPal la lliurarà. Alliberar-la ací
    // provocaria double free.
    IntroScene::~IntroScene() = default;

    void IntroScene::onEnter() {
        playMusic("music/menu.ogg");

        gfx_ = SurfaceHandle("gfx/logo.gif");
        pal_ = Jd8::loadPalette("gfx/logo.gif");
        Jd8::setScreenPalette(pal_);

        Jd8::clearScreen(0);

        phase_ = Phase::INITIAL_WAIT;
        phase_acc_ms_ = 0;
        reveal_index_ = 0;
        palette_step_ = 0;
    }

    void IntroScene::render() {
        switch (phase_) {
            case Phase::INITIAL_WAIT:
                Jd8::clearScreen(0);
                break;

            case Phase::REVEAL: {
                const RevealStep& s = REVEAL_STEPS[reveal_index_];
                if (s.clear) {
                    Jd8::clearScreen(0);
                }
                if (s.wordmark) {
                    drawWordmark(gfx_);
                } else if (s.body_w > 0) {
                    Jd8::blit(43, 78, gfx_, 43, 155, s.body_w, 45);
                }
                if (s.plane_x >= 0) {
                    Jd8::blit(s.plane_x, 78, gfx_, 274, 155, 27, 45);
                }
                break;
            }

            case Phase::PALETTE_CYCLE:
            case Phase::FINAL_WAIT:
                // Wordmark complet fix mentre cicla la paleta — l'últim
                // pas del revelat (PAS 15) deixa la pantalla en aquest mateix
                // estat, i el vell doIntro no redibuixava durant el cicle.
                Jd8::clearScreen(0);
                drawWordmark(gfx_);
                break;

            case Phase::SPRITES:
            case Phase::DONE:
                break;
        }
    }

    void IntroScene::advancePaletteCycle() {
        // Replica exacta del cicle del doIntro vell sobre pal[16..31] — el
        // grup del verd brillant del logo. Index 17 s'acosta a blanc mentre
        // els altres convergeixen cap al mateix gris mitjà.
        for (int i = 16; i < 32; i++) {
            if (i == 17) {
                if (pal_[i].r < 255) {
                    pal_[i].r++;
                }
                if (pal_[i].g < 255) {
                    pal_[i].g++;
                }
                if (pal_[i].b < 255) {
                    pal_[i].b++;
                }
            }
            if (pal_[i].b < pal_[i].g) {
                pal_[i].b++;
            }
            if (pal_[i].b > pal_[i].g) {
                pal_[i].b--;
            }
            if (pal_[i].r < pal_[i].g) {
                pal_[i].r++;
            }
            if (pal_[i].r > pal_[i].g) {
                pal_[i].r--;
            }
        }
    }

    void IntroScene::tick(int delta_ms) {
        // Qualsevol tecla durant revelat/paleta salta TOTA la intro
        // (inclou saltar la fase de sprites). Durant SPRITES deixem que
        // la sub-escena gestione el seu propi skip internament, que a més
        // respecta la fase "final" no skippable de la variant 0.
        if (phase_ != Phase::SPRITES && phase_ != Phase::DONE && Ji::anyKey()) {
            Info::ctx.num_piramide = 0;
            phase_ = Phase::DONE;
            return;
        }

        switch (phase_) {
            case Phase::INITIAL_WAIT:
                phase_acc_ms_ += delta_ms;
                render();
                if (phase_acc_ms_ >= INITIAL_MS) {
                    phase_ = Phase::REVEAL;
                    phase_acc_ms_ = 0;
                    reveal_index_ = 0;
                }
                break;

            case Phase::REVEAL:
                phase_acc_ms_ += delta_ms;
                render();
                if (phase_acc_ms_ >= REVEAL_STEPS[reveal_index_].duration_ms) {
                    phase_acc_ms_ = 0;
                    ++reveal_index_;
                    if (reveal_index_ >= REVEAL_COUNT) {
                        phase_ = Phase::PALETTE_CYCLE;
                    }
                }
                break;

            case Phase::PALETTE_CYCLE:
                phase_acc_ms_ += delta_ms;
                // Avancem tants passos com permet el delta, per evitar
                // saltar-ne si el frame ha vingut lent.
                while (phase_acc_ms_ >= PALETTE_CYCLE_STEP_MS &&
                    palette_step_ < PALETTE_CYCLE_STEPS) {
                    phase_acc_ms_ -= PALETTE_CYCLE_STEP_MS;
                    advancePaletteCycle();
                    ++palette_step_;
                }
                render();
                if (palette_step_ >= PALETTE_CYCLE_STEPS) {
                    phase_ = Phase::FINAL_WAIT;
                    phase_acc_ms_ = 0;
                }
                break;

            case Phase::FINAL_WAIT:
                phase_acc_ms_ += delta_ms;
                render();
                if (phase_acc_ms_ >= FINAL_WAIT_MS) {
                    phase_ = Phase::SPRITES;
                }
                break;

            case Phase::SPRITES:
                // Sub-escena construïda al vol al primer tick d'aquesta fase.
                // Transferim el gfx_ per move — la sub-escena se n'ocupa
                // fins que es destruix. Una vegada feta, els ticks delegats
                // avancen l'animació dels sprites.
                if (!sprites_scene_) {
                    sprites_scene_ = std::make_unique<IntroSpritesScene>(std::move(gfx_));
                    sprites_scene_->onEnter();
                }
                sprites_scene_->tick(delta_ms);
                if (sprites_scene_->done()) {
                    // Equivalent al vell `Go()` post-switch: passem al menú.
                    // Sense açò el while del fiber tornaria a crear IntroScene
                    // infinitament amb num_piramide encara a 255.
                    Info::ctx.num_piramide = 0;
                    phase_ = Phase::DONE;
                }
                break;

            case Phase::DONE:
                break;
        }
    }

}  // namespace Scenes
