--[[
============================================================================
OutfitCompatibility.lua  (added in v14.3.0)
============================================================================

Determines whether a saved outfit is wearable / equippable by a given class.
Two concerns:

  1. ARMOR compatibility — chest/legs/etc must match the class's primary
     armor type. Cloth/Leather/Mail/Plate. A Mage cannot transmog a leather
     chestpiece onto its cloth chestpiece. This is HARD: armor mismatch
     means the outfit cannot be applied.

  2. WEAPON compatibility — main hand / off hand weapon types must match
     class proficiencies. A Mage cannot transmog a sword onto its staff
     (well — technically Mages can use swords on 5.4.8, but you get the
     idea). This is SOFT: weapon mismatches are warnings only because the
     player can swap weapons before transmogging.

Slots that have NO armor type (always-cosmetic):
  Cloak, Shirt, Tabard — no armor restriction, every class wears them.

Why a separate module:
  - The classification logic (item -> armor type) requires GetItemInfoInstant
    calls and caching. That's its own concern.
  - Used from multiple places (Outfits panel filter, save flow enrichment,
    future "show on dressing room" / "show on character pane" features).
  - Keeping it separate from Outfits.lua means the UI file stays focused on
    rendering.

Tables CLASS_PRIMARY_ARMOR / CLASS_WEAPONS are duplicated from TransmogUI.lua
for now. Once we do the audit and consolidate, we'll have a single source.
============================================================================
]]--
local ADDON_NAME, NS = ...
local ETB = NS.ETB
NS.ETB.OutfitCompatibility = NS.ETB.OutfitCompatibility or {}
local OC = NS.ETB.OutfitCompatibility

-- =============================================================================
-- CLASS PROFILES
-- =============================================================================
-- v14.8.0: tables consolidated to Data/Categories/ClassData.lua.
-- We keep these public references on OC so any existing callers that read
-- OC.CLASS_PRIMARY_ARMOR or OC.CLASS_WEAPONS keep working — the assignments
-- below pull from the canonical source. If ClassData is missing (e.g. load-
-- order regression), we fall back to inline definitions so the addon still
-- works in degraded mode.

local CD = ETB.ClassData
if CD then
    OC.CLASS_PRIMARY_ARMOR = CD.PRIMARY_ARMOR
    OC.CLASS_WEAPONS = CD.WEAPONS
else
    -- Fallback if ClassData didn't load. Safer to keep a working addon than
    -- to nil-crash on first compatibility check.
    OC.CLASS_PRIMARY_ARMOR = {
        WARRIOR = "PLATE", PALADIN = "PLATE", DEATHKNIGHT = "PLATE",
        HUNTER = "MAIL", SHAMAN = "MAIL",
        ROGUE = "LEATHER", DRUID = "LEATHER", MONK = "LEATHER",
        MAGE = "CLOTH", WARLOCK = "CLOTH", PRIEST = "CLOTH",
    }
    OC.CLASS_WEAPONS = {
        WARRIOR     = { AXE=true, SWORD=true, MACE=true, POLEARM=true, STAFF=true, DAGGER=true, FIST=true, SHIELD=true },
        PALADIN     = { AXE=true, SWORD=true, MACE=true, POLEARM=true, SHIELD=true },
        DEATHKNIGHT = { AXE=true, SWORD=true, MACE=true, POLEARM=true },
        HUNTER      = { AXE=true, SWORD=true, POLEARM=true, STAFF=true, DAGGER=true, FIST=true, BOW=true, GUN=true, CROSSBOW=true },
        SHAMAN      = { AXE=true, MACE=true, STAFF=true, DAGGER=true, FIST=true, SHIELD=true },
        ROGUE       = { AXE=true, SWORD=true, MACE=true, DAGGER=true, FIST=true },
        DRUID       = { MACE=true, POLEARM=true, STAFF=true, DAGGER=true, FIST=true },
        MONK        = { AXE=true, SWORD=true, MACE=true, POLEARM=true, STAFF=true, FIST=true },
        MAGE        = { SWORD=true, STAFF=true, DAGGER=true, WAND=true },
        WARLOCK     = { SWORD=true, STAFF=true, DAGGER=true, WAND=true },
        PRIEST      = { MACE=true, STAFF=true, DAGGER=true, WAND=true },
    }
end

-- Armor slots whose item type IS armor-restricted (skip cloak/shirt/tabard).
-- These are slot NAMES as stored in outfit.items (matches Blizzard slot tokens).
local ARMOR_RESTRICTED_SLOTS = {
    Head = true, Shoulder = true, Chest = true, Wrist = true,
    Hands = true, Waist = true, Legs = true, Feet = true,
}

-- Slots that NEVER restrict armor type (always cosmetic / universal).
local UNRESTRICTED_SLOTS = {
    Back = true, Shirt = true, Tabard = true,
}

-- The Blizzard `equipLoc` string -> our slot-name token, used to map
-- GetItemInfoInstant's result back to where the item lives in an outfit.
-- Not directly used in this module right now, but kept for future enrichment.
--
-- v14.8.0 audit note: this looks like a duplicate of INVTYPE_TO_SLOT in
-- SetBuilder.lua and Commands.lua (/etb deepfix), but each maps to a
-- DIFFERENT value space:
--   - This file: INVTYPE -> slot NAME ("Head", "Chest")
--   - SetBuilder: INVTYPE -> SetBuilder internal slot number (1=head, 2=shoulder)
--   - Commands /etb deepfix: INVTYPE -> WoW invSlot number (1=head, 3=shoulder)
-- Consolidating would require three output-side tables anyway. Left separate.
local EQUIP_LOC_TO_SLOT = {
    INVTYPE_HEAD = "Head", INVTYPE_SHOULDER = "Shoulder",
    INVTYPE_CHEST = "Chest", INVTYPE_ROBE = "Chest",
    INVTYPE_WRIST = "Wrist", INVTYPE_HAND = "Hands",
    INVTYPE_WAIST = "Waist", INVTYPE_LEGS = "Legs", INVTYPE_FEET = "Feet",
    INVTYPE_CLOAK = "Back", INVTYPE_BODY = "Shirt", INVTYPE_TABARD = "Tabard",
    INVTYPE_WEAPON = "MainHand", INVTYPE_WEAPONMAINHAND = "MainHand",
    INVTYPE_WEAPONOFFHAND = "OffHand", INVTYPE_HOLDABLE = "OffHand",
    INVTYPE_RANGED = "MainHand", INVTYPE_RANGEDRIGHT = "MainHand",
    INVTYPE_2HWEAPON = "MainHand", INVTYPE_THROWN = "MainHand",
    INVTYPE_SHIELD = "OffHand",
}

-- subclassID -> our weapon-type token. From Blizzard's Item Subclass enum.
-- Reference: Item.itemSubClass values for itemClass=2 (weapons).
local WEAPON_SUBCLASS_TO_TYPE = {
    [0] = "AXE",      -- One-Handed Axe
    [1] = "AXE",      -- Two-Handed Axe (same proficiency token)
    [2] = "BOW",
    [3] = "GUN",
    [4] = "MACE",     -- One-Handed Mace
    [5] = "MACE",     -- Two-Handed Mace
    [6] = "POLEARM",
    [7] = "SWORD",    -- One-Handed Sword
    [8] = "SWORD",    -- Two-Handed Sword
    [10] = "STAFF",
    [13] = "FIST",
    [15] = "DAGGER",
    [16] = "THROWN",
    [18] = "CROSSBOW",
    [19] = "WAND",
    [20] = "FISHINGPOLE",
}

-- subclassID -> our armor-type token. itemClass=4 (armor), but only the
-- material subclasses (not shields, librams, etc).
local ARMOR_SUBCLASS_TO_TYPE = {
    [1] = "CLOTH",
    [2] = "LEATHER",
    [3] = "MAIL",
    [4] = "PLATE",
}

-- =============================================================================
-- ITEM CLASSIFICATION
-- =============================================================================

-- Cache (itemID -> armor token | false). False means "asked, not armor" so
-- repeat lookups don't keep calling GetItemInfoInstant. We accept that some
-- items might return nil from the API on first attempt; those will retry.
local _armorCache = {}
local _weaponCache = {}

function OC:GetItemArmorType(itemID)
    if not itemID then return nil end
    local cached = _armorCache[itemID]
    if cached ~= nil then return cached or nil end
    if not GetItemInfoInstant then return nil end
    -- MoP 5.4.8 return shape: (itemID, itemType, itemSubType, equipLoc, icon, classID, subclassID)
    -- Note the first return is the itemID (numeric), NOT a name.
    local _, _, _, _, _, classID, subclassID = GetItemInfoInstant(itemID)
    if classID == 4 then  -- Armor
        local token = ARMOR_SUBCLASS_TO_TYPE[subclassID]
        _armorCache[itemID] = token or false
        return token
    end
    _armorCache[itemID] = false
    return nil
end

function OC:GetItemWeaponType(itemID)
    if not itemID then return nil end
    local cached = _weaponCache[itemID]
    if cached ~= nil then return cached or nil end
    if not GetItemInfoInstant then return nil end
    local _, _, _, _, _, classID, subclassID = GetItemInfoInstant(itemID)
    if classID == 2 then  -- Weapon
        local token = WEAPON_SUBCLASS_TO_TYPE[subclassID]
        _weaponCache[itemID] = token or false
        return token
    elseif classID == 4 and subclassID == 6 then
        -- Shields are armor classID=4, subclassID=6, but we treat them as
        -- "weapons" for proficiency purposes (Paladins shield, Mages don't).
        _weaponCache[itemID] = "SHIELD"
        return "SHIELD"
    end
    _weaponCache[itemID] = false
    return nil
end

-- Walks outfit.items, fills in armorType / weaponType where missing. Idempotent.
-- Returns the number of items enriched (for diagnostics).
function OC:EnrichOutfitItems(outfit)
    if not outfit or not outfit.items then return 0 end
    local enriched = 0
    for slotName, itemData in pairs(outfit.items) do
        if itemData and itemData.itemID then
            if ARMOR_RESTRICTED_SLOTS[slotName] and not itemData.armorType then
                local t = self:GetItemArmorType(itemData.itemID)
                if t then itemData.armorType = t; enriched = enriched + 1 end
            end
            if (slotName == "MainHand" or slotName == "OffHand") and not itemData.weaponType then
                local w = self:GetItemWeaponType(itemData.itemID)
                if w then itemData.weaponType = w; enriched = enriched + 1 end
            end
        end
    end
    return enriched
end

-- =============================================================================
-- COMPATIBILITY CHECK
-- =============================================================================

-- Returns:
--   armorOK    (boolean) — false means outfit cannot be applied (hard fail)
--   weaponOK   (boolean) — false means outfit can apply, but weapon mismatch
--                          (soft warning only)
--   reason     (string)  — short label like "Cloth", "Sword", or nil
--
-- classID is the WoW class token (e.g. "WARRIOR", "MAGE"). If nil, returns
-- (true, true, nil) — assume compatible.
function OC:CheckCompatibility(outfit, classID)
    if not outfit or not outfit.items then return true, true, nil end
    if not classID then return true, true, nil end

    -- Lazy enrichment so older outfits work too
    self:EnrichOutfitItems(outfit)

    local primaryArmor = self.CLASS_PRIMARY_ARMOR[classID]
    local weapons = self.CLASS_WEAPONS[classID]

    local armorMismatchType = nil
    local weaponMismatchType = nil

    for slotName, itemData in pairs(outfit.items) do
        if itemData and itemData.itemID then
            -- Armor check (hard)
            if ARMOR_RESTRICTED_SLOTS[slotName]
                and itemData.armorType
                and primaryArmor
                and itemData.armorType ~= primaryArmor
            then
                armorMismatchType = itemData.armorType
                break  -- one bad slot is enough; stop scanning
            end
            -- Weapon check (soft)
            if (slotName == "MainHand" or slotName == "OffHand")
                and itemData.weaponType
                and weapons
                and not weapons[itemData.weaponType]
            then
                weaponMismatchType = itemData.weaponType
                -- don't break — keep scanning for armor mismatch
            end
        end
    end

    if armorMismatchType then
        return false, weaponMismatchType == nil, armorMismatchType
    end
    if weaponMismatchType then
        return true, false, weaponMismatchType
    end
    return true, true, nil
end

-- Convenience: short status string for the outfit list info line.
-- Returns one of:
--   nil                           — compatible, nothing to show
--   "[Cloth]" / "[Leather]" / etc — armor mismatch (incompatible)
--   "(weapon: Sword)" etc         — weapon mismatch (warning only)
function OC:GetStatusLabel(outfit, classID)
    local armorOK, weaponOK, reason = self:CheckCompatibility(outfit, classID)
    if not armorOK then
        return string.format("|cffff5555[%s]|r",
            (reason or "?"):sub(1, 1) .. (reason or "?"):sub(2):lower())
    end
    if not weaponOK then
        return string.format("|cffffaa00(weapon: %s)|r",
            (reason or "?"):sub(1, 1) .. (reason or "?"):sub(2):lower())
    end
    return nil
end

-- Convenience: full multi-line tooltip text for hover.
function OC:GetTooltipText(outfit, classID)
    local armorOK, weaponOK, reason = self:CheckCompatibility(outfit, classID)
    if armorOK and weaponOK then return nil end

    local lines = {}
    if not armorOK then
        lines[#lines + 1] = "|cffff5555Cannot equip:|r " .. (reason or "?") .. " armor"
        lines[#lines + 1] = "Your class wears " .. (self.CLASS_PRIMARY_ARMOR[classID] or "?") .. "."
    elseif not weaponOK then
        lines[#lines + 1] = "|cffffaa00Weapon mismatch:|r " .. (reason or "?")
        lines[#lines + 1] = "You can still load it; swap weapons first."
    end
    return table.concat(lines, "\n")
end
