Actions

Module

DataTableParserV2: Difference between revisions

From Dune Awakening DB

mNo edit summary
mNo edit summary
Line 64: Line 64:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Function: loadBuildingData
-- Function: loadBuildingData
-- Uses externaldata to fetch building data from data_buildings as a wikitable.
-- Uses externaldata to fetch building data from data_buildings as a structured table.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function loadBuildingData(frame)
local function loadBuildingData(frame)
     local edQuery = [[
    local buildingName = frame.args[1] or frame.args.name or mw.title.getCurrentTitle().text
{{#get_external_data: source=externaldb
   
|from=data_buildings
    -- Create a direct query that returns field-value pairs instead of trying to parse a wikitable
|data=ID=id,
     local edQuery = string.format([[
      BuildingType=building_type,
        {{#get_external_data: source=externaldb
      Name=name,
        |from=data_buildings
      Description=description,
        |data=ID=id,
      PowerCost=power_cost,
              BuildingType=building_type,
      GeneratesPower=generates_power,
              Name=name,
      StorageSlots=storage_slots,
              Description=description,
      StorageCapacity=storage_capacity,
              PowerCost=power_cost,
      SchematicRequirement=schematic_requirement,
              GeneratesPower=generates_power,
      JourneyRequirement=journey_requirement,
              StorageSlots=storage_slots,
      Health=health,
              StorageCapacity=storage_capacity,
      PlacedWith=placed_with,
              SchematicRequirement=schematic_requirement,
      AdditionalNotes=additional_notes,
              JourneyRequirement=journey_requirement,
      RecipeToBuild=recipe_to_build,
              Health=health,
      ImageFile=image_file,
              PlacedWith=placed_with,
      IconFile=icon_file,
              AdditionalNotes=additional_notes,
      Category1=category_1,
              RecipeToBuild=recipe_to_build,
      Category2=category_2,
              ImageFile=image_file,
      Category3=category_3,
              IconFile=icon_file,
      Gallery1=gallery_1,
              Category1=category_1,
      Gallery2=gallery_2,
              Category2=category_2,
      Gallery3=gallery_3,
              Category3=category_3,
      Gallery4=gallery_4,
              Gallery1=gallery_1,
      YoutubeVideoLink=youtube_video_link
              Gallery2=gallery_2,
|cache=yes
              Gallery3=gallery_3,
|where=name='{{PAGENAME}}'
              Gallery4=gallery_4,
|limit=1
              YoutubeVideoLink=youtube_video_link
}}
        |cache=yes
{| class="wikitable"
        |where=name='%s'
! ID
        |limit=1
! Building Type
        }}
! Name
    ]], buildingName)
! Description
   
! Power Cost
    local result = frame:preprocess(edQuery)
! Generates Power
   
! Storage Slots
    -- Create a structure to hold our building data
! Storage Capacity
    local buildingData = {}
! Schematic Requirement
    local colMap = {}
! Journey Requirement
    local fieldValues = {}
! Health
   
! Placed With
    -- Parse field values directly from ExternalData variables
! Additional Notes
    local fields = {
! Recipe To Build
        "ID", "BuildingType", "Name", "Description", "PowerCost", "GeneratesPower",
! Image File
        "StorageSlots", "StorageCapacity", "SchematicRequirement", "JourneyRequirement",
! Icon File
        "Health", "PlacedWith", "AdditionalNotes", "RecipeToBuild", "ImageFile",
! Category 1
        "IconFile", "Category1", "Category2", "Category3", "Gallery1", "Gallery2",
! Category 2
        "Gallery3", "Gallery4", "YoutubeVideoLink"
! Category 3
     }
! Gallery 1
   
! Gallery 2
     for i, field in ipairs(fields) do
! Gallery 3
         colMap[field] = i
! Gallery 4
        local value = frame:preprocess("{{{" .. field .. "}}}")
! YouTube Video Link
         if value and value ~= "" and value ~= "{{{" .. field .. "}}}" then
{{#for_external_table:|
             fieldValues[i] = value
{{!}}-
        else
{{!}} {{{ID}}}
            fieldValues[i] = ""
{{!}} {{{BuildingType}}}
{{!}} {{{Name}}}
{{!}} {{{Description}}}
{{!}} {{{PowerCost}}}
{{!}} {{{GeneratesPower}}}
{{!}} {{{StorageSlots}}}
{{!}} {{{StorageCapacity}}}
{{!}} {{{SchematicRequirement}}}
{{!}} {{{JourneyRequirement}}}
{{!}} {{{Health}}}
{{!}} {{{PlacedWith}}}
{{!}} {{{AdditionalNotes}}}
{{!}} {{{RecipeToBuild}}}
{{!}} {{{ImageFile}}}
{{!}} {{{IconFile}}}
{{!}} {{{Category1}}}
{{!}} {{{Category2}}}
{{!}} {{{Category3}}}
{{!}} {{{Gallery1}}}
{{!}} {{{Gallery2}}}
{{!}} {{{Gallery3}}}
{{!}} {{{Gallery4}}}
{{!}} {{{YoutubeVideoLink}}}
}}
|}
 
]]
    local content = frame:preprocess(edQuery)
    if not content or content == "" then
        return nil, "Error: No building data returned from external source!"
    end
 
    local lines = mw.text.split(content, "\n")
     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
     end
     end
     if #filtered < 2 then
      
        return nil, "Error: No valid building data found!"
     -- Check if we got any data
     end
     local hasData = false
 
     for _, v in ipairs(fieldValues) do
    local inTable = false
         if v ~= "" then
    local headers = {}  -- store table headers
             hasData = true
    local colMap = {}  -- map header name to column index
             break
    local data = {}    -- array of row arrays
     local currentRow = {}
    local readingHeader = true
 
     for _, line in ipairs(filtered) do
         if line:match("^{|") then
             inTable = true
        elseif line:match("^|%-") then
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            currentRow = {}
            readingHeader = false
        elseif line:match("^!") then
            local header = line:gsub("^!+%s*", "")
            header = mw.text.trim(header)
            table.insert(headers, header)
        elseif line:match("^|%+") then
            -- skip table caption
        elseif line:match("^|}") then
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            inTable = false
        elseif inTable and line:match("^|[^%-+}]") then
            local cell = line:gsub("^|+%s*", "")
            cell = mw.text.trim(cell)
            if not readingHeader then
                table.insert(currentRow, cell)
             end
         end
         end
     end
     end
 
   
     for i, h in ipairs(headers) do
     if not hasData then
         colMap[mw.text.trim(h)] = i
         return nil, "No data found for building: " .. buildingName
     end
     end
 
   
     return { rows = data, colMap = colMap }, nil
     -- Create the structure expected by the rest of the code
    buildingData.colMap = colMap
    buildingData.rows = {fieldValues}
   
    return buildingData, nil
end
end


Line 423: Line 359:
     end
     end


     local building, colMap = p.getBuildingData(buildingName, frame)
     local buildingData, err = loadBuildingData(frame)
     if not building then
     if not buildingData then
         return "Error: Building '" .. buildingName .. "' not found. Try adding '|debug=1' to see more information."
         return "Error: " .. (err or "Building '" .. buildingName .. "' not found")
     end
     end
      
      
     -- Check for required fields
     local colMap = buildingData.colMap
     local requiredFields = {"Name", "Description"}
     local building = buildingData.rows[1]
    for _, field in ipairs(requiredFields) do
   
        if not colMap[field] then
    if not building then
            return "Error: Required field '" .. field .. "' missing in database schema"
        return "Error: No data row found for building '" .. buildingName .. "'"
        end
        if not building[colMap[field]] or building[colMap[field]] == "" or building[colMap[field]] == "-" then
            return "Error: Required field '" .. field .. "' is empty for building '" .. buildingName .. "'"
        end
     end
     end


Line 443: Line 375:
         Name = building[colMap["Name"]] or buildingName,
         Name = building[colMap["Name"]] or buildingName,
         Tier = building[colMap["BuildingType"]] or "Unknown",
         Tier = building[colMap["BuildingType"]] or "Unknown",
         Description = "",
         Description = building[colMap["Description"]] or "",
         JourneyRequirement = "",
         JourneyRequirement = building[colMap["JourneyRequirement"]] or "",
         Health = building[colMap["Health"]] or "0",
         Health = building[colMap["Health"]] or "0",
         EnergyConsumption = building[colMap["PowerCost"]] or "0",
         EnergyConsumption = building[colMap["PowerCost"]] or "0",
Line 450: Line 382:
         StorageSlots = building[colMap["StorageSlots"]] or "0",
         StorageSlots = building[colMap["StorageSlots"]] or "0",
         StorageVolume = building[colMap["StorageCapacity"]] or "0",
         StorageVolume = building[colMap["StorageCapacity"]] or "0",
         Components = "",
         Components = building[colMap["RecipeToBuild"]] or "",
         PlacedWith = "",
         PlacedWith = building[colMap["PlacedWith"]] or "",
         AdditionalNotes = "",
         AdditionalNotes = building[colMap["AdditionalNotes"]] or "",
         ImageFile = building[colMap["ImageFile"]] or "",
         ImageFile = building[colMap["ImageFile"]] or "",
         PrimarySource = "Crafting"
         PrimarySource = "Crafting"
Line 458: Line 390:
      
      
     -- Process fields that need special formatting
     -- Process fields that need special formatting
     if colMap["Description"] and building[colMap["Description"]] then
     if params.Description ~= "" then
         params.Description = p.iconize({args = {[1] = building[colMap["Description"]]}})
         params.Description = p.iconize({args = {[1] = params.Description}})
     end
     end
      
      
     if colMap["JourneyRequirement"] and building[colMap["JourneyRequirement"]] then
     if params.JourneyRequirement ~= "" then
         params.JourneyRequirement = p.iconize({args = {[1] = building[colMap["JourneyRequirement"]]}})
         params.JourneyRequirement = p.iconize({args = {[1] = params.JourneyRequirement}})
     end
     end
      
      
     if colMap["RecipeToBuild"] and building[colMap["RecipeToBuild"]] then
     if params.Components ~= "" then
         params.Components = p.formatComponent(building[colMap["RecipeToBuild"]])
         params.Components = p.formatComponent(params.Components)
     end
     end
      
      
     if colMap["PlacedWith"] and building[colMap["PlacedWith"]] then
     if params.PlacedWith ~= "" then
         params.PlacedWith = p.iconize({args = {[1] = building[colMap["PlacedWith"]]}})
         params.PlacedWith = p.iconize({args = {[1] = params.PlacedWith}})
     end
     end
      
      
     if colMap["AdditionalNotes"] and building[colMap["AdditionalNotes"]] then
     if params.AdditionalNotes ~= "" then
         params.AdditionalNotes = p.iconize({args = {[1] = building[colMap["AdditionalNotes"]]}})
         params.AdditionalNotes = p.iconize({args = {[1] = params.AdditionalNotes}})
     end
     end


     -- Safety check for functions that might fail
     -- Add YouTube video if available
     local success, result = pcall(function()
     if colMap["YoutubeVideoLink"] and building[colMap["YoutubeVideoLink"]] and building[colMap["YoutubeVideoLink"]] ~= "" then
        -- Append additional columns by calling other functions:
         params.YoutubeEmbed = p.getYoutubeEmbed(frame)
        local refiningRecipes = p.getRefiningRecipes(frame) or "No refining recipes found"
    else
        local youtubeEmbed = p.getYoutubeEmbed(frame) or "No video available"
         params.YoutubeEmbed = "Coming Soon"
        local category3 = p.getCategory3(frame) or "Buildings"
     end
         local relatedBuildings = p.relatedBuildings(frame) or "No related buildings found"
       
         return {
            RefiningRecipes = refiningRecipes,
            YoutubeEmbed = youtubeEmbed,
            Category3 = category3,
            RelatedBuildings = relatedBuildings
        }
     end)
      
      
     if success then
     -- Add refining recipes
        -- Add the results to params
    params.RefiningRecipes = p.getRefiningRecipes(frame)
        for key, value in pairs(result) do
   
            params[key] = value
    -- Add category and related buildings
         end
    if colMap["Category3"] and building[colMap["Category3"]] and building[colMap["Category3"]] ~= "" then
         params.Category3 = building[colMap["Category3"]]
     else
     else
        -- Log the error but continue with empty values
        mw.log("Error getting additional data: " .. (result or "unknown error"))
        params.RefiningRecipes = "Error loading recipes"
        params.YoutubeEmbed = "Error loading video"
         params.Category3 = "Buildings"
         params.Category3 = "Buildings"
        params.RelatedBuildings = "Error loading related buildings"
     end
     end
   
    params.RelatedBuildings = p.relatedBuildings(frame)
      
      
     -- Construct the template call
     -- Construct the template call
Line 516: Line 437:
     end
     end
     templateCall = templateCall .. "\n}}"
     templateCall = templateCall .. "\n}}"
   
    -- Add debug info at the bottom if requested
    if frame.args.verbose then
        templateCall = templateCall .. "\n\n<div style='display:none'>Debug info: " ..
                      "Building name: " .. buildingName ..
                      "</div>"
    end
      
      
     -- Process the template
     -- Process the template
     local result = frame:preprocess(templateCall)
     return frame:preprocess(templateCall)
    if result == templateCall then
        -- If preprocessing didn't change anything, the template might not exist
        return "Error: Template 'BuildingRefinerDisplayV2' might not exist."
    end
   
    return result
end
end



Revision as of 01:03, 27 March 2025

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

-- Module:DataTableParserV2
-- Handles display and formatting of building data for Templates using External Data.
-- Data is fetched from your SQL tables (data_buildings and data_refining_recipes)
-- via the ExternalData extension rather than from a wiki table page.

local p = {}

--------------------------------------------------
-- Helper: Get icon file reference for a resource
--------------------------------------------------
local function getResourceIcon(resourceName)
    if not resourceName or resourceName == "" then
        return ""
    end
    local fileName = resourceName:gsub("%s+", "_") .. "_-_Icon.png"
    local fileTitle = mw.title.new("File:" .. fileName)
    if fileTitle and fileTitle.exists then
        return "[[File:" .. fileName .. "|20px]]"
    else
        return ""
    end
end

---------------------------------------------
-- Function: iconize
-- Adds icons to resource links in text
---------------------------------------------
function p.iconize(frame)
    local text = frame.args[1] or ""
    text = text:gsub("%[%[([^%]]+)%]%]", function(resourceName)
        local icon = getResourceIcon(resourceName)
        if icon ~= "" then
            return icon .. " [[" .. resourceName .. "]]"
        else
            return "[[" .. resourceName .. "]]"
        end
    end)
    return text
end

------------------------------------------------------
-- Function: formatComponent
-- Formats a recipe component by adding icons to resource links.
------------------------------------------------------
function p.formatComponent(text)
    if not text or text == "" then
        return ""
    end
    local components = mw.text.split(text, ";")
    local formatted = {}
    for i, component in ipairs(components) do
        component = mw.text.trim(component)
        local itemName, quantity = component:match("%[%[([^%]]+)%]%] x (%d+)")
        if itemName and quantity then
            local icon = getResourceIcon(itemName)
            table.insert(formatted, icon .. " [[" .. itemName .. "]] x " .. quantity)
        else
            table.insert(formatted, component)
        end
    end
    return table.concat(formatted, "<br>")
end

--------------------------------------------------------------------------------
-- Function: loadBuildingData
-- Uses externaldata to fetch building data from data_buildings as a structured table.
--------------------------------------------------------------------------------
local function loadBuildingData(frame)
    local buildingName = frame.args[1] or frame.args.name or mw.title.getCurrentTitle().text
    
    -- Create a direct query that returns field-value pairs instead of trying to parse a wikitable
    local edQuery = string.format([[
        {{#get_external_data: source=externaldb
         |from=data_buildings
         |data=ID=id,
               BuildingType=building_type,
               Name=name,
               Description=description,
               PowerCost=power_cost,
               GeneratesPower=generates_power,
               StorageSlots=storage_slots,
               StorageCapacity=storage_capacity,
               SchematicRequirement=schematic_requirement,
               JourneyRequirement=journey_requirement,
               Health=health,
               PlacedWith=placed_with,
               AdditionalNotes=additional_notes,
               RecipeToBuild=recipe_to_build,
               ImageFile=image_file,
               IconFile=icon_file,
               Category1=category_1,
               Category2=category_2,
               Category3=category_3,
               Gallery1=gallery_1,
               Gallery2=gallery_2,
               Gallery3=gallery_3,
               Gallery4=gallery_4,
               YoutubeVideoLink=youtube_video_link
         |cache=yes
         |where=name='%s'
         |limit=1
        }}
    ]], buildingName)
    
    local result = frame:preprocess(edQuery)
    
    -- Create a structure to hold our building data
    local buildingData = {}
    local colMap = {}
    local fieldValues = {}
    
    -- Parse field values directly from ExternalData variables
    local fields = {
        "ID", "BuildingType", "Name", "Description", "PowerCost", "GeneratesPower",
        "StorageSlots", "StorageCapacity", "SchematicRequirement", "JourneyRequirement",
        "Health", "PlacedWith", "AdditionalNotes", "RecipeToBuild", "ImageFile",
        "IconFile", "Category1", "Category2", "Category3", "Gallery1", "Gallery2",
        "Gallery3", "Gallery4", "YoutubeVideoLink"
    }
    
    for i, field in ipairs(fields) do
        colMap[field] = i
        local value = frame:preprocess("{{{" .. field .. "}}}")
        if value and value ~= "" and value ~= "{{{" .. field .. "}}}" then
            fieldValues[i] = value
        else
            fieldValues[i] = ""
        end
    end
    
    -- Check if we got any data
    local hasData = false
    for _, v in ipairs(fieldValues) do
        if v ~= "" then
            hasData = true
            break
        end
    end
    
    if not hasData then
        return nil, "No data found for building: " .. buildingName
    end
    
    -- Create the structure expected by the rest of the code
    buildingData.colMap = colMap
    buildingData.rows = {fieldValues}
    
    return buildingData, nil
end

--------------------------------------------------------------------------------
-- Function: loadRefiningData
-- Uses externaldata to fetch refining recipes from data_refining_recipes.
--------------------------------------------------------------------------------
local function loadRefiningData(frame)
    local edQuery = [[
{{#get_external_data: source=externaldb
 |from=data_refining_recipes
 |data=Refiner=Refiner,Output=Output,Ingredients=Ingredients,Time=Time,Recipe=Recipe
 |cache=yes
 |where=Refiner='{{PAGENAME}}'
}}
{| class="wikitable"
! Refiner
! Output
! Ingredients
! Time
! Recipe
{{#for_external_table:|
{{!}}-
{{!}} {{{Refiner}}}
{{!}} {{{Output}}}
{{!}} {{{Ingredients}}}
{{!}} {{{Time}}}
{{!}} {{{Recipe}}}
}}
|}
]]
    local content = frame:preprocess(edQuery)
    if not content or content == "" then
        return nil, "Error: No refining data returned from external source!"
    end

    local lines = mw.text.split(content, "\n")
    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 refining data found!"
    end

    local inTable = false
    local headers = {}
    local colMap = {}
    local data = {}
    local currentRow = {}
    local readingHeader = true

    for _, line in ipairs(filtered) do
        if line:match("^{|") then
            inTable = true
        elseif line:match("^|%-") then
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            currentRow = {}
            readingHeader = false
        elseif line:match("^!") then
            local header = line:gsub("^!+%s*", "")
            header = mw.text.trim(header)
            table.insert(headers, header)
        elseif line:match("^|%+") then
            -- skip table caption
        elseif line:match("^|}") then
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            inTable = false
        elseif inTable and line:match("^|[^%-+}]") then
            local cell = line:gsub("^|+%s*", "")
            cell = mw.text.trim(cell)
            if not readingHeader then
                table.insert(currentRow, cell)
            end
        end
    end

    for i, h in ipairs(headers) do
        colMap[mw.text.trim(h)] = i
    end

    if #data == 0 then
        return nil, "Error: No data rows found in refining data!"
    end

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

--------------------------------------------------------------------------------
-- Function: getBuildingData
-- Returns the row (and colMap) for a given building name from external data.
--------------------------------------------------------------------------------
function p.getBuildingData(buildingName, frame)
    local buildingData, err = loadBuildingData(frame)
    if not buildingData then
        return nil, err
    end

    local colMap = buildingData.colMap
    local rows = buildingData.rows

    if not colMap["Name"] then
        return nil, "Error: 'Name' column not found in building data"
    end

    for _, fields in ipairs(rows) do
        if fields[colMap["Name"]] == buildingName then
            return fields, colMap
        end
    end
    return nil, "Building '" .. buildingName .. "' not found"
end

--------------------------------------------------------------------------------
-- Function: getRefiningRecipes
-- Returns a table of refining recipes matching the building name.
--------------------------------------------------------------------------------
function p.getRefiningRecipes(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    local refiningData, err = loadRefiningData(frame)
    if not refiningData then
        return "Error loading refining data: " .. (err or "Unknown error")
    end

    local rows = refiningData.rows
    local colMap = refiningData.colMap

    local function normalizeText(text)
        if not text then return "" end
        text = text:gsub('["\']', '')
        text = text:gsub("%[%[([^%]]+)%]%]", "%1")
        return mw.text.trim(text)
    end

    local matchingRecipes = {}
    for _, row in ipairs(rows) do
        local refinerNeeded = row[colMap["Refiner"]] or ""
        if normalizeText(refinerNeeded) == normalizeText(buildingName) then
            table.insert(matchingRecipes, row)
        end
    end

    if #matchingRecipes == 0 then
        return '<tr><td colspan="3" style="text-align:center;">No refining recipes found for this building.</td></tr>'
    end

    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"
    for _, recipe in ipairs(matchingRecipes) do
        local outputItem = recipe[colMap["Output"]] or ""
        local ingredients = recipe[colMap["Ingredients"]] or ""
        local time = recipe[colMap["Time"]] or ""
        local recipeQty = recipe[colMap["Recipe"]] or ""
        local qty = "1"
        if recipeQty ~= "" then
            local extractQty = recipeQty:match("x%s*(%d+)")
            if extractQty then
                qty = extractQty
            end
        end
        local formattedOutput = p.iconize({args = {[1] = outputItem}})
        if qty ~= "1" then
            formattedOutput = formattedOutput .. " × " .. qty
        end
        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
        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

--------------------------------------------------------------------------------
-- Function: formatBuilding
-- Formats building data as a template call to BuildingRefinerDisplayV2.
--------------------------------------------------------------------------------
function p.formatBuilding(frame)
    local buildingName = frame.args[1] or frame.args.name or mw.title.getCurrentTitle().text
    if not buildingName then
        return "Error: No building name provided"
    end

    -- Add debug information if debug flag is set
    if frame.args.debug then
        return p.debugBuildingData(frame)
    end

    local buildingData, err = loadBuildingData(frame)
    if not buildingData then
        return "Error: " .. (err or "Building '" .. buildingName .. "' not found")
    end
    
    local colMap = buildingData.colMap
    local building = buildingData.rows[1]
    
    if not building then
        return "Error: No data row found for building '" .. buildingName .. "'"
    end

    -- Prepare params with default values and better error handling
    local params = {
        Name = building[colMap["Name"]] or buildingName,
        Tier = building[colMap["BuildingType"]] or "Unknown",
        Description = building[colMap["Description"]] or "",
        JourneyRequirement = building[colMap["JourneyRequirement"]] or "",
        Health = building[colMap["Health"]] or "0",
        EnergyConsumption = building[colMap["PowerCost"]] or "0",
        GeneratesPower = building[colMap["GeneratesPower"]] or "0",
        StorageSlots = building[colMap["StorageSlots"]] or "0",
        StorageVolume = building[colMap["StorageCapacity"]] or "0",
        Components = building[colMap["RecipeToBuild"]] or "",
        PlacedWith = building[colMap["PlacedWith"]] or "",
        AdditionalNotes = building[colMap["AdditionalNotes"]] or "",
        ImageFile = building[colMap["ImageFile"]] or "",
        PrimarySource = "Crafting"
    }
    
    -- Process fields that need special formatting
    if params.Description ~= "" then
        params.Description = p.iconize({args = {[1] = params.Description}})
    end
    
    if params.JourneyRequirement ~= "" then
        params.JourneyRequirement = p.iconize({args = {[1] = params.JourneyRequirement}})
    end
    
    if params.Components ~= "" then
        params.Components = p.formatComponent(params.Components)
    end
    
    if params.PlacedWith ~= "" then
        params.PlacedWith = p.iconize({args = {[1] = params.PlacedWith}})
    end
    
    if params.AdditionalNotes ~= "" then
        params.AdditionalNotes = p.iconize({args = {[1] = params.AdditionalNotes}})
    end

    -- Add YouTube video if available
    if colMap["YoutubeVideoLink"] and building[colMap["YoutubeVideoLink"]] and building[colMap["YoutubeVideoLink"]] ~= "" then
        params.YoutubeEmbed = p.getYoutubeEmbed(frame)
    else
        params.YoutubeEmbed = "Coming Soon"
    end
    
    -- Add refining recipes
    params.RefiningRecipes = p.getRefiningRecipes(frame)
    
    -- Add category and related buildings
    if colMap["Category3"] and building[colMap["Category3"]] and building[colMap["Category3"]] ~= "" then
        params.Category3 = building[colMap["Category3"]]
    else
        params.Category3 = "Buildings"
    end
    
    params.RelatedBuildings = p.relatedBuildings(frame)
    
    -- Construct the template call
    local templateCall = "{{BuildingRefinerDisplayV2"
    for key, value in pairs(params) do
        if value and value ~= "" then
            templateCall = templateCall .. "\n|" .. key .. "=" .. value
        end
    end
    templateCall = templateCall .. "\n}}"
    
    -- Process the template
    return frame:preprocess(templateCall)
end

--------------------------------------------------------------------------------
-- Function: getYoutubeEmbed
-- Returns a processed YouTube embed tag.
--------------------------------------------------------------------------------
function p.getYoutubeEmbed(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    local width = frame.args.width or 400
    local height = frame.args.height or 300
    local dimensions = width .. "x" .. height

    local building, colMap = p.getBuildingData(buildingName, frame)
    if not building then
        return "Error loading data"
    end

    local youtubeUrl = building[colMap["YoutubeVideoLink"]] or ""
    if youtubeUrl == "" or youtubeUrl == "-" then
        return "Coming Soon"
    end

    local videoId = youtubeUrl:match("v=([%w-_]+)")
    if not videoId then
        return "Coming Soon"
    end

    local ytMarkup = string.format('<youtube dimensions="%s" alignment="center">%s</youtube>', dimensions, videoId)
    return frame:preprocess(ytMarkup)
end


--------------------------------------------------------------------------------
-- Function: getCategory3
-- Returns the Category3 field for the current building.
--------------------------------------------------------------------------------
function p.getCategory3(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    local building, colMap = p.getBuildingData(buildingName, frame)
    if not building then
        return "Buildings"
    end
    local cat3 = building[colMap["Category3"]] or ""
    if cat3 == "-" or cat3 == "" then
        return "Buildings"
    end
    if cat3:find("%[%[") then
        cat3 = cat3:match("%[%[(.-)%]%]")
    end
    return mw.text.trim(cat3)
end

--------------------------------------------------------------------------------
-- Function: relatedBuildings
-- Finds and formats related buildings based on Category3.
--------------------------------------------------------------------------------
function p.relatedBuildings(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    local building, colMap = p.getBuildingData(buildingName, frame)
    if not building then
        return "Error loading building data"
    end
    local targetCategory = building[colMap["Category3"]] or ""
    if targetCategory == "-" or targetCategory == "" then
        return "No related buildings found"
    end
    if targetCategory:find("%[%[") then
        targetCategory = targetCategory:match("%[%[(.-)%]%]")
    end
    targetCategory = mw.text.trim(targetCategory)

    -- Load all building rows (assuming the externaldata query returns all rows)
    local allData, err = loadBuildingData(frame)
    if not allData then
        return "Error loading building data"
    end
    local rows = allData.rows

    local relatedBuildings = {}
    for _, row in ipairs(rows) do
        if row[colMap["Name"]] ~= buildingName then
            local category = row[colMap["Category3"]] or ""
            if category ~= "-" and category ~= "" then
                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

    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 _, b in ipairs(relatedBuildings) do
        local name = b[colMap["Name"]] or ""
        local tier = b[colMap["Tier"]] or ""
        local description = b[colMap["Description"]] or ""
        if tier == "-" then tier = "" end
        if description == "-" then description = "" end
        local nameWithIcon = "[[" .. name .. "]]"
        local iconFile = b[colMap["ImageFile"]] or ""
        if iconFile ~= "-" and iconFile ~= "" then
            nameWithIcon = "[[File:" .. iconFile .. "|20px]] [[" .. name .. "]]"
        end
        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

--------------------------------------------------------------------------------
-- Function: debugBuildingData
-- Better error logging
--------------------------------------------------------------------------------

function p.debugBuildingData(frame)
    local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
    local buildingData, err = loadBuildingData(frame)
    
    if not buildingData then
        return "Error: " .. (err or "Unknown error")
    end
    
    local output = "Debug information for '" .. buildingName .. "':\n\n"
    output = output .. "Raw data from database:\n"
    output = output .. "<pre>"
    
    -- Output column map
    output = output .. "Column mapping:\n"
    for name, idx in pairs(buildingData.colMap) do
        output = output .. name .. " => " .. idx .. "\n"
    end
    
    -- Output row data
    output = output .. "\nRows (" .. #buildingData.rows .. " found):\n"
    for i, row in ipairs(buildingData.rows) do
        output = output .. "Row " .. i .. ":\n"
        for j, cell in ipairs(row) do
            local colName = "Unknown"
            for name, idx in pairs(buildingData.colMap) do
                if idx == j then
                    colName = name
                    break
                end
            end
            output = output .. "  " .. colName .. " (" .. j .. "): '" .. cell .. "'\n"
        end
    end
    
    output = output .. "</pre>"
    return output
end


return p