--[[
    Commander 3.0 - Type System

    Defines object types for the pipeline.
    All data flowing through pipes conforms to a standard structure.
]]

local ADDON_NAME, C3 = ...

local Types = {}
C3.Types = Types

-- Use shared utilities
local GetThemeColor = function(...) return C3.Colors.GetColor(...) end

-- Type registry
Types.registry = {}

---Register a new type
---@param typeName string
---@param definition table
function Types:Register(typeName, definition)
    -- Support both simple array of names and array of {name, desc} tables
    local props = {}
    if definition.properties then
        for _, prop in ipairs(definition.properties) do
            if type(prop) == "string" then
                props[#props + 1] = { name = prop, desc = nil }
            elseif type(prop) == "table" then
                props[#props + 1] = { name = prop[1] or prop.name, desc = prop[2] or prop.desc }
            end
        end
    end

    self.registry[typeName] = {
        name = typeName,
        properties = props,
        source = definition.source,  -- e.g., "bags", "who", "mounts"
        description = definition.description,
        defaultSort = definition.defaultSort or "name",
        format = definition.format,
        validate = definition.validate,
    }
end

---Get type definition
---@param typeName string
---@return table?
function Types:Get(typeName)
    return self.registry[typeName]
end

---Get all registered type names
---@return table
function Types:GetAll()
    local names = {}
    for name in pairs(self.registry) do
        table.insert(names, name)
    end
    table.sort(names)
    return names
end

---Find types that a command produces (reverse lookup by source)
---@param commandName string Command name (e.g., "bags", "quests")
---@return table[] types Array of type definitions
function Types:GetTypesForCommand(commandName)
    local results = {}
    local cmdLower = commandName:lower()

    for _, typeDef in pairs(self.registry) do
        if typeDef.source then
            -- source is comma-separated: "bags, equipped" or "who <query>"
            for sourceEntry in typeDef.source:gmatch("([^,]+)") do
                sourceEntry = sourceEntry:match("^%s*(.-)%s*$") -- trim whitespace
                -- Extract first word only (handles "who <query>" -> "who")
                local sourceCmd = sourceEntry:match("^(%S+)")
                if sourceCmd and sourceCmd:lower() == cmdLower then
                    results[#results + 1] = typeDef
                    break
                end
            end
        end
    end

    return results
end

-------------------------------------------------------------------------------
-- Object Lifecycle (no-ops kept for API compatibility)
-------------------------------------------------------------------------------

---Release a typed object (no-op)
---@param obj table
function Types.Release(obj)
end

---Release all objects in an array (no-op)
---@param objects table
function Types.ReleaseAll(objects)
end

---Get pool statistics (returns empty - pooling removed)
---@return table stats
function Types.GetPoolStats()
    return {}
end

---Create a typed object (Robust to . and : syntax)
---@param typeName string
---@param data table
---@return table
function Types.Create(selfOrTypeName, typeNameOrData, dataOrNil)
    local typeName, data

    -- Handle : vs . call styles
    -- If called as Types:Create("Name", {}), first arg is self (Types table)
    if type(selfOrTypeName) == "table" and selfOrTypeName == Types then
        typeName = typeNameOrData
        data = dataOrNil
    else
        -- Called as Types.Create("Name", {})
        typeName = selfOrTypeName
        data = typeNameOrData
    end

    -- Guard against nil data to prevent crashes
    if type(data) ~= "table" then
        data = {}
    end

    local obj = {
        _type = typeName,
        _meta = {
            createdAt = GetTime(),
        },
    }

    for k, v in pairs(data) do
        if k ~= "_type" and k ~= "_meta" then
            obj[k] = v
        end
    end

    return obj
end

---Check if an object is of a specific type
---@param obj table
---@param typeName string
---@return boolean
function Types:IsType(obj, typeName)
    return type(obj) == "table" and obj._type == typeName
end

---Get the type of an object
---@param obj any
---@return string?
function Types:GetType(obj)
    if type(obj) == "table" then
        return obj._type
    end
    return nil
end

---Validate an object against its type definition
---@param obj table
---@return boolean valid
---@return string? error
function Types:Validate(obj)
    if type(obj) ~= "table" then
        return false, "Not a table"
    end

    local typeName = obj._type
    if not typeName then
        return true -- Untyped objects are valid
    end

    local typedef = self.registry[typeName]
    if not typedef then
        return true -- Unknown types are valid
    end

    -- Check required properties
    for propName, propDef in pairs(typedef.properties) do
        local isOptional = type(propDef) == "string" and propDef:match("%?$")

        if not isOptional and obj[propName] == nil then
            return false, "Missing required property: " .. propName
        end
    end

    -- Run custom validator
    if typedef.validate then
        return typedef.validate(obj)
    end

    return true
end

-------------------------------------------------------------------------------
-- Built-in Types
-------------------------------------------------------------------------------

-- Text (simple string wrapper)
Types:Register("Text", {
    description = "Plain text content",
    source = "echo, print",
    properties = {
        { "content", "Text string content" },
    },
    format = function(obj)
        return obj.content
    end,
})

-- Item (base definition - extended in datasources/items.lua)
Types:Register("Item", {
    description = "Item from bags or equipment",
    source = "bags, equipped",
    properties = {
        { "name", "Item name" },
        { "itemId", "Item ID number" },
        { "ilvl", "Item level" },
        { "quality", "Quality: 0=Poor to 5=Legendary" },
        { "icon", "Icon texture ID" },
        { "count", "Stack count" },
        { "bag", "Bag index (0-4)" },
        { "slot", "Slot index within bag" },
        { "itemLink", "Clickable item link" },
        { "type", "Item type: Armor, Weapon, etc." },
        { "subType", "Item subtype: Plate, Sword, etc." },
        { "equipLoc", "Equipment slot location" },
        { "isBound", "true if soulbound" },
    },
    defaultSort = "name",
})

-- Mount (base definition - extended in datasources/mounts.lua)
Types:Register("Mount", {
    description = "Collected mount",
    source = "mounts",
    properties = {
        { "name", "Mount name" },
        { "id", "Mount ID number" },
        { "spellID", "Spell ID to summon mount" },
        { "icon", "Icon texture ID" },
        { "isUsable", "true if usable in current zone" },
        { "isCollected", "true if collected" },
        { "mountType", "Type: ground, flying, aquatic, etc." },
        { "source", "How to obtain this mount" },
    },
    defaultSort = "name",
})

-- Spell
Types:Register("Spell", {
    description = "Spell from spellbook",
    source = "spells",
    properties = {
        { "name", "Spell name" },
        { "spellID", "Spell ID number" },
        { "icon", "Icon texture ID" },
        { "castTime", "Cast time in milliseconds (0 = instant)" },
        { "rank", "Spell rank or subtext" },
        { "school", "Spell school: Physical, Fire, Frost, etc." },
        { "isPassive", "true if passive ability" },
        { "isUsable", "true if currently usable" },
        { "isOnCooldown", "true if on cooldown" },
        { "minRange", "Minimum range in yards" },
        { "maxRange", "Maximum range in yards" },
        { "tabName", "Spellbook tab name" },
    },
    defaultSort = "name",
})

-- Currency (base definition - extended in datasources/currency.lua)
Types:Register("Currency", {
    description = "Currency token",
    source = "currencies",
    properties = {
        { "name", "Currency name" },
        { "currencyID", "Currency ID number" },
        { "amount", "Current amount" },
        { "icon", "Icon texture ID" },
        { "maxAmount", "Maximum cap (0 = unlimited)" },
    },
    defaultSort = "name",
})

-- Player/Unit
Types:Register("Unit", {
    description = "Game unit (player, NPC, or creature)",
    source = "target, focus, player",
    properties = {
        { "name", "Unit name" },
        { "class", "Class file name: WARRIOR, MAGE, etc." },
        { "level", "Unit level" },
        { "health", "Current health" },
        { "maxHealth", "Maximum health" },
        { "power", "Current power (mana/rage/energy)" },
        { "maxPower", "Maximum power" },
        { "guid", "Unique unit identifier (GUID)" },
    },
    defaultSort = "name",
})

-- EmptySlot
Types:Register("EmptySlot", {
    description = "Empty bag or equipment slot",
    source = "bags -e, equipped -e",
    properties = {
        { "bag", "Bag index (0=backpack, 1-4=bags)" },
        { "slot", "Slot index within bag" },
        { "slotName", "Equipment slot name (if equipment slot)" },
        { "display", "Formatted display string" },
    },
})

-- Error
Types:Register("Error", {
    description = "Error message from command execution",
    source = "any command",
    properties = {
        { "message", "Error message text" },
        { "code", "Error code identifier (optional)" },
    },
    format = function(obj)
        local errColor = GetThemeColor("accent.error") or "BF616A"
        return string.format("|cff%sError: %s|r", errColor, obj.message)
    end,
})

-------------------------------------------------------------------------------
-- Helper functions
-------------------------------------------------------------------------------

---Create a Text object
---@param content string
---@return table
function Types.Text(content)
    return Types:Create("Text", { content = content })
end

---Create an Error object
---@param message string
---@param code? string
---@return table
function Types.Error(message, code)
    return Types:Create("Error", { message = message, code = code })
end

---Wrap any value as a typed object if needed
---@param value any
---@return table
function Types.Wrap(value)
    if type(value) == "table" and value._type then
        return value -- Already typed
    elseif type(value) == "string" then
        return Types.Text(value)
    elseif type(value) == "table" then
        -- Wrap array of objects
        local wrapped = {}
        for i, v in ipairs(value) do
            wrapped[i] = Types.Wrap(v)
        end
        return wrapped
    else
        return Types.Text(tostring(value))
    end
end

return Types
