TFA._KeyBindTable = TFA._KeyBindTable or {}
local KeyBindTable = TFA._KeyBindTable

local cv_prefix = "cl_tfa_keys_"

local sp = game.SinglePlayer()

if sp then -- THANK YOU GARRY FOR HIGH QUALITY PREDICTION IN SINGLEPLAYER
	if SERVER then
		util.AddNetworkString("TFA_KB_State")
		util.AddNetworkString("TFA_KB_Think")
	end

	if CLIENT then
		net.Receive("TFA_KB_State", function()
			local ply = LocalPlayer()

			local bind = net.ReadString()
			local state = net.ReadBool()

			local data = KeyBindTable[bind]

			if data and data.state ~= state then
				data.state = state

				if state then
					data.onpress(ply)
				else
					data.onrelease(ply)
				end
			end
		end)

		net.Receive("TFA_KB_Think", function()
			local ply = LocalPlayer()

			local bind = net.ReadString()

			local data = KeyBindTable[bind]

			if data and data.think and data.state then
				data.think(ply)
			end
		end)
	end
end

local function empty()
end

function TFA.RegisterKeyBind(data_in)
	assert(type(data_in) == "table", "Data must be a table!")
	assert(data_in.bind and type(data_in.bind) == "string", "Invalid bind name!")
	-- assert(not TFA._KeyBindTable[data.bind], "Keybind already registered!")

	local data = table.Copy(data_in)

	if not data.onpress then
		data.onpress = empty
	elseif type(data.onpress) ~= "function" then
		error("data.onpress - function expected, got " .. type(data.onpress))
	end

	if not data.onrelease then
		data.onrelease = empty
	elseif type(data.onrelease) ~= "function" then
		error("data.onrelease - function expected, got " .. type(data.onrelease))
	end

	data.state = false

	if CLIENT and GetConVar(cv_prefix .. data.bind) == nil then
		CreateClientConVar(cv_prefix .. data.bind, 0, true, true, data.desc)
	end

	hook.Add("PlayerButtonDown", "TFA_KB_KeyDown_" .. data.bind, function(ply, button)
		if not IsFirstTimePredicted() then return end
		local cv_key = ply:GetInfoNum(cv_prefix .. data.bind, 0)

		if cv_key > 0 and cv_key == button and not data.state then
			data.state = true
			data.onpress(ply)

			if sp and SERVER then
				net.Start("TFA_KB_State", true)
				net.WriteString(data.bind)
				net.WriteBool(data.state)
				net.Send(ply)
			end
		end
	end)

	hook.Add("PlayerButtonUp", "TFA_KB_KeyUp_" .. data.bind, function(ply, button)
		if not IsFirstTimePredicted() then return end
		local cv_key = ply:GetInfoNum(cv_prefix .. data.bind, 0)

		if cv_key > 0 and cv_key == button and data.state then
			data.state = false
			data.onrelease(ply)

			if sp and SERVER then
				net.Start("TFA_KB_State", true)
				net.WriteString(data.bind)
				net.WriteBool(data.state)
				net.Send(ply)
			end
		end
	end)

	hook.Remove("PlayerPostThink", "TFA_KB_Think_" .. data.bind)

	if data.think and type(data.think) == "function" then
		hook.Add("PlayerPostThink", "TFA_KB_Think_" .. data.bind, function(ply)
			if data.state then
				data.think(ply)

				if sp and SERVER then
					net.Start("TFA_KB_Think", true)
					net.WriteString(data.bind)
					net.Send(ply)
				end
			end
		end)
	end

	KeyBindTable[data.bind] = data
end

if CLIENT then -- Populate spawnmenu settings with registered keybinds
	local function tfaOptionKeys(panel)
		panel:Help("#tfa.keybinds.help.bind")
		panel:Help("#tfa.keybinds.help.bound")
		panel:Help("#tfa.keybinds.help.unbind")
		panel:Help("")

		for _, data in pairs(KeyBindTable) do
			local cv = GetConVar(cv_prefix .. data.bind)

			if cv then
				panel:Help("#tfa.keybind." .. data.bind)

				local binder = vgui.Create("DBinder")

				binder:SetValue(cv:GetInt())

				function binder:OnChange(newcode)
					cv:SetInt(newcode)
				end

				panel:AddItem(binder)
				panel:Help("")
			end
		end
	end

	hook.Add("PopulateToolMenu", "TFA_AddKeyBinds", function()
		spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "TFASwepBaseKeybinds", "#tfa.smsettings.keybinds", "", "", tfaOptionKeys)
	end)
end

-- Default keybinds
TFA.RegisterKeyBind({
	bind = "bash",

	onpress = function(plyv)
		if not plyv:IsValid() then return end

		plyv.tfa_bash_hack = true
	end,

	onrelease = function(plyv)
		if not plyv:IsValid() then return end

		plyv.tfa_bash_hack = false
	end
})

TFA.RegisterKeyBind({
	bind = "customize",
	onpress = CLIENT and function(plyv)
		if not plyv:IsValid() then return end

		RunConsoleCommand("impulse", TFA.INSPECTION_IMPULSE_STRING)
	end
})

TFA.RegisterKeyBind({
	bind = "inspect",
	onpress = function(plyv)
		local wepv = plyv:GetActiveWeapon()

		if (IsValid(wepv) and wepv.GetStat) and (wepv:GetActivityEnabled(ACT_VM_FIDGET) or wepv.InspectionActions) and wepv:GetStatus() == TFA.Enum.STATUS_IDLE then
			local _, tanim, ttype = wepv:ChooseInspectAnim()
			wepv:ScheduleStatus(TFA.Enum.STATUS_FIDGET, wepv:GetActivityLength(tanim, false, ttype))
		end
	end
})

TFA.RegisterKeyBind({
	bind = "firemode",
	onpress = CLIENT and function(plyv)
		local wepv = plyv:GetActiveWeapon()

		if IsValid(wepv) and wepv.GetStat then
			if wepv:GetStatL("SelectiveFire") and not wepv:GetOwner():KeyDown(IN_SPEED) then
				RunConsoleCommand("impulse", TFA.CYCLE_FIREMODE_IMPULSE_STRING)
			elseif wepv:GetOwner():KeyDown(IN_SPEED) then
				RunConsoleCommand("impulse", TFA.CYCLE_SAFETY_IMPULSE_STRING)
			end
		end
	end
})

TFA.RegisterKeyBind({
	bind = "silencer",
	onpress = function(plyv)
		local wepv = plyv:GetActiveWeapon()

		if (IsValid(wepv) and wepv.GetStat) and wepv:GetStatRawL("CanBeSilenced") and TFA.Enum.ReadyStatus[wepv:GetStatus()] then
			local _, tanim, ttype = wepv:ChooseSilenceAnim(not wepv:GetSilenced())
			wepv:ScheduleStatus(TFA.Enum.STATUS_SILENCER_TOGGLE, wepv:GetActivityLength(tanim, true, ttype))
		end
	end
})

-- EXAMPLE KEYBIND:
--[[
	TFA.RegisterKeyBind({
		bind = "whatever", -- bind id, cvar is cl_tfa_keys_whatever
		onpress = function(ply) end, -- function called on key press
		onrelease = function(ply) end, -- function called on key release
		think = function(ply) end, -- called from PlayerPostThink when key is held down
	})
]]
