#pragma once

#include <cmath>
#include <optional>
#include <string>

#include "adapters/il2cpp/Il2CppObjectInstance.h"
#include "adapters/unity/bindings/UnityTypes.h"
#include "foundation/diagnostics/OnceLogger.h"

namespace ta::adapters::eft::bindings
{
    namespace detail
    {
        inline std::optional<float> scope_zoom_error(const char* key, const char* message)
        {
            ta::foundation::diagnostics::OnceLogger::error("AIMBOT", key, message);
            return std::nullopt;
        }
    }

    inline std::optional<float> read_scope_zoom(
        ta::adapters::il2cpp::Il2CppObjectInstance& player)
    {
        const auto get_hands_controller =
            player.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_HandsController", 0);
        if (!get_hands_controller)
            return detail::scope_zoom_error(
                "scope-hands-controller-method",
                "Scope zoom read failed: get_HandsController unavailable");

        Il2CppObject* hands_controller_object =
            get_hands_controller(player.get_instance());
        if (!hands_controller_object)
            return 1.0f;
        if (!hands_controller_object->klass || !hands_controller_object->klass->name)
            return detail::scope_zoom_error(
                "scope-hands-controller-class",
                "Scope zoom read failed: hands controller class unavailable");

        if (std::string(hands_controller_object->klass->name) != "FirearmController")
            return 1.0f;

        ta::adapters::il2cpp::Il2CppObjectInstance firearm_controller(hands_controller_object);
        const auto get_is_aiming =
            firearm_controller.get_method<bool(*)(Il2CppObject*)>("get_IsAiming", 0);
        if (!get_is_aiming)
            return detail::scope_zoom_error(
                "scope-is-aiming-method",
                "Scope zoom read failed: get_IsAiming unavailable");
        if (!get_is_aiming(hands_controller_object))
            return 1.0f;

        const auto get_procedural_weapon_animation =
            player.get_method<Il2CppObject*(*)(Il2CppObject*)>(
                "get_ProceduralWeaponAnimation", 0);
        if (!get_procedural_weapon_animation)
            return detail::scope_zoom_error(
                "scope-procedural-animation-method",
                "Scope zoom read failed: get_ProceduralWeaponAnimation unavailable");

        Il2CppObject* procedural_weapon_animation_object =
            get_procedural_weapon_animation(player.get_instance());
        if (!procedural_weapon_animation_object)
            return detail::scope_zoom_error(
                "scope-procedural-animation-null",
                "Scope zoom read failed: ProceduralWeaponAnimation is null while aiming");

        ta::adapters::il2cpp::Il2CppObjectInstance procedural_weapon_animation(
            procedural_weapon_animation_object);
        const auto get_current_aiming_mod =
            procedural_weapon_animation.get_method<Il2CppObject*(*)(Il2CppObject*)>(
                "get_CurrentAimingMod", 0);
        if (!get_current_aiming_mod)
            return detail::scope_zoom_error(
                "scope-current-mod-method",
                "Scope zoom read failed: get_CurrentAimingMod unavailable");

        Il2CppObject* mod_ptr =
            get_current_aiming_mod(procedural_weapon_animation.get_instance());
        if (!mod_ptr)
            return detail::scope_zoom_error(
                "scope-current-mod-null",
                "Scope zoom read failed: CurrentAimingMod is null while aiming");

        ta::adapters::il2cpp::Il2CppObjectInstance sight_component(mod_ptr);
        float zoom = 0.0f;
        if (!sight_component.try_get_field("ScopeZoomValue", zoom))
            return detail::scope_zoom_error(
                "scope-zoom-field",
                "Scope zoom read failed: ScopeZoomValue unavailable");
        if (!std::isfinite(zoom) || zoom < 0.0f)
            return detail::scope_zoom_error(
                "scope-zoom-invalid",
                "Scope zoom read failed: ScopeZoomValue is invalid");
        if (zoom == 0.0f)
            return 1.0f;

        const auto get_adjustable_optic_data =
            sight_component.get_method<Il2CppObject*(*)(Il2CppObject*)>(
                "get_AdjustableOpticData", 0);
        if (!get_adjustable_optic_data)
            return detail::scope_zoom_error(
                "scope-adjustable-data-method",
                "Scope zoom read failed: get_AdjustableOpticData unavailable");

        Il2CppObject* adjustable_data_object =
            get_adjustable_optic_data(sight_component.get_instance());
        if (!adjustable_data_object)
            return detail::scope_zoom_error(
                "scope-adjustable-data-null",
                "Scope zoom read failed: AdjustableOpticData is null");

        ta::adapters::il2cpp::Il2CppObjectInstance adjustable_data(adjustable_data_object);
        ta::adapters::unity::vector3 minmax{};
        if (!adjustable_data.try_get_field("MinMaxFov", minmax))
            return detail::scope_zoom_error(
                "scope-min-max-field",
                "Scope zoom read failed: MinMaxFov unavailable");
        if (minmax.x <= 0.0f)
            return detail::scope_zoom_error(
                "scope-min-max-invalid",
                "Scope zoom read failed: MinMaxFov.x is invalid");
        return minmax.x / zoom;
    }
}
