local mod = get_mod("newflight")

-- ##########################################################
-- ################## Variables #############################

local FreeFlightManager = require("scripts/foundation/managers/free_flight/free_flight_manager")
local PlayerMovement = require("scripts/utilities/player_movement")
local ScriptViewport = require("scripts/foundation/utilities/script_viewport")
local ScriptWorld = require("scripts/foundation/utilities/script_world")
local Views = require("scripts/ui/views/views")

local free_flight_default_input_path = "scripts/foundation/managers/free_flight/free_flight_default_input"

local _freeflight_data = mod:persistent_table("freeflight_data")

local _offset = {
    x = 0.0,
    y = 1.0,
    z = 0,
}

local Camera = Camera
local Managers = Managers
local Quaternion = Quaternion
local Network = Network
local ScriptUnit = ScriptUnit
local Vector3 = Vector3

local null_input_types = {
  move_right = true,
  move_left = true,
  move_forward = true,
  move_backward = true,
  look_raw = true,
}

local lock_camera = false

mod.settings = mod:persistent_table("settings")

-- ##########################################################
-- ################## Functions #############################

local initialize_settings_cache = function()
  mod.settings["nf_teleport_player"] = mod:get("nf_teleport_player")
end

local pressed_toggle_look_input = function()
  return false -- Keyboard.pressed(Keyboard.button_index("["))
end

local pressed_teleport_player_to_camera = function()
  return false -- Keyboard.pressed(Keyboard.button_index("]"))
end

local pressed_roll_left = function()
  return 0 -- xbox_controller_right_shoulder
end

local pressed_roll_right = function()
  return 0 -- xbox_controller_left_shoulder
end

local is_server = function()
  return Managers.state and Managers.state.game_session and Managers.state.game_session:is_server()
end

local teleport_player_to_camera = function(cam, player)
  local pos = Camera.local_position(cam)
  local rot = Camera.local_rotation(cam)
  
  -- Flat-level position of player relative to camera
  local offset = { x = -0.8, y = 0.6, z = -1.2 }
  offset.x = offset.x + _offset.x
  offset.y = offset.y + _offset.y
  offset.z = offset.z + _offset.z
  -- Modifier for z-axis depending on pitch of camera
  local pitch = math.abs(Quaternion.pitch(rot))
  local percent_change = pitch / math.pi
  offset.z = offset.z - ((2 * offset.z) * percent_change)
  
  -- Apply offset to get final player position
  local x = offset.x * Quaternion.right(rot)
  local y = offset.y * Quaternion.forward(rot)
  local z = Vector3(0, 0, offset.z)
  pos = pos + x + y + z
  
  PlayerMovement.teleport(player, pos, rot)
end

mod.toggle_lock_camera = function(self)
  lock_camera = not lock_camera
  if lock_camera then
    mod:hook_disable(CLASS.InputService, "_get")
    mod:hook_disable(CLASS.InputService, "_get_simulate")
  else
    mod:hook_enable(CLASS.InputService, "_get")
    mod:hook_enable(CLASS.InputService, "_get_simulate")
  end
  mod:echo("lock_camera: " .. tostring(lock_camera))
end

local null_movement_hook = function (func, self, action_name, ...)
  if _freeflight_data.enable_freeflight and null_input_types[action_name] then
    if action_name == "look_raw" and not lock_camera then
      return Vector3.zero()
    end
    return 0
  end

  return func(self, action_name, ...)
end

mod.use_3p_hub_camera = function(self)
  if Managers.state and Managers.state.game_mode and Managers.state.game_mode.use_third_person_hub_camera then
    return Managers.state.game_mode:use_third_person_hub_camera()
  end
  
  return false
end

mod.is_in_free_flight = function(self)
  return Managers.free_flight and Managers.free_flight:is_in_free_flight()
end

mod.get_player = function(self)
  local player_manager = Managers.player
  local player = player_manager and Network and player_manager:local_player(1)
  
  return player
end

mod.get_player_unit = function(self)
  local player = mod:get_player()
  local player_unit = player and player:unit_is_alive() and player.player_unit
  
  return player_unit
end

mod.set_3p = function(self, enabled)
  local player_unit = mod:get_player_unit()
  if player_unit then
    local player_1p_extension = ScriptUnit.has_extension(player_unit, "first_person_system")
    if player_1p_extension then
      player_1p_extension._force_third_person_mode = enabled
    end
  end
end

mod.toggle_freeflight = function(self)
  if Managers.ui:chat_using_input() then
    return
  end
  _freeflight_data.enable_freeflight = not _freeflight_data.enable_freeflight
end

mod.toggle_look_input = function(self)
  if Managers.ui:chat_using_input() then
    return
  end
  look_input = not look_input
end

-- ##########################################################
-- #################### Hooks ###############################

-- Custom handlers for look and teleport player
mod:hook_require(free_flight_default_input_path, function(instance)
  if not instance.toggle_look_input then
    instance.toggle_look_input = pressed_toggle_look_input
  end
  if not instance.teleport_player_to_camera then
    instance.teleport_player_to_camera = pressed_teleport_player_to_camera
  end
  if not instance.roll_left then
    instance.roll_left = pressed_roll_left
  end
  if not instance.roll_right then
    instance.roll_right = pressed_roll_right
  end
end)

-- Restore freeflight in views
mod:hook_origin(CLASS.FreeFlightManager, "_get_input", function (self)
  local imgui_manager = Managers.imgui
  local input = imgui_manager and imgui_manager:using_input() and self._input:null_service() or self._input

  return input
end)

-- Handle player teleport
mod:hook_safe(CLASS.FreeFlightManager, "_update_camera", function (self, input_, dt_, camera_data)
  if mod.settings["nf_teleport_player"] and is_server() then
    local world = Managers.world:world(camera_data.viewport_world_name)
    local viewport = world and ScriptWorld.global_free_flight_viewport(world)
    local cam = camera_data.frustum_freeze_camera or viewport and ScriptViewport.camera(viewport)
    
    -- Always change player position while flying
    local player = Managers.player:local_player(1)
    if cam and player then
      teleport_player_to_camera(cam, player)
    end
  end
  if mod.settings["nf_toggle_debug"] then
    mod:hook_enable(CLASS.FreeFlightManager, "_handle_dof")
  elseif not mod.settings["nf_toggle_debug"] then 
    mod:hook_disable(CLASS.FreeFlightManager, "_handle_dof")
  end
end)

mod:hook_safe(CLASS.FreeFlightManager, "_reset_dof", function (self, data)
--[[
    mod:echo("Dof reset")
	mod:echo("Dof Focal Distance: %f", data.dof_focal_distance)
	mod:echo("Dof Focal Region: %f", data.dof_focal_region)
	mod:echo("Dof Focal Padding: %f", data.dof_focal_region_start)
	mod:echo("Dof Focal Scale: %f", data.dof_focal_near_scale)
    ]]
    if self._follow_path then
        self:toggle_follow_path()
    end
end)

mod:hook_safe(CLASS.FreeFlightManager, "_handle_dof", function (self, dt, input, data, world)
    local bottom_viewport = ScriptWorld.bottom_viewport(world)
    if not bottom_viewport then
        mod:echo("_handle_dof hook: not bottom_viewport, returning")
		return
	end
    local shading_env = Viewport.get_data(bottom_viewport, "shading_environment")

	if input:get("toggle_dof") and not input:get("reset_dof") then
        mod:echo("Dof: " .. tostring(data.dof_enabled))
        mod:echo("ShadingEnvironment dof_enabled: " .. tostring(ShadingEnvironment.scalar(shading_env, "dof_enabled")))
	end

	if input:get("inc_dof_distance") then
		mod:echo("Dof Focal Distance: %f", data.dof_focal_distance)
	end

	if input:get("dec_dof_distance") then
		mod:echo("Dof Focal Distance: %f", data.dof_focal_distance)
	end

	if input:get("inc_dof_region") then
		mod:echo("Dof Focal Region: %f", data.dof_focal_region)
	end

	if input:get("dec_dof_region") then
		mod:echo("Dof Focal Region: %f", data.dof_focal_region)
	end

	if input:get("inc_dof_padding") then
        mod:echo("Dof Focal Padding: %f", data.dof_focal_region_start)
	end

	if input:get("dec_dof_padding") then
		mod:echo("Dof Focal Padding: %f", data.dof_focal_region_start)
	end

	if input:get("inc_dof_scale") then
		mod:echo("Dof Focal Scale: %f", data.dof_focal_near_scale)
	end

	if input:get("dec_dof_scale") then
		mod:echo("Dof Focal Scale: %f", data.dof_focal_near_scale)
	end
end)

local _table_contains = function(tbl, v)
    for i = 1, #tbl do
        if tbl[i] == v then
            return true
        end
    end
    return false
end

-- Override vanilla toggle and fix missing hotkeys
mod:hook(CLASS.FreeFlightDefaultInput, "get", function (func, self, action_name, ...)
  if action_name == "global_toggle" then
    if mod:is_in_free_flight() then
      return not _freeflight_data.enable_freeflight
    else
      return _freeflight_data.enable_freeflight
    end
  end
  if lock_camera then
    if action_name == "move_forward" or action_name == "move_backward" or action_name == "move_left" or action_name == "move_right" then
      return 0
    end
    if action_name == "look" then
      return Vector3.zero()
    end
  end

  -- Prevent missing hotkeys
  if not self[action_name] then
    return false
  end

  return func(self, action_name, ...)
end)

-- Detach player movement from camera
mod:hook(CLASS.InputService, "_get", null_movement_hook)

-- Detach simulated player movement from camera
mod:hook(CLASS.InputService, "_get_simulate", null_movement_hook)

-- Allow freeflight when gameplay starts
mod:hook_safe(CLASS.StateGameplay, "on_enter", function (self)
  _freeflight_data.ready = true
end)

-- Prevent freeflight until gameplay starts
mod:hook_safe(CLASS.StateGameplay, "on_exit", function (self)
  _freeflight_data.ready = false
end)

-- Handle updating the freeflight manager
mod:hook_safe(CLASS.StateGame, "update", function (self, dt)
  if Managers.free_flight then
    local main_t = Managers.time:time("main")
    Managers.free_flight:update(dt, main_t)
    
    if _freeflight_data.ready and not mod:use_3p_hub_camera() then
      mod:set_3p(mod:is_in_free_flight())
    end
  end
end)

-- End freeflight when game world is disabled
mod:hook_safe(CLASS.UIManager, "open_view", function (self, view_name)
  local view_settings = Views[view_name]
  if not view_settings or view_settings.disable_game_world == true then
    _freeflight_data.enable_freeflight = false
  end
end)

-- End freeflight when game world was disabled
mod:hook_safe(CLASS.UIManager, "close_view", function (self, view_name)
  local view_settings = Views[view_name]
  if not view_settings or view_settings.disable_game_world == true then
    _freeflight_data.enable_freeflight = false
  end
end)

local function split_with_comma(str)
  local fields = {}
  for field in str:gmatch('([^,]+)') do
    fields[#fields+1] = field
  end
  return fields
end

--[[
mod:command("offset", mod:localize("cmd_offset"), function(offset_str) 
    mod:echo("input: " .. tostring(offset_str))
	local offset_str_tbl = split_with_comma(offset_str)
    for i, axis_offset in ipairs(offset_str_tbl) do
        if axis_offset then
            _offset[i] = tonumber(axis_offset)
            mod:echo("_offset[%d]: %f", i, _offset[i])
        end
    end
    ]]
    --mod:echo("axis_offset: " .. tostring(_offset[1]) .. ", " .. tostring(_offset[2]) .. ", " .. tostring(_offset[3]))
    --[[

    
	if not offset then
		mod:echo("bad offset")
		return
	end

	for i, axis_offset in ipairs(offset_str_tbl) do
		if axis_offset then
            if i == 1 then
                offset_x = tonumber(axis_offset)
            end
            if i == 2 then
                offset_y = tonumber(axis_offset)
            end
            if i == 3 then
                offset_z = tonumber(axis_offset)
            end

		end
	end
    ]]
--end)
-- ##########################################################
-- ################### Callback #############################

-- Call when game state changes (e.g. StateLoading -> StateIngame)
mod.on_game_state_changed = function(status, state)
  _freeflight_data.enable_freeflight = false
end

-- Call when setting is changed in mod settings
mod.on_setting_changed = function(setting_name)
  mod.settings[setting_name] = mod:get(setting_name)
end

-- ##########################################################
-- ################### Script ###############################

Managers.free_flight = Managers.free_flight or FreeFlightManager:new()

Managers.free_flight.STD_MINIMUM_SPEED = 0.5
Managers.free_flight.STD_MAXIMUM_SPEED = 30

_freeflight_data.enable_freeflight = false

initialize_settings_cache()

-- ##########################################################
