Jump to content

Module:DynamicCapiunto

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

local capiunto = require 'capiunto'
local p = {}

function p.main(frame)
    local args = frame:getParent().args
    local qid = args.qid or args[1]

    if not qid then
        return '<span class="error">Error: No Wikidata QID provided</span>'
    end

    -- Register the entity association
    frame:callParserFunction('#unlinkedwikibase', 'id=' .. qid)

    -- Get entity data from UnlinkedWikibase
    local entity = mw.ext.UnlinkedWikibase.getEntity(qid)

    if not entity then
        return '<span class="error">Error: Entity ' .. qid ..
                   ' not found</span>'
    end

    -- Helper function to get property value from entity
    local function getPropertyValue(pid)
        if not entity.claims or not entity.claims[pid] then return nil end

        local claims = entity.claims[pid]
        if not claims or #claims == 0 then return nil end

        local claim = claims[1]
        if not claim.mainsnak or not claim.mainsnak.datavalue then
            return nil
        end

        return claim.mainsnak.datavalue.value
    end

    -- Helper function to format different value types
    local function formatValue(value, valueType)
        if not value then return nil end

        if type(value) == 'table' then
            -- Handle time values
            if value.time then
                -- Extract year-month-day from +2023-01-15T00:00:00Z format
                local dateStr = value.time
                local year, month, day =
                    dateStr:match('^[+-]?(%d+)-(%d+)-(%d+)')
                if year then
                    local y = tonumber(year)
                    local m = tonumber(month) or 0
                    local d = tonumber(day) or 0
                    -- If month or day are zero/unknown, show only the year
                    if m == 0 or d == 0 then
                        return string.format('%04d', y)
                    end
                    return string.format('%04d-%02d-%02d', y, m, d)
                end
                return value.time
                -- Handle entity references (Q-IDs)
            elseif value.id then
                -- Register a parser cache dependency so the page is invalidated
                -- when UnlinkedWikibase populates or updates this entity's data.
                mw.ext.UnlinkedWikibase.getLocalTitle(value.id)
                local linkedEntity = mw.ext.UnlinkedWikibase.getEntity(value.id)
                if linkedEntity then
                    local label = (linkedEntity.labels and
                                      linkedEntity.labels.en) and
                                      linkedEntity.labels.en.value or value.id
                    -- Then prefer an en.wikipedia sitelink (external link)
                    if linkedEntity.sitelinks and linkedEntity.sitelinks.enwiki and
                        linkedEntity.sitelinks.enwiki.url then
                        return
                            '[' .. linkedEntity.sitelinks.enwiki.url .. ' ' ..
                                label .. ']'
                    end
                    -- Fallback to the English label if available
                    if linkedEntity.labels and linkedEntity.labels.en then
                        return label
                    end
                end
                return value.id
                -- Handle monolingual text
            elseif value.text then
                return value.text
            end
        end

        return tostring(value)
    end

    -- Helper to get claim entity id (if the claim is an entity)
    local function getClaimEntityId(pid)
        local v = getPropertyValue(pid)
        if type(v) == 'table' and v.id then return v.id end
        return nil
    end

    -- Helper to get the first claim object for a property
    local function getFirstClaimObject(pid)
        if not entity.claims or not entity.claims[pid] then return nil end
        local claims = entity.claims[pid]
        if not claims or #claims == 0 then return nil end
        return claims[1]
    end

    -- Helper to get a qualifier value (prefer English monolingual text for P2096)
    local function getQualifierValue(claim, qualPid)
        if not claim or not claim.qualifiers or not claim.qualifiers[qualPid] then
            return nil
        end
        local qsnaks = claim.qualifiers[qualPid]
        if not qsnaks or #qsnaks == 0 then return nil end
        for i, q in ipairs(qsnaks) do
            -- Check for valid snak with datavalue
            if q and q.snaktype == 'value' and q.datavalue and q.datavalue.value then
                local v = q.datavalue.value
                if type(v) == 'table' then
                    -- For monolingual text, prefer English
                    if v.language and v.language == 'en' and v.text then
                        return v.text
                    end
                else
                    return tostring(v)
                end
            end
        end
        return nil
    end

    -- Extract a {latitude, longitude} coordinate (P625) from an entity object.
    local function getCoordFromEntity(ent)
        if not ent or not ent.claims or not ent.claims.P625 then return nil end
        local claims = ent.claims.P625
        if not claims or #claims == 0 then return nil end
        local snak = claims[1].mainsnak
        if not snak or not snak.datavalue then return nil end
        local v = snak.datavalue.value
        if type(v) == 'table' and v.latitude and v.longitude then return v end
        return nil
    end

    -- Add a Leaflet/OpenStreetMap map for this entity.
    -- Prefer the entity's own coordinate location (P625). If it has none, fall
    -- back to the coordinates of the entity referenced as its headquarters
    -- location (P159), fetched via UnlinkedWikibase.
    -- Rendered via the Maps extension's #display_map parser function. A module's
    -- return value goes through the main parse pass but NOT the preprocessor, so
    -- parser-function syntax would otherwise be emitted literally. We expand it
    -- here with frame:preprocess() and add the resulting HTML to the infobox.
    local function addMap(box)
        local coord = getCoordFromEntity(entity)
        if not coord then
            local hqId = getClaimEntityId('P159')
            if hqId then
                -- Register a parser cache dependency on the HQ entity so the
                -- page is invalidated when its data is populated/updated.
                mw.ext.UnlinkedWikibase.getLocalTitle(hqId)
                coord = getCoordFromEntity(
                            mw.ext.UnlinkedWikibase.getEntity(hqId))
            end
        end
        if not coord then return end
        local mapHtml = frame:preprocess(string.format(
                            '{{#display_map:%s,%s|width=100%%|height=200|zoom=13|service=leaflet}}',
                            tostring(coord.latitude), tostring(coord.longitude)))
        box:addWikitext(mapHtml)
    end

    local function addWikidataLink(box)
        box:addWikitext(
            '<div style="width:100%; text-align:center; font-size:85%;">[' ..
                'https://www.wikidata.org/wiki/' .. qid ..
                ' View on Wikidata]</div>')
    end

    -- Decide which infobox to render based on instance (P31)
    local instanceId = getClaimEntityId('P31')
    local infobox

    if instanceId == 'Q5' then
        -- Person infobox
        local personTitle = qid
        if entity.labels and entity.labels.en then
            personTitle = entity.labels.en.value
        end

        infobox = capiunto.create({
            title = personTitle,
            bodyClass = 'infobox vevent',
            bodyStyle = 'width: 300px;'
        })

        -- Add image (P18) with media legend (P2096) as caption
        local imageClaim = getFirstClaimObject('P18')
        if imageClaim then
            local imageVal = imageClaim.mainsnak and
                                 imageClaim.mainsnak.datavalue and
                                 imageClaim.mainsnak.datavalue.value
            if imageVal then
                local imageStr = tostring(imageVal)
                local caption = getQualifierValue(imageClaim, 'P2096')
                if caption and caption ~= '' then
                    infobox:addImage('[[File:' .. imageStr .. '|300px]]',
                                     caption)
                else
                    infobox:addImage('[[File:' .. imageStr .. '|300px]]')
                end
            end
        end

        -- Birthplace (P19)
        local birthplace = getPropertyValue('P19')
        if birthplace then
            infobox:addRow('Birthplace', formatValue(birthplace))
        end

        -- Birthday (P569)
        local birthday = getPropertyValue('P569')
        if birthday then
            infobox:addRow('Birthday', formatValue(birthday))
        end

        -- Death (P570)
        local death = getPropertyValue('P570')
        if death then infobox:addRow('Death', formatValue(death)) end

        -- Occupation (P106)
        local occupation = getPropertyValue('P106')
        if occupation then
            infobox:addRow('Occupation', formatValue(occupation))
        end

        addMap(infobox)
        addWikidataLink(infobox)

        return tostring(infobox) .. '\n[[Category:People]]'
    else
        -- Default: organisation/troupe infobox (existing behavior)
        local title = qid
        if entity.labels and entity.labels.en then
            title = entity.labels.en.value
        end

        infobox = capiunto.create({
            title = title,
            bodyClass = 'infobox vevent',
            bodyStyle = 'width: 300px;'
        })

        -- Add official name (P1448)
        local officialName = getPropertyValue('P1448')
        if officialName then
            infobox:addRow('Official Name', formatValue(officialName))
        end
    end

    -- Add logo (P154)
    local logo = getPropertyValue('P154')
    if logo then
        local logoStr = formatValue(logo)
        if logoStr then
            infobox:addImage('[[File:' .. logoStr .. '|150px]]', 'Logo')
        end
    end

    -- Add image (P18) with media legend (P2096) as caption
    local imageClaim = getFirstClaimObject('P18')
    if imageClaim then
        local imageVal =
            imageClaim.mainsnak and imageClaim.mainsnak.datavalue and
                imageClaim.mainsnak.datavalue.value
        if imageVal then
            local imageStr = tostring(imageVal)
            local caption = getQualifierValue(imageClaim, 'P2096')
            if caption and caption ~= '' then
                infobox:addImage('[[File:' .. imageStr .. '|300px]]', caption)
            else
                infobox:addImage('[[File:' .. imageStr .. '|300px]]')
            end
        end
    end

    -- Add headquarters (P159)
    local headquarters = getPropertyValue('P159')
    if headquarters then
        infobox:addRow('Headquarters', formatValue(headquarters))
    end

    -- Add founding date (P571)
    local foundingDate = getPropertyValue('P571')
    if foundingDate then
        infobox:addRow('Founding Date', formatValue(foundingDate))
    end

    -- Add dissolution date (P576)
    local dissolutionDate = getPropertyValue('P576')
    if dissolutionDate then
        infobox:addRow('Dissolution Date', formatValue(dissolutionDate))
    end

    -- Add country (P17)
    local country = getPropertyValue('P17')
    if country then infobox:addRow('Country', formatValue(country)) end

    -- Add parent organization (P749)
    local parentOrg = getPropertyValue('P749')
    if parentOrg then
        infobox:addRow('Parent Organization', formatValue(parentOrg))
    end

    -- Add official website (P856) as full-width row at bottom
    local website = getPropertyValue('P856')
    if website then
        local websiteStr = formatValue(website)
        if websiteStr then
            -- Strip http:// or https:// from display text
            local displayUrl = websiteStr:gsub('^https?://', '')
            infobox:addWikitext(
                '<div style="width:100%; text-align:center;">[' .. websiteStr ..
                    ' ' .. displayUrl .. ']</div>')
        end
    end

    addMap(infobox)
    addWikidataLink(infobox)

    return tostring(infobox) ..
               '\n[[Category:Clubs, Societies and Institutions]]'
end

return p