#pragma once
#include "BoneAnimatedModelNew.h"
#include "render/Renderer.h"
#include "render/TextureManager.h"
#include "SparkEmitter.h"
#include <functional>
#include <memory>
#include <map>
#include <string>
#include <cstdint>
#include <vector>
#include <unordered_map>

namespace ZL {

class TextRenderer;

enum class AnimationState {
    STAND = 0,
    WALK = 1,
    STAND_TO_ACTION = 2,
    ACTION_ATTACK = 3,
    ACTION_ATTACK_2 = 4,
    ACTION_IDLE = 5,
    ACTION_TO_STAND = 6,
	ACTION_TO_DEATH = 7,
    DEATH_IDLE = 8,
};

class Character {
public:
    using PathPlanner = std::function<std::vector<Eigen::Vector3f>(
        const Eigen::Vector3f& start,
        const Eigen::Vector3f& end)>;

    void loadAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = "");
    void loadBinaryAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = "");

    // Assigns the given texture to a specific mesh (by name, as defined in the animation file).
    void setTexture(const std::string& meshName, std::shared_ptr<Texture> texture);
    // Assigns the given texture to every mesh in every loaded animation.
    // Call AFTER loading animations so the mesh names are known.
    void setTexture(std::shared_ptr<ZL::Texture> texture);
    void update(int64_t deltaMs);
    // onArrived is called once when the character reaches this target.
    // From Python you can chain further commands (walk, wait, trigger others).
    void setTarget(const Eigen::Vector3f& target,
                   std::function<void()> onArrived = nullptr);

    void setPathPlanner(PathPlanner planner);
    void draw(Renderer& renderer);
    void drawShadowDepth(Renderer& renderer);
    void drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);

	// Character-to-character collision (XZ-plane). Used by Location to keep
	// player/NPCs from walking through each other.
	float collisionRadius = 0.45f;

	// Movement/path introspection used for dynamic replanning.
	bool isMoving() const;
	const Eigen::Vector3f& getRequestedWalkTarget() const { return requestedWalkTarget; }
	Eigen::Vector3f getCurrentNavigationTarget() const;
	void forceReplan();
	// Cancels any active walk target and clears waypoints so the character
	// stays at its current position. Used when an external push moves the
	// character and we don't want it to walk back to where it was.
	void stopInPlace();


 	// attackDirection is a world-space horizontal vector pointing from the
 	// attacker toward this character — i.e. the direction the hit pushes
 	// them. A zero vector disables directional sparks (isotropic burst).
 	void applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection = Eigen::Vector3f::Zero());

	// Configures the per-character hit-spark emitter with the shared spark
	// texture. Safe to call once per character after construction.
	void setupHitSparks(std::shared_ptr<Texture> sparkTexture);

	// Call per-frame between update() and draw(), with the active camera
	// view matrix (world → eye). The emitter billboards and sorts its
	// particles against that camera before its VBO is uploaded.
	void prepareHitSparksForDraw(const Eigen::Matrix4f& cameraViewMatrix);

	// Draws the NPC name floating above this character in 2D screen space.
	// No-op for the player, for dead characters, or if npcName is empty.
	// The text is green for peaceful (canAttack == false) NPCs, red otherwise.
	void drawName(TextRenderer& textRenderer,
	              const Eigen::Matrix4f& cameraViewMatrix,
	              const Eigen::Matrix4f& projectionMatrix);

	// Draws a horizontal health bar above this character in 2D screen space.
	// Visible only for the player and hostile NPCs (canAttack == true), and
	// only while hp > 0. The left portion is green (hp / initialHp) and the
	// remainder is red.
	void drawHealthBar(Renderer& renderer,
	                   const Eigen::Matrix4f& cameraViewMatrix,
	                   const Eigen::Matrix4f& projectionMatrix);

    // Public: read by Game for camera tracking and ray-cast origin
    Eigen::Vector3f position = Eigen::Vector3f(0.f, 0.f, 0.f);
    float facingAngle = 0.0f;
    float targetFacingAngle = 0.0f;


    // Per-character tuning — set after construction, before first update
    float walkSpeed = 3.0f;
    float rotationSpeed = 4.0f;
    float modelScale = 0.12f;
    // Applied after scale, fixes model-space orientation (e.g. Blender Z-up exports)
    Eigen::Quaternionf modelCorrectionRotation = Eigen::Quaternionf::Identity();

    std::string npcId;
    std::string npcName;
    float hp = 200.f;
    float initialHp = 200.f;

    // The position the NPC should return to if pushed too far away.
    // Initialised to the spawn position; updated whenever a script walk command is issued.
    Eigen::Vector3f homePosition = Eigen::Vector3f::Zero();
    // Maximum allowed drift from homePosition before a return walk is triggered (metres).
    // Set to 0 to disable drift recovery for this character.
    float maxHomeDrift = 3.0f;


    int battle_state = 0;
    bool resetAnim = false;
    int attack = 0;
    AnimationState currentState = AnimationState::STAND;
    float attack_cooldown = 0.f;
    bool canAttack = false;
    Character* attackTarget = nullptr;
    // While standing still, smoothly rotate once to face this character.
    // Auto-cleared by update() the moment this character finishes rotating,
    // so it acts as a one-shot orient (e.g. an NPC turning to the player at
    // the start of a conversation, then holding that pose).
    Character* faceTarget = nullptr;
    bool isPlayer = false;
    bool enabled = true;
    bool useGpuSkinning = true;
    //bool useGpuSkinning = false;

    float interactionRadius = 0.0f;

    VertexRenderStruct weaponMesh;
    std::shared_ptr<Texture> weaponTexture;
    Eigen::Matrix3f weaponInitialRotation = Eigen::Matrix3f::Identity();
    Eigen::Vector3f weaponInitialPosition = Eigen::Vector3f::Zero();
    std::string weaponAttachBoneName = "RightHand";
    bool showWeapon = true;

    SparkEmitter hitSparkEmitter;

    float slowMoTimeRemaining = 0.0f;
    float slowMoTransitionTime = 0.5f;
    float slowMoActiveTime = 6.0f;
    float getSpeedMultiplier(float transitionProgress) const;

    // Called once when the death animation finishes and the character enters DEATH_IDLE.
    std::function<void()> onDeathAnimComplete;

private:

    std::map<AnimationState, BoneAnimationDataNew> animations;
    VertexRenderStruct modelMutable;
    VertexRenderStruct healthBarMesh;
    std::unordered_map<std::string, std::shared_ptr<Texture>> meshTextures;

    Eigen::Vector3f walkTarget = Eigen::Vector3f(0.f, 0.f, 0.f);
    Eigen::Vector3f requestedWalkTarget = Eigen::Vector3f(0.f, 0.f, 0.f);
    std::vector<Eigen::Vector3f> pathWaypoints;
    size_t currentWaypointIndex = 0;
    PathPlanner pathPlanner;
    std::function<void()> onArrivedCallback;

    static constexpr float WALK_THRESHOLD = 0.05f;
    static constexpr float TARGET_REPLAN_THRESHOLD = 0.25f;
    static constexpr float HOME_DRIFT_CHECK_INTERVAL = 5.0f;

    float homeDriftCheckTimer = HOME_DRIFT_CHECK_INTERVAL;

    // Returns the animation state to actually play/draw, falling back to IDLE
    // if the requested state has no loaded animation.
    AnimationState resolveActiveState() const;

    bool prepareGpuSkinning();

    // Draws the weapon mesh attached to weaponAttachBoneName.
    // Caller must ensure the right shader is active with matching uniforms.
    void drawAttachedWeapon(Renderer& renderer);

    // GPU skinning: draw using shader-based skinning
    void drawGpuSkinning(Renderer& renderer);
    // Shadow: draw into depth map (no texture, no projection push)
    void drawShadowDepthGpuSkinning(Renderer& renderer);
    void drawShadowDepthCpu(Renderer& renderer);
    // Shadow: draw with shadow-aware shaders
    void drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
    void drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
};

} // namespace ZL
