#pragma once

#include "character/CharacterDataLoader.h"
#include "character/CharacterEquipmentManager.h"
#include <frostbite2D/2d/actor.h>
#include <frostbite2D/2d/canvas_actor.h>
#include <frostbite2D/animation/animation.h>
#include <frostbite2D/base/RefPtr.h>
#include <functional>
#include <map>
#include <string>
#include <unordered_set>
#include <vector>

namespace frostbite2D {

class CharacterObject;

class CharacterAnimation : public Actor {
public:
  using ActionAnimationList = std::map<std::string, std::vector<RefPtr<Animation>>>;
  using ActionFrameFlagCallback = std::function<void(int)>;
  using ActionEndCallback = std::function<void()>;

  struct CompositeFrameInfo {
    bool valid = false;
    Rect localBounds;
    Vec2 originInTexture = Vec2::Zero();
    Vec2 groundAnchorInTexture = Vec2::Zero();
    int width = 1;
    int height = 1;
  };

  bool Init(CharacterObject* parent,
            const character::CharacterConfig& config,
            const CharacterEquipmentManager& equipmentManager);
  void Update(float deltaTime) override;
  void Render() override;

  bool SetAction(const std::string& actionName);
  void SetDirection(int direction);
  const std::string& GetCurrentAction() const { return currentActionTag_; }
  bool HasAction(const std::string& actionName) const {
    return actionAnimations_.count(actionName) > 0;
  }
  std::string DescribeAvailableActions() const;
  bool IsCurrentActionFinished() const;
  int GetCurrentFrameIndex() const;
  int GetCurrentFrameCount() const;
  float GetCurrentNormalizedProgress() const;
  animation::AniFrame GetCurrentFrameInfo() const;
  void SetActionFrameFlagCallback(ActionFrameFlagCallback callback);
  void SetActionEndCallback(ActionEndCallback callback);
  bool EnsureCompositeTextureReady();
  bool HasCompositeTexture() const;
  Ptr<Texture> GetCompositeTexture() const;
  Vec2 GetCompositeTextureSize() const;
  Vec2 GetCompositeOriginInTexture() const;
  Vec2 GetCompositeGroundAnchorInTexture() const;
  uint64 GetCompositeVersion() const { return compositeVersion_; }

private:
  static std::string FormatImgPath(std::string path, Animation::ReplaceData data);
  Animation* GetPrimaryAnimation(const std::string& actionName) const;
  Animation* GetCurrentPrimaryAnimation() const;
  void RefreshRuntimeCallbacks();
  bool shouldSkipMissingAnimation(const std::string& aniPath);
  void BuildCompositeActionFrames();
  CompositeFrameInfo ComputeCompositeFrameInfo(const std::vector<RefPtr<Animation>>& animations,
                                               int direction) const;
  CompositeFrameInfo GetCurrentCompositeFrameInfo() const;
  uint64 CaptureCurrentRenderSignature() const;
  void MarkCompositeDirty();
  void UpdateCompositeCamera();
  void RefreshCompositeTextureIfNeeded();
  void RenderCurrentActionToCompositeCanvas();

  void CreateAnimationBySlot(const std::string& actionName,
                             const std::string& slotName,
                             const std::string& actionPath,
                             const character::CharacterConfig& config,
                             const CharacterEquipmentManager& equipmentManager);

  CharacterObject* parent_ = nullptr;
  ActionAnimationList actionAnimations_;
  std::string currentActionTag_;
  int direction_ = 1;
  ActionFrameFlagCallback actionFrameFlagCallback_;
  ActionEndCallback actionEndCallback_;
  std::unordered_set<std::string> missingAnimationPaths_;
  size_t skippedMissingAnimationCount_ = 0;
  RefPtr<CanvasActor> compositeCanvas_ = nullptr;
  std::map<std::string, CompositeFrameInfo> compositeFrameInfoByAction_;
  bool compositeDirty_ = true;
  uint64 compositeVersion_ = 0;
  uint64 lastCompositeSignature_ = 0;
};

} // namespace frostbite2D
