const fs = require('fs');

const escapeHTML = (s) => String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');

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 {}; }
  })();
  
  // Build linesForStation for coloring (still needed for visualization)
  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 = {};
  // Optional: We can still use groups for coloring logic, but NOT for graph edges if we want strict mode.
  // But let's keep groupsMap for color logic (lines 30-46) as it helps determine if lines connect visually.
  stations.forEach(s => {
    const k = String(s.name || s.cn_name || s.station_name || s.en_name || '').trim();
    if (!k) return; (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'];
    // Logic to find connecting colors via transfer groups
    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); };
    
    // Create a set of valid station codes for fast lookup
    const validStationCodes = new Set(stations.map(s => String(s.code || '')));

    // 1. Edges from explicit FARES only
    (fares || []).forEach(f => {
      const a = String(f.from || ''), 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);
    });

    // 2. Edges from TRANSFERS (0 cost)
    if (Array.isArray(transfers)) {
        transfers.forEach(p => {
            const a = String(p[1] || ''), b = String(p[2] || ''); // startup.lua used index 1,2? No, lua is 1-based. JS is 0-based.
            // Wait, config transfers is [[a,b], [c,d]].
            // Let's check how it's stored. usually JSON array of arrays.
            // In startup.lua: p[1], p[2].
            // In JS: p[0], p[1].
            // But let's check standard.
            // If I look at startup.lua: `local a, b = tostring(p[1] or ''), tostring(p[2] or '')` (Lua 1-based index)
            // So in JSON it is `["A", "B"]`.
            // In JS `p[0]` is "A", `p[1]` is "B".
            // Let's be safe and check both or iterate.
            
            const u = String(p[0] || '');
            const v = String(p[1] || '');
            
            if (u && v && validStationCodes.has(u) && validStationCodes.has(v)) {
                addEdge(u, v, 0);
            }
        });
    }

    // REMOVED: Edges from Lines (implicit 0 cost)
    // REMOVED: Edges from Groups (implicit 0 cost) - unless we want to keep it as fallback?
    // User said "Strictly use database data". If transfers are in DB, we use them.
    // If user relies on name grouping but didn't put it in DB transfers, they might be unhappy.
    // But "Strictly use database data" implies we shouldn't automagically connect things.
    // I will remove the auto-grouping for graph edges to be consistent with startup.lua changes.

    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 = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" style="background:#ffffff;font-family:Inter, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif">`;
  // Column headers
  for (let j = 0; j < n; j++) {
    const code = codes[j]; const name = nameByCode[code] || code; const en = enNameByCode[code] || '';
    const x = margin + leftW + j * cellW + cellW / 2; const y = margin + 20;
    svg += `<text x="${x}" y="${y}" font-size="14" font-weight="900" fill="#000000" text-anchor="middle">${escapeHTML(name)}</text>`;
    if (en) svg += `<text x="${x}" y="${y + 14}" font-size="10" fill="#000000" text-anchor="middle">${escapeHTML(en)}</text>`;
    svg += `<text x="${x}" y="${y + 26}" font-size="10" fill="#000000" text-anchor="middle">${escapeHTML(code)}</text>`;
  }

  for (let i = 0; i < n; i++) {
    const code = codes[i]; const name = nameByCode[code] || code; const en = enNameByCode[code] || '';
    const xText = margin + 6; const baseY = margin + topH + i * cellH + 22;
    svg += `<text x="${xText}" y="${baseY - 10}" font-size="14" font-weight="900" fill="#000000">${escapeHTML(name)}</text>`;
    if (en) svg += `<text x="${xText}" y="${baseY + 4}" font-size="10" fill="#000000">${escapeHTML(en)}</text>`;
    svg += `<text x="${xText}" y="${baseY + 16}" font-size="10" fill="#000000">${escapeHTML(code)}</text>`;
    for (let j = 0; j < n; j++) {
      const x0 = margin + leftW + j * cellW; const y0 = margin + topH + i * cellH;
      svg += `<rect x="${x0}" y="${y0}" width="${cellW}" height="${cellH}" fill="#ffffff" stroke="#e5e7eb"/>`;
      if (i === j) {
        svg += `<text x="${x0 + cellW / 2}" y="${y0 + 22}" font-size="12" fill="#6b7280" text-anchor="middle">-</text>`;
      } else {
        const val = Math.round(Number(distAll[code][codes[j]] || 0));
        const intensity = maxFare > 0 ? Math.min(1, val / maxFare) : 0;
        
        // Get route colors
        const routeColors = colorsForRoute(code, codes[j]);
        
        // Draw background strips for multiple lines
        if (routeColors.length > 0) {
            const stripW = cellW / routeColors.length;
            for (let k = 0; k < routeColors.length; k++) {
                const c = routeColors[k] || '#93a2b7';
                const stripX = x0 + k * stripW;
                // Use fill-opacity for robustness against any color format
                svg += `<rect x="${stripX}" y="${y0}" width="${stripW}" height="${cellH}" fill="${escapeHTML(c)}" fill-opacity="0.15" stroke="none"/>`;
            }
        } else {
            svg += `<rect x="${x0}" y="${y0}" width="${cellW}" height="${cellH}" fill="#000000" fill-opacity="0.03" stroke="none"/>`;
        }
        
        // Add cell border
        svg += `<rect x="${x0}" y="${y0}" width="${cellW}" height="${cellH}" fill="none" stroke="#e5e7eb" stroke-width="0.5"/>`;
        
        svg += `<text x="${x0 + cellW / 2}" y="${y0 + 22}" font-size="12" font-weight="800" fill="#000000" text-anchor="middle">${escapeHTML(String(val))}</text>`;
        
        // Show small color indicators at top left
        const dots = routeColors.slice(0, 4);
        for (let k = 0; k < dots.length; k++) {
          const cx = x0 + 8 + k * 10; const cy = y0 + 10;
          const dotColor = dots[k] || '#93a2b7';
          svg += `<circle cx="${cx}" cy="${cy}" r="3.5" fill="${escapeHTML(dotColor)}" stroke="white" stroke-width="1"/>`;
        }
      }
    }
  }
  svg += `</svg>`;
  return svg;
}

module.exports = { generate };
