模組:CNBUS/sandbox

local p = {}

local obsolete = '[[Category:调用CNBUS废弃接口的页面]]'

---@overload fun(s: string): boolean?
---@overload fun(s: string, default: boolean): boolean
local yesno = require('Module:yesno')

---@param var any
---@return boolean
local function isEmpty(var)
	return not var or var == ''
end

---将空字符串转换为`nil`
---@param var string?
---@return string?
local function nilEmpty(var)
	if var == '' then return nil
	else return var
	end
end

--#region 线路定义

---旧版线路
---@class metalineLegacy
---@field code string
---@field endpoint1 string
---@field time1 string?
---@field direction string?
---@field endpoint2 string
---@field time2 string?
---@field direction1 string?
---@field endpoint3 string?
---@field time3 string?
---@field fare string
---@field company string
---@field image string?
---@field vehicle string?
---@field brt_a string?
---@field brt_a_in string?
---@field brt_a_out string?
---@field brt_b string?
---@field brt_b_in string?
---@field brt_b_out string?
---@field brt_info string?
---@field note string?
---@field suspend string?
local LL = {}

---转换旧版线路
---@return line
function LL:convert()
	---@type line
	local line = {
		{
			{ self.endpoint1, time = self.time1 },
			{ self.direction },
			{ self.endpoint2, time = self.time2 },
		},
		code = self.code,
		fare = self.fare,
		operator = self.company,
		image = self.image,
		vehicle = self.vehicle and mw.text.split(self.vehicle, '<br/>'),
		note = self.note,
	}
	if self.endpoint3 then
		table.insert(line, {
			{ self.endpoint1, time = self.time1 },
			{ self.direction1 },
			{ self.endpoint3, time = self.time3 },
		})
	end
	if self.brt_a or self.brt_b then
		line.brt = { info = self.brt_info }
		if self.brt_a then
			line.brt[1] = {
				self.brt_a,
				i = self.brt_a_in,
				o = self.brt_a_out,
			}
		end
		if self.brt_b then
			line.brt[2] = {
				self.brt_b,
				i = self.brt_b_in,
				o = self.brt_b_out,
			}
		end
	end
	if line.note and mw.ustring.match(line.note, '已停[办辦]') then
		line.status = { -1, date = self.suspend }
	elseif line.note and line.note == '暂停服务' or line.note == '暂停服务' then
		line.status = { 0, date = self.suspend }
	end
	return line
end

---@alias terminus { [1]: string?, time: string? }
---@alias direction { [1]: string?, mark: string? }
---@alias route { [1]: terminus?, [2]: direction?, [3]: terminus? }
---@alias pTerminus { [1]: string?, time: string?, rowspan: integer? }
---@alias pRoute { [1]: pTerminus, [2]: direction, [3]: pTerminus }

---@alias brt { [1]: string, i: string, o: string }

---@class line: { [number]: route }
---@field code string
---@field mark string?
---@field fare string
---@field operator string
---@field image string?
---@field vehicle string[]?
---@field brt { [1]: brt?, [2]: brt?, info: string? }?
---@field note string?
---@field status { [1]: integer, date: string }?
local L = {}

---获取解析区间表
---@return pRoute[]
function L:getParsedRoutes()
	---@type pRoute[]
	local routes = {}
	local last_route = { { }, nil, { } } -- 引用上一区间/交路
	for r, route in ipairs(self) do
		local el = isEmpty(route[1] and route[1][1]) and r > 1
		local ed = isEmpty(route[2] and route[2][1])
		local er = isEmpty(route[3] and route[3][1])
		if el or ed or er then
			---@type terminus
			local left, right

			-- 当终点站表整体悬空时,克隆所有属性
			if not route[1] then
				left = last_route[1]
			-- 否则,只复制站名
			else
				left = {
					nilEmpty(route[1][1]) or last_route[1][1],
					time = route[1].time
				}
			end

			-- 同上,但首行右终点将回落左终点
			if not route[3] then
				right = r == 1 and left or last_route[3]
			else
				right = {
					nilEmpty(route[3][1]) or (r == 1 and left[1] or last_route[3][1]),
					time = route[3].time
				}
			end

			-- 隐式方向
			local direction = { ed and ((left[1] == right[1]) and '↺' or '⇆') or route[2] }

			table.insert(routes, { left, direction, right, note = self.note })
		else
			table.insert(routes, route)
		end
	end

	if #routes > 1 then
		for r = #routes, 2, -1 do
			if nilEmpty(routes[r][1][1]) == nilEmpty(routes[r - 1][1][1]) and
			   nilEmpty(routes[r][1].time) == nilEmpty(routes[r - 1][1].time) then
				routes[r - 1][1].rowspan = (routes[r][1].rowspan or 1) + 1
				routes[r][1].rowspan = 0
			end
			if nilEmpty(routes[r][3][1]) == nilEmpty(routes[r - 1][3][1]) and
			   nilEmpty(routes[r][3].time) == nilEmpty(routes[r - 1][3].time) then
				routes[r - 1][3].rowspan = (routes[r][3].rowspan or 1) + 1
				routes[r][3].rowspan = 0
			end
		end
	end

	return routes
end

--#endregion

--#region 区域定义

---@alias mArea { name: string, page: string, source: string, aliases: string[] }

---@class area
---@field name string
---@field page string
---@field source string
---@field aliases string
---@field lines { [string]: line }
local A = {}

---获取线路 w/ err
---@param l string
---@param inline boolean?
---@return line
---@return string?
function A:getLine(l, inline)
	local line = self.lines[l]
	---@diagnostic disable-next-line: undefined-field
	if line and (line.endpoint1 or line.note == '已停办' or line.note == '已停辦' or line.note == '暂停服务' or line.note == '暂停服务') then
		---@diagnostic disable-next-line: param-type-mismatch
		line = LL.convert(line)
	end

	local err = nil
	if inline then
		if isEmpty(l) then
			err = '未输入线路'
		elseif not line then
			err = string.format('[[%s]]中无%s线路', self.source, line.code)
		elseif line.status then
			if line.status[1] == -1 then
				if isEmpty(line.status.date) then
					err = string.format('%s已停办', line.code)
				else
					err = string.format('%s已于%s停办', line.code, line.status.date)
				end
			elseif line.status[1] == 0 then
				if isEmpty(line.status.date) then
					err = string.format('%s已暂停服务', line.code)
				else
					err = string.format('%s自%s起暂停服务', line.code, line.status.date)
				end
			end
		end
	else
		if isEmpty(l) then
			err = string.format('请输入线路[[%s巴士路线列表|编号]]', self.name)
		elseif not line then
			err = string.format('[[%s]]中未包含这条[[%s巴士路线列表|%s]]的线路', self.source, self.name, self.name)
		elseif line.status then
			if line.status[1] == -1 then
				if isEmpty(line.status.date) then
					err = '本线已停办,请移除'
				else
					err = string.format('本线已于%s停办,请移除', line.status.date)
				end
			elseif line.status[1] == 0 then
				if isEmpty(line.status.date) then
					err = '本线已暂停服务'
				else
					err = string.format('本线自%s起暂停服务', line.status.date)
				end
			end
		end
	end

	return line, err
end

--#endregion

--#region 城市定义

---@alias rmap { [string]: string[] }
---@alias qmap { [string]: string }
---@alias mCityLegacy { colors: qmap, company: rmap, data: qmap, titlename: qmap, listname: qmap, location: rmap }
---@alias mCity { areas: { [string]: mArea }, operators: { [string]: operator } }

---@alias operator { color: string, aliases: string[] }

---@class city
---@field areas { [string]: area }
---@field area_map { [string]: string }
---@field lines { [string]: { [string]: line } }
---@field operators { [string]: operator }
---@field operator_map { [string]: string }
local data = {}

---@param a string
---@return area
function data:getArea(a)
	return self.areas[a] or self.areas[self.area_map[a]] or self.areas['default']
end

---@param o string
---@return operator
function data:getOperator(o)
	return self.operators[o] or self.operators[self.operator_map[o]]
end

--#endregion

--#region 数据模块

---导入城市数据
---@param c string
local function loadCityData(c)
	if not data.areas then
		if isEmpty(c) then
			error(string.format('“city”参数为空,请输入城市代码'))
		end

		local success, ro_data = pcall(mw.loadData, 'Module:CNBUS/' .. c)
		if not success then
			error(string.format('[[Module:CNBUS]]不存在“%s”的公交系统数据', c))
		end

		-- 引用只读数据
		if ro_data.data then
			---@cast ro_data mCityLegacy
			data.area_map = {}
			data.areas = {}
			data.operator_map = {}
			data.operators = {}

			for a, source in pairs(ro_data.data) do
				data.areas[a] = { source = source }
			end

			for a, name in pairs(ro_data.titlename or {}) do
				data.areas[a].name = name
			end

			for a, page in pairs(ro_data.listname or {}) do
				data.areas[a].page = page
			end

			for a, aliases in pairs(ro_data.location or {}) do
				for _, alias in ipairs(aliases) do
					data.area_map[alias] = a
				end
			end

			for o, color in pairs(ro_data.colors) do
				data.operators[o] = { color = color }
			end

			for o, aliases in pairs(ro_data.company or {}) do
				for _, alias in ipairs(aliases) do
					data.operator_map[alias] = o
				end
			end
		else
			---@cast ro_data mCity
			data.area_map = {}
			data.operator_map = {}

			for a, ro_area in ro_data.areas do
				data.areas[a] = setmetatable({}, { __index = ro_area })

				for _, alias in ipairs(ro_area.aliases) do
					data.area_map[alias] = a
				end
			end

			data.operators = setmetatable({}, { __index = ro_data.operators })
			for o, operator in pairs(data.operators) do
				for _, alias in ipairs(operator.aliases) do
					data.operator_map[alias] = o
				end
			end
		end
	end
end

---导入区域线路数据
---@param c string
---@param a string
local function loadAreaData(c, a)
	loadCityData(c)

	if isEmpty(a) then
		error(string.format('“area”参数为空,请输入区域代码'))
	end

	local area = data:getArea(a)

	if not area then
		error(string.format('[[Module:CNBUS/%s]]中未包含“%s”的资料模块', c, a))
	end

	if not area.lines then
		local success, ro_data = pcall(mw.loadData, area.source)
		if not success then
			error(string.format('资料模块[[%s]]出现错误,请前往检查', area.source))
		end

		area.lines = ro_data
	end
end

--#endregion

--#region 颜色模板

---@param city string
---@param operator string?
---@return string
function p._color(city, operator)
	loadCityData(city)

	local info = data:getOperator(operator or 'other')
	if info then
		return info.color
	-- 运营商名超过6字(UTF-8下18字节)视为联营
	elseif (operator and string.len(operator) > 18) or operator == 'multi' then
		return data:getOperator('multi').color
	else
		return data:getOperator('other').color
	end
end

---运营商颜色
---@param frame frame
---@return string
function p.color(frame)
	local a = frame.args
	return p._color(a.city, a.operator or a.company)
end

---@param city string
---@param operator string
---@deprecated
function p._colorBox(city, operator)
	return string.format('width=1%% bgcolor=%s', p._color(city, operator))
end

---{{巴士公司色块}}
---@param frame frame
---@return string
---@deprecated
function p.colorbox(frame)
	local a = frame.args
	return p._colorBox(a.city, a.operator or a.company) -- .. obsolete
end

--#endregion

--#region 列表公共模板

local fdash = mw.ustring.char(0x2012)

---@param s string
---@return string
local function fixFigureDash(s)
	s, _ = mw.ustring.gsub(s, '-', fdash)
	return s
end

---@param color string
---@param rowspan integer?
---@return html?
local function makeBarCell(color, rowspan)
	local td = mw.html.create('td')
		:addClass('bar')
		:css('background-color', color)

	if rowspan and rowspan > 1 then
		td	:attr('rowspan', rowspan)
	end

	return td:allDone()
end

---@param line line
---@param rowspan integer?
---@return html?
local function makeCodeCell(line, rowspan)
	local td = mw.html.create('td')
		:addClass('code')
		:wikitext(line.code)

	if rowspan and rowspan > 1 then
		td	:attr('rowspan', rowspan)
	end

	if nilEmpty(line.mark) then
		td:tag('br', { selfClosing = true }):done():tag('small'):wikitext(line.mark):done()
	end

	return td:allDone()
end

---@param route pRoute
---@param t 1|3 Left or right
---@param bTime boolean
---@param rowspan integer? Override
---@return html?
local function makeTerminusCell(route, t, bTime, rowspan)
	local terminus = route[t]
	local n_rows = rowspan or terminus.rowspan

	if n_rows == 0 then
		return nil
	end

	local td = mw.html.create('td')
	td	:addClass('terminus-' .. ((t == 1) and 'left' or 'right'))
		:wikitext(terminus[1])

	if n_rows and n_rows > 1 then
		td	:attr('rowspan', n_rows)
	end

	if bTime and nilEmpty(terminus.time) then
		td	:tag('br', { selfClosing = true }):done()
			:tag('small'):wikitext(fixFigureDash(terminus.time)):done()
	end

	return td:allDone()
end

---@param route pRoute
---@param direction string? override
---@return html?
local function makeDirectionCell(route, direction)
	local td = mw.html.create('td')
		:addClass('direction')

	if not direction and nilEmpty(route[2].mark) then
		td:tag('small'):wikitext(route[2].mark):done():tag('br', { selfClosing = true }):done()
	end

	return td:wikitext(direction or route[2][1]):allDone()
end

---@param prop string
---@param rowspan integer?
---@param dash boolean?
local function makePropCell(prop, rowspan, dash)
	local td = mw.html.create('td')

	if dash then
		td:wikitext(fixFigureDash(prop))
	else
		td:wikitext(prop)
	end

	if rowspan and rowspan > 1 then
		td	:attr('rowspan', rowspan)
	end

	return td:allDone()
end

---@param line line
---@param bImage boolean
---@param rowspan integer?
---@return html?
local function makeNoteCell(line, bImage, rowspan)
	local td = mw.html.create('td')

	if bImage then
		local sep = (nilEmpty(line.note) or nilEmpty(line.image)) and '<br/>' or ''

		td	:addClass('note')
			:wikitext(line.note, sep, line.image)
			:done()
	else
		td	:addClass('note')
			:wikitext(line.note)
			:done()
	end

	if rowspan and rowspan > 1 then
		td	:attr('rowspan', rowspan)
	end

	return td:allDone()
end

--#endregion

--#region 列表模板

---@alias listFlags { brt: boolean, time: boolean, fare: boolean, operator: boolean, vehicle: boolean, image: boolean }

---解析列表标志位
---@param typ string
---@param fTime string
---@param fVehicle string
---@param fImage string
---@return listFlags flags
local function parseListFlags(typ, fTime, fVehicle, fImage)
	typ = mw.ustring.lower(typ)
	if mw.ustring.find(typ, 'brt') then
		return {
			brt      = true,
			time     = yesno(fTime, false),
			fare     = false,
			operator = false,
			vehicle  = false,
			image    = yesno(fImage, false),
		}
	else
		return {
			brt      = false,
			time     = yesno(fTime, false),
			fare     = not mw.ustring.match(typ, 'no%w*fa'),
			operator = not mw.ustring.match(typ, 'no%w*co'),
			vehicle  = yesno(fVehicle, false),
			image    = yesno(fImage, false),
		}
	end
end

---获取行CSS类
---@param f listFlags
---@return string
local function getListRowClass(f)
	if f.brt then
		return 'cnbus-brt'
	else
		return 'cnbus-l' .. ((f.fare and 1 or 0) + (f.operator and 1 or 0) + (f.vehicle and 2 or 0))
	end
end

---@param c string
---@param a string
---@param f listFlags
---@param class string
---@return html head
function p._listHead(c, a, f, class)
	local success, err = pcall(loadAreaData, c, a)

	if not success then
		if f.brt then
			return mw.html.create('tr')
				:tag('th'):attr('colspan', 10):wikitext(err)
				:allDone()
		else
			local n_cols = 6 + (f.fare and 1 or 0) + (f.operator and 1 or 0) + (f.vehicle and 1 or 0)
			return mw.html.create('tr')
				:tag('th'):attr('colspan', n_cols):wikitext(err)
				:allDone()
		end
	end

	local area = data:getArea(a)
	local header_lines = (f.time and "线路及运营时间") or "线路"
	local header_note = (f.image and "图片及备注") or "备注"
	local link_page = area.page or string.format('%s巴士路线列表', area.name)

	if f.brt then
		return mw.html.create('tr')
			:addClass(class)
			:tag('th'):attr('colspan', 2):wikitext(string.format('[[%s|编号]]',link_page)):done()
			:tag('th'):attr('colspan', 3):wikitext(header_lines):done()
			:tag('th'):wikitext('驶入BRT通道'):addClass('inout'):done()
			:tag('th'):wikitext('驶出BRT通道'):addClass('inout'):done()
			:tag('th'):wikitext('BRT通道停靠站数'):addClass('count'):done()
			:tag('th'):wikitext('运营商'):addClass('operator'):done()
			:tag('th'):wikitext(header_note):addClass('note'):done()
			:allDone()
	else
		local tr = mw.html.create('tr'):addClass(class)

		tr	:tag('th'):attr('colspan', 2):wikitext(string.format('[[%s|编号]]', link_page)):done()
			:tag('th'):attr('colspan', 3):wikitext(header_lines):done()
		if f.fare then
			tr:tag('th'):wikitext('收费'):addClass('fare'):done()
		end
		if f.operator then
			tr:tag('th'):wikitext('运营商'):addClass('operator'):done()
		end
		if f.vehicle then
			tr:tag('th'):wikitext('运力'):addClass('vehicle'):done()
		end
		tr:tag('th'):wikitext(header_note):addClass('note'):done()

		return tr:allDone()
	end
end

---表头模板
---@param frame frame
---@return html|string
---@deprecated
function p.title(frame)
	local a = frame.args
	local flags = parseListFlags(
		a.type or a.format or '',
		a.time,
		a.vehicle,
		a.image)
	local result = p._listHead(
		a.city,
		a.area or a.loc,
		flags,
		getListRowClass(flags))
	local output = mw.html.create('')
	return output:node(result):wikitext(obsolete):newline():allDone()
end

---@param c string
---@param a string
---@param l string
---@param f listFlags
---@param class string
---@return html head
function p._listRow(c, a, l, f, class)
	local success
	local err
	success, err = pcall(loadAreaData, c, a)

	local output = mw.html.create()

	if not success then
		l = '错误'
	else
		local area = data:getArea(a)
		local line
		line, err = A.getLine(area, l)

		if not err then
			local color = p._color(c, line.operator)

			-- BRT线路(广州、中山)
			if f.brt then
				if not line.brt then
					err = '本线并非[[快速公交系统|BRT线路]]'
				else
					local route = line[1]

					if (line.brt[1] and line.brt[2]) then
						output
							:tag('tr')
								:addClass(class)
								:addClass('line')
								:node(makeBarCell(color, 2))
								:node(makeCodeCell(line, 2))
								:node(makeTerminusCell(route, 1, f.time, 2))
								:node(makeDirectionCell(route, '→'))
								:node(makeTerminusCell(route, 3, f.time, 2))
								:node(makePropCell(line.brt[1].i))
								:node(makePropCell(line.brt[1].o))
								:node(makePropCell(line.brt[1][1]))
								:node(makePropCell(line.operator, 2))
								:node(makeNoteCell(line, f.time, 2))
								:done()
							:tag('tr')
								:addClass('route')
								:node(makeDirectionCell(route, '←'))
								:node(makePropCell(line.brt[2].i))
								:node(makePropCell(line.brt[2].o))
								:node(makePropCell(line.brt[2][1]))
								:done()
					else
						local info = line.brt[1] or line.brt[2] --[[@as brt]]

						output
							:tag('tr')
								:addClass(class)
								:addClass('line')
								:node(makeBarCell(color, 1))
								:node(makeCodeCell(line, 1))
								:node(makeTerminusCell(route, 1, f.time, 1))
								:node(makeDirectionCell(route))
								:node(makeTerminusCell(route, 3, f.time, 1))
								:node(makePropCell(info.i))
								:node(makePropCell(info.o))
								:node(makePropCell(tostring(info[1])))
								:node(makePropCell(line.operator))
								:node(makeNoteCell(line, f.image))
								:done()
					end
				end
			-- 常规线路
			else
				local routes = L.getParsedRoutes(line)
				local tr

				for r, route in ipairs(routes) do
					tr = mw.html.create('tr')

					if r == 1 then
						tr	:addClass(class)
							:addClass('line')
							:node(makeBarCell(color, #routes))
							:node(makeCodeCell(line, #routes))
					else
						tr	:addClass('route')
					end

					tr	:node(makeTerminusCell(route, 1, f.time))
						:node(makeDirectionCell(route))
						:node(makeTerminusCell(route, 3, f.time))

					if r == 1 then
						if f.fare then
							tr:node(makePropCell(line.fare, #routes, true))
						end

						if f.operator then
							tr:node(makePropCell(line.operator, #routes))
						end

						if f.vehicle then
							tr:node(makePropCell(table.concat(line.vehicle, '<br/>'), #routes))
						end

						tr:node(makeNoteCell(line, f.image, #routes))
					end

					output:node(tr:allDone())
				end
			end
		end

		if err then
			local n_cols
			if f.brt then
				n_cols = 8
			else
				n_cols = 4 + (f.fare and 1 or 0) + (f.operator and 1 or 0) + (f.vehicle and 1 or 0)
			end

			output:tag('tr'):addClass('msg')
				:tag('td'):addClass('bar'):done()
				:tag('td'):addClass('code')
					:wikitext((line and line.code) or l)
					:done()
				:tag('td'):addClass('msg'):attr('colspan', n_cols)
					:wikitext(err)
					:done()
		end
	end

	return output:allDone()
end

---列表模板
---@param frame frame
---@return string
function p.list(frame)
	local a = frame.args

	if a.code then
		---@deprecated
		return obsolete
	else
		local open = yesno(a.open, true)
		local close = yesno(a.close, true)

		---@overload fun(frame: frame, options: table?): { [any]: string }
		local getArgs = require('Module:Arguments').getArgs
		a = getArgs(frame)
		local flags = parseListFlags(
			a.type or '',
			a.time,
			a.vehicle,
			a.image)
		local class = getListRowClass(flags)

		local rows = {}
		local prepend = ''
		if open then
			prepend = '<table class="wikitable sortable cnbus-normal">'
			table.insert(
				rows,
				tostring(
					p._listHead(
						a.city,
						a.area,
						flags,
						class)))
		end

		for _, l in ipairs(a) do
			table.insert(
				rows,
				tostring(
					p._listRow(
						a.city,
						a.area,
						l,
						flags,
						class)))
		end

		local append = close and '</table>' or ''

		return prepend .. table.concat(rows) .. append
	end
end

--#endregion

--#region 折叠列表模板

---@param c string
---@param a string
---@param l string
---@return html head
function p._collapsibleListRow(c, a, l)
	local success
	local err
	success, err = pcall(loadAreaData, c, a)

	local output = mw.html.create()

	if not success then
		l = '错误'
	end

	if not err then
		local area = data:getArea(a)
		local line
		line, err = A.getLine(area, l)

		if err then
			output:tag('tr')
				:tag('td'):wikitext(l):done()
				:tag('td'):attr('colspan', 4):wikitext(err):done()
			:done()
		else
			local routes = L.getParsedRoutes(line)

			for r, route in ipairs(routes) do
				local tr = mw.html.create('tr')

				if r == 1 then
					tr:node(makeCodeCell(line, #routes))
				end

				tr	:node(makeTerminusCell(route, 1, false))
					:node(makeDirectionCell(route))
					:node(makeTerminusCell(route, 3, false))

				if r == 1 then
					tr:node(makeNoteCell(line, false, #routes))
				end

				output:node(tr:allDone())
			end
		end
	end

	return output:allDone()
end

---折叠列表模板
---@param frame frame
---@return html|string
function p.collapsibleListRows(frame)
	---@overload fun(frame: frame, options: table?): { [any]: string }
	local getArgs = require('Module:Arguments').getArgs
	local a = getArgs(frame)

	local rows = {}
	for _, l in ipairs(a) do
		table.insert(rows, p._collapsibleListRow(a.city, a.area, l))
	end

	return table.concat(rows)
end

---简单列表单行模板(兼容中)
---@param frame frame
---@return html|string
---@deprecated
function p.simplelist(frame)
	local c = frame.args.city
	local a = frame.args.loc
	local l = frame.args.code

	local success
	local err
	success, err = pcall(loadAreaData, c, a)

	if not success then
		l = '错误'
	end

	if not err then
		local area = data:getArea(a)
		local line
		line, err = A.getLine(area, l)

		if not err then
			---@type string
			local dir, ep1, ep2
			ep1 = line[1][1][1]
			if isEmpty(line[1][3][1]) then
				ep2 = ep1								-- 未填入endpoint2默认为循环线
			else
				ep2 = line[1][3][1]
			end
			if isEmpty(line[1][2][1]) then					-- 部分线路省略了方向
				if isEmpty(line[1][3][1]) then				-- 未填入endpoint2默认为循环线
					dir = "↺"
				else
					dir = "⇆"
				end
			elseif line[2] and nilEmpty(line[2][2][1]) then			-- 部分线路的第二个方向
				if nilEmpty(line[2][3][1]) then			-- 部分线路去程终点和返程起点不同
					dir = line[1][2][1]..'<hr>'..line[2][2][1]
					ep2 = ep2..'<hr>'..line[2][3][1]
				else
					dir = line[1][2][1]..'<br>'..line[2][2][1]
				end
			else
				dir = line[1][2][1]
			end
			local data_note = line.note or ''
			l = line.code

			return string.format(
				[[|align=right|'''%s'''||align=right|%s||align=center nowrap=true|%s||%s||%s]],
				l,
				ep1,
				dir,
				ep2,
				data_note) .. obsolete
		end
	end

	return string.format(
		[[|align=right|'''%s'''||colspan=3 align=center|%s]],
		l,
		err) .. obsolete
end

--#endregion

--#region 紧凑列表模板

--#endregion

--#region 线路编号模板(惠州)

---@param c string
---@param a string
---@param l string
---@return string
function p._code(c, a, l)
	local success
	local err
	success, err = pcall(loadAreaData, c, a)

	if not success then
		l = '错误'
	end

	if not err then
		local area = data:getArea(a)
		local line
		line, err = A.getLine(area, l)

		if not err then
			return line.code or l
		end
	end

	return string.format('(%s)', err)
end

---线路编号模板
---@param frame frame
---@return string
---@deprecated
function p.code(frame)
	local a = frame.args
	return p._code(
		a.city,
		a.area or a.loc,
		a.code)
end

--#endregion

return p