--
-- This module implements {{بطاقة}}
--
 
local p = {}

local navbar = require('Module:Navbar')._navbar

local args = {}
local origArgs
local root

local function notempty( s ) return s and s:match( '%S' ) end

local function fixChildBoxes(sval, tt)
	if notempty(sval) then
		local marker = '<span class=special_infobox_marker>'
		local s = sval
		s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1')
		s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker)
		if s:match(marker) then
			s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '')
			s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1')
			s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1')
			s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s,  marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1')
			s = mw.ustring.gsub(s,  marker .. '(%s*\n|%})', '%1')
		end
		if s:match(marker) then
			local subcells = mw.text.split(s, marker)
			s = ''
			for k = 1, #subcells do
				if k == 1 then
					s = s .. subcells[k] .. '</' .. tt .. '></tr>'
				elseif k == #subcells then
					local rowstyle = ' style="display:none"'
					if notempty(subcells[k]) then rowstyle = ''	end
					s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k]
				elseif notempty(subcells[k]) then
					if (k % 2) == 0 then
						s = s .. subcells[k]
					else
						s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>'
					end
				end
			end
		end
		-- the next two lines add a newline at the end of lists for the PHP parser
		-- https://en.wiki.x.io/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481
		-- remove when [[:phab:T191516]] is fixed or OBE
		s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n')
		s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n')
		s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1')
		s = mw.ustring.gsub(s, '^(%{%|)', '\n%1')
		return s
	else
		return sval
	end
end

local function union(t1, t2)
    -- Returns the union of the values of two tables, as a sequence.
    local vals = {}
    for k, v in pairs(t1) do
        vals[v] = true
    end
    for k, v in pairs(t2) do
        vals[v] = true
    end
    local ret = {}
    for k, v in pairs(vals) do
        table.insert(ret, k)
    end
    return ret
end

local function getArgNums(prefix)
    -- Returns a table containing the numbers of the arguments that exist
    -- for the specified prefix. For example, if the prefix was 'data', and
    -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}.
    local nums = {}
    for k, v in pairs(args) do
        local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$')
        if num then table.insert(nums, tonumber(num)) end
    end
    table.sort(nums)
    return nums
end

local function addRow(rowArgs)
    -- Adds a row to the infobox, with either a header cell
    -- or a label/data cell combination.
    if rowArgs.header and rowArgs.header ~= '_BLANK_' then
        root
            :tag('tr')
                :addClass(rowArgs.rowclass)
                :cssText(rowArgs.rowstyle)
                :attr('id', rowArgs.rowid)
                :tag('th')
                    :attr('colspan', 2)
                    :attr('id', rowArgs.headerid)
                    :addClass(rowArgs.class)
                    :addClass(args.headerclass)
                    :css('text-align', 'center')
                    :cssText(args.headerstyle)
                    :cssText(rowArgs.rowcellstyle)
                    :wikitext(fixChildBoxes(rowArgs.header, 'th'))
        if rowArgs.data then
			root:wikitext('[[تصنيف:Pages which use infobox templates with ignored data cells]]')
		end
    elseif rowArgs.data then
    	if not rowArgs.data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then
			rowArgs.rowstyle = 'display:none'
		end
        local row = root:tag('tr')
        row:addClass(rowArgs.rowclass)
        row:cssText(rowArgs.rowstyle)
        row:attr('id', rowArgs.rowid)
        if rowArgs.label then
            row
                :tag('th')
                    :attr('scope', 'row')
                    :attr('id', rowArgs.labelid)
                    :cssText(args.labelstyle)
                    :cssText(rowArgs.rowcellstyle)
                    :wikitext(rowArgs.label)
                    :done()
        end
        
        local dataCell = row:tag('td')
        if not rowArgs.label then 
            dataCell
                :attr('colspan', 2)
                :css('text-align', 'center') 
        end
        dataCell
            :attr('id', rowArgs.dataid)
            :addClass(rowArgs.class)
            :cssText(rowArgs.datastyle)
            :cssText(rowArgs.rowcellstyle)
            :wikitext(fixChildBoxes(rowArgs.data, 'td'))
    end
end

local function renderTitle()
    if not args.title then return end

    root
        :tag('caption')
            :addClass(args.titleclass)
            :cssText(args.titlestyle)
            :wikitext(args.title)
end

local function renderAboveRow()
    if not args.above then return end
    
    root
        :tag('tr')
            :tag('th')
                :attr('colspan', 2)
                :addClass(args.aboveclass)
                :css('text-align', 'center')
                :css('font-size', '125%')
                :css('font-weight', 'bold')
                :cssText(args.abovestyle)
                :wikitext(fixChildBoxes(args.above,'th'))
end

local function renderBelowRow()
    if not args.below then return end
    
    root
        :tag('tr')
            :tag('td')
                :attr('colspan', '2')
                :addClass(args.belowclass)
                :css('text-align', 'center')
                :cssText(args.belowstyle)
                :wikitext(fixChildBoxes(args.below,'td'))
end

local function renderSubheaders()
    if args.subheader then
        args.subheader1 = args.subheader
    end
    if args.subheaderrowclass then
        args.subheaderrowclass1 = args.subheaderrowclass
    end
    local subheadernums = getArgNums('subheader')
    for k, num in ipairs(subheadernums) do
        addRow({
            data = args['subheader' .. tostring(num)],
            datastyle = args.subheaderstyle,
            rowcellstyle = args['subheaderstyle' .. tostring(num)],
            class = args.subheaderclass,
            rowclass = args['subheaderrowclass' .. tostring(num)]
        })
    end
end

local function renderImages()
    -- إذا كانت هناك صورة، قم بتعيين الصورة الأولى
    if args.image or args['صورة'] then
        args.image1 = args.image or args['صورة']
    end
    -- إذا كان هناك تعليق، قم بتعيين التعليق الأول
    if args.caption or args['تعليق'] then
        args.caption1 = args.caption or args['تعليق']
    end
    
    -- الحصول على أرقام الصور
    local imagenums = getArgNums('image') or getArgNums('صورة')
    for k, num in ipairs(imagenums) do
        -- الحصول على تعليق الصورة
        local caption = args['caption' .. tostring(num)] or args['تعليق' .. tostring(num)]
        -- إنشاء عنصر HTML للصورة
        local data = mw.html.create():wikitext(args['image' .. tostring(num)] or args['صورة' .. tostring(num)])
        
        -- إضافة التعليق إلى الصورة إذا كان موجوداً
        if caption then
            data
                :tag('div')
                    :cssText(args.captionstyle or args['نمط التعليق']) -- تطبيق نمط التعليق
                    :wikitext(caption)                             -- تعيين نص التعليق
        end
        
        -- إضافة السطر إلى العرض
        addRow({
            data = tostring(data),
            datastyle = args.imagestyle or args['نمط الصورة'],   -- تطبيق نمط الصورة
            class = args.imageclass or args['فئة الصورة'],       -- تعيين فئة الصورة
            rowclass = args['imagerowclass' .. tostring(num)] or args['فئة صف الصورة' .. tostring(num)] -- تعيين فئة صف الصورة
        })
    end
end


local function preprocessRows()
    -- الحصول على الاتحاد بين أرقام المعاملات "header" و "data"،
    -- ثم يتم عرضهم جميعًا باستخدام addRow.
    local rownums = union(getArgNums('header', 'رأس'), getArgNums('data', 'بيان'))
    table.sort(rownums)
	local lastheader
	for k, num in ipairs(rownums) do
		if args['header' .. tostring(num)] or args['رأس' .. tostring(num)] then
			if lastheader then
				args['header' .. tostring(lastheader)] = nil
				args['رأس' .. tostring(lastheader)] = nil
			end
			lastheader = num
		elseif (args['data' .. tostring(num)] or args['بيان' .. tostring(num)]) and
			(args['data' .. tostring(num)] or args['بيان' .. tostring(num)]):gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then
			local data = args['data' .. tostring(num)] or args['بيان' .. tostring(num)]
			if data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('%S') then
				lastheader = nil
			end
		end
	end
	if lastheader then
		args['header' .. tostring(lastheader)] = nil
		args['رأس' .. tostring(lastheader)] = nil
	end
end


local function renderRows()
	-- الحصول على الاتحاد بين أرقام المعاملات "header" و "data"،
	-- ثم يتم عرضهم جميعًا بترتيب باستخدام addRow.
	local rownums = union(getArgNums('header', 'رأس'), getArgNums('data', 'بيان'))
	table.sort(rownums)
    for k, num in ipairs(rownums) do
        addRow({
            header = args['header' .. tostring(num)] or args['رأس' .. tostring(num)],  -- رأس
            label = args['label' .. tostring(num)] or args['تسمية' .. tostring(num)],  -- تسمية
            data = args['data' .. tostring(num)] or args['بيان' .. tostring(num)],    -- بيان
            datastyle = args.datastyle or args['نمط_البيان'],                         -- نمط البيانات
            class = args['class' .. tostring(num)] or args['فئة' .. tostring(num)],    -- فئة
            rowclass = args['rowclass' .. tostring(num)] or args['فئة_الصف' .. tostring(num)], -- فئة الصف
            rowstyle = args['rowstyle' .. tostring(num)] or args['نمط_الصف' .. tostring(num)], -- نمط الصف
            rowcellstyle = args['rowcellstyle' .. tostring(num)] or args['نمط_خلية_الصف' .. tostring(num)], -- نمط خلية الصف
            dataid = args['dataid' .. tostring(num)] or args['معرف_البيان' .. tostring(num)], -- معرف البيان
            labelid = args['labelid' .. tostring(num)] or args['معرف_التسمية' .. tostring(num)], -- معرف التسمية
            headerid = args['headerid' .. tostring(num)] or args['معرف_الرأس' .. tostring(num)], -- معرف الرأس
            rowid = args['rowid' .. tostring(num)] or args['معرف_الصف' .. tostring(num)] -- معرف الصف
        })
    end
end


local function renderNavBar()
    if not args.name then return end
    
    root
        :tag('tr')
            :tag('td')
                :attr('colspan', '2')
                :css('text-align', 'right')
                :wikitext(navbar{
                    args.name,
                    mini = 1,
                })
end

local function renderItalicTitle()
    local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title'])
    if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then
        root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'}))
    end
end

local function renderTrackingCategories()
    if args.decat ~= 'yes' then
        if args.child == 'yes' then
			if args.title then
				root:wikitext('[[تصنيف:Pages which use embedded infobox templates with the title parameter]]')
			end
        elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then
            root:wikitext('[[تصنيف:مقالات تستخدم صناديق معلومات بدون صفوف بيانات]]')
        end
    end
    if(mw.title.getCurrentTitle().namespace == 0) then
        root:wikitext('[[تصنيف:مقالات تستعمل قوالب معلومات]]')
    end
end

local function loadTemplateStyles()
	local frame = mw.getCurrentFrame()
	
	-- See function description
	local base_templatestyles = ''
	-- local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } }

	local templatestyles = ''
	if args['templatestyles'] then 
		templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = args['templatestyles'] }
		}
	end
	
	local child_templatestyles = ''
	if args['child templatestyles'] then 
		child_templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = args['child templatestyles'] }
		}
	end
	
	local grandchild_templatestyles = ''
	if args['grandchild templatestyles'] then 
		grandchild_templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = args['grandchild templatestyles'] }
		}
	end

	return table.concat({
		base_templatestyles, -- see function description
		templatestyles,
		child_templatestyles,
		grandchild_templatestyles
	})
end

local function _infobox()
    -- Specify the overall layout of the infobox, with special settings
    -- if the infobox is used as a 'child' inside another infobox.
    if args.child ~= 'yes' then
        root = mw.html.create('table')
        
        root
            :addClass((args.subbox ~= 'yes') and 'infobox' or nil)
            :addClass(args.bodyclass)
            
            if args.subbox == 'yes' then
                root
                    :css('padding', '0')
                    :css('border', 'none')
                    :css('margin', '-3px')
                    :css('width', 'auto')
                    :css('min-width', '100%')
                    :css('font-size', '100%')
                    :css('clear', 'none')
                    :css('float', 'none')
                    :css('background-color', 'transparent')
            else
                root
                    :css('width', '22em')
            end
        root
            :cssText(args.bodystyle)
    
        renderTitle()
        renderAboveRow()
    else
        root = mw.html.create()
        
        root
            :wikitext(args.title)
    end

    renderSubheaders()
    renderImages()
    if args.autoheaders then
		preprocessRows()
	end
    renderRows() 
    renderBelowRow()  
    renderNavBar()
    renderItalicTitle()
    renderTrackingCategories()
    
    return loadTemplateStyles() .. tostring(root)
end

local function preprocessSingleArg(argName)
    -- If the argument exists and isn't blank, add it to the argument table.
    -- Blank arguments are treated as nil to match the behaviour of ParserFunctions.
    if origArgs[argName] and origArgs[argName] ~= '' then
        args[argName] = origArgs[argName]
    end
end

local function preprocessArgs(prefixTable, step)
    -- Assign the parameters with the given prefixes to the args table, in order, in batches
    -- of the step size specified. This is to prevent references etc. from appearing in the
    -- wrong order. The prefixTable should be an array containing tables, each of which has
    -- two possible fields, a "prefix" string and a "depend" table. The function always parses
    -- parameters containing the "prefix" string, but only parses parameters in the "depend"
    -- table if the prefix parameter is present and non-blank.
    if type(prefixTable) ~= 'table' then
        error("Non-table value detected for the prefix table", 2)
    end
    if type(step) ~= 'number' then
        error("Invalid step value detected", 2)
    end
    
    -- Get arguments without a number suffix, and check for bad input.
    for i,v in ipairs(prefixTable) do
        if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then
            error('Invalid input detected to preprocessArgs prefix table', 2)
        end
        preprocessSingleArg(v.prefix)
        -- Only parse the depend parameter if the prefix parameter is present and not blank.
        if args[v.prefix] and v.depend then
            for j, dependValue in ipairs(v.depend) do
                if type(dependValue) ~= 'string' then
                    error('Invalid "depend" parameter value detected in preprocessArgs')
                end
                preprocessSingleArg(dependValue)
            end
        end
    end

    -- Get arguments with number suffixes.
    local a = 1 -- Counter variable.
    local moreArgumentsExist = true
    while moreArgumentsExist == true do
        moreArgumentsExist = false
        for i = a, a + step - 1 do
            for j,v in ipairs(prefixTable) do
                local prefixArgName = v.prefix .. tostring(i)
                if origArgs[prefixArgName] then
                    moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones.
                    preprocessSingleArg(prefixArgName)
                end
                -- Process the depend table if the prefix argument is present and not blank, or
                -- we are processing "prefix1" and "prefix" is present and not blank, and
                -- if the depend table is present.
                if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then
                    for j,dependValue in ipairs(v.depend) do
                        local dependArgName = dependValue .. tostring(i)
                        preprocessSingleArg(dependArgName)
                    end
                end
            end
        end
        a = a + step
    end
end
 
local function parseDataParameters()
	-- تحليل معلمات البيانات بنفس الترتيب الذي كان يعتمده {{infobox}} القديم،
	-- حتى يتم عرض المراجع وما إلى ذلك في الأماكن المتوقعة.
	-- المعلمات التي تعتمد على معلمة أخرى يتم معالجتها فقط إذا كانت تلك المعلمة موجودة،
	-- لتجنب ظهور مراجع غير متوقعة في قوائم مراجع المقالات.
	
	preprocessSingleArg('autoheaders', 'رؤوس تلقائية')
	preprocessSingleArg('child', 'فرعي')
	preprocessSingleArg('bodyclass', 'فئة الجسم')
	preprocessSingleArg('subbox', 'صندوق فرعي')
	preprocessSingleArg('bodystyle', 'نمط الجسم')
	preprocessSingleArg('title', 'العنوان')
	preprocessSingleArg('titleclass', 'فئة العنوان')
	preprocessSingleArg('titlestyle', 'نمط العنوان')
	preprocessSingleArg('above', 'نص علوي')
	preprocessSingleArg('aboveclass', 'فئة النص العلوي')
	preprocessSingleArg('abovestyle', 'نمط النص العلوي')

	-- معالجة سلسلة من المعلمات مع اعتماد بعضها على الآخر
	preprocessArgs({
		{prefix = 'subheader', 'رأس فرعي', depend = {'subheaderstyle', 'نمط الرأس الفرعي', 'subheaderrowclass', 'فئة صف الرأس الفرعي'}}
	}, 10)

	preprocessSingleArg('subheaderstyle', 'نمط الرأس الفرعي')
	preprocessSingleArg('subheaderclass', 'فئة الرأس الفرعي')

	preprocessArgs({
		{prefix = 'image', 'صورة', depend = {'caption', 'تعليق', 'imagerowclass', 'فئة صف الصورة'}}
	}, 10)

	preprocessSingleArg('captionstyle', 'نمط التعليق')
	preprocessSingleArg('imagestyle', 'نمط الصورة')
	preprocessSingleArg('imageclass', 'فئة الصورة')

	preprocessArgs({
		{prefix = 'header', 'رأس'},            -- رؤوس
		{prefix = 'data', 'بيان', depend = {'label', 'تسمية'}},  -- بيانات وتعتمد على التسمية
		{prefix = 'rowclass', 'فئة الصف'},          -- فئة الصف
		{prefix = 'rowstyle', 'نمط الصف'},          -- نمط الصف
		{prefix = 'rowcellstyle', 'نمط خلية الصف'},      -- نمط خلية الصف
		{prefix = 'class', 'فئة'},             -- فئة
		{prefix = 'dataid', 'معرف البيان'},            -- معرف البيانات
		{prefix = 'labelid', 'معرف التسمية'},           -- معرف التسمية
		{prefix = 'headerid', 'معرف الرأس'},          -- معرف الرأس
		{prefix = 'rowid', 'معرف الصف'}              -- معرف الصف
	}, 50)

	preprocessSingleArg('headerclass', 'فئة الرأس')
	preprocessSingleArg('headerstyle', 'نمط الرأس')
	preprocessSingleArg('labelstyle', 'نمط التسمية')
	preprocessSingleArg('datastyle', 'نمط البيانات')
	preprocessSingleArg('below', 'تحت')
	preprocessSingleArg('belowclass', 'فئة تحت')
	preprocessSingleArg('belowstyle', 'نمط تحت')
	preprocessSingleArg('name', 'الاسم')
	
	-- معاملة خاصة لمعامل "العنوان المائل" (italic title)
	args['italic title'] = origArgs['italic title']

	preprocessSingleArg('decat', 'تصنيف معطل')
	preprocessSingleArg('templatestyles', 'أنماط القالب')
	preprocessSingleArg('child templatestyles', 'أنماط القالب الفرعي')
	preprocessSingleArg('grandchild templatestyles', 'أنماط القالب الفرعي الثاني')

end


function p.infobox(frame)
    -- If called via #invoke, use the args passed into the invoking template.
    -- Otherwise, for testing purposes, assume args are being passed directly in.
    if frame == mw.getCurrentFrame() then
        origArgs = frame:getParent().args
    else
        origArgs = frame
    end
    
    parseDataParameters()
	
	return _infobox()
end

function p.infoboxTemplate(frame)
	-- For calling via #invoke within a template
	origArgs = {}
	for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end
	
	parseDataParameters()
	
	return _infobox()
end
return p