local yesno = require('Module:Yesno')
local barBox = require('Module:Bar box')

local lang = mw.getContentLanguage()
local language = lang:getCode()
local i18n = require('Module:Medical cases chart/i18n')[language]
assert(i18n, '次の言語に翻訳された図表はありません: ' .. mw.language.fetchLanguageName(language, 'ja'))
local monthAbbrs = {}
for i = 1, 12 do
	monthAbbrs[i] = lang:formatDate('M', '2020-' .. ('%02d'):format(i))
end

local p = {}

function p._toggleButton(active, customtoggles, id, label)
	local on  = active and '' or ' mw-collapsed'
	local off = active and ' mw-collapsed' or ''
	local outString =
		'<span class="mw-collapsible' .. on  .. customtoggles .. '" id="mw-customcollapsible-' .. id .. '" ' ..
		'style="border:2px solid lightblue">' .. label .. '</span>' ..
		'<span class="mw-collapsible' .. off .. customtoggles .. '" id="mw-customcollapsible-' .. id .. '">' .. label  .. '</span>'
	return outString
end

function p._yearToggleButton(year)
	return p._toggleButton(year.l, ' mw-customtoggle-' .. year.year, year.year, year.year)
end

function p._monthToggleButton(year, month)
	local lmon, label = lang:lc(month.mon), month.mon
	local id = (year or '') .. lmon
	local customtoggles = ' mw-customtoggle-' .. id

	if month.s then
		label = label .. '&nbsp;' .. month.s -- "Mmm ##"
		if month.s ~= month.e then -- "Mmm ##–##"
			label = label .. '–' .. month.e
		end
	else
		customtoggles = customtoggles .. (month.l and customtoggles .. month.l or '')
	end

	for i, combination in ipairs(month.combinations) do
		customtoggles = customtoggles .. ' mw-customtoggle-' .. combination -- up to 2 combinations per month so no need to table.concat()
	end

	return p._toggleButton(false, customtoggles, id, label)
end

function p._lastXToggleButton(years, duration, combinationsL)
	local months, id = years[#years].months, 'l' .. duration
	local i, customtoggles = #months, {' mw-customtoggle-' .. id}

	if #years > 1 then
		local year = years[#years].year
		while months[i].l do
			customtoggles[#customtoggles+1] = ' mw-customtoggle-' .. year .. lang:lc(months[i].mon) .. '-' .. id
			if i == 1 then
				if year == years[#years].year then
					year = years[#years-1].year
					months = years[#years-1].months
					i = #months
				else -- either first month is also lastX month or lastX spans more than 2 years, which is not intended yet
					break
				end
			else
				i = i - 1
			end
		end
	else
		while i > 0 and months[i].l do
			customtoggles[#customtoggles+1] = ' mw-customtoggle-' .. lang:lc(months[i].mon) .. '-' .. id
			i = i - 1
		end
	end

	for i, combinationL in ipairs(combinationsL) do
		customtoggles[#customtoggles+1] = ' mw-customtoggle-' .. combinationL -- up to 3 combinationsL in 90 days
	end

	return p._toggleButton(true, table.concat(customtoggles), id, mw.ustring.format(i18n.lastXDays, duration))
end

function p._buildTogglesBar(dateList, duration, nooverlap)
	local years = {{year=dateList[1].year, months={{mon=dateList[1].mon, combinations={}}}}}
	local months, combinationsL = years[1].months, {}

	local function addMonth(month)
		if month.mon ~= months[#months].mon then -- new month
			if month.year ~= years[#years].year then -- new year
				years[#years+1] = {year=month.year, months={}}
				months = years[#years].months -- switch months list
			end
			months[#months+1] = {mon=month.mon, combinations={}}
		end
	end
	for i = 2, #dateList do -- deduplicate years and months
		if #dateList[i] == 0 then -- specific date
			addMonth(dateList[i])
			months[#months].l = months[#months].l or dateList[i].l -- so that both ...-mon and ...-mon-lX classes are created
		elseif #dateList[i] == 1 then -- interval within month
			addMonth(dateList[i][1])
			months[#months].l = months[#months].l or dateList[i].l
		else -- multimonth interval
			for j, month in ipairs(dateList[i]) do
				addMonth(month)
				months[#months].combinations[#months[#months].combinations+1] = dateList[i].id
			end
			combinationsL[#combinationsL+1] = dateList[i].id:find('-l%d+$') and dateList[i].id
		end
	end

	if nooverlap then
		local lastDate = dateList[#dateList]
		months[#months].e = tonumber(os.date('%d', lastDate.nDate or lastDate.nEndDate or lastDate.nAltEndDate)) -- end of final month

		local i = #dateList
		repeat
			i = i - 1
		until i == 0 or (dateList[i].mon or dateList[i][1].mon) ~= months[#months].mon
		if i == 0 then -- start of first and final month
			months[#months].s = tonumber(os.date('%d', dateList[1].nDate))
		else
			months[#months].s = 1
		end
	end

	years[#years].l = true -- to activate toggle and respective months bar

	local monthToggles, divs = {}, nil
	if #years > 1 then
		local yearToggles, monthsDivs = {}, {}
		for i, year in ipairs(years) do
			yearToggles[#yearToggles+1] = p._yearToggleButton(year)
			monthToggles = {}
			months = year.months
			for j, month in ipairs(months) do
				monthToggles[#monthToggles+1] = p._monthToggleButton(year.year, month)
			end
			monthsDivs[#monthsDivs+1] =
				'<div class="mw-collapsible' .. (year.l and '' or ' mw-collapsed') ..
				'" id="mw-customcollapsible-' .. year.year .. '">' .. table.concat(monthToggles) .. '</div>'
		end
		divs = '<div>' .. table.concat(yearToggles) .. '</div>' .. table.concat(monthsDivs)
	else
		for i, month in ipairs(months) do
			monthToggles[#monthToggles+1] = p._monthToggleButton(nil, month)
		end
		divs = '<div>' .. table.concat(monthToggles) .. '</div>'
	end
	divs = divs .. '<div>' .. p._lastXToggleButton(years, duration, combinationsL) .. '</div>'
	return '<div class="nomobile" style="text-align:center">' .. divs .. '</div>'
end

p._barColors = {
	'DimGrey',  --deaths
	'SkyBlue',  --recoveries
	'Tomato',   --cases or altlbl1
	'Gold',     --altlbl2
	'OrangeRed' --altlbl3
}

function p._customBarStacked(args)
	local barargs = {}

	barargs[1] = args[1]

	local function _numwidth(i)
		local nw = args.numwidth:sub(i, i)
		if nw == 'n' then
			return 0
		elseif nw == 't' then
			return 2.45
		elseif nw == 'm' or nw == 'd' then
			return 3.5
		elseif nw == 'w' then
			return 4.55
		elseif nw == 'x' then
			return 5.6
		end
	end

	barargs[2] =
		'<span class="cbs-ibr" style="padding:0 0.3em 0 0; width:' .. _numwidth(1) .. 'em">' .. (args[7] or '') .. '</span>' ..
		'<span class="cbs-ibl" style="width:' .. _numwidth(2) .. 'em">' .. (args[8] or '') .. '</span>'

	if #args.numwidth == 4 then
		local pad = args.numwidth:sub(3, 3) == 'n' and '0' or '0.3em'
		barargs.note2 =
			'<span class="cbs-ibr" style="padding:0 ' .. pad .. ' 0 0; width:' .. _numwidth(3) .. 'em">' .. (args[9] or '') .. '</span>' ..
			'<span class="cbs-ibl" style="width:' .. _numwidth(4) .. 'em">' .. (args[10] or '') .. '</span>'
	end

	for i = 1, 5 do
		barargs[2*i + 1] = p._barColors[i]
		barargs[2*i + 2] = args[i+1] / args.divisor
		barargs['title' .. i] = args[i+1]
	end

	barargs.align = 'cdcc'
	barargs.collapsed = args.collapsed
	barargs.id = args.id
	barargs.rowstyle = args.rowheight and 'line-height:'.. args.rowheight ..';'

	return barBox._stacked(barargs)
end

function p._row(args)
	local barargs = {}

	barargs[1] = (args[1] or '⋮') .. (args.note0 or '')
	barargs[2] = args[2] or 0
	barargs[3] = args[3] or 0

	if args['alttot1'] then
		barargs[4] = args['alttot1']
	elseif args[4] then
		barargs[4] = (args[4] or 0) - (barargs[2] or 0) - (barargs[3] or 0)
	else
		barargs[4] = 0
	end

	barargs[5] = args[5] or 0

	if args['alttot2'] then
		barargs[6] = args['alttot2']
	elseif args[6] then
		barargs[6] = (args[6] or 0) - (barargs[2] or 0) - (barargs[3] or 0)
	else
		barargs[6] = 0
	end

	barargs[7] = args[7]

	local function changeArg(firstright, valuecol, changecol)
		local change = ''
		if args['firstright' .. firstright] then
			change = '(' .. i18n.na .. ')'
		elseif not args[1] and args[valuecol] then
			change = '(=)'
		else
			change = args[changecol] and '(' .. args[changecol] .. ')' or ''
		end
		change = change .. (args['note' .. firstright] or '')

		return change
	end

	barargs[8] = changeArg(1, 7, 8)
	barargs[9] = args[9]
	barargs[10] = changeArg(2, 9, 10)

	barargs.divisor = args.divisor
	barargs.numwidth = args.numwidth
	barargs.rowheight = args.rowheight

	local dates
	if args.collapsible then
		local duration = args.duration
		if args.daysToEnd >= duration then
			barargs.collapsed = 'y'
		else
			barargs.collapsed = ''
		end

		if args.nooverlap and args.daysToEnd < duration then
			barargs.id = 'l' .. duration
		elseif args.nDate then
			dates = {year=tonumber(os.date('%Y', args.nDate)), mon=lang:formatDate('M', os.date('%Y-%m', args.nDate)),
				l=args.daysToEnd < duration and '-l' .. duration, nDate=args.nDate}
			barargs.id = (args.multiyear and dates.year or '') .. lang:lc(dates.mon) .. (dates.l or '')
		else
			local id, y, m, ey, em = {},
				tonumber(os.date('%Y', args.nStartDate or args.nAltStartDate)),
				tonumber(os.date('%m', args.nStartDate or args.nAltStartDate)),
				tonumber(os.date('%Y', args.nEndDate   or args.nAltEndDate  )),
				tonumber(os.date('%m', args.nEndDate   or args.nAltEndDate  ))
			dates = {nStartDate=args.nStartDate, nAltStartDate=args.nAltStartDate, nEndDate=args.nEndDate, nAltEndDate=args.nAltEndDate}

			repeat
				id[#id+1] = (args.multiyear and y or '') .. lang:lc(monthAbbrs[m])
				dates[#dates+1] = {year=y, mon=monthAbbrs[m]}
				y = y + math.floor(m / 12)
				m = m % 12 + 1
			until y == ey and m > em or y > ey

			dates.l = args.daysToEnd < duration and '-l' .. duration
			id = table.concat(id, '-') .. (dates.l or '')
			barargs.id = id
			dates.id = id
		end
	else
		barargs.collapsed = ''
		barargs.id = ''
	end

	return p._customBarStacked(barargs), dates
end

function p._buildBars(args)
	local frame = mw.getCurrentFrame()
	local updatePeriod = 86400 -- temporary implementation only supports daily updates

	local function getUnix(timestamp)
		return lang:formatDate('U', timestamp)
	end

	local rows, prevRow = {}, {}
	for line in mw.text.gsplit(args.data, '\n') do
		local i, barargs = 1, {}
		-- parameter parsing, basic type/missing value handling
		for parameter in mw.text.gsplit(line, ';') do
			if parameter:find('^%s*%a') then
				parameter = mw.text.split(parameter, '=')
				parameter[1] = mw.text.trim(parameter[1])
				if parameter[1]:find('^alttot') then
					parameter[2] = tonumber(frame:callParserFunction('#expr', parameter[2]))
				else
					parameter[2] = mw.text.trim(parameter[2])
					if parameter[1]:find('^firstright') then
						parameter[2] = yesno(parameter[2])
					elseif parameter[2] == '' then
						parameter[2] = nil
					end
				end
				barargs[parameter[1]] = parameter[2]
			else
				parameter = mw.text.trim(parameter)
				if parameter ~= '' then
					if i >= 2 and i <= 6 then
						parameter = tonumber(frame:callParserFunction('#expr', parameter))
						assert(parameter, 'データ引数2から6はフォーマットしないでください')
					end
					barargs[i] = parameter
				end
				i = i + 1
			end
		end

		local bValid, nDateDiff
		-- get relevant date info based on previous row
		if barargs[1] then
			bValid, barargs.nDate = pcall(getUnix, barargs[1])
			assert(bValid, '不正な日付 "' .. barargs[1] .. '"')
			if prevRow.nDate or prevRow.nEndDate then
				nDateDiff = barargs.nDate - (prevRow.nDate or prevRow.nEndDate)
				if nDateDiff > updatePeriod then
					if nDateDiff == 2 * updatePeriod then
						prevRow = {nDate=barargs.nDate-updatePeriod}
						prevRow[1] = os.date('%Y-%m-%d', prevRow.nDate)
					else
						prevRow = {nStartDate=(prevRow.nDate or prevRow.nEndDate)+updatePeriod, nEndDate=barargs.nDate-updatePeriod}
					end
					rows[#rows+1] = prevRow
				end
			else
				prevRow.nEndDate = barargs.nDate - updatePeriod
				if prevRow.nStartDate == prevRow.nEndDate then
					prevRow.nDate = prevRow.nEndDate
					prevRow[1] = os.date('%Y-%m-%d', prevRow.nDate)
				-- as nAltStartDate assumes a minimal multiday interval, it's possible for it to be greater if a true previous span is 1 day
				elseif prevRow.nAltStartDate and prevRow.nAltStartDate >= prevRow.nEndDate then
					error('a row in a consecutive intervals group is 1 day long and misses the date parameter')
				end
			end
		else
			if barargs.enddate then
				bValid, barargs.nEndDate = pcall(getUnix, barargs.enddate)
				assert(bValid, '不正な終了日 "' .. barargs.enddate .. '"')
			end
			if prevRow.nDate or prevRow.nEndDate then
				barargs.nStartDate = (prevRow.nDate or prevRow.nEndDate) + updatePeriod
				if barargs.nStartDate == barargs.nEndDate then
					barargs.nDate = barargs.nEndDate
					barargs[1] = os.date('%Y-%m-%d', barargs.nDate)
				end
			else
				prevRow.nAltEndDate = (prevRow.nStartDate or prevRow.nAltStartDate) + updatePeriod
				barargs.nAltStartDate = prevRow.nAltEndDate + updatePeriod
				if barargs.nEndDate and barargs.nAltStartDate >= barargs.nEndDate then
					error('a row in a consecutive intervals group is 1 day long and misses the date parameter')
				end
			end
		end

		local function fillCols(col, change)
			local data = args['right' .. col .. 'data']
			local changetype = args['changetype' .. col]
			local value, num, prevnum

			if data == 'alttot1' then
				num = barargs.alttot1 or barargs[4]
				prevnum = prevRow.alttot1 or prevRow[4]
			elseif data == 'alttot2' then
				num = barargs.alttot2 or barargs[6]
				prevnum = prevRow.alttot2 or prevRow[6]
			elseif data then
				num = barargs[data+1]
				prevnum = prevRow[data+1]
			end

			if data and num then -- nothing in column, source found, and data exists
				value = changetype == 'o' and '' or lang:formatNum(num) -- set value to num if changetype isn't 'o'

				if not change and not barargs['firstright' .. col] then
					if prevnum and prevnum ~= 0 then -- data on previous row
						if num - prevnum ~= 0 then --data has changed since previous row
							change = num-prevnum
							if changetype == 'a' then -- change type is "absolute"
								if change > 0 then
									change = '+' .. lang:formatNum(change)
								end
							else -- change type is "percent", "only percent" or undefined
								local percent = 100 * change / prevnum -- calculate percent
								local rounding = math.abs(percent) >= 10 and '%.0f' or math.abs(percent) >= 1 and '%.1f' or '%.2f'
								percent = tonumber(rounding:format(percent)) -- round to two sigfigs

								if percent > 0 then
									change = '+' .. lang:formatNum(percent) .. '%'
								elseif percent < 0 then
									change = lang:formatNum(percent) .. '%'
								else
									change = '='
								end
							end
						else -- data has not changed since previous row
							change = '='
						end
					else -- no data on previous row
						barargs['firstright' .. col] = true -- set to (n.a.)
					end
				end
			end

			return value, change
		end

		if not barargs[7] then
			barargs[7], barargs[8] = fillCols(1, barargs[8])
		end
		if not barargs[9] then
			barargs[9], barargs[10] = fillCols(2, barargs[10])
		end

		rows[#rows+1] = barargs
		prevRow = barargs
	end

	-- calculate and pass repetitive (except daysToEnd) parameters to each row
	local lastRow = rows[#rows]
	local total = {lastRow[2] or 0, lastRow[3] or 0, [4]=lastRow[5] or 0}
	total[3] = lastRow.alttot1 or lastRow[4] and lastRow[4] - total[1] - total[2] or 0
	total[5] = lastRow.alttot2 or lastRow[6] and lastRow[6] - total[1] - total[2] or 0
	local divisor = (total[1] + total[2] + total[3] + total[4] + total[5]) / (0.95 * args.barwidth)
	local firstDate, lastDate = rows[1].nDate, lastRow.nDate or lastRow.nEndDate
	local multiyear = os.date('%Y', firstDate) ~= os.date('%Y', lastDate - (args.nooverlap and args.duration * 86400 or 0))
	if args.collapsible ~= false then
		args.collapsible = (lastDate - firstDate) / 86400 >= args.duration
	end

	local bars, dateList = {}, {}
	for i, row in ipairs(rows) do -- build rows
		row.divisor = divisor
		row.numwidth = args.numwidth
		row.rowheight = args.rowheight
		row.collapsible = args.collapsible
		row.duration = args.duration
		row.nooverlap = args.nooverlap
		row.daysToEnd = (lastDate - (row.nDate or row.nEndDate or row.nAltEndDate)) / 86400
		row.multiyear = multiyear

		bars[#bars+1], dateList[#dateList+1] = p._row(row)
	end

	return table.concat(bars), dateList
end

function p._legend0(args)
	return
		'<span style="font-size:90%; margin:0px">' ..
			'<span style="background-color:' .. (args[1] or 'none') ..
			'; border:' .. (args.border or 'none') ..
			'; color:' .. (args[1] or 'none') .. '">' ..
				'&nbsp;&nbsp;&nbsp;&nbsp;' .. '</span>' ..
			'&nbsp;' .. (args[2] or '') .. '</span>'
end

function p._chart(args)
	for key, value in pairs(args) do
		if ({float=1, barwidth=1, numwidth=1, changetype=1})[key:gsub('%d', '')] then
			args[key] = value:lower()
		end
	end

	local barargs = {}

	barargs.css = 'Template:Medical cases chart/styles.css'
	barargs.float = args.float or 'right'

	args.barwidth = args.barwidth or 'medium'
	local barwidth
	if args.barwidth == 'thin' then
		barwidth = 120
	elseif args.barwidth == 'medium' then
		barwidth = 280
	elseif args.barwidth == 'wide' then
		barwidth = 400
	elseif args.barwidth == 'auto' then
		barwidth = 'auto'
	else
		error('barwidthが認識できません')
	end

	local function _numwidth(i)
		local nw = args.numwidth:sub(i, i)
		if nw == 'n' then
			return 0
		elseif nw == 't' then
			return 40
		elseif nw == 'm' or nw == 'd' then
			return 55
		elseif nw == 'w' then
			return 70
		elseif nw == 'x' then
			return 85
		else
			error('numwidthが認識できない値[' .. i .. ']です')
		end
	end

	args.numwidth = args.numwidth or 'mm'
	local numwidth = _numwidth(1) + 10 + _numwidth(2)
	local right1width = numwidth
	if #args.numwidth == 4 then
		numwidth = numwidth + _numwidth(3) + _numwidth(4)
		if args.numwidth:sub(3, 3) == 'n' then
			numwidth = numwidth + 6
		else
			numwidth = numwidth + 10
		end
		if not args.right2 then
			right1width = numwidth
		end
	end
	right1width = right1width - 8 -- -8 because of padding

	if tonumber(barwidth) then
		barargs.width = 85 + barwidth + numwidth .. 'px'
		barargs.barwidth = barwidth .. 'px'
	else
		barargs.width = 'auto'
		barargs.barwidth = 'auto'
	end

	local title = {}

	local function spaces(n)
		local nbsp = '&nbsp;'
		return '<span class="nowrap">' .. nbsp:rep(n) .. '</span>'
	end

	local location = lang:ucfirst(mw.ustring.gsub(args.location, i18n.the_, ''))
	local navbartitle = args.outbreak .. 'データ/症例数の推移/図表/' ..
		(args.location3 and args.location3 .. '/' or '') ..
		(args.location2 and args.location2 .. '/' or '') ..
		location .. ''

	local navbar = require('Module:Navbar')._navbar
	title[1] = '<div style="display:flex; justify-content:center;">' .. (args.pretitle and args.pretitle .. '' or '') ..
		(args.location3 and '' .. args.location3 or '') .. (args.postposition3 and args.postposition3 .. '' or '') ..
		(args.location2 and '' .. args.location2 or '') .. (args.postposition2 and args.postposition2 .. '' or '') ..
		args.location .. '' .. i18n.casesIn .. '' .. args.disease ..
		(args.posttitle and 'の症例数' .. args.posttitle or 'の症例数') .. '<span class="nowrap">&nbsp;&nbsp;</span>(<div style="font-size:75%;">' ..
		navbar({[1] = navbartitle, titleArg = ':' .. mw.getCurrentFrame():getParent():getTitle(), mini = 1, nodiv = 1}) ..
		'</div>)</div>'

	title[2] = p._legend0({p._barColors[1], i18n.deaths})
	args.recoveries = args.recoveries == nil and true or args.recoveries
	title[3] = args.recoveries and spaces(3) .. p._legend0({p._barColors[2], args.reclbl or i18n.recoveries}) or ''
	title[4] = args.altlbl1 ~= 'hide' and spaces(3) .. p._legend0({p._barColors[3], args.altlbl1 or i18n.activeCases}) or ''
	title[5] = args.altlbl2 and spaces(3) .. p._legend0({p._barColors[4], args.altlbl2}) or ''
	title[6] = args.altlbl3 and spaces(3) .. p._legend0({p._barColors[5], args.altlbl3}) or ''

	local togglesbar, buildargs = nil, {}

	args.right1 = args.right1 or i18n.noOfCases
	args.duration = args.duration or 15
	args.nooverlap = args.nooverlap or false

	buildargs.barwidth = tonumber(barwidth) or 280
	buildargs.numwidth = args.numwidth
	buildargs.rowheight = args.rowheight
	if args.datapage then
		local externalData = require('Module:Medical cases chart/data')._externalData
		buildargs.data = externalData(args)
	else
		buildargs.data = args.data
	end
	-- if no right1data and right1 title is cases, use 3rd classification
	buildargs.right1data = args.right1data or args.right1 == i18n.noOfCases and 3
	-- if no right2data and right2 title is deaths, use 1st classification
	buildargs.right2data = args.right2data or (args.right2 == i18n.noOfDeaths or args.right2 == i18n.noOfDeaths2) and 1
	buildargs.changetype1 = (args.changetype1 or args.changetype or ''):sub(1, 1) -- 1st letter
	buildargs.changetype2 = (args.changetype2 or args.changetype or ''):sub(1, 1) -- 1st letter
	buildargs.collapsible = args.collapsible
	buildargs.duration = args.duration
	buildargs.nooverlap = args.nooverlap

	local dateList
	barargs.bars, dateList = p._buildBars(buildargs)

	if buildargs.collapsible then
		togglesbar = p._buildTogglesBar(dateList, args.duration, args.nooverlap)
	end

	title[7] = togglesbar and '<br />' .. togglesbar or ''
	barargs.title = table.concat(title)

	barargs.left1 =
		'<div class="center" style="width:77px">' .. -- 85-8 because of padding
			"'''" .. i18n.date .. "'''" .. '</div>'

	barargs.right1 =
		'<div class="center" style="width:' .. right1width .. 'px">' ..
			"'''" .. args.right1 .. "'''" .. '</div>'

	if args.right2 then
		local right2width = numwidth - right1width - 16 -- -8-8...
		barargs.right2 =
			'<div class="center" style="width:' .. right2width ..'px">' ..
				"'''" .. args.right2 .. "'''" .. '</div>'
	end

	barargs.caption = args.caption
	return barBox._box(barargs)
end

function p.chart(frame)
	local getArgs = require('Module:Arguments').getArgs
	local args = getArgs(frame, {
		valueFunc = function (key, value)
			if value and value ~= '' then
				key = key:gsub('%d', '')
				if ({rowheight=1, duration=1, rightdata=1})[key] then -- if key in {...}
					return tonumber(value) or value
				end
				if ({recoveries=1, collapsible=1, nooverlap=1})[key] then
					return yesno(value)
				end
				return value
			end
			return nil
		end
	})
	return p._chart(args)
end

return p