local render_links_delay = 1
local waypoint_ids = {}

local function happy_particles(pos)
    local vel = vector.new(-3, -3, -3)
    minetest.add_particlespawner {
        amount = 1000,
        time = 0.1,
        exptime = 3,
        collisiondetection = true,
        collision_removal = true,
        texture = 'vizlib_particle.png^[colorize:green:255',
        glow = 14,
        pos = pos,
        vel = { min = -vel, max = vel },
    }
end

local function render_individual_link(pos, name)
    waypoint_ids[#waypoint_ids + 1] = sbz_api.set_waypoint(pos, {
        name = name,
        dist = 0,
        max_dist = 100,
        precision = 0,
        image = 'visualiser_trail.png^[colorize:red:100^[verticalframe:3:0',
    })
end

local timer = 0
local function render_links(dtime)
    timer = timer + dtime
    if timer < render_links_delay then return end
    timer = 0

    for k, v in pairs(waypoint_ids) do
        sbz_api.remove_waypoint(v)
    end
    waypoint_ids = {}

    for k, v in pairs(minetest.get_connected_players()) do
        local wielded_item = v:get_wielded_item()
        if wielded_item:get_name() == 'sbz_power:sensor_linker' then
            local itemmeta = wielded_item:get_meta()
            local linked_pos_data = itemmeta:get_string 'linked'
            if linked_pos_data ~= '' then
                local linked_pos = core.deserialize(linked_pos_data)
                local linked_node = sbz_api.get_or_load_node(linked_pos)
                local linked_meta = minetest.get_meta(linked_pos)
                local radius = core.registered_nodes[linked_node.name].linking_range
                    or linked_meta:get_int 'linking_range'

                vizlib.draw_cube(linked_pos, radius + 0.5, {
                    player = v,
                    color = 'blue',
                    infinite = false,
                    time = render_links_delay + 0.1,
                })
                local links = core.deserialize(linked_meta:get_string 'links')
                if type(links) == 'table' then
                    for name, vector_array in pairs(links) do
                        for _, position in pairs(vector_array) do
                            render_individual_link(position, name)
                        end
                    end
                end
            end
        end
    end
end

core.register_globalstep(render_links)

local function try_to_link_to_tool(stack, pos, placer)
    local meta = stack:get_meta()
    local name = placer:get_player_name()
    local node = sbz_api.get_or_load_node(pos)
    if not node then return end
    node = node.name
    local ndef = minetest.registered_nodes[node]
    if not ndef then return end
    if not ndef.can_sensor_link then return sbz_api.displayDialogLine(name, "Can't link with that.") end
    -- ok yeah it can link
    meta:set_string('linked', core.serialize(pos))
    minetest.chat_send_player(name, 'Succesfully linked to the sensor.')
    happy_particles(pos)
end

local function err_link_invalid(placer)
    minetest.chat_send_player(placer:get_player_name(), 'Link is invalid, please link the tool to your sensor again.')
end

local function make_link(meta, pos, placer)
    local linked = meta:get_string 'linked'
    local placer_name = placer:get_player_name()
    if linked == '' then return core.chat_send_player(placer_name, "Tool isn't linked to any machine.") end

    local linked_pos = core.deserialize(linked)
    local linked_node = sbz_api.get_or_load_node(linked_pos)
    linked_node = linked_node.name

    local ndef = minetest.registered_nodes[linked_node]
    if ndef == nil then return err_link_invalid(placer) end
    if not ndef.can_sensor_link then
        return core.chat_send_player(
            placer_name,
            "The node that the tool is linked with doesn't support linking with the sensor linker."
        )
    end

    local linked_meta = minetest.get_meta(linked_pos)
    local linked_range = ndef.linking_range or linked_meta:get_int 'linking_range'

    if linked_range == 0 then
        minetest.chat_send_player(placer:get_player_name(), "Can't link anything in that sensor, linked range is 0.")
        return
    end

    if not logic.in_square_radius(linked_pos, pos, linked_range) then
        minetest.chat_send_player(placer:get_player_name(), 'Outside of the radius')
        return
    end

    local name = meta:get_string 'name'
    if name == '' then
        minetest.chat_send_player(placer:get_player_name(), 'You need to set a name first (Left click)')
        return
    end

    if ndef.add_link then
        ndef.add_link(meta, pos, placer)
    else
        local links = minetest.deserialize(linked_meta:get_string 'links') or {}
        links[name] = links[name] or {}
        links[name][#links[name] + 1] = pos

        -- Hmm... WTF WAS I THINKING WHEN I WROTE THIS
        -- it works
        -- don't touch it
        -- dupe check
        local names = {}
        for lname, lpos in pairs(links) do
            for lname_2, lpos_2 in ipairs(lpos) do
                if vector.equals(pos, lpos_2) then names[#names + 1] = { lname, lname_2 } end
            end
        end
        if #names >= 2 then
            local tables_to_fix = {}
            for _, v in pairs(names) do
                links[v[1]][v[2]] = nil
                tables_to_fix[#tables_to_fix + 1] = v[1]
            end
            for _, t in ipairs(tables_to_fix) do
                local new_t = {}
                for k, v in pairs(links[t]) do
                    new_t[#new_t + 1] = v
                end
                links[t] = new_t
            end
        end
        for k, v in pairs(links) do
            if #v == 0 then links[k] = nil end
        end
        linked_meta:set_string('links', minetest.serialize(links))
    end
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
    if formname ~= 'sbz_power:sensor_linker_form' then return end
    local name = fields.set_name
    local wield_item = player:get_wielded_item()
    if wield_item:get_name() ~= 'sbz_power:sensor_linker' then return true, 'sus' end

    wield_item:get_meta():set_string('name', (name or ''):trim())
    if player:get_meta():get_string 'target' then
        local target = player:get_meta():get_string 'target'
        local target_pos = vector.from_string(target)
        if target_pos == nil then return true, '???' end
        make_link(wield_item:get_meta(), target_pos, player)
        player:set_wielded_item(wield_item)
    end
    return true
end)

minetest.register_craftitem('sbz_power:sensor_linker', {
    description = 'Sensor Linker',
    info_extra = {
        'Right click: asks for a name, if a block is pointed to, links to the to the sensor.',
        'Left click: uses the previous name from when you right clicked, links it to the sensor.',
        'Aux1 + right click/left click: Links the tool to the sensor.',
        "If you hold it, it should show all the links and the sensor's linking radius.",
    },
    inventory_image = 'sensor_linker.png',
    range = 10,
    liquids_pointable = true,
    on_place = function(stack, placer, pointed)
        if pointed.type ~= 'node' then return end
        if core.is_protected(pointed.under, placer:get_player_name()) then
            core.record_protection_violation(pointed.under, placer:get_player_name())
            return
        end

        if placer:get_player_control().aux1 == false then
            local target = pointed.under
            minetest.show_formspec(
                placer:get_player_name(),
                'sbz_power:sensor_linker_form',
                'field[set_name;The name of the link;]'
            )
            if pointed.type ~= 'node' then
                placer:get_meta():set_string('target', '')
                return
            end
            placer:get_meta():set_string('target', vector.to_string(target))
        else
            if pointed.type ~= 'node' then return end
            try_to_link_to_tool(stack, pointed.under, placer)
        end
        return stack
    end,
    on_use = function(stack, placer, pointed)
        if pointed.type ~= 'node' then return end
        if core.is_protected(pointed.under, placer:get_player_name()) then
            core.record_protection_violation(pointed.under, placer:get_player_name())
            return
        end
        local target = pointed.under
        if placer:get_player_control().aux1 == false then
            make_link(stack:get_meta(), target, placer)
        else
            try_to_link_to_tool(stack, target, placer)
        end
        return stack
    end,
    groups = {},
    stack_max = 1,
})

do -- Sensor Linker recipe scope
    local Sensor_Linker = 'sbz_power:sensor_linker'
    local WC = 'sbz_resources:warp_crystal'
    local St = 'sbz_resources:stone'
    local SC = 'sbz_resources:shock_crystal'
    core.register_craft({
        output = Sensor_Linker,
        recipe = {
            { WC, St, SC },
            { '', St, '' },
            { '', St, '' },
        }
    })
end
