#include "../gen/Def.h"
#include "Gen.h"
#include "Global.h"
#include "Input.h"
#include "Instance.h"
#include "Level.h"
#include "Log.h"
#include "Puzzle.h"
#include "rendering/Rendering.h"

#include "SDL3/SDL_mouse.h"
#include "bgfx/bgfx.h"
#include "bx/error.h"
#include "bx/file.h"
#include "bx/filepath.h"
#include "bx/string.h"
#include "imgui.h"
#include <SDL3/SDL.h>
#include <bx/math.h>
#include <cstdint>
#include <tracy/Tracy.hpp>

using namespace Gen;

namespace Game
{
    void EntityRenderData::Render(const Model* models, const Material* materials, const Texture* textures)
    {
        if (models == nullptr || materials == nullptr || textures == nullptr) return;
        if (!Gen::IsValid(ModelH) || MaterialHandle >= EMaterial::EntryCount) return;
        if (!Visible) return;
        auto& rendering = GameRendering::Get();

        UpdateMatrix(Transform);
        bgfx::setTransform(Transform.M.M);

        const Model& currentModel = models[ModelH.ModelIdx];
        const Material& currentMaterial = materials[(uint16_t)MaterialHandle];

        if (!isValid(currentModel.IndexBuffer) || !isValid(currentModel.VertexBuffer)) return;
        if (!isValid(currentMaterial.Shader)) return;

        bgfx::setVertexBuffer(0, currentModel.VertexBuffer);
        bgfx::setIndexBuffer(currentModel.IndexBuffer);
        bgfx::setState(currentMaterial.State);

        float timeValues[4]{0.0f};
        timeValues[0] = GetInstance().Time.Now;

        float texInfo[4]{0.0f};
        texInfo[0] = textures[0].Info.width;
        texInfo[1] = textures[0].Info.height;
        texInfo[2] = rendering.DitherTextures.DitherTexWH;
        texInfo[3] = rendering.DitherTextures.DitherTexDepth;

        bgfx::UniformHandle sampler = rendering.DefaultSampler;
        bgfx::TextureHandle tex = rendering.Textures[0].RenderHandle;
        if (IsValid(TextureHandle))
        {
            sampler = textures[TextureHandle.TextureIdx].SamplerHandle;
            tex = textures[TextureHandle.TextureIdx].RenderHandle;
            texInfo[0] = textures[TextureHandle.TextureIdx].Info.width;
            texInfo[1] = textures[TextureHandle.TextureIdx].Info.height;
        }

        bgfx::setTexture(0, sampler, tex);
        bgfx::setTexture(1, rendering.DitherTextures.DitherSampler, rendering.DitherTextures.FinalTex);
        bgfx::setTexture(2, rendering.DitherTextures.RampSampler, rendering.DitherTextures.RampTex);
        bgfx::setUniform(currentMaterial.Uniforms[Material::UTime], timeValues);
        bgfx::setUniform(currentMaterial.Uniforms[Material::UDotColor], &DotColor.x);
        bgfx::setUniform(currentMaterial.Uniforms[Material::UTexInfo], texInfo);
        bgfx::setUniform(currentMaterial.Uniforms[Material::UBaseColor], &BaseColor.x);

        bgfx::submit(currentMaterial.ViewID, currentMaterial.Shader);
    }

    void EntityRenderData::LoadFromSaved(const Gen::SavedEntityRenderData& saved)
    {
        DotColor = saved.HighlightColor;
        BaseColor = saved.BaseColor;
        Transform = saved.TF;
        // TODO: fix handle indices
        MaterialHandle = saved.Material;
        TextureHandle = saved.Texture;
        ModelH = saved.Model;
        Visible = saved.Visible;
    }

    namespace
    {
        void UpdatePlayerInputMode()
        {
            bool IsGaming = GetInstance().Player.InputM == InputMode::Game;
            bool captureMouse = IsGaming && GetInstance().Player.InteractionM == InteractionMode::Walk;
            SDL_SetWindowRelativeMouseMode(GetShared().Window.SDLWindow, captureMouse);
            auto& IO = ImGui::GetIO();
            IO.ConfigFlags = FlagBool(IO.ConfigFlags, ImGuiConfigFlags_NoMouse | ImGuiConfigFlags_NoKeyboard, IsGaming);
            GameRendering::Get().UIVisible = IsGaming ? UIVisibilityState::Game : UIVisibilityState::Debug;
        }
    } // namespace

    void Level::Setup(GameData& data)
    {
        LOG("Level setup");
        void* storagePtr = data.EntityStorage;
        bool needReset = false;
        needReset |= Cubes.Setup(storagePtr, needReset);
        needReset |= Tests.Setup(storagePtr, needReset);
        needReset |= PuzzleTiles.Setup(storagePtr, needReset);
        needReset |= UIQuads.Setup(storagePtr, needReset);
        needReset |= LevelEntities.Setup(storagePtr, needReset);

        Puzzle::Setup();
        UIQuads.Count = 0;
        PuzzleTiles.Count = 0;

        bx::Error err;
        bx::DirectoryReader dirIter;
        bx::FileInfo info;
        bx::FilePath puzzleDirPath{Puzzle::PuzzleFileDir};
        if (dirIter.open(puzzleDirPath, &err))
        {
            while (true)
            {
                int32_t readCount = dirIter.read(&info, sizeof(info), &err);
                if (readCount == 0) break;
                if (err.isOk())
                {
                    if (info.type != bx::FileType::File) continue;
                    bx::StringView pathEnd = info.filePath.getExt();
                    if (bx::strCmpI(pathEnd, ".pzl") != 0) continue;

                    bx::FilePath fullPath = puzzleDirPath;
                    fullPath.join(info.filePath);
                    LOG("Loading %s", fullPath.getCPtr());

                    Gen::Deserializer ser;
                    Gen::PuzzleData dataBuf;
                    if (ser.Init(fullPath, "PZZL") && ser.ReadT(dataBuf))
                    {
                        if (dataBuf.ID >= BX_COUNTOF(Puzzles))
                        {
                            LOG_ERROR("Puzzle ID out of bounds: %u", dataBuf.ID);
                            ser.Finish();
                            continue;
                        }
                        Puzzles[dataBuf.ID].Data = dataBuf;
                        Puzzles[dataBuf.ID].Setup();
                    }
                    else
                    {
                        LOG_WARN("Failed to load puzzle!");
                    }
                    ser.Finish();
                }
                else
                {
                    LOG_ERROR("Failed parsing file name at %s:\n%s", puzzleDirPath.getCPtr(), err.getMessage());
                    break;
                }
            }
        }
        else
        {
            LOG_ERROR("Failed to open puzzle dir at %s:\n%s", puzzleDirPath.getCPtr(), err.getMessage().getCPtr());
        }

        if (!IsValid(PlayerOutsideViewCube))
        {
            PlayerOutsideViewCube = Cubes.New();
            Cubes.Get(PlayerOutsideViewCube).Setup();
        }

        UIQuads.Count = 0;
        PuzzleTiles.Count = 0;
        for (int32_t i = 0; i < BX_COUNTOF(Puzzles); ++i)
        {
            if (Puzzles[i].Data.ID != UINT16_MAX)
            {
                Puzzles[i].Setup();
            }
        }
        TabletHandle = UIQuads.New();
        UpdatePlayerInputMode();

        {
            Deserializer d;
            d.Init("game/data/static/uiconfig.dat", "UICO");
            d.ReadT(GetInstance().Player.Config.TabletRenderData);
            d.Finish();
        }
    }

    void Level::Update()
    {
        ZoneScopedN("Level update");
        START_PERF();
        PlayerData& player = GetInstance().Player;

        // Input
        float delta = GetInstance().Time.Delta;
        float moveSpeed = player.MovementSpeed;
        float rotSpeed = player.MouseSensitivity;

        float forwardInput = (GetKey(ScanCode::W) ? 1.0f : 0.0f) + (GetKey(ScanCode::S) ? -1.0f : 0.0f);
        float rightInput = (GetKey(ScanCode::D) ? 1.0f : 0.0f) + (GetKey(ScanCode::A) ? -1.0f : 0.0f);
        bx::Vec3 moveInput = bx::Vec3{rightInput, forwardInput, 0.0f};
        moveInput = bx::normalize(moveInput);
        bx::Vec3 inputVec = {moveInput.x * delta * moveSpeed, 0.0f, moveInput.y * delta * moveSpeed};
        Vec2 mouseMovement = GetMouseMovement();
        bx::Vec3 rotInput = {mouseMovement.y * delta * -rotSpeed, mouseMovement.x * delta * -rotSpeed, 0.0f};

        if (GetKeyPressedNow(ScanCode::F1))
        {
            player.CameraM = player.CameraM == CameraMode::Walk ? CameraMode::Freefly : CameraMode::Walk;
        }

        if (GetKeyPressedNow(ScanCode::F2))
        {
            if (player.InputM == InputMode::Game)
            {
                player.InputM = InputMode::UI;
            }
            else
            {
                player.InputM = InputMode::Game;
            }
            UpdatePlayerInputMode();
        }

        if (player.CameraM == CameraMode::Freefly)
        {
            if (GetMouseButton(MouseButton::Left))
            {
                player.FreeflyXRot += rotInput.x;
                player.FreeflyYRot += rotInput.y;
                bx::mtxRotateXYZ(player.FreeflyCamTransform.Rotation.M, player.FreeflyXRot, player.FreeflyYRot, 0.0f);
            }

            TranslateLocal(player.FreeflyCamTransform, {0.0f, 0.0f, inputVec.z});
            TranslateLocal(player.FreeflyCamTransform, {inputVec.x, 0.0f, 0.0f});
        }
        else if (player.CameraM == CameraMode::Walk)
        {
            TranslateLocal(player.PlayerCamTransform, {0.0f, 0.0f, inputVec.z});
            TranslateLocal(player.PlayerCamTransform, {inputVec.x, 0.0f, 0.0f});
            player.PlayerCamTransform.Position.y = 3.0f;

            player.WalkXRot += rotInput.x;
            player.WalkYRot += rotInput.y;
            bx::mtxRotateXYZ(player.PlayerCamTransform.Rotation.M, player.WalkXRot, player.WalkYRot, 0.0f);
        }

        if (GetKeyPressedNow(ScanCode::SPACE))
        {
            player.InteractionM = player.InteractionM == InteractionMode::ReadTablet ? InteractionMode::Walk
                                                                                     : InteractionMode::ReadTablet;
            UpdatePlayerInputMode();
        }

        // UI Tablet
        if (IsValid(TabletHandle))
        {
            auto& tablet = UIQuads.Get(TabletHandle);
            tablet.EData.LoadFromSaved(player.Config.TabletRenderData);
            UpdateMatrix(player.PlayerCamTransform);
            tablet.EData.Transform.Rotation = player.PlayerCamTransform.Rotation;
            Rotate(tablet.EData.Transform, {0.5f * bx::kPi, 0.0f, 0.0f});
            tablet.EData.Transform.Position =
                player.PlayerCamTransform.Position + AxisForward(player.PlayerCamTransform.M) * 1.0f;
        }

        // Cubes
        for (uint16_t i = 0; i < Cubes.Count; ++i)
        {
            Cubes.Get({i}).Update();
        }

        // Puzzle tiles
        for (int32_t i = 0; i < BX_COUNTOF(Puzzles); ++i)
        {
            Puzzles[i].IsActive = GetInstance().DebugData.SelectedDebugLevel == i;
            Puzzles[i].Update();
        }

        END_PERF(GetShared().Window.PerfCounters, PerfCounterType::GameLevelUpdate, GetShared().Window.FrameCounter);
    }

    void Level::Render(uint16_t viewId, const Model* models, const Material* materials, const Texture* textures)
    {
        ZoneScopedN("Level Render");
        auto& shared = GetShared();
        auto& player = GetInstance().Player;

        bx::mtxProj(&player.Projection.M[0],
                    75.0f,
                    float(shared.Window.WindowWidth) / float(shared.Window.WindowHeight),
                    0.1f,
                    1000.0f,
                    bgfx::getCaps()->homogeneousDepth);
        bx::mtxInverse(&player.ProjectionInverse.M[0], &player.Projection.M[0]);

        bool isFreefly = player.CameraM == CameraMode::Freefly;
        Cubes.Get(PlayerOutsideViewCube).EData.Visible = isFreefly;
        if (isFreefly)
        {
            UpdateMatrix(player.FreeflyCamTransform);
            bgfx::setViewTransform(viewId, player.FreeflyCamTransform.MI.M, &player.Projection.M[0]);
        }
        else
        {
            UpdateMatrix(player.PlayerCamTransform);
            bgfx::setViewTransform(viewId, player.PlayerCamTransform.MI.M, &player.Projection.M[0]);
        }

        bgfx::touch(viewId);

        Cubes.Render(models, materials, textures);
        Tests.Render(models, materials, textures);
        PuzzleTiles.Render(models, materials, textures);
        if (player.InteractionM == InteractionMode::ReadTablet)
        {
            UIQuads.Render(models, materials, textures);
        }
    }

    void Cube::Setup()
    {
        EData.MaterialHandle = EMaterial::UI;
        EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/cube.gltf");
    }

    void Cube::Update()
    {
        EData.Transform.Position = GetInstance().Player.PlayerCamTransform.Position;
        EData.Transform.Rotation = GetInstance().Player.PlayerCamTransform.Rotation;
        EData.Transform.Scale = {0.2f, 0.2f, 0.2f};
    }

    void TestEntity::Setup()
    {
        EData.MaterialHandle = EMaterial::Default;
        EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/cube.gltf");
    }

    void WorldPuzzle::Setup()
    {
        auto& level = GetInstance().GameLevel;
        for (int32_t i = 0; i < Puzzle::Config::MaxCardsInPuzzle; ++i)
        {
            TileHandles[i] = level.PuzzleTiles.New();
            auto& tile = level.PuzzleTiles.Get(TileHandles[i]);
            tile.EData.MaterialHandle = EMaterial::Default;

            UIPlacedCards[i] = level.UIQuads.New();
            auto& quad = level.UIQuads.Get(UIPlacedCards[i]);
            quad.EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/plane.glb");
            quad.EData.MaterialHandle = EMaterial::UI;
        }
        for (int32_t i = 0; i < Puzzle::Config::MaxAvailableStacks * WorldPuzzle::UIAvailableCardMaxStackPreview; ++i)
        {
            UIAvailableCards[i] = level.UIQuads.New();
            auto& quad = level.UIQuads.Get(UIAvailableCards[i]);
            quad.EData.MaterialHandle = EMaterial::UI;
            quad.EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/plane.glb");
        }
        IsSetup = true;
        LOG("finished setup!");
    }

    Vec3 GetMousePosWorld()
    {
        auto& window = GetShared().Window;
        Vec2 mousePos = GetMousePos();
        mousePos.x = mousePos.x / window.WindowWidth;
        mousePos.y = mousePos.y / window.WindowHeight;
        mousePos *= 2.0f;
        mousePos -= 1.0f;
        Vec4 mousePosView = {mousePos.x, -mousePos.y, 0.0f, 1.0f};
        Vec4 mousePosCam4 = Mul(GetInstance().Player.ProjectionInverse, mousePosView);
        Vec3 mousePosCam = Vec3{
            mousePosCam4.x /= mousePosCam4.w,
            mousePosCam4.y /= mousePosCam4.w,
            mousePosCam4.z /= mousePosCam4.w,
        };

        return LocalToGlobalPoint(GetInstance().Player.PlayerCamTransform, mousePosCam);
    }

    bool IsQuadHovered(Transform& quadTransform, Vec3 mousePosWorld, Vec3& outQuadPlaneIntersectPos)
    {
        Vec3 quadPosWorld = quadTransform.Position;
        Vec3 quadXWorld = LocalToGlobalPoint(quadTransform, {1, 0, 0});
        Vec3 quadZWorld = LocalToGlobalPoint(quadTransform, {0, 0, 1});
        if (RayPlaneIntersect(GetInstance().Player.PlayerCamTransform.Position,
                              mousePosWorld,
                              quadPosWorld,
                              quadXWorld,
                              quadZWorld,
                              outQuadPlaneIntersectPos))
        {
            Vec3 quadSpaceIntersect = GlobalToLocalPoint(quadTransform, outQuadPlaneIntersectPos);
            if (quadSpaceIntersect.x >= -1.0f && quadSpaceIntersect.x <= 1.0f && quadSpaceIntersect.z >= -1.0f &&
                quadSpaceIntersect.z <= 1.0f)
            {
                return true;
            }
        }
        return false;
    }

    void WorldPuzzle::Update()
    {
        Level& level = GetInstance().GameLevel;
        auto& staticCards = Puzzle::GetStaticPuzzleData().Cards;
        auto& visuals = Puzzle::GetStaticPuzzleData().Visuals;

        Transform& camTransform = GetInstance().Player.PlayerCamTransform;
        UpdateMatrix(camTransform);
        Vec3 cameraPos = camTransform.Position;

        Vec2 uiOffset = Vec2{static_cast<float>(Data.WidthTiles / Puzzle::Config::CardSize - 1),
                             static_cast<float>(Data.HeightTiles / Puzzle::Config::CardSize) - 1};
        uiOffset *= -UICardOffset * 0.5f;

        Transform& boardTransform = level.UIQuads.Get(level.TabletHandle).EData.Transform;
        Transform tileOriginTransform = boardTransform;
        tileOriginTransform.Position += AxisForward(camTransform.M) * -0.01f;
        TranslateLocal(tileOriginTransform, Vec3{uiOffset.x, 0.0f, uiOffset.y} * 1.0f);
        UpdateMatrix(tileOriginTransform);

        Vec3 mousePosWorld = GetMousePosWorld();

        // Available Cards
        for (int32_t i = 0; i < Puzzle::Config::MaxAvailableStacks; ++i)
        {
            auto& card = Data.AvailableCards[i];
            for (int32_t j = 0; j < UIAvailableCardMaxStackPreview; j++)
            {
                auto& quad = level.UIQuads.Get(UIAvailableCards[i * UIAvailableCardMaxStackPreview + j]);
                int32_t remaining = (int32_t)card.MaxAvailableCount - (int32_t)card.UsedCount;
                if (i < Data.AvailableCardCount && j < remaining)
                {
                    quad.EData.Visible = IsActive;
                    quad.EData.TextureHandle = Puzzle::IsValid(Data.AvailableCards[i].RefCard)
                                                   ? staticCards[Data.AvailableCards[i].RefCard.Idx].BoardTextureHandle
                                                   : Gen::TextureHandle{};
                    quad.EData.Transform.Position = tileOriginTransform.Position;
                    quad.EData.Transform.Rotation = camTransform.Rotation;
                    TranslateLocal(quad.EData.Transform,
                                   Vec3{j * 0.05f + i * 1.2f, 6.0f + (j % 2 == 0 ? 0.02f : 0.0f), j * 0.001f} *
                                       UICardOffset * UICardScale);
                    Rotate(quad.EData.Transform, Vec3{bx::kPi * 0.5f, 0.0f, (1.0f - 0 * 0.5f) * bx::kPi});
                    quad.EData.Transform.Scale = {UICardScale, UICardScale, UICardScale};
                    quad.EData.DotColor = {1.0f, 1.0f, 1.0f, 1.0f};
                    quad.EData.BaseColor = {1.0f, 1.0f, 1.0f, 1.0f};

                    if (j == 0)
                    {
                        Vec3 quadPlaneIntersectPos;
                        if (IsQuadHovered(quad.EData.Transform, mousePosWorld, quadPlaneIntersectPos) &&
                            DraggedAvailableCardIdx == UINT16_MAX && DraggedCard.X == -1 &&
                            GetMouseButtonPressedNow(MouseButton::Left))
                        {
                            DraggedAvailableCardIdx = i;
                        }
                        if (DraggedAvailableCardIdx == i)
                        {
                            Vec3 dragPos = quadPlaneIntersectPos;
                            dragPos -= AxisForward(camTransform.M) * 0.01f;
                            quad.EData.Transform.Position = dragPos;

                            Vec3 boardPos = GlobalToLocalPoint(tileOriginTransform, quadPlaneIntersectPos);
                            Vec3 boardTilePos = boardPos / UICardOffset;
                            int32_t xPos = (int32_t)bx::round(boardTilePos.x);
                            int32_t yPos = (int32_t)bx::round(boardTilePos.z);

                            if (!GetMouseButton(MouseButton::Left))
                            {
                                if (xPos >= 0 && xPos < Data.WidthTiles / Puzzle::Config::CardSize && yPos >= 0 &&
                                    yPos < Data.HeightTiles / Puzzle::Config::CardSize)
                                {
                                    Gen::PuzPos targetCardPos = {(int8_t)xPos, (int8_t)yPos};
                                    Gen::PlacedPuzzleCard& targetCard =
                                        Data.PlacedCards[yPos * Puzzle::Config::MaxPuzzleSizeCards + xPos];
                                    if (!Puzzle::IsValid(targetCard.RefCard) || targetCard.RefCard.Idx == 0)
                                    {
                                        Puzzle::DragAvailableCardTo(Data, targetCardPos, DraggedAvailableCardIdx, 0);
                                    }
                                }
                                DraggedAvailableCardIdx = UINT16_MAX;
                            }
                        }
                    }
                }
                else
                {
                    quad.EData.Visible = false;
                }
            }
        }

        // Board
        for (int8_t y = 0; y < Data.HeightTiles / Puzzle::Config::CardSize; ++y)
        {
            for (int8_t x = 0; x < Data.WidthTiles / Puzzle::Config::CardSize; ++x)
            {
                int32_t cardIdx = y * Puzzle::Config::MaxPuzzleSizeCards + x;
                Gen::PlacedPuzzleCard& card = Data.PlacedCards[cardIdx];
                auto& tile = level.PuzzleTiles.Get(TileHandles[cardIdx]);
                auto& quad = level.UIQuads.Get(UIPlacedCards[cardIdx]);

                bool isValid = Puzzle::IsValid(card.RefCard);

                // World Tile
                tile.EData.Visible = IsActive;
                tile.EData.ModelH = isValid ? staticCards[card.RefCard.Idx].ModelHandle : staticCards[0].ModelHandle;
                tile.EData.DotColor = visuals.TileDotColor;
                tile.EData.BaseColor = visuals.TileBaseColor;

                Vec3 cardPos = {
                    (float)card.Position.X * Puzzle::Config::CardScaleWorld,
                    -5.0f,
                    (float)card.Position.Y * Puzzle::Config::CardScaleWorld,
                };
                if (!isValid)
                {
                    cardPos = {x * Puzzle::Config::CardScaleWorld, -5.0f, y * Puzzle::Config::CardScaleWorld};
                }
                tile.EData.Transform.Position = cardPos;
                bx::mtxRotateY(tile.EData.Transform.Rotation.M, card.Rotation * bx::kPi * 0.5f);

                // UI Quad
                quad.EData.Visible = isValid && IsActive;
                quad.EData.TextureHandle =
                    isValid ? staticCards[card.RefCard.Idx].BoardTextureHandle : Gen::TextureHandle{};
                quad.EData.DotColor = card.IsLocked ? Puzzle::GetStaticPuzzleData().Visuals.DisabledCardTint
                                                    : Vec4{1.0f, 1.0f, 1.0f, 1.0f};

                quad.EData.Transform.Position = tileOriginTransform.Position;
                quad.EData.Transform.Rotation = camTransform.Rotation;
                TranslateLocal(quad.EData.Transform,
                               Vec3{(float)card.Position.X, (float)card.Position.Y, 0.0f} * UICardOffset * UICardScale);
                Rotate(quad.EData.Transform, Vec3{bx::kPi * 0.5f, 0.0f, (1.0f - card.Rotation * 0.5f) * bx::kPi});
                quad.EData.Transform.Scale = {UICardScale, UICardScale, UICardScale};

                Vec3 quadPlaneIntersectPos;
                if (isValid && IsQuadHovered(quad.EData.Transform, mousePosWorld, quadPlaneIntersectPos))
                {
                    if (!card.IsLocked && DraggedCard.X == -1 && DraggedAvailableCardIdx == UINT16_MAX)
                    {
                        if (GetMouseButtonPressedNow(MouseButton::Left))
                        {
                            DraggedCard.X = x;
                            DraggedCard.Y = y;
                        }
                        if (GetMouseButtonPressedNow(MouseButton::Right))
                        {
                            Puzzle::RotateCard(card);
                        }
                    }
                }
                if (DraggedCard.X == x && DraggedCard.Y == y)
                {
                    Vec3 dragPos = quadPlaneIntersectPos;
                    dragPos -= AxisForward(camTransform.M) * 0.01f;
                    quad.EData.Transform.Position = dragPos;

                    Vec3 boardPos = GlobalToLocalPoint(tileOriginTransform, quadPlaneIntersectPos);
                    Vec3 boardTilePos = boardPos / UICardOffset;
                    int32_t xPos = (int32_t)bx::round(boardTilePos.x);
                    int32_t yPos = (int32_t)bx::round(boardTilePos.z);
                    Gen::PuzPos srcCardPos = {(int8_t)DraggedCard.X, (int8_t)DraggedCard.Y};
                    Gen::PlacedPuzzleCard& srcCard =
                        Data.PlacedCards[srcCardPos.Y * Puzzle::Config::MaxPuzzleSizeCards + srcCardPos.X];

                    if (GetMouseButtonPressedNow(MouseButton::Right))
                    {
                        Puzzle::RotateCard(srcCard);
                    }

                    if (!GetMouseButton(MouseButton::Left))
                    {
                        Gen::PuzPos targetCardPos = {(int8_t)xPos, (int8_t)yPos};
                        if (xPos >= 0 && xPos < Data.WidthTiles / Puzzle::Config::CardSize && yPos >= 0 &&
                            yPos < Data.HeightTiles / Puzzle::Config::CardSize)
                        {
                            PlacedPuzzleCard srcCardCopy = srcCard;
                            Gen::PlacedPuzzleCard& targetCard =
                                Data.PlacedCards[yPos * Puzzle::Config::MaxPuzzleSizeCards + xPos];
                            bool canBeReplaced = !Puzzle::IsValid(targetCard.RefCard) || targetCard.RefCard.Idx == 0;
                            if (canBeReplaced && Puzzle::ReturnPlacedCard(Data, srcCardPos))
                            {
                                int32_t foundIdx = -1;
                                for (int32_t availCardIdx = 0; availCardIdx < Data.AvailableCardCount; ++availCardIdx)
                                {
                                    LOG("CHECK: %u", Data.AvailableCards[availCardIdx].RefCard.Idx);
                                    if (Data.AvailableCards[availCardIdx].RefCard.Idx == srcCardCopy.RefCard.Idx)
                                    {
                                        foundIdx = availCardIdx;
                                        break;
                                    }
                                }
                                if (foundIdx >= 0)
                                {
                                    Puzzle::DragAvailableCardTo(Data, targetCardPos, foundIdx, srcCard.Rotation);
                                }
                                else
                                {
                                    LOG_ERROR("NOTFOUND: %u", srcCardCopy.RefCard.Idx);
                                }
                            }
                        }
                        else
                        {
                            Puzzle::ReturnPlacedCard(Data, srcCardPos);
                        }
                        DraggedCard.X = -1;
                        DraggedCard.Y = -1;
                    }
                }
            }
        }
    }
} // namespace Game
