-- Forever Beacon 0.1.0
-- Proof of concept for World of Warcraft: Forever (beta interface 16001).
-- Secret-safe. No combat log. No secure snippets. No Classic API.
-- Install: World of Warcraft\_classic_beta_\Interface\AddOns\ForeverBeacon\
-- Then fully restart the client. /fb help

local addonName, ns = ...

local PREFIX = "|cffd4a85a[Beacon]|r "

local defaults = {
    enabled = true,
    welcomeShown = false,
    point = { point = "CENTER", rel = "CENTER", x = 0, y = 80 },
    notes = {},
}

ns.db = nil

local frame = CreateFrame("Frame")
local events = {}
local pending = {}

local function Queue(action)
    if InCombatLockdown() then
        pending[#pending + 1] = action
    else
        action()
    end
end

local function IsSecret(value)
    if type(issecretvalue) ~= "function" then
        return false
    end
    return issecretvalue(value) and true or false
end

local function SafeString(value, fallback)
    if value == nil or IsSecret(value) then
        return fallback
    end
    return tostring(value)
end

local function Say(text)
    DEFAULT_CHAT_FRAME:AddMessage(PREFIX .. text, 0.95, 0.92, 0.86)
end

local function MergeDefaults(db)
    for key, value in pairs(defaults) do
        if db[key] == nil then
            if type(value) == "table" then
                local copy = {}
                for innerKey, innerValue in pairs(value) do
                    copy[innerKey] = innerValue
                end
                db[key] = copy
            else
                db[key] = value
            end
        end
    end
end

function events:ADDON_LOADED(loaded)
    if loaded ~= addonName then
        return
    end
    -- Beta note: Forever has been observed writing SavedVariables and then
    -- failing to restore them. Code the correct pattern anyway.
    ForeverBeaconDB = ForeverBeaconDB or {}
    MergeDefaults(ForeverBeaconDB)
    ns.db = ForeverBeaconDB
    ns:BuildPanel()
    self:UnregisterEvent("ADDON_LOADED")
end

function events:PLAYER_LOGIN()
    if not ns.db then
        return
    end
    if not ns.db.welcomeShown then
        ns.db.welcomeShown = true
        Say("online. /fb help. This panel draws health. It never does maths on it.")
    end
    ns:Refresh()
end

function events:PLAYER_ENTERING_WORLD()
    ns:Refresh()
end

function events:ZONE_CHANGED_NEW_AREA()
    ns:Refresh()
end

function events:PLAYER_REGEN_ENABLED()
    for i = 1, #pending do
        pending[i]()
    end
    wipe(pending)
end

function events:UNIT_HEALTH(unit)
    if unit == "player" then
        ns:PaintHealth()
    end
end

function events:UNIT_MAXHEALTH(unit)
    if unit == "player" then
        ns:PaintHealth()
    end
end

function ns:PaintHealth()
    local bar = ns.healthBar
    if not bar then
        return
    end
    local cur = UnitHealth("player")
    local maxv = UnitHealthMax("player")
    -- Status bars accept secrets. Arithmetic, comparison, and print do not.
    bar:SetMinMaxValues(0, maxv)
    bar:SetValue(cur)
    if ns.healthLabel then
        if IsSecret(cur) or IsSecret(maxv) then
            ns.healthLabel:SetText("Vitals sealed")
        else
            ns.healthLabel:SetText("Vitals open")
        end
    end
end

function ns:Refresh()
    if not ns.zoneText then
        return
    end
    local zone = SafeString(GetZoneText(), "Unknown wilds")
    local name = SafeString(UnitName("player"), "Adventurer")
    ns.zoneText:SetText(name .. "  ·  " .. zone)
    ns:PaintHealth()
    ns:PaintNotes()
end

function ns:PaintNotes()
    if not ns.noteText or not ns.db then
        return
    end
    local lines = ns.db.notes
    if #lines == 0 then
        ns.noteText:SetText("No field notes.  /fb note your text")
        return
    end
    local buf = ""
    local startAt = #lines - 4
    if startAt < 1 then
        startAt = 1
    end
    for i = startAt, #lines do
        if buf ~= "" then
            buf = buf .. "\n"
        end
        buf = buf .. lines[i]
    end
    ns.noteText:SetText(buf)
end

function ns:BuildPanel()
    local panel = CreateFrame("Frame", "ForeverBeaconPanel", UIParent, "BackdropTemplate")
    panel:SetSize(280, 168)
    panel:SetFrameStrata("MEDIUM")
    panel:SetClampedToScreen(true)
    panel:SetMovable(true)
    panel:EnableMouse(true)
    panel:RegisterForDrag("LeftButton")
    panel:SetScript("OnDragStart", function(self)
        if InCombatLockdown() then
            return
        end
        self:StartMoving()
    end)
    panel:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        local point, _, rel, x, y = self:GetPoint(1)
        if ns.db and point and rel then
            ns.db.point = { point = point, rel = rel, x = x, y = y }
        end
    end)

    local p = ns.db and ns.db.point
    if p and p.point then
        panel:SetPoint(p.point, UIParent, p.rel or "CENTER", p.x or 0, p.y or 80)
    else
        panel:SetPoint("CENTER", UIParent, "CENTER", 0, 80)
    end

    panel: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 },
    })
    panel:SetBackdropColor(0.07, 0.06, 0.05, 0.92)
    panel:SetBackdropBorderColor(0.83, 0.66, 0.35, 0.9)

    local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOPLEFT", 12, -10)
    title:SetText("Forever Beacon")

    local zone = panel:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    zone:SetPoint("TOPLEFT", 12, -28)
    zone:SetPoint("TOPRIGHT", -12, -28)
    zone:SetJustifyH("LEFT")
    ns.zoneText = zone

    local bar = CreateFrame("StatusBar", nil, panel)
    bar:SetPoint("TOPLEFT", 12, -48)
    bar:SetPoint("TOPRIGHT", -12, -48)
    bar:SetHeight(12)
    bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
    bar:SetStatusBarColor(0.55, 0.12, 0.12, 1)
    bar:SetMinMaxValues(0, 1)
    bar:SetValue(0)
    ns.healthBar = bar

    local healthLabel = panel:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    healthLabel:SetPoint("TOPLEFT", 12, -64)
    healthLabel:SetText("Vitals")
    ns.healthLabel = healthLabel

    local note = panel:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    note:SetPoint("TOPLEFT", 12, -82)
    note:SetPoint("BOTTOMRIGHT", -12, 12)
    note:SetJustifyH("LEFT")
    note:SetJustifyV("TOP")
    ns.noteText = note

    if ns.db and not ns.db.enabled then
        panel:Hide()
    end
    ns.panel = panel
end

local function Probe()
    local build = SafeString(select(2, GetBuildInfo()), "?")
    local iface = SafeString(select(4, GetBuildInfo()), "?")
    local project = SafeString(WOW_PROJECT_ID, "?")
    Say("build " .. build .. "  interface " .. iface .. "  WOW_PROJECT_ID " .. project)
    if not IsSecret(WOW_PROJECT_ID) and WOW_PROJECT_ID == 1 then
        Say("Project id is 1 (Mainline). Forever reports retail. Do not take a Classic branch.")
    end
    if type(C_Spell) == "table" and type(C_Spell.GetSpellInfo) == "function" then
        local info = C_Spell.GetSpellInfo(8690)
        if info and not IsSecret(info.name) then
            Say("C_Spell.GetSpellInfo(8690) -> " .. SafeString(info.name, "sealed"))
        elseif info == nil then
            if type(C_Spell.RequestLoadSpellData) == "function" then
                C_Spell.RequestLoadSpellData(8690)
            end
            Say("Hearthstone is not cached. Requested a load. Probe again after the result event.")
        else
            Say("Spell name is sealed. Leave it sealed.")
        end
    else
        Say("C_Spell.GetSpellInfo is missing on this build. Retest before you ship spell UI.")
    end
    if type(GetSpellInfo) == "function" then
        Say("GetSpellInfo still exists here. Prefer C_Spell anyway.")
    else
        Say("GetSpellInfo is absent. Classic snippets that call it will error.")
    end
    local h = UnitHealth("player")
    if IsSecret(h) then
        Say("UnitHealth(player) is secret. The bar can draw it. Lua must not add to it.")
    else
        Say("UnitHealth(player) is plain right now. Do not assume that stays true in combat.")
    end
end

function events:SPELL_DATA_LOAD_RESULT(spellID, success)
    if spellID ~= 8690 then
        return
    end
    if success and type(C_Spell) == "table" then
        local info = C_Spell.GetSpellInfo(8690)
        Say("Spell data arrived: " .. SafeString(info and info.name, "sealed"))
    else
        Say("Spell data load failed for 8690.")
    end
end

SLASH_FOREVERBEACON1 = "/fb"
SLASH_FOREVERBEACON2 = "/foreverbeacon"
SlashCmdList["FOREVERBEACON"] = function(msg)
    local text = msg or ""
    local cmd, rest = text:match("^(%S*)%s*(.*)$")
    cmd = (cmd or ""):lower()
    if cmd == "" or cmd == "help" then
        Say("/fb note <text>   /fb toggle   /fb probe   /fb reset")
    elseif cmd == "toggle" then
        if not ns.db or not ns.panel then
            return
        end
        Queue(function()
            ns.db.enabled = not ns.db.enabled
            if ns.db.enabled then
                ns.panel:Show()
                ns:Refresh()
            else
                ns.panel:Hide()
            end
        end)
    elseif cmd == "note" then
        if not ns.db then
            return
        end
        if not rest or rest == "" then
            Say("Usage: /fb note ferry timing on the west dock")
            return
        end
        ns.db.notes[#ns.db.notes + 1] = rest
        ns:PaintNotes()
        Say("noted.")
    elseif cmd == "probe" then
        Probe()
    elseif cmd == "reset" then
        ForeverBeaconDB = nil
        ReloadUI()
    else
        Say("Unknown command. /fb help")
    end
end

function ForeverBeacon_OnCompartmentClick()
    if not ns.db or not ns.panel then
        return
    end
    Queue(function()
        if ns.panel:IsShown() then
            ns.db.enabled = false
            ns.panel:Hide()
        else
            ns.db.enabled = true
            ns.panel:Show()
            ns:Refresh()
        end
    end)
end

function ForeverBeacon_OnCompartmentEnter(_, owner)
    GameTooltip:SetOwner(owner, "ANCHOR_LEFT")
    GameTooltip:SetText("Forever Beacon", 1, 0.82, 0.45)
    GameTooltip:AddLine("Toggle the field journal.", 0.9, 0.9, 0.9)
    GameTooltip:Show()
end

function ForeverBeacon_OnCompartmentLeave()
    GameTooltip:Hide()
end

frame:SetScript("OnEvent", function(self, event, ...)
    local handler = events[event]
    if handler then
        handler(self, ...)
    end
end)

frame:RegisterEvent("ADDON_LOADED")
frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:RegisterEvent("SPELL_DATA_LOAD_RESULT")
if frame.RegisterUnitEvent then
    frame:RegisterUnitEvent("UNIT_HEALTH", "player")
    frame:RegisterUnitEvent("UNIT_MAXHEALTH", "player")
else
    frame:RegisterEvent("UNIT_HEALTH")
    frame:RegisterEvent("UNIT_MAXHEALTH")
end
