local RemoteView = {}
--[[ Wrapper to the vanilla remote view ]]--

-- constants
RemoteView.name_shortcut = mod_prefix .. "remote-view"
RemoteView.name_event = mod_prefix .. "remote-view"
RemoteView.name_pins_event = mod_prefix .. "remote-view-pins"
RemoteView.name_setting_overhead_satellite = mod_prefix .. "show-overhead-button-satellite-mode"
RemoteView.name_satellite_light = mod_prefix .. "satellite-light"
RemoteView.max_history = 16 -- not visible to player, could be lower but no need
RemoteView.nb_satellites_to_unlock_intersurface = 2

--destroys remote view satellite light
---@param player_index uint player.index for player the light is attached to
function RemoteView.destroy_light(player_index)
  local player_in_remote_view = storage.connected_players_in_remote_view[player_index]
  if not player_in_remote_view then return end
  if player_in_remote_view.satellite_light then
    player_in_remote_view.satellite_light.destroy()
  end
  player_in_remote_view.satellite_light = nil
end

--create satellite light for remote view players
---@param player_index uint player.index to attach the light to
function RemoteView.create_light(player_index)
  local player = game.get_player(player_index) ---@cast player -?
  local remote_data = storage.connected_players_in_remote_view[player_index]
  if not remote_data then return end
  RemoteView.destroy_light(player_index)
  remote_data.satellite_light = rendering.draw_light{
    sprite = RemoteView.name_satellite_light,
    surface = player.surface,
    target = player.position,
    scale = 4,
    players = {player}
  }
end

---@param player LuaPlayer
---@param force_exit boolean?
function RemoteView.stop(player, force_exit)
  -- Abort if player is in a cutscene; fix for factorio-mods#182

  -- remove ghost targeters after exiting targeting modes
  if player.cursor_ghost and player.cursor_ghost.name and player.cursor_ghost.name.name
    and (EnergyBeam.is_targeter(player.cursor_ghost.name.name) or DeliveryCannon.is_targeter(player.cursor_ghost.name.name)) then
      player.cursor_ghost = nil
  end

  if not force_exit and player.controller_type == defines.controllers.cutscene then
    player.print({"space-exploration.remote-view-stop-in-cutscene"})
    return
  end

  RemoteView.destroy_light(player.index)
  storage.connected_players_in_remote_view[player.index] = nil

  -- exit remote view
  local playerdata = get_make_playerdata(player)
  if playerdata.remote_view_active then

    playerdata.remote_view_active = nil
    playerdata.remote_view_activity = nil

    RemoteView.add_history_current(player)

    RemoteViewGUI.close(player)
    MapView.stop_map(player)
  end

  if playerdata.anchor_scouting_for_spaceship_index then
    Spaceship.stop_anchor_scouting(player)
  end

  -- return to character
  if player.controller_type == defines.controllers.remote and playerdata.character and playerdata.character.valid then
    player.teleport(playerdata.character.position, playerdata.character.surface) -- avoid wrong surface error.
    player.set_controller{type=defines.controllers.character, character=playerdata.character}
  end

  Event.trigger("on_remote_view_stopped", {player=player})
end

---Adds the current position as a location reference to player's navigation history.
---@param player LuaPlayer Player
function RemoteView.add_history_current(player)
  local location_reference = Location.make_reference(Zone.from_surface(player.surface), player.position, nil)
  RemoteView.add_history(player, location_reference, true)
end

---Adds a given location reference to player's navigation history.
---@param player LuaPlayer Player
---@param location_reference? LocationReference Location reference
---@param force_to_end? boolean
function RemoteView.add_history(player, location_reference, force_to_end)
  if not location_reference then return end

  local playerdata = get_make_playerdata(player)
  if not playerdata.location_history then
    playerdata.location_history = {}
    playerdata.location_history.references = {location_reference}
    playerdata.location_history.current_pointer = 1
  else
    -- if back in history, clear any skipped forward
    if not force_to_end then
      for i = playerdata.location_history.current_pointer + 1, #playerdata.location_history.references do
        playerdata.location_history.references[i] = nil
      end
    end

     -- don't add duplicate
    if next(playerdata.location_history.references) then
      local last_ref = playerdata.location_history.references[#playerdata.location_history.references]
      --Log.debug(serpent.block(last_ref))
      --Log.debug(serpent.block(location_reference))
      if last_ref.type == location_reference.type and last_ref.index == location_reference.index then
        -- same zone, maybe have a setting to not have multiple history events on the same zone.
        if last_ref.position == nil or last_ref.name == nil then
          -- replace the postionless location
          playerdata.location_history.references[#playerdata.location_history.references] = location_reference
        elseif location_reference.position and Util.vectors_delta_length(last_ref.position, location_reference.position) > 1 then
          -- add different position
          table.insert(playerdata.location_history.references, location_reference)
        end
      else -- different zone, always add
        table.insert(playerdata.location_history.references, location_reference)
      end
    else
      table.insert(playerdata.location_history.references, location_reference)
    end

    -- fit in max history
    while #playerdata.location_history.references > RemoteView.max_history do
      table.remove(playerdata.location_history.references, 1)
    end

    -- update pointer
    playerdata.location_history.current_pointer = #playerdata.location_history.references --[[@as uint]]
  end
end

--[[
Makes the data valid.
Deletes histories to deleted surfaces/spaceships
]]
---@param player LuaPlayer
function RemoteView.make_history_valid(player)
  local playerdata = get_make_playerdata(player)
  if playerdata.location_history then
    local old_history = playerdata.location_history
    playerdata.location_history = nil
    for _, location_reference in pairs(old_history.references) do
      if Location.from_reference(location_reference) then
        RemoteView.add_history(player, location_reference, true)
      end
    end
  end
end

--[[
---@param player LuaPlayer
function RemoteView.history_goto_next(player)
  local location_reference = RemoteView.history_next(player)
  if location_reference then
    local playerdata = get_make_playerdata(player)
    playerdata.location_history.current_pointer = playerdata.location_history.current_pointer + 1
    Location.goto_reference(player, location_reference, true)
  end
end

---@param player LuaPlayer
function RemoteView.history_goto_previous(player)
  local location_reference = RemoteView.history_previous(player)
  if location_reference then
    local playerdata = get_make_playerdata(player)
    playerdata.location_history.current_pointer = playerdata.location_history.current_pointer - 1
    Location.goto_reference(player, location_reference, true)
  end
end

---@param player LuaPlayer
function RemoteView.history_goto_last(player)
  local playerdata = get_make_playerdata(player)
  if playerdata.location_history.references then
    local location_reference = playerdata.location_history.references[#playerdata.location_history.references]
    if location_reference then
      Location.goto_reference(player, location_reference, true)
    end
  end
end

]]--

---@param player LuaPlayer
---@return LocationReference?
function RemoteView.history_next(player)
  local playerdata = get_make_playerdata(player)
  if not playerdata.location_history then return end
  return playerdata.location_history.references[playerdata.location_history.current_pointer + 1]
end


---@param player LuaPlayer
---@return LocationReference?
function RemoteView.history_previous(player)
  local playerdata = get_make_playerdata(player)
  if not playerdata.location_history then return end
  return playerdata.location_history.references[playerdata.location_history.current_pointer - 1]
end

---@param player LuaPlayer
function RemoteView.history_delete_all(player)
  local playerdata = get_make_playerdata(player)
  if not playerdata.location_history then return end
  local current_history = nil
  if playerdata.location_history.current_pointer > 0 and playerdata.location_history.references then
    current_history = playerdata.location_history.references[playerdata.location_history.current_pointer]
  end
  playerdata.location_history.current_pointer = 0
  for key, value in pairs(playerdata.location_history.references) do
    playerdata.location_history.references[key] = nil
  end
  if current_history then
    playerdata.location_history.current_pointer = 1
    table.insert(playerdata.location_history.references, current_history)
  end
end

---@param force_name string
---@return boolean
function RemoteView.is_unlocked_force(force_name)
  return storage.debug_view_all_zones or (storage.forces[force_name] and storage.forces[force_name].satellites_launched >= 1)
end

---@param player LuaPlayer
---@return boolean
function RemoteView.is_unlocked(player)
  -- Players can always use remote view. Unlocking it simply means that SE
  -- will automatically chart the area around the player while in remote view.
  -- The odd naming is due to legacy reasons.
  return RemoteView.is_unlocked_force(player.force.name)
end

---@param player LuaPlayer
---@return LocalisedString
function RemoteView.unlock_requirement_string(player)
  if is_player_force(player.force.name) then
    return {"space-exploration.remote-view-requires-satellite"}
  else
    return {"space-exploration.cannot-use-with-force"}
  end
end

---@param force_name string
---@return boolean
function RemoteView.is_intersurface_unlocked_force(force_name)
  return storage.debug_view_all_zones or (storage.forces[force_name] and storage.forces[force_name].satellites_launched >= RemoteView.nb_satellites_to_unlock_intersurface)
end

---@param player LuaPlayer
---@return boolean
function RemoteView.is_intersurface_unlocked(player)
  return RemoteView.is_intersurface_unlocked_force(player.force.name)
end

---@param player LuaPlayer
---@return LocalisedString
function RemoteView.intersurface_unlock_requirement_string(player)
  if is_player_force(player.force.name) then
    return {"space-exploration.remote-view-intersurface-requires-satellite", RemoteView.nb_satellites_to_unlock_intersurface - storage.forces[player.force.name].satellites_launched}
  else
    return {"space-exploration.cannot-use-with-force"}
  end
end

---@param force_name string
---@param err_str string
---@return LocalisedString
function RemoteView.intersurface_unlock_requirement_string_2(force_name, err_str)
  err_str = err_str or "space-exploration.remote-view-intersurface-requires-satellite"
  return {err_str, RemoteView.nb_satellites_to_unlock_intersurface - storage.forces[force_name].satellites_launched}
end

---@param player LuaPlayer
---@param surface LuaSurface
---@param position MapPosition
---@param zoom number?
local function _remote_view_start(player, surface, position, zoom)
  local playerdata = get_make_playerdata(player)

  if not playerdata.remote_view_active then
    playerdata.remote_view_active = true

    storage.connected_players_in_remote_view[player.index] = {
      player = player,
      has_radar = RemoteView.is_unlocked(player)
    }

    -- Hide SurfaceList from RemoteView
    local game_view_settings = player.game_view_settings
    game_view_settings.show_surface_list = false
    player.game_view_settings = game_view_settings

  end

  local player_opened = player.opened
  if player.controller_type == defines.controllers.remote and player.surface == surface and util.vectors_delta_length_sq(player.position, position) < 1 then
    -- don't add an engine history point
    player.teleport(position)
  else
    player.set_controller{
      type = defines.controllers.remote,
      surface = surface,
      position = position,
    }
    if player.character and player.character.name ~= MapView.name_map_revealer then
      playerdata.character = player.character
    end
  end
  -- Executing player.set_controller{} makes the LuaGuiElement in player_opened invalid.
  --player.opened = player_opened

  if zoom then player.zoom = zoom end
  RemoteViewGUI.open(player)
end

---Starts remote view for a given player.
---@param player LuaPlayer Player
---@param zone? AnyZoneType|SpaceshipType Zone to open
---@param position? MapPosition Position to go to, if any
---@param zoom? number Zoom level, if any
---@param location_name? string Player-given location name, if any
---@param freeze_history? boolean Determines whether entry is added to history
function RemoteView.start(player, zone, position, zoom, location_name, freeze_history)
  if not is_player_force(player.force.name) then
    RemoteView.stop(player, true)
    return player.print({"space-exploration.cannot-use-with-force"})
  end

  if zone and zone.surface_index ~= player.surface.index then
    if not RemoteView.is_intersurface_unlocked(player) then
      return player.print(RemoteView.intersurface_unlock_requirement_string(player))
    end
  end

  Spaceship.stop_anchor_scouting(player)

  local playerdata = get_make_playerdata(player)

  zone = zone or Zone.from_surface(player.surface)

  -- If in a vault, get the zone it belongs to
  if not zone then
    local vault = Ancient.vault_from_surface(player.surface)
    if vault then
      zone = Zone.from_zone_index(vault.zone_index)
    end
  end

  -- This probably should be changed for MP with multiple forces, as some may not know about Nauvis
  -- It should get the force's homeworld
  if (not zone) and not playerdata.starmap_active_map then
    zone = Zone.get_default()
  end

  playerdata.remote_view_current_zone = nil

  if zone then
    ---@type LuaSurface?
    local surface

    playerdata.remote_view_current_zone = zone

    if zone.type == "spaceship" then
      ---@cast zone SpaceshipType
      local spaceship = zone
      surface = Spaceship.get_current_surface(zone)
      if position then
        -- Nothing to do
      elseif spaceship.known_tiles_average_x and spaceship.known_tiles_average_y then
        -- It's nicest to place the view in the middle of the spaceship, rather than at the console
        position = {x=spaceship.known_tiles_average_x,y=spaceship.known_tiles_average_y}
      elseif spaceship.console and spaceship.console.valid then
        position = spaceship.console.position
      else
        position = {x=0,y=0}
      end

      -- Nicely frame the spaceship zoom level so that the entire height is visible with some buffer
      if not zoom and spaceship.known_bounds then
        local height = spaceship.known_bounds.right_bottom.y - spaceship.known_bounds.left_top.y
        local screen_height_pixels = player.display_resolution.height / player.display_scale / player.display_density_scale
        zoom = (screen_height_pixels * 0.8) / (height * 32) -- 32 pixels per tile at zoom 1.0 with 20% padding
        zoom = math.max(0.1, math.min(zoom, 2.0)) -- Clamp zoom to reasonable bounds
      end

    else
      ---@cast zone -SpaceshipType
      surface = Zone.get_make_surface(zone)
      if not playerdata.surface_positions or not playerdata.surface_positions[surface.index] then
        player.force.chart(surface, util.position_to_area({x = 0, y = 0}, 64)) -- smaller region first
        player.force.chart(surface, util.position_to_area({x = 0, y = 0}, 256))
      elseif not position then
        position = playerdata.surface_positions[zone.surface_index]
      end
      if not position then
        position = {x = 0, y = 0}
      end
      Zone.apply_markers(zone) -- in case the surface exists
    end

    if not freeze_history then -- maybe this should be in _remote_view_start?
      local location_reference = Location.make_reference(zone, position, location_name) --[[@as LocationReference]]
      RemoteView.add_history(player, location_reference)
    end

    _remote_view_start(player, surface, position, zoom)

    -- Post setup regular remote view
    player.enable_flashlight()
    RemoteView.create_light(player.index)

    Event.trigger("on_remote_view_started", {player=player, zone=zone, position=position, zoom=zoom})
  end
end

---@param player LuaPlayer
---@return boolean
function RemoteView.is_active(player)
  return get_make_playerdata(player).remote_view_active == true
end

---@param player LuaPlayer
function RemoteView.toggle(player)
  if RemoteView.is_active(player) then
    RemoteView.stop(player)
  else
    RemoteView.start(player)
  end
end

---@param event EventData.on_player_controller_changed
function RemoteView.on_player_controller_changed(event)
  local player = game.get_player(event.player_index)
  if not player then return end
  local playerdata = get_make_playerdata(player)
  if playerdata.remote_view_active and not (player.controller_type == defines.controllers.remote) then
    if player.controller_type == defines.controllers.ghost and playerdata.anchor_scouting_for_spaceship_index then
      -- Scouting for anchor, don't end remote view (playerdata.remote_view_active is needed for after)
    else
      RemoteView.stop(player)
    end
  elseif player.controller_type == defines.controllers.remote and not playerdata.remote_view_active then
    if playerdata.starmap_active_map then
      MapView.stop_map(player)
    elseif playerdata.last_clicked_gps_tag
      and  playerdata.last_clicked_gps_tag.is_starmap_surface
    then
      MapView.stop_map(player)
      MapView.starmap_view_cycle(player)
      playerdata.starmap_character.teleport(playerdata.last_clicked_gps_tag.position)
      playerdata.last_clicked_gps_tag = nil
    else
      RemoteView.start(player)
    end
  elseif player.controller_type == defines.controllers.editor and MapView.is_surface_starmap(player.surface) then
    -- not allowed, put back into character.
    MapView.stop_map(player)
  end
end
Event.addListener(defines.events.on_player_controller_changed, RemoteView.on_player_controller_changed )

---@param player LuaPlayer
function RemoteView.restore_editor_state(player)
  local playerdata = get_make_playerdata(player)
  local character_entity = playerdata.editor_character_data.surface.find_entity(playerdata.editor_character_data.character_name, playerdata.editor_character_data.position)
  if not character_entity then
    character_entity = player.surface.find_entity(playerdata.editor_character_data.character_name, player.position)
  end
  if not character_entity then
    character_entity = playerdata.editor_character_data.surface.find_entities_filtered{
      type = "character",
      name = playerdata.editor_character_data.character_name
    }[1]
  end
  if not character_entity then
    character_entity = player.surface.find_entities_filtered{
      type = "character",
      name = playerdata.editor_character_data.character_name,
    }[1]
  end
  if not character_entity then
    --player.teleport(playerdata.editor_character_data.position, playerdata.editor_character_data.surface)
    return error("We have lost the stashed character")
  end
  if character_entity then
    local success = character_entity.teleport(playerdata.editor_character_data.position, playerdata.editor_character_data.surface)
    if not success then error("Failed Teleport") end
  end
  local player_position, player_surface = player.position, player.surface
  player.teleport(character_entity.position, character_entity.surface)
  player.set_controller{type = defines.controllers.character, character = character_entity} -- this does not raise on_pre_player_toggled_map_editor
  player.set_controller{type = defines.controllers.editor}
  player.teleport(player_position, player_surface)
end

---@param event EventData.on_pre_player_toggled_map_editor
function RemoteView.on_pre_player_toggled_map_editor(event)
  local player = game.get_player(event.player_index)
  if not player then return end
  if MapView.is_surface_starmap(player.surface) then
    return MapView.stop_map(player) -- nope
  end
  local playerdata = get_make_playerdata(player)
  if player.controller_type ~= defines.controllers.editor then
    if playerdata.character and playerdata.character.valid and playerdata.character.name ~= MapView.name_map_revealer then
      playerdata.editor_character_data = {
        surface = playerdata.character.surface,
        position = playerdata.character.position,
        character_name = playerdata.character.prototype.name,
      }
    elseif player.character and player.character.valid and player.character.name ~= MapView.name_map_revealer then
      playerdata.editor_character_data = {
        surface = player.character.surface,
        position = player.character.position,
        character_name = player.character.prototype.name,
      }
    end
  else
    playerdata.editor_character_data = nil
  end
end
Event.addListener(defines.events.on_pre_player_toggled_map_editor, RemoteView.on_pre_player_toggled_map_editor)

---Handles clicks for remote view.
---@param event EventData.on_gui_click Event data
function RemoteView.on_gui_click(event)
  if not (event.element and event.element.valid) then return end

  local element = event.element
  local player = game.get_player(event.player_index)
  ---@cast player -?

  if element.tags and element.tags.se_action == "go_to_entity_and_select" then
    local surface = game.get_surface(element.tags.surface_index --[[@as uint]])
    if surface then
      local zone = Zone.from_surface(surface)
      if zone then
        player.clear_cursor()
        local entity = surface.find_entity(
          element.tags.name --[[@as string]],
          element.tags.position --[[@as MapPosition]]
        )
        RemoteView.start(player, zone, element.tags.position)
        if entity then player.opened = entity end
      else
        RemoteView.stop(player)
      end
    end
  end
end
Event.addListener(defines.events.on_gui_click, RemoteView.on_gui_click)

---@param event EventData.CustomInputEvent Event data
function RemoteView.on_remote_view_pins_keypress(event)
  if event.player_index
    and game.get_player(event.player_index)
    and game.get_player(event.player_index).connected
  then
    Pin.window_toggle(game.get_player(event.player_index))
  end
end
Event.addListener(RemoteView.name_pins_event, RemoteView.on_remote_view_pins_keypress)

---@param event EventData.on_player_clicked_gps_tag Event data
function RemoteView.on_player_clicked_gps_tag(event)
  local player = game.get_player(event.player_index)
  if not player then return end
  local playerdata = get_make_playerdata(player)
  if playerdata.spectator then return end
  local surface = game.get_surface(event.surface)
  if surface then
    local zone = Zone.from_surface(surface)
    if not zone then
      if event.surface ~= player.surface.name then
        return player.print({"space-exploration.gps_no_zone"})
      end
      if string.match(surface.name, "^spaceship%-.*") then
        -- This GPS points to a cached spaceship surface where the spaceship is currently landed
        -- We explicitly stop the remote view, which would stop remote view even if the player
        -- was in remote view. Otherwise it's possible to view the empty spaceship surface in some conditions.
        RemoteView.stop(player)
        return
      end
      if MapView.is_surface_starmap(surface) then
        local map_owner = MapView.get_player_index_from_surface(surface)
        local map_owner_playerdata = get_make_playerdata(game.get_player(map_owner))
        playerdata.last_clicked_gps_tag = {
          tick = event.tick,
          is_starmap_surface = true,
          position = table.deepcopy(event.position),
          mapview_data = table.deepcopy(map_owner_playerdata.last_made_gps_tag.starmap_active_map)
        }
      end
    else
      if not RemoteView.is_intersurface_unlocked(player) then
        if event.surface ~= player.surface.name then
          return player.print({"space-exploration.gps_requires_satellite"})
        else
          -- default to map shift with no message
        end
      else
        if Zone.is_visible_to_force(zone, player.force.name) then
          RemoteView.start(player, zone, event.position)
        else
          player.print({"space-exploration.gps_undiscovered"})
        end
      end
    end
  else
    player.print({"space-exploration.gps_invalid"})
  end
end
Event.addListener(defines.events.on_player_clicked_gps_tag, RemoteView.on_player_clicked_gps_tag)

---@param event EventData.on_console_chat Event data
function RemoteView.on_console_chat(event)
  local s = MapView.name_surface_prefix
  if not event.player_index then return end -- Was message from server console
  local starmap = string.sub(s,1,string.len(s)-1) .. "%-" .. event.player_index
  if string.find(event.message,"%[gps=[-]*%d+%.*%d+%,[-]*%d+%.*%d+%,"..starmap.."%]") then
    local player = game.get_player(event.player_index)
    ---@cast player LuaPlayer
    if MapView.player_is_in_interstellar_map(player) or MapView.player_is_in_system_map(player) then
      local playerdata = get_make_playerdata(player)
      playerdata.last_made_gps_tag = {
        starmap_active_map = table.deepcopy(playerdata.starmap_active_map)
      }
    end
  end
end
Event.addListener(defines.events.on_console_chat, RemoteView.on_console_chat)

function RemoteView.on_tick()
  --move light to follow player
  for player_index, player_in_remote_view in pairs(storage.connected_players_in_remote_view) do
    local satellite_light = player_in_remote_view.satellite_light
    if satellite_light and satellite_light.valid then
      satellite_light.target = player_in_remote_view.player.position
    else
      RemoteView.create_light(player_index)
    end
  end
end
Event.addListener(defines.events.on_tick, RemoteView.on_tick)

---@param event EventData.on_player_changed_surface Event data
function RemoteView.on_player_changed_surface(event)
  local player = game.get_player(event.player_index)
  if not player then return end

  -- if this has gone to a starmap surface but we're not in starmap mode...
  if MapView.is_surface_starmap(player.surface) then
    local playerdata = get_make_playerdata(player)
    if not playerdata.starmap_active_map then
      RemoteView.stop(player, true)
      MapView.starmap_view_cycle(player)
    end
  end

  -- RemoteView is already active, so check if playerdata.remote_view_current_zone
  -- matchs with the current surface and if not we should trigger the update of
  -- that datastructure.
  if RemoteView.is_active(player) then
    local playerdata = get_make_playerdata(player)

    local surface_index_to_view = playerdata.remote_view_current_zone.surface_index

    if not surface_index_to_view and playerdata.remote_view_current_zone.type == "spaceship" then
      -- The `surface_index` property doesn't exist on spaceships.
      surface_index_to_view = Spaceship.get_current_surface(playerdata.remote_view_current_zone --[[@as SpaceshipType]]).index
    end

    if surface_index_to_view ~= player.surface_index then
      local zone = Zone.from_surface(player.surface)
      if zone then
        RemoteView.start(player, zone)
      elseif not MapView.is_surface_starmap(player.surface) then
        -- Something went wrong, stop remote view. This can happen if a player uses
        -- back/forward history to go to a landed spaceship's cached surface.
        RemoteView.stop(player)
      end
    end
  end

  -- Move light with player when they change surfaces.
  if storage.connected_players_in_remote_view[event.player_index] then
    RemoteView.create_light(event.player_index) -- Will also destroy the light if it exists
  end
end
Event.addListener(defines.events.on_player_changed_surface, RemoteView.on_player_changed_surface)

function RemoteView.on_nth_tick_600()
  for _, player_in_remote_view in pairs(storage.connected_players_in_remote_view) do
    if not player_in_remote_view.has_radar then goto continue end
    local player = player_in_remote_view.player
    if not (player.valid and player.connected) then goto continue end

    -- Don't chart if player is zoomed out far enough to see the chart view.
    if player.render_mode ~= defines.render_mode.chart_zoomed_in then goto continue end

    player.force.chart(
      player.surface,
      util.position_to_area(player.position, 4 * 32, 1.25)
    )
    ::continue::
  end
end
Event.addListener("on_nth_tick_60", RemoteView.on_nth_tick_600)

function RemoteView.on_init()
  storage.connected_players_in_remote_view = {}
end
Event.addListener("on_init", RemoteView.on_init, true)

return RemoteView
