const escapeHTML = (s) => String(s || '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
function generate(stations, lines, fares, nameByCode, transfers) {
const enNameByCode = (() => {
try {
const map = {};
for (const s of (stations || [])) {
map[String(s.code || '')] = s.en_name || s.enName || '';
}
return map;
} catch (_) {
return {};
}
})();
const linesForStation = {};
(lines || []).forEach((li) => {
const id = li.id || '';
const name = li.name || li.cn_name || '';
const color = li.color || '#93a2b7';
const arr = (Array.isArray(li.stations) ? li.stations : (Array.isArray(li.stops) ? li.stops : [])).map((x) => String(x));
for (const code of arr) {
if (!linesForStation[code]) linesForStation[code] = [];
linesForStation[code].push({ id, name, color, stations: arr });
}
});
const groupsMap = {};
(stations || []).forEach((s) => {
const k = String(s.name || s.cn_name || s.station_name || s.en_name || '').trim();
if (!k) return;
if (!groupsMap[k]) groupsMap[k] = [];
groupsMap[k].push(String(s.code || ''));
});
const groupsForColors = Object.values(groupsMap).filter((arr) => arr.length > 1);
const colorsForRoute = (a, b) => {
const la = linesForStation[a] || [];
const lb = linesForStation[b] || [];
const same = la.find((x) => lb.some((y) => y.id === x.id));
if (same) return [same.color || '#93a2b7'];
for (const g of groupsForColors) {
const startLine = la.find((li) => (li.stations || []).some((code) => g.includes(code)));
const endLine = lb.find((li) => (li.stations || []).some((code) => g.includes(code)));
if (startLine && endLine) {
const c1 = startLine.color || '#93a2b7';
const c2 = endLine.color || '#93a2b7';
return (startLine.id === endLine.id) ? [c1] : [c1, c2];
}
}
return [];
};
const buildGraph = () => {
const adj = new Map();
const addNode = (c) => { if (!adj.has(c)) adj.set(c, new Map()); };
const addEdge = (u, v, w) => {
addNode(u);
addNode(v);
adj.get(u).set(v, w);
adj.get(v).set(u, w);
};
const validStationCodes = new Set((stations || []).map((s) => String(s.code || '')));
(fares || []).forEach((f) => {
const a = String(f.from || '');
const b = String(f.to || '');
if (!a || !b) return;
if (!validStationCodes.has(a) || !validStationCodes.has(b)) return;
const w = Number(f.cost_regular ?? f.cost) || 0;
addEdge(a, b, w);
});
if (Array.isArray(transfers)) {
transfers.forEach((p) => {
const u = String(p[0] || '');
const v = String(p[1] || '');
if (u && v && validStationCodes.has(u) && validStationCodes.has(v)) {
addEdge(u, v, 0);
}
});
}
(stations || []).forEach((s) => {
const c = String(s.code || '');
if (c) addNode(c);
});
return adj;
};
const adj = buildGraph();
const codes = Array.from(adj.keys()).sort((a, b) => a.localeCompare(b));
const neighborsOf = (u) => adj.get(u) || new Map();
const dijkstra = (src) => {
const dist = {};
const visited = new Set();
for (const n of adj.keys()) dist[n] = Infinity;
dist[src] = 0;
while (true) {
let u = null;
let best = Infinity;
for (const n of adj.keys()) {
if (!visited.has(n) && dist[n] < best) {
best = dist[n];
u = n;
}
}
if (u === null) break;
visited.add(u);
for (const [v, w] of neighborsOf(u)) {
const nd = dist[u] + Number(w || 0);
if (nd < dist[v]) dist[v] = nd;
}
}
return dist;
};
const distAll = {};
codes.forEach((c) => {
distAll[c] = dijkstra(c);
});
let maxFare = 1;
for (const r of codes) {
for (const c of codes) {
if (r === c) continue;
const v = Number(distAll[r][c] || 0);
if (v > maxFare) maxFare = v;
}
}
const margin = 8;
const cellW = 56;
const cellH = 40;
const estimateWidth = (text, size) => Math.ceil(String(text || '').length * (size <= 10 ? 7 : 10));
const maxCnW = Math.max(1, ...codes.map((c) => estimateWidth(nameByCode[c] || c, 14)));
const maxEnW = Math.max(1, ...codes.map((c) => estimateWidth(enNameByCode[c] || '', 10)));
const maxCodeW = Math.max(1, ...codes.map((c) => estimateWidth(c, 10)));
const leftW = Math.max(140, 24 + Math.max(maxCnW, maxEnW, maxCodeW));
const topH = 64;
const n = codes.length;
const width = margin + leftW + n * cellW + margin;
const height = margin + topH + n * cellH + margin;
let svg = ``;
return svg;
}
module.exports = { generate };