--[[
InspectionUtils.lua - Mr. Mythical Gear Check Inspection Utilities

Purpose: Utilities for group inspection and gear checking
Dependencies: GearUtils, ConfigData
Author: Braunerr
--]]

local MrMythicalGearCheck = MrMythicalGearCheck or {}
MrMythicalGearCheck.InspectionUtils = {}

local InspectionUtils = MrMythicalGearCheck.InspectionUtils

local function getDependency(name)
    return (_G.MrMythicalGearCheck and _G.MrMythicalGearCheck[name]) or MrMythicalGearCheck[name]
end

local function getInspectionUnits()
    return getDependency("InspectionUnits")
end

local function getInspectionState()
    return getDependency("InspectionState")
end

--- Common inspect frame names used across multiple functions
local INSPECT_FRAME_NAMES = {
    "InspectFrame",
    "InspectFrameNew", 
    "Blizzard_InspectUI",
    "InspectPaperDollFrame"
}

--- Used for cooldown tracking between group scans
local lastInspectionTime = 0

--- Seconds to wait for INSPECT_READY before failing the current target
local INSPECTION_TIMEOUT_SECONDS = 5

--- Active timeout handle for the in-flight inspection
local inspectionTimeoutHandle = nil

--- Persistent group scan state - stores scan data for each group member
--- Key: playerName, Value: scan data or nil if not scanned/failed
local InspectionState = getInspectionState()
InspectionUtils.groupScanState = (InspectionState and InspectionState.groupScanState) or {}

--- Scan queue for players to inspect
local scanQueue = {}

local groupInspectionCallback
local uiProgressCallback

local function cancelInspectionTimeout()
    if inspectionTimeoutHandle then
        inspectionTimeoutHandle:Cancel()
        inspectionTimeoutHandle = nil
    end
end

local function getUnitFullName(unit)
    local InspectionUnits = getInspectionUnits()
    if InspectionUnits and InspectionUnits.GetUnitFullName then
        return InspectionUnits:GetUnitFullName(unit) or UnitName(unit)
    end
    return UnitName(unit)
end

local function scheduleInspectionTimeout(unit, playerName, isRescan)
    cancelInspectionTimeout()
    inspectionTimeoutHandle = C_Timer.NewTimer(INSPECTION_TIMEOUT_SECONDS, function()
        inspectionTimeoutHandle = nil
        if InspectionUtils.currentInspectionUnit == unit and InspectionUtils.currentInspectionName == playerName then
            InspectionUtils:HandleInspectionFailure(unit or "", playerName, "Timed out waiting for inspect data", isRescan)
        end
    end)
end

--- Adds a player to the scan queue
--- @param playerName string Name of the player to add
--- @return boolean True if added successfully
function InspectionUtils:AddPlayerToScanQueue(playerName)
    if not playerName or playerName == "" then
        return false
    end
    
    -- Check if already in queue
    for _, queued in ipairs(scanQueue) do
        if queued.name == playerName then
            return false -- Already in queue
        end
    end
    
    table.insert(scanQueue, {
        name = playerName,
        addedTime = GetTime()
    })

    return true
end

--- Adds all current group members to the scan queue
--- @return number Number of players added
function InspectionUtils:AddGroupToScanQueue()
    local added = 0
    local groupMembers = self:GetGroupMembers()
    
    for _, unit in ipairs(groupMembers) do
        local name = getUnitFullName(unit)
        if name and self:AddPlayerToScanQueue(name) then
            added = added + 1
        end
    end
    
    return added
end

--- Clears the scan queue
function InspectionUtils:ClearScanQueue()
    scanQueue = {}
end

--- Removes a player from the scan queue
--- @param playerName string Name of the player to remove
--- @return boolean True if removed successfully
function InspectionUtils:RemovePlayerFromScanQueue(playerName)
    for i, queued in ipairs(scanQueue) do
        if queued.name == playerName then
            table.remove(scanQueue, i)
            return true
        end
    end
    return false
end

--- Scans all players in the scan queue
--- @param callback function Optional callback when all scans complete
--- @param progressCallback function Optional callback for progress updates
--- @param clearPrevious boolean Optional - if true, clears previous results before scanning
function InspectionUtils:ScanQueuedPlayers(callback, progressCallback, clearPrevious)
    if #scanQueue == 0 then
        if callback then callback({}) end
        return
    end
    
    -- Process scan queue
    
    -- Check if we're in combat
    if InCombatLockdown() then
        if callback then
            callback({
                hasResults = false,
                failedCount = #scanQueue,
                error = "You are in combat"
            })
        end
        return
    end
    
    -- Check cooldown
    local currentTime = GetTime()
    local cooldownTime = 5.0
    if lastInspectionTime and (currentTime - lastInspectionTime) < cooldownTime then
        local remainingTime = cooldownTime - (currentTime - lastInspectionTime)
        if callback then
            callback({
                hasResults = false,
                failedCount = #scanQueue,
                error = string.format("Scan cooldown (%.1fs remaining)", remainingTime)
            })
        end
        return
    end
    lastInspectionTime = currentTime
    
    -- Clear previous results if requested (for fresh scan)
    if clearPrevious then
        self:ClearGroupScanState()
    end
    
    -- Store callbacks
    groupInspectionCallback = callback
    uiProgressCallback = progressCallback
    
    -- Create inspection queue from members that need scanning
    self.inspectionQueue = {}
    for i, queued in ipairs(scanQueue) do
        local playerName = queued.name
        -- Only add to queue if they don't have scan data
        if not self.groupScanState[playerName] then
            table.insert(self.inspectionQueue, {
                name = playerName,
                index = i
            })
        end
    end
    
    -- Start scanning
    self:StartNextInspection(false)
end

--- Inspects all group members' gear
--- @param callback function Optional callback when all inspections complete
--- @param progressCallback function Optional callback for progress updates
--- @param clearPrevious boolean Optional - if true, clears previous results before scanning
function InspectionUtils:InspectGroup(callback, progressCallback, clearPrevious)
    -- Add all group members to scan queue
    self:ClearScanQueue()
    local added = self:AddGroupToScanQueue()
    
    if added == 0 then
        if callback then callback({}) end
        return
    end
    
    -- Now scan the queued players
    self:ScanQueuedPlayers(callback, progressCallback, clearPrevious)
end

--- Rescans failed inspections
--- @param callback function Callback to execute when rescan completes
--- @param progressCallback function Optional callback for progress updates
function InspectionUtils:RescanFailedInspections(callback, progressCallback)
    -- Clear existing scan queue
    self:ClearScanQueue()
    
    -- Get the current inspection status to find failed inspections
    local currentStatus = self:GetInspectionStatus()
    if not currentStatus or not currentStatus.failedInspections or #currentStatus.failedInspections == 0 then
        if callback then 
            callback(currentStatus or {
                memberReports = {},
                failedCount = 0,
                inspectedMembers = 0,
                totalMembers = 0,
                hasResults = false
            }) 
        end
        return
    end
    
    -- Add failed inspections to the rescan queue
    self.rescanQueue = {}
    local index = 1
    for _, failedInspection in ipairs(currentStatus.failedInspections) do
        if failedInspection.name then
            local unit = self:FindUnitByName(failedInspection.name)
            if unit then
                table.insert(self.rescanQueue, {
                    unit = unit,
                    name = failedInspection.name,
                    index = index,
                    retryCount = (failedInspection.retryCount or 0) + 1
                })
                index = index + 1
            end
        end
    end
    
    if #self.rescanQueue == 0 then
        if callback then callback(currentStatus) end
        return
    end
    
    -- Start the rescan process
    groupInspectionCallback = callback
    uiProgressCallback = progressCallback
    self:StartNextInspection(true) -- true indicates this is a rescan
end

--- Finds the unit ID for a given player name
--- @param playerName string Name of the player to find
--- @return string|nil Unit ID if found, nil otherwise
function InspectionUtils:FindUnitByName(playerName)
    local InspectionUnits = getInspectionUnits()
    if not InspectionUnits then
        return nil
    end

    return InspectionUnits:FindUnitByName(playerName)
end

--- Starts inspection of the next unit in the queue (consolidated function for both inspection and rescan)
--- @param isRescan boolean Whether this is a rescan operation
function InspectionUtils:StartNextInspection(isRescan)
    local queue = isRescan and self.rescanQueue or self.inspectionQueue
    local progressType = isRescan and "rescan_progress" or "progress"
    local messagePrefix = isRescan and "Rescanning" or "Analyzing"
    local completionType = isRescan and "rescan_completed" or "completed"
    local completionMessage = isRescan and "Rescan complete!" or "Inspection complete!"

    if InCombatLockdown() then
        cancelInspectionTimeout()
        self:ClearInspectionState()
        self:SendProgressCallback({
            type = "error",
            message = "Scan paused - you entered combat"
        })
        if groupInspectionCallback then
            groupInspectionCallback(self:GetInspectionStatus())
            groupInspectionCallback = nil
            uiProgressCallback = nil
        end
        return
    end

    if not queue or #queue == 0 then
        -- All inspections complete
        local successfulCount = 0
        local totalCount = 0

        -- Count successful scans from persistent state
        for playerName, scanData in pairs(self.groupScanState) do
            totalCount = totalCount + 1
            if scanData.hasData then
                successfulCount = successfulCount + 1
            end
        end

        local failedCount = totalCount - successfulCount

        -- Clear any pending inspections and close inspect window
        cancelInspectionTimeout()
        self:ClearInspectionState()

        -- Final progress update
        self:SendProgressCallback({
            type = completionType,
            message = completionMessage,
            failedCount = failedCount,
            canRescan = failedCount > 0
        })

        -- Call the stored callback with results
        if groupInspectionCallback then
            groupInspectionCallback(self:GetInspectionStatus())
            groupInspectionCallback = nil  -- Clear it after use
            uiProgressCallback = nil
        end

        -- Clear the appropriate queue
        if isRescan then
            self.rescanQueue = nil
        else
            self.inspectionQueue = nil
        end

        -- Results are kept in memory for the session
        return
    end

    -- Get next player to inspect
    local nextItem = table.remove(queue, 1)
    local playerName = nextItem.name
    local index = nextItem.index

    -- Progress update
    if uiProgressCallback then
        local totalMembers = 0
        for _ in pairs(self.groupScanState) do
            totalMembers = totalMembers + 1
        end
        if totalMembers == 0 then
            totalMembers = (isRescan and self.rescanQueue and (#self.rescanQueue + 1)) or (self.inspectionQueue and (#self.inspectionQueue + 1)) or 1
        end
        self:SendProgressCallback({
            type = progressType,
            current = index - 1,
            total = totalMembers,
            progress = math.floor(((index - 1) / math.max(totalMembers, 1)) * 100),
            message = messagePrefix .. " " .. playerName .. "..."
        })
    end

    -- Find the unit for this player name
    local unit = nextItem.unit or self:FindUnitByName(playerName)

    -- Set current inspection target
    self.currentInspectionUnit = unit
    self.currentInspectionName = playerName

    if not unit then
        self:HandleInspectionFailure("", playerName, "Player not found in group", isRescan)
        return
    end

    -- Prefer the stable full name for state keys going forward
    playerName = getUnitFullName(unit) or playerName
    self.currentInspectionName = playerName

    -- Attempt to inspect the unit
    local InspectionUnits = getInspectionUnits()
    local failureReason = InspectionUnits and InspectionUnits.GetInspectFailureReason and InspectionUnits:GetInspectFailureReason(unit)
    if failureReason then
        self:HandleInspectionFailure(unit, playerName, failureReason, isRescan)
        return
    end

    local isValid, errorMsg = self:IsValidInspectionUnit(unit, false)
    if isValid then
        local success = self:RequestInspection(unit)
        if success then
            scheduleInspectionTimeout(unit, playerName, isRescan)
        else
            self:HandleInspectionFailure(unit or "", playerName, "Failed to request inspection", isRescan)
        end
    else
        self:HandleInspectionFailure(unit or "", playerName, errorMsg or "Unit validation failed", isRescan)
    end
end

--- Sends a progress callback if callback is registered
--- @param callbackData table The callback data to send
function InspectionUtils:SendProgressCallback(callbackData)
    if uiProgressCallback then
        uiProgressCallback(callbackData)
    end
end

--- Validates if a unit is suitable for inspection
--- @param unit string Unit ID to validate
--- @param requireCanInspect boolean Whether to also check CanInspect (default: false)
--- @return boolean True if unit is valid for inspection
--- @return string|nil Error message if invalid
function InspectionUtils:IsValidInspectionUnit(unit, requireCanInspect)
    local InspectionUnits = getInspectionUnits()
    if not InspectionUnits then
        return false, "InspectionUnits is unavailable"
    end

    return InspectionUnits:IsValidInspectionUnit(unit, requireCanInspect)
end

--- Requests inspection for a unit
--- @param unit string Unit ID
--- @return boolean True if inspection was requested successfully
function InspectionUtils:RequestInspection(unit)
    if not unit or unit == "player" then
        return false
    end

    if InCombatLockdown() then
        return false
    end

    if not CanInspect(unit) then
        return false
    end
    
    -- Close any existing inspect window
    self:CloseInspectWindow()
    
    -- Prefer NotifyInspect for background group scans
    local notify = rawget(_G, "NotifyInspect")
    local success, err
    if type(notify) == "function" then
        success, err = pcall(notify, unit)
    else
        success, err = pcall(InspectUnit, unit)
    end
    if success then
        return true
    else
        return false
    end
end

--- Handles successful inspection data retrieval
--- @param unit string Unit ID
--- @param name string Player name
function InspectionUtils:ProcessInspectionSuccess(unit, name)
    
    -- Check if this is a rescan (if we're in rescan mode)
    local isRescan = self.rescanQueue ~= nil
    cancelInspectionTimeout()

    name = getUnitFullName(unit) or name
    
    -- Generate and show summary for this player
    local summary, details = self:CreatePlayerSummary(unit, name)
    if not summary then
        -- Inspect event fired before item links finished caching; retry shortly.
        self.currentInspectionUnit = unit
        self.currentInspectionName = name
        scheduleInspectionTimeout(unit, name, isRescan)
        C_Timer.After(0.4, function()
            if self.currentInspectionUnit == unit and self.currentInspectionName == name then
                local retrySummary, retryDetails = self:CreatePlayerSummary(unit, name)
                if retrySummary then
                    cancelInspectionTimeout()
                    self:SendProgressCallback({
                        type = isRescan and "rescan_complete" or "character_complete",
                        playerName = name,
                        summary = retrySummary,
                        details = retryDetails,
                        message = name .. ": " .. retrySummary
                    })
                    self.groupScanState[name] = {
                        hasData = true,
                        summary = retrySummary,
                        details = retryDetails,
                        timestamp = time()
                    }
                    self.currentInspectionUnit = nil
                    self.currentInspectionName = nil
                    self:CloseInspectWindow()
                    C_Timer.After(0.5, function()
                        self:StartNextInspection(isRescan and true or false)
                    end)
                else
                    -- Let the timeout handler mark failure if data never completes.
                end
            end
        end)
        return
    end
        
    -- Send character overview through UI progress callback
    self:SendProgressCallback({
        type = isRescan and "rescan_complete" or "character_complete",
        playerName = name,
        summary = summary,
        details = details,
        message = name .. ": " .. summary
    })
    
    -- Store successful scan data in persistent state
    self.groupScanState[name] = {
        hasData = true,
        summary = summary,
        details = details,
        timestamp = time()
    }
    
    -- Clear current inspection state
    self.currentInspectionUnit = nil
    self.currentInspectionName = nil
    
    -- Close inspect window before next inspection
    self:CloseInspectWindow()
    
    -- Move to next inspection/rescan after a short delay
    C_Timer.After(0.5, function()
        if isRescan then
            self:StartNextInspection(true)
        else
            self:StartNextInspection(false)
        end
    end)
end

--- Handles inspection or rescan failure (consolidated function)
--- @param unit string Unit ID
--- @param name string Player name
--- @param reason string Failure reason
--- @param isRescan boolean Whether this is a rescan operation
function InspectionUtils:HandleInspectionFailure(unit, name, reason, isRescan)
    local operationName = isRescan and "rescan" or "inspection"
    local progressType = isRescan and "rescan_failed" or "character_failed"
    cancelInspectionTimeout()

    reason = reason or "Unknown failure"
    if unit and unit ~= "" then
        name = getUnitFullName(unit) or name
    end

    local previous = name and self.groupScanState[name] or nil
    local retryCount = (previous and previous.retryCount or 0) + (isRescan and 1 or 0)

    -- Keep a durable failed record so Retry Failed / UI can show the real reason.
    self.groupScanState[name] = {
        hasData = false,
        summary = "Failed - " .. reason,
        timestamp = time(),
        reason = reason,
        retryCount = retryCount
    }

    -- Send failure through UI progress callback
    self:SendProgressCallback({
        type = progressType,
        playerName = name,
        summary = operationName .. " failed - " .. reason,
        reason = reason,
        message = name .. ": " .. operationName .. " failed - " .. reason
    })

    -- Clear current inspection state
    self.currentInspectionUnit = nil
    self.currentInspectionName = nil

    -- Move to next inspection/rescan after a short delay
    C_Timer.After(0.5, function()
        if isRescan then
            self:StartNextInspection(true)
        else
            self:StartNextInspection(false)
        end
    end)
end

--- Gets all current group members
--- @return table Array of unit IDs
function InspectionUtils:GetGroupMembers()
    local InspectionUnits = getInspectionUnits()
    if not InspectionUnits then
        return {}
    end

    return InspectionUnits:GetGroupMembers()
end

--- Creates a brief summary for an inspected player
--- @param unit string Unit ID
--- @param playerName string Player name
--- @return string Brief summary of player's gear status
--- @return table|nil Detail payload
function InspectionUtils:CreatePlayerSummary(unit, playerName)
    local state = getInspectionState()
    if not state then
        return "Inspection completed", nil
    end

    return state:CreatePlayerSummary(unit, playerName)
end

-- Event frames for debugging and monitoring (not critical for functionality)
-- Create event frame for inspection completion
local inspectionEventFrame = CreateFrame("Frame")
inspectionEventFrame:RegisterEvent("INSPECT_READY")
inspectionEventFrame:RegisterEvent("INSPECT_HONOR_UPDATE")  -- Fired when inspect data is available

inspectionEventFrame:SetScript("OnEvent", function(self, event, unitGUID)
    if event == "INSPECT_READY" or event == "INSPECT_HONOR_UPDATE" then
        -- Check if we have an active inspection
        if InspectionUtils.currentInspectionUnit then
            local unit = InspectionUtils.currentInspectionUnit
            local name = InspectionUtils.currentInspectionName
            
            if unit and UnitGUID(unit) == unitGUID then
                
                -- Process inspection success - now uses live data only
                InspectionUtils:ProcessInspectionSuccess(unit, name)
            end
        end
    end
end)

-- Create frame to monitor inspect window state
local inspectWindowFrame = CreateFrame("Frame")
-- Note: INSPECTOR_SHOW and INSPECTOR_HIDE are not valid WoW events
-- We monitor inspect window state through the IsInspectWindowOpen() function instead

inspectWindowFrame:SetScript("OnEvent", function(self, event)
    -- No events registered for this frame - monitoring is done via polling
end)

--- Checks if the inspect window is currently open
--- @return boolean True if inspect window is open
function InspectionUtils:IsInspectWindowOpen()
    -- Check multiple possible inspect frame names
    for _, frameName in ipairs(INSPECT_FRAME_NAMES) do
        local frame = _G[frameName]
        if frame and frame:IsVisible() then
            return true
        end
    end
    
    -- Also check if any frame with "Inspect" in the name is visible
    for name, frame in pairs(_G) do
        if type(frame) == "table" and type(frame.IsVisible) == "function" and 
           string.find(name, "Inspect") and frame:IsVisible() then
            return true
        end
    end
    
    return false
end

--- Gets the unit currently being inspected
--- @return string|nil Unit ID of currently inspected unit, or nil if none
function InspectionUtils:GetInspectedUnit()
    -- Check multiple possible inspect frame names
    for _, frameName in ipairs(INSPECT_FRAME_NAMES) do
        local frame = _G[frameName]
        if frame and frame:IsVisible() and frame.unit then
            local unit = frame.unit
            if UnitExists(unit) then
                return unit
            end
        end
    end
    
    -- Try to get from global inspected unit if available
    if InspectFrame and InspectFrame.unit then
        local unit = InspectFrame.unit
        if UnitExists(unit) then
            return unit
        end
    end
    
    -- Check if we can determine from any visible inspect-related frame
    for name, frame in pairs(_G) do
        if type(frame) == "table" and type(frame.IsVisible) == "function" and 
           string.find(name, "Inspect") and frame:IsVisible() and frame.unit then
            local unit = frame.unit
            if UnitExists(unit) then
                return unit
            end
        end
    end
    
    return nil
end

--- Checks if the currently inspected unit matches the expected unit
--- @param expectedUnit string Expected unit ID
--- @return boolean True if the inspected unit matches
function InspectionUtils:IsInspectingCorrectUnit(expectedUnit)
    local inspectedUnit = self:GetInspectedUnit()
    if not inspectedUnit then
        return false
    end
    
    -- Compare unit GUIDs for accuracy
    local expectedGUID = UnitGUID(expectedUnit)
    local inspectedGUID = UnitGUID(inspectedUnit)
    
    return expectedGUID == inspectedGUID
end

--- Forces the inspect UI to load and open
--- @param unit string Unit to inspect
function InspectionUtils:ForceOpenInspectUI(unit)
    local isAddOnLoaded = rawget(_G, "IsAddOnLoaded")
    local loadAddOn = rawget(_G, "LoadAddOn")

    if isAddOnLoaded and loadAddOn and not isAddOnLoaded("Blizzard_InspectUI") then
        local loaded, reason = loadAddOn("Blizzard_InspectUI")
        if not loaded then
            return false
        end
    end
    
    -- Try to show the inspect frame
    if InspectFrame and InspectFrame.Show then
        InspectFrame:Show()
        InspectFrame.unit = unit
        return true
    end
    
    return false
end

--- Checks if we can safely attempt to get inspection data
--- @param unit string Unit ID being inspected
--- @return boolean True if safe to attempt data retrieval
function InspectionUtils:CanSafelyGetInspectData(unit)
    -- Must be able to inspect the unit
    if not CanInspect(unit) then
        return false
    end
    
    -- Unit must exist and be a player
    if not UnitExists(unit) or not UnitIsPlayer(unit) then
        return false
    end
    
    -- Unit should not be in combat
    if UnitAffectingCombat(unit) then
        return false
    end
    
    -- Check if inspect window is open (if we're not inspecting ourselves)
    if unit ~= "player" and not self:IsInspectWindowOpen() then
        return false
    end
    
    -- Check if we're inspecting the correct unit
    if unit ~= "player" and not self:IsInspectingCorrectUnit(unit) then
        return false
    end
    
    return true
end

--- Gets list of failed inspections (members that need scanning)
--- @return table List of members that haven't been successfully scanned
function InspectionUtils:GetFailedInspectionsList()
    local state = getInspectionState()
    if not state then
        return {}
    end

    return state:GetFailedInspectionsList()
end

--- Clears scan state for current group members (for fresh scan)
function InspectionUtils:ClearGroupScanState()
    local state = getInspectionState()
    if not state then
        return
    end

    state:ClearGroupScanState()
end

--- Gets current inspection status for UI display
--- @return table Current inspection status
function InspectionUtils:GetInspectionStatus()
    local state = getInspectionState()
    if not state then
        return {
            hasResults = false,
            totalMembers = 0,
            inspectedMembers = 0,
            failedCount = 0,
            canRescan = false,
            memberReports = {},
            failedInspections = {},
            queuedCount = #scanQueue,
            scanQueue = scanQueue
        }
    end

    return state:GetInspectionStatus(scanQueue)
end

--- Closes any open inspect windows
function InspectionUtils:CloseInspectWindow()
    -- Close standard inspect frames
    for _, frameName in ipairs(INSPECT_FRAME_NAMES) do
        local frame = _G[frameName]
        if frame and frame.Hide then
            frame:Hide()
        end
    end
    
    -- Also try to close via HideUIPanel (Blizzard's preferred method)
    if InspectFrame then
        HideUIPanel(InspectFrame)
    end
end

--- Clears inspection state and closes windows
function InspectionUtils:ClearInspectionState()
    cancelInspectionTimeout()
    self.currentInspectionUnit = nil
    self.currentInspectionName = nil
    self:CloseInspectWindow()
end
