local pluginName, pluginShared = ...
local L = LibStub("AceLocale-3.0"):GetLocale("AppearanceMatic")

-- The collection announce. One line per collected starred appearance or mount: to the guild when
-- its box is on, to exactly one group channel when grouped (battleground: instance chat; raid: raid;
-- party: party; each behind its own box), and to the emote channel only when no group line went
-- (not grouped, the group's box is off, or emote is the only box on).
local Social = {}
pluginShared.Social = Social

local MARKER = 8 -- the raid-target icon the family's group lines open with
local HANDLE = "AppearanceMatic"
local PINK = "|cffffb6d9"
-- The star, twice over: {rt1} is the tag the chat system turns into the star icon for everyone who
-- reads the line (ICON_TAG_LIST, ChatFrameConstants.lua:42), while a printed line is never parsed for
-- tags, so that one carries the texture itself (ICON_LIST[1], ChatFrameConstants.lua:30-31).
local STAR_TAG = "{rt1}"
local STAR_ICON = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_1:0|t"

local function boxes()
    local profile = pluginShared.db and pluginShared.db.profile
    return profile and profile.social or nil
end

-- Where the player stands right now
function Social:Grouping()
    local inInstance, instanceType = false, nil
    if type(IsInInstance) == "function" then
        inInstance, instanceType = IsInInstance()
    end
    return {
        grouped = type(IsInGroup) == "function" and IsInGroup() and true or false,
        raid = type(IsInRaid) == "function" and IsInRaid() and true or false,
        lfg = type(IsPartyLFG) == "function" and IsPartyLFG() and true or false,
        battleground = (inInstance and instanceType == "pvp") and true or false,
        guild = type(IsInGuild) == "function" and IsInGuild() and true or false,
    }
end

-- The chat types one collection reaches, from the Social boxes and the grouping
function Social:ChannelsFor(social, grouping)
    local out = {}
    if type(social) ~= "table" or type(grouping) ~= "table" then
        return out
    end
    local groupLine = false
    if grouping.grouped then
        local chatType, allowed
        if grouping.battleground then
            chatType, allowed = "INSTANCE_CHAT", social.battleground
        elseif grouping.lfg then
            chatType, allowed = "INSTANCE_CHAT", grouping.raid and social.raid or social.party
        elseif grouping.raid then
            chatType, allowed = "RAID", social.raid
        else
            chatType, allowed = "PARTY", social.party
        end
        if allowed then
            out[#out + 1] = chatType
            groupLine = true
        end
    end
    if social.guild and grouping.guild then
        out[#out + 1] = "GUILD"
    end
    if social.emote and not groupLine then
        out[#out + 1] = "EMOTE"
    end
    return out
end

-- Group and guild lines go through the family's broadcaster (marker, dedupe); emotes go straight out
function Social:Send(text, chatType)
    if chatType == "EMOTE" then
        local sender = (type(C_ChatInfo) == "table" and C_ChatInfo.SendChatMessage) or SendChatMessage
        if type(sender) == "function" then
            sender(text, "EMOTE")
        end
        return
    end
    local Broadcast = LibStub and LibStub("LibMatic-Broadcast-1.0", true)
    if Broadcast then
        Broadcast:Broadcast(HANDLE, { text = text, marker = MARKER, channel = chatType })
    elseif type(SendChatMessage) == "function" then
        SendChatMessage("{rt" .. MARKER .. "} " .. text, chatType)
    end
end

-- Seconds between guild lines: a lucky run announces everything, one line a minute, none dropped.
local GUILD_GAP_SECONDS = 60

-- The guild queue: the first line goes at once, the rest wait their turn in order. With no clock or
-- timer to pace by, lines go straight out.
function Social:PushGuild(text)
    local now = type(GetTime) == "function" and GetTime() or nil
    local canWait = now and type(C_Timer) == "table" and type(C_Timer.After) == "function"
    if not canWait then
        self:Send(text, "GUILD")
        return
    end
    if self.guildQueue and #self.guildQueue > 0 then
        self.guildQueue[#self.guildQueue + 1] = text
        return
    end
    if not self.guildSentAt or (now - self.guildSentAt) >= GUILD_GAP_SECONDS then
        self.guildSentAt = now
        self:Send(text, "GUILD")
        return
    end
    self.guildQueue = self.guildQueue or {}
    self.guildQueue[#self.guildQueue + 1] = text
    self:ArmGuildDrain((self.guildSentAt + GUILD_GAP_SECONDS) - now)
end

function Social:ArmGuildDrain(delay)
    if self.guildDrainArmed then
        return
    end
    self.guildDrainArmed = true
    C_Timer.After(delay, function()
        Social.guildDrainArmed = nil
        local queue = Social.guildQueue
        local text = queue and table.remove(queue, 1)
        if not text then
            return
        end
        Social.guildSentAt = type(GetTime) == "function" and GetTime() or Social.guildSentAt
        Social:Send(text, "GUILD")
        if #queue > 0 then
            Social:ArmGuildDrain(GUILD_GAP_SECONDS)
        end
    end)
end

function Social:LineFor(kind, label, chatType)
    local key
    if chatType == "EMOTE" then
        key = kind == "mount" and "social_emote_mount_fmt" or "social_emote_appearance_fmt"
    else
        key = kind == "mount" and "social_collected_mount_fmt" or "social_collected_appearance_fmt"
    end
    return string.format(L[key], label)
end

-- kind: "appearance" or "mount"; label: the item or mount link (its name when no link is known).
-- Returns the chat types the line went to.
function Social:Announce(kind, label)
    local social = boxes()
    if not (social and type(label) == "string" and label ~= "") then
        return {}
    end
    local sent = {}
    for _, chatType in ipairs(self:ChannelsFor(social, self:Grouping())) do
        if chatType == "GUILD" then
            self:PushGuild(self:LineFor(kind, label, chatType))
        else
            self:Send(self:LineFor(kind, label, chatType), chatType)
        end
        sent[#sent + 1] = chatType
    end
    return sent
end

-- Every line the addon says for itself: the star, its name, then the message. It goes out as an emote
-- when the Emote box is on, so the people around you read what you are chasing; with the box off it is
-- printed where only you can see it. Returns how it went out.
function Social:Line(text)
    if type(text) ~= "string" or text == "" then
        return nil
    end
    local social = boxes()
    if social and social.emote then
        local sender = (type(C_ChatInfo) == "table" and C_ChatInfo.SendChatMessage) or SendChatMessage
        if type(sender) == "function" then
            sender(string.format(L["chat_line_fmt"], STAR_TAG, HANDLE, text), "EMOTE")
            return "EMOTE"
        end
    end
    return self:Tell(text)
end

-- The same line, for the player alone: removing a route or a star is bookkeeping the people around you
-- do not need to read, so it is printed whatever the Emote box says.
function Social:Tell(text)
    if type(text) ~= "string" or text == "" then
        return nil
    end
    print(string.format(L["chat_line_fmt"], STAR_ICON, PINK .. HANDLE .. "|r", text))
    return "PRINT"
end
