-- ============================================================================
-- HDG_PriceIntegration.lua - Price Addon Integration Module
-- ============================================================================
-- Handles integration with Direct AH, TSM, and Auctionator for material pricing
-- ============================================================================

HDG_PriceIntegration = {}

-- ============================================================================
-- PRICE LOOKUP
-- ============================================================================

-- Lazy-built vendor price cache from ReagentsDB "Vendor:copper" entries
local vendorPriceCache

-- Direct AH price cache. Lives in HDG.Store.state.directPriceCache;
-- this local is a reference to the Store table, populated by LoadCache().
-- [itemID] = price (copper), 0 = not listed
local directPriceCache = {}

local function GetStore()
    return _G.HousingDecorGuide and _G.HousingDecorGuide.Store
end

--- Load cached prices from Store (call after Store has loaded SavedVariables)
function HDG_PriceIntegration.LoadCache()
    local store = GetStore()
    if store and store.state and type(store.state.directPriceCache) == "table" then
        -- Alias to the Store table so mutations below auto-propagate.
        directPriceCache = store.state.directPriceCache
    end
end

--- Save current cache to Store (triggers debounced SavedVariables write)
local function SaveCache()
    local store = GetStore()
    if not store or not store.state then return end
    -- Ensure the Store table IS our local (first write; LoadCache may not
    -- have run if the user hasn't opened HDG yet).
    store.state.directPriceCache = directPriceCache
    store.state.directPriceCacheTime = time()
    if store.QueueSave then store:QueueSave() end
end

local function GetVendorPrice(itemID)
    if not vendorPriceCache then
        vendorPriceCache = {}
        if HDG_ReagentsDB then
            for id, src in pairs(HDG_ReagentsDB) do
                if type(src) == "string" then
                    local copper = src:match("^Vendor:(%d+)$")
                    if copper then vendorPriceCache[id] = tonumber(copper) end
                end
            end
        end
    end
    return vendorPriceCache[itemID]
end

-- Helper to check if TSM is available
local function IsTSMAvailable()
    return TSM_API and TSM_API.GetCustomPriceValue
end

-- Helper to check if Auctionator is available
local function IsAuctionatorAvailable()
    return Auctionator and Auctionator.API and Auctionator.API.v1 and Auctionator.API.v1.GetAuctionPriceByItemID
end

-- Helper to get price from TSM
local function GetTSMPrice(itemID)
    if not IsTSMAvailable() then return nil end
    local HDG = _G.HousingDecorGuide
    local state = HDG and HDG.Store and HDG.Store:GetState()
    local mode = state and state.config and state.config.tsmPriceMode
    local priceString = (mode == "region") and "DBRegionSaleAvg" or "DBMinBuyout"
    local price = TSM_API.GetCustomPriceValue(priceString, "i:" .. itemID)
    if price and price > 0 then
        return price
    end
    return nil
end

-- Helper to get price from Auctionator
local function GetAuctionatorPrice(itemID)
    if not IsAuctionatorAvailable() then return nil end
    local price = Auctionator.API.v1.GetAuctionPriceByItemID("HousingDecorGuide", itemID)
    if price and price > 0 then
        return price
    end
    return nil
end

-- Helper to get cached direct AH price
local function GetDirectPrice(itemID)
    local price = directPriceCache[itemID]
    if price and price > 0 then return price end
    return nil
end

--- Get market price for an item from preferred source
-- @param itemID The item ID to get price for
-- @param forceAddon Optional: force using specific source ("Direct", "TSM", or "Auctionator")
-- @return price (in copper), source ("Direct", "TSM", "Auctionator", "Vendor", or nil)
function HDG_PriceIntegration.GetItemPrice(itemID, forceAddon)
    if not itemID then return nil, nil end

    -- Determine which source to use
    local preferred = forceAddon
    if not preferred then
        local HDG = _G.HousingDecorGuide
        local state = HDG and HDG.Store and HDG.Store:GetState()
        preferred = state and state.config and state.config.preferredPriceAddon
    end

    -- If user has a preference, try only that source
    if preferred == "Direct" then
        local price = GetDirectPrice(itemID)
        if price then return price, "Direct" end
    elseif preferred == "TSM" then
        local price = GetTSMPrice(itemID)
        if price then return price, "TSM" end
    elseif preferred == "Auctionator" then
        local price = GetAuctionatorPrice(itemID)
        if price then return price, "Auctionator" end
    else
        -- Default: TSM first, then Auctionator, then Direct cache
        local price = GetTSMPrice(itemID)
        if price then return price, "TSM" end

        price = GetAuctionatorPrice(itemID)
        if price then return price, "Auctionator" end

        local directPrice = GetDirectPrice(itemID)
        if directPrice then return directPrice, "Direct" end
    end

    -- Fallback: vendor price from ReagentsDB (always checked)
    local vendorPrice = GetVendorPrice(itemID)
    if vendorPrice then
        return vendorPrice, "Vendor"
    end

    return nil, nil
end

-- ============================================================================
-- DIRECT AH SCANNING (browse query - like Auctionator's full scan)
-- ============================================================================

local directScanFrame = nil
local directScanState = {
    active = false,
    neededItems = {},  -- [itemID] = true, set of items we want prices for
    found = 0,         -- count of matching items found so far
    total = 0,         -- total items we need
    progressCb = nil,  -- function(found, total)
    doneCb = nil,      -- function()
    timeoutTimer = nil,
}

--- Check if AH window is currently open
function HDG_PriceIntegration.IsAHOpen()
    return AuctionHouseFrame and AuctionHouseFrame:IsShown()
end

--- Direct is always available as a source (requires AH open to scan)
function HDG_PriceIntegration.IsDirectAvailable()
    return true
end

--- Check if a direct scan is currently in progress
function HDG_PriceIntegration.IsScanInProgress()
    return directScanState.active
end

--- Get count of items in direct price cache
function HDG_PriceIntegration.GetDirectCacheCount()
    local count = 0
    for _ in pairs(directPriceCache) do count = count + 1 end
    return count
end

--- Clear direct price cache
function HDG_PriceIntegration.ClearDirectCache()
    directPriceCache = {}
    SaveCache()
end

--- Get timestamp of last direct cache save (or nil if never scanned)
function HDG_PriceIntegration.GetDirectCacheTime()
    local store = GetStore()
    return store and store.state and store.state.directPriceCacheTime or nil
end

--- Get direct scan progress (found, total) or nil if not scanning
function HDG_PriceIntegration.GetDirectScanProgress()
    if not directScanState.active then return nil, nil end
    return directScanState.found, directScanState.total
end

-- Process a batch of browse results, caching minPrice for items we need
local function ProcessBrowseResults(results)
    if not directScanState.active or not results then return end
    local needed = directScanState.neededItems

    for _, resultInfo in ipairs(results) do
        local itemID = resultInfo.itemKey and resultInfo.itemKey.itemID
        if itemID and needed[itemID] and resultInfo.totalQuantity > 0 then
            directPriceCache[itemID] = resultInfo.minPrice
            directScanState.found = directScanState.found + 1
        end
    end

    if directScanState.progressCb then
        directScanState.progressCb(directScanState.found, directScanState.total)
    end
end

-- Finalize scan: mark unfound items as 0, fire done callback
local function FinalizeScan()
    directScanState.active = false
    if directScanState.timeoutTimer then
        directScanState.timeoutTimer:Cancel()
        directScanState.timeoutTimer = nil
    end
    -- Items not found on AH get price 0
    for itemID in pairs(directScanState.neededItems) do
        if directPriceCache[itemID] == nil then
            directPriceCache[itemID] = 0
        end
    end
    SaveCache()
    if directScanState.doneCb then directScanState.doneCb() end
end

-- Handle browse events during direct scan
local function OnDirectBrowseEvent(_, event, ...)
    if not directScanState.active then return end

    if event == "AUCTION_HOUSE_BROWSE_RESULTS_UPDATED" then
        local browseResults = C_AuctionHouse.GetBrowseResults()
        ProcessBrowseResults(browseResults)
        if C_AuctionHouse.HasFullBrowseResults() then
            FinalizeScan()
        else
            C_AuctionHouse.RequestMoreBrowseResults()
        end

    elseif event == "AUCTION_HOUSE_BROWSE_RESULTS_ADDED" then
        local addedResults = ...
        ProcessBrowseResults(addedResults)
        if C_AuctionHouse.HasFullBrowseResults() then
            FinalizeScan()
        else
            C_AuctionHouse.RequestMoreBrowseResults()
        end

    elseif event == "AUCTION_HOUSE_CLOSED" then
        directScanState.active = false
    end
end

--- Start scanning via AH browse query (dumps all AH listings, filters locally)
-- @param itemIDs Table of item IDs to scan
-- @param progressCb Optional callback: function(found, total)
-- @param doneCb Optional callback: function() called when scan completes
-- @return true if scan started, false if AH not open
function HDG_PriceIntegration.StartDirectScan(itemIDs, progressCb, doneCb)
    if not HDG_PriceIntegration.IsAHOpen() then
        if doneCb then doneCb() end
        return false
    end

    -- Create event frame on first use
    if not directScanFrame then
        directScanFrame = CreateFrame("Frame")
        directScanFrame:RegisterEvent("AUCTION_HOUSE_BROWSE_RESULTS_UPDATED")
        directScanFrame:RegisterEvent("AUCTION_HOUSE_BROWSE_RESULTS_ADDED")
        directScanFrame:RegisterEvent("AUCTION_HOUSE_CLOSED")
        directScanFrame:SetScript("OnEvent", OnDirectBrowseEvent)
    end

    -- Build set of items we need (not yet cached)
    local needed = {}
    local count = 0
    for _, id in ipairs(itemIDs) do
        if directPriceCache[id] == nil then
            needed[id] = true
            count = count + 1
        end
    end

    if count == 0 then
        if doneCb then doneCb() end
        return true
    end

    directScanState.active = true
    directScanState.neededItems = needed
    directScanState.found = 0
    directScanState.total = count
    directScanState.progressCb = progressCb
    directScanState.doneCb = doneCb

    -- Safety timeout (60s should be plenty for a browse scan)
    directScanState.timeoutTimer = C_Timer.NewTimer(60, function()
        if directScanState.active then FinalizeScan() end
    end)

    -- Browse ALL AH items - results stream in via paging events
    C_AuctionHouse.SendBrowseQuery({
        searchString = "",
        sorts = {},
        filters = {},
        itemClassFilters = {},
    })

    return true
end

--- Stop an in-progress direct scan
function HDG_PriceIntegration.StopDirectScan()
    if directScanState.timeoutTimer then
        directScanState.timeoutTimer:Cancel()
        directScanState.timeoutTimer = nil
    end
    directScanState.active = false
    directScanState.neededItems = {}
end

-- ============================================================================
-- FORMATTING
-- ============================================================================

--- Format copper value as gold string
-- @param copper Amount in copper
-- @param useTextures Whether to use coin textures (default false for compact display)
-- @return Formatted string
function HDG_PriceIntegration.FormatGold(copper, useTextures)
    if not copper or copper == 0 then return "0g" end

    if useTextures then
        return C_CurrencyInfo.GetCoinTextureString(copper)
    end

    local gold = math.floor(copper / 10000)
    local silver = math.floor((copper % 10000) / 100)

    if gold > 0 then
        if silver > 0 then
            return string.format("%d.%02dg", gold, silver)
        end
        return string.format("%dg", gold)
    elseif silver > 0 then
        return string.format("%ds", silver)
    else
        return string.format("%dc", copper % 100)
    end
end

--- Format copper value as gold-only with comma separators (e.g., "1,234g")
-- @param copper Amount in copper
-- @return Formatted string with commas, gold only
function HDG_PriceIntegration.FormatGoldOnly(copper)
    if not copper or copper == 0 then return "0g" end

    local gold = math.floor(copper / 10000)
    if _G.HDG_UIHelpers and _G.HDG_UIHelpers.FormatWithCommas then
        return _G.HDG_UIHelpers.FormatWithCommas(gold) .. "g"
    end
    return gold .. "g"
end

-- ============================================================================
-- ADDON DETECTION
-- ============================================================================

--- Check if any price source is available (always true now - Direct is built-in)
-- @return boolean, string (source name)
function HDG_PriceIntegration.HasPriceAddon()
    if IsTSMAvailable() then
        return true, "TSM"
    end
    if IsAuctionatorAvailable() then
        return true, "Auctionator"
    end
    return true, "Direct"
end

--- Check if TSM is available
-- @return boolean
function HDG_PriceIntegration.IsTSMAvailable()
    return IsTSMAvailable()
end

--- Check if Auctionator is available
-- @return boolean
function HDG_PriceIntegration.IsAuctionatorAvailable()
    return IsAuctionatorAvailable()
end

--- Get list of available price sources
-- @return table of source names (always includes "Direct")
function HDG_PriceIntegration.GetAvailableAddons()
    local addons = { "Direct" }
    if IsTSMAvailable() then
        table.insert(addons, "TSM")
    end
    if IsAuctionatorAvailable() then
        table.insert(addons, "Auctionator")
    end
    return addons
end

--- Get the name of the active price addon
-- @return string (never nil - Direct is always available)
function HDG_PriceIntegration.GetActiveAddon()
    local _, addonName = HDG_PriceIntegration.HasPriceAddon()
    return addonName
end

-- ============================================================================
-- TSM-SPECIFIC DATA
-- ============================================================================

--- Get TSM region sale average price
-- @param itemID The item ID to get region sale average for
-- @return price (in copper) or nil
function HDG_PriceIntegration.GetRegionSaleAvg(itemID)
    if not IsTSMAvailable() or not itemID then return nil end

    local price = TSM_API.GetCustomPriceValue("DBRegionSaleAvg", "i:" .. itemID)
    if price and price > 0 then
        return price
    end
    return nil
end

-- ============================================================================
-- BULK PRICING
-- ============================================================================

--- Calculate total price for a list of materials
-- @param materials Table of {itemID, amount} pairs
-- @return totalPrice (copper), itemsWithPrice (count), itemsWithoutPrice (count)
function HDG_PriceIntegration.CalculateTotalPrice(materials)
    local totalPrice = 0
    local itemsWithPrice = 0
    local itemsWithoutPrice = 0

    for _, mat in ipairs(materials) do
        local price, _ = HDG_PriceIntegration.GetItemPrice(mat.id or mat.itemID)
        local amount = mat.amount or mat.qty or 1

        if price then
            totalPrice = totalPrice + (price * amount)
            itemsWithPrice = itemsWithPrice + 1
        else
            itemsWithoutPrice = itemsWithoutPrice + 1
        end
    end

    return totalPrice, itemsWithPrice, itemsWithoutPrice
end
