ax_core.players = {}
ax_core.physics = {
    strength = 0,
    mass = 1,
    air_resistance = -1.35,
    gravity = -9,
    friction = 0.15,
}

ax_core.agent_properties = {
    initial_properties = {
        physical = true,
        collide_with_objects = true,
        pointable = false,
        collisionbox = {-0.4, -0.5, -0.4, 0.4, 0.5, 0.4},
        visual = "mesh",
        mesh = "agent.obj",
        textures = {"invisible.png"},
        automatic_face_movement_dir = 0,
    },

    on_activate = function(self, staticdata)
        self.object:set_armor_groups({immortal = 1})
    end,

    static_save = false,

    on_step = function(self, dtime, moveresult)
        local player_data = {}
        if self.player_name then
            player_data = ax_core.players[self.player_name]
        elseif self.replay then
            local replay = self.replay
            replay.time = replay.time + dtime
            while #replay.entries >= replay.index+1 and replay.time > replay.entries[replay.index+1].time do
                replay.index = replay.index + 1
            end
            if replay.index == #replay.entries and replay.time > replay.entries[replay.index].time then
                if replay.loop then
                    replay.index = 1
                    replay.time = 0
                    self.object:set_pos(replay.starting_pos)
                    self.object:set_velocity(vector.zero())
                else
                    self.object:remove()
                    return
                end
            end
            player_data = {
                target = replay.entries[replay.index].target,
                strength = replay.entries[replay.index].strength
            }
        else
            self.object:remove()
            return
        end
        local physics = ax_core.physics

        -- If we don't have a player or target, do nothing.
        local velocity = self.object:get_velocity()
        local distance = 100
        local current_pos = self.object:get_pos()
        if not player_data or not player_data.target then
            -- Apply basic gravity if there's no target active
            velocity.y = velocity.y + (physics.gravity * dtime)
            self.object:set_velocity(velocity)
        else
            -- Physics Model
            local target_pos = player_data.target
            local target_strength = player_data.strength
            local vector_to_target = vector.subtract(target_pos, current_pos)
            distance = vector.length(vector_to_target)
            
            -- To prevent division by zero if we are exactly at the target
            if distance < 0 then
                self.object:set_velocity(velocity)
                return
            end
            local direction = vector.normalize(vector_to_target)
            local gravity_force = vector.new(0, physics.mass * physics.gravity, 0)
            local pull_force = vector.multiply(direction, target_strength)
            local damping_force = vector.multiply(velocity, physics.air_resistance)
            total_force = vector.add(vector.add(gravity_force, pull_force), damping_force)

            local acceleration = vector.divide(total_force, physics.mass)
            velocity = vector.add(velocity, vector.multiply(acceleration, dtime))

            -- beam effect
            ax_core.beam(current_pos, target_pos)
        end
        local on_ground = false
        if moveresult.collides then
            for _, col in ipairs(moveresult.collisions) do
                if col.type == "node" and string.find(core.get_node(col.node_pos).name, "field") then
                    core.sound_play("lava",{gain=ax_core.volume.effects/100}, true)
                    ax_core.lava_particles(current_pos)
                    if self.replay then
                        self.object:remove()
                        return
                    elseif self.player_name then
                        ax_core.dieplayer(self.player_name)
                        return
                    end
                end
                if col.axis == "y" and velocity.y < 0 then
                    velocity.y = 0
                    on_ground = true
                end
                if col.axis == "x" then
                    velocity.x = 0
                end
                if col.axis == "z" then
                    velocity.z = 0
                end
            end
        end
        -- Apply Friction if on ground
        if on_ground then
            velocity = vector.add(velocity, vector.multiply(vector.subtract(vector.multiply(velocity, vector.new(0,1,0)),velocity),(dtime / physics.friction)))
        end
        -- Stop moving when close to prevent oscilation
        if vector.length(velocity) < 1 and distance < 2 then
            velocity = vector.zero()
        end

        self.object:set_velocity(velocity)
    end,
}

ax_core.dieplayer = function(player_name)
    -- revert to last enabled position
    local player = core.get_player_by_name(player_name)
    local entity = player:get_attach()
    player_data = ax_core.players[player_name]
    entity:set_pos(player_data.enabled_pos)
    player:set_look_vertical(player_data.enabled_look_vertical)
    player:set_look_horizontal(player_data.enabled_look_horizontal)
    entity:set_velocity(vector.zero())
    player_data.target = nil
    player_data.start_time = core.get_gametime()
    local lang_data = ax_core.lang.players[player_name]
    if lang_data.timer then
        lang_data.timer.time = lang_data.timer.orig_time
        local tsec = lang_data.timer.time
        player:hud_change(lang_data.timer.hud, "text", string.format("%d:%02d", math.floor(tsec/60), tsec%60))
    end
end

core.register_entity("ax_core:agent", ax_core.agent_properties)

ax_core.enable = function(name)
    local player = core.get_player_by_name(name)
    if not player then return end
    if not ax_core.players[name] then
        ax_core.players[name] = {
            enabled = false
        }
    end
    if not ax_core.players[name].enabled then
        local stack = ItemStack("ax_core:gun")
        player:get_inventory():set_list("main", {stack})
        player:set_wielded_item(stack)
        if ax_core.buildMode then
            player:hud_set_flags({
                hotbar = false,
                healthbar = false,
                breathbar = false,
                wielditem = false,
                minimap = false,
                crosshair = true
            })
        end
        ax_core.players[name].enabled = true
        ax_core.players[name].enabled_pos = player:get_pos()
        ax_core.players[name].enabled_look_vertical = player:get_look_vertical()
        ax_core.players[name].enabled_look_horizontal = player:get_look_horizontal()
        ax_core.players[name].start_time = core.get_gametime()
        local entity = core.add_entity(vector.add(player:get_pos(), vector.new(0,0.1,0)), "ax_core:agent")
        if entity then
            entity:get_luaentity().player_name = name
            player:set_attach(entity, "", {x=0, y=0, z=0}, {x=0, y=90, z=0})
        end
    end
end

ax_core.disable = function(name)
    local player = core.get_player_by_name(name)
    if not player then return end
    if not ax_core.players[name] then
        ax_core.players[name] = {
            enabled = false
        }
    end
    if ax_core.players[name].enabled then
        if ax_core.buildMode then
            player:hud_set_flags({
                    hotbar = true,
                    healthbar = true,
                    breathbar = false,
                    wielditem = false,
                    minimap = false,
                    crosshair = true
            })
        end
        ax_core.players[name].enabled = false
        ax_core.players[name].target = nil
        local entity = player:get_attach()
        player:set_detach()
        ax_core.players[name].end_time = core.get_gametime()
        if entity then
            entity:remove()
        end
        if ax_core.lang.players[name].timer then
            player:hud_remove(ax_core.lang.players[name].timer.hud)
        end
    end
end

ax_core.default_max_target_distance = 25

-- on_use and on_place both redirect here
function ax_core.click(itemstack, user, pointed_thing)
    if user then
        local player_name = user:get_player_name()
        if player_name then
            if pointed_thing and pointed_thing.type == "node" then
                local target_position = pointed_thing.under
                local target_pattern = "^ax_core:[%w_]*target_([%w]*)"
                local node_name = core.get_node(target_position).name
                local target_node = node_name:match(target_pattern)
                if target_node and vector.distance(target_position, user:get_pos()) <= ax_core.players[player_name].max_target_distance then
                    ax_core.set_target(player_name, target_position, target_node)
                    return nil
                end
            end
            ax_core.set_target(player_name)
        end
    end
    return nil
end

ax_core.set_target = function(name, pos, target_name)
    local strength_values = {
        attractor = 30,
        weak = 15,
        repulsor = -15,
    }
    local strength = 0
    if type(target_name) == "string" then
        for key,val in pairs(strength_values) do
            if string.find(target_name, key) ~= nil then
                strength = val
                break
            end
        end
    end

    if ax_core.players[name].replay then
        local replay = ax_core.players[name].replay
        if (#replay.entries == 0) or
            (pos ~= replay.entries[#replay.entries].target) or
            (replay.entries[#replay.entries].target ~= nil and not vector.equals(replay.entries[#replay.entries].target, pos)) then
            table.insert(replay.entries, {
                time = core.get_us_time(),
                target = pos,
                strength = strength
            })
        end
    end
    if (pos ~= nil and not ax_core.players[name].target) or 
       (ax_core.players[name].target and pos and not vector.equals(ax_core.players[name].target,pos)) then
        core.sound_play("target",{gain=ax_core.volume.effects/100}, true)
    elseif pos == nil and ax_core.players[name].target ~= nil then
        core.sound_play("untarget",{gain=ax_core.volume.effects/100}, true)
    end
    ax_core.players[name].target = pos
    ax_core.players[name].strength = strength
end

core.register_tool("ax_core:gun", {
	description = "Agent X Gun",
	inventory_image = "glow_red.png", -- Placeholder image
	range = 256,
	full_punch_interval = 0.2,
	on_use = ax_core.click,
	on_place = ax_core.click,
	on_secondary_use = ax_core.click,
})
