#include "world/GameWorld.h"
#include "character/CharacterObject.h"
#include "map/GameDataLoader.h"
#include "scene/GameDebugUIScene.h"
#include <SDL2/SDL.h>
#include <frostbite2D/scene/scene_manager.h>

namespace frostbite2D {

namespace {

constexpr int kDefaultTownId = 1;

Vec2 ComputeMoveAreaCenter(const game::MoveAreaBounds& bounds) {
  return Vec2((bounds.leftTop.x + bounds.rightBottom.x) * 0.5f,
              (bounds.leftTop.y + bounds.rightBottom.y) * 0.5f);
}

void SetActorGroundPosition(RefPtr<Actor> actor, const Vec2& position) {
  if (!actor) {
    return;
  }

  if (auto* character = dynamic_cast<CharacterObject*>(actor.Get())) {
    character->SetCharacterPosition(position);
    return;
  }

  actor->SetPosition(position);
}

} // namespace

GameWorld::GameWorld() = default;

void GameWorld::onEnter() {
  Scene::onEnter();
  EnsureDebugScene();

  if (!initialized_) {
    initialized_ = InitWorld();
  }

  RefreshDebugContext();
}

void GameWorld::onExit() {
  pendingCharacterMove_.reset();
  ClearSuppressedMoveArea();
  if (debugScene_) {
    debugScene_->ClearDebugContext();
    SceneManager::get().RemoveUIScene(debugScene_.Get());
  }

  Scene::onExit();
}

void GameWorld::Update(float deltaTime) {
  Scene::Update(deltaTime);
  ProcessPendingCharacterMove();
}

bool GameWorld::InitWorld() {
  RemoveAllChildren();
  townPathMap_.clear();
  townMap_.clear();
  mainActor_.Reset();
  pendingCharacterMove_.reset();
  ClearSuppressedMoveArea();
  curTown_ = -1;

  townPathMap_ = game::loadTownList();
  if (townPathMap_.empty()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: no town entries found");
    return false;
  }

  for (const auto& [townId, townPath] : townPathMap_) {
    auto town = MakePtr<GameTown>();
    if (!town->Init(townId, townPath)) {
      continue;
    }
    townMap_[townId] = town;
  }

  if (townMap_.empty()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: failed to create any town");
    return false;
  }

  int defaultTownId = kDefaultTownId;
  if (townMap_.find(defaultTownId) == townMap_.end()) {
    defaultTownId = townMap_.begin()->first;
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "GameWorld: default town %d missing, fallback to town %d",
                kDefaultTownId, defaultTownId);
  }

  if (!InitMainCharacter(defaultTownId)) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "GameWorld: falling back to empty town %d without main character",
                defaultTownId);
    AddCharacter(nullptr, defaultTownId);
  }

  return true;
}

bool GameWorld::InitMainCharacter(int townId) {
  auto mainCharacter = MakePtr<CharacterObject>();
  if (!mainCharacter->Construction(0)) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                 "GameWorld: failed to construct default main character");
    return false;
  }

  mainCharacter->EnableEventReceive();
  mainCharacter->SetEventPriority(-100);
  mainCharacter->SetInputEnabled(true);
  AddCharacter(mainCharacter, townId);
  return true;
}

void GameWorld::AddCharacter(RefPtr<Actor> actor, int townId) {
  auto it = townMap_.find(townId);
  if (it == townMap_.end()) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: town %d not found", townId);
    return;
  }

  mainActor_ = actor;
  curTown_ = townId;
  it->second->AddCharacter(actor);
  AddChild(it->second);
  RefreshDebugContext();
}

void GameWorld::MoveCharacter(RefPtr<Actor> actor, int townId, int area) {
  auto townIt = townMap_.find(townId);
  if (townIt == townMap_.end()) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "GameWorld: target town %d missing", townId);
    return;
  }

  ClearSuppressedMoveArea();

  int sourceTownId = curTown_;
  int sourceAreaId = -1;
  if (curTown_ != -1) {
    auto currentTown = townMap_.find(curTown_);
    if (currentTown != townMap_.end()) {
      sourceAreaId = currentTown->second->GetCurAreaIndex();
    }
  }

  RefPtr<GameMap> targetMap = townIt->second->GetArea(area);
  if (actor && targetMap && sourceTownId != -1 && sourceAreaId != -1) {
    bool foundTransitionEntry = false;
    const auto& moveAreaInfo = targetMap->GetMoveAreaInfo();
    for (size_t i = 0; i < moveAreaInfo.size(); ++i) {
      if (moveAreaInfo[i].town != sourceTownId || moveAreaInfo[i].area != sourceAreaId) {
        continue;
      }

      game::MoveAreaBounds bounds = targetMap->GetMovablePositionBounds(i);
      SetActorGroundPosition(actor, ComputeMoveAreaCenter(bounds));
      SetSuppressedMoveArea(targetMap.Get(), i);
      foundTransitionEntry = true;
      break;
    }

    if (!foundTransitionEntry) {
      SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                  "GameWorld: no matching move area in town %d area %d for source town %d area %d",
                  townId, area, sourceTownId, sourceAreaId);
    }
  } else if (actor && !targetMap) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "GameWorld: target area %d missing in town %d, use town fallback spawn",
                area, townId);
  }

  if (curTown_ != -1) {
    auto currentTown = townMap_.find(curTown_);
    if (currentTown != townMap_.end()) {
      RemoveChild(currentTown->second);
    }
  }

  curTown_ = townId;
  mainActor_ = actor;
  townIt->second->AddCharacter(actor, area);
  AddChild(townIt->second);
  RefreshDebugContext();
}

void GameWorld::RequestMoveCharacter(RefPtr<Actor> actor, int townId, int area) {
  if (!actor) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "GameWorld: ignore move request without actor");
    return;
  }

  pendingCharacterMove_ = PendingCharacterMove{actor, townId, area};
}

void GameWorld::UpdateMoveAreaSuppression(GameMap* map, size_t moveAreaIndex) {
  if (!suppressedMoveArea_) {
    return;
  }

  if (suppressedMoveArea_->map != map ||
      suppressedMoveArea_->moveAreaIndex != moveAreaIndex) {
    ClearSuppressedMoveArea();
  }
}

bool GameWorld::ShouldSuppressMoveArea(GameMap* map, size_t moveAreaIndex) const {
  return suppressedMoveArea_ && suppressedMoveArea_->map == map &&
         suppressedMoveArea_->moveAreaIndex == moveAreaIndex;
}

void GameWorld::ProcessPendingCharacterMove() {
  if (!pendingCharacterMove_) {
    return;
  }

  PendingCharacterMove move = *pendingCharacterMove_;
  pendingCharacterMove_.reset();
  MoveCharacter(move.actor, move.townId, move.area);
}

void GameWorld::EnsureDebugScene() {
  if (!debugScene_) {
    debugScene_ = MakePtr<GameDebugUIScene>();
  }

  SceneManager::get().RemoveUIScene(debugScene_.Get());
  SceneManager::get().PushUIScene(debugScene_);
}

void GameWorld::RefreshDebugContext() {
  if (!debugScene_) {
    return;
  }

  debugScene_->SetDebugContext(GetCurrentMap(), GetMainCharacter());
}

void GameWorld::SetSuppressedMoveArea(GameMap* map, size_t moveAreaIndex) {
  if (!map || moveAreaIndex == GameMap::kInvalidMoveAreaIndex) {
    ClearSuppressedMoveArea();
    return;
  }

  suppressedMoveArea_ = SuppressedMoveArea{map, moveAreaIndex};
}

void GameWorld::ClearSuppressedMoveArea() {
  suppressedMoveArea_.reset();
}

Actor* GameWorld::GetMainActor() const {
  return mainActor_.Get();
}

CharacterObject* GameWorld::GetMainCharacter() const {
  return dynamic_cast<CharacterObject*>(mainActor_.Get());
}

GameTown* GameWorld::GetCurrentTown() const {
  auto it = townMap_.find(curTown_);
  if (it == townMap_.end()) {
    return nullptr;
  }

  return it->second.Get();
}

GameMap* GameWorld::GetCurrentMap() const {
  GameTown* town = GetCurrentTown();
  if (!town) {
    return nullptr;
  }

  RefPtr<GameMap> area = town->GetCurrentArea();
  return area.Get();
}

GameWorld* GameWorld::GetWorld() {
  return dynamic_cast<GameWorld*>(SceneManager::get().GetCurrentScene());
}

} // namespace frostbite2D
