// ship_animator.cpp - Implementació de l'animador de naus 3D
// © 2026 JailDesigner

#include "ship_animator.hpp"

#include <algorithm>
#include <cmath>
#include <cstddef>

#include "core/defaults.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/math/easing.hpp"

namespace Title {

    namespace {

        // Profunditat d'extrusió de la silueta 2D de la nau (en unitats mundials).
        // 0.0F → emet només la silueta plana. >0 emet volum extrudit.
        constexpr float SHIP_EXTRUSION_DEPTH = 1.0F;

        // VP lògic per definir forward_dir / direcció del path. Tots els paths
        // s'allunyen cap a aquest punt; les naus exiting continuen MÉS ENLLÀ
        // (vegeu SHIP_EXIT_TRAVEL) per no desaparèixer en arribar al VP.
        constexpr float SHIP_EXIT_Z = 800.0F;
        constexpr Vec3 VANISHING_POINT{.x = 0.0F, .y = 0.0F, .z = SHIP_EXIT_Z};

        // Profunditat addicional darrere del VP cap a la qual les naus exiting
        // convergeixen. Així P1 (X<0) i P2 (X>0) mantenen sempre els seus
        // hemisferis i no es creuen al passar pel VP — totes dues acaben al
        // centre projectat (640, 360) sense travessar-lo.
        constexpr float SHIP_EXIT_OVERFLOW = 700.0F;

        // Directors VP → origen de cada nau, normalitzats. P1 ve des de "les 7"
        // del rellotge (baix-esquerra), P2 des de "les 5" (baix-dreta). Els
        // components estan calibrats perquè a TARGET_DIST el pixel projectat
        // caigui aprox sota la "P de PRESS" / "Y de PLAY" del text del títol.
        constexpr Vec3 PATH_DIR_P1{.x = -0.0887F, .y = -0.0683F, .z = -0.9938F};
        constexpr Vec3 PATH_DIR_P2{.x = +0.0887F, .y = -0.0683F, .z = -0.9938F};

        // Distàncies des del VP al llarg del path (unitats mundials).
        // Reduïm TARGET_DIST per acostar el descans al VP (puja en pantalla,
        // s'allunya de PRESS START); compensem amb SHIP_FLOAT_SCALE més gran.
        constexpr float TARGET_DIST = 480.0F;  // Descans a Z≈323 → pixel ≈ (558, 423)
        constexpr float ENTRY_DIST = 770.0F;   // Inicial a Z≈35 → fora pantalla baix-esq.

        // Pitch addicional sobre el look-at pur per fer que el dors de la nau
        // s'incline cap a la càmera (~-14° afegits). Amb forward quasi paral·lel
        // a +Z, el pitch look-at és ~-94°; afegint això queda al voltant de -108°,
        // que és l'angle visualment validat com a "bo" per l'usuari.
        constexpr float PITCH_LIFT_RAD = -0.25F;

        // Look-at: calcula pitch+yaw que duen (0,-1,0) local a forward_dir mundial.
        // Requereix l'ordre de rotació X→Y→Z al applyTransform de wireframe3d.
        // Si forward és quasi vertical (sin(pitch) ≈ 0), retorna yaw=0 (qualsevol).
        auto computePitchYawForLookAt(const Vec3& forward_dir) -> Vec2 {
            const float DY = std::clamp(forward_dir.y, -1.0F, 1.0F);
            const float PITCH_LOOKAT = -std::acos(-DY);  // ∈ [-π, 0]
            const float SIN_PITCH = std::sin(PITCH_LOOKAT);
            float yaw = 0.0F;
            if (std::abs(SIN_PITCH) >= 1.0E-5F) {
                const float SY = -forward_dir.x / SIN_PITCH;
                const float CY = -forward_dir.z / SIN_PITCH;
                yaw = std::atan2(SY, CY);
            }
            return Vec2{.x = PITCH_LOOKAT + PITCH_LIFT_RAD, .y = yaw};
        }

        auto safeNormalize(const Vec3& v, const Vec3& fallback) -> Vec3 {
            return v.lengthSquared() > 0.0F ? v.normalized() : fallback;
        }

        auto entryForward(const TitleShip& ship) -> Vec3 {
            return safeNormalize(ship.target_position - ship.initial_position,
                Vec3{.x = 0.0F, .y = 0.0F, .z = 1.0F});
        }

        auto floatingForward(const Vec3& target) -> Vec3 {
            return safeNormalize(VANISHING_POINT - target,
                Vec3{.x = 0.0F, .y = 0.0F, .z = 1.0F});
        }

        auto exitForward(const Vec3& current) -> Vec3 {
            return safeNormalize(VANISHING_POINT - current,
                Vec3{.x = 0.0F, .y = 0.0F, .z = 1.0F});
        }

        // Mida visual i animació.
        constexpr float SHIP_FLOAT_SCALE = 2.0F;
        constexpr float SHIP_ENTRY_SCALE = 2.0F;  // Mida mundial idèntica; la perspectiva fa la resta
        // ENTRY_DURATION viu a Defaults::Title::Ships::ENTRY_DURATION (compartit
        // amb title_scene.cpp per calcular el threshold T_SHIPS_LANDED).
        constexpr float ENTRY_DURATION = Defaults::Title::Ships::ENTRY_DURATION;
        constexpr float EXIT_DURATION = Defaults::Title::Ships::EXIT_DURATION;

        // Oscil·lació en unitats mundials (al voltant del target_position).
        constexpr float FLOAT_AMPLITUDE_X = 1.5F;
        constexpr float FLOAT_AMPLITUDE_Y = 1.0F;
        constexpr float FLOAT_FREQUENCY_X_BASE = 0.5F;
        constexpr float FLOAT_FREQUENCY_Y_BASE = 0.7F;
        constexpr float FLOAT_PHASE_OFFSET = 1.57F;

        constexpr float P1_FREQUENCY_MULTIPLIER = 0.88F;
        constexpr float P2_FREQUENCY_MULTIPLIER = 1.12F;
        constexpr float P1_ENTRY_DELAY = 0.0F;
        constexpr float P2_ENTRY_DELAY = 0.5F;

    }  // namespace

    ShipAnimator::ShipAnimator(Rendering::Renderer* renderer,
        const Graphics::Camera3D* camera)
        : renderer_(renderer),
          camera_(camera) {
    }

    void ShipAnimator::init() {
        auto shape_p1 = Graphics::ShapeLoader::load("ship/arrow.shp");
        auto shape_p2 = Graphics::ShapeLoader::load("ship/wedge.shp");

        ships_[0].player_id = 1;
        if (shape_p1 && shape_p1->isValid()) {
            ships_[0].mesh = Graphics::extrudeShape2D(*shape_p1, SHIP_EXTRUSION_DEPTH);
        }
        configureShipP1(ships_[0]);

        ships_[1].player_id = 2;
        if (shape_p2 && shape_p2->isValid()) {
            ships_[1].mesh = Graphics::extrudeShape2D(*shape_p2, SHIP_EXTRUSION_DEPTH);
        }
        configureShipP2(ships_[1]);
    }

    void ShipAnimator::update(float delta_time) {
        for (auto& ship : ships_) {
            if (!ship.visible) {
                continue;
            }
            switch (ship.state) {
                case ShipState::ENTERING:
                    updateEntering(ship, delta_time);
                    break;
                case ShipState::FLOATING:
                    updateFloating(ship, delta_time);
                    break;
                case ShipState::EXITING: {
                    updateExiting(ship, delta_time);
                    // Transició a invisible: la nau acaba d'arribar al VP.
                    if (!ship.visible && on_ship_disappear_) {
                        on_ship_disappear_(ship.player_id);
                    }
                    break;
                }
            }
        }
    }

    void ShipAnimator::draw() const {
        if (camera_ == nullptr || renderer_ == nullptr) {
            return;
        }
        for (std::size_t i = 0; i < ships_.size(); ++i) {
            const auto& ship = ships_[i];
            if (!ship.visible) {
                continue;
            }
            const Vec2 EULER = computePitchYawForLookAt(ship.forward_dir);
            const Graphics::Transform3D TRANSFORM{
                .position = ship.current_position,
                .rotation_euler = Vec3{.x = EULER.x, .y = EULER.y, .z = 0.0F},
                .scale = ship.current_scale,
            };
            const SDL_Color SHIP_COLOR = (i == 0)
                ? Defaults::Title::Colors::SHIP_P1
                : Defaults::Title::Colors::SHIP_P2;
            Graphics::drawWireframe(renderer_, *camera_, ship.mesh, TRANSFORM, 1.0F, SHIP_COLOR);
        }
    }

    void ShipAnimator::startEntryAnimation() {
        for (auto& ship : ships_) {
            ship.state = ShipState::ENTERING;
            ship.state_time = 0.0F;
            ship.current_position = ship.initial_position;
            ship.current_scale = ship.initial_scale;
            ship.forward_dir = entryForward(ship);
        }
    }

    void ShipAnimator::triggerExitAnimation() {
        for (auto& ship : ships_) {
            ship.state = ShipState::EXITING;
            ship.state_time = 0.0F;
            ship.initial_position = ship.current_position;
            ship.forward_dir = exitForward(ship.current_position);
        }
    }

    void ShipAnimator::triggerExitAnimationForPlayer(int player_id) {
        for (auto& ship : ships_) {
            if (ship.player_id == player_id) {
                ship.state = ShipState::EXITING;
                ship.state_time = 0.0F;
                ship.initial_position = ship.current_position;
                ship.forward_dir = exitForward(ship.current_position);
                break;
            }
        }
    }

    void ShipAnimator::skipToFloatingState() {
        for (auto& ship : ships_) {
            ship.state = ShipState::FLOATING;
            ship.state_time = 0.0F;
            ship.oscillation_phase = 0.0F;
            ship.current_position = ship.target_position;
            ship.current_scale = ship.target_scale;
            ship.forward_dir = floatingForward(ship.target_position);
        }
    }

    void ShipAnimator::setVisible(bool visible) {
        for (auto& ship : ships_) {
            ship.visible = visible;
        }
    }

    auto ShipAnimator::isVisible() const -> bool {
        for (const auto& s : ships_) {
            if (s.visible) {
                return true;
            }
        }
        return false;
    }

    auto ShipAnimator::isAnimationComplete() const -> bool {
        for (const auto& s : ships_) {
            if (s.visible) {
                return false;
            }
        }
        return true;
    }

    void ShipAnimator::updateEntering(TitleShip& ship, float delta_time) {
        ship.state_time += delta_time;
        if (ship.state_time < ship.entry_delay) {
            ship.current_position = ship.initial_position;
            ship.current_scale = ship.initial_scale;
            return;
        }
        const float ELAPSED = ship.state_time - ship.entry_delay;
        const float PROGRESS = std::min(1.0F, ELAPSED / ENTRY_DURATION);
        const float EASED = Easing::easeOutQuad(PROGRESS);

        // Acumula la fase d'oscil·lació també durant ENTERING; sense això,
        // al passar a FLOATING la posició salta d'amplitud_y de cop perquè
        // l'offset Y comença a sin(π/2) = 1. Acumulant-la abans, la nau
        // ja oscil·la mentre s'aproxima i la transició és contínua.
        ship.oscillation_phase += delta_time;

        const float INTERP_X = Easing::lerp(ship.initial_position.x, ship.target_position.x, EASED);
        const float INTERP_Y = Easing::lerp(ship.initial_position.y, ship.target_position.y, EASED);
        const float INTERP_Z = Easing::lerp(ship.initial_position.z, ship.target_position.z, EASED);

        const float TWO_PI = 2.0F * Defaults::Math::PI;
        const float OFFSET_X = ship.amplitude_x *
            std::sin(TWO_PI * ship.frequency_x * ship.oscillation_phase);
        const float OFFSET_Y = ship.amplitude_y *
            std::sin((TWO_PI * ship.frequency_y * ship.oscillation_phase) + FLOAT_PHASE_OFFSET);

        ship.current_position.x = INTERP_X + OFFSET_X;
        ship.current_position.y = INTERP_Y + OFFSET_Y;
        ship.current_position.z = INTERP_Z;
        ship.current_scale = Easing::lerp(ship.initial_scale, ship.target_scale, EASED);

        if (ELAPSED >= ENTRY_DURATION) {
            ship.state = ShipState::FLOATING;
            ship.state_time = 0.0F;
            // No resetegem oscillation_phase: així updateFloating continua
            // l'oscil·lació iniciada durant ENTERING sense salt.
            ship.forward_dir = floatingForward(ship.target_position);
        }
    }

    void ShipAnimator::updateFloating(TitleShip& ship, float delta_time) {
        ship.state_time += delta_time;
        ship.oscillation_phase += delta_time;

        const float TWO_PI = 2.0F * Defaults::Math::PI;
        const float OFFSET_X = ship.amplitude_x *
            std::sin(TWO_PI * ship.frequency_x * ship.oscillation_phase);
        const float OFFSET_Y = ship.amplitude_y *
            std::sin((TWO_PI * ship.frequency_y * ship.oscillation_phase) + FLOAT_PHASE_OFFSET);

        ship.current_position.x = ship.target_position.x + OFFSET_X;
        ship.current_position.y = ship.target_position.y + OFFSET_Y;
        ship.current_position.z = ship.target_position.z;
        ship.current_scale = ship.target_scale;
    }

    void ShipAnimator::updateExiting(TitleShip& ship, float delta_time) {
        ship.state_time += delta_time;
        const float PROGRESS = std::min(1.0F, ship.state_time / EXIT_DURATION);
        const float EASED = Easing::easeInQuad(PROGRESS);

        // Destí: punt fix a (VP.x, VP.y, VP.z + OVERFLOW). Cada nau s'apropa
        // al centre projectat des del seu costat sense creuar el VP.
        const Vec3 EXIT_DEST{
            .x = VANISHING_POINT.x,
            .y = VANISHING_POINT.y,
            .z = VANISHING_POINT.z + SHIP_EXIT_OVERFLOW,
        };
        ship.current_position.x = Easing::lerp(ship.initial_position.x, EXIT_DEST.x, EASED);
        ship.current_position.y = Easing::lerp(ship.initial_position.y, EXIT_DEST.y, EASED);
        ship.current_position.z = Easing::lerp(ship.initial_position.z, EXIT_DEST.z, EASED);
        ship.current_scale = ship.target_scale;  // L'escala visual baixa via la perspectiva

        if (PROGRESS >= 1.0F) {
            ship.visible = false;
        }
    }

    void ShipAnimator::configureShipP1(TitleShip& ship) {
        ship.state = ShipState::FLOATING;
        ship.state_time = 0.0F;
        // Target i initial sobre el path VP → "les 7" del rellotge (P1).
        ship.target_position = VANISHING_POINT + (PATH_DIR_P1 * TARGET_DIST);
        ship.initial_position = VANISHING_POINT + (PATH_DIR_P1 * ENTRY_DIST);
        ship.current_position = ship.initial_position;
        ship.target_scale = SHIP_FLOAT_SCALE;
        ship.current_scale = SHIP_FLOAT_SCALE;
        ship.initial_scale = SHIP_ENTRY_SCALE;
        ship.oscillation_phase = 0.0F;
        ship.entry_delay = P1_ENTRY_DELAY;
        ship.amplitude_x = FLOAT_AMPLITUDE_X;
        ship.amplitude_y = FLOAT_AMPLITUDE_Y;
        ship.frequency_x = FLOAT_FREQUENCY_X_BASE * P1_FREQUENCY_MULTIPLIER;
        ship.frequency_y = FLOAT_FREQUENCY_Y_BASE * P1_FREQUENCY_MULTIPLIER;
        ship.forward_dir = entryForward(ship);
        ship.visible = true;
    }

    void ShipAnimator::configureShipP2(TitleShip& ship) {
        ship.state = ShipState::FLOATING;
        ship.state_time = 0.0F;
        // Target i initial sobre el path VP → "les 5" del rellotge (P2).
        ship.target_position = VANISHING_POINT + (PATH_DIR_P2 * TARGET_DIST);
        ship.initial_position = VANISHING_POINT + (PATH_DIR_P2 * ENTRY_DIST);
        ship.current_position = ship.initial_position;
        ship.target_scale = SHIP_FLOAT_SCALE;
        ship.current_scale = SHIP_FLOAT_SCALE;
        ship.initial_scale = SHIP_ENTRY_SCALE;
        ship.oscillation_phase = 0.0F;
        ship.entry_delay = P2_ENTRY_DELAY;
        ship.amplitude_x = FLOAT_AMPLITUDE_X;
        ship.amplitude_y = FLOAT_AMPLITUDE_Y;
        ship.frequency_x = FLOAT_FREQUENCY_X_BASE * P2_FREQUENCY_MULTIPLIER;
        ship.frequency_y = FLOAT_FREQUENCY_Y_BASE * P2_FREQUENCY_MULTIPLIER;
        ship.forward_dir = entryForward(ship);
        ship.visible = true;
    }

}  // namespace Title
