#ifndef UNIT_WORLD_STATE_HPP
#define UNIT_WORLD_STATE_HPP

#include "entity_health.hpp"
#include "goap/actor_world_state.hpp"
#include <godot_cpp/classes/navigation_agent3d.hpp>
#include <godot_cpp/classes/node3d.hpp>

namespace gd = godot;

class Unit;

struct CachedWorldProperty {
    gd::Variant value{};
    double auto_invalidate_time{0.0};
};

class UnitWorldState : public goap::ActorWorldState {
    GDCLASS(UnitWorldState, goap::ActorWorldState);
    static void _bind_methods();
public:
    virtual void _enter_tree() override;
    virtual gd::Variant get_world_property(gd::String property) override;
    bool get_can_see_target();
    bool get_can_see_node(gd::Node3D *node) const;
    bool get_is_at_target() const;
    bool get_has_target() const;
    bool get_is_target_dead() const;
    bool get_is_target_unit() const;
    bool get_is_target_enemy() const;
    bool get_is_unit_enemy(Unit *unit) const;
    bool get_is_in_melee_range() const;
    bool get_is_health_safe() const;
    gd::Vector3 get_parent_global_position() const;
    gd::Vector3 get_target_global_position() const;

    void set_target_node(gd::Node3D *node);
    gd::Node3D *get_target_node() const;
private:
    void cache_property(gd::String property, gd::Variant value, double auto_invalidate_time);
    void target_destroyed(gd::Node3D *target);
private:
    gd::Callable const target_node_exited_tree{callable_mp(this, &UnitWorldState::target_destroyed)};
protected:
    Unit *parent_unit{nullptr};
    EntityHealth *health{nullptr};
    gd::NavigationAgent3D *agent{nullptr};
    gd::Node3D *target_node{nullptr};
    gd::Node3D *eye_location{nullptr};
    gd::HashMap<gd::String, CachedWorldProperty> cache;
};

#endif // !UNIT_WORLD_STATE_HPP
