local dispatch = require("__debugadapter__/dispatch.lua")
local invert = require("__debugadapter__/enumutil.lua").invert
local __DebugAdapter = __DebugAdapter
local defines = defines
local pcall = pcall
local print = print
local load = load
local pairs = pairs
local type = type
local rawget = rawget
local string = string
local smatch = string.match
local setmetatable = setmetatable
local debug = debug
local dgetmetatable = debug.getmetatable
local debugprompt = debug.debug
local tconcat = table.concat
local assert = assert

local _ENV = nil

local function stringInterp(...)
  stringInterp = assert(dispatch.bind("stringInterp"))
  return stringInterp(...)
end

---@class LuaObjectInfo
local luaObjectInfo = {
  alwaysValid = {},
  expandKeys = {},
}
do
  ---@type string[]
  local objectChunks = {"return "}

  ---Called by VSCode to load class data.
  ---Called with no arguments after final chunk.
  ---@param chunk string
  ---@overload fun()
  function __DebugAdapter.loadObjectInfo(chunk) ---@diagnostic disable-line inject-field
    if chunk then
      objectChunks[#objectChunks+1] = chunk
    else
      luaObjectInfo = load(tconcat(objectChunks),"=(objectinfo)","t")() --[[@as LuaObjectInfo]]
    end
  end
  print("\xEF\xB7\x90\xEE\x80\x84")
  debugprompt()
  __DebugAdapter.loadObjectInfo = nil ---@diagnostic disable-line inject-field
end

---@type {[string]:string|fun(obj:LuaObject,short?:boolean):string}
luaObjectInfo.lineItem = {
  ---@param stack LuaItemStack
  ---@param short boolean | nil
  ---@return string
  LuaItemStack = function(stack,short)
    if stack.valid_for_read then
      if not short then
        return (stringInterp(
          [[<LuaItemStack>{[}name={name}, count={count}{]}]],
          nil,
          stack,
          "luaobjectline"
        ))
      else
        return [[<LuaItemStack>]]
      end
    else
      return [[<Empty LuaItemStack>]]
    end
  end,
  LuaPlayer = [[<LuaPlayer>{[}name={name}, index={index}{]}]],
  LuaSurface = [[<LuaSurface>{[}name={name}, index={index}{]}]],
  LuaForce = [[<LuaForce>{[}name={name}, index={index}{]}]],
  LuaGuiElement = [[<LuaGuiElement>{[}name={name}, type={type}, index={index}{]}]],
  LuaStyle = [[<LuaStyle>{[}name={name}{]}]],
  LuaEntity = [[<LuaEntity>{[}name={name}, type={type}, unit_number={unit_number}{]}]],
}

--[[
LuaObjects since 1.1.49 share metatables per class, and the metatables are held
in the registry by class name once an object of that class has been created.

From 1.2 onward, the LuaObject itself is a userdata instead of a table,
and the old `__self` member is gone.

Most meta funcs have single upval,
  lightuserdata(pointer to member function)
  object pointers are retrieved from the LuaObject passed in param 1 when calling

`__eq` is shared by all, and has two (also in registry as `luaobject__eq`)
  lightuserdata(pointer to object (`game`))
  lightuserdata(pointer to member function(`__eq`))

Normal API functions have three upvals:
  lightuserdata(pointer to object),
  lightuserdata(pointer to member function),
  LuaObject(parent object)

all functions from one object will have the same value in the first
all instances of the same class::function will have the same value in the second
third is the parent LuaObject of the specific api closure, to keep it from being disposed

]]



---Check if a value is a LuaObject or LuaBindableObject
---@param obj any
---@return LuaObject.object_name|nil
local function try_object_name(obj)
  -- basic checks for LuaObject-like things: userdata(>=1.2), has masked meta
  if type(obj) ~= "userdata" then return end

  local mt = dgetmetatable(obj)
  if not mt then return end
  if rawget(mt, "__metatable") ~= "private" then return end

  -- LuaBindableObjects don't have `isluaobject`, so use `object_name` instead
  -- pcall in case it's still not a real LuaObject...
  local success,object_name = pcall(rawget(mt, "__index"),obj,"object_name")
  if success then
    return object_name
  end
  return
end
luaObjectInfo.try_object_name = try_object_name

luaObjectInfo.alwaysValid.LuaMapSettings = true
luaObjectInfo.alwaysValid.LuaDifficultySettings = true
luaObjectInfo.alwaysValid.LuaGameViewSettings = true

local enumSpecial = {
  ["defines.inventory"] = function()
    local burner = {
      [defines.inventory.fuel] = "defines.inventory.fuel",
      [defines.inventory.burnt_result] = "defines.inventory.burnt_result",
    }
    local function with(super,t) return setmetatable(t,{__index = super}) end

    local chest = {
      [defines.inventory.chest] = "defines.inventory.chest",
    }

    local assembler = with(burner,invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^assembling_machine_") end))

    local character = invert(defines.inventory,"defines.inventory.",function(k,v) return (not not smatch(k,"^character_")) and k ~= "character_corpse" end)
    local robot = invert(defines.inventory,"defines.inventory.",function(k,v) return (not not smatch(k,"^robot_")) end)

    local invname = {
      burner = burner,
      item = invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^item_") end),
      player = {
        [defines.controllers.character] = character,
        [defines.controllers.god] =
          invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^god_") end),
        [defines.controllers.editor] =
          invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^editor_") end),
      },
      entity = {
        ["container"]=chest,
        ["logistic-container"]=chest,
        ["cargo-wagon"]={ [defines.inventory.cargo_wagon] = "defines.inventory.cargo_wagon" },
        ["rocket-silo-rocket"]={ [defines.inventory.cargo_wagon] = "defines.inventory.rocket" },

        ["construction-robot"]=robot,
        ["logistic-robot"]=robot,

        ["ammo-turret"]=invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^turret_") end),
        ["artillery-turret"]=invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^artillery_turret_") end),
        ["artillery-wagon"]=invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^artillery_wagon_") end),
        ["roboport"]=invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^roboport_") end),
        ["beacon"]=invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^beacon_") end),
        ["character-corpse"]=invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^character_corpse_") end),

        ["furnace"]=with(burner,invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^furnace_") end)),
        ["assembling-machine"]=assembler,
        ["mining-drill"]=with(burner,invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^mining_drill_") end)),
        ["lab"]=with(burner,invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^lab_") end)),
        ["car"]=with(burner,invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^car_") end)),
        ["spider-vehicle"]=with(burner,invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^spider_") end)),
        ["rocket-silo"]=with(assembler,invert(defines.inventory,"defines.inventory.",function(k,v) return not not smatch(k,"^rocket_silo_") end)),

      },
    }
    return function(inv,index)
      local owner = inv.player_owner
      if owner then
        -- check if player is character/god/editor
        local names = invname.player[owner.controller_type]
        if names then
          return names[index]
        end
        return
      end
      owner = inv.equipment_owner
      if owner then
        -- burner inside equipment
        return invname.burner[index]
      end
      owner = inv.entity_owner
      if owner then
        -- check entity type
        local names = invname.entity[owner.type]
        if names then
          return names[index]
        end
        return
      end
      owner = inv.mod_owner
      if owner then
        return nil
      end
      local names = invname.item
      return names[index]
    end
  end,
  ["defines.transport_line"] = function() end,
  ["defines.circuit_condition_index"] = function() end, --1.2
  ["defines.wire_connector_id"] = function () --1.2
    ---@diagnostic disable-next-line: undefined-field
    local wire_connector_id = defines.wire_connector_id
    local default = invert(wire_connector_id,"defines.wire_connector_id.",function(k,v) return (not not smatch(k,"^circuit")) end)
    local combinator = invert(wire_connector_id,"defines.wire_connector_id.",function(k,v) return (not not smatch(k,"^combinator")) end)
    local pole = invert(wire_connector_id,"defines.wire_connector_id.",function(k,v) return (not not smatch(k,"^pole")) end)
    local switch = invert(wire_connector_id,"defines.wire_connector_id.",function(k,v) return (not not smatch(k,"^power_switch")) end)
    local entity = {
      ["electric-pole"] = pole,
      ["power-switch"] = switch,
      ["decider-combinator"] = combinator,
      ["arithmetic-combinator"] = combinator,
    }

    return function(obj,id)
      local object_name = obj.object_name
      local owner
      if object_name == "LuaCircuitNetwork" then
        owner = obj.entity
      elseif object_name == "LuaWireConnector" then
        owner = obj.owner
      else
        return
      end
      local map = entity[owner.type]
      if map then
        return map[id] or default[id]
      else
        return default[id]
      end
    end
  end,
}

for _, class in pairs(luaObjectInfo.expandKeys) do
  for _,prop in pairs(class) do
    local enumFrom = prop.enumFrom
    prop.enumFrom = nil
    if enumFrom then
      local special = enumSpecial[enumFrom]
      if special then
        local success,result = pcall(special)
        if success and result then
          prop.enum = result
        end
      else
        local success,result = pcall(function()
          return load("return " .. enumFrom)()
        end)
        if success then
          prop.enum = invert(result, enumFrom .. ".")
        end
      end
    end
  end
end

local extraKeys = {
  LuaProfiler = {
    ["<translated>"] = {thisTranslated = true},
  },
  LuaDifficultySettings = {
    recipe_difficulty = {},
    technology_difficulty = {},
    technology_price_multiplier = {},
    research_queue_setting = {},
  },
  LuaGameViewSettings = {
    show_controller_gui = {},
    show_minimap = {},
    show_research_info = {},
    show_entity_info = {},
    show_alert_gui = {},
    update_entity_selection = {},
    show_rail_block_visualisation = {},
    show_side_menu = {},
    show_map_view_options = {},
    show_quickbar = {},
    show_shortcut_bar = {},
  },
  LuaMapSettings = {
    pollution = {},
    enemy_evolution = {},
    enemy_expansion = {},
    unit_group = {},
    steering = {},
    path_finder = {},
    max_failed_behavior_count = {},
  },

  ["LuaMapSettings.pollution"] = {
    enabled = {},
    diffusion_ratio = {},
    min_to_diffuse = {},
    ageing = {},
    expected_max_per_chunk = {},
    min_to_show_per_chunk = {},
    min_pollution_to_damage_trees = {},
    pollution_with_max_forest_damage = {},
    pollution_per_tree_damage = {},
    pollution_restored_per_tree_damage = {},
    max_pollution_to_restore_trees = {},
    enemy_attack_pollution_consumption_modifier = {},
  },
  ["LuaMapSettings.enemy_evolution"] = {
    enabled = {},
    time_factor = {},
    destroy_factor = {},
    pollution_factor = {},
  },
  ["LuaMapSettings.enemy_expansion"] = {
    enabled = {},
    max_expansion_distance = {},
    friendly_base_influence_radius = {},
    enemy_building_influence_radius = {},
    building_coefficient = {},
    other_base_coefficient = {},
    neighbouring_chunk_coefficient = {},
    neighbouring_base_chunk_coefficient = {};
    max_colliding_tiles_coefficient = {},
    settler_group_min_size = {},
    settler_group_max_size = {},
    min_expansion_cooldown = {},
    max_expansion_cooldown = {},
  },
  ["LuaMapSettings.unit_group"] = {
    min_group_gathering_time = {},
    max_group_gathering_time = {},
    max_wait_time_for_late_members = {},
    max_group_radius = {},
    min_group_radius = {},
    max_member_speedup_when_behind = {},
    max_member_slowdown_when_ahead = {},
    max_group_slowdown_factor = {},
    max_group_member_fallback_factor = {},
    member_disown_distance = {},
    tick_tolerance_when_member_arrives = {},
    max_gathering_unit_groups = {},
    max_unit_group_size = {},
  },
  ["LuaMapSettings.steering"] = {
    default = {}, moving = {},
  },
  ["LuaMapSettings.steering.default"] = {
    radius = {},
    separation_force = {},
    separation_factor = {},
    force_unit_fuzzy_goto_behavior = {},
  },
  ["LuaMapSettings.steering.moving"] = {
    radius = {},
    separation_force = {},
    separation_factor = {},
    force_unit_fuzzy_goto_behavior = {},
  },
  ["LuaMapSettings.path_finder"] = {
    fwd2bwd_ratio = {},
    goal_pressure_ratio = {},
    max_steps_worked_per_tick = {},
    max_work_done_per_tick = {},
    use_path_cache = {},
    short_cache_size = {},
    long_cache_size = {},
    short_cache_min_cacheable_distance = {},
    short_cache_min_algo_steps_to_cache = {},
    long_cache_min_cacheable_distance = {},
    cache_max_connect_to_cache_steps_multiplier = {},
    cache_accept_path_start_distance_ratio = {},
    cache_accept_path_end_distance_ratio = {},
    negative_cache_accept_path_start_distance_ratio = {},
    negative_cache_accept_path_end_distance_ratio = {},
    cache_path_start_distance_rating_multiplier = {},
    cache_path_end_distance_rating_multiplier = {},
    stale_enemy_with_same_destination_collision_penalty = {},
    ignore_moving_enemy_collision_distance = {},
    enemy_with_different_destination_collision_penalty = {},
    general_entity_collision_penalty = {},
    general_entity_subsequent_collision_penalty = {},
    extended_collision_penalty = {},
    max_clients_to_accept_any_new_request = {},
    max_clients_to_accept_short_new_request = {},
    direct_distance_to_consider_short_request = {},
    short_request_max_steps = {},
    short_request_ratio = {},
    min_steps_to_check_path_find_termination = {},
    start_to_goal_cost_multiplier_to_terminate_path_find = {},
  },
}

for classname, class in pairs(extraKeys) do
  local c = luaObjectInfo.expandKeys[classname]
  if c then
    for propname,prop in pairs(class) do
      c[propname] = c[propname] or prop
    end
  else
    luaObjectInfo.expandKeys[classname] = class
  end
end

return luaObjectInfo