#include "Runtime/MP1/CSamusDoll.hpp"

#include "Runtime/CDependencyGroup.hpp"
#include "Runtime/CSimplePool.hpp"
#include "Runtime/Collision/CollisionUtil.hpp"
#include "Runtime/GameGlobalObjects.hpp"
#include "Runtime/Graphics/CCubeRenderer.hpp"
#include "Runtime/World/CMorphBall.hpp"

#include <array>
#include <cfloat>
#include <cmath>
#include <memory>

#include <zeus/CColor.hpp>
#include <zeus/CEulerAngles.hpp>

namespace metaforce::MP1 {
namespace {
constexpr std::array<std::pair<const char*, u32>, 8> SpiderBallGlassModels{{
    {"SamusSpiderBallGlassCMDL", 0},
    {"SamusSpiderBallGlassCMDL", 0},
    {"SamusSpiderBallGlassCMDL", 1},
    {"SamusPhazonBallGlassCMDL", 0},
    {"SamusSpiderBallGlassCMDL", 0},
    {"SamusSpiderBallGlassCMDL", 0},
    {"SamusSpiderBallGlassCMDL", 1},
    {"SamusPhazonBallGlassCMDL", 0},
}};

constexpr std::array<std::pair<const char*, u32>, 8> SpiderBallCharacters{{
    {"SamusSpiderBallANCS", 0},
    {"SamusSpiderBallANCS", 0},
    {"SamusSpiderBallANCS", 1},
    {"SamusPhazonBallANCS", 0},
    {"SamusFusionBallANCS", 0},
    {"SamusFusionBallANCS", 2},
    {"SamusFusionBallANCS", 1},
    {"SamusFusionBallANCS", 3},
}};

constexpr std::array<std::pair<const char*, u32>, 8> BallCharacters{{
    {"SamusBallANCS", 0},
    {"SamusBallANCS", 0},
    {"SamusBallANCS", 1},
    {"SamusBallANCS", 0},
    {"SamusFusionBallANCS", 0},
    {"SamusFusionBallANCS", 2},
    {"SamusFusionBallANCS", 1},
    {"SamusFusionBallANCS", 3},
}};

constexpr std::array<u32, 8> SpiderBallGlowColorIdxs{
    3, 3, 2, 4, 5, 7, 6, 8,
};

constexpr std::array<u32, 8> BallGlowColorIdxs{
    0, 0, 1, 0, 5, 7, 6, 8,
};

constexpr std::array BeamModels{
    "CMDL_InvPowerBeam", "CMDL_InvIceBeam", "CMDL_InvWaveBeam", "CMDL_InvPlasmaBeam", "CMDL_InvPowerBeam",
};

constexpr std::array VisorModels{
    "CMDL_InvVisor",       "CMDL_InvGravityVisor", "CMDL_InvVisor",       "CMDL_InvPhazonVisor",
    "CMDL_InvFusionVisor", "CMDL_InvFusionVisor",  "CMDL_InvFusionVisor", "CMDL_InvFusionVisor",
};

constexpr std::array FinModels{
    "CMDL_InvPowerFins", "CMDL_InvPowerFins", "CMDL_InvPowerFins",   "CMDL_InvPowerFins",
    "CMDL_InvPowerFins", "CMDL_InvVariaFins", "CMDL_InvGravityFins", "CMDL_InvPhazonFins",
};

constexpr std::array<u32, 8> Character1Idxs{
    0, 6, 2, 10, 16, 24, 20, 28,
};

constexpr std::array<u32, 8> CharacterBootsIdxs{
    1, 7, 3, 11, 17, 25, 21, 29,
};

constexpr std::array<std::array<u32, 2>, 8> Character2and3Idxs{{
    {14, 15},
    {8, 9},
    {4, 5},
    {12, 13},
    {18, 19},
    {26, 27},
    {22, 23},
    {30, 31},
}};
} // Anonymous namespace

CSamusDoll::CSamusDoll(const CDependencyGroup& suitDgrp, const CDependencyGroup& ballDgrp,
                       CPlayerState::EPlayerSuit suit, CPlayerState::EBeamId beam, bool hasSpiderBall,
                       bool hasGrappleBeam)
: x10_ballXf(zeus::CTransform::Translate(0.f, 0.f, 0.625f * g_tweakPlayer->GetPlayerBallHalfExtent()))
, x44_suit(suit)
, x48_beam(beam)
, x270_24_hasSpiderBall(hasSpiderBall)
, x270_25_hasGrappleBeam(hasGrappleBeam) {
  x70_fixedRot.rotateZ(M_PIF);
  x90_userInterpRot = xb0_userRot = x70_fixedRot;
  x1d4_spiderBallGlass = g_SimplePool->GetObj(SpiderBallGlassModels[size_t(suit)].first);
  x1e0_ballMatIdx = hasSpiderBall ? SpiderBallCharacters[size_t(suit)].second : BallCharacters[size_t(suit)].second;
  x1e4_glassMatIdx = SpiderBallGlassModels[size_t(suit)].second;
  x1e8_ballGlowColorIdx = hasSpiderBall ? SpiderBallGlowColorIdxs[size_t(suit)] : BallGlowColorIdxs[size_t(suit)];
  x1ec_itemScreenSamus = g_SimplePool->GetObj("ANCS_ItemScreenSamus");
  x1f4_invBeam = g_SimplePool->GetObj(BeamModels[size_t(beam)]);
  x200_invVisor = g_SimplePool->GetObj(VisorModels[size_t(suit)]);
  x20c_invGrappleBeam = g_SimplePool->GetObj("CMDL_InvGrappleBeam");
  x218_invFins = g_SimplePool->GetObj(FinModels[size_t(suit)]);
  x224_ballInnerGlow = g_SimplePool->GetObj("BallInnerGlow");
  x22c_ballInnerGlowGen = std::make_unique<CElementGen>(x224_ballInnerGlow);
  x230_ballTransitionFlash = g_SimplePool->GetObj("MorphBallTransitionFlash");
  x23c_lights.push_back(CLight::BuildDirectional(zeus::skForward, zeus::skWhite));
  x24c_actorLights = std::make_unique<CActorLights>(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f);
  x22c_ballInnerGlowGen->SetGlobalScale(zeus::CVector3f(0.625f));
  x0_depToks.reserve(suitDgrp.GetObjectTagVector().size() + ballDgrp.GetObjectTagVector().size());
  for (const SObjectTag& tag : suitDgrp.GetObjectTagVector()) {
    x0_depToks.push_back(g_SimplePool->GetObj(tag));
    x0_depToks.back().Lock();
  }
  for (const SObjectTag& tag : ballDgrp.GetObjectTagVector()) {
    x0_depToks.push_back(g_SimplePool->GetObj(tag));
    x0_depToks.back().Lock();
  }
}

bool CSamusDoll::IsLoaded() const {
  if (x270_31_loaded)
    return true;
  if (!x1ec_itemScreenSamus.IsLoaded())
    return false;
  if (!x1f4_invBeam.IsLoaded())
    return false;
  if (!x200_invVisor.IsLoaded())
    return false;
  if (!x20c_invGrappleBeam.IsLoaded())
    return false;
  if (!x1d4_spiderBallGlass.IsLoaded())
    return false;
  if (x218_invFins && !x218_invFins.IsLoaded())
    return false;
  return xc8_suitModel0.operator bool();
}

CModelData CSamusDoll::BuildSuitModelData1(CPlayerState::EPlayerSuit suit) {
  CModelData ret(CAnimRes(g_ResFactory->GetResourceIdByName("ANCS_ItemScreenSamus")->id, Character1Idxs[size_t(suit)],
                          zeus::skOne3f, 2, true));
  constexpr CAnimPlaybackParms parms(2, -1, 1.f, true);
  ret.GetAnimationData()->SetAnimation(parms, false);
  return ret;
}

CModelData CSamusDoll::BuildSuitModelDataBoots(CPlayerState::EPlayerSuit suit) {
  CModelData ret(CAnimRes(g_ResFactory->GetResourceIdByName("ANCS_ItemScreenSamus")->id,
                          CharacterBootsIdxs[size_t(suit)], zeus::skOne3f, 2, true));
  constexpr CAnimPlaybackParms parms(2, -1, 1.f, true);
  ret.GetAnimationData()->SetAnimation(parms, false);
  return ret;
}

bool CSamusDoll::CheckLoadComplete() {
  if (IsLoaded())
    return true;

  for (const CToken& tok : x0_depToks)
    if (!tok.IsLoaded())
      return false;

  xc8_suitModel0.emplace(BuildSuitModelData1(x44_suit));
  for (int i = 0; i < 2; ++i) {
    CAnimRes res(g_ResFactory->GetResourceIdByName("ANCS_ItemScreenSamus")->id, Character2and3Idxs[size_t(x44_suit)][i],
                 zeus::skOne3f, 2, true);
    CModelData mData(res);
    x118_suitModel1and2.push_back(mData.GetAnimationData()->GetModelData());
    x118_suitModel1and2.back().Lock();
  }
  x134_suitModelBoots.emplace(BuildSuitModelDataBoots(x44_suit));

  CAnimRes res(g_ResFactory
                   ->GetResourceIdByName(x270_24_hasSpiderBall ? SpiderBallCharacters[size_t(x44_suit)].first
                                                               : BallCharacters[size_t(x44_suit)].first)
                   ->id,
               0, zeus::skOne3f, 0, true);
  x184_ballModelData.emplace(res);
  x1e0_ballMatIdx =
      x270_24_hasSpiderBall ? SpiderBallCharacters[size_t(x44_suit)].second : BallCharacters[size_t(x44_suit)].second;
  x270_31_loaded = true;
  return true;
}

void CSamusDoll::Update(float dt, CRandom16& rand) {
  if (x1f4_invBeam.IsLoaded())
    x1f4_invBeam->Touch(0);
  if (x200_invVisor.IsLoaded())
    x200_invVisor->Touch(0);
  if (x20c_invGrappleBeam.IsLoaded())
    x20c_invGrappleBeam->Touch(0);
  if (x1d4_spiderBallGlass.IsLoaded())
    x1d4_spiderBallGlass->Touch(0);
  if (x218_invFins.IsLoaded())
    x218_invFins->Touch(0);

  if (!CheckLoadComplete())
    return;

  x40_alphaIn = std::min(x40_alphaIn + 2.f * dt, 1.f);
  if (x54_remTransitionTime > 0.f) {
    float oldRemTransTime = x54_remTransitionTime;
    x54_remTransitionTime = std::max(0.f, x54_remTransitionTime - dt);
    if (!x4c_completedMorphball && x4d_selectedMorphball && oldRemTransTime >= x50_totalTransitionTime - 0.5f &&
        x54_remTransitionTime < x50_totalTransitionTime - 0.5f) {
      x238_ballTransitionFlashGen = std::make_unique<CElementGen>(x230_ballTransitionFlash);
      x238_ballTransitionFlashGen->SetGlobalScale(zeus::CVector3f(0.625f));
    }

    if (x54_remTransitionTime == 0.f) {
      x4c_completedMorphball = x4d_selectedMorphball;
      if (!x4d_selectedMorphball) {
        xc8_suitModel0->GetAnimationData()->SetAnimation(CAnimPlaybackParms(2, -1, 1.f, true), false);
        x134_suitModelBoots->GetAnimationData()->SetAnimation(CAnimPlaybackParms(2, -1, 1.f, true), false);
      }
    }
  }

  if (x270_26_pulseSuit)
    x58_suitPulseFactor = std::min(x58_suitPulseFactor + 2.f * dt, 1.f);
  else
    x58_suitPulseFactor = std::max(x58_suitPulseFactor - 2.f * dt, 0.f);

  if (x270_27_pulseBeam)
    x5c_beamPulseFactor = std::min(x5c_beamPulseFactor + 2.f * dt, 1.f);
  else
    x5c_beamPulseFactor = std::max(x5c_beamPulseFactor - 2.f * dt, 0.f);

  if (x270_28_pulseGrapple)
    x60_grapplePulseFactor = std::min(x60_grapplePulseFactor + 2.f * dt, 1.f);
  else
    x60_grapplePulseFactor = std::max(x60_grapplePulseFactor - 2.f * dt, 0.f);

  if (x270_29_pulseBoots)
    x64_bootsPulseFactor = std::min(x64_bootsPulseFactor + 2.f * dt, 1.f);
  else
    x64_bootsPulseFactor = std::max(x64_bootsPulseFactor - 2.f * dt, 0.f);

  if (x270_30_pulseVisor)
    x68_visorPulseFactor = std::min(x68_visorPulseFactor + 2.f * dt, 1.f);
  else
    x68_visorPulseFactor = std::max(x68_visorPulseFactor - 2.f * dt, 0.f);

  if (x4c_completedMorphball)
    x6c_ballPulseFactor = std::min(x6c_ballPulseFactor + 2.f * dt, 1.f);
  else
    x6c_ballPulseFactor = std::max(x6c_ballPulseFactor - 2.f * dt, 0.f);

  if (x44_suit == CPlayerState::EPlayerSuit::Phazon) {
    if (!x250_phazonIndirectTexture)
      x250_phazonIndirectTexture = g_SimplePool->GetObj("PhazonIndirectTexture");
  } else {
    if (x250_phazonIndirectTexture)
      x250_phazonIndirectTexture = TLockedToken<CTexture>();
  }

  if (x250_phazonIndirectTexture) {
    x260_phazonOffsetAngle += 0.03f;
    x260_phazonOffsetAngle.makeRel();
    g_Renderer->AllocatePhazonSuitMaskTexture();
  }

  xc8_suitModel0->AdvanceAnimationIgnoreParticles(dt, rand, true);
  x134_suitModelBoots->AdvanceAnimationIgnoreParticles(dt, rand, true);
  x184_ballModelData->AdvanceAnimationIgnoreParticles(dt, rand, true);

  SetupLights();

  x22c_ballInnerGlowGen->SetGlobalTranslation(x10_ballXf.origin);
  x22c_ballInnerGlowGen->Update(dt);

  if (x238_ballTransitionFlashGen) {
    if (x238_ballTransitionFlashGen->IsSystemDeletable())
      x238_ballTransitionFlashGen.reset();
    if (x238_ballTransitionFlashGen) {
      x22c_ballInnerGlowGen->SetGlobalTranslation(x10_ballXf.origin);
      x22c_ballInnerGlowGen->Update(dt);
    }
  }

  if (xc4_viewInterp != 0.f && xc4_viewInterp != 1.f) {
    if (xc4_viewInterp < 0.f)
      xc4_viewInterp = std::min(xc4_viewInterp + 3.f * dt, 0.f);
    else
      xc4_viewInterp = std::min(xc4_viewInterp + 3.f * dt, 1.f);

    float interp = std::fabs(xc4_viewInterp);
    float oneMinusInterp = 1.f - interp;
    xa4_offset = x84_interpStartOffset * interp + skInitialOffset * oneMinusInterp;

    xb0_userRot = zeus::CQuaternion::slerpShort(x70_fixedRot, x90_userInterpRot, interp);

    if (xc4_viewInterp <= 0.f) // Zoom out
      xc0_userZoom = x80_fixedZoom * oneMinusInterp + xa0_userInterpZoom * interp;
    else // Zoom in
      xc0_userZoom = x80_fixedZoom * interp + xa0_userInterpZoom * oneMinusInterp;
  }
}

void CSamusDoll::Draw(const CStateManager& mgr, float alpha) {
  if (!IsLoaded())
    return;
  SCOPED_GRAPHICS_DEBUG_GROUP("CSamusDoll::Draw", zeus::skPurple);

  alpha *= x40_alphaIn;

  float itemPulse = zeus::clamp(0.f, (std::sin(5.f * CGraphics::GetSecondsMod900()) + 1.f) * 0.5f, 1.f) *
                    (1.f - std::fabs(xc4_viewInterp));

  g_Renderer->SetPerspective(55.f, CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(), 0.2f, 4096.f);

  CGraphics::SetViewPointMatrix(zeus::CTransform(xb0_userRot, xa4_offset) *
                                zeus::CTransform::Translate(0.f, xc0_userZoom, 0.f));

  zeus::CTransform gunXf = xc8_suitModel0->GetScaledLocatorTransform("GUN_LCTR");
  zeus::CTransform visorXf = xc8_suitModel0->GetScaledLocatorTransform("VISOR_LCTR");
  zeus::CTransform grappleXf = xc8_suitModel0->GetScaledLocatorTransform("GRAPPLE_LCTR");

  if (!x4c_completedMorphball || !x4d_selectedMorphball) {
    float suitPulse = itemPulse * x58_suitPulseFactor;
    float bootsPulse = std::max(suitPulse, itemPulse * x64_bootsPulseFactor);

    bool phazonSuit = x44_suit == CPlayerState::EPlayerSuit::Phazon;
    if (phazonSuit) {
      GXSetDstAlpha(true, 1.f);
    }

    for (size_t i = 0; i <= x118_suitModel1and2.size(); ++i) {
      TCachedToken<CSkinnedModel> backupModelData = xc8_suitModel0->GetAnimationData()->GetModelData();
      if (i < x118_suitModel1and2.size())
        xc8_suitModel0->GetAnimationData()->SubstituteModelData(x118_suitModel1and2[i]);
      xc8_suitModel0->MultiLightingDraw(CModelData::EWhichModel::Normal, zeus::CTransform(), x24c_actorLights.get(),
                                        zeus::CColor(1.f, alpha), zeus::CColor(1.f, alpha * suitPulse));
      xc8_suitModel0->GetAnimationData()->SubstituteModelData(backupModelData);
    }

    x134_suitModelBoots->MultiLightingDraw(CModelData::EWhichModel::Normal, zeus::CTransform(), x24c_actorLights.get(),
                                           zeus::CColor(1.f, alpha), zeus::CColor(1.f, alpha * bootsPulse));

    {
      CGraphics::LoadLight(0, x23c_lights[0]);
      CGraphics::EnableLight(0);
      CGraphics::SetAmbientColor(zeus::skClear);
      CGraphics::SetModelMatrix(gunXf);
      x1f4_invBeam->Draw({5, 0, 3, zeus::CColor{1.f, alpha}});
      x1f4_invBeam->Draw({7, 0, 1, zeus::CColor(1.f, alpha * itemPulse * x5c_beamPulseFactor)});
    }

    {
      CGraphics::SetModelMatrix(visorXf);

      float visorT = std::fmod(CGraphics::GetSecondsMod900(), 1.f) * (1.f - std::fabs(xc4_viewInterp));
      float alphaBlend = (visorT < 0.25f) ? 1.f - 2.f * visorT : (visorT < 0.5f) ? 2.f * (visorT - 0.25f) + 0.5f : 1.f;
      float addBlend = (visorT > 0.75f) ? 1.f - 4.f * (visorT - 0.75f) : (visorT > 0.5f) ? 4.f * (visorT - 0.5f) : 0.f;

      const auto c1 =
          zeus::CColor::lerp(zeus::CColor{1.f, alpha}, zeus::CColor{alphaBlend, alpha}, x68_visorPulseFactor);
      x200_invVisor->Draw({5, 0, 3, c1});
      const zeus::CColor c2{1.f, alpha * addBlend * x68_visorPulseFactor};
      x200_invVisor->Draw({7, 0, 1, c2});
    }

    if (x270_25_hasGrappleBeam) {
      CGraphics::SetModelMatrix(grappleXf);
      x20c_invGrappleBeam->Draw({5, 0, 3, zeus::CColor(1.f, alpha)});
      x20c_invGrappleBeam->Draw({7, 0, 1, zeus::CColor(1.f, alpha * itemPulse * x60_grapplePulseFactor)});
    } else if (x44_suit >= CPlayerState::EPlayerSuit::FusionPower) {
      CGraphics::SetModelMatrix(grappleXf);
      x218_invFins->Draw({5, 0, 3, zeus::CColor(1.f, alpha)});
      x218_invFins->Draw({7, 0, 1, zeus::CColor(1.f, alpha * suitPulse)});
    }

    if (x54_remTransitionTime > 0.f) {
      float ballT = 1.f - x54_remTransitionTime / x50_totalTransitionTime;

      float ballAlpha = 0.f;
      if (x4d_selectedMorphball)
        ballAlpha = 1.f - std::min(x54_remTransitionTime / 0.25f, 1.f);
      else if (x4c_completedMorphball)
        ballAlpha = std::max(0.f, (x54_remTransitionTime - (x50_totalTransitionTime - 0.25f)) / 0.25f);

      const auto ballMatIdx = static_cast<u8>(x1e0_ballMatIdx);
      const float combinedBallAlpha = alpha * ballAlpha;
      if (ballAlpha > 0.f) {
        const std::array flags{
            CModelFlags{5, ballMatIdx, 3, zeus::CColor{1.f, 0.f}},
            CModelFlags{5, ballMatIdx, 3, zeus::CColor{1.f, combinedBallAlpha}},
        };
        x184_ballModelData->MultiPassDraw(CModelData::EWhichModel::Normal, x10_ballXf, x24c_actorLights.get(),
                                          flags.data(), flags.size());
        x184_ballModelData->Render(
            mgr, x10_ballXf, x24c_actorLights.get(),
            CModelFlags{7, ballMatIdx, 3, zeus::CColor{1.f, x6c_ballPulseFactor * combinedBallAlpha * itemPulse}});
      }

      if (x4d_selectedMorphball && ballT > 0.5f) {
        float ballEndT = (ballT - 0.5f) / 0.5f;
        float oneMinusBallEndT = 1.f - ballEndT;

        float spinScale = 0.75f * oneMinusBallEndT + 1.f;
        float spinAlpha;
        if (ballEndT < 0.1f)
          spinAlpha = 0.f;
        else if (ballEndT < 0.2f)
          spinAlpha = (ballEndT - 0.1f) / 0.1f;
        else if (ballEndT < 0.9f)
          spinAlpha = 1.f;
        else
          spinAlpha = 1.f - (ballT - 0.9f) / 0.1f;

        zeus::CRelAngle spinAngle = zeus::degToRad(360.f * oneMinusBallEndT);
        spinAlpha *= 0.5f;
        if (spinAlpha > 0.f) {
          const CModelFlags flags{7, ballMatIdx, 1, zeus::CColor{1.f, spinAlpha * alpha}};
          x184_ballModelData->Render(
              mgr, x10_ballXf * zeus::CTransform::RotateZ(spinAngle) * zeus::CTransform::Scale(spinScale),
              x24c_actorLights.get(), flags);
        }
      }

      if (x270_24_hasSpiderBall && ballAlpha > 0.f) {
        CGraphics::SetModelMatrix(x10_ballXf);
        const auto glassMatIdx = static_cast<u8>(x1e4_glassMatIdx);
        x1d4_spiderBallGlass->Draw({5, glassMatIdx, 3, zeus::CColor{1.f, 0.f}});
        x1d4_spiderBallGlass->Draw({5, glassMatIdx, 3, zeus::CColor{1.f, combinedBallAlpha}});
        x1d4_spiderBallGlass->Draw(
            {7, glassMatIdx, 3, zeus::CColor{1.f, x6c_ballPulseFactor * itemPulse * combinedBallAlpha}});
      }
    }

    if (phazonSuit && alpha > 0.1f) {
      float radius = zeus::clamp(0.2f, (10.f - (xc0_userZoom >= 0.f ? xc0_userZoom : -xc0_userZoom)) / 20.f, 1.f);
      float offset = std::sin(x260_phazonOffsetAngle);
      zeus::CColor color = g_tweakGuiColors->GetPauseBlurFilterColor();
      color.a() = alpha;
      g_Renderer->DrawPhazonSuitIndirectEffect(zeus::CColor(0.1f, alpha), x250_phazonIndirectTexture, color, radius,
                                               0.1f, offset, offset);
    }
  } else {
    const auto ballMatIdx = static_cast<u8>(x1e0_ballMatIdx);
    const std::array flags{
        CModelFlags{5, ballMatIdx, 3, zeus::CColor{1.f, 0.f}},
        CModelFlags{5, ballMatIdx, 3, zeus::CColor{1.f, alpha}},
    };
    x184_ballModelData->MultiPassDraw(CModelData::EWhichModel::Normal, x10_ballXf, x24c_actorLights.get(), flags.data(),
                                      flags.size());
    x184_ballModelData->Render(
        mgr, x10_ballXf, nullptr,
        CModelFlags{7, ballMatIdx, 3, zeus::CColor{1.f, x6c_ballPulseFactor * alpha * itemPulse}});

    const CMorphBall::ColorArray ballGlowColorData = CMorphBall::BallGlowColors[x1e8_ballGlowColorIdx];
    const zeus::CColor ballGlowColor{
        float(ballGlowColorData[0]) / 255.f,
        float(ballGlowColorData[1]) / 255.f,
        float(ballGlowColorData[2]) / 255.f,
        alpha,
    };
    x22c_ballInnerGlowGen->SetModulationColor(ballGlowColor);

    if (alpha > 0.f) {
      if (x22c_ballInnerGlowGen->GetNumActiveChildParticles() > 0) {
        const CMorphBall::ColorArray transFlashColorData = CMorphBall::BallTransFlashColors[x1e8_ballGlowColorIdx];
        const zeus::CColor transFlashColor{
            float(transFlashColorData[0]) / 255.f,
            float(transFlashColorData[1]) / 255.f,
            float(transFlashColorData[2]) / 255.f,
            alpha,
        };
        x22c_ballInnerGlowGen->GetActiveChildParticle(0).SetModulationColor(transFlashColor);

        if (x22c_ballInnerGlowGen->GetNumActiveChildParticles() > 1) {
          const CMorphBall::ColorArray auxColorData = CMorphBall::BallAuxGlowColors[x1e8_ballGlowColorIdx];
          const zeus::CColor auxColor{
              float(auxColorData[0]) / 255.f,
              float(auxColorData[1]) / 255.f,
              float(auxColorData[2]) / 255.f,
              alpha,
          };
          x22c_ballInnerGlowGen->GetActiveChildParticle(1).SetModulationColor(auxColor);
        }
      }
      x22c_ballInnerGlowGen->Render();
    }

    if (x270_24_hasSpiderBall) {
      const auto glassMatIdx = static_cast<u8>(x1e4_glassMatIdx);
      CGraphics::SetModelMatrix(x10_ballXf);
      x1d4_spiderBallGlass->Draw({5, 0, 3, zeus::CColor{1.f, 0.f}});
      x1d4_spiderBallGlass->Draw({5, glassMatIdx, 3, zeus::CColor{1.f, alpha}});
      x1d4_spiderBallGlass->Draw({7, glassMatIdx, 3, zeus::CColor{1.f, x6c_ballPulseFactor * alpha * itemPulse}});
    }
  }

  if (x238_ballTransitionFlashGen) {
    const CMorphBall::ColorArray c = CMorphBall::BallTransFlashColors[x1e8_ballGlowColorIdx];
    const zeus::CColor color{
        float(c[0]) / 255.f,
        float(c[1]) / 255.f,
        float(c[2]) / 255.f,
        1.f,
    };
    x238_ballTransitionFlashGen->SetModulationColor(color);
    x238_ballTransitionFlashGen->Render();
  }

  CGraphics::DisableAllLights();
}

void CSamusDoll::Touch() {
  if (!CheckLoadComplete())
    return;
  xc8_suitModel0->GetAnimationData()->PreRender();
  x134_suitModelBoots->GetAnimationData()->PreRender();
  x184_ballModelData->GetAnimationData()->PreRender();
  xc8_suitModel0->Touch(CModelData::EWhichModel::Normal, 0);
  x134_suitModelBoots->Touch(CModelData::EWhichModel::Normal, 0);
  x184_ballModelData->Touch(CModelData::EWhichModel::Normal, 0);
}

void CSamusDoll::SetupLights() {
  x23c_lights[0] = CLight::BuildDirectional(xb0_userRot.toTransform().basis[1], zeus::CColor(0.75f, 1.f));
  x24c_actorLights->BuildFakeLightList(x23c_lights, zeus::skBlack);
}

void CSamusDoll::SetInMorphball(bool morphball) {
  if (x54_remTransitionTime > 0.f)
    return;
  if (x4d_selectedMorphball == morphball)
    return;
  x4d_selectedMorphball = morphball;
  SetTransitionAnimation();
}

void CSamusDoll::SetTransitionAnimation() {
  if (!x4c_completedMorphball) {
    /* Into morphball */
    xc8_suitModel0->GetAnimationData()->SetAnimation(CAnimPlaybackParms{0, -1, 1.f, true}, false);
    x134_suitModelBoots->GetAnimationData()->SetAnimation(CAnimPlaybackParms{0, -1, 1.f, true}, false);
    x50_totalTransitionTime = x54_remTransitionTime = xc8_suitModel0->GetAnimationData()->GetAnimationDuration(0);
  } else if (!x4d_selectedMorphball) {
    /* Outta morphball */
    xc8_suitModel0->GetAnimationData()->SetAnimation(CAnimPlaybackParms{1, -1, 1.f, true}, false);
    x134_suitModelBoots->GetAnimationData()->SetAnimation(CAnimPlaybackParms{1, -1, 1.f, true}, false);
    x50_totalTransitionTime = x54_remTransitionTime = xc8_suitModel0->GetAnimationData()->GetAnimationDuration(1);
  }
}

void CSamusDoll::SetRotationSfxPlaying(bool playing) {
  if (playing) {
    if (x268_rotateSfx)
      return;
    x268_rotateSfx = CSfxManager::SfxStart(SFXui_map_rotate, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);
  } else {
    CSfxManager::SfxStop(x268_rotateSfx);
    x268_rotateSfx.reset();
  }
}

void CSamusDoll::SetOffsetSfxPlaying(bool playing) {
  if (playing) {
    if (x264_offsetSfx)
      return;
    x264_offsetSfx = CSfxManager::SfxStart(SFXui_map_pan, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);
  } else {
    CSfxManager::SfxStop(x264_offsetSfx);
    x264_offsetSfx.reset();
  }
}

void CSamusDoll::SetZoomSfxPlaying(bool playing) {
  if (playing) {
    if (x26c_zoomSfx)
      return;
    x26c_zoomSfx = CSfxManager::SfxStart(SFXui_map_zoom, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId);
  } else {
    CSfxManager::SfxStop(x26c_zoomSfx);
    x26c_zoomSfx.reset();
  }
}

void CSamusDoll::SetRotation(float xDelta, float zDelta, float dt) {
  if (xc4_viewInterp != 0.f && xc4_viewInterp != 1.f)
    return;
  SetRotationSfxPlaying(xDelta != 0.f || zDelta != 0.f);
  zeus::CEulerAngles angles(xb0_userRot);

  zeus::CRelAngle angX(angles.x());
  angX.makeRel();
  zeus::CRelAngle angZ(angles.z());
  angZ.makeRel();

  angX += xDelta;
  angX.makeRel();
  angZ += zDelta;
  angZ.makeRel();

  float angXCenter = angX;
  if (angXCenter > M_PIF)
    angXCenter -= 2.f * M_PIF;
  angXCenter = zeus::clamp(-1.555f, angXCenter, 1.555f);

  zeus::CQuaternion quat;
  quat.rotateZ(angZ);
  quat.rotateX(zeus::CRelAngle(angXCenter).asRel());
  xb0_userRot = quat;
}

void CSamusDoll::SetOffset(const zeus::CVector3f& offset, float dt) {
  if (xc4_viewInterp != 0.f && xc4_viewInterp != 1.f)
    return;
  zeus::CVector3f oldOffset = xa4_offset;
  zeus::CMatrix3f rotMtx = xb0_userRot.toTransform().basis;
  xa4_offset += rotMtx * zeus::CVector3f(offset.x(), 0.f, offset.z());
  SetOffsetSfxPlaying((oldOffset - xa4_offset).magnitude() > dt);
  float oldZoom = xc0_userZoom;
  xc0_userZoom = zeus::clamp(-4.f, xc0_userZoom + offset.y(), -2.2f);
  bool zoomSfx = std::fabs(xc0_userZoom - oldZoom) > dt;
  float zoomDelta = offset.y() - (xc0_userZoom - oldZoom);
  zeus::CVector3f newOffset = rotMtx[1] * zoomDelta + xa4_offset;
  zeus::CVector3f delta = newOffset - xa4_offset;
  oldOffset = xa4_offset;
  if (!delta.isZero()) {
    zeus::CSphere sphere(skInitialOffset, 1.f);
    float T;
    zeus::CVector3f point;
    if (CollisionUtil::RaySphereIntersection(sphere, xa4_offset, delta.normalized(), 0.f, T, point)) {
      if ((point - xa4_offset).magnitude() < std::fabs(zoomDelta))
        xa4_offset = point;
      else
        xa4_offset = newOffset;
    } else {
      xa4_offset = newOffset;
    }
  }
  if ((oldOffset - xa4_offset).magnitude() > dt)
    zoomSfx = true;
  SetZoomSfxPlaying(zoomSfx);
  delta = xa4_offset - skInitialOffset;
  if (delta.magnitude() > 1.f)
    xa4_offset = delta.normalized() + skInitialOffset;
}

void CSamusDoll::BeginViewInterpolate(bool zoomIn) {
  if (xc4_viewInterp == 0.f) {
    CSfxManager::SfxStart(SFXui_samus_doll_enter, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);
  } else {
    SetOffsetSfxPlaying(false);
    SetZoomSfxPlaying(false);
    SetRotationSfxPlaying(false);
    CSfxManager::SfxStart(SFXui_samus_doll_exit, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);
  }

  xc4_viewInterp = zoomIn ? FLT_EPSILON : (-1.f + FLT_EPSILON);
  x84_interpStartOffset = xa4_offset;
  x90_userInterpRot = xb0_userRot;
  xa0_userInterpZoom = xc0_userZoom;
  x80_fixedZoom = zoomIn ? -2.2f : -3.6f;
}

} // namespace metaforce::MP1