Module:MathModDBHelperMethods

From MaRDI portal

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

-- Module for executing SPARQL queries
local sparql = require('SPARQL')
-- Required module containing helper methods
local helper = require('Module:HelperMethods')
-- MediaWiki library for logging and utilities
local mw = require('mw')
local json = require("mw.text") 

-- Main table to hold all functions
local p = {}

-------------------------------- Local Functions -------------------------------

-- Utility: get all P31 values of an entity
local function getInstanceOf(qid)
    local e = mw.wikibase.getEntityObject(qid)
    local result = {}
    if not e or not e.claims or not e.claims["P31"] then
        return result
    end
    for _, claim in pairs(e.claims["P31"]) do
        local id = claim.mainsnak and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value.id
        if id then
            table.insert(result, id)
        end
    end
    return result
end
-------------------------------- Local Functions -------------------------------

-- Function to return a canonical URL as clickable wikitext
-- Accepts either frame (from #invoke) or raw QID
-- Optional: pass label="YourText" as a named argument in frame
function p.canonicalLink(frameOrQID)
    local qid, customLabel

    -- Determine how function was called
    if frameOrQID then
        if frameOrQID.args then
            -- Called via #invoke
            qid = frameOrQID.args[1]
            customLabel = frameOrQID.args.label
        else
            -- Called directly from Lua
            qid = frameOrQID
        end
    end

    if not qid then return "No QID provided" end

	local url
    -- Get the entity
    local e = mw.wikibase.getEntity(qid)
    if not e or not e.sitelinks or not e.sitelinks.mardi then
		return "https://portal.mardi4nfdi.de/wiki/Item:" .. qid
    end

    -- Build URL using the sitelink title
    local title = e.sitelinks.mardi.title
    local url = "https://portal.mardi4nfdi.de/wiki/" .. mw.uri.encode(title, "PATH")

    -- Determine label to show
    local label = customLabel or mw.wikibase.label(qid) or title

    -- Return clickable wikitext link
    if frameOrQID.args then
        -- called via #invoke, preprocess needed for wikitext
        return frameOrQID:preprocess(string.format("[%s %s]", url, label))
    else
        -- called from Lua, return plain wikitext string
        return string.format("%s", url)
    end
end

-- Function to debug sitelinks
-- It prints the entire entity object, including sitelinks, claims, etc.
function p.debugSitelink(frame)
    local qid = frame.args[1]
    local e = mw.wikibase.getEntity(qid)
    return mw.text.nowiki(mw.dumpObject(e))
end



-- Function to get instance of types as links
function p.getInstanceOfTypes(frame)
    local entityId = frame.args[1]

    -- Validate input parameter
    if not entityId or entityId == '' then
        return ""
    end

    -- SPARQL query: get instance of items and their labels
    local sparqlQuery = [[
PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
SELECT ?InstanceOfType ?InstanceOfTypeLabel WHERE {
  entityId: p:P31 ?statement .
  ?statement ps:P31 ?InstanceOfType .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
ORDER BY ?InstanceOfTypeLabel
    ]]

    -- Execute SPARQL
    local jsonResults = sparql.runQuery(sparqlQuery)
    if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
    end

    local InstanceOfTypes = {}
    local total = #jsonResults.results.bindings

    -- Loop through results
    for i = 0, total do
        local item = jsonResults.results.bindings[i]
        if item and item.InstanceOfType and item.InstanceOfType.value then
            local qid = item.InstanceOfType.value:match(".*/(Q%d+)$") or item.InstanceOfType.value
            local label = (item.InstanceOfTypeLabel and item.InstanceOfTypeLabel.value) or qid
            local url = "https://portal.mardi4nfdi.de/wiki/Item:" .. qid
            local link = string.format("[%s %s]", url, label)
            table.insert(InstanceOfTypes, link)
        end
    end

    -- Build wikitext table
    local wikitextTable = "{| class='wikitable'\n|-\n| " .. table.concat(InstanceOfTypes, " || ") .. "\n|}"
    return wikitextTable
end

-- Function to get instance of types

-- Function to replace pattern in the comma pattern separated list 
function p.replace_pattern(frame)
    local input = frame.args[1] or ""
    local sep = frame.args[2] or " "  -- default to space if no separator given
    -- Replace comma+pattern (,xxxx) with sep
    local result = string.gsub(input, ",xxxx%s*", sep)
    return result
end

-- Function to generate a table listing a type of individuals
-- test with =p.getList{args={'Model'}}
function p.getList(frame)
	
	local entityType = frame.args[1] or 'Model'
	local height = frame.args[2] or '400px' -- Default height if not specified
    local width = frame.args[3] or '800px' -- Default width if not specified
    local baseUrl = mw.site.server -- Get the current URL
    
    -- Define mapping of entity types to their corresponding Q IDs and URL prefixes
    local entityConfig = {
        ['Model'] = {qid = 'Q68663', urlPrefix = 'Model:', title = 'mathematical models'},
        ['Academic discipline'] = {qid = 'Q60231', urlPrefix = 'Academic_discipline:', title = 'academic disciplines'},
        ['Research problem'] = {qid = 'Q6534292', urlPrefix = 'Research_problem:', title = 'research problems'},
        ['Task'] = {qid = 'Q6534247', urlPrefix = 'Task:', title = 'computational tasks'},
        ['Quantity'] = {qid = 'Q6534237', urlPrefix = 'Quantity:', title = 'quantities'},
        ['Quantity kind'] = {qid = 'Q6534245', urlPrefix = 'Quantity:', title = 'quantity kind items'},
        ['Formula'] = {qid = 'Q6481152', urlPrefix = 'Formula:', title = 'mathematical expressions'}
    }
    
    -- Get configuration for the specified entity type
    local config = entityConfig[entityType]
    if not config then
        return "Invalid entity type. Valid options are: Model, Academic discipline, Research problem, Task, Quantity, Quantity kind, Formula"
    end

    -- Q6534265 refers to MathModDB community
    local sparqlQuery = [[
	SELECT ?itemLabel ?modelURL ?item
    WHERE {
      ?item wdt:P31 wd:]] .. config.qid .. [[;
            wdt:P1495 wd:Q6534265 .
      ?item rdfs:label ?itemLabel .
      FILTER(LANG(?itemLabel) = "en")
      BIND(REPLACE(STR(?item), "^.*/Q", "]] .. baseUrl .. [[/wiki/]] .. config.urlPrefix .. [[") AS ?modelURL)
    }
    ORDER BY LCASE(?itemLabel)
    ]]

    local jsonResults = sparql.runQuery(sparqlQuery)

	-- Handle error in SPARQL query execution
	if jsonResults and jsonResults.error then
    	mw.log("Error in SPARQL query: " .. tostring(jsonResults.error))
    	return nil
	end

	if not jsonResults then
        return "Could not fetch data."
	end

	if helper.countElementsInBindings(jsonResults.results.bindings) == 0 then
        return "No records found."
	end
	
	-- Convert the JSON results into a Lua table
    local fieldOrder = {"modelURL", "itemLabel", "item"}
	local dataTable = helper.convertJsonToTableOrdered(jsonResults, fieldOrder)
	
	-- Create and return HTML table from the data
    local headers = {entityType}
    local htmlTable =  helper.createHtmlTableWithMergedCols(dataTable, headers, {{3}})
	
	-- Create a parent container for the table
	local parentContainer = mw.html.create('div')
    	:addClass('parent-container')
    	:css('width', width)

	local heading = mw.html.create('h2')
    	:wikitext('List of ' .. config.title)

    -- Add the table and chart to the parent container
	parentContainer
	    :node(heading)
    	:node(htmlTable)

    return tostring(parentContainer)
end

-- Function to get linked items
function p.getLinkedItems(frame)
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    local sparqlQuery = [[
PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>

SELECT DISTINCT ?itemLabel ?item ?propertyLabel ?property 
WHERE {
  # Replace with your specific entity
  ?itemInter ?ps entityId: .

  # Convert property URL to entity-style URL
 BIND(
    IRI(REPLACE(STR(?ps),
                 "^https://portal.mardi4nfdi.de/prop/(direct|qualifier|statement)/",
                 "https://portal.mardi4nfdi.de/entity/"))
    AS ?property
 )
 # Skip unwanted property
 FILTER(?property != <http://schema.org/about>)

  # Clean URL: keep only QID, remove /statement/ and trailing UUID
  BIND(
    IRI(
      REPLACE(
        STR(?itemInter),
        ".*/(Q[0-9]+).*",
        "https://portal.mardi4nfdi.de/entity/$1"
      )
    ) AS ?item
  )

  # Get labels
  SERVICE wikibase:label { 
    bd:serviceParam wikibase:language "en". 
    ?item rdfs:label ?itemLabel .
    ?property rdfs:label ?propertyLabel .
  }
}
ORDER BY LCASE(?itemLabel)

]]


	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
       	return ""
    end
	
	local linkedItems = {}
	local totalLinkedItems = #jsonResults.results.bindings

	if totalLinkedItems >= 0 then
    	-- Loop through the bindings 
    	for index = 0, totalLinkedItems do
        	local item = jsonResults.results.bindings[index]
        	
        	if not item then
        		return ""
    		end

        	if not item.itemLabel or not item.itemLabel.value then
        		return ""
        	elseif not item.item or not item.item.value then
            	return ""
        	else
            	local itemLabel = item.itemLabel.value
            	local itemURL = item.item.value
            	local propertyLabel = item.propertyLabel and item.propertyLabel.value or ""
            	local propertyURL = item.property and item.property.value or ""

            	local itemWithURL = string.format('[%s %s]', tostring(itemURL), tostring(itemLabel))
            	local propertyWithURL = string.format('[%s %s]', tostring(propertyURL), tostring(propertyLabel))

            	table.insert(linkedItems, '| ' .. itemWithURL .. ' || ' .. propertyWithURL)
        	end
    	end
    	-- if there are linked items 
    	-- Construct the Wikitext table
		local wikitextTable = "{| class='wikitable'\n" ..
    	"! Item\n! Property\n" ..          -- header row
    	"|-\n" ..                          -- separator before first data row
    	table.concat(linkedItems, "\n|-\n") ..
    	"\n|}"
		return wikitextTable
	else
		return ""
	end

end

-- Function to get subclass of items
function p.getSubclassOf(frame)
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Constructing the SPARQL query with dynamic entity entityId
    -- P36: subclass of property id
    local sparqlQuery = [[
PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
	SELECT ?URL ?Label  
	WHERE {
		entityId: wdt:P36 ?URL.
		?URL rdfs:label ?Label
		FILTER(LANG(?Label) = "en")
	}
	ORDER BY ?Label
	]]

	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
       	return "No specialized academic discipline found"
    end
	
	local subClasses = {}
    -- Get the number of specialized academic disciplines
	local totalSubClasses = #jsonResults.results.bindings
	-- Loop through the bindings
	for index = 0, totalSubClasses  do
    	local item = jsonResults.results.bindings[index]
    	if not item then
        		return ""
    	end
    	if not item.Label.value then
        	return ""
    	elseif not item.URL.value then
        	return ""
    	else
    		local label = item.Label.value
        	local url = item.URL.value
        	local numericId = url:match("Q(%d+)")
        	local urlRendered = "https://portal.mardi4nfdi.de/wiki/Academic_discipline:" .. numericId
        	local labelWithUrl = string.format('[%s %s]', tostring(url), tostring(label))
        	table.insert(subClasses, "| " .. labelWithUrl)
    	end
	end

	-- Construct the Wikitext table
	local wikitextTable = "{| class='wikitable'\n" .. table.concat(subClasses, "\n|-\n") .. "\n|}"
	return wikitextTable
end	


-- Function to get described by source
function p.getDescribedBySource(frame)
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Constructing the SPARQL query with dynamic entity entityId
    -- P286: described by source property id
    local sparqlQuery = [[
PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
	SELECT ?URL ?Label  
	WHERE {
		entityId: wdt:P286 ?URL.
		?URL rdfs:label ?Label
		FILTER(LANG(?Label) = "en")
	}
	ORDER BY ?Label
	]]

	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
       	return "No specialized academic discipline found"
    end
	
	local describedBySources = {}
    -- Get the number of specialized academic disciplines
	local totalSources = #jsonResults.results.bindings
	-- Loop through the bindings
	for index = 0, totalSources  do
    	local item = jsonResults.results.bindings[index]
    	if not item then
        		return ""
    	end
    	if not item.Label.value then
        	return ""
    	elseif not item.URL.value then
        	return ""
    	else
    		local label = item.Label.value
        	local url = item.URL.value
        	local numericId = url:match("Q(%d+)")
        	local urlRendered = "https://portal.mardi4nfdi.de/wiki/Academic_discipline:" .. numericId
        	local labelWithUrl = string.format('[%s %s]', tostring(url), tostring(label))
        	table.insert(describedBySources, "| " .. labelWithUrl)
    	end
	end

	-- Construct the Wikitext table
	local wikitextTable = "{| class='wikitable'\n" .. table.concat(describedBySources, "\n|-\n") .. "\n|}"
	return wikitextTable
end	

function p.getImageWithLegend(frame)
	-- Property ID for the image
    local pidImage  = "P356" 
    -- Property ID for the (qualifier) media legend
    local pidMediaLegend = "P401"   
    local defaultLegend = "No legend available."

   	
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Attempt to retrieve entity data
    local entity = mw.wikibase.getEntity(entityId)
    local imageClaims = entity.claims[pidImage]
    local imageStrings = {}
	if imageClaims and type(imageClaims) == "table" then
    	local numClaims = #imageClaims
    	-- Loop through the image claims
		-- for index = 1, numClaims  do
		local index = 1
		for _, claim in ipairs(imageClaims) do
			-- Extract the image filename
    		-- local imageFilename = imageClaims[index].mainsnak.datavalue.value
    		local imageFilename = claim.mainsnak.datavalue.value
    		-- Default legend (empty string)
        	local imageLegend = ""
    		-- Check if the media legend qualifier exists
        	if claim.qualifiers and claim.qualifiers[pidMediaLegend] 
        		and #claim.qualifiers[pidMediaLegend] > 0 
        		and claim.qualifiers[pidMediaLegend][1].datavalue
        		and claim.qualifiers[pidMediaLegend][1].datavalue.value
        		and claim.qualifiers[pidMediaLegend][1].datavalue.value.text then

            	imageLegend = claim.qualifiers[pidMediaLegend][1].datavalue.value.text
        	end
			-- local imageLegend = imageClaims[index].qualifiers[pidMediaLegend][1].datavalue.value.text
			-- Add this image line to the list
			-- table.insert(imageStrings, {filename = imageFilename, legend = imageLegend})
        	table.insert(imageStrings, string.format("[[File:%s|thumb|380px|%s]]", imageFilename, imageLegend))
        
        	if index < numClaims then
        		-- spacer column
        		table.insert(imageStrings, '&nbsp;')  
        	end
			index = index + 1
		end    
		-- Combine all image cells into one row of a wikitable
		local imageTable = '{| class="wikitable"\n| ' .. table.concat(imageStrings, ' || ') .. '\n|}'
		return imageTable .. "\n\n"
	else
    	return "" 
	end

end


function p.getVideoWithLegend(frame)
	-- Property ID for the video
    local pidVideo  = "P797" 
    -- Property ID for the (qualifier) media legend
    local pidMediaLegend = "P401"   
    local defaultLegend = "No legend available."

   	
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Attempt to retrieve entity data
    local entity = mw.wikibase.getEntity(entityId)
    local videoClaims = entity.claims[pidVideo]
    local videoStrings = {}
	if videoClaims and type(videoClaims) == "table" then
    	local numClaims = #videoClaims
    	-- Loop through the image claims
		for index = 1, numClaims  do
			-- Extract the image filename
    		local videoFilename = videoClaims[index].mainsnak.datavalue.value
			local videoLegend = videoClaims[index].qualifiers[pidMediaLegend][1].datavalue.value.text
			-- Add this image line to the list
        	 table.insert(videoStrings, string.format("[[File:%s|thumb|340px|%s]]", videoFilename, videoLegend))
        
        	if index < numClaims then
        		-- spacer column
        		table.insert(videoStrings, '&nbsp;')  
    end
		end    
		-- Combine all image cells into one row of a wikitable
		local videoTable = '{| class="wikitable"\n| ' .. table.concat(videoStrings, ' || ') .. '\n|}'
		return videoTable .. "\n\n"
	else
    	return " " 
	end

end

function p.getLocalImageWithLegend(frame)
	-- Property ID for the image
    local pidImage  = "P1640" 
    -- Property ID for the (qualifier) media legend
    local pidMediaLegend = "P401"   
    local defaultLegend = "No legend available."

   	
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Attempt to retrieve entity data
    local entity = mw.wikibase.getEntity(entityId)
    local imageClaims = entity.claims[pidImage]
    local imageStrings = {}
	if imageClaims and type(imageClaims) == "table" then
    	local numClaims = #imageClaims
    	-- Loop through the image claims
		for index = 1, numClaims  do
			-- Extract the image filename
    		local imageFilename = imageClaims[index].mainsnak.datavalue.value
    		-- Default legend (empty string)
        	local imageLegend = ""
    		-- Check if the media legend qualifier exists
        	if imageClaims[index].qualifiers and imageClaims[index].qualifiers[pidMediaLegend] 
        		and #imageClaims[index].qualifiers[pidMediaLegend] > 0 
        		and imageClaims[index].qualifiers[pidMediaLegend][1].datavalue
        		and imageClaims[index].qualifiers[pidMediaLegend][1].datavalue.value
        		and imageClaims[index].qualifiers[pidMediaLegend][1].datavalue.value.text then

            	imageLegend = imageClaims[index].qualifiers[pidMediaLegend][1].datavalue.value.text
        	end
			-- Add this image line to the list
        	 table.insert(imageStrings, string.format("[[File:%s|thumb|380px|%s]]", imageFilename, imageLegend))
        
        	if index < numClaims then
        		-- spacer column
        		table.insert(imageStrings, '&nbsp;')  
    end
		end    
		-- Combine all image cells into one row of a wikitable
		local imageTable = '{| class="wikitable"\n| ' .. table.concat(imageStrings, ' || ') .. '\n|}'
		return imageTable .. "\n\n"
	else
    	return " " 
	end

end


function p.getLocalVideoWithLegend(frame)
	-- Property ID for the video
    local pidVideo  = "P1675" 
    -- Property ID for the (qualifier) media legend
    local pidMediaLegend = "P401"   
    local defaultLegend = "No legend available."

   	
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Attempt to retrieve entity data
    local entity = mw.wikibase.getEntity(entityId)
    local videoClaims = entity.claims[pidVideo]
    local videoStrings = {}
	if videoClaims and type(videoClaims) == "table" then
    	local numClaims = #videoClaims
    	-- Loop through the image claims
		for index = 1, numClaims  do
			-- Extract the image filename
    		local videoFilename = videoClaims[index].mainsnak.datavalue.value
			local videoLegend = videoClaims[index].qualifiers[pidMediaLegend][1].datavalue.value.text
			-- Add this image line to the list
        	 table.insert(videoStrings, string.format("[[File:%s|thumb|340px|%s]]", videoFilename, videoLegend))
        
        	if index < numClaims then
        		-- spacer column
        		table.insert(videoStrings, '&nbsp;')  
    end
		end    
		-- Combine all image cells into one row of a wikitable
		local videoTable = '{| class="wikitable"\n| ' .. table.concat(videoStrings, ' || ') .. '\n|}'
		return videoTable .. "\n\n"
	else
    	return " " 
	end

end

-- Function to get list with qualifier values only
function p.getListWithQualifierValues(frame)
	local entityId = frame.args[1]
    local propertyId = frame.args[2]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return " "
    end
   
    -- Constructing the SPARQL query with dynamic entity entityId
    local sparqlQuery = [[
	PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
	SELECT ?URL ?Label ?qualPropEntity ?qualPropLabel ?qualValue ?qualValueLabel
	WHERE {
		entityId: p:]] .. propertyId .. [[  ?statement .
		?statement ps:]] .. propertyId .. [[ ?URL .
		?URL rdfs:label ?Label
		FILTER(LANG(?Label) = "en")

		OPTIONAL {
    		?statement ?qualProp ?qualValue .
    		FILTER(STRSTARTS(STR(?qualProp), STR(pq:)))   # only qualifiers

	    	# Map qualifier property to its property entity
    		BIND(IRI(REPLACE(STR(?qualProp), "prop/qualifier", "entity")) AS ?qualPropEntity)

	    	OPTIONAL {
    			?qualPropEntity rdfs:label ?qualPropLabel .
    			FILTER(LANG(?qualPropLabel) = "en")
    		}

    		OPTIONAL {
    			?qualValue rdfs:label ?qualValueLabel .
    			FILTER(LANG(?qualValueLabel) = "en")
    		}
		}
	}
	ORDER BY ?Label
	]]

	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
       	return "No specialized research fields found"
    end
	
	local listItems = {}
    -- Get the number of specialized research fields
	local totallistItems = #jsonResults.results.bindings
	-- Loop through the bindings
	for index = 0, totallistItems do
    	local item = jsonResults.results.bindings[index]
    	if not item.Label.value then
        	return "Error: Missing item.Label.value"
    	elseif not item.URL.value then
        	return "Error: Missing item.URL.value"
    	else
    		local label = item.Label.value
        	local url = item.URL.value
        	local numericId = url:match("Q(%d+)")
        	local urlRendered = "https://portal.mardi4nfdi.de/wiki/Model:" .. numericId
        	local qualValue = item.qualValueLabel or item.qualValue or ""
	        local labelWithUrl = string.format('[%s %s]', tostring(url), tostring(label))
	        if qualValue ~= "" then
	       		local qualValueWithUrl = string.format('[%s %s]', tostring(item.qualValue.value), tostring(item.qualValueLabel.value))
	        	local row = "| " .. labelWithUrl ..  " || (" ..  qualValueWithUrl .. ")"
    			table.insert(listItems, row)
	        else
        		table.insert(listItems, "| " .. labelWithUrl)
        	end
    	end
	end

	-- Construct the Wikitext table
	local wikitextTable = "{| class='wikitable'\n" .. table.concat(listItems, "\n|-\n") .. "\n|}"
	return wikitextTable
end


-- Function to get list with qualifier values and qualifier labels
function p.getListWithQualifierValuesLabels(frame)
	local entityId = frame.args[1]
    local propertyId = frame.args[2]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return " "
    end
   
    -- Constructing the SPARQL query with dynamic entity entityId
    local sparqlQuery = [[
	PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
	SELECT ?URL ?Label ?qualPropEntity ?qualPropLabel ?qualValue ?qualValueLabel
	WHERE {
		entityId: p:]] .. propertyId .. [[  ?statement .
		?statement ps:]] .. propertyId .. [[ ?URL .
		?URL rdfs:label ?Label
		FILTER(LANG(?Label) = "en")

		OPTIONAL {
    		?statement ?qualProp ?qualValue .
    		FILTER(STRSTARTS(STR(?qualProp), STR(pq:)))   # only qualifiers

	    	# Map qualifier property to its property entity
    		BIND(IRI(REPLACE(STR(?qualProp), "prop/qualifier", "entity")) AS ?qualPropEntity)

	    	OPTIONAL {
    			?qualPropEntity rdfs:label ?qualPropLabel .
    			FILTER(LANG(?qualPropLabel) = "en")
    		}

    		OPTIONAL {
    			?qualValue rdfs:label ?qualValueLabel .
    			FILTER(LANG(?qualValueLabel) = "en")
    		}
		}
	}
	ORDER BY ?Label
	]]

	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
       	return "No specialized research fields found"
    end
	
	local listItems = {}
    -- Get the number of specialized research fields
	local totallistItems = #jsonResults.results.bindings
	-- Loop through the bindings
	for index = 0, totallistItems do
    	local item = jsonResults.results.bindings[index]
    	if not item.Label.value then
        	return "Error: Missing item.Label.value"
    	elseif not item.URL.value then
        	return "Error: Missing item.URL.value"
    	else
    		local label = item.Label.value
        	local url = item.URL.value
        	local numericId = url:match("Q(%d+)")
        	local urlRendered = "https://portal.mardi4nfdi.de/wiki/Model:" .. numericId
        	local qualValue = item.qualValueLabel or item.qualValue or ""
        	local qualPropLabel = item.qualPropLabel or "" 
	        local labelWithUrl = string.format('[%s %s]', tostring(url), tostring(label))
	        if qualValue ~= "" then
	       		local qualValueWithUrl = string.format('[%s %s]', tostring(item.qualValue.value), tostring(item.qualValueLabel.value))
	        	local row = "| " .. labelWithUrl ..  " || " ..  qualPropLabel.value .. " || " .. qualValueWithUrl 
    			table.insert(listItems, row)
	        else
        		table.insert(listItems, "| " .. labelWithUrl)
        	end
    	end
	end

	-- Construct the Wikitext table
	local wikitextTable = "{| class='wikitable'\n" .. table.concat(listItems, "\n|-\n") .. "\n|}"
	return wikitextTable
end


-- Function to get instance of types
function p.getInstanceOfTypes(frame)
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Constructing the SPARQL query with dynamic entity entityId
    -- P31: instance of property id
    local sparqlQuery = [[
PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
SELECT  ?InstanceOfTypeLabel WHERE {
  entityId: p:P31 ?statement .
  ?statement ps:P31 ?InstanceOfType .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
ORDER BY ?InstanceOfTypeLabel
	]]


	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
    end
	
	local InstanceOfTypes = {}
    -- Get the number of model types
	local totalInstanceOfTypes = #jsonResults.results.bindings
	-- Loop through the bindings
	for index = 0, totalInstanceOfTypes  do
    	local item = jsonResults.results.bindings[index]
    	if item and item.InstanceOfTypeLabel and item.InstanceOfTypeLabel.value then
        	local label = item.InstanceOfTypeLabel.value
        	table.insert(InstanceOfTypes, label)
    	end
	end

	-- Construct the Wikitext table
	local wikitextTable = "{| class='wikitable'\n|-\n| " .. table.concat(InstanceOfTypes, " || ") .. "\n|}"
	return wikitextTable

end

-- Function to get instance of types
function p.getListNamedAfter(frame)
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Constructing the SPARQL query with dynamic entity entityId
    -- P31: instance of property id
    local sparqlQuery = [[
PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
SELECT ?namedAfterLabel ?namedAfter
WHERE {
  entityId: p:P558 ?statement .
  ?statement ps:P558 ?namedAfter .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
ORDER BY ?namedAfterLabel
	]]


	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
	end

	local ListNamedAfter = {}
    -- Get the number of persons named after
	local totalPersons = #jsonResults.results.bindings
	-- Loop through the bindings
	for index = 0, totalPersons  do
    	local item = jsonResults.results.bindings[index]
    	if item and item.namedAfterLabel and item.namedAfterLabel.value then
        	local label = item.namedAfterLabel.value
        	local url = item.namedAfter.value
        	local numericId = url:match("Q(%d+)")
    		local urlRendered = "https://portal.mardi4nfdi.de/wiki/Person:" .. numericId
        	local labelWithUrl = string.format('[%s %s]', tostring(urlRendered), tostring(label))
        	table.insert(ListNamedAfter,  labelWithUrl)
    	end
	end
	
	return table.concat(ListNamedAfter, ', ')
    
-- end of function getListNamedAfter
end
    



-- Function to get Formulations with Quantities
function p.getFormulationsWithQuantities(frame)
	
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end

    -- Constructing the SPARQL query with dynamic entity entityId
    -- P1560: contains property id
    -- P989: defining formula property id
    local sparqlQuery = [[
PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
	SELECT ?Formula ?defining_formulation ?IDFormula ?qualValueLabel ?qualValue ?qualProp ?seriesOrdinal
	WHERE {
		# full statement node for P1560
		entityId: p:P1560 ?statement .
		?statement ps:P1560 ?IDFormula .
  
		OPTIONAL { 
    		?IDFormula rdfs:label ?Formula .
    		FILTER(LANG(?Formula) = "en")
		}
		OPTIONAL { ?IDFormula wdt:P989 ?defining_formulation . }

		# qualifiers on this statement
		OPTIONAL {
    		?statement ?qualProp ?qualValue .
    		FILTER(STRSTARTS(STR(?qualProp), STR(pq:)))   # only qualifiers
    		OPTIONAL { 
    		?qualProp rdfs:label ?qualPropLabel .
    		FILTER(LANG(?qualPropLabel) = "en")
    		}
    	OPTIONAL { 
    		?qualValue rdfs:label ?qualValueLabel .
    		FILTER(LANG(?qualValueLabel) = "en")
    		}
    		# 👇 extract series ordinal when qualifier is P146
        OPTIONAL {
            FILTER(?qualProp = pq:P146)
            ?qualValue wikibase:quantityAmount ?seriesOrdinal .
        	}
		}
	}
	ORDER BY
    IF(BOUND(?seriesOrdinal), ?seriesOrdinal, 1e9)
]]

	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	
		-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
		return "\"No mathematical expressions found.\""
    end
	
	-- local resultTable = p.convertJsonToTable(jsonResults)
	-- return "<pre>" .. mw.text.nowiki(json.jsonEncode(resultTable)) .. "</pre>"
    -- local jsonString = mw.text.jsonEncode(jsonResults)
    -- return "<pre>" .. mw.text.nowiki(jsonString) .. "</pre>"
	return p.extractDefiningFormulationsWithQuantities(jsonResults)

end


-- Function to extract Formulations with Quantities 
function p.extractDefiningFormulationsWithQuantities(jsonResults)
	local frame = mw.getCurrentFrame()
	-- Table to store contained entities
    local containedEntities = {}
    -- Table to store quantity symbol and name id 
    local quantitySymbolNameIdPairs = {}
    
    local jsonString = mw.text.jsonEncode(jsonResults)
    -- Decode the JSON string into a Lua table
	local data = mw.text.jsonDecode(jsonString)
    
    -- Get the number of contained entities
	local totalContainedEntities = #data.results.bindings

    if totalContainedEntities > 0 then
    	-- Create a table to keep track of seen IDs
		local seenEquationIDs = {}
    	-- Loop through the bindings
		for index = 1, totalContainedEntities  do
    		local item = data.results.bindings[index]
    		local itemLabel = nil
    		if item and item.Formula and item.Formula.value
			then
    			itemLabel = item.Formula.value
			end
			
			

        	local equationIDURL = item.IDFormula.value
        	local equationID = equationIDURL:match(".*/(.-)$") or equationIDURL -- Extracts the part after the last '/'
        	--  Check if we’ve already seen this ID
    		if not seenEquationIDs[equationID] then
        		-- mark it as seen to avoid duplicates
        		seenEquationIDs[equationID] = true
        	
        		local instances = getInstanceOf(equationID)
        		for _, id in ipairs(instances) do
        			if(id == "Q6481152") then
        				-- if is about a mathematical expression (Q6481152) that is contained
        				local numericId = equationID:sub(2)  -- remove "Q" to get "6674540"
        				local urlRendered = p.canonicalLink(equationID)
    					local equationWithUrl = string.format('[%s %s]', tostring(urlRendered), tostring(itemLabel))
    					-- add new row that starts with equationWithUrl
    					table.insert(containedEntities, string.format('| %s ', equationWithUrl))
        	
        				local equation = mw.wikibase.getEntity(equationID)
        	
		    		-- Get all statements for property P989 (defining formula)
					local definingFormulas = equation:getBestStatements('P989')
					-- Check if there are any
					if definingFormulas and #definingFormulas > 0 then
						for i = 1, #definingFormulas do
							local definingFormula = definingFormulas[i]
        					local value = definingFormula.mainsnak.datavalue.value
    						mathTag = frame:extensionTag{
	        					name = "math",
	         					content = value
        					}
        					if(i == 1) then
        						-- for the first mathematical expression (value of defining formula) concatenate it next to the equation label (with URL)
        						containedEntities[#containedEntities] = containedEntities[#containedEntities] .. string.format('|| %s ', mathTag)
        					else
        						-- for every other mathematical expression (value of defining formula) create a new row
        						table.insert(containedEntities, string.format('|  || %s', mathTag))
        					end
  							local qualValue = item.qualValueLabel or item.qualValue or ""
	        				local qualValueWithUrl = nil
	        				-- Skip qualifier P146 completely
							if item.qualProp and item.qualProp.value then
    							local qualPid = item.qualProp.value:match("P%d+$")
    							if qualPid == "P146" then
    							else
       

local qualValue = item.qualValueLabel or item.qualValue or ""
local qualValueWithUrl = ""
	        				if qualValue ~= "" then

local qv = item.qualValue

-- Only render if it’s not a numeric-looking string
if not (qv.type == "string" and tonumber(qv.value)) then

    if qv.type == "wikibase-entityid" then
        -- entity → clickable link
        qualValueWithUrl = string.format(
            '[[%s|%s]]',
            qv.value.id,
            item.qualValueLabel and item.qualValueLabel.value or qv.value.id
        )

    elseif qv.type == "quantity" then
        -- numeric qualifier → optional: skip or format
        qualValueWithUrl = mw.ustring.gsub(qv.value.amount, "^%+", "")

    elseif qv.type == "time" then
        -- time qualifier
        qualValueWithUrl = tostring(qv.value.time)

    else
        -- safe fallback for strings, monolingualtext, etc.
        qualValueWithUrl =
            (item.qualValueLabel and item.qualValueLabel.value)
            or tostring(qv.value)
    end

    -- Only append if there’s something meaningful
    if qualValueWithUrl and qualValueWithUrl ~= "" then
        containedEntities[#containedEntities] =
            containedEntities[#containedEntities] .. string.format('|| %s ', qualValueWithUrl)
    end

end
								
	        				end
end
end

	        			-- go over defining formulas	
						end
					else
	       			--	local qualValue = item.qualValueLabel or item.qualValue or ""
	       			--		local qualValueWithUrl = ""
	        		--		if qualValue ~= "" then
	        		--			qualValueWithUrl = string.format('[%s %s]', tostring(item.qualValue.value), tostring(item.qualValueLabel.value))
	        		--		end
					--		if qualValue ~= "" then
					--			local row = "| " .. equationWithUrl .. " || " ..   qualValueWithUrl
					--			table.insert(containedEntities, row)
					--		else
					--			table.insert(containedEntities, "| " .. equationWithUrl)
					--		end
					end
   
        	
        			quantitySymbolNameIdPairs = p.extractQuantities(equationID)
        	
        			if type(quantitySymbolNameIdPairs) == "table" then
        				-- Accessing the stored pairs
						for i, pair in ipairs(quantitySymbolNameIdPairs) do
							local pairFirstValue = pair[1]
							local pairFirstValue = mw.text.decode(pairFirstValue or "")
							local quantitySymbolMathTag = frame:extensionTag{
								name = "math",
            					content = pairFirstValue
        					}
        					-- Construct the Portal URL
        					local numericId = pair[3]:sub(2)  -- remove "Q" to get "6674540"
        					local instances = getInstanceOf(pair[3])
        					-- Define a set of allowed IDs for Quantity
							local allowedQuantityIds = {
    						Q6534245 = true,
    						Q6534237 = true,
    						}
    						local url = ""
        					for _, id in ipairs(instances) do
        						if allowedQuantityIds[id] then
        							-- if the symbol is a quantity kind or a quantity
        							-- Construct the Portal URL
        							url = "https://portal.mardi4nfdi.de/wiki/Quantity:"
        						elseif(id == "Q6481152") then
        							-- if is about a mathematical expression (Q6481152) that is contained
        							url = "https://portal.mardi4nfdi.de/wiki/Formula:"
        						end
        					-- go over values of instances
        					end
        					
							-- local fullUrl = url .. numericId
							local fullUrl = p.canonicalLink(pair[3])
    					
        					local labelWithUrl = string.format('[%s %s]', tostring(fullUrl), tostring(pair[2]))
        	
            				table.insert(containedEntities, "| " .. " || ".. quantitySymbolMathTag   ..  " represents "  .. labelWithUrl)
						end
					table.insert(containedEntities, "| " .. " " )
					else
					end
				elseif(id == "Q68663") then
					-- if is about a model (Q68663) that is contained
					local numericId = equationID:sub(2)  -- remove "Q" to get "6674540"
					-- local urlRendered = "https://portal.mardi4nfdi.de/wiki/Model:" .. numericId
    				local urlRendered = p.canonicalLink(equationID)
    				local modelWithUrl = string.format('[%s %s]', tostring(urlRendered), tostring(itemLabel))
					table.insert(containedEntities, "| " .. modelWithUrl)
				
				elseif(id == "Q6534237") then
					-- if is about a quantity (Q6534237) that is contained
					local numericId = equationID:sub(2)  -- remove "Q" to get "6674540"
					-- local urlRendered = "https://portal.mardi4nfdi.de/wiki/Quantity:" .. numericId
					local urlRendered = p.canonicalLink(equationID)
    				local quantityWithUrl = string.format('[%s %s]', tostring(urlRendered), tostring(itemLabel))
    				-- add new row that starts with quantityWithUrl
    				table.insert(containedEntities, string.format('| %s ', quantityWithUrl))
    				
    				local equation = mw.wikibase.getEntity(equationID)
        	
		    		-- Get all statements for property P989 (defining formula)
					local definingFormulas = equation:getBestStatements('P989')
		    		-- Check if there are any
					if definingFormulas and #definingFormulas > 0 then
						-- go over defining formula values (if multiple)
						for i = 1, #definingFormulas do
							local definingFormula = definingFormulas[i]
        					local value = definingFormula.mainsnak.datavalue.value
    						mathTag = frame:extensionTag{
	        					name = "math",
	         					content = value
        					}
        					if(i == 1) then
        						-- for the first mathematical expression (value of defining formula) concatenate it next to the equation label (with URL)
        						containedEntities[#containedEntities] = containedEntities[#containedEntities] .. string.format('|| %s ', mathTag)
        					else
        						-- for every other mathematical expression (value of defining formula) create a new row
        						table.insert(containedEntities, string.format('|  || %s', mathTag))
        					end
	        			local qualValue = item.qualValueLabel or item.qualValue or ""
        				local qualValueWithUrl = ""
        				if qualValue ~= "" then
	        				qualValueWithUrl = string.format('[%s %s]', tostring(item.qualValue.value), tostring(item.qualValueLabel.value))
	        					containedEntities[#containedEntities] = containedEntities[#containedEntities] .. string.format('|| %s ', qualValueWithUrl)
        				end	
					-- go over defining formulas	
					end
					else
	       				--local qualValue = item.qualValueLabel or item.qualValue or ""
						--if qualValue ~= "" then
						--	local row = "| " .. quantityWithUrl .. " || " .. qualValue.value 
    					--	table.insert(containedEntities, row)
						--	-- table.insert(containedEntities, "| " .. quantityWithUrl, qualValue.value )
						--else
						--	table.insert(containedEntities, "| " .. quantityWithUrl)
					--	end
					end
					quantitySymbolNameIdPairs = p.extractQuantities(equationID)
        	
        			if type(quantitySymbolNameIdPairs) == "table" then
        				-- Accessing the stored pairs
						for i, pair in ipairs(quantitySymbolNameIdPairs) do
							local pairFirstValue = pair[1]
							local pairFirstValue = mw.text.decode(pairFirstValue or "")
							local quantitySymbolMathTag = frame:extensionTag{
								name = "math",
            					content = pairFirstValue
        					}
        					-- Construct the Portal URL
        					local url = "https://portal.mardi4nfdi.de/wiki/Quantity:"
        					local numericId = pair[3]:sub(2)  -- remove "Q" to get "6674540"
        					-- local fullUrl = url .. numericId
        					local fullUrl = p.canonicalLink(pair[3])
    				
        					local labelWithUrl = string.format('[%s %s]', tostring(fullUrl), tostring(pair[2]))
        	
            					table.insert(containedEntities, "| " .. " || ".. quantitySymbolMathTag   ..  " represents "  .. labelWithUrl)
							end
						table.insert(containedEntities, "| " .. " " )
						else
						end
					-- if is about a quantity (Q6534237) that is contained
					end
				-- Loop through the instances	
        		end
        	-- if not seen 
			end
        -- Loop through the bindings
		end

		-- Construct the Wikitext table
    	local wikitextTable = [[
{| class="wikitable" style="table-layout: auto;"
]] .. table.concat(containedEntities, "\n|-\n") .. "\n|}"
    	return wikitextTable
    else 
    	return "No mathematical expressions found."
    end
end

function p.extractQuantities(qid)
	-- Property ID for in defining formula 
    local pidInDefiningFormula  = "P983"
    -- Property ID for the (qualifier) symbol represents
    local pidSymbolRepresents = "P984"   

    -- Attempt to retrieve entity data
    local entity = mw.wikibase.getEntity(qid)
    if not entity or not entity.claims[pidInDefiningFormula] then
        return "No formulation found"
    end
    
    
    local inDefiningFormulaClaims = entity.claims[pidInDefiningFormula]
    local count = 0
    -- Table to store pairs of quantity symbol and quantity name
    local quantitySymbolNameIdPairs = {}
	for _ in pairs(inDefiningFormulaClaims or {}) do
    	count = count + 1
    	-- Get the quantity symbol
    	local quantitySymbol = inDefiningFormulaClaims[count].mainsnak.datavalue.value
    	if not quantitySymbol then
        	return "No valid symbol found"
    	end
    	quantity = inDefiningFormulaClaims[count].qualifiers[pidSymbolRepresents][1].datavalue.value.text
        quantityId = inDefiningFormulaClaims[count].qualifiers[pidSymbolRepresents][1].datavalue.value.id
	    local quantityName = mw.wikibase.label(quantityId)
	    -- Insert pair into the table
	    table.insert(quantitySymbolNameIdPairs, {quantitySymbol, quantityName, quantityId})
    end
    return quantitySymbolNameIdPairs
end


function p.get_instances_of_target(frame)
    local item = frame.args[1]          -- e.g., "Q12345"
    local target_q = frame.args[2] or "Q6775701"

    if not item then return "" end

    local entity = mw.wikibase.getEntity(item)
    if not entity then return "" end

    local results = {}

	-- get label and make link
    local itemLabel = mw.wikibase.getLabel(item) or item
    local itemUrlRendered = p.canonicalLink(item)
    local itemWithUrl = string.format('[%s %s]', tostring(itemUrlRendered), tostring(itemLabel))
    						
    local p31_claims = entity.claims["P31"] or {}
    for _, claim in ipairs(p31_claims) do
        local mainsnak = claim.mainsnak
        if mainsnak.datatype == "wikibase-item" and mainsnak.datavalue then
            local value_id = mainsnak.datavalue.value.id
            local value_entity = mw.wikibase.getEntity(value_id)
            if value_entity then
                local value_p31_claims = value_entity.claims["P31"] or {}
                for _, vc in ipairs(value_p31_claims) do
                    local v_sn = vc.mainsnak
                    if v_sn.datatype == "wikibase-item" and v_sn.datavalue then
                        local v_id = v_sn.datavalue.value.id
                        if v_id == target_q then
                            -- get label and make link
                            local label = mw.wikibase.getLabel(value_id) or value_id
                            local urlRendered = p.canonicalLink(value_id)
    						local valueWithUrl = string.format('[%s %s]', tostring(urlRendered), tostring(label))
    						table.insert(results,  '| ' ..  itemWithUrl .. ' || ' .. valueWithUrl)
    						break -- stop after first match
                        end
                    end
                end
            end
        end
    end

    -- return as line-separated list
    -- return table.concat(results, "<br />")
    -- Construct the Wikitext table
	local wikitextTable = "{| class='wikitable'\n" ..
    "! Computational Task\n! Algorithmic Task\n" ..          -- header row
    "|-\n" ..                          -- separator before first data row
    table.concat(results, "\n|-\n") ..
    "\n|}"
	return wikitextTable

end

return p