EbonholdHub.JournalLoadoutHook = EbonholdHub.JournalLoadoutHook or {}

local JLH = EbonholdHub.JournalLoadoutHook

local SCAN_INTERVAL = 1.0
local scanFrame
local lastScanAt = 0
local hookedFunctions = {}

local FRAME_HINTS = { "echo", "journal", "loadout", "perk" }

local function IsLoadoutExportString(text)
    text = strtrim(text or "")
    return text:match("^EBH%d+:") ~= nil or text:match("^EWL%d+:") ~= nil
end

function EbonholdHub.ImportJournalLoadout(text, options)
    options = options or {}
    text = strtrim(text or "")
    if not IsLoadoutExportString(text) then
        return false, "Not an Echo Journal loadout string."
    end

    if not EbonholdHub.EchoLoadoutCodec or not EbonholdHub.Build then
        return false, "EbonholdHub loadout import is not ready."
    end

    local parsed, err = EbonholdHub.EchoLoadoutCodec.Parse(text)
    if not parsed then
        return false, err or "Could not decode loadout."
    end

    EbonholdHubDB.pendingAffixLoadout = nil
    if EbonholdHub.EquipmentLoadout then
        EbonholdHub.EquipmentLoadout.ClearPending()
    end
    if EbonholdHub.TierData then
        EbonholdHub.TierData.ClearPending()
    end

    local lockedEchoes
    if parsed.lockedEchoes and #parsed.lockedEchoes > 0 then
        lockedEchoes = EbonholdHub.Build.NormalizeLockedEchoes(parsed.lockedEchoes)
    else
        lockedEchoes = EbonholdHub.Build.EmptyLockedEchoes()
    end

    local build = EbonholdHub.Build.Create({
        title = parsed.title or "Imported Loadout",
        class = parsed.class or EbonholdHub.Build.PlayerClassToken(),
        spec = 1,
        comments = "Imported from loadout string (" .. (parsed.version or "EBH1") .. ").",
        lockedEchoes = lockedEchoes,
        echoTiers = parsed.echoTiers,
        echoTierOrder = parsed.echoTierOrder,
        echoMaxPicks = {},
        loadoutEchoSpellIds = parsed.loadoutEchoSpellIds,
        affixLoadout = {},
        equipmentLoadout = {},
        talents = {},
        talentOrder = {},
        settings = EbonholdHub.Build.DefaultSettings(),
    })

    EbonholdHub.Build.SetActive(build.id)
    if EbonholdHub.BuildList and EbonholdHub.BuildList.Refresh then
        EbonholdHub.BuildList.Refresh()
    end

    local title = parsed.title or "Loadout"
    if EbonholdHub.Toast then
        local msg = "Imported \"" .. title .. "\""
        if parsed.echoCount and parsed.echoCount > 0 then
            msg = msg .. " (" .. parsed.echoCount .. " echoes)."
        else
            msg = msg .. "."
        end
        EbonholdHub.Toast.Show(msg)
    end

    if options.openHub ~= false then
        if EbonholdHub.ViewRouter then
            EbonholdHub.ViewRouter.Show("buildOverview", { build = build })
        end
        if EbonholdHub.MainWindow and EbonholdHub.MainWindow._frame then
            EbonholdHub.MainWindow._frame:Show()
        end
    end

    return true, build
end

_G.EbonholdHub_ImportLoadout = function(text, options)
    return EbonholdHub.ImportJournalLoadout(text, options)
end

local function ConfirmImport(text)
    StaticPopupDialogs = StaticPopupDialogs or {}
    StaticPopupDialogs["EBONHOLDHUB_IMPORT_JOURNAL_LOADOUT"] = {
        text = "Import this Echo Journal loadout into EbonholdHub?",
        button1 = "Import",
        button2 = "Cancel",
        OnAccept = function()
            local payload = StaticPopup1 and StaticPopup1.data
            if payload and payload.text then
                EbonholdHub.ImportJournalLoadout(payload.text)
            end
        end,
        timeout = 0,
        whileDead = true,
        hideOnEscape = true,
    }
    StaticPopup_Show("EBONHOLDHUB_IMPORT_JOURNAL_LOADOUT", nil, nil, { text = text })
end

function JLH.OnExportString(text, source)
    text = strtrim(text or "")
    if not IsLoadoutExportString(text) then return end
    if EbonholdHubDB and EbonholdHubDB.journalLoadoutAutoImport == false then
        ConfirmImport(text)
        return
    end
    EbonholdHub.ImportJournalLoadout(text, { source = source or "journal-export" })
end

local function WrapExportFunction(owner, key, path)
    if hookedFunctions[path] then return end
    local original = owner[key]
    if type(original) ~= "function" then return end

    owner[key] = function(...)
        local result = original(...)
        if type(result) == "string" and IsLoadoutExportString(result) then
            JLH.OnExportString(result, path)
        end
        return result
    end
    hookedFunctions[path] = true
end

local function ShouldHookExportKey(key, parentPath)
    local lower = strlower(tostring(key))
    local pathLower = strlower(parentPath or "")
    if lower:find("export", 1, true) and lower:find("loadout", 1, true) then
        return true
    end
    if lower == "exportloadout" or lower == "exportloadoutstring" or lower == "getloadoutexportstring" then
        return true
    end
    if lower:find("export", 1, true) and pathLower:find("loadout", 1, true) then
        return true
    end
    return false
end

local function ScanTableForExportFunctions(root, path, depth)
    if depth > 5 or type(root) ~= "table" then return end
    for key, value in pairs(root) do
        if type(key) == "string" then
            local childPath = path .. "." .. key
            if type(value) == "function" and ShouldHookExportKey(key, path) then
                WrapExportFunction(root, key, childPath)
            elseif type(value) == "table" then
                ScanTableForExportFunctions(value, childPath, depth + 1)
            end
        end
    end
end

local function FrameNameMatches(name)
    local lower = strlower(name or "")
    for _, hint in ipairs(FRAME_HINTS) do
        if lower:find(hint, 1, true) then
            return true
        end
    end
    return false
end

local function FindExportTextInFrame(frame)
    if not frame then return nil, nil end

    if frame.IsVisible and frame:IsVisible() and frame.GetObjectType then
        if frame:GetObjectType() == "EditBox" then
            local text = frame:GetText()
            if IsLoadoutExportString(text) then
                return text, frame
            end
        end
    end

    if frame.GetChildren then
        local children = { frame:GetChildren() }
        for i = 1, #children do
            local text, editBox = FindExportTextInFrame(children[i])
            if text then return text, editBox end
        end
    end

    if frame.GetNumChildren then
        for i = 1, frame:GetNumChildren() do
            local child = select(i, frame:GetChildren())
            local text, editBox = FindExportTextInFrame(child)
            if text then return text, editBox end
        end
    end

    return nil, nil
end

local function AttachHubButton(editBox, exportText)
    if not editBox then return end
    local parent = editBox:GetParent() or editBox
    if parent._ebhHubImportButton then
        parent._ebhHubImportButton._exportText = exportText
        return
    end

    local UI = EbonholdHub.UI
    local btn
    if UI and UI.CreateButton then
        btn = UI.CreateButton(parent, 96, 22, "EbonholdHub", { hoverBorder = UI.Colors.accent })
    else
        btn = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
        btn:SetSize(96, 22)
        btn:SetText("Hub")
    end

    btn._exportText = exportText
    btn:SetPoint("LEFT", editBox, "RIGHT", 8, 0)
    btn:SetScript("OnClick", function(self)
        local text = self._exportText or (editBox.GetText and editBox:GetText()) or ""
        EbonholdHub.ImportJournalLoadout(text, { source = "journal-button" })
    end)
    btn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText("Import to EbonholdHub", 1, 0.82, 0)
        GameTooltip:AddLine("Creates a build from this loadout export.", 1, 1, 1, true)
        GameTooltip:Show()
    end)
    btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
    parent._ebhHubImportButton = btn
end

local function ScanVisibleFrames()
    if not UIParent or not UIParent.GetChildren then return end
    local children = { UIParent:GetChildren() }
    for i = 1, #children do
        local frame = children[i]
        local name = frame.GetName and frame:GetName()
        if frame.IsVisible and frame:IsVisible() and FrameNameMatches(name) then
            local text, editBox = FindExportTextInFrame(frame)
            if text and editBox then
                AttachHubButton(editBox, text)
            end
        end
    end
end

function JLH.HookProjectEbonhold()
    if not ProjectEbonhold then return 0 end
    local before = 0
    for _ in pairs(hookedFunctions) do before = before + 1 end
    ScanTableForExportFunctions(ProjectEbonhold, "ProjectEbonhold", 0)
    local after = 0
    for _ in pairs(hookedFunctions) do after = after + 1 end
    return after - before
end

function JLH.Probe()
    print("|cff66ccff[EBH loadout probe]|r scanning ProjectEbonhold export hooks...")
    local hooked = JLH.HookProjectEbonhold()
    print("|cff66ccff[EBH loadout probe]|r hooked " .. hooked .. " export function(s).")
    print("|cff66ccff[EBH loadout probe]|r global API: EbonholdHub_ImportLoadout(text)")

    if not ProjectEbonhold then
        print("|cffff9966[EBH loadout probe]|r ProjectEbonhold not loaded.")
        return
    end

    local candidates = {}
    local function collect(root, path, depth)
        if depth > 5 or type(root) ~= "table" then return end
        for key, value in pairs(root) do
            if type(key) == "string" then
                local childPath = path .. "." .. key
                if type(value) == "function" and ShouldHookExportKey(key, path) then
                    candidates[#candidates + 1] = childPath .. (hookedFunctions[childPath] and " (hooked)" or "")
                elseif type(value) == "table" then
                    collect(value, childPath, depth + 1)
                end
            end
        end
    end
    collect(ProjectEbonhold, "ProjectEbonhold", 0)
    if #candidates == 0 then
        print("|cffff9966[EBH loadout probe]|r no export functions found — using export dialog scanner.")
    else
        for i = 1, #candidates do
            print("  " .. candidates[i])
        end
    end
end

function JLH.Init()
    if JLH._initialized then return end
    JLH._initialized = true

    JLH.HookProjectEbonhold()

    if not scanFrame then
        scanFrame = CreateFrame("Frame")
        scanFrame:SetScript("OnUpdate", function(_, elapsed)
            lastScanAt = lastScanAt + (elapsed or 0)
            if lastScanAt < SCAN_INTERVAL then return end
            lastScanAt = 0
            ScanVisibleFrames()
        end)
    end
end
