local addonName = ...
local addonPrefix = "WNW2"
local separator = "\031"

local RefreshRequestList
local lastBroadcastTime = 0
local lastAutomaticSyncTime = 0

local function WNWPrint(color, message)
    print(color .. "Who Needs What:|r " .. message)
end

local function GetPlayerFullName()
    local name, realm = UnitFullName("player")
    if not name then return "Unknown" end
    if realm and realm ~= "" then return name .. "-" .. realm end
    return name
end

local function GetShortName(name)
    return name and (name:match("^[^-]+") or name) or ""
end

local function IsSamePlayer(firstName, secondName)
    if firstName == secondName then return true end
    if not firstName or not secondName then return false end

    local firstHasRealm = firstName:find("-", 1, true)
    local secondHasRealm = secondName:find("-", 1, true)

    if firstHasRealm and secondHasRealm then return false end
    return GetShortName(firstName) == GetShortName(secondName)
end

local function CanGuildSync()
    local insideInstance = IsInInstance()
    return IsInGuild() and not insideInstance
end

local function RequestExists(requestID)
    if not WhoNeedsWhatDB or not WhoNeedsWhatDB.requests then return false end

    for _, request in ipairs(WhoNeedsWhatDB.requests) do
        if request.id == requestID then return true end
    end

    return false
end

local function SendRequest(request)
    if not CanGuildSync() or not request.id or not request.item then
        return false
    end

    local message = table.concat({
        "ADD",
        request.id,
        request.player or "Unknown",
        tostring(request.quantity or 1),
        request.item
    }, separator)

    C_ChatInfo.SendAddonMessage(addonPrefix, message, "GUILD")
    return true
end

local function SendDelete(requestID)
    if not CanGuildSync() or not requestID then return false end

    local message = table.concat({ "DELETE", requestID }, separator)
    C_ChatInfo.SendAddonMessage(addonPrefix, message, "GUILD")
    return true
end

local function SendMyRequests()
    if not CanGuildSync() or not WhoNeedsWhatDB then return end

    local now = GetTime()
    if now - lastBroadcastTime < 3 then return end
    lastBroadcastTime = now

    local currentPlayer = GetPlayerFullName()
    local clearMessage = table.concat({ "CLEAR", currentPlayer }, separator)
    C_ChatInfo.SendAddonMessage(addonPrefix, clearMessage, "GUILD")

    local numberToSend = 0
    for _, request in ipairs(WhoNeedsWhatDB.requests) do
        if IsSamePlayer(request.player, currentPlayer) then
            numberToSend = numberToSend + 1
            local requestToSend = request
            local delay = numberToSend * 0.15

            C_Timer.After(delay, function()
                SendRequest(requestToSend)
            end)
        end
    end
end

local function RequestGuildSync(showMessage)
    if not CanGuildSync() then
        if showMessage then
            WNWPrint(
                "|cffffaa00",
                "Guild syncing is available outside instances."
            )
        end
        return
    end

    C_ChatInfo.SendAddonMessage(addonPrefix, "SYNC_REQUEST", "GUILD")
    SendMyRequests()

    if showMessage then
        WNWPrint(
            "|cff33ff99",
            "Asked online guildmates for their requests."
        )
    end
end

-- Load saved data and register the sync channel.
local loader = CreateFrame("Frame")
loader:RegisterEvent("ADDON_LOADED")

loader:SetScript("OnEvent", function(self, event, loadedAddonName)
    if loadedAddonName ~= addonName then return end

    WhoNeedsWhatDB = WhoNeedsWhatDB or {}
    WhoNeedsWhatDB.requests = WhoNeedsWhatDB.requests or {}

    local currentPlayer = GetPlayerFullName()
    local migrationTime = GetServerTime()

    for index, request in ipairs(WhoNeedsWhatDB.requests) do
        request.player = request.player or currentPlayer

        if not request.id then
            request.id = table.concat({
                request.player,
                "legacy",
                migrationTime,
                index,
                math.random(1000, 9999)
            }, ":")
        end
    end

    local result = C_ChatInfo.RegisterAddonMessagePrefix(addonPrefix)

    if result == Enum.RegisterAddonMessagePrefixResult.Success then
        WNWPrint("|cff33ff99", "Guild sync channel ready!")
    else
        WNWPrint("|cffff4444", "Could not register the sync channel.")
    end

    WNWPrint(
        "|cff33ff99",
        "Loaded! Saved requests: " .. #WhoNeedsWhatDB.requests
    )

    self:UnregisterEvent("ADDON_LOADED")
end)

-- Receive sync messages from guildmates.
local syncFrame = CreateFrame("Frame")
syncFrame:RegisterEvent("CHAT_MSG_ADDON")

syncFrame:SetScript("OnEvent", function(
    self,
    event,
    prefix,
    message,
    channel,
    sender
)
    if prefix ~= addonPrefix then return end

    if message == "PING" then
        WNWPrint("|cff33ff99", "Received PING from " .. sender)
        return
    end

    if message == "SYNC_REQUEST" then
        if not IsSamePlayer(sender, GetPlayerFullName()) then
            C_Timer.After(math.random(2, 8) / 10, SendMyRequests)
        end
        return
    end

    local action, requestID, playerName, quantityText, itemName =
        strsplit(separator, message, 5)

    if action == "CLEAR" then
        if IsSamePlayer(sender, GetPlayerFullName()) then return end
        if not IsSamePlayer(requestID, sender) then return end

        for index = #WhoNeedsWhatDB.requests, 1, -1 do
            local request = WhoNeedsWhatDB.requests[index]
            if IsSamePlayer(request.player, sender) then
                table.remove(WhoNeedsWhatDB.requests, index)
            end
        end

        if RefreshRequestList then RefreshRequestList() end
        return
    end

    if action == "ADD" then
        if not requestID or requestID == "" then return end
        if not itemName or itemName == "" then return end
        if not IsSamePlayer(playerName, sender) then return end
        if RequestExists(requestID) then return end

        table.insert(WhoNeedsWhatDB.requests, {
            id = requestID,
            item = itemName,
            quantity = tonumber(quantityText) or 1,
            player = playerName
        })

        if RefreshRequestList then RefreshRequestList() end

        WNWPrint(
            "|cff33ff99",
            "Received a new request from " .. playerName
        )
        return
    end

    if action == "DELETE" then
        for index = #WhoNeedsWhatDB.requests, 1, -1 do
            local request = WhoNeedsWhatDB.requests[index]

            if request.id == requestID
                and IsSamePlayer(request.player, sender) then

                table.remove(WhoNeedsWhatDB.requests, index)
                if RefreshRequestList then RefreshRequestList() end

                WNWPrint("|cff33ff99", sender .. " removed a request.")
                return
            end
        end
    end
end)

-- Sync after login or after leaving an instance.
local worldFrame = CreateFrame("Frame")
worldFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
worldFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")

worldFrame:SetScript("OnEvent", function()
    C_Timer.After(4, function()
        if not CanGuildSync() then return end

        local now = GetTime()
        if now - lastAutomaticSyncTime < 30 then return end

        lastAutomaticSyncTime = now
        RequestGuildSync(false)
    end)
end)

-- Create the window.
local window = CreateFrame(
    "Frame",
    "WhoNeedsWhatWindow",
    UIParent,
    "BasicFrameTemplateWithInset"
)

window:SetSize(500, 350)
window:SetPoint("CENTER")
window:SetMovable(true)
window:SetClampedToScreen(true)
window:EnableMouse(true)
window:RegisterForDrag("LeftButton")

window:SetScript("OnDragStart", function(self)
    self:StartMoving()
end)

window:SetScript("OnDragStop", function(self)
    self:StopMovingOrSizing()
end)

window.TitleText:SetText("Who Needs What")

local itemLabel = window:CreateFontString(nil, "OVERLAY", "GameFontNormal")
itemLabel:SetPoint("TOPLEFT", 25, -45)
itemLabel:SetText(
    "What item do you need?  |cffaaaaaaType 3+ letters|r"
)

local itemBox = CreateFrame("EditBox", nil, window, "InputBoxTemplate")
itemBox:SetSize(300, 30)
itemBox:SetPoint("TOPLEFT", itemLabel, "BOTTOMLEFT", 0, -5)
itemBox:SetAutoFocus(false)
itemBox:SetMaxLetters(100)

-- Show matching real WoW items while the player types.
local maximumSuggestions = 8
local suggestionButtons = {}
local selectedSuggestion = 0

local suggestionFrame = CreateFrame(
    "Frame",
    nil,
    window,
    "BackdropTemplate"
)
suggestionFrame:SetSize(330, maximumSuggestions * 24 + 12)
suggestionFrame:SetPoint("TOPLEFT", itemBox, "BOTTOMLEFT", -5, 4)
suggestionFrame:SetFrameLevel(window:GetFrameLevel() + 20)
suggestionFrame:SetBackdrop({
    bgFile = "Interface/Tooltips/UI-Tooltip-Background",
    edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
    tile = true,
    tileSize = 16,
    edgeSize = 12,
    insets = { left = 3, right = 3, top = 3, bottom = 3 }
})
suggestionFrame:SetBackdropColor(0.03, 0.03, 0.03, 0.98)
suggestionFrame:SetBackdropBorderColor(0.5, 0.5, 0.5, 1)
suggestionFrame:Hide()

local function RefreshSuggestionHighlight()
    for index, button in ipairs(suggestionButtons) do
        if index == selectedSuggestion and button:IsShown() then
            button.selectedTexture:Show()
        else
            button.selectedTexture:Hide()
        end
    end
end

local function ChooseSuggestion(result)
    if not result then return end

    itemBox:SetText(result.name)
    itemBox:SetCursorPosition(#result.name)
    suggestionFrame:Hide()
    selectedSuggestion = 0
end

for index = 1, maximumSuggestions do
    local button = CreateFrame("Button", nil, suggestionFrame)
    button:SetHeight(24)
    button:SetPoint("TOPLEFT", 6, -6 - ((index - 1) * 24))
    button:SetPoint("TOPRIGHT", -6, -6 - ((index - 1) * 24))

    button.selectedTexture = button:CreateTexture(nil, "BACKGROUND")
    button.selectedTexture:SetAllPoints()
    button.selectedTexture:SetColorTexture(0.2, 0.45, 0.8, 0.45)
    button.selectedTexture:Hide()

    button.text = button:CreateFontString(
        nil,
        "OVERLAY",
        "GameFontHighlight"
    )
    button.text:SetPoint("LEFT", 6, 0)
    button.text:SetPoint("RIGHT", -6, 0)
    button.text:SetJustifyH("LEFT")

    button:SetScript("OnEnter", function(self)
        selectedSuggestion = index
        RefreshSuggestionHighlight()
    end)

    button:SetScript("OnClick", function(self)
        ChooseSuggestion(self.result)
    end)

    suggestionButtons[index] = button
end

local function ShowItemSuggestions()
    if not WhoNeedsWhatItemSearch
        or not WhoNeedsWhatItemSearch.Find then

        suggestionFrame:Hide()
        return
    end

    local results = WhoNeedsWhatItemSearch.Find(
        itemBox:GetText(),
        maximumSuggestions
    )

    if #results == 0 then
        suggestionFrame:Hide()
        selectedSuggestion = 0
        return
    end

    selectedSuggestion = 0

    for index, button in ipairs(suggestionButtons) do
        local result = results[index]
        button.result = result

        if result then
            button.text:SetText(result.name)

            local color = ITEM_QUALITY_COLORS[result.quality or 1]
            if color then
                button.text:SetTextColor(color.r, color.g, color.b)
            else
                button.text:SetTextColor(1, 1, 1)
            end

            button:Show()
        else
            button:Hide()
        end
    end

    RefreshSuggestionHighlight()
    suggestionFrame:Show()
end

itemBox:SetScript("OnTextChanged", function(self, userInput)
    if userInput then
        ShowItemSuggestions()
    end
end)

itemBox:SetScript("OnArrowPressed", function(self, key)
    if not suggestionFrame:IsShown() then return end

    local shownButtons = 0
    for _, button in ipairs(suggestionButtons) do
        if button:IsShown() then shownButtons = shownButtons + 1 end
    end

    if shownButtons == 0 then return end

    if key == "DOWN" then
        selectedSuggestion = selectedSuggestion + 1
        if selectedSuggestion > shownButtons then
            selectedSuggestion = 1
        end
    elseif key == "UP" then
        selectedSuggestion = selectedSuggestion - 1
        if selectedSuggestion < 1 then
            selectedSuggestion = shownButtons
        end
    end

    RefreshSuggestionHighlight()
end)

itemBox:SetScript("OnEnterPressed", function()
    if suggestionFrame:IsShown() then
        local index = selectedSuggestion > 0 and selectedSuggestion or 1
        ChooseSuggestion(suggestionButtons[index].result)
    else
        itemBox:ClearFocus()
    end
end)

itemBox:SetScript("OnTabPressed", function()
    if suggestionFrame:IsShown() then
        local index = selectedSuggestion > 0 and selectedSuggestion or 1
        ChooseSuggestion(suggestionButtons[index].result)
    end
end)

itemBox:SetScript("OnEscapePressed", function()
    suggestionFrame:Hide()
    selectedSuggestion = 0
    itemBox:ClearFocus()
end)

local quantityLabel = window:CreateFontString(
    nil,
    "OVERLAY",
    "GameFontNormal"
)
quantityLabel:SetPoint("TOPLEFT", itemBox, "BOTTOMLEFT", 0, -15)
quantityLabel:SetText("How many?")

local quantityBox = CreateFrame("EditBox", nil, window, "InputBoxTemplate")
quantityBox:SetSize(80, 30)
quantityBox:SetPoint("TOPLEFT", quantityLabel, "BOTTOMLEFT", 0, -5)
quantityBox:SetAutoFocus(false)
quantityBox:SetNumeric(true)
quantityBox:SetMaxLetters(5)
quantityBox:SetText("1")

local addButton = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
addButton:SetSize(140, 30)
addButton:SetPoint("TOPLEFT", quantityBox, "BOTTOMLEFT", 0, -20)
addButton:SetText("Add Request")

local removeButton = CreateFrame(
    "Button",
    nil,
    window,
    "UIPanelButtonTemplate"
)
removeButton:SetSize(140, 30)
removeButton:SetPoint("LEFT", addButton, "RIGHT", 15, 0)
removeButton:SetText("Remove My Last")

local listLabel = window:CreateFontString(
    nil,
    "OVERLAY",
    "GameFontNormalLarge"
)
listLabel:SetPoint("TOPLEFT", addButton, "BOTTOMLEFT", 0, -25)
listLabel:SetText("Requests")

local requestList = window:CreateFontString(
    nil,
    "OVERLAY",
    "GameFontHighlight"
)
requestList:SetPoint("TOPLEFT", listLabel, "BOTTOMLEFT", 0, -10)
requestList:SetWidth(440)
requestList:SetJustifyH("LEFT")
requestList:SetJustifyV("TOP")

RefreshRequestList = function()
    if not WhoNeedsWhatDB or not WhoNeedsWhatDB.requests then
        requestList:SetText("Loading requests...")
        return
    end

    if #WhoNeedsWhatDB.requests == 0 then
        requestList:SetText("No requests yet.")
        return
    end

    local lines = {}

    for number, request in ipairs(WhoNeedsWhatDB.requests) do
        table.insert(
            lines,
            number
            .. ". "
            .. (request.player or "Unknown")
            .. " needs "
            .. (request.quantity or 1)
            .. "x "
            .. (request.item or "Unknown item")
        )
    end

    requestList:SetText(table.concat(lines, "\n"))
end

-- Add and share a request.
addButton:SetScript("OnClick", function()
    local itemName = itemBox:GetText():match("^%s*(.-)%s*$")
    local quantity = tonumber(quantityBox:GetText()) or 1

    if itemName == "" then
        WNWPrint("|cffff4444", "Please enter an item.")
        return
    end

    quantity = math.max(1, quantity)

    local playerName = GetPlayerFullName()
    local request = {
        id = table.concat({
            playerName,
            GetServerTime(),
            math.random(1000, 9999)
        }, ":"),
        item = itemName,
        quantity = quantity,
        player = playerName
    }

    table.insert(WhoNeedsWhatDB.requests, request)
    RefreshRequestList()

    if SendRequest(request) then
        WNWPrint("|cff33ff99", "Request shared with online guildmates!")
    elseif IsInGuild() then
        WNWPrint(
            "|cffffaa00",
            "Saved locally. It will sync after you leave the instance."
        )
    end

    WNWPrint("|cff33ff99", "Added " .. quantity .. "x " .. itemName)
    itemBox:SetText("")
    quantityBox:SetText("1")
    suggestionFrame:Hide()
end)

-- Remove and share the current player's newest request.
removeButton:SetScript("OnClick", function()
    local currentPlayer = GetPlayerFullName()

    for index = #WhoNeedsWhatDB.requests, 1, -1 do
        local request = WhoNeedsWhatDB.requests[index]

        if IsSamePlayer(request.player, currentPlayer) then
            table.remove(WhoNeedsWhatDB.requests, index)
            RefreshRequestList()
            SendDelete(request.id)

            WNWPrint(
                "|cff33ff99",
                "Removed "
                .. (request.quantity or 1)
                .. "x "
                .. (request.item or "Unknown item")
            )
            return
        end
    end

    WNWPrint("|cffff4444", "You do not have a request to remove.")
end)

window:Hide()

SLASH_WHONEEDSWHAT1 = "/wnw"
SLASH_WHONEEDSWHAT2 = "/whoneedswhat"

SlashCmdList["WHONEEDSWHAT"] = function()
    if window:IsShown() then
        window:Hide()
    else
        RefreshRequestList()
        window:Show()
        RequestGuildSync(true)
    end
end

SLASH_WNWSYNCTEST1 = "/wnwsynctest"

SlashCmdList["WNWSYNCTEST"] = function()
    if not IsInGuild() then
        WNWPrint("|cffff4444", "You are not in a guild.")
        return
    end

    if not CanGuildSync() then
        WNWPrint("|cffff4444", "Test this outside a dungeon or raid.")
        return
    end

    C_ChatInfo.SendAddonMessage(addonPrefix, "PING", "GUILD")
    WNWPrint("|cff33ff99", "Sync test sent!")
end
