#include "character/CharacterObject.h"
#include "character/CharacterShadowActor.h"
#include "common/math/GameMath.h"
#include "map/GameMap.h"
#include "world/GameWorld.h"
#include <SDL2/SDL.h>
#include <frostbite2D/core/application.h>
#include <frostbite2D/utils/startup_trace.h>
#include <sstream>
#include <utility>

namespace frostbite2D {
namespace {

const char* StateIdToString(CharacterStateId stateId) {
  switch (stateId) {
    case CharacterStateId::Idle:
      return "Idle";
    case CharacterStateId::Move:
      return "Move";
    case CharacterStateId::Jump:
      return "Jump";
    case CharacterStateId::Fall:
      return "Fall";
    case CharacterStateId::Landing:
      return "Landing";
    case CharacterStateId::Action:
      return "Action";
    case CharacterStateId::Hurt:
      return "Hurt";
    case CharacterStateId::Dead:
      return "Dead";
    default:
      return "<unknown>";
  }
}

const char* NonEmptyOrPlaceholder(const std::string& value, const char* placeholder) {
  return value.empty() ? placeholder : value.c_str();
}

Vec3 ToWorldVector(const CharacterWorldPosition& position) {
  return Vec3(static_cast<float>(position.x), static_cast<float>(position.y),
              static_cast<float>(position.z));
}

constexpr int kShadowNormalLayerZBias = 1000000;

} // namespace

CharacterObject::~CharacterObject() {
  DetachShadowActor();
  shadowActor_ = nullptr;
}

bool CharacterObject::Construction(int jobId) {
  ScopedStartupTrace startupTrace("CharacterObject::Construction");
  // Reset all runtime state before rebuilding the character from config.
  EnableEventReceive();
  DetachShadowActor();
  RemoveAllChildren();
  animationManager_ = nullptr;
  shadowActor_ = nullptr;
  shadowAttachedMap_ = nullptr;
  config_.reset();
  currentAction_.clear();
  actionLibrary_ = CharacterActionLibrary();
  commandBuffer_.Clear();
  currentIntent_ = CharacterIntent();
  runtimeBlackboard_.Clear();
  pendingActionContext_.Clear();
  currentActionContext_.Clear();
  stateMachine_.Reset();
  motor_ = CharacterMotor();
  status_ = CharacterStatus();
  lastDeltaTime_ = 0.0f;
  inputEnabled_ = true;

  auto config = [&]() {
    ScopedStartupTrace stageTrace("character::loadCharacterConfig");
    return character::loadCharacterConfig(jobId);
  }();
  if (!config) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                 "CharacterObject: failed to load job %d config", jobId);
    return false;
  }

  jobId_ = jobId;
  growType_ = -1;
  direction_ = 1;
  config_ = *config;
  stateMachine_.Configure(*config_);
  {
    ScopedStartupTrace stageTrace("CharacterEquipmentManager::Init");
    equipmentManager_.Init(config_->baseJobConfig);
  }
  if (auto actionLibrary = [&]() {
        ScopedStartupTrace stageTrace("loadCharacterActionLibrary");
        return loadCharacterActionLibrary(*config_);
      }()) {
    actionLibrary_ = *actionLibrary;
  }

  animationManager_ = MakePtr<CharacterAnimation>();
  {
    ScopedStartupTrace stageTrace("CharacterAnimation::Init");
    if (!animationManager_->Init(this, *config_, equipmentManager_)) {
      ReportFatalCharacterError("CharacterObject::Construction",
                                "no usable animation tags were loaded");
      return false;
    }
  }
  AddChild(animationManager_);

  shadowActor_ = MakePtr<CharacterShadowActor>();
  shadowAttachedMap_ = nullptr;

  {
    ScopedStartupTrace stageTrace("CharacterObject initial action setup");
    if (!RequireAction("idle", "CharacterObject::Construction")) {
      return false;
    }
    if (!SetActionStrict("rest", "CharacterObject::Construction", "idle")) {
      return false;
    }
  }

  SetWorldPosition({});
  SetFacing(1);
  return true;
}

void CharacterObject::SetAction(const std::string& actionName) {
  SetActionStrict(actionName, "CharacterObject::SetAction", std::string());
}

bool CharacterObject::SetActionStrict(const std::string& actionName,
                                      const char* phase,
                                      const std::string& requestedActionId) {
  if (actionName.empty()) {
    ReportFatalCharacterError(phase, "requested animation tag is empty",
                              requestedActionId, actionName);
    return false;
  }
  if (!animationManager_) {
    ReportFatalCharacterError(phase, "animation manager is not initialized",
                              requestedActionId, actionName);
    return false;
  }
  if (!animationManager_->HasAction(actionName)) {
    ReportFatalCharacterError(phase, "requested animation tag is not loaded",
                              requestedActionId, actionName);
    return false;
  }
  if (!animationManager_->SetAction(actionName)) {
    ReportFatalCharacterError(phase, "failed to activate requested animation tag",
                              requestedActionId, actionName);
    return false;
  }

  currentAction_ = actionName;
  return true;
}

void CharacterObject::SetDirection(int direction) {
  direction_ = direction >= 0 ? 1 : -1;
  if (animationManager_) {
    animationManager_->SetDirection(direction_);
  }
}

void CharacterObject::SetCharacterPosition(const Vec2& pos) {
  motor_.SetGroundPosition(pos);
  SyncActorPositionFromWorld();
}

void CharacterObject::SetWorldPosition(const CharacterWorldPosition& pos) {
  motor_.SetWorldPosition(pos);
  SyncActorPositionFromWorld();
}

void CharacterObject::SyncActorPositionFromWorld() {
  const CharacterWorldPosition& worldPosition = motor_.position;
  SetPosition(worldPosition.ToScreenPosition());
  SetZOrder(worldPosition.y);
}

void CharacterObject::DetachShadowActor() {
  if (shadowActor_ && shadowActor_->GetParent()) {
    shadowActor_->GetParent()->RemoveChild(shadowActor_);
  }
  shadowAttachedMap_ = nullptr;
}

void CharacterObject::SyncShadowAttachment() {
  if (!shadowActor_) {
    return;
  }

  GameMap* map = FindOwningMap();
  if (!map) {
    DetachShadowActor();
    return;
  }

  if (shadowAttachedMap_ == map && shadowActor_->GetParent()) {
    return;
  }

  if (shadowActor_->GetParent()) {
    shadowActor_->GetParent()->RemoveChild(shadowActor_);
  }

  map->AddObjectToLayer("normal", shadowActor_);
  shadowAttachedMap_ = map;
}

void CharacterObject::SyncShadowPresentation() {
  if (!shadowActor_) {
    return;
  }

  CharacterShadowFrameSnapshot snapshot;
  snapshot.groundScreenPosition = motor_.position.ToGroundPosition();
  snapshot.zOrder = motor_.position.y - kShadowNormalLayerZBias;


  if (HasCompositeTexture()) {
    snapshot.visible = true;
    snapshot.texture = GetCompositeTexture();
    snapshot.textureSize = GetCompositeTextureSize();
    snapshot.groundAnchorInTexture = GetCompositeGroundAnchorInTexture();
    snapshot.textureVersion = GetCompositeTextureVersion();
  }

  shadowActor_->ApplyFrameSnapshot(snapshot);
}

GameMap* CharacterObject::FindOwningMap() const {
  Actor* node = GetParent();
  while (node) {
    if (auto* map = dynamic_cast<GameMap*>(node)) {
      return map;
    }
    node = node->GetParent();
  }
  return nullptr;
}

void CharacterObject::ApplyMapMovementConstraints(
    const CharacterWorldPosition& previousPosition) {
  GameMap* map = FindOwningMap();
  if (!map) {
    return;
  }

  Vec3 previousWorldPos = ToWorldVector(previousPosition);
  Vec3 worldOffset(static_cast<float>(motor_.position.x - previousPosition.x),
                   static_cast<float>(motor_.position.y - previousPosition.y),
                   static_cast<float>(motor_.position.z - previousPosition.z));
  Vec3 resolvedWorldPos = map->CheckIsItMovable(previousWorldPos, worldOffset);
  motor_.position.x = gameMath::RoundWorldCoordinate(resolvedWorldPos.x);
  motor_.position.y = gameMath::RoundWorldCoordinate(resolvedWorldPos.y);
  motor_.position.z = gameMath::RoundWorldCoordinate(resolvedWorldPos.z);
}

void CharacterObject::QueueMapTransitionIfNeeded() {
  GameMap* map = FindOwningMap();
  if (!map) {
    return;
  }

  GameWorld* world = GameWorld::GetWorld();
  if (!world) {
    return;
  }

  size_t moveAreaIndex = map->FindMoveAreaIndex(ToWorldVector(motor_.position));
  world->UpdateMoveAreaSuppression(map, moveAreaIndex);
  if (moveAreaIndex == GameMap::kInvalidMoveAreaIndex ||
      world->ShouldSuppressMoveArea(map, moveAreaIndex)) {
    return;
  }

  const auto& moveAreaInfo = map->GetMoveAreaInfo();
  if (moveAreaIndex >= moveAreaInfo.size()) {
    return;
  }

  const GameMap::MapMoveArea& target = moveAreaInfo[moveAreaIndex];
  world->RequestMoveCharacter(RefPtr<Actor>(this), target.town, target.area);
}

void CharacterObject::PushCommand(const CharacterCommand& command) {
  commandBuffer_.Submit(command);
}

CharacterActionContextData& CharacterObject::PreparePendingActionContext(
    const std::string& requestedActionId,
    const std::string& sourceActionId,
    CharacterStateId sourceStateId) {
  pendingActionContext_.Clear();
  pendingActionContext_.valid = true;
  pendingActionContext_.requestedActionId = requestedActionId;
  pendingActionContext_.sourceActionId = sourceActionId;
  pendingActionContext_.sourceStateId = sourceStateId;
  pendingActionContext_.hasSourceStateId =
      sourceStateId != CharacterStateId::Idle || !sourceActionId.empty();
  return pendingActionContext_;
}

void CharacterObject::ClearPendingActionContext() {
  pendingActionContext_.Clear();
}

const CharacterActionContextData* CharacterObject::GetPendingActionContext() const {
  return pendingActionContext_.valid ? &pendingActionContext_ : nullptr;
}

CharacterActionContextData* CharacterObject::GetPendingActionContextMutable() {
  return pendingActionContext_.valid ? &pendingActionContext_ : nullptr;
}

const CharacterActionContextData* CharacterObject::GetCurrentActionContext() const {
  return currentActionContext_.valid ? &currentActionContext_ : nullptr;
}

CharacterActionContextData* CharacterObject::GetCurrentActionContextMutable() {
  return currentActionContext_.valid ? &currentActionContext_ : nullptr;
}

void CharacterObject::ClearCurrentActionContext() {
  currentActionContext_.Clear();
}

void CharacterObject::CommitPendingActionContext(
    const std::string& defaultRequestedActionId,
    const std::string& defaultSourceActionId,
    CharacterStateId defaultSourceStateId) {
  currentActionContext_.Clear();
  if (pendingActionContext_.valid) {
    currentActionContext_ = pendingActionContext_;
  } else {
    currentActionContext_.valid = true;
  }

  if (currentActionContext_.requestedActionId.empty()) {
    currentActionContext_.requestedActionId = defaultRequestedActionId;
  }
  if (currentActionContext_.sourceActionId.empty()) {
    currentActionContext_.sourceActionId = defaultSourceActionId;
  }
  if (!currentActionContext_.hasSourceStateId) {
    currentActionContext_.SetSourceStateId(defaultSourceStateId);
  }

  pendingActionContext_.Clear();
}

void CharacterObject::ApplyHit(const HitContext& hit) {
  if (status_.invincible) {
    return;
  }
  if (status_.superArmor && !hit.ignoreInterrupt) {
    return;
  }
  if (!hit.ignoreInterrupt && !stateMachine_.CanBeInterrupted()) {
    return;
  }

  const CharacterActionDefinition* hurtAction = RequireAction(
      hit.hurtAction, "CharacterObject::ApplyHit");
  if (!hurtAction) {
    return;
  }

  motor_.verticalVelocity = hit.launchZVelocity;
  if (hit.launchZVelocity > 0.0f) {
    motor_.grounded = false;
  }
  stateMachine_.ForceHurt(*this, hurtAction);
}

void CharacterObject::Update(float deltaTime) {
  Actor::Update(deltaTime);
  SyncShadowAttachment();
  SyncShadowPresentation();
}

void CharacterObject::PrepareRenderFrame() {
  if (animationManager_) {
    animationManager_->EnsureCompositeTextureReady();
  }
  SyncShadowPresentation();
}

void CharacterObject::OnUpdate(float deltaTime) {
  // Fixed update order keeps input, state transitions, and motion deterministic.
  lastDeltaTime_ = deltaTime;
  commandBuffer_.Advance(deltaTime);
  inputRouter_.EmitCommands(commandBuffer_);
  currentIntent_ = commandBuffer_.BuildIntent();
  stateMachine_.Update(*this, commandBuffer_, currentIntent_, deltaTime);
  CharacterWorldPosition previousPosition = motor_.position;
  motor_.Update(deltaTime);
  ApplyMapMovementConstraints(previousPosition);
  QueueMapTransitionIfNeeded();
  SyncActorPositionFromWorld();
  SetFacing(motor_.facing);
}

void CharacterObject::OnAdded(Actor* parent) {
  (void)parent;
  SyncShadowAttachment();
  SyncShadowPresentation();
}

bool CharacterObject::OnEvent(const Event& event) {
  if (!IsEventReceiveEnabled() || !inputEnabled_) {
    return false;
  }

  switch (event.getType()) {
    case EventType::KeyDown: {
      const auto& keyEvent = static_cast<const KeyEvent&>(event);
      if (keyEvent.isRepeat()) {
        return false;
      }
      return inputRouter_.OnKeyDown(keyEvent);
    }
    case EventType::KeyUp:
      return inputRouter_.OnKeyUp(static_cast<const KeyUpEvent&>(event));
    case EventType::JoystickAxis:
      return inputRouter_.OnJoystickAxis(static_cast<const JoystickAxisEvent&>(event));
    case EventType::JoystickButtonDown:
      return inputRouter_.OnJoystickButtonDown(
          static_cast<const JoystickButtonDownEvent&>(event));
    case EventType::JoystickButtonUp:
      return inputRouter_.OnJoystickButtonUp(
          static_cast<const JoystickButtonUpEvent&>(event));
    default:
      return false;
  }
}

const CharacterActionDefinition* CharacterObject::FindAction(
    const std::string& actionId) const {
  return actionLibrary_.FindAction(actionId);
}

const CharacterActionDefinition* CharacterObject::RequireAction(
    const std::string& actionId,
    const char* phase) const {
  const CharacterActionDefinition* action = FindAction(actionId);
  if (!action) {
    ReportFatalCharacterError(phase, "requested logical action does not exist", actionId);
    return nullptr;
  }
  return action;
}

void CharacterObject::PlayAnimationTag(const std::string& actionTag) {
  SetActionStrict(actionTag, "CharacterObject::PlayAnimationTag", std::string());
}

bool CharacterObject::HasAnimationTag(const std::string& actionTag) const {
  return animationManager_ && animationManager_->HasAction(actionTag);
}

bool CharacterObject::IsCurrentAnimationFinished() const {
  return animationManager_ && animationManager_->IsCurrentActionFinished();
}

int CharacterObject::GetCurrentAnimationFrameIndex() const {
  return animationManager_ ? animationManager_->GetCurrentFrameIndex() : 0;
}

int CharacterObject::GetCurrentAnimationFrameCount() const {
  return animationManager_ ? animationManager_->GetCurrentFrameCount() : 0;
}

float CharacterObject::GetCurrentAnimationProgressNormalized() const {
  return animationManager_ ? animationManager_->GetCurrentNormalizedProgress() : 0.0f;
}

animation::AniFrame CharacterObject::GetCurrentAnimationFrameInfo() const {
  return animationManager_ ? animationManager_->GetCurrentFrameInfo() : animation::AniFrame();
}

bool CharacterObject::HasCompositeTexture() const {
  return animationManager_ && animationManager_->HasCompositeTexture();
}

Ptr<Texture> CharacterObject::GetCompositeTexture() const {
  return animationManager_ ? animationManager_->GetCompositeTexture() : nullptr;
}

Vec2 CharacterObject::GetCompositeTextureSize() const {
  return animationManager_ ? animationManager_->GetCompositeTextureSize() : Vec2::Zero();
}

Vec2 CharacterObject::GetCompositeOriginInTexture() const {
  return animationManager_ ? animationManager_->GetCompositeOriginInTexture()
                           : Vec2::Zero();
}

Vec2 CharacterObject::GetCompositeGroundAnchorInTexture() const {
  return animationManager_ ? animationManager_->GetCompositeGroundAnchorInTexture()
                           : Vec2::Zero();
}

uint64 CharacterObject::GetCompositeTextureVersion() const {
  return animationManager_ ? animationManager_->GetCompositeVersion() : 0;
}

void CharacterObject::SetAnimationFrameFlagCallback(
    CharacterAnimation::ActionFrameFlagCallback callback) {
  if (animationManager_) {
    animationManager_->SetActionFrameFlagCallback(std::move(callback));
  }
}

void CharacterObject::SetAnimationEndCallback(
    CharacterAnimation::ActionEndCallback callback) {
  if (animationManager_) {
    animationManager_->SetActionEndCallback(std::move(callback));
  }
}

std::string CharacterObject::DescribeConfiguredAnimationTags() const {
  if (!config_ || config_->animationPath.empty()) {
    return "<none>";
  }

  std::ostringstream stream;
  bool first = true;
  for (const auto& [actionTag, path] : config_->animationPath) {
    (void)path;
    if (!first) {
      stream << ", ";
    }
    stream << actionTag;
    first = false;
  }
  return stream.str();
}

void CharacterObject::ReportFatalCharacterError(
    const char* phase,
    const char* reason,
    const std::string& requestedActionId,
    const std::string& requestedAnimationTag) const {
  std::ostringstream stream;
  stream << "CharacterObject fatal error: "
         << (reason && reason[0] != '\0' ? reason : "unknown error") << '\n'
         << "  phase: " << (phase && phase[0] != '\0' ? phase : "<unknown>") << '\n'
         << "  jobId: " << jobId_ << '\n'
         << "  jobTag: "
         << (config_ ? NonEmptyOrPlaceholder(config_->jobTag, "<empty>") : "<config not loaded>")
         << '\n'
         << "  state: " << StateIdToString(stateMachine_.GetState()) << '\n'
         << "  currentActionId: "
         << NonEmptyOrPlaceholder(stateMachine_.GetCurrentActionId(), "<none>") << '\n'
         << "  currentAnimationTag: " << NonEmptyOrPlaceholder(currentAction_, "<none>") << '\n'
         << "  currentActionContext.requestedActionId: "
         << NonEmptyOrPlaceholder(
                currentActionContext_.valid ? currentActionContext_.requestedActionId
                                            : std::string(),
                "<none>")
         << '\n'
         << "  currentActionContext.sourceActionId: "
         << NonEmptyOrPlaceholder(
                currentActionContext_.valid ? currentActionContext_.sourceActionId
                                            : std::string(),
                "<none>")
         << '\n'
         << "  pendingActionContext.requestedActionId: "
         << NonEmptyOrPlaceholder(
                pendingActionContext_.valid ? pendingActionContext_.requestedActionId
                                            : std::string(),
                "<none>")
         << '\n'
         << "  pendingActionContext.sourceActionId: "
         << NonEmptyOrPlaceholder(
                pendingActionContext_.valid ? pendingActionContext_.sourceActionId
                                            : std::string(),
                "<none>")
         << '\n'
         << "  requestedActionId: "
         << NonEmptyOrPlaceholder(requestedActionId, "<none>") << '\n'
         << "  requestedAnimationTag: "
         << NonEmptyOrPlaceholder(requestedAnimationTag, "<empty>") << '\n'
         << "  loadedActionIds: " << actionLibrary_.DescribeActionIds() << '\n'
         << "  configuredAnimationTags: " << DescribeConfiguredAnimationTags() << '\n'
         << "  loadedAnimationTags: "
         << (animationManager_ ? animationManager_->DescribeAvailableActions()
                               : std::string("<animation manager not initialized>"));

  SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", stream.str().c_str());
  Application::get().quit();
}

void CharacterObject::RunStateHook(const std::string& phase,
                                   CharacterStateId stateId,
                                   const CharacterActionDefinition* action) {
  (void)phase;
  (void)stateId;
  (void)action;
}

void CharacterObject::SetFacing(int direction) {
  motor_.facing = direction >= 0 ? 1 : -1;
  SetDirection(motor_.facing);
}

} // namespace frostbite2D
