Actions

Module

DataTableParser: Difference between revisions

From Dune Awakening DB

mNo edit summary
mNo edit summary
 
(8 intermediate revisions by the same user not shown)
Line 254: Line 254:
end
end


----------------------------------------------------------------------
-- Helper function to load/parse the refining data wiki table
----------------------------------------------------------------------
local function loadRefiningWikiTable()
    local title = "Data:Refining"  -- Make sure this matches your wiki page path exactly
    local content = mw.title.new(title):getContent()
    if not content then
        return nil, "Error: Refining wiki table page not found at " .. title
    end
    local lines = mw.text.split(content, "\n")
    -- Filter out magic words, noindex tags, or blank lines
    local filtered = {}
    for _, line in ipairs(lines) do
        line = mw.text.trim(line)
        if line ~= "" and not line:match("^__") and not line:match("<noindex>") and not line:match("</noindex>") then
            table.insert(filtered, line)
        end
    end
    if #filtered < 2 then
        return nil, "Error: No valid wiki table content found in " .. title
    end
    local inTable = false
    local headers = {}  -- store the table headers
    local colMap = {}  -- map header name to column index
    local data = {}    -- array of row arrays
    local currentRow = {}
    local readingHeader = true  -- before the first row separator ("|-"), treat cells as headers
    for _, line in ipairs(filtered) do
        if line:match("^{|") then
            -- Start of table (line starts with "{|")
            inTable = true
        elseif line:match("^|%-") then
            -- Row separator ("|-")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            currentRow = {}
            readingHeader = false  -- after the first row separator, subsequent cells are row cells
        elseif line:match("^!") then
            -- Header cell (starts with "!")
            local header = line:gsub("^!+%s*", "")  -- Remove leading ! and whitespace
            header = mw.text.trim(header)
            table.insert(headers, header)
        elseif line:match("^|%+") then
            -- Table caption line ("|+"), skip it
        elseif line:match("^|}") then
            -- End of table ("|}")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            inTable = false
        elseif inTable and line:match("^|[^%-+}]") then
            -- Process table cell lines (lines that start with "|" but not "|+", "|-", or "|}")
            local cell = line:gsub("^|+%s*", "")  -- Remove leading | and whitespace
            cell = mw.text.trim(cell)
            if not readingHeader then
                table.insert(currentRow, cell)
            end
        end
    end
    -- Build colMap from headers (map header name to its column index)
    for i, h in ipairs(headers) do
        colMap[mw.text.trim(h)] = i
    end
    -- Add debug logging
    if #data == 0 then
        return nil, "Error: No data rows found in " .. title
    end
    return { rows = data, colMap = colMap }, nil
end
----------------------------------------------------------------------
-- Function to display refining recipes for a building
----------------------------------------------------------------------
-- Function to display refining recipes for a building
-- Function to display refining recipes for a building
function p.getRefiningRecipes(frame)
function p.getRefiningRecipes(frame)
     local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
     local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
   
    -- For debugging
    -- return "Looking for recipes for building: " .. buildingName
      
      
     -- Get refining data
     -- Get refining data
Line 269: Line 347:
     local rows = refiningData.rows
     local rows = refiningData.rows
      
      
     -- Get column indices (using hardcoded indices to match the table structure)
     -- Fixed indices for the required columns
     local resourceClassCol = 1  -- Resource Class
     local resourceClassCol = 1  -- Resource Class
     local nameCol = 2          -- Name
     local nameCol = 2          -- Name
Line 275: Line 353:
     local ingredientsCol = 4  -- Ingredients to Smelt
     local ingredientsCol = 4  -- Ingredients to Smelt
     local recipeCol = 5        -- Recipe to Smelt
     local recipeCol = 5        -- Recipe to Smelt
     local refinerNeededCol = 8 -- Refiner Needed
   
    -- Helper function to find refiner name in a row
     local function getRefinerFromRow(row)
        -- Check potential positions for Refiner Needed column
        -- Try column 8 first (matches original table structure)
        if row[8] and row[8] ~= "" then
            return row[8]
        -- Try column 6 next (as seen in debug output)
        elseif row[6] and row[6] ~= "" then
            return row[6]
        -- As a fallback, try each column from the end
        else
            for i = #row, 1, -1 do
                if row[i] and row[i] ~= "" then
                    return row[i]
                end
            end
        end
        return ""
    end
      
      
     -- Helper function to normalize text for comparison
     -- Helper function to normalize text for comparison
Line 291: Line 388:
          
          
         return text
         return text
    end
   
    -- Helper function to check if a building name is in a semicolon-separated list
    local function isInList(list, name)
        -- Normalize the name we're looking for
        name = normalizeText(name)
       
        -- Split the list by semicolons
        for _, item in ipairs(mw.text.split(list, ";")) do
            -- Normalize each item and compare
            if normalizeText(item) == name then
                return true
            end
        end
       
        return false
     end
     end
      
      
Line 312: Line 393:
     local matchingRecipes = {}
     local matchingRecipes = {}
     for _, row in ipairs(rows) do
     for _, row in ipairs(rows) do
         -- Check if the Refiner Needed column contains the building name
         -- Get the refiner name from the row
         local refinerNeeded = row[refinerNeededCol] or ""
         local refinerNeeded = getRefinerFromRow(row)
          
          
         -- Debug output for the first few rows
         -- Normalize both texts for comparison
         -- if _ <= 3 then
         local normalizedRefiner = normalizeText(refinerNeeded)
        --    return "Row " .. _ .. ": " .. refinerNeeded .. ", Looking for: " .. buildingName ..
         local normalizedBuilding = normalizeText(buildingName)
        --            ", Normalized: " .. normalizeText(refinerNeeded) ..
         --            ", IsInList: " .. tostring(isInList(refinerNeeded, buildingName))
        -- end
          
          
         if isInList(refinerNeeded, buildingName) then
         if normalizedRefiner == normalizedBuilding then
             table.insert(matchingRecipes, row)
             table.insert(matchingRecipes, row)
         end
         end
Line 329: Line 407:
     -- If no recipes found, return a message
     -- If no recipes found, return a message
     if #matchingRecipes == 0 then
     if #matchingRecipes == 0 then
         return '<tr><td colspan="4" style="text-align:center;">No refining recipes found for this building.</td></tr>'
         return '<tr><td colspan="3" style="text-align:center;">No refining recipes found for this building.</td></tr>'
     end
     end
   
    -- Start with the updated table headers
    local output = "<tr>\n" ..
              "<th style=\"text-align:left;\">Output</th>\n" ..
              "<th style=\"text-align:left;\">Ingredients</th>\n" ..
              "<th style=\"text-align:left;\">Craft Time</th>\n" ..
                  "</tr>\n"
      
      
     -- Generate table rows for each recipe
     -- Generate table rows for each recipe
    local output = ""
     for _, recipe in ipairs(matchingRecipes) do
     for _, recipe in ipairs(matchingRecipes) do
         local outputItem = recipe[nameCol] or ""
         local outputItem = recipe[nameCol] or ""
Line 349: Line 433:
         end
         end
          
          
         -- Format cells with icons for resources
         -- Format the output item with icon, including quantity
         local formattedOutput = p.iconize({args = {[1] = outputItem}})
         local formattedOutput = p.iconize({args = {[1] = outputItem}})
         local formattedIngredients = p.iconize({args = {[1] = ingredients}})
        if qty ~= "1" then
            formattedOutput = formattedOutput .. " × " .. qty
        end
       
        -- Split ingredients by semicolon and format each with icon
         local formattedIngredients = ""
        if ingredients ~= "" then
            local ingredientsList = mw.text.split(ingredients, ";")
            for i, ingredient in ipairs(ingredientsList) do
                local trimmedIngredient = mw.text.trim(ingredient)
                if trimmedIngredient ~= "" then
                    local formattedIngredient = p.iconize({args = {[1] = trimmedIngredient}})
                    if i > 1 then
                        formattedIngredients = formattedIngredients .. "<br>"
                    end
                    formattedIngredients = formattedIngredients .. formattedIngredient
                end
            end
        end
          
          
         -- Add table row
         -- Add table row
         output = output .. "<tr>\n" ..
         output = output .. "<tr>\n" ..
                 "<td>" .. formattedOutput .. "</td>\n" ..
                 "<td style=\"text-align:left;\">" .. formattedOutput .. "</td>\n" ..
                "<td>" .. formattedIngredients .. "</td>\n" ..
    "<td style=\"text-align:left;\">" .. formattedIngredients .. "</td>\n" ..
                "<td style=\"text-align:center;\">" .. time .. "</td>\n" ..
        "<td style=\"text-align:left;\">" .. time .. "</td>\n" ..
                "<td style=\"text-align:center;\">" .. qty .. "</td>\n" ..
                 "</tr>\n"
                 "</tr>\n"
    end
   
    return output
end
----------------------------------------------------------------------
-- Debug function to show raw refining data
----------------------------------------------------------------------
function p.debugRefiningData(frame)
    local refiningData, err = loadRefiningWikiTable()
    if not refiningData then
        return "Error loading refining data: " .. (err or "Unknown error")
    end
   
    local output = "Refining data loaded successfully!\n\n"
    output = output .. "Number of rows: " .. #refiningData.rows .. "\n\n"
   
    output = output .. "Column mappings:\n"
    for name, index in pairs(refiningData.colMap) do
        output = output .. name .. " -> " .. index .. "\n"
    end
   
    output = output .. "\nFirst 3 rows:\n"
    for i = 1, math.min(3, #refiningData.rows) do
        output = output .. "Row " .. i .. ":\n"
        for j, cell in ipairs(refiningData.rows[i]) do
            output = output .. "  Column " .. j .. ": " .. cell .. "\n"
        end
     end
     end
      
      
Line 431: Line 560:
end
end


-- Helper function to display building with the template
-- Helper function to display building with the template
-- Helper function to display building with the template
function p.displayBuilding(frame)
function p.displayBuilding(frame)
     local buildingName = frame.args[1] or frame.args.name
     local buildingName = frame.args[1] or frame.args.name
    local customNotes = frame.args[2] or ""
      
      
     if not buildingName then
     if not buildingName then
Line 461: Line 590:
     end
     end
      
      
     -- For simplicity and to avoid confusion, map each parameter explicitly
     -- Map building data to template parameters
     local args = {}
     local args = {}
      
      
     -- Map row values to template parameters directly
     -- Direct mapping with explicit indices based on the wiki table
     args["Name"] = building[2] or ""                -- Column 2: Name
     args["Name"] = building[2] or ""                -- Column 2: Name
     args["Description"] = building[3] or ""         -- Column 3: Description 
     args["Tier"] = building[3] or ""               -- Column 3: Tier
     args["JourneyRequirement"] = building[4] or "" -- Column 4: JourneyRequirement
     args["Description"] = building[4] or ""         -- Column 4: Description
     args["Health"] = building[5] or ""             -- Column 5: Health
     args["JourneyRequirement"] = building[5] or "" -- Column 5: JourneyRequirement
     args["EnergyConsumption"] = building[6] or ""   -- Column 6: PowerCost
     args["Health"] = building[6] or ""             -- Column 6: Health
     args["GeneratesPower"] = building[7] or ""     -- Column 7: GeneratesPower
     args["EnergyConsumption"] = building[7] or ""   -- Column 7: PowerCost
     args["StorageSlots"] = building[8] or ""       -- Column 8: StorageSlots
     args["GeneratesPower"] = building[8] or ""     -- Column 8: GeneratesPower
     args["StorageVolume"] = building[9] or ""       -- Column 9: StorageCapacity
     args["StorageSlots"] = building[9] or ""       -- Column 9: StorageSlots
     args["Components"] = building[10] or ""         -- Column 10: RecipeToBuild
     args["StorageVolume"] = building[10] or ""     -- Column 10: StorageCapacity
     args["PlacedWith"] = building[11] or ""        -- Column 11: PlacedWith
     args["Components"] = building[11] or ""        -- Column 11: RecipeToBuild
     args["AdditionalNotes"] = building[12] or ""   -- Column 12: AdditionalNotes
     args["PlacedWith"] = building[12] or ""         -- Column 12: PlacedWith
     args["ImageFile"] = building[14] or ""          -- Column 14: ImageFile
     args["ImageFile"] = building[15] or ""          -- Column 15: ImageFile
     args["PrimarySource"] = "Crafting"              -- Default value
     args["PrimarySource"] = "Crafting"              -- Default value
      
      
     -- Handle YouTube video if present (Column 19: Youtube)
    -- Use custom notes if provided, otherwise use notes from data
     if building[19] and building[19] ~= "-" and building[19] ~= "" then
    if customNotes and customNotes ~= "" then
        args["AdditionalNotes"] = customNotes
    else
        args["AdditionalNotes"] = building[13] or "" -- Column 13: AdditionalNotes
    end
   
     -- Handle YouTube video (Column 20)
     if building[20] and building[20] ~= "-" and building[20] ~= "" then
         -- Set video title
         -- Set video title
         args["VideoTitle"] = "Building Guide"
         args["VideoTitle"] = "Building Guide"
          
          
         local youtubeUrl = building[19]
         local youtubeUrl = building[20]
          
          
         -- Extract video ID from the URL
         -- Extract video ID from the URL
         local videoId = youtubeUrl:match("v=([%w-_]+)")
         local videoId = youtubeUrl:match("v=([%w-_]+)")
         if videoId then
         if videoId then
             -- Use the #tag parser function instead of direct XML tags
             -- We'll store just the ID
             args["VideoEmbed"] = '{{#tag:youtube|' .. videoId .. '}}'
             args["VideoID"] = videoId
        else
            args["VideoEmbed"] = "Coming Soon"
         end
         end
    else
        args["VideoEmbed"] = "Coming Soon"
     end
     end
      
      
Line 522: Line 654:
         args["PlacedWith"] = p.iconize({args = {[1] = args["PlacedWith"]}})
         args["PlacedWith"] = p.iconize({args = {[1] = args["PlacedWith"]}})
     end
     end
   
    if args["AdditionalNotes"] and args["AdditionalNotes"] ~= "" then
        args["AdditionalNotes"] = p.iconize({args = {[1] = args["AdditionalNotes"]}})
    end
   
    -- Generate formatted template call
    local templateCall = "{{BuildingRefinerDisplay"
    for param, value in pairs(args) do
        if value and value ~= "" then
            templateCall = templateCall .. "\n|" .. param .. "=" .. value
        end
    end
    templateCall = templateCall .. "\n}}"
      
      
     -- Expand the template with our parameters
     -- Expand the template with our parameters
     return frame:expandTemplate{ title = 'BuildingRefinerDisplay', args = args }
     return frame:preprocess(templateCall)
end
end


-- Debug function to help troubleshoot
-- Debug function to show raw building data
function p.debugBuilding(frame)
function p.debugBuildingData(frame)
     local buildingName = frame.args[1] or frame.args.name
     local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
      
      
    if not buildingName then
     -- Get building data
        return "Error: No building name provided"
    end
   
     -- Get building data directly
     local buildingData, err = loadBuildingWikiTable()
     local buildingData, err = loadBuildingWikiTable()
     if not buildingData then
     if not buildingData then
         return err
         return "Error loading building data: " .. (err or "Unknown error")
     end
     end
      
      
    local colMap = buildingData.colMap
     local rows = buildingData.rows
     local rows = buildingData.rows
      
      
     -- Find the building
     -- Find the building by name
     local building = nil
     local building = nil
     for _, row in ipairs(rows) do
     for _, row in ipairs(rows) do
         if row[colMap["Name"]] == buildingName then
         if row[2] == buildingName then
             building = row
             building = row
             break
             break
Line 554: Line 694:
      
      
     if not building then
     if not building then
         return "Error: Building '" .. buildingName .. "' not found"
         return "Building '" .. buildingName .. "' not found."
     end
     end
      
      
     -- Output all data for debugging
     -- Output all column data
     local debug = "Building: " .. buildingName .. "\n\n"
     local output = "Data for building: " .. buildingName .. "\n\n"
     for k, v in pairs(colMap) do
   
         debug = debug .. k .. ": " .. tostring(building[v] or "nil") .. "\n"
     for i, value in ipairs(building) do
         output = output .. "Column " .. i .. ": " .. tostring(value or "nil") .. "\n"
     end
     end
      
      
     return debug
     return output
end
end


Line 672: Line 813:
     end
     end
      
      
     if not building or not building[19] or building[19] == "-" or building[19] == "" then
    -- Changed from column 19 to column 20 to account for the new Tier column
     if not building or not building[20] or building[20] == "-" or building[20] == "" then
         return "Coming Soon"
         return "Coming Soon"
     end
     end
      
      
     -- Get YouTube URL and extract video ID
     -- Get YouTube URL and extract video ID (now from column 20)
     local youtubeUrl = building[19]
     local youtubeUrl = building[20]
     local videoId = youtubeUrl:match("v=([%w-_]+)")
     local videoId = youtubeUrl:match("v=([%w-_]+)")
      
      
Line 691: Line 833:
end
end


-- Breadcrumb trail function
function p.breadcrumb(frame)
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return err
    end
    local rows = buildingData.rows
   
    -- Use the passed parameter 'name' or the current page title
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
   
    for _, row in ipairs(rows) do
        if row[2] == buildingName then
            -- Get categories (using hardcoded indices based on your table)
            local cat1 = row[17] or "" -- Category1 (column 17)
            local cat2 = row[18] or "" -- Category2 (column 18)
            local cat3 = row[19] or "" -- Category3 (column 19)
           
            -- Clean up categories (remove brackets and Category: prefix)
            local function cleanCategory(cat)
                if not cat or cat == "-" then return "" end
               
                if cat:find("%[%[Category:") then
                    cat = cat:match("%[%[Category:%s*(.-)%s*%]%]")
                elseif cat:find("%[%[") then
                    cat = cat:match("%[%[(.-)%]%]")
                end
                return mw.text.trim(cat)
            end
           
            cat1 = cleanCategory(cat1)
            cat2 = cleanCategory(cat2)
            cat3 = cleanCategory(cat3)
           
            -- Build breadcrumb parts
            local breadcrumbParts = {}
            if cat1 ~= "" then
                table.insert(breadcrumbParts, "[[" .. cat1 .. "]]")
            end
            if cat2 ~= "" then
                table.insert(breadcrumbParts, "[[" .. cat2 .. "]]")
            end
            if cat3 ~= "" then
                table.insert(breadcrumbParts, "[[" .. cat3 .. "]]")
            end
           
            -- Add current building name in bold
            table.insert(breadcrumbParts, "'''" .. buildingName .. "'''")
           
            return table.concat(breadcrumbParts, " > ")
        end
    end


    return "Breadcrumb not available"
end


-- Get Category 3 for "Other X" title
function p.getCategory3(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
   
    -- Get building data
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return "Buildings"
    end
   
    local rows = buildingData.rows
   
    -- Find the building
    for _, row in ipairs(rows) do
        if row[2] == buildingName then
            -- Get Category3 (column 19)
            local cat3 = row[19] or ""
           
            -- Clean up category
            if cat3 == "-" or cat3 == "" then
                return "Buildings"
            end
           
            -- Remove brackets if present
            if cat3:find("%[%[") then
                cat3 = cat3:match("%[%[(.-)%]%]")
            end
           
            return mw.text.trim(cat3)
        end
    end
   
    return "Buildings" -- Default if building not found
end
-- Find related buildings based on Category 3
function p.relatedBuildings(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
   
    -- Get building data
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return "Error loading building data"
    end
   
    local rows = buildingData.rows
   
    -- Find current building and its Category3
    local targetCategory = nil
    for _, row in ipairs(rows) do
        if row[2] == buildingName then
            -- Get Category3 (column 19)
            targetCategory = row[19] or ""
           
            -- Clean up category
            if targetCategory == "-" or targetCategory == "" then
                return "No related buildings found"
            end
           
            -- Remove brackets if present
            if targetCategory:find("%[%[") then
                targetCategory = targetCategory:match("%[%[(.-)%]%]")
            end
           
            targetCategory = mw.text.trim(targetCategory)
            break
        end
    end
   
    if not targetCategory then
        return "Building not found"
    end
   
    -- Find related buildings with the same Category3
    local relatedBuildings = {}
    for _, row in ipairs(rows) do
        if row[2] ~= buildingName then -- Skip current building
            local category = row[19] or ""
           
            -- Clean up category
            if category ~= "-" and category ~= "" then
                -- Remove brackets if present
                if category:find("%[%[") then
                    category = category:match("%[%[(.-)%]%]")
                end
               
                category = mw.text.trim(category)
               
                if category == targetCategory then
                    table.insert(relatedBuildings, row)
                end
            end
        end
    end
   
    if #relatedBuildings == 0 then
        return "No other " .. targetCategory .. " found"
    end
   
    -- Format as a table
    local output = '<table class="infobox-dune" style="width:100%">\n'
    output = output .. "<tr>\n"
    output = output .. "<th style=\"text-align:left;\">Name</th>\n"
    output = output .. "<th style=\"text-align:left;\">Tier</th>\n"
    output = output .. "<th style=\"text-align:left;\">Description</th>\n"
    output = output .. "</tr>\n"
   
    for _, building in ipairs(relatedBuildings) do
        local name = building[2] or ""
        local tier = building[3] or ""
        local description = building[4] or ""
       
        -- Clean up values
        if tier == "-" then tier = "" end
        if description == "-" then description = "" end
       
        -- Format name with icon
        local nameWithIcon = name
        local iconFile = building[16] or "" -- IconFile (column 16)
        if iconFile ~= "-" and iconFile ~= "" then
            nameWithIcon = "[[File:" .. iconFile .. "|20px]] [[" .. name .. "]]"
        else
            nameWithIcon = "[[" .. name .. "]]"
        end
       
        -- Add table row
        output = output .. "<tr>\n"
        output = output .. "<td>" .. nameWithIcon .. "</td>\n"
        output = output .. "<td>" .. tier .. "</td>\n"
        output = output .. "<td>" .. description .. "</td>\n"
        output = output .. "</tr>\n"
    end
   
    output = output .. "</table>"
    return output
end


return p
return p

Latest revision as of 00:22, 17 March 2025

Documentation for this module may be created at Module:DataTableParser/doc

-- Module:DataTableParser
-- Handles display and formatting of building data for Templates
-- Updated to parse wiki tables directly instead of using Cargo

local p = {}

-- Function to generate icon file reference for a resource
local function getResourceIcon(resourceName)
    if not resourceName or resourceName == "" then
        return ""
    end
    
    -- Convert spaces to underscores for file name
    local fileName = resourceName:gsub("%s+", "_") .. "_-_Icon.png"
    
    -- Check if the file actually exists in the wiki
    local fileTitle = mw.title.new("File:" .. fileName)
    if fileTitle and fileTitle.exists then
        -- Return file reference with appropriate size only if it exists
        return "[[File:" .. fileName .. "|20px]]"
    else
        -- If file doesn't exist, return empty string
        return ""
    end
end

-- Function to add icons to resource links in text
function p.iconize(frame)
    local text = frame.args[1] or ""
    
    -- Find all resource links [[Resource Name]] and add appropriate icons
    text = text:gsub("%[%[([^%]]+)%]%]", function(resourceName)
        local icon = getResourceIcon(resourceName)
        if icon ~= "" then
            return icon .. " [[" .. resourceName .. "]]"
        else
            -- If no icon exists, just return the original link
            return "[[" .. resourceName .. "]]"
        end
    end)
    
    return text
end

----------------------------------------------------------------------
-- Helper function to load/parse the wiki table page for buildings
----------------------------------------------------------------------
local function loadBuildingWikiTable()
    local title = "Data:Building"  -- The page containing your building table
    local content = mw.title.new(title):getContent()
    if not content then
        return nil, "Error: Building wiki table page not found!"
    end

    local lines = mw.text.split(content, "\n")
    -- Filter out magic words, noindex tags, or blank lines
    local filtered = {}
    for _, line in ipairs(lines) do
        line = mw.text.trim(line)
        if line ~= "" and not line:match("^__") and not line:match("<noindex>") and not line:match("</noindex>") then
            table.insert(filtered, line)
        end
    end
    if #filtered < 2 then
        return nil, "Error: No valid wiki table content found!"
    end

    local inTable = false
    local headers = {}  -- store the table headers
    local colMap = {}  -- map header name to column index
    local data = {}     -- array of row arrays
    local currentRow = {}

    local readingHeader = true  -- before the first row separator ("|-"), treat cells as headers

    for _, line in ipairs(filtered) do
        if line:match("^{|") then
            -- Start of table (line starts with "{|")
            inTable = true
        elseif line:match("^|%-") then
            -- Row separator ("|-")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            currentRow = {}
            readingHeader = false  -- after the first row separator, subsequent cells are row cells
        elseif line:match("^!") then
            -- Header cell (starts with "!")
            local header = line:gsub("^!+%s*", "")  -- Remove leading ! and whitespace
            header = mw.text.trim(header)
            table.insert(headers, header)
        elseif line:match("^|%+") then
            -- Table caption line ("|+"), skip it
        elseif line:match("^|}") then
            -- End of table ("|}")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            inTable = false
        elseif inTable and line:match("^|[^%-+}]") then
            -- Process table cell lines (lines that start with "|" but not "|+", "|-", or "|}")
            local cell = line:gsub("^|+%s*", "")  -- Remove leading | and whitespace
            cell = mw.text.trim(cell)
            if not readingHeader then
                table.insert(currentRow, cell)
            end
        end
    end

    -- Build colMap from headers (map header name to its column index)
    for i, h in ipairs(headers) do
        colMap[mw.text.trim(h)] = i
    end

    return { rows = data, colMap = colMap }, nil
end

----------------------------------------------------------------------
-- Helper function to load/parse the wiki table page for refining data
----------------------------------------------------------------------
local function loadRefiningWikiTable()
    local title = "Data:Refining"  -- The page containing your refining table
    local content = mw.title.new(title):getContent()
    if not content then
        return nil, "Error: Refining wiki table page not found!"
    end

    local lines = mw.text.split(content, "\n")
    -- Filter out magic words, noindex tags, or blank lines
    local filtered = {}
    for _, line in ipairs(lines) do
        line = mw.text.trim(line)
        if line ~= "" and not line:match("^__") and not line:match("<noindex>") and not line:match("</noindex>") then
            table.insert(filtered, line)
        end
    end
    if #filtered < 2 then
        return nil, "Error: No valid wiki table content found!"
    end

    local inTable = false
    local headers = {}  -- store the table headers
    local colMap = {}  -- map header name to column index
    local data = {}     -- array of row arrays
    local currentRow = {}

    local readingHeader = true  -- before the first row separator ("|-"), treat cells as headers

    for _, line in ipairs(filtered) do
        if line:match("^{|") then
            -- Start of table (line starts with "{|")
            inTable = true
        elseif line:match("^|%-") then
            -- Row separator ("|-")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            currentRow = {}
            readingHeader = false  -- after the first row separator, subsequent cells are row cells
        elseif line:match("^!") then
            -- Header cell (starts with "!")
            local header = line:gsub("^!+%s*", "")  -- Remove leading ! and whitespace
            header = mw.text.trim(header)
            table.insert(headers, header)
        elseif line:match("^|%+") then
            -- Table caption line ("|+"), skip it
        elseif line:match("^|}") then
            -- End of table ("|}")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            inTable = false
        elseif inTable and line:match("^|[^%-+}]") then
            -- Process table cell lines (lines that start with "|" but not "|+", "|-", or "|}")
            local cell = line:gsub("^|+%s*", "")  -- Remove leading | and whitespace
            cell = mw.text.trim(cell)
            if not readingHeader then
                table.insert(currentRow, cell)
            end
        end
    end

    -- Build colMap from headers (map header name to its column index)
    for i, h in ipairs(headers) do
        colMap[mw.text.trim(h)] = i
    end

    return { rows = data, colMap = colMap }, nil
end

-- Function to add icons to resource links in text
function p.iconize(frame)
    local text = frame.args[1] or ""
    
    -- Find all resource links [[Resource Name]] and add appropriate icons
    text = text:gsub("%[%[([^%]]+)%]%]", function(resourceName)
        local icon = getResourceIcon(resourceName)
        return icon .. " [[" .. resourceName .. "]]"
    end)
    
    return text
end

-- Function to format a recipe component
function p.formatComponent(text)
    if not text or text == "" then
        return ""
    end
    
    -- Split recipe into components (in case there are multiple)
    local components = mw.text.split(text, ";")
    local formatted = {}
    
    for i, component in ipairs(components) do
        component = mw.text.trim(component)
        -- Extract item name and quantity (e.g., "[[Salvaged Metal]] x 25")
        local itemName, quantity = component:match("%[%[([^%]]+)%]%] x (%d+)")
        
        if itemName and quantity then
            local icon = getResourceIcon(itemName)
            table.insert(formatted, icon .. " [[" .. itemName .. "]] x " .. quantity)
        else
            -- If pattern doesn't match, use as is
            table.insert(formatted, component)
        end
    end
    
    return table.concat(formatted, "<br>")
end

-- Function to get building data by name
function p.getBuildingData(buildingName)
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return nil, err
    end
    
    local colMap = buildingData.colMap
    local rows = buildingData.rows
    
    local nameCol = colMap["Name"]
    if not nameCol then
        return nil, "Error: 'Name' column not found in Building table"
    end
    
    for _, fields in ipairs(rows) do
        local name = fields[nameCol]
        if name == buildingName then
            return fields, colMap
        end
    end
    
    return nil, "Building '" .. buildingName .. "' not found"
end

----------------------------------------------------------------------
-- Helper function to load/parse the refining data wiki table
----------------------------------------------------------------------
local function loadRefiningWikiTable()
    local title = "Data:Refining"  -- Make sure this matches your wiki page path exactly
    local content = mw.title.new(title):getContent()
    if not content then
        return nil, "Error: Refining wiki table page not found at " .. title
    end

    local lines = mw.text.split(content, "\n")
    -- Filter out magic words, noindex tags, or blank lines
    local filtered = {}
    for _, line in ipairs(lines) do
        line = mw.text.trim(line)
        if line ~= "" and not line:match("^__") and not line:match("<noindex>") and not line:match("</noindex>") then
            table.insert(filtered, line)
        end
    end
    if #filtered < 2 then
        return nil, "Error: No valid wiki table content found in " .. title
    end

    local inTable = false
    local headers = {}  -- store the table headers
    local colMap = {}  -- map header name to column index
    local data = {}     -- array of row arrays
    local currentRow = {}

    local readingHeader = true  -- before the first row separator ("|-"), treat cells as headers

    for _, line in ipairs(filtered) do
        if line:match("^{|") then
            -- Start of table (line starts with "{|")
            inTable = true
        elseif line:match("^|%-") then
            -- Row separator ("|-")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            currentRow = {}
            readingHeader = false  -- after the first row separator, subsequent cells are row cells
        elseif line:match("^!") then
            -- Header cell (starts with "!")
            local header = line:gsub("^!+%s*", "")  -- Remove leading ! and whitespace
            header = mw.text.trim(header)
            table.insert(headers, header)
        elseif line:match("^|%+") then
            -- Table caption line ("|+"), skip it
        elseif line:match("^|}") then
            -- End of table ("|}")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            inTable = false
        elseif inTable and line:match("^|[^%-+}]") then
            -- Process table cell lines (lines that start with "|" but not "|+", "|-", or "|}")
            local cell = line:gsub("^|+%s*", "")  -- Remove leading | and whitespace
            cell = mw.text.trim(cell)
            if not readingHeader then
                table.insert(currentRow, cell)
            end
        end
    end

    -- Build colMap from headers (map header name to its column index)
    for i, h in ipairs(headers) do
        colMap[mw.text.trim(h)] = i
    end

    -- Add debug logging
    if #data == 0 then
        return nil, "Error: No data rows found in " .. title
    end

    return { rows = data, colMap = colMap }, nil
end

----------------------------------------------------------------------
-- Function to display refining recipes for a building
----------------------------------------------------------------------
-- Function to display refining recipes for a building
function p.getRefiningRecipes(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    
    -- Get refining data
    local refiningData, err = loadRefiningWikiTable()
    if not refiningData then
        return "Error loading refining data: " .. (err or "Unknown error")
    end
    
    local rows = refiningData.rows
    
    -- Fixed indices for the required columns
    local resourceClassCol = 1  -- Resource Class
    local nameCol = 2          -- Name
    local timeCol = 3          -- Time to Smelt
    local ingredientsCol = 4   -- Ingredients to Smelt
    local recipeCol = 5        -- Recipe to Smelt
    
    -- Helper function to find refiner name in a row
    local function getRefinerFromRow(row)
        -- Check potential positions for Refiner Needed column
        -- Try column 8 first (matches original table structure)
        if row[8] and row[8] ~= "" then
            return row[8]
        -- Try column 6 next (as seen in debug output) 
        elseif row[6] and row[6] ~= "" then
            return row[6]
        -- As a fallback, try each column from the end
        else
            for i = #row, 1, -1 do
                if row[i] and row[i] ~= "" then
                    return row[i]
                end
            end
        end
        return ""
    end
    
    -- Helper function to normalize text for comparison
    local function normalizeText(text)
        if not text then return "" end
        
        -- Remove quotes
        text = text:gsub('["\']', '')
        
        -- Remove wiki link brackets and extract the name
        text = text:gsub("%[%[([^%]]+)%]%]", "%1")
        
        -- Trim whitespace
        text = mw.text.trim(text)
        
        return text
    end
    
    -- Find recipes matching the building name
    local matchingRecipes = {}
    for _, row in ipairs(rows) do
        -- Get the refiner name from the row
        local refinerNeeded = getRefinerFromRow(row)
        
        -- Normalize both texts for comparison
        local normalizedRefiner = normalizeText(refinerNeeded)
        local normalizedBuilding = normalizeText(buildingName)
        
        if normalizedRefiner == normalizedBuilding then
            table.insert(matchingRecipes, row)
        end
    end
    
    -- If no recipes found, return a message
    if #matchingRecipes == 0 then
        return '<tr><td colspan="3" style="text-align:center;">No refining recipes found for this building.</td></tr>'
    end
    
    -- Start with the updated table headers
    local output = "<tr>\n" ..
              "<th style=\"text-align:left;\">Output</th>\n" ..
              "<th style=\"text-align:left;\">Ingredients</th>\n" ..
              "<th style=\"text-align:left;\">Craft Time</th>\n" ..
                  "</tr>\n"
    
    -- Generate table rows for each recipe
    for _, recipe in ipairs(matchingRecipes) do
        local outputItem = recipe[nameCol] or ""
        local ingredients = recipe[ingredientsCol] or ""
        local time = recipe[timeCol] or ""
        local recipeQty = recipe[recipeCol] or ""
        
        -- Extract quantity from recipe text (e.g., "[[Copper Ore]] x 4" → "4")
        local qty = "1"
        if recipeQty ~= "" then
            local extractQty = recipeQty:match("x%s*(%d+)")
            if extractQty then
                qty = extractQty
            end
        end
        
        -- Format the output item with icon, including quantity
        local formattedOutput = p.iconize({args = {[1] = outputItem}})
        if qty ~= "1" then
            formattedOutput = formattedOutput .. " × " .. qty
        end
        
        -- Split ingredients by semicolon and format each with icon
        local formattedIngredients = ""
        if ingredients ~= "" then
            local ingredientsList = mw.text.split(ingredients, ";")
            for i, ingredient in ipairs(ingredientsList) do
                local trimmedIngredient = mw.text.trim(ingredient)
                if trimmedIngredient ~= "" then
                    local formattedIngredient = p.iconize({args = {[1] = trimmedIngredient}})
                    if i > 1 then
                        formattedIngredients = formattedIngredients .. "<br>"
                    end
                    formattedIngredients = formattedIngredients .. formattedIngredient
                end
            end
        end
        
        -- Add table row
        output = output .. "<tr>\n" ..
                "<td style=\"text-align:left;\">" .. formattedOutput .. "</td>\n" ..
    			"<td style=\"text-align:left;\">" .. formattedIngredients .. "</td>\n" ..
        		"<td style=\"text-align:left;\">" .. time .. "</td>\n" ..
                "</tr>\n"
    end
    
    return output
end

----------------------------------------------------------------------
-- Debug function to show raw refining data
----------------------------------------------------------------------
function p.debugRefiningData(frame)
    local refiningData, err = loadRefiningWikiTable()
    if not refiningData then
        return "Error loading refining data: " .. (err or "Unknown error")
    end
    
    local output = "Refining data loaded successfully!\n\n"
    output = output .. "Number of rows: " .. #refiningData.rows .. "\n\n"
    
    output = output .. "Column mappings:\n"
    for name, index in pairs(refiningData.colMap) do
        output = output .. name .. " -> " .. index .. "\n"
    end
    
    output = output .. "\nFirst 3 rows:\n"
    for i = 1, math.min(3, #refiningData.rows) do
        output = output .. "Row " .. i .. ":\n"
        for j, cell in ipairs(refiningData.rows[i]) do
            output = output .. "  Column " .. j .. ": " .. cell .. "\n"
        end
    end
    
    return output
end

-- Main function to format building data for the template
function p.formatBuilding(frame)
    local buildingName = frame.args[1] or frame.args.name
    
    if not buildingName then
        return "Error: No building name provided"
    end
    
    -- Get building data
    local building, colMap = p.getBuildingData(buildingName)
    
    if not building then
        return "Error: Building '" .. buildingName .. "' not found"
    end
    
    -- Map building data to template parameters
    local params = {
        Name = building[colMap["Name"]] or "",
        Description = p.iconize({args = {[1] = building[colMap["Description"]] or ""}}),
        JourneyRequirement = p.iconize({args = {[1] = building[colMap["Journey Requirement"]] or ""}}),
        ImageFile = building[colMap["Image File"]] or "",
        Health = building[colMap["Health"]] or "",
        EnergyConsumption = building[colMap["Power Cost"]] or "",
        GeneratesPower = building[colMap["Generates Power"]] or "",
        StorageSlots = building[colMap["Storage Slots"]] or "",
        StorageVolume = building[colMap["Storage Capacity"]] or "",
        Components = p.formatComponent(building[colMap["Recipe to Build"]] or ""),
        PlacedWith = p.iconize({args = {[1] = building[colMap["Placed With"]] or ""}}),
        AdditionalNotes = p.iconize({args = {[1] = building[colMap["Additional Notes"]] or ""}}),
        PrimarySource = "Crafting" -- Default value, adjust as needed
    }
    
    -- Generate formatted output for template
    local output = ""
    for param, value in pairs(params) do
        if value and value ~= "" then
            output = output .. "|" .. param .. "=" .. value .. "\n"
        end
    end
    
    return output
end

-- Function to format all refining recipes for a building
function p.formatRefiningRecipes(frame)
    local buildingName = frame.args[1] or frame.args.name
    
    if not buildingName then
        return "Error: No building name provided"
    end
    
    -- Get refining recipes
    local recipes, colMap = p.getRefiningRecipes(buildingName)
    
    if not recipes or #recipes == 0 then
        return "<tr><td colspan=\"4\" style=\"text-align:center;\">No recipes found.</td></tr>"
    end
    
    local output = ""
    for _, recipe in ipairs(recipes) do
        output = output .. p.formatRecipeRow(recipe, colMap)
    end
    
    return output
end

-- Helper function to display building with the template
function p.displayBuilding(frame)
    local buildingName = frame.args[1] or frame.args.name
    local customNotes = frame.args[2] or ""
    
    if not buildingName then
        return "Error: No building name provided"
    end
    
    -- Get building data directly
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return err
    end
    
    local rows = buildingData.rows
    
    -- Find the building by name (column 2)
    local building = nil
    for _, row in ipairs(rows) do
        if row[2] == buildingName then
            building = row
            break
        end
    end
    
    if not building then
        return "Error: Building '" .. buildingName .. "' not found"
    end
    
    -- Map building data to template parameters
    local args = {}
    
    -- Direct mapping with explicit indices based on the wiki table
    args["Name"] = building[2] or ""                -- Column 2: Name
    args["Tier"] = building[3] or ""                -- Column 3: Tier 
    args["Description"] = building[4] or ""         -- Column 4: Description
    args["JourneyRequirement"] = building[5] or ""  -- Column 5: JourneyRequirement
    args["Health"] = building[6] or ""              -- Column 6: Health
    args["EnergyConsumption"] = building[7] or ""   -- Column 7: PowerCost
    args["GeneratesPower"] = building[8] or ""      -- Column 8: GeneratesPower
    args["StorageSlots"] = building[9] or ""        -- Column 9: StorageSlots
    args["StorageVolume"] = building[10] or ""      -- Column 10: StorageCapacity
    args["Components"] = building[11] or ""         -- Column 11: RecipeToBuild
    args["PlacedWith"] = building[12] or ""         -- Column 12: PlacedWith
    args["ImageFile"] = building[15] or ""          -- Column 15: ImageFile
    args["PrimarySource"] = "Crafting"              -- Default value
    
    -- Use custom notes if provided, otherwise use notes from data
    if customNotes and customNotes ~= "" then
        args["AdditionalNotes"] = customNotes
    else
        args["AdditionalNotes"] = building[13] or "" -- Column 13: AdditionalNotes
    end
    
    -- Handle YouTube video (Column 20)
    if building[20] and building[20] ~= "-" and building[20] ~= "" then
        -- Set video title
        args["VideoTitle"] = "Building Guide"
        
        local youtubeUrl = building[20]
        
        -- Extract video ID from the URL
        local videoId = youtubeUrl:match("v=([%w-_]+)")
        if videoId then
            -- We'll store just the ID
            args["VideoID"] = videoId
        end
    end
    
    -- Replace "-" placeholders with empty strings
    for key, value in pairs(args) do
        if value == "-" then
            args[key] = ""
        end
    end
    
    -- Process recipe components
    if args["Components"] and args["Components"] ~= "" then
        args["Components"] = p.formatComponent(args["Components"])
    end
    
    -- Process descriptions and journey requirements with iconize
    if args["Description"] and args["Description"] ~= "" then
        args["Description"] = p.iconize({args = {[1] = args["Description"]}})
    end
    
    if args["JourneyRequirement"] and args["JourneyRequirement"] ~= "" then
        args["JourneyRequirement"] = p.iconize({args = {[1] = args["JourneyRequirement"]}})
    end
    
    if args["PlacedWith"] and args["PlacedWith"] ~= "" then
        args["PlacedWith"] = p.iconize({args = {[1] = args["PlacedWith"]}})
    end
    
    if args["AdditionalNotes"] and args["AdditionalNotes"] ~= "" then
        args["AdditionalNotes"] = p.iconize({args = {[1] = args["AdditionalNotes"]}})
    end
    
    -- Generate formatted template call
    local templateCall = "{{BuildingRefinerDisplay"
    for param, value in pairs(args) do
        if value and value ~= "" then
            templateCall = templateCall .. "\n|" .. param .. "=" .. value
        end
    end
    templateCall = templateCall .. "\n}}"
    
    -- Expand the template with our parameters
    return frame:preprocess(templateCall)
end

-- Debug function to show raw building data
function p.debugBuildingData(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    
    -- Get building data
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return "Error loading building data: " .. (err or "Unknown error")
    end
    
    local rows = buildingData.rows
    
    -- Find the building by name
    local building = nil
    for _, row in ipairs(rows) do
        if row[2] == buildingName then
            building = row
            break
        end
    end
    
    if not building then
        return "Building '" .. buildingName .. "' not found."
    end
    
    -- Output all column data
    local output = "Data for building: " .. buildingName .. "\n\n"
    
    for i, value in ipairs(building) do
        output = output .. "Column " .. i .. ": " .. tostring(value or "nil") .. "\n"
    end
    
    return output
end

-- Debug function to show raw table data
function p.debugRawData(frame)
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return err
    end
    
    local colMap = buildingData.colMap
    
    -- Show all column mappings
    local output = "Column mappings:\n"
    for name, index in pairs(colMap) do
        output = output .. name .. " -> " .. index .. "\n"
    end
    
    output = output .. "\n\nFirst row raw data:\n"
    local row = buildingData.rows[1]
    for i = 1, #row do
        output = output .. "Column " .. i .. ": " .. tostring(row[i]) .. "\n"
    end
    
    return output
end

-- Debug function to show template parameters
function p.debugTemplateParams(frame)
    local buildingName = frame.args[1] or frame.args.name
    
    if not buildingName then
        return "Error: No building name provided"
    end
    
    -- Get building data directly
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return err
    end
    
    local colMap = buildingData.colMap
    local rows = buildingData.rows
    
    -- Find the building
    local building = nil
    for _, row in ipairs(rows) do
        if row[colMap["Name"]] == buildingName then
            building = row
            break
        end
    end
    
    if not building then
        return "Error: Building '" .. buildingName .. "' not found"
    end
    
    -- Map building data to template parameters
    local args = {
        ["Name"] = building[colMap["Name"]] or "",
        ["Description"] = building[colMap["Description"]] or "",
        ["JourneyRequirement"] = building[colMap["Journey Requirement"]] or "",
        ["ImageFile"] = building[colMap["Image File"]] or "",
        ["Health"] = building[colMap["Health"]] or "",
        ["EnergyConsumption"] = building[colMap["Power Cost"]] or "",
        ["GeneratesPower"] = building[colMap["Generates Power"]] or "",
        ["StorageSlots"] = building[colMap["Storage Slots"]] or "",
        ["StorageVolume"] = building[colMap["Storage Capacity"]] or "",
        ["Components"] = building[colMap["Recipe to Build"]] or "",
        ["PlacedWith"] = building[colMap["Placed With"]] or "",
        ["AdditionalNotes"] = building[colMap["Additional Notes"]] or "",
        ["PrimarySource"] = "Crafting" -- Default value, adjust as needed
    }
    
    -- Output the template parameters
    local result = "Template Parameters:\n"
    for param, value in pairs(args) do
        result = result .. param .. " = " .. tostring(value) .. "\n"
    end
    
    return result
end

-- Function to generate a YouTube embed with customizable dimensions
function p.getYoutubeEmbed(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    
    -- Get optional dimensions parameters
    local width = frame.args.width or 400
    local height = frame.args.height or 300
    local dimensions = width .. "x" .. height
    
    -- Get building data
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return "Error loading data"
    end
    
    local rows = buildingData.rows
    
    -- Find the building
    local building = nil
    for _, row in ipairs(rows) do
        if row[2] == buildingName then
            building = row
            break
        end
    end
    
    -- Changed from column 19 to column 20 to account for the new Tier column
    if not building or not building[20] or building[20] == "-" or building[20] == "" then
        return "Coming Soon"
    end
    
    -- Get YouTube URL and extract video ID (now from column 20)
    local youtubeUrl = building[20]
    local videoId = youtubeUrl:match("v=([%w-_]+)")
    
    if not videoId then
        return "Coming Soon"
    end
    
    -- Create YouTube tag with specified dimensions and centered alignment
    local ytMarkup = string.format('<youtube dimensions="%s" alignment="center">%s</youtube>', dimensions, videoId)
    
    -- Process the markup so MediaWiki parses it as wikitext
    return frame:preprocess(ytMarkup)
end

-- Breadcrumb trail function
function p.breadcrumb(frame)
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return err
    end

    local rows = buildingData.rows
    
    -- Use the passed parameter 'name' or the current page title
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    
    for _, row in ipairs(rows) do
        if row[2] == buildingName then
            -- Get categories (using hardcoded indices based on your table)
            local cat1 = row[17] or "" -- Category1 (column 17)
            local cat2 = row[18] or "" -- Category2 (column 18)
            local cat3 = row[19] or "" -- Category3 (column 19)
            
            -- Clean up categories (remove brackets and Category: prefix)
            local function cleanCategory(cat)
                if not cat or cat == "-" then return "" end
                
                if cat:find("%[%[Category:") then
                    cat = cat:match("%[%[Category:%s*(.-)%s*%]%]")
                elseif cat:find("%[%[") then
                    cat = cat:match("%[%[(.-)%]%]")
                end
                return mw.text.trim(cat)
            end
            
            cat1 = cleanCategory(cat1)
            cat2 = cleanCategory(cat2)
            cat3 = cleanCategory(cat3)
            
            -- Build breadcrumb parts
            local breadcrumbParts = {}
            if cat1 ~= "" then
                table.insert(breadcrumbParts, "[[" .. cat1 .. "]]")
            end
            if cat2 ~= "" then
                table.insert(breadcrumbParts, "[[" .. cat2 .. "]]")
            end
            if cat3 ~= "" then
                table.insert(breadcrumbParts, "[[" .. cat3 .. "]]")
            end
            
            -- Add current building name in bold
            table.insert(breadcrumbParts, "'''" .. buildingName .. "'''")
            
            return table.concat(breadcrumbParts, " > ")
        end
    end

    return "Breadcrumb not available"
end

-- Get Category 3 for "Other X" title
function p.getCategory3(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    
    -- Get building data
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return "Buildings"
    end
    
    local rows = buildingData.rows
    
    -- Find the building
    for _, row in ipairs(rows) do
        if row[2] == buildingName then
            -- Get Category3 (column 19)
            local cat3 = row[19] or ""
            
            -- Clean up category
            if cat3 == "-" or cat3 == "" then
                return "Buildings"
            end
            
            -- Remove brackets if present
            if cat3:find("%[%[") then
                cat3 = cat3:match("%[%[(.-)%]%]")
            end
            
            return mw.text.trim(cat3)
        end
    end
    
    return "Buildings" -- Default if building not found
end

-- Find related buildings based on Category 3
function p.relatedBuildings(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    
    -- Get building data
    local buildingData, err = loadBuildingWikiTable()
    if not buildingData then
        return "Error loading building data"
    end
    
    local rows = buildingData.rows
    
    -- Find current building and its Category3
    local targetCategory = nil
    for _, row in ipairs(rows) do
        if row[2] == buildingName then
            -- Get Category3 (column 19)
            targetCategory = row[19] or ""
            
            -- Clean up category
            if targetCategory == "-" or targetCategory == "" then
                return "No related buildings found"
            end
            
            -- Remove brackets if present
            if targetCategory:find("%[%[") then
                targetCategory = targetCategory:match("%[%[(.-)%]%]")
            end
            
            targetCategory = mw.text.trim(targetCategory)
            break
        end
    end
    
    if not targetCategory then
        return "Building not found"
    end
    
    -- Find related buildings with the same Category3
    local relatedBuildings = {}
    for _, row in ipairs(rows) do
        if row[2] ~= buildingName then -- Skip current building
            local category = row[19] or ""
            
            -- Clean up category
            if category ~= "-" and category ~= "" then
                -- Remove brackets if present
                if category:find("%[%[") then
                    category = category:match("%[%[(.-)%]%]")
                end
                
                category = mw.text.trim(category)
                
                if category == targetCategory then
                    table.insert(relatedBuildings, row)
                end
            end
        end
    end
    
    if #relatedBuildings == 0 then
        return "No other " .. targetCategory .. " found"
    end
    
    -- Format as a table
    local output = '<table class="infobox-dune" style="width:100%">\n'
    output = output .. "<tr>\n"
    output = output .. "<th style=\"text-align:left;\">Name</th>\n"
    output = output .. "<th style=\"text-align:left;\">Tier</th>\n"
    output = output .. "<th style=\"text-align:left;\">Description</th>\n"
    output = output .. "</tr>\n"
    
    for _, building in ipairs(relatedBuildings) do
        local name = building[2] or ""
        local tier = building[3] or ""
        local description = building[4] or ""
        
        -- Clean up values
        if tier == "-" then tier = "" end
        if description == "-" then description = "" end
        
        -- Format name with icon
        local nameWithIcon = name
        local iconFile = building[16] or "" -- IconFile (column 16)
        if iconFile ~= "-" and iconFile ~= "" then
            nameWithIcon = "[[File:" .. iconFile .. "|20px]] [[" .. name .. "]]"
        else
            nameWithIcon = "[[" .. name .. "]]"
        end
        
        -- Add table row
        output = output .. "<tr>\n"
        output = output .. "<td>" .. nameWithIcon .. "</td>\n"
        output = output .. "<td>" .. tier .. "</td>\n"
        output = output .. "<td>" .. description .. "</td>\n"
        output = output .. "</tr>\n"
    end
    
    output = output .. "</table>"
    return output
end

return p