function SquadMenu.GetLanguageText( id )
    return language.GetPhrase( "squad_menu." .. id ):Trim()
end

function SquadMenu:PlayUISound( path )
    if self.Config.enableSounds then
        sound.Play( path, Vector(), 0, 120, 0.75 )
    end
end

local L = SquadMenu.GetLanguageText

function SquadMenu.GlobalMessage( ... )
    chat.AddText( SquadMenu.THEME_COLOR, "[" .. L( "title" )  .. "] ", Color( 255, 255, 255 ), ... )
end

function SquadMenu.SquadMessage( ... )
    local squad = SquadMenu.mySquad
    if not squad then return end

    local contents = { color_white, "[", squad.color, squad.name, color_white, "] ", ... }

    if CustomChat then
        CustomChat:AddMessage( contents, "squad" )
    else
        chat.AddText( unpack( contents ) )
    end
end

function SquadMenu.LeaveMySquad( buttonToBlank, leaveNow )
    local squad = SquadMenu.mySquad
    if not squad then return end

    if not leaveNow and squad.leaderId == SquadMenu.GetPlayerId( LocalPlayer() ) then
        Derma_Query( L"leave_leader", L"leave_squad", L"yes", function()
            SquadMenu.LeaveMySquad( buttonToBlank, true )
        end, L"no" )

        return
    end

    if IsValid( buttonToBlank ) then
        buttonToBlank:SetEnabled( false )
        buttonToBlank:SetText( "..." )
    end

    SquadMenu.StartCommand( SquadMenu.LEAVE_SQUAD )
    net.SendToServer()
end

--- If GMinimap is installed, update squad members' blips.
function SquadMenu:UpdatePlayerBlips( icon, color )
    if not self.mySquad then return end

    local me = LocalPlayer()
    local byId = self.AllPlayersById()

    for _, member in ipairs( self.mySquad.members ) do
        local ply = byId[member.id]

        if ply and ply ~= me then
            ply:SetBlipIcon( icon )
            ply:SetBlipColor( color )
        end
    end
end

--- Set the current members of the local player's squad.
--- Updates the HUD and shows join/leave messages (if `printMessages` is `true`).
---
--- `newMembers` is an array where items are also arrays
--- with a number (player id) and a string (player name).
function SquadMenu:SetMembers( newMembers, printMessages )
    local members = self.mySquad.members
    local membersById = self.mySquad.membersById
    local keep = {}

    -- Add new members that we do not have on our end
    for _, m in ipairs( newMembers ) do
        local id = m[1]
        local member = { id = id, name = m[2] }

        keep[id] = true

        if not membersById[id] then
            membersById[id] = member
            members[#members + 1] = member

            self:AddMemberToHUD( member )

            if printMessages then
                self.SquadMessage( string.format( L"member_joined", member.name ) )
            end
        end
    end

    local byId = self.AllPlayersById()

    -- Remove members that we have locally but do not exist on `newMembers`.
    -- Backwards loop because we use `table.remove`
    for i = #members, 1, -1 do
        local member = members[i]
        local id = member.id

        if not keep[id] then
            membersById[id] = nil
            table.remove( members, i )

            self:RemoveMemberFromHUD( member )

            if printMessages then
                self.SquadMessage( string.format( L"member_left", member.name ) )
            end

            local ply = byId[id]

            if IsValid( ply ) and GMinimap then
                ply:SetBlipIcon( nil )
                ply:SetBlipColor( nil )
            end
        end
    end
end

--- Set the local player's squad.
--- `data` is a table that comes from `squad:GetBasicInfo`.
function SquadMenu:SetupSquad( data )
    local squad = self.mySquad or { id = -1 }
    local isUpdate = data.id == squad.id

    self.mySquad = squad

    squad.id = data.id
    squad.name = data.name
    squad.icon = data.icon

    squad.leaderId = data.leaderId
    squad.leaderName = data.leaderName or ""

    squad.enableRings = data.enableRings
    squad.friendlyFire = data.friendlyFire
    squad.isPublic = data.isPublic

    squad.color = Color( data.r, data.g, data.b )

    if CustomChat and squad.name then
        CustomChat:CreateCustomChannel( "squad", squad.name, squad.icon )
    end

    if not isUpdate then
        squad.requests = {}
        squad.members = {}
        squad.membersById = {}

        self:PlayUISound( "buttons/combine_button3.wav" )
        self.SquadMessage( L"squad_welcome", squad.color, " " .. squad.name )
        self.SquadMessage( L"chat_tip", " " .. table.concat( self.CHAT_PREFIXES, ", " ) )
    end

    self:UpdateMembersHUD()
    self:SetMembers( data.members, isUpdate )

    if IsValid( self.frame ) then
        self:RequestSquadListUpdate()
        self:UpdateSquadStatePanel()
        self:UpdateRequestsPanel()
        self:UpdateSquadMembersPanel()
        self:UpdateSquadPropertiesPanel()

        self.frame:SetActiveTabByIndex( 3 ) -- squad members
    end

    if GMinimap then
        self:UpdatePlayerBlips( "gminimap/blips/npc_default.png", squad.color )

        hook.Add( "CanSeePlayerBlip", "ShowSquadBlips", function( ply )
            if ply:GetSquadID() == squad.id then return true, 50000 end
        end )
    end
end

function SquadMenu:OnLeaveSquad( reason )
    if GMinimap then
        self:UpdatePlayerBlips( nil, nil )
        hook.Remove( "CanSeePlayerBlip", "ShowSquadBlips" )
    end

    local reasonText = {
        [self.LEAVE_REASON_DELETED] = "deleted_squad",
        [self.LEAVE_REASON_KICKED] = "kicked_from_squad"
    }

    if self.mySquad then
        self.GlobalMessage( L( reasonText[reason] or "left_squad" ) )
        self:PlayUISound( "buttons/combine_button2.wav" )
    end

    self.mySquad = nil
    self:RemoveMembersHUD()

    if IsValid( self.frame ) then
        self:RequestSquadListUpdate()
        self:UpdateSquadStatePanel()
        self:UpdateRequestsPanel()
        self:UpdateSquadMembersPanel()
        self:UpdateSquadPropertiesPanel()

        if self.frame.lastTabIndex ~= 5 then -- not in settings
            self.frame:SetActiveTabByIndex( 1 ) -- squad list
        end
    end

    if CustomChat then
        CustomChat:RemoveCustomChannel( "squad" )
    end
end

----------

local commands = {}

commands[SquadMenu.SQUAD_LIST] = function()
    SquadMenu:UpdateSquadList( SquadMenu.ReadTable() )
end

commands[SquadMenu.SETUP_SQUAD] = function()
    local data = SquadMenu.ReadTable()
    SquadMenu:SetupSquad( data )
end

commands[SquadMenu.LEAVE_SQUAD] = function()
    local reason = net.ReadUInt( 3 )
    SquadMenu:OnLeaveSquad( reason )
end

commands[SquadMenu.REQUESTS_LIST] = function()
    local squad = SquadMenu.mySquad
    if not squad then return end

    local requests = squad.requests

    -- Remember which players have requested before
    local alreadyRequested = {}

    for _, member in ipairs( requests ) do
        alreadyRequested[member.id] = true
    end

    -- Compare the new requests against what we already got
    local requestsById = SquadMenu.ReadTable()
    local newCount = 0

    for id, name in pairs( requestsById ) do
        if not alreadyRequested[id] then
            -- This is a new request for us
            requests[#requests + 1] = { id = id, name = name }
            newCount = newCount + 1

            SquadMenu.SquadMessage( string.format( L"request_message", name ) )
        end
    end

    if newCount > 0 then
        SquadMenu:PlayUISound( "buttons/combine_button1.wav" )
    end

    -- Remove requests we already got if they aren't on the new requests list
    for i = #requests, 1, -1 do
        local member = requests[i]

        if not requestsById[member.id] then
            table.remove( requests, i )
        end
    end

    SquadMenu:UpdateRequestsPanel()
end

commands[SquadMenu.PING] = function()
    local pos = net.ReadVector()
    local label = net.ReadString()
    local id = net.ReadString()

    local ping = SquadMenu.pings[id]

    if not ping then
        ping = {}
    end

    ping.pos = pos
    ping.label = label
    ping.start = RealTime()
    ping.lifetime = 5

    SquadMenu.pings[id] = ping

    if not SquadMenu.Config.enableSounds then return end

    local eyePos = EyePos()
    local soundDir = pos - eyePos
    soundDir:Normalize()

    sound.Play( "friends/friend_join.wav", eyePos + soundDir * 500, 100, 120, 1 )
end

commands[SquadMenu.BROADCAST_EVENT] = function()
    local data = SquadMenu.ReadTable()
    local event = data.event

    SquadMenu.PrintF( "Event received: %s", event )

    if event == "open_menu" then
        SquadMenu:OpenFrame()

    elseif event == "squad_position_changed" then
        if SquadMenu.membersPanel then
            SquadMenu.membersPanel:InvalidateLayout()
        end

    elseif event == "player_joined_squad" then
        local squad = SquadMenu.mySquad
        if not squad then return end

        -- Remove this player from my requests list
        local requests = squad.requests

        for i, member in ipairs( requests ) do
            if data.playerId == member.id then
                table.remove( requests, i )
                break
            end
        end

        SquadMenu:UpdateRequestsPanel()

    elseif event == "squad_created" or event == "squad_deleted" then
        SquadMenu:RequestSquadListUpdate()

        if event == "squad_created" and data.name and SquadMenu.GetShowCreationMessage() then
            local color = Color( data.r, data.g, data.b )
            SquadMenu.GlobalMessage( string.format( L"squad_created", data.leaderName ), color, " " .. data.name )
        end

    elseif event == "members_chat" then
        local squad = SquadMenu.mySquad
        if not squad then return end

        SquadMenu.SquadMessage( squad.color, data.senderName, color_white, ": ", data.text )
    end
end

net.Receive( "squad_menu.command", function()
    local cmd = net.ReadUInt( SquadMenu.COMMAND_SIZE )

    if not commands[cmd] then
        SquadMenu.PrintF( "Received a unknown network command! (%d)", cmd )
        return
    end

    commands[cmd]( ply, ent )
end )

concommand.Add(
    "squad_menu",
    function() SquadMenu:OpenFrame() end,
    nil,
    "Opens the squad menu."
)

if engine.ActiveGamemode() == "sandbox" then
    list.Set(
        "DesktopWindows",
        "SquadMenuDesktopIcon",
        {
            title = SquadMenu.GetLanguageText( "title" ),
            icon = "materials/squad_menu/squad_menu.png",
            init = function() SquadMenu:OpenFrame() end
        }
    )
end
