مَوادَس کُن گٔژھِو

Module:Databox

وِکیٖپیٖڈیا پؠٹھٕ، اَکھ آزاد اِنسایکلوپیٖڈیا

"یَمہٕ ماڈیوٗلُک دَستاویز ییٚہِ Module:Databox/دَستاویز جاے بَناونہٕ"

-- Please DO NOT edit this page unless you know what you are doing.
-- Maintainer: User:511KeV

-- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual
-- https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua

local property_blacklist = {
    'P360', --is a list of
    'P4224', --category contains
    'P935', -- Commons gallery
    'P1472', -- Commons Creator page
    'P1612', -- Commons Institution page
    'P373', -- Commons category
    'P3722', -- Commons maps category
    'P1151', -- topic's main Wikimedia portal
    'P1424', -- topic's main template
    'P910', -- topic's main category
    'P1200', -- bodies of water basin category
    'P1792', -- category of associated people
    'P2521', -- female form of label
    'P3321', -- male form of label 
    'P6186', -- category for eponymous categories
    'P1464', -- category for people born here
    'P1465', -- category for people who died here
    'P1791', -- category of people buried here
    'P1740', -- category for films shot at this location
    'P2033', -- Category for pictures taken with camera
    'P2517', -- category for recipients of this award
    'P4195', -- category for employees of the organization
    'P1754', -- category related to list
    'P8596', -- category for the exterior of the item
    'P301', -- category's main topic
    'P6112',-- category for members of a team
    'P971', -- category combines topics
    'P3876', -- category for alumni of educational institution
    'P8933', -- category for the view from the item
    'P1753', -- list related to category
    'P3921', -- Wikidata SPARQL query equivalent
    'P1204', -- Wikimedia portal's main topic
    'P1423', -- template's main topic
    'P1709', -- equivalent class
    'P3950', -- narrower external class
    'P2888', -- exact match
    'P1382', -- coincident with
    'P2670', -- has parts of the class
    'P3113', -- does not have part
    'P2737', -- union of
    'P2738', -- disjoint union of
    'P2445', -- metasubclass of
    'P1963', -- properties for this type
    'P3176', -- uses property
    'P1889', -- different from
    'P460', -- said to be the same as
    'P2959', -- permanent duplicated item
    'P2860', -- cites
    'P5125', -- wikimedia outline
    'P5008', -- on focus list of Wikimedia project
    'P2559', -- Wikidata usage instructions
    'P1343', -- described by source
    'P8687', -- social media followers 
    'P972', --  catalogue
    'P1282', -- OSM tag or key
    'P4839', -- Wolfram Language entity code
    'P6104', -- Maintained by Wikiproject
    'P5996', -- Category for films in this language
    'P735', -- Given name
    'P734', -- Family name
    'P1559', -- Native name (Handled separately)
    'P21', -- Sex or gender
    'P2184', -- history of topic
    'P1438', -- Jewish Encyclopedia ID
    'P7867', -- category for maps
    'P8402', -- Open Data portal
    'P1448', -- official name (Handled separately)
    'P569', -- date of birth (Handled separately)
    'P570', -- date of death (Handled separately)
    'P19', -- place of birth
    'P20', -- place of death
    'P27', -- country of citizenship
    'P2747', -- Filmiroda rating
    'P7561', -- category for the interior of the item
    'P6365', -- member category
    'P465', -- sRGB color hex triplet
    'P487', -- Unicode character
    'P7084', -- related category
    'P7763', -- Copyright status as a creator
    'P1814', -- name in kana
    'P2001', -- Revised Romanization
    'P2572', -- Hashtag
    'P8989', -- category for the view of the item
    'P6363', -- WordLift
}

-- Exceptions to the datatype blocking:
local property_whitelist = { 
    'P856', -- official website
    'P3896',-- geoshape
    'P345', -- IMDB id
    'P6375' -- street address
}

-- Properties with higher level items (Bulleted Hierarchy):
local properties_with_parents = { 
    'P131', -- located in the administrative territorial entity
    'P144', -- based on
    'P155', -- follows
    'P171', -- parent taxon
    'P276', -- location
    'P279', -- subclass of
    'P361', -- part of
    'P706', -- located on terrain feature
    'P749', -- parent organization
    'P807', -- separated/forked from
    'P1365', -- replaced
    'P1647', -- subproperty of
    'P3730' -- next higher rank
}
local properties_with_children = { 
-- Properties with lower level items:
    'P150', -- contains administrative territorial entity
    'P156', -- followed by
    'P355', -- subsidiary
    'P527', -- has part 
    'P1012', -- contains
    'P1366', -- replaced by
    'P3729', -- next lower rank
    'P4330', -- contains
    'P7888' -- merged into
}

-- Import the new qualifiers sub-module
local qualifiersHelper = require('Module:databox/qualifiers')

-- Merge two tables and return a new table
function mergeTables(first, second)
    result = {}
    for k,v in pairs(first) do
        table.insert(result, v)
    end
    for k,v in pairs(second) do
        table.insert(result, v)
    end
    return result
end

-- Turn index based tables into key based tables
function valuesToKeys(array)
    local result = {}
    for _, v in pairs(array) do
        result[v:upper()] = true
    end
    return result
end

-- Helper to get description for tooltips
function getDescription(id)
    return mw.wikibase.getDescription(id, 'ks') or mw.wikibase.getDescription(id, 'en') or ""
end

function getBirthStatement(lang, date_of_birth, date_of_death, place_of_birth)
    local birth_time = ''
    if date_of_death then
        birth_time = formatDate(lang, date_of_birth.time)
    else
        local date_of_birth_parts = mw.text.split(formatDate(lang, date_of_birth.time, nil, 'Y-m-j'), '-')

        birth_time = string.format('{{Birth date and age|%s|%s|%s}}', 
            date_of_birth_parts[1], date_of_birth_parts[2], date_of_birth_parts[3])
    end
    local birth = birth_time

    if place_of_birth then
        -- Try to get the Kashmiri article name for the entity
        local birth_location = mw.wikibase.getSitelink(place_of_birth.id, 'kswiki')
        
        local link = true
        if not birth_location then
            -- If there was no Kashmiri article for the entity, then get an article name from other wikis
            birth_location = mw.wikibase.getLabel(place_of_birth.id)
            link = false
        end
        
        if birth_location then
            if link then birth_location = '[[' .. birth_location .. ']]' end
             
            birth = birth .. '<br>' .. birth_location
    
            local birth_country = getBestStatementById(place_of_birth.id, 'P17')
            if birth_country then
                local birth_country_label = mw.wikibase.getSitelink(birth_country.id, 'kswiki')
                local link = true
                if not birth_country_label then
                     birth_country_label = mw.wikibase.getSitelink(birth_country.id, 'enwiki')
                     link = false
                end
                
                if link then birth_country_label = '[[' .. birth_country_label .. ']]' end
    
                birth = birth .. '، ' .. birth_country_label
            end
        end
    end
    
    return birth
end

function getDeathStatement(lang, date_of_birth, date_of_death, place_of_death)
    local date_of_birth_parts = mw.text.split(formatDate(lang, date_of_birth.time, nil, 'Y-m-j'), '-')
    local date_of_death_parts = mw.text.split(formatDate(lang, date_of_death.time, nil, 'Y-m-j'), '-')

    local death_time = string.format('{{Death date and age|%s|%s|%s|%s|%s|%s}}', 
        date_of_death_parts[1], date_of_death_parts[2], date_of_death_parts[3],
        date_of_birth_parts[1], date_of_birth_parts[2], date_of_birth_parts[3])
    
    local death = death_time
    
    if place_of_death then
        local death_location = mw.wikibase.getSitelink(place_of_death.id, 'kswiki')

        local link = true
        if not death_location then
            death_location = mw.wikibase.getSitelink(place_of_death.id, 'enwiki')
            link = false
        end
        
        if not death_location then
            death_location = mw.wikibase.getLabel(place_of_death.id)
            link = false
        end
        
        if death_location then
            if link then death_location = '[[' .. death_location .. ']]' end
                
            death = death .. '<br>' .. death_location
        
            local death_country = getBestStatementById(place_of_death.id, 'P17')
            if death_country then
                local death_country_label = mw.wikibase.getSitelink(death_country.id, 'kswiki')
                
                local link = true
                if not death_country_label then
                     death_country_label = mw.wikibase.getSitelink(death_country.id, 'enwiki')
                     link = false
                end
                
                if link then death_country_label = '[[' .. death_country_label .. ']]' end
                
                death = death .. '، ' .. death_country_label
            end
        end
    end

    return death
end

-- Convert Arabic numbers (0123456789) to Kashmiri numbers (٠١٢٣٤٥٦٧٨٩) and translate date terms
function toKashmirinumbers(text)
    if text == nil then return nil end
    return text:gsub('0', '0')
    :gsub('1', '1')
    :gsub('2', '2')
    :gsub('3', '3')
    :gsub('4', '4')
    :gsub('5', '5')
    :gsub('6', '6')
    :gsub('7', '7')
    :gsub('8', '8')
    :gsub('9', '9')
    :gsub('century', 'صٔدی')
    :gsub('BCE', 'مَسیٖح برٛونٛہہ')
    :gsub('million years', 'مِلیَن ؤری')
    :gsub('AD', 'عیٖسوی')
    :gsub('square kilometre', 'کِلومیٖٹَر چَکور')
    :gsub('kilometre', 'کِلومیٖٹَر')
end

function formatDate(lang, dateString, fallback, format)
    -- Check if the date is year-only (Wikidata uses -00-00 for unknown month/day)
    local isYearOnly = string.find(dateString, '%-00%-00T') ~= nil
    
    -- If no specific format is requested, set default based on precision
    if not format then 
        if isYearOnly then
            format = 'Y' -- Only show the Year
        else
            format = 'j xg Y' -- Show Day Month Year
        end
    end
    
    -- For BCE dates (starting with -), MediaWiki's formatDate often returns the raw string.
    -- We return it here so toKashmirinumbers can translate "century" and "BCE" later.
    if dateString:sub(1, 1) == '-' then return fallback or dateString end  
    
    -- Standardize the string for the parser
    dateString = dateString:gsub('%-00%-00T', '-01-01T')  
    return lang:formatDate(format, dateString, false)
end

function isEnglish(text)
    return string.find(text, '[abcdefghijklmnopqrstuvwxyz]') ~= nil
end

-- Returns the best statements for the first property this item has
function getBestStatement(item, ...)
    for i,v in ipairs(arg) do
        local statements = item:getBestStatements(v)
        if len(statements) >= 1 and statements[1] and statements[1].mainsnak.datavalue then
            return statements[1].mainsnak.datavalue.value
        end
    end

    return nil
end

-- Returns the best statements for the first property this item has
function getBestStatementById(id, ...)
    for i,v in ipairs(arg) do
        local statements = mw.wikibase.getBestStatements( id, v)
        if len(statements) >= 1 and statements[1] and statements[1].mainsnak.datavalue then
            return statements[1].mainsnak.datavalue.value
        end
    end

    return nil
end

function len(t)
  local count = 0
  for _ in pairs(t) do count = count + 1 end
  return count
end

function isEmpty(s)
  return s == nil or s == ''
end

local module_properties = { ['item'] = true, ['آئٹم'] = true, ['کھٔٹِتھ'] = true, ['صِرِف کٲشُر'] = true, ['module'] = true, ['ماڈیول'] = true }
function getOverridenProperties(args)
    properties = {}
    for key, value in pairs(args) do
        if (not module_properties[key]) then 
            properties[key:upper()] = value
        end
    end
    return properties
end

local p = {}

function p.databox(frame)
    local args = frame:getParent().args
    local itemId = nil
    local show_english_properties = true
    
    if args.item or args['اَیٹم'] then
        itemId = args.item or args['اَیٹم']
    end
    
    if args['صِرِف کٲشُر'] == true or args['صِرِف کٲشُر'] == 'آ' then
        show_english_properties = false
    end
    
    local overriden_properties = getOverridenProperties(args)
    
    local hidden_properties = {}
    if args['کھٔٹِتھ'] then
        hidden_properties = mw.text.split(args['کھٔٹِتھ'], "%s*[,،]%s*")
    end

    local lang = mw.language.getContentLanguage()
    local item = mw.wikibase.getEntity(itemId)
    local levels = 3 -- Default Max Levels for hierarchy

    if item == nil then
        mw.addWarning("Wikidata item not found")
        return ""
    end

    -- ==========================================
    -- NEW: Get Dynamic Theme securely via pcall
    -- ==========================================
    local themeColor = '#F2F2F2'
    local themeIcon = ''
    local themeTextColor = 'black' -- Default text color
    
    local success, themeModule = pcall(require, 'Module:Databox/theme')
    if success and type(themeModule) == 'table' then
        local themeData = themeModule.getTheme(args, item)
        if themeData then
            themeColor = themeData.color or '#F2F2F2'
            themeIcon = themeData.icon or ''
            themeTextColor = themeData.textcolor or 'black' -- Pull the text color!
        end
    end
    -- Check for manual override (args.title), then Wikidata label, then page title
    local headerText = args.title
    if headerText == nil or headerText == '' then
        headerText = item:getLabel() or mw.title.getCurrentTitle().text
    end

    if themeIcon ~= '' then
        headerText = string.format("[[File:%s|22px|middle|link=]] %s", themeIcon, headerText)
    end
    -- ==========================================

    --Table Construction
    local dataTable = mw.html.create('table')
        :addClass('infobox vcard')
        :css({
            ['width'] = '22em'
        })

    -- Title
    dataTable:tag('tr'):tag('th')
        :addClass('fn')
        :attr('colspan', 2)
        :css({
            ['text-align'] = 'center',
            ['background-color'] = themeColor,
            ['border-top'] = args.titleborder or 'none',
            ['border-bottom'] = args.titleborder or 'none',
            ['padding'] = '0.5em 0',
            ['margin'] = '0.5em 0',
            ['font-size'] = '125%',
            ['color'] = args.titletextcolor or themeTextColor,
            ['font-weight'] = 'bold',
        })
        :wikitext(headerText)

    -- Native name
    local officialName = getBestStatement(item, 'P1448', 'P1559')
    if officialName then
        if officialName.language ~= 'ks' then
            local langName = mw.language.fetchLanguageName(officialName.language, 'ks')
            dataTable:tag('tr'):tag('th')
            :attr('colspan', 2)
            :css({
                ['text-align'] = 'center',
                padding = '0.5em 0',
                margin = '0.5em 0',
                ['font-size'] = '90%',
                ['font-weight'] = 'bold',
                ['max-width'] = '240px'
            })
            :wikitext('اَصٕل ناو  [[' .. langName ..']] زَبانہِ مَنٛز: ' .. officialName.text)
        end
    end
    
    -- 1. Logo (P154) - 200px
    local logo = args['لوگو'] or args['logo']
    -- Manual caption for Logo
    local logo_caption = args['لوگو_تَفصیٖل'] or args['logo_caption']

    if not logo and item then
        local st = item:getBestStatements('P154')
        if #st > 0 then logo = st[1].mainsnak.datavalue.value end
    end

    if logo then
        local logoCell = dataTable:tag('tr'):tag('td')
            :attr('colspan', 2)
            :css({['text-align'] = 'center', ['padding-bottom'] = '5px'})
        
        -- Render Logo
        logoCell:wikitext('[[File:' .. logo .. '|frameless|200px]]')

        -- Render Logo Caption (Only if manually added)
        if logo_caption then
            logoCell:tag('div')
                :css({['padding-top'] = '0.4em', ['line-height'] = '1.2em', ['font-size'] = '90%'})
                :wikitext(logo_caption)
        end
    end

    -- 2. Image (P18) - 300px
    local image = args['تَصویٖر'] or args['image']
    -- Manual caption for Image
    local caption = args['تَصویٖر_تَفصیٖل'] or args['image_caption']

    if not image and item then
        local st = item:getBestStatements('P18')
        if #st > 0 then image = st[1].mainsnak.datavalue.value end
    end

    if image then
        local imageCell = dataTable:tag('tr'):tag('td')
            :attr('colspan', 2)
            :css({['text-align'] = 'center'})
        
        -- Render Image
        imageCell:wikitext('[[File:' .. image .. '|frameless|300px]]')
        
        -- Render Image Caption (Only if manually added)
        if caption then
            imageCell:tag('div')
                :css({['padding-top'] = '0.4em', ['line-height'] = '1.2em', ['font-size'] = '90%'})
                :wikitext(caption)
        end
    end

    -- P31 (Instance of)
    local p31_stmts = item:getBestStatements('P31')
    local p31_links = {}   -- ALWAYS create this table, even if empty

    if #p31_stmts > 0 then
        for i = 1, math.min(#p31_stmts, 2) do
            local snak = p31_stmts[i].mainsnak
            if snak.datavalue and snak.datavalue.value.id then
                local id = snak.datavalue.value.id
                local label = mw.wikibase.getLabel(id) or id
                local sitelink = mw.wikibase.getSitelink(id)
                local en_sitelink = mw.wikibase.getSitelink(id, 'enwiki')
                
                if sitelink then
                    table.insert(p31_links, '[[' .. sitelink .. '|' .. label .. ']]')
                elseif en_sitelink then
                    local desc = getDescription(id)
                    table.insert(p31_links, '<span title="' .. desc .. '">' .. label .. '</span> [[File:Noun Project label icon 1116097 cc mirror.svg|12px|link=d:' .. id .. '|title=وِکی ڈیٹا]]')
                else
                    table.insert(p31_links, label)
                end
            end
        end
    end

    -- Render P31 row only if we actually have links
    if #p31_links > 0 then
        dataTable:tag('tr'):tag('td')
             :attr('colspan', 2)
             :css({
                 ['background-color'] = themeColor,
                 ['font-weight'] = 'bold',
                 ['text-align'] = 'center',
                 ['color'] = args.titletextcolor or themeTextColor,
                 ['border-top'] = args.headerborder or 'none',
                 ['border-bottom'] = args.headerborder or 'none',
                 ['padding'] = '0.2em'
             })
             :wikitext(table.concat(p31_links, '، '))
    end

    local properties = mw.wikibase.orderProperties(item:getProperties())
    local property_blacklist_hash = valuesToKeys(mergeTables(property_blacklist, hidden_properties))
    local property_whitelist_hash = valuesToKeys(property_whitelist)
    local properties_with_parents_hash = valuesToKeys(properties_with_parents)
    local properties_with_children_hash = valuesToKeys(properties_with_children)

    property_blacklist_hash['P31'] = true
    property_blacklist_hash['P18'] = true
    
    local edit_message = mw.message.new('vector-view-edit'):plain()
    
    -- Birth/Death Logic
    local date_of_birth = getBestStatement(item, 'P569')
    local date_of_death = getBestStatement(item, 'P570')
    local instance_of = getBestStatement(item, 'P31')
    local place_of_birth = getBestStatement(item, 'P19')
    local place_of_death = getBestStatement(item, 'P20')
    local countryid = ' '
    
    pcall(function () 
        countryid = item.claims['P17'][1].mainsnak.datavalue.value.id 
    end)

    if instance_of and instance_of.id == 'Q5' and date_of_birth and date_of_birth.time:sub(1, 1) ~= '-' then 
        local birth = getBirthStatement(lang, date_of_birth, date_of_death, place_of_birth)
        
        dataTable:tag('tr')
                :tag('th')
                    :attr('scope', 'row')
                    :css({
                        ['padding-top'] = '0.225em',
                        ['line-height'] = '1.1em',
                        ['padding-right'] = '0.65em'
                    })
                    :wikitext('پٲدٲیِش'):done()
                :tag('td')
                    :css({ ['line-height'] = '1.4em', ['max-width'] = '180px' })
                    :wikitext(frame:preprocess(birth))
                    
        if date_of_death then
            local death = getDeathStatement(lang, date_of_birth, date_of_death, place_of_death)
            dataTable:tag('tr')
                :tag('th')
                    :attr('scope', 'row')
                    :css({
                        ['padding-top'] = '0.225em',
                        ['line-height'] = '1.1em',
                        ['padding-right'] = '0.65em'
                    })
                    :wikitext('وَفات'):done()
                :tag('td')
                    :css({ ['line-height'] = '1.4em', ['max-width'] = '180px' })
                    :wikitext(frame:preprocess(death))
        end
    end

    local merged_chronology = false -- flag for P155/P156

    for _, property in pairs(properties) do
        -- Skip rendering P156 separately if we merged it in P155 loop
        local skip_this_property = false
        if merged_chronology and property == 'P156' then
            skip_this_property = true
        end

        if not skip_this_property then
            -- Safely get datatype
            local claims = item.claims[property]
            local datatype = 'unknown'
            if claims and claims[1] and claims[1].mainsnak and claims[1].mainsnak.datatype then
                datatype = claims[1].mainsnak.datatype
            end
    
            local english_label = mw.wikibase.getLabelByLang(property, 'en')
            english_label = english_label and english_label:upper() or property
            local kashmiri_label = mw.wikibase.getLabelByLang(property, 'ks')
    
            if property == 'P1082' or property == 'P1120' or property == 'P1339' or property == 'P2046' or property == 'P2044' then
                  datatype = 'number'
            end
        
            overriden_value = overriden_properties[english_label] or overriden_properties[kashmiri_label]
    
            local shouldShow = false
            if property_whitelist_hash[property] then
                shouldShow = true
            elseif datatype ~= 'commonsMedia' and datatype ~= 'external-id' 
				and datatype ~= 'wikibase-property' and
               datatype ~= 'geo-shape' and datatype ~= 'tabular-data' and
               (not property_blacklist_hash[property] and not property_blacklist_hash[english_label] and not property_blacklist_hash[kashmiri_label]) then
                 shouldShow = true
            end
    
            local statements = item:getBestStatements(property)
            
            if shouldShow and (show_english_properties or kashmiri_label ~= nil or overriden_value) and #statements <= 30 then
                
                local propertyValue = item:formatStatements(property)
                -- RESTORED FEATURE: Link property label (P1629)
                -- Checks if the property (e.g., "Capital") has a subject item (e.g., "Capital City")
                -- and if that item has a Kashmiri article, it links the label.
                local propertyEntity = mw.wikibase.getEntity(property)
                if propertyEntity then
                    local propertySubjects = propertyEntity:getBestStatements('P1629') -- 'P1629 = subject item of this property'
                    if #propertySubjects == 1 
                       and propertyEntity['claims']['P1629'][1].mainsnak.snaktype == 'value' then
                        local subjectItemQid = propertyEntity['claims']['P1629'][1].mainsnak.datavalue.value.id
                        local articleSitelink = mw.wikibase.getSitelink(subjectItemQid)
                        if articleSitelink ~= nil then -- Property subject item has local article
                            propertyValue.label = '[[' .. articleSitelink .. '|' .. propertyValue.label .. ']]'  
                        end
                    end
                end

                local value = overriden_value
                
                -- Check if value is manually overriden
                if (value == nil) then
                    -- HIERARCHY FEATURE LOGIC
                    if #statements == 1 and levels >= 2 and (properties_with_parents_hash[property] or properties_with_children_hash[property]) then
                        value = propertyValue.value
                        
                        if property == 'P155' or property == 'P156' then -- follows or followed by
                            value = '• ' .. value
                        end     
    
                        local level = {}
                        
                        -- Show parent/child item if any:
                        if item['claims'][property][1].mainsnak.snaktype == 'value' then                    
                            level[1] = {}
                            level[1].value = item['claims'][property][1].mainsnak.datavalue.value
                            level[1].item = mw.wikibase.getEntity(level[1].value.id) 
                            if level[1].item then
                                level[1].statements = mw.wikibase.getBestStatements(level[1].item.id, property)
                            else
                                level[1].statements = {}
                            end
                        end
                        
                        if item['claims'][property][1].mainsnak.snaktype == 'value' 
                           and #level[1].statements == 1 and level[1].statements[1].mainsnak.datavalue then
                            level[1].qid = level[1].statements[1].mainsnak.datavalue.value.id
                            if level[1].qid ~= countryid then -- do not repeat country
                                level[1].propertyValue = level[1].item:formatStatements(property)
                                if level[1].propertyValue then
                                    -- Show multi-level list as bulleted list:
                                    if not (property == 'P155' or property == 'P156') then
                                        value = '• ' .. value
                                    end                         
                                    
                                    if properties_with_children_hash[property] then -- next lower level / child item
                                        if property == 'P156' then 
                                            value = value .. '<br/>• ' .. level[1].propertyValue.value
                                        else 
                                            value = value .. '<br/>' .. '&nbsp;• ' .. level[1].propertyValue.value
                                        end
                                        
                                    else -- next higher level / parent item
                                        if property == 'P155' then 
                                            value = '• ' .. level[1].propertyValue.value .. '<br/>' .. value 
                                        else 
                                            value = '• ' .. level[1].propertyValue.value .. '<br/>&nbsp;' .. value 
                                        end
                                    end
                            
                                    local lc = 2 -- level counter
                                    while lc < levels and level[lc-1].item and level[lc-1].item.claims[property] and level[lc-1].item.claims[property][1] and level[lc-1].item.claims[property][1].mainsnak.datavalue do 
                                        level[lc]={}
                                        level[lc].value = level[lc-1].item.claims[property][1].mainsnak.datavalue.value 
                                        level[lc].item = mw.wikibase.getEntity(level[lc].value.id) 
                                        if not level[lc].item then break end
                                        level[lc].statements = mw.wikibase.getBestStatements(level[lc].item.id, property)  
                                         
                                        if #level[lc].statements > 0 and level[lc].statements[1].mainsnak.datavalue then 
                                            level[lc].qid = level[lc].statements[1].mainsnak.datavalue.value.id
                                            if #level[lc].statements == 1 and level[lc].qid ~= countryid then
                                                level[lc].propertyValue = level[lc].item:formatStatements(property) 
                                                if properties_with_children_hash[property] then 
                                                    if property == 'P156' then 
                                                        value = value .. '<br/>' .. '• ' .. level[lc].propertyValue.value
                                                    else 
                                                        value = value .. '<br/>' .. string.rep('&nbsp;', lc) .. '• ' .. level[lc].propertyValue.value
                                                    end
                                                else 
                                                    if property == 'P155' then 
                                                        value = '• ' .. level[lc].propertyValue.value .. '<br/>' .. value
                                                    else 
                                                        value = '• ' .. level[lc].propertyValue.value .. '<br/>&nbsp;' .. value:gsub('<br/>', '<br/>&nbsp;')
                                                    end
                                                end
                                                lc = lc+1
                                            else
                                                break
                                            end
                                        else
                                            break
                                        end
                                    end -- while lc
                                end
                            end
                        end
                          if property == 'P155' then -- P155 (follows / Chronology)
    local visited = {}
    visited[item.id] = true

    -- 1. Fetch previous items (P155 chain) up to 2 steps back
    local prev_items = {}
    local curr_qid = item.id
    
    for step = 1, 2 do
        local st = mw.wikibase.getBestStatements(curr_qid, 'P155')
        if st and st[1] and st[1].mainsnak and st[1].mainsnak.datavalue and st[1].mainsnak.datavalue.value and st[1].mainsnak.datavalue.value.id then
            local prev_id = st[1].mainsnak.datavalue.value.id
            if prev_id == countryid or visited[prev_id] then break end
            table.insert(prev_items, prev_id)
            visited[prev_id] = true
            curr_qid = prev_id
        else
            break
        end
    end

    value = ""

    -- 2. Print previous items in CHRONOLOGICAL order (oldest first)
    if #prev_items > 0 then
        for i = #prev_items, 1, -1 do
            local id = prev_items[i]
            local label = mw.wikibase.getLabel(id) or mw.wikibase.getLabelByLang(id, 'en') or id
            local sitelink = mw.wikibase.getSitelink(id)
            local en_sitelink = mw.wikibase.getSitelink(id, 'enwiki')

            if sitelink then
                value = value .. "<p>• [[" .. sitelink .. "|" .. label .. "]]"
            elseif en_sitelink then
                local desc = getDescription(id)
                value = value .. '<p>• <span title="' .. desc .. '">' .. label .. '</span> [[File:Noun Project label icon 1116097 cc mirror.svg|12px|link=d:' .. id .. '|title=وِکی ڈیٹا]]'
            else
                value = value .. "<p>• " .. label
            end
        end
    end

    -- 3. Add Current Item (Bold)
    value = value .. "<p>• \'\'\'" .. (item:getLabel() or mw.title.getCurrentTitle().text) .. "\'\'\'"

    -- 4. Fetch next items (P156 chain) up to 2 steps forward
    local next_items = {}
    curr_qid = item.id

    for step = 1, 2 do
        local st = mw.wikibase.getBestStatements(curr_qid, 'P156')
        if st and st[1] and st[1].mainsnak and st[1].mainsnak.datavalue and st[1].mainsnak.datavalue.value and st[1].mainsnak.datavalue.value.id then
            local next_id = st[1].mainsnak.datavalue.value.id
            -- Stop if country ID or if item was already visited above
            if next_id == countryid or visited[next_id] then break end
            table.insert(next_items, next_id)
            visited[next_id] = true
            curr_qid = next_id
        else
            break
        end
    end

    if #next_items > 0 then
        for _, id in ipairs(next_items) do
            local label = mw.wikibase.getLabel(id) or mw.wikibase.getLabelByLang(id, 'en') or id
            local sitelink = mw.wikibase.getSitelink(id)
            local en_sitelink = mw.wikibase.getSitelink(id, 'enwiki')

            if sitelink then
                value = value .. "<p>• [[" .. sitelink .. "|" .. label .. "]]"
            elseif en_sitelink then
                local desc = getDescription(id)
                value = value .. '<p>• <span title="' .. desc .. '">' .. label .. '</span> [[File:Noun Project label icon 1116097 cc mirror.svg|12px|link=d:' .. id .. '|title=وِکی ڈیٹا]]'
            else
                value = value .. "<p>• " .. label
            end
        end
    end

    propertyValue.label = 'کال زان' -- Chronology
    merged_chronology = true
end    
                        -- END HIERARCHY LOGIC
                    else
                         -- Standard processing for non-hierarchical items
                        if datatype == 'time' then
                            local statementObject = getBestStatement(item, property)
                            if statementObject and statementObject.time then
                                local dateString = statementObject.time
                                if property == 'P1317' or property == 'P2031' then 
                                    value = formatDate(lang, dateString, propertyValue.value, 'Y')
                                else
                                    value = formatDate(lang, dateString, propertyValue.value)
                                end
                            else
                                value = '' 
                            end
                        elseif datatype == 'wikibase-item' then
                            -- Custom formatting to add Tooltip, Icon, and Date Qualifiers
                            local vals = {}
                            local has_qualifier = false
                            
                            for _, stmt in ipairs(statements) do
                                if stmt.mainsnak.snaktype == 'value' and stmt.mainsnak.datavalue and stmt.mainsnak.datavalue.value.id then
                                    local id = stmt.mainsnak.datavalue.value.id
                                    local label = mw.wikibase.getLabel(id) or mw.wikibase.getLabelByLang(id, 'en') or id
                                    local sitelink = mw.wikibase.getSitelink(id)
                                    local en_sitelink = mw.wikibase.getSitelink(id, 'enwiki')
                                    
                                    local item_str = ""
                                    if sitelink then
                                        item_str = '[[' .. sitelink .. '|' .. label .. ']]'
                                    elseif en_sitelink then
                                        local desc = getDescription(id)
                                        item_str = '<span title="' .. desc .. '">' .. label .. '</span> [[File:Noun Project label icon 1116097 cc mirror.svg|12px|link=d:' .. id .. '|title=وِکی ڈیٹا]]'
                                    else
                                        item_str = label
                                    end

                                                                        -- Extract Qualifiers using the new sub-module
                                    local formatted_str = qualifiersHelper.formatStatement(stmt, item_str, toKashmirinumbers)
                                    
                                    -- If the sub-module added qualifiers, update the string and trigger the line-break flag
                                    if formatted_str ~= item_str then
                                        -- Optional: wrap in <small> tags to make qualifiers slightly smaller than main text
                                        item_str = item_str .. ' <small>' .. formatted_str:sub(#item_str + 1) .. '</small>'
                                        has_qualifier = true
                                    end

                                    table.insert(vals, item_str)
                                end
                            end

                            -- Switch format: Unbulleted list (<br/>) if qualifiers exist, comma-separated (، ) if not
                            if has_qualifier then
                                value = table.concat(vals, '<br/>')
                            else
                                value = table.concat(vals, '، ')
                            end

                            if value == '' then value = propertyValue.value end
                                                else
                            if statements[1] then
                                local formatted_str = qualifiersHelper.formatStatement(statements[1], propertyValue.value, toKashmirinumbers)
                                if formatted_str ~= propertyValue.value then
                                    -- Optional: wrap the appended qualifiers in <small> tags
                                    value = propertyValue.value .. ' <small>' .. formatted_str:sub(#propertyValue.value + 1) .. '</small>'
                                else
                                    value = propertyValue.value
                                end
                                                        else
                                value = propertyValue.value
                            end
                        end
                    end

                elseif (value:find("^[Q]%d+") ~= nil) then 
                    value = '[[' .. mw.wikibase.getSitelink(value) .. ']]'
                end
                
                if (datatype == 'time' or datatype == 'number' or datatype == 'quantity') then
                    value = toKashmirinumbers(value)
                end
                
                -- COLLAPSIBLE LIST LOGIC FOR > 10 ITEMS (NO HEADER)
                if #statements > 10 then
                    value = '<div class="mw-collapsible mw-collapsed" id="mw-custom-collapsible-' .. property .. '" style="border:none; background:transparent;">' ..
                            '<div class="mw-collapsible-content">' .. value .. '</div></div>'
                end
                
                if not isEmpty(value) then
                    dataTable:tag('tr')
                        :tag('th')
                            :attr('scope', 'row')
                            :css({
                                ['padding-top'] = '0.225em',
                                ['line-height'] = '1.1em',
                                ['padding-right'] = '0.65em',
                                ['vertical-align'] = 'text-top'
                            })
                            :wikitext(lang:ucfirst(propertyValue.label)):done()
                        :tag('td')
                            :css({ 
                                ['line-height'] = '1.4em', 
                                ['max-width'] = '180px',
                                ['vertical-align'] = 'text-top'
                             })
                            :wikitext(frame:preprocess(value))
                end
            end
        end -- end skip_this_property check
    end
    -- MANUAL ROWS AND CUSTOM HEADERS
    for i = 1, 10 do
        
        local label_param = 'label' .. i
        local data_param = 'data' .. i
        local header_param = 'customheader' .. i
        
        -- Support for 3 columns
        local data_a_param = 'data' .. i .. 'a'
        local data_b_param = 'data' .. i .. 'b'
        local data_c_param = 'data' .. i .. 'c'
        
        local label_value = args[label_param]
        local data_value = args[data_param]
        local header_value = args[header_param]
        
        local data_a_value = args[data_a_param]
        local data_b_value = args[data_b_param]
        local data_c_value = args[data_c_param]
        
        -- STRICT CHECKS: Make sure the parameters aren't just empty strings
        local has_label = label_value and #label_value > 0
        local has_data = data_value and #data_value > 0
        local has_data_a = data_a_value and #data_a_value > 0
        local has_data_b = data_b_value and #data_b_value > 0
        local has_data_c = data_c_value and #data_c_value > 0
        
        -- Custom Header Row
        if header_value and #header_value > 0 then
            dataTable:tag('tr')
                :tag('th')
                    :attr('colspan', '2')
                    :css({
                        ['text-align'] = 'center',
                        ['font-weight'] = 'bold',
                        ['background-color'] = themeColor,
                        ['color'] = args.titletextcolor or '#000000',
                        ['border-top'] = args.headerborder or 'none',
                        ['border-bottom'] = args.headerborder or 'none',
                        ['padding'] = '0.2em'
                    })
                    :wikitext(frame:preprocess(header_value))
                    :done()
                :done()
        end
        
        -- Data Row
        if has_data or has_data_a or has_data_b or has_data_c then
            local row = dataTable:tag('tr')
            
            -- NEW Case 1: Dynamic Multi-Column layout (1, 2, or 3 columns)
            if has_data_a or has_data_b or has_data_c then
                
                local cell
                if has_label then
                    row:tag('th')
                        :attr('scope', 'row')
                        :css({
                            ['padding-top'] = '0.225em',
                            ['line-height'] = '1.1em',
                            ['padding-right'] = '0.65em'
                        })
                        :wikitext(label_value)
                        :done()
                    cell = row:tag('td')
                else
                    cell = row:tag('td'):attr('colspan', '2')
                end
                
                local innerTable = cell:tag('table')
                    :css({
                        ['width'] = '100%',
                        ['table-layout'] = 'fixed',
                        ['text-align'] = 'center',
                        ['background'] = 'transparent',
                        ['border-collapse'] = 'collapse',
                        ['line-height'] = '1.4em'
                    })
                local innerRow = innerTable:tag('tr'):css('vertical-align', 'top')
                
                -- Count how many columns actually exist to calculate perfect width
                local active_cols = 0
                if has_data_a then active_cols = active_cols + 1 end
                if has_data_b then active_cols = active_cols + 1 end
                if has_data_c then active_cols = active_cols + 1 end
                
                local col_width = (100 / active_cols) .. '%'
                local printed_cols = 0
                
                -- Column A
                if has_data_a then
                    innerRow:tag('td')
                        :css({ ['width'] = col_width, ['padding'] = '0.2em' })
                        :wikitext(frame:preprocess(data_a_value))
                        :done()
                    printed_cols = printed_cols + 1
                end
                    
                -- Column B 
                if has_data_b then
                    local css_b = { ['width'] = col_width, ['padding'] = '0.2em' }
                    if printed_cols > 0 then css_b['border-left'] = '1px solid #aaa' end
                    
                    innerRow:tag('td')
                        :css(css_b)
                        :wikitext(frame:preprocess(data_b_value))
                        :done()
                    printed_cols = printed_cols + 1
                end
                    
                -- Column C
                if has_data_c then
                    local css_c = { ['width'] = col_width, ['padding'] = '0.2em' }
                    if printed_cols > 0 then css_c['border-left'] = '1px solid #aaa' end
                    
                    innerRow:tag('td')
                        :css(css_c)
                        :wikitext(frame:preprocess(data_c_value))
                        :done()
                end

            -- Original Case 2: Both standard label and standard data exist
            elseif has_label then
                row:tag('th')
                    :attr('scope', 'row')
                    :css({
                        ['padding-top'] = '0.225em',
                        ['line-height'] = '1.1em',
                        ['padding-right'] = '0.65em'
                    })
                    :wikitext(label_value)
                    :done()
                row:tag('td')
                    :css({ 
                        ['line-height'] = '1.4em', 
                        ['max-width'] = '180px' 
                    })
                    :wikitext(frame:preprocess(data_value))
                    :done()
            
           -- Original Case 3: Only standard data exists
            else
                row:tag('td')
                    :attr('colspan', '2')
                    :css({ 
                        ['line-height'] = '1.6em',   
                        ['text-align'] = 'center',
                        ['padding'] = '0.5em 0.3em', 
                        ['font-size'] = '100%'
                    })
                    :wikitext(frame:preprocess(data_value))
                    :done()
            end
            row:done()
        end
    end
    -- Standalone Module at the bottom (Preserved exactly as requested)
    local standalone_module = args['module'] or args['ماڈیول']
    if standalone_module and #standalone_module > 0 then
        dataTable:tag('tr')
            :tag('td')
                :attr('colspan', '2')
                :css({ 
                    ['padding'] = '0',
                    ['text-align'] = 'center',
                    ['width'] = '100%'
                })
                :wikitext(frame:preprocess(standalone_module))
                :done()
        :done()
    end

    -- Map
    local coordinates_statements = item:getBestStatements('P625')
    if #coordinates_statements >= 1 and coordinates_statements[1].mainsnak.datavalue and coordinates_statements[1].mainsnak.datavalue.value.globe == 'http://www.wikidata.org/entity/Q2' then
        local latitude = coordinates_statements[1].mainsnak.datavalue.value.latitude
        local longitude = coordinates_statements[1].mainsnak.datavalue.value.longitude
        local geojson = {
            type = 'Feature',
            geometry = {
                type = 'Point',
                coordinates = { longitude, latitude }
            },
            properties = {
                title = item:getLabel() or mw.title.getCurrentTitle().text,
                ['marker-symbol'] = 'marker',
                ['marker-color'] =  '#224422',
            }
        }
        
        dataTable:tag('tr'):tag('td')
            :attr('colspan', 2)
            :css({ ['text-align'] = 'center'})
            :wikitext(frame:extensionTag('mapframe', mw.text.jsonEncode(geojson), {
            height = 250,
            width = 250,
            frameless = 'frameless',
            align = 'center',
            latitude = latitude,
            longitude = longitude,
            zoom = 6
        }))
    end

    -- EXTRA IMAGES (via sub-module)
    --
    local successExtra, extraImagesModule = pcall(require, 'Module:Databox/extra images')
    if successExtra and type(extraImagesModule) == 'table' and extraImagesModule.renderImages then
        extraImagesModule.renderImages(dataTable, item, themeColor, args.titletextcolor or themeTextColor, args.headerborder)
    end
    -- 
    local div_start = '<div style="border-style: solid; border-color:gray; border-width: 1px 0 0 0; margin-top: 2em; text-align: center;">'
    local pen_icon = '&nbsp;[[File:Wikidata-logo.svg|' .. edit_message .. '|22px|baseline|class=noviewer|link=https://www.wikidata.org/wiki/' .. item.id .. ']]'
    local edit_message_link = '[https://www.wikidata.org/wiki/' .. item.id .. ' وِکی ڈیٹا پؠٹھ کٔرِو اؠڈِٹ]'
    dataTable:tag('tr'):tag('td')
            :attr('colspan', 2)
            :css({ ['text-align'] = 'center'})
            :wikitext(div_start .. edit_message_link .. pen_icon .. '</div>')
     
     return tostring(dataTable)
end

return p