'use strict';

const fs   = require('fs');
const path = require('path');
const os   = require('os');

// Read results path: config.json > server settings > default
function getResultsDir() {
  const isWindows = process.platform === 'win32';
  const defaultDir = isWindows ? path.join('C:', 'ace-manager', 'results') : path.join(os.homedir(), 'results');

  // 1. Check config.json (central config)
  try {
    const cfgPath = path.join(__dirname, '..', 'config.json');
    if (fs.existsSync(cfgPath)) {
      const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
      if (cfg.resultsPath && cfg.resultsPath.trim()) return cfg.resultsPath.trim();
    }
  } catch {}

  // 2. Check server settings files
  const settingsDir = isWindows ? path.join('C:', 'ace-manager', 'ACE') : path.join(os.homedir(), 'ACE');
  try {
    if (fs.existsSync(settingsDir)) {
      for (let i = 1; i <= 10; i++) {
        const sf = path.join(settingsDir, `server_${i}.json`);
        if (!fs.existsSync(sf)) continue;
        const s = JSON.parse(fs.readFileSync(sf, 'utf8'));
        if (s.resultsPath && s.resultsPath.trim()) {
          const rp = s.resultsPath.trim();
          // Skip old homedir-based default
          if (isWindows && path.resolve(rp) === path.resolve(path.join(os.homedir(), 'results'))) continue;
          return rp;
        }
      }
    }
  } catch {}

  return defaultDir;
}

const RESULTS_DIR = getResultsDir();
const DB_FILE     = path.join(RESULTS_DIR, 'stats_cache.json');
console.log(`[STATS] Results directory: ${RESULTS_DIR}`);

// ─── In-memory database ───────────────────────────────────────────────────────
// drivers:   Map<steamId, DriverStats>
// fastestLaps: Map<"track|layout", Map<steamId, LapEntry>>
let db = {
  drivers:      {},   // steamId -> DriverStats
  fastestLaps:  {},   // "track|layout" -> { steamId -> LapEntry }
  sectorBests:  {},   // "track|layout" -> { sectorIdx -> [{ name, nation, car, time, timeMs }] }
  recentSessions: [], // last 50 full session results
  lastUpdated:  null,
  totalSessions: 0,
};

function msToLapTime(ms) {
  if (!ms) return null;
  const min = Math.floor(ms / 60000);
  const sec = Math.floor((ms % 60000) / 1000);
  const msec = ms % 1000;
  return `${min}:${String(sec).padStart(2,'0')}.${String(msec).padStart(3,'0')}`;
}

function lapTimeToMs(str) {
  if (!str) return Infinity;
  const [min, rest] = str.split(':');
  const [sec, ms] = rest.split('.');
  return parseInt(min)*60000 + parseInt(sec)*1000 + parseInt(ms||0);
}

function getOrCreateDriver(steamId, name, nation) {
  if (!db.drivers[steamId]) {
    db.drivers[steamId] = {
      steamId, name, nation,
      totalRaces: 0,
      totalQuals: 0,
      wins: 0,
      podiums: 0,
      totalLaps: 0,
      cleanLaps: 0,      // laps with no collisions
      wallHits: 0,
      carHits: 0,
      trackCuts: 0,
      totalDistanceKm: 0,
      fastestLapMs: Infinity,
      fastestLapStr: null,
      fastestLapTrack: null,
      bestQualMs: Infinity,
      bestQualStr: null,
      bestQualTrack: null,
      cars: {},           // car model -> count
      tracks: {},         // track name -> { races, wins, bestLapMs }
      recentResults: [],  // last 20 sessions [{date, track, type, pos, time}]
      xp: 0,
    };
  }
  const d = db.drivers[steamId];
  // Update name/nation if we have better data
  if (name && name !== 'Unknown') d.name = name;
  if (nation) d.nation = nation;
  return d;
}

function processFile(filePath, filename) {
  let raw;
  try { raw = JSON.parse(fs.readFileSync(filePath, 'utf8')); }
  catch { return; }

  if (!raw.drivers || !raw.laps) return;

  // Build lookup maps
  const driverMap = {};
  for (const drv of raw.drivers) {
    const key = `${drv.guid.a}_${drv.guid.b}`;
    driverMap[key] = drv;
  }
  const carMap = {};
  for (const car of (raw.cars || [])) {
    const key = `${car.car_id.a}_${car.car_id.b}`;
    carMap[key] = car;
  }

  const isRace = raw.session_type === 'Race';
  const isQual = raw.session_type === 'Qualify';
  const isPractice = !isRace && !isQual;
  const track  = raw.track_name || 'Unknown';
  const layout = raw.track_layout_name || '';
  const trackKey = layout ? `${track} ${layout}` : track;
  const trackKeyFull = `${track}|${layout}`;
  const sessionDate = filename.match(/(\d{8}_\d{6})/)?.[1] || '';
  const dateStr = sessionDate
    ? `${sessionDate.slice(0,4)}-${sessionDate.slice(4,6)}-${sessionDate.slice(6,8)} ${sessionDate.slice(9,11)}:${sessionDate.slice(11,13)}`
    : '';

  // Best lap per driver in this session
  const bestPerDriver = {};
  for (const lap of raw.laps) {
    const dk = `${lap.driver_key.a}_${lap.driver_key.b}`;
    if (!lap.time || lap.time <= 0) continue;
    if (!bestPerDriver[dk] || lap.time < bestPerDriver[dk].time) {
      bestPerDriver[dk] = lap;
    }
  }

  // Distance — calculate from known track lengths × laps, or estimate from lap times
  // All ACE EVO tracks and layouts with accurate lengths (km)
  // Sources: Coach Dave Academy, Traxion, official data
  const TRACK_KM = {
    // Mount Panorama / Bathurst
    'Mount Panorama': 6.213, 'Bathurst': 6.213,
    // Brands Hatch
    'Brands Hatch GP': 3.916, 'Brands Hatch Grand Prix': 3.916,
    'Brands Hatch Indy': 1.928,
    'Brands Hatch': 3.916,
    // Circuit de Spa-Francorchamps
    'Circuit de Spa Francorchamps': 7.004, 'Spa': 7.004,
    'Spa Francorchamps': 7.004, 'Spa-Francorchamps': 7.004,
    // Donington Park
    'Donington Park GP': 4.020, 'Donington Park Grand Prix': 4.020,
    'Donington Park National': 3.064,
    'Donington Park': 4.020,
    // Fuji Speedway
    'Fuji Speedway GP': 4.563, 'Fuji GP': 4.563,
    'Fuji Speedway Short': 3.430, 'Fuji Short': 3.430,
    'Fuji Speedway': 4.563, 'Fuji International Speedway': 4.563,
    // Imola
    'Imola': 4.909, 'Autodromo Internazionale Enzo e Dino Ferrari': 4.909,
    // Laguna Seca
    'Laguna Seca': 3.602, 'Weathertech Raceway Laguna Seca': 3.602,
    // Monza
    'Monza': 5.793, 'Autodromo Nazionale Monza': 5.793,
    // Nürburgring
    'Nurburgring GP': 5.137, 'Nürburgring GP': 5.137,
    'Nurburgring Grand Prix': 5.137, 'Nürburgring Grand Prix': 5.137,
    'Nurburgring Nordschleife': 25.378, 'Nürburgring Nordschleife': 25.378,
    'Nurburgring 24h': 25.378, 'Nürburgring 24h': 25.378,
    'Nordschleife': 20.832, // Touristenfahrten variant
    'Nurburgring': 5.137, 'Nürburgring': 5.137,
    // Oulton Park
    'Oulton Park International': 2.692,
    'Oulton Park Foster': 2.410,
    'Oulton Park Island': 1.674,
    'Oulton Park': 2.692,
    // Paul Ricard
    'Circuit Paul Ricard': 5.842, 'Paul Ricard': 5.842,
    'Paul Ricard 1A': 5.842, 'Paul Ricard 1C': 5.842,
    'Paul Ricard 3A': 3.830, 'Paul Ricard 3C': 3.830,
    // Red Bull Ring
    'Red Bull Ring GP': 4.318, 'Red Bull Ring Grand Prix': 4.318,
    'Red Bull Ring National': 3.348,
    'Red Bull Ring': 4.318,
    // Road Atlanta
    'Road Atlanta': 4.088, 'Road Atlanta GP': 4.088,
    // Sebring
    'Sebring': 6.020, 'Sebring International Raceway': 6.020,
    // Suzuka
    'Suzuka': 5.807, 'Suzuka GP': 5.807,
    'Suzuka East': 2.243, 'Suzuka West': 3.600,
    // Watkins Glen
    'Watkins Glen': 5.435, 'Watkins Glen International': 5.435,
    'Watkins Glen Short': 3.401, 'Watkins Glen Boot': 3.401,
  };
  const trackLenKm = (() => {
    const t = track + (layout ? ' ' + layout : '');
    for (const [k, v] of Object.entries(TRACK_KM)) {
      if (t.toLowerCase().includes(k.toLowerCase())) return v;
    }
    return null;
  })();

  // Track limit cuts per driver (InvestigationType_Racecar_Cut)
  const cutsByDriver = {};
  const sessPenalties = (raw.penalty_collection?.session_penalties) || [];
  for (const sp of sessPenalties) {
    const ck = `${sp.car_id.a}_${sp.car_id.b}`;
    // Find driver key from laps
    let dk = null;
    for (const lap of raw.laps) {
      const lapCk = `${lap.car_key.a}_${lap.car_key.b}`;
      if (lapCk === ck) { dk = `${lap.driver_key.a}_${lap.driver_key.b}`; break; }
    }
    if (!dk) continue;
    const cuts = [...(sp.pending_penalties||[]), ...(sp.cleared_penalties||[])]
      .filter(p => p.investigation === 'InvestigationType_Racecar_Cut').length;
    cutsByDriver[dk] = (cutsByDriver[dk] || 0) + cuts;
  }
  const hitsByDriver = {};
  for (const col of (raw.collisions || [])) {
    const ck = `${col.car_id.a}_${col.car_id.b}`;
    // Find driver for this car via laps
    let dk = null;
    for (const lap of raw.laps) {
      const lapCk = `${lap.car_key.a}_${lap.car_key.b}`;
      if (lapCk === ck) { dk = `${lap.driver_key.a}_${lap.driver_key.b}`; break; }
    }
    if (!dk) continue;
    if (!hitsByDriver[dk]) hitsByDriver[dk] = { wall: 0, car: 0 };
    if (col.collider === 'Car') hitsByDriver[dk].car++;
    else hitsByDriver[dk].wall++;
  }

  // Driver standings (finishing order for race)
  const finishOrder = (raw.driver_standings || []).map(s => `${s.a}_${s.b}`);
  const timeStandings = raw.time_standings || [];

  // Process each driver
  const processedDrivers = new Set();
  for (const lap of raw.laps) {
    const dk = `${lap.driver_key.a}_${lap.driver_key.b}`;
    if (processedDrivers.has(dk)) continue;
    processedDrivers.add(dk);

    const drvInfo  = driverMap[dk];
    if (!drvInfo) continue;
    const steamId  = drvInfo.player_id || dk;
    const name     = `${drvInfo.first_name||''} ${drvInfo.last_name||''}`.trim() || drvInfo.nickname || 'Unknown';
    const nation   = drvInfo.nation || '';

    // Find car
    let carName = '';
    for (const lap2 of raw.laps) {
      if (`${lap2.driver_key.a}_${lap2.driver_key.b}` === dk) {
        const ck = `${lap2.car_key.a}_${lap2.car_key.b}`;
        if (carMap[ck]) { carName = carMap[ck].model_displayname || ''; break; }
      }
    }

    const drv = getOrCreateDriver(steamId, name, nation);

    // Count laps
    const myLaps = raw.laps.filter(l => `${l.driver_key.a}_${l.driver_key.b}` === dk && l.time > 0);
    drv.totalLaps += myLaps.length;

    // Distance calculation
    if (trackLenKm && myLaps.length > 0) {
      // Known track length × valid laps
      drv.totalDistanceKm += trackLenKm * myLaps.length;
    } else if (myLaps.length > 0) {
      // Fallback: estimate from lap times assuming avg ~150 km/h
      const avgLapMs = myLaps.reduce((a,l) => a+l.time, 0) / myLaps.length;
      const estKmPerLap = (avgLapMs / 3600000) * 150;
      drv.totalDistanceKm += estKmPerLap * myLaps.length;
    }

    // Car usage
    if (carName) drv.cars[carName] = (drv.cars[carName] || 0) + 1;

    // Track stats
    if (!drv.tracks[trackKey]) drv.tracks[trackKey] = { races: 0, wins: 0, bestLapMs: Infinity };
    const tt = drv.tracks[trackKey];

    // Collisions
    const hits = hitsByDriver[dk] || { wall: 0, car: 0 };
    drv.wallHits += hits.wall;
    drv.carHits  += hits.car;
    drv.trackCuts += cutsByDriver[dk] || 0;

    // Clean laps = laps - total collision sessions (rough approximation)
    const cleanLapsThisSession = hits.wall === 0 && hits.car === 0 ? myLaps.length : Math.max(0, myLaps.length - hits.wall - hits.car);
    drv.cleanLaps += cleanLapsThisSession;

    const finishPos = finishOrder.indexOf(dk);

    if (isRace) {
      drv.totalRaces++;
      tt.races++;

      // XP approximation based on position
      const xp = finishPos === 0 ? 500 : finishPos === 1 ? 300 : finishPos === 2 ? 200 : 100;
      drv.xp += xp;

      if (finishPos === 0 && finishOrder.length >= 2) { drv.wins++; tt.wins++; }
      if (finishPos >= 0 && finishPos <= 2 && finishOrder.length >= 2) drv.podiums++;

      // Official race time
      const officialTime = timeStandings[finishPos];
      if (officialTime && officialTime > 0) {
        const myLapsFull = raw.laps
          .filter(l => `${l.driver_key.a}_${l.driver_key.b}` === dk && l.time > 0)
          .map((l, i) => ({
            lap: i + 1,
            time: msToLapTime(l.time),
            timeMs: l.time,
            splits: (l.split || []).map(s => s > 0 ? msToLapTime(s) : null),
          }));
        drv.recentResults.unshift({
          date: dateStr, track: trackKey, type: 'Race',
          pos: finishPos + 1, time: msToLapTime(officialTime),
          server: raw.server_name || '',
          laps: myLapsFull,
        });
        if (drv.recentResults.length > 20) drv.recentResults.pop();
      }
    }

    if (isQual) {
      drv.totalQuals++;
      const myBest = bestPerDriver[dk];
      if (myBest && myBest.time > 0) {
        if (myBest.time < drv.bestQualMs) {
          drv.bestQualMs  = myBest.time;
          drv.bestQualStr = msToLapTime(myBest.time);
          drv.bestQualTrack = trackKey;
        }
        const qualLaps = raw.laps
          .filter(l => `${l.driver_key.a}_${l.driver_key.b}` === dk && l.time > 0)
          .map((l, i) => ({
            lap: i + 1,
            time: msToLapTime(l.time),
            timeMs: l.time,
            splits: (l.split || []).map(s => s > 0 ? msToLapTime(s) : null),
          }));
        drv.recentResults.unshift({
          date: dateStr, track: trackKey, type: 'Qual',
          pos: finishPos >= 0 ? finishPos + 1 : null,
          time: msToLapTime(myBest.time),
          server: raw.server_name || '',
          laps: qualLaps,
        });
        if (drv.recentResults.length > 20) drv.recentResults.pop();
      }
    }

    if (isPractice) {
      const myBest = bestPerDriver[dk];
      if (myBest && myBest.time > 0) {
        const pracLaps = raw.laps
          .filter(l => `${l.driver_key.a}_${l.driver_key.b}` === dk && l.time > 0)
          .map((l, i) => ({
            lap: i + 1,
            time: msToLapTime(l.time),
            timeMs: l.time,
            splits: (l.split || []).map(s => s > 0 ? msToLapTime(s) : null),
          }));
        drv.recentResults.unshift({
          date: dateStr, track: trackKey, type: 'Practice',
          pos: null,
          time: msToLapTime(myBest.time),
          server: raw.server_name || '',
          laps: pracLaps,
        });
        if (drv.recentResults.length > 20) drv.recentResults.pop();
      }
    }

    // Fastest lap overall
    const myBestLap = bestPerDriver[dk];
    if (myBestLap && myBestLap.time > 0) {
      if (myBestLap.time < drv.fastestLapMs) {
        drv.fastestLapMs  = myBestLap.time;
        drv.fastestLapStr = msToLapTime(myBestLap.time);
        drv.fastestLapTrack = trackKey;
      }
      if (myBestLap.time < tt.bestLapMs) tt.bestLapMs = myBestLap.time;

      // Fastest lap leaderboard + sector bests
      if (!db.fastestLaps[trackKeyFull]) db.fastestLaps[trackKeyFull] = {};
      if (!db.sectorBests[trackKeyFull]) db.sectorBests[trackKeyFull] = {};

      // Build dk -> steamId and dk -> driverInfo maps for sector lookup
      const driverKeyToSteamId = {};
      const driversByKey = {};
      for (const drv of (raw.drivers || [])) {
        const dk2 = drv.guid ? `${drv.guid.a}_${drv.guid.b}` : null;
        if (!dk2) continue;
        driversByKey[dk2] = drv;
        driverKeyToSteamId[dk2] = drv.player_id || dk2;
      }
      const rawLaps = raw.laps || [];

      // Track sector bests from ALL laps (not just best overall lap)
      for (const lap of rawLaps) {
        if (!lap.split || !lap.time || lap.time <= 0) continue;
        const lapDk = `${lap.driver_key.a}_${lap.driver_key.b}`;
        const lapSteamId = driverKeyToSteamId[lapDk];
        if (!lapSteamId) continue;
        const lapDriver = driversByKey[lapDk] || {};
        const lapName = [lapDriver.first_name, lapDriver.last_name].filter(Boolean).join(' ') || lapDriver.nickname || lapSteamId;
        const lapNation = lapDriver.nation || '';
        const lapCk = `${lap.car_key.a}_${lap.car_key.b}`;
        const lapCar = carMap[lapCk]?.model_displayname || '';

        lap.split.forEach((splitMs, si) => {
          if (!splitMs || splitMs <= 0 || splitMs > 300000) return;
          if (!db.sectorBests[trackKeyFull][si]) db.sectorBests[trackKeyFull][si] = {};
          const existing = db.sectorBests[trackKeyFull][si][lapSteamId];
          if (!existing || splitMs < existing.timeMs) {
            db.sectorBests[trackKeyFull][si][lapSteamId] = {
              steamId: lapSteamId, name: lapName, nation: lapNation,
              car: lapCar, timeMs: splitMs, time: msToLapTime(splitMs),
              date: dateStr, server: raw.server_name || '',
            };
          }
        });
      }
      const existing = db.fastestLaps[trackKeyFull][steamId];
      if (!existing || myBestLap.time < existing.timeMs) {
        // Parse sector times from lap.split array
        const sectors = (myBestLap.split||[]).map(ms => ms > 0 ? msToLapTime(ms) : null).filter(Boolean);
        db.fastestLaps[trackKeyFull][steamId] = {
          steamId, name, nation, car: carName,
          timeMs: myBestLap.time,
          time: msToLapTime(myBestLap.time),
          date: dateStr,
          server: raw.server_name || '',
          sectors,
        };
      }
    }
  }

  // ─── Collect full session results for Race Results tab ───────────────────────
  {
    const gridOrder = (raw.grid_standings || []).map(s => `${s.a}_${s.b}`);
    const leaderTime = timeStandings[0] || 0;
    const sessionResults = [];

    // Determine driver order: race uses finishOrder, practice/qual sorted by best lap
    const allDriverKeys = [...processedDrivers];
    const driverOrder = isRace && finishOrder.length > 0
      ? finishOrder
      : [...allDriverKeys].sort((a, b) => {
          const ba = bestPerDriver[a]?.time || Infinity;
          const bb = bestPerDriver[b]?.time || Infinity;
          return ba - bb;
        });

    for (let pos = 0; pos < driverOrder.length; pos++) {
      const dk = driverOrder[pos];
      const drvInfo = driverMap[dk];
      if (!drvInfo) continue;

      const name = `${drvInfo.first_name||''} ${drvInfo.last_name||''}`.trim() || drvInfo.nickname || 'Unknown';
      const nation = drvInfo.nation || '';

      // Car name
      let carName = '';
      for (const lap2 of raw.laps) {
        if (`${lap2.driver_key.a}_${lap2.driver_key.b}` === dk) {
          const ck = `${lap2.car_key.a}_${lap2.car_key.b}`;
          if (carMap[ck]) { carName = carMap[ck].model_displayname || ''; break; }
        }
      }

      // Laps & best lap
      const myLaps = raw.laps.filter(l => `${l.driver_key.a}_${l.driver_key.b}` === dk && l.time > 0);
      const bestLapMs = myLaps.length ? Math.min(...myLaps.map(l => l.time)) : null;

      // Race time & gap (race only)
      const raceTime = isRace ? (timeStandings[pos] || 0) : 0;
      const lapCount = myLaps.length;
      let gap = '';
      if (isRace) {
        const leaderLaps = raw.laps.filter(l => `${l.driver_key.a}_${l.driver_key.b}` === driverOrder[0] && l.time > 0).length;
        if (pos === 0) { gap = '—'; }
        else if (lapCount < leaderLaps) { gap = `${leaderLaps - lapCount} Lap${leaderLaps - lapCount > 1 ? 's' : ''}`; }
        else if (raceTime > 0 && leaderTime > 0) { gap = msToLapTime(raceTime - leaderTime); }
      } else {
        // Practice/Qual: gap = diff to best lap of leader
        const leaderBest = bestPerDriver[driverOrder[0]]?.time || 0;
        if (pos === 0) { gap = '—'; }
        else if (bestLapMs && leaderBest) { gap = '+' + msToLapTime(bestLapMs - leaderBest); }
      }

      // Incidents
      const hits = hitsByDriver[dk] || { wall: 0, car: 0 };
      const incidents = hits.wall + hits.car;

      // Grid position & delta (race only)
      const gridPos = isRace ? gridOrder.indexOf(dk) : -1;
      const posChange = gridPos >= 0 ? gridPos - pos : 0;

      // Points (race only)
      const POINTS = [25,18,15,12,10,8,6,4,2,1];
      const points = isRace && pos < POINTS.length ? POINTS[pos] : 0;

      // Per-lap detail
      const lapDetail = myLaps.map((l, i) => ({
        lap: i + 1,
        time: msToLapTime(l.time),
        timeMs: l.time,
        splits: (l.split || []).map(s => s > 0 ? msToLapTime(s) : null),
      }));

      sessionResults.push({
        pos: pos + 1,
        name, nation, car: carName,
        laps: lapCount,
        time: isRace && raceTime > 0 ? msToLapTime(raceTime) : (bestLapMs ? msToLapTime(bestLapMs) : ''),
        bestLap: bestLapMs ? msToLapTime(bestLapMs) : '',
        bestLapMs: bestLapMs || Infinity,
        gap,
        incidents,
        points,
        posChange,
        gridPos: gridPos >= 0 ? gridPos + 1 : null,
        lapDetail,
      });
    }

    if (sessionResults.length > 0) {
      db.recentSessions.unshift({
        date: dateStr,
        track: trackKey,
        type: raw.session_type || 'Unknown',
        server: raw.server_name || '',
        results: sessionResults,
      });
      if (db.recentSessions.length > 100) db.recentSessions.pop();
      if (_sessionCompleteCallback && _initialScanDone) {
        try { _sessionCompleteCallback({ track: trackKey, type: raw.session_type || 'Unknown', results: sessionResults }); } catch {}
      }
    }
  }

  db.totalSessions++;
}

// ─── Scan all results files ───────────────────────────────────────────────────
const processedFiles = new Set();

function scanResultsDir() {
  if (!fs.existsSync(RESULTS_DIR)) return;

  const files = fs.readdirSync(RESULTS_DIR)
    .filter(f => f.endsWith('.json') && f.startsWith('results_'));

  let newFiles = 0;
  for (const filename of files) {
    if (processedFiles.has(filename)) continue;
    processedFiles.add(filename);
    processFile(path.join(RESULTS_DIR, filename), filename);
    newFiles++;
  }

  if (newFiles > 0) {
    console.log(`[STATS] Processed ${newFiles} new result file(s). Total sessions: ${db.totalSessions}`);
    db.lastUpdated = new Date().toISOString();
    saveCache();
  }
}

function saveCache() {
  try {
    fs.writeFileSync(DB_FILE, JSON.stringify(buildPublicStats(), null, 2), 'utf8');
  } catch (e) { console.warn('[STATS] Could not save cache:', e.message); }
}

// ─── Build public stats payload ───────────────────────────────────────────────
function buildPublicStats() {
  // Driver rankings — sorted by wins desc, then total races, then XP
  const driverList = Object.values(db.drivers).map(d => {
    const totalHits = d.wallHits + d.carHits;
    const safetyPct = d.totalLaps > 0
      ? Math.round(((d.totalLaps - totalHits) / d.totalLaps) * 1000) / 10
      : 100;
    const topCar = Object.entries(d.cars).sort((a,b)=>b[1]-a[1])[0]?.[0] || '';
    return {
      steamId: d.steamId,
      name: d.name,
      nation: d.nation,
      xp: d.xp,
      totalRaces: d.totalRaces,
      totalQuals: d.totalQuals,
      wins: d.wins,
      podiums: d.podiums,
      totalLaps: d.totalLaps,
      cleanLaps: d.cleanLaps,
      wallHits: d.wallHits,
      carHits: d.carHits,
      trackCuts: d.trackCuts,
      safetyPct,
      totalDistanceKm: Math.round(d.totalDistanceKm * 10) / 10,
      fastestLap: d.fastestLapStr,
      fastestLapTrack: d.fastestLapTrack,
      bestQual: d.bestQualStr,
      bestQualTrack: d.bestQualTrack,
      topCar,
      recentResults: d.recentResults.slice(0, 10),
    };
  });

  driverList.sort((a, b) => b.wins - a.wins || b.totalRaces - a.totalRaces || b.xp - a.xp);

  // Fastest laps — sorted per track
  const fastestLaps = {};
  for (const [trackKey, drivers] of Object.entries(db.fastestLaps)) {
    const [track, layout] = trackKey.split('|');
    const trackName = layout ? `${track} ${layout}` : track;
    fastestLaps[trackName] = Object.values(drivers)
      .sort((a, b) => a.timeMs - b.timeMs)
      .map((e, i) => ({ pos: i + 1, ...e }));
  }

  // Safety ratings — cleanest and dirtiest
  const withRaces = driverList.filter(d => d.totalLaps >= 5);
  const cleanest  = [...withRaces].sort((a,b) => b.safetyPct - a.safetyPct).slice(0, 15);
  const dirtiest  = [...withRaces].sort((a,b) => a.safetyPct - b.safetyPct).slice(0, 15);
  const mostHits  = [...withRaces].sort((a,b) => (b.wallHits+b.carHits) - (a.wallHits+a.carHits)).slice(0, 15);
  // Best track limits: fewest cuts per lap (min 5 laps), then fewest total
  const bestTrackLimits = [...withRaces]
    .filter(d => d.totalLaps >= 5)
    .sort((a,b) => {
      const aRate = a.trackCuts / a.totalLaps;
      const bRate = b.trackCuts / b.totalLaps;
      return aRate - bRate || a.trackCuts - b.trackCuts;
    }).slice(0, 15);

  // Build sectorBests for API — track -> sector -> sorted array
  const sectorBests = {};
  for (const [trackKeyFull, sectors] of Object.entries(db.sectorBests)) {
    const [track, layout] = trackKeyFull.split('|');
    const trackName = layout ? `${track} ${layout}` : track;
    sectorBests[trackName] = {};
    for (const [si, driverMap2] of Object.entries(sectors)) {
      sectorBests[trackName][si] = Object.values(driverMap2)
        .sort((a, b) => a.timeMs - b.timeMs)
        .slice(0, 10);
    }
  }

  return {
    lastUpdated: db.lastUpdated,
    totalSessions: db.totalSessions,
    totalDrivers: driverList.length,
    driverRankings: driverList,
    fastestLaps,
    sectorBests,
    safetyRatings: { cleanest, dirtiest, mostHits, bestTrackLimits },
    recentSessions: db.recentSessions.slice(0, 30),
  };
}

// ─── File watcher ─────────────────────────────────────────────────────────────
function startWatcher() {
  // Initial scan — callback disabled during this
  scanResultsDir();
  _initialScanDone = true;
  console.log('[STATS] Initial scan complete — Discord notifications enabled for new events');

  // Watch for new files
  if (!fs.existsSync(RESULTS_DIR)) return;
  fs.watch(RESULTS_DIR, (event, filename) => {
    if (filename && filename.endsWith('.json') && filename.startsWith('results_')) {
      setTimeout(scanResultsDir, 500); // small delay to ensure file is fully written
    }
  });
  console.log(`[STATS] Watching ${RESULTS_DIR} for new result files`);
}

// ─── Live lap recording (called from server.js log parser) ────────────────────
function recordLiveLap({ steamId, name, nation, car, track, timeMs, sectors, server }) {
  if (!steamId || !track || !timeMs || timeMs <= 0) return;

  const trackKey = track;
  const dateStr = new Date().toISOString().slice(0, 10);
  const timeStr = msToLapTime(timeMs);

  // Update driver record
  const drv = getOrCreateDriver(steamId, name, nation);
  drv.totalLaps++;

  // Update fastest lap for this track
  if (!db.fastestLaps[trackKey]) db.fastestLaps[trackKey] = {};
  const existing = db.fastestLaps[trackKey][steamId];
  if (!existing || timeMs < existing.timeMs) {
    db.fastestLaps[trackKey][steamId] = {
      steamId, name, nation, car,
      timeMs, time: timeStr,
      date: dateStr, server: server || '',
    };
  }

  // Update sector bests
  if (sectors && sectors.length) {
    if (!db.sectorBests[trackKey]) db.sectorBests[trackKey] = {};
    for (let si = 0; si < sectors.length; si++) {
      const sMs = sectors[si];
      if (!sMs || sMs <= 0) continue;
      if (!db.sectorBests[trackKey][si]) db.sectorBests[trackKey][si] = [];
      const arr = db.sectorBests[trackKey][si];
      const idx = arr.findIndex(e => e.steamId === steamId);
      if (idx === -1) {
        arr.push({ steamId, name, nation, car, timeMs: sMs, time: msToLapTime(sMs) });
      } else if (sMs < arr[idx].timeMs) {
        arr[idx] = { steamId, name, nation, car, timeMs: sMs, time: msToLapTime(sMs) };
      }
      arr.sort((a, b) => a.timeMs - b.timeMs);
    }
  }

  // Debounced save — don't write on every lap
  if (!recordLiveLap._saveTimer) {
    recordLiveLap._saveTimer = setTimeout(() => {
      recordLiveLap._saveTimer = null;
      db.lastUpdated = new Date().toISOString();
      saveCache();
    }, 10000); // save at most every 10s
  }
}

module.exports = { startWatcher, buildPublicStats, scanResultsDir, recordLiveLap, onSessionComplete, deleteFastestLap };

let _sessionCompleteCallback = null;
let _initialScanDone = false;
function onSessionComplete(cb) { _sessionCompleteCallback = cb; }

function deleteFastestLap(track, steamId) {
  if (db.fastestLaps[track] && db.fastestLaps[track][steamId]) {
    delete db.fastestLaps[track][steamId];
    // Also remove from sectorBests
    if (db.sectorBests[track]) {
      for (const si of Object.keys(db.sectorBests[track])) {
        db.sectorBests[track][si] = (db.sectorBests[track][si] || []).filter(e => e.steamId !== steamId);
      }
    }
    saveCache();
    return true;
  }
  return false;
}
