#include "audio/MapAudioController.h"
#include "common/math/GameMath.h"
#include "character/CharacterObject.h"
#include "map/GameMap.h"
#include <SDL2/SDL.h>
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/animation/animation.h>
#include <frostbite2D/audio/music.h>
#include <frostbite2D/core/application.h>
#include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/resource/audio_database.h>
#include <frostbite2D/utils/startup_trace.h>
#include <algorithm>
#include <cmath>

namespace frostbite2D {

namespace {

// 图层名和层级顺序与原始地图资源的层语义保持一致，方便直接按 layer 字段挂载。
static const char* kLayerNames[] = {
    "contact", "distantback", "middleback", "bottom", "closeback",
    "normal",  "close",       "cover",      "max"};

static const int kLayerOrders[] = {
    10000, 50000, 100000, 150000, 200000, 250000, 300000, 350000, 400000};

constexpr int kTileRootZOrder = -1000000;
constexpr float kExtendedTileStepY = 120.0f;

// 地图里的 tile/img 统一走这里创建，失败时返回空 Sprite
// 占位，避免整张地图中断。
Ptr<Sprite> createMapSprite(const std::string& path, int index) {
  auto sprite = Sprite::createFromNpk(path, static_cast<size_t>(index));
  if (!sprite) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameMap: failed to create sprite %s[%d]",
                path.c_str(), index);
    sprite = MakePtr<Sprite>();
  }
  return sprite;
}

MapAudioState BuildMapAudioState(const game::MapConfig& mapConfig) {
  MapAudioState state;
  if (mapConfig.soundIds.empty()) {
    return state;
  }

  auto& audioDatabase = AudioDatabase::get();
  for (const auto& soundId : mapConfig.soundIds) {
    AudioEntryType type = audioDatabase.typeOf(soundId);
    std::string filePath = audioDatabase.filePath(soundId);
    if (filePath.empty()) {
      continue;
    }

    if (type == AudioEntryType::Music) {
      if (!state.bgmTrack) {
        state.bgmTrack = MapAudioTrack{soundId, filePath};
      }
      continue;
    }

    if (type == AudioEntryType::Ambient) {
      state.ambientLoops.push_back(MapAudioTrack{soundId, filePath});
      continue;
    }

    if (type == AudioEntryType::Effect) {
      SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION,
                     "GameMap: ignore one-shot map sound %s in [%s]",
                     soundId.c_str(), mapConfig.mapPath.c_str());
      continue;
    }

    if (type == AudioEntryType::Random) {
      SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION,
                     "GameMap: ignore random map audio %s in [%s]",
                     soundId.c_str(), mapConfig.mapPath.c_str());
    }
  }

  return state;
}

} // namespace

GameMap::GameMap() {
  EnableEventReceive();
  initLayers();
}

void GameMap::initLayers() {
  for (size_t i = 0; i < std::size(kLayerNames); ++i) {
    auto layer = MakePtr<GameMapLayer>();
    layer->SetName(kLayerNames[i]);
    layer->SetZOrder(kLayerOrders[i]);
    layerMap_[kLayerNames[i]] = layer;
    AddChild(layer);
  }
}

void GameMap::clearLayerChildren() {
  for (auto& [_, layer] : layerMap_) {
    if (layer) {
      layer->RemoveAllChildren();
      layer->ClearDebugAreaInfo();
    }
  }
  tileRoot_.Reset();
  movablePolygon_.clear();
  moveArea_.clear();
  currentMusic_.Reset();
  backgroundRepeatWidth_ = 0;
  SetDebugHighlightedMoveAreaIndex(kInvalidMoveAreaIndex);
}

bool GameMap::LoadMap(const std::string &mapName) {
  ScopedStartupTrace startupTrace("GameMap::LoadMap");
  clearLayerChildren();
  {
    ScopedStartupTrace stageTrace("game::loadMapConfig");
    if (!game::loadMapConfig(mapName, mapConfig_)) {
      SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                   "GameMap: failed to load map %s", mapName.c_str());
      return false;
    }
  }

  mapOffsetY_ = mapConfig_.backgroundPos;
  {
    ScopedStartupTrace stageTrace("GameMap::InitTile");
    InitTile();
  }
  {
    ScopedStartupTrace stageTrace("GameMap::InitBackgroundAnimation");
    InitBackgroundAnimation();
  }
  {
    ScopedStartupTrace stageTrace("GameMap::InitMapAnimation");
    InitMapAnimation();
  }
  {
    ScopedStartupTrace stageTrace("GameMap::InitVirtualMovableArea");
    InitVirtualMovableArea();
  }
  {
    ScopedStartupTrace stageTrace("GameMap::InitMoveArea");
    InitMoveArea();
  }

  return true;
}

void GameMap::Enter() {
  MapAudioController::get().ApplyMapAudio(BuildMapAudioState(mapConfig_));
}

void GameMap::Update(float deltaTime) { Actor::Update(deltaTime); }

void GameMap::Render() {
  PrepareRuntimeObjectsForRender();
  Actor::Render();
}

void GameMap::PrepareRuntimeObjectsForRender() {
  auto layerIt = layerMap_.find("normal");
  if (layerIt == layerMap_.end() || !layerIt->second) {
    return;
  }

  for (auto it = layerIt->second->GetChildren().begin();
       it != layerIt->second->GetChildren().end(); ++it) {
    if (!*it) {
      continue;
    }

    if (auto* character = dynamic_cast<CharacterObject*>((*it).Get())) {
      character->PrepareRenderFrame();
    }
  }
}

void GameMap::InitTile() {
  if (mapConfig_.tilePaths.empty() && mapConfig_.extendedTilePaths.empty()) {
    return;
  }

  auto bottomIt = layerMap_.find("bottom");
  if (bottomIt == layerMap_.end() || !bottomIt->second) {
    return;
  }

  tileRoot_ = MakePtr<Actor>();
  tileRoot_->SetName("tileRoot");
  tileRoot_->SetZOrder(kTileRootZOrder);
  bottomIt->second->AddChild(tileRoot_);

  float maxBaseHeight = 0.0f;
  float maxOffset = mapConfig_.tilePos.y;

  int normalTileCount = static_cast<int>(mapConfig_.tilePaths.size());
  float nextNormalX = mapConfig_.tilePos.x;

  for (size_t i = 0; i < mapConfig_.tilePaths.size(); ++i) {
    game::TileInfo info;
    if (!game::loadTileInfo(mapConfig_.tilePaths[i], info)) {
      continue;
    }

    std::string spritePath = info.spritePath.empty()
                                 ? "sprite/character/common/circlecooltime.img"
                                 : info.spritePath;
    auto sprite = createMapSprite(spritePath, info.spriteIndex);
    float posX = nextNormalX;
    float posY = mapConfig_.tilePos.y;
    sprite->SetPosition(posX, posY);
    tileRoot_->AddChild(sprite);

    Vec2 size = sprite->GetSize();
    maxOffset = std::max(maxOffset, posY);
    maxBaseHeight = std::max(maxBaseHeight, size.y);
    backgroundRepeatWidth_ =
        std::max(backgroundRepeatWidth_, static_cast<int>(posX + size.x));
    nextNormalX += size.x;
  }

  float nextExtendedX = mapConfig_.tilePos.x;
  for (size_t i = 0; i < mapConfig_.extendedTilePaths.size(); ++i) {
    game::TileInfo info;
    if (!game::loadTileInfo(mapConfig_.extendedTilePaths[i], info)) {
      continue;
    }

    std::string spritePath = info.spritePath.empty()
                                 ? "sprite/character/common/circlecooltime.img"
                                 : info.spritePath;
    auto sprite = createMapSprite(spritePath, info.spriteIndex);
    int row = normalTileCount > 0 ? static_cast<int>(i) / normalTileCount : 0;
    float posX = nextExtendedX;
    float posY = maxOffset + maxBaseHeight + row * kExtendedTileStepY;
    sprite->SetPosition(posX, posY);
    tileRoot_->AddChild(sprite);

    Vec2 size = sprite->GetSize();
    backgroundRepeatWidth_ =
        std::max(backgroundRepeatWidth_, static_cast<int>(posX + size.x));
    nextExtendedX += size.x;
  }
}

void GameMap::InitBackgroundAnimation() {
  for (const auto& ani : mapConfig_.backgroundAnimations) {
    auto animation = MakePtr<Animation>(ani.filename);
    if (!animation || !animation->IsUsable()) {
      continue;
    }

    // 远景背景通常需要横向重复铺满地图；滚动倍率越大，需要铺的份数也越多。
    int repeatCount = 1;
    Vec2 animationSize = animation->GetSize();
    float rate = std::max(1.0f, mapConfig_.farSightScroll / 100.0f);
    if (animationSize.x > 0.0f && backgroundRepeatWidth_ > 0) {
      repeatCount =
          std::max(1, static_cast<int>((backgroundRepeatWidth_ * rate) /
                                       animationSize.x) +
                          1);
    }

    auto layerIt = layerMap_.find(ani.layer);
    if (layerIt == layerMap_.end()) {
      continue;
    }

    for (int i = 0; i < repeatCount; ++i) {
      auto backgroundAni = MakePtr<Animation>(ani.filename);
      if (!backgroundAni || !backgroundAni->IsUsable()) {
        continue;
      }
      // 背景动画在各自层内从左到右平铺，zOrder 放到极低，避免压住该层其他内容。
      backgroundAni->SetPosition(i * animationSize.x, -120.0f);
      backgroundAni->SetZOrder(-1000000);
      layerIt->second->AddChild(backgroundAni);
    }
  }
}

void GameMap::InitMapAnimation() {
  for (const auto& ani : mapConfig_.mapAnimations) {
    auto animation = MakePtr<Animation>(ani.filename);
    if (!animation || !animation->IsUsable()) {
      continue;
    }

    // 地图对象的显示 y 会扣掉 zPos，等价于把“离地高度”折算回屏幕坐标。
    animation->SetPosition(static_cast<float>(ani.xPos),
                           static_cast<float>(ani.yPos - ani.zPos));
    // 同层内继续按 y 排序，保证越靠下的物件越靠前。
    animation->SetZOrder(ani.yPos);

    auto layerIt = layerMap_.find(ani.layer);
    if (layerIt != layerMap_.end()) {
      layerIt->second->AddChild(animation);
    }
  }
}

void GameMap::InitVirtualMovableArea() {
  movablePolygon_ = mapConfig_.virtualMovablePolygon;
  if (movablePolygon_.empty()) {
    return;
  }
  if (!movableAreaCheckEnabled_) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "GameMap: movable area check disabled, allowing free movement");
  }

  if (!debugMode_) {
    return;
  }

  auto layerIt = layerMap_.find("max");
  if (layerIt == layerMap_.end() || !layerIt->second) {
    return;
  }
  layerIt->second->SetDebugFeasibleAreaPolygon(movablePolygon_);
}

void GameMap::InitMoveArea() {
  moveArea_ = mapConfig_.townMovableAreas;
  if (!debugMode_) {
    return;
  }

  auto layerIt = layerMap_.find("max");
  if (layerIt == layerMap_.end() || !layerIt->second) {
    return;
  }

  for (size_t i = 0;
       i < moveArea_.size() && i < mapConfig_.townMovableAreaTargets.size();
       ++i) {
    layerIt->second->AddDebugMoveAreaInfo(moveArea_[i], i);
  }
}

Vec2 GameMap::GetDefaultCameraFocus() const {
  float centerX = static_cast<float>(mapConfig_.cameraLimitMinX +
                                     mapConfig_.cameraLimitMaxX) *
                  0.5f;
  float centerY = static_cast<float>(mapConfig_.cameraLimitMinY +
                                     mapConfig_.cameraLimitMaxY) *
                  0.5f;
  return Vec2(centerX, centerY);
}

Vec2 GameMap::ClampCameraFocus(const Vec2 &focus, float zoom) const {
  Camera* camera = Renderer::get().getCamera();
  float targetX = focus.x;
  float targetY = focus.y;
  float halfWidth = 0.0f;
  float halfHeight = 0.0f;

  if (camera) {
    float safeZoom = std::max(zoom, 0.01f);
    halfWidth =
        static_cast<float>(camera->getViewportWidth()) * 0.5f / safeZoom;
    halfHeight =
        static_cast<float>(camera->getViewportHeight()) * 0.5f / safeZoom;
  }

  float minFocusX = static_cast<float>(mapConfig_.cameraLimitMinX) + halfWidth;
  float maxFocusX = static_cast<float>(mapConfig_.cameraLimitMaxX) - halfWidth;
  float minFocusY = static_cast<float>(mapConfig_.cameraLimitMinY) + halfHeight;
  float maxFocusY = static_cast<float>(mapConfig_.cameraLimitMaxY) - halfHeight;

  if (minFocusX > maxFocusX) {
    targetX = (static_cast<float>(mapConfig_.cameraLimitMinX) +
               static_cast<float>(mapConfig_.cameraLimitMaxX)) *
              0.5f;
  } else {
    targetX = std::clamp(targetX, minFocusX, maxFocusX);
  }

  if (minFocusY > maxFocusY) {
    targetY = (static_cast<float>(mapConfig_.cameraLimitMinY) +
               static_cast<float>(mapConfig_.cameraLimitMaxY)) *
              0.5f;
  } else {
    targetY = std::clamp(targetY, minFocusY, maxFocusY);
  }

  return Vec2(targetX, targetY);
}

void GameMap::ApplyCameraFocus(const Vec2 &focus) {
  updateLayerPositions(focus);
}

void GameMap::updateLayerPositions(const Vec2 &cameraFocus) {
  (void)cameraFocus;

  Camera *camera = Renderer::get().getCamera();
  if (!camera) {
    return;
  }

  Vec2 cameraPos = camera->getRenderPosition();

  // 主视图平移完全交给底层
  // Camera，这里只保留地图层自己的静态校准和少量视差偏移。
  for (auto& entry : layerMap_) {
    const std::string& name = entry.first;
    RefPtr<GameMapLayer>& layer = entry.second;
    float posX = 0.0f;
    float posY = static_cast<float>(mapOffsetY_);

    // 远景层只做部分水平跟随；主 Camera
    // 已经负责完整位移，这里补上“少动一些”的差值。
    if (name == "distantback") {
      float scrollFactor =
          static_cast<float>(mapConfig_.farSightScroll) / 100.0f;
      posX = cameraPos.x * (1.0f - scrollFactor);
    }
    layer->SetPosition(posX, posY);
  }
}

void GameMap::AddObject(RefPtr<Actor> object) {
  AddObjectToLayer("normal", object);
}

void GameMap::AddObjectToLayer(const std::string& layerName, RefPtr<Actor> object) {
  if (!object) {
    return;
  }

  auto layerIt = layerMap_.find(layerName);
  if (layerIt == layerMap_.end() || !layerIt->second) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "GameMap: layer %s missing, fallback to normal",
                layerName.c_str());
    layerIt = layerMap_.find("normal");
    if (layerIt == layerMap_.end() || !layerIt->second) {
      return;
    }
  }

  // Keep runtime objects sorted by their ground y inside the target layer.
  object->SetZOrder(static_cast<int>(object->GetPosition().y));
  layerIt->second->AddObject(object);
}

Vec3 GameMap::CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const {
  int currentX = gameMath::RoundWorldCoordinate(curPos.x);
  int currentY = gameMath::RoundWorldCoordinate(curPos.y);
  int targetX = gameMath::RoundWorldCoordinate(curPos.x + posOffset.x);
  int targetY = gameMath::RoundWorldCoordinate(curPos.y + posOffset.y);
  int targetZ = gameMath::RoundWorldCoordinate(curPos.z + posOffset.z);
  Vec3 result = gameMath::MakeIntegerWorldPosition(currentX, currentY, targetZ);
  if (!movableAreaCheckEnabled_) {
    return gameMath::MakeIntegerWorldPosition(targetX, targetY, targetZ);
  }
  if (movablePolygon_.size() < 3) {
    return gameMath::MakeIntegerWorldPosition(targetX, targetY, targetZ);
  }
  // Prefer the full destination first; only fall back to single-axis sliding
  // when the combined move would leave the movable polygon.
  if (gameMath::IsPointMovable(movablePolygon_, targetX, targetY)) {
    result.x = static_cast<float>(targetX);
    result.y = static_cast<float>(targetY);
    return result;
  }

  bool isXValid = gameMath::IsPointMovable(movablePolygon_, targetX, currentY);
  bool isYValid = gameMath::IsPointMovable(movablePolygon_, currentX, targetY);

  if (isXValid && isYValid) {
    int moveX = std::abs(targetX - currentX);
    int moveY = std::abs(targetY - currentY);
    if (moveX >= moveY) {
      result.x = static_cast<float>(targetX);
    } else {
      result.y = static_cast<float>(targetY);
    }
    return result;
  }

  if (isXValid) {
    result.x = static_cast<float>(targetX);
  } else if (isYValid) {
    result.y = static_cast<float>(targetY);
  }

  return result;
}

GameMap::MapMoveArea GameMap::CheckIsItMoveArea(const Vec3& curPos) const {
  MapMoveArea target;
  if (TryGetMoveAreaTarget(curPos, target)) {
    return target;
  }
  return MapMoveArea();
}

size_t GameMap::FindMoveAreaIndex(const Vec3& curPos) const {
  int currentX = gameMath::RoundWorldCoordinate(curPos.x);
  int currentY = gameMath::RoundWorldCoordinate(curPos.y);
  for (size_t i = 0;
       i < moveArea_.size() && i < mapConfig_.townMovableAreaTargets.size();
       ++i) {
    if (moveArea_[i].containsPoint(gameMath::MakeIntegerWorldPoint(currentX, currentY))) {
      return i;
    }
  }
  return kInvalidMoveAreaIndex;
}

bool GameMap::TryGetMoveAreaTarget(const Vec3& curPos, MapMoveArea& outTarget) const {
  size_t index = FindMoveAreaIndex(curPos);
  if (index != kInvalidMoveAreaIndex &&
      index < mapConfig_.townMovableAreaTargets.size()) {
    outTarget = mapConfig_.townMovableAreaTargets[index];
    return true;
  }
  return false;
}

void GameMap::SetDebugHighlightedMoveAreaIndex(size_t index) {
  auto layerIt = layerMap_.find("max");
  if (layerIt == layerMap_.end() || !layerIt->second) {
    return;
  }
  layerIt->second->SetDebugHighlightedMoveAreaIndex(index);
}

const std::vector<GameMap::MapMoveArea>& GameMap::GetMoveAreaInfo() const {
  return mapConfig_.townMovableAreaTargets;
}

game::MoveAreaBounds GameMap::GetMovablePositionBounds(size_t index) const {
  if (index >= mapConfig_.townMovableAreaBounds.size()) {
    return game::MoveAreaBounds();
  }
  return mapConfig_.townMovableAreaBounds[index];
}

Rect GameMap::GetMovablePositionArea(size_t index) const {
  if (index >= mapConfig_.townMovableAreas.size()) {
    return Rect::Zero();
  }
  return mapConfig_.townMovableAreas[index];
}

} // namespace frostbite2D
