// ur2d-db.jsx — Bituko's UR2D2 results database (Supabase, public read-only REST API).
// THE SPLIT (user, 2026-10-05): the CURRENT season reads its Google Sheet (store liveSource);
// every FINISHED season reads this database. TEST SCOPE: CL only — add leagues to UR_DB_LEAGUES.
// Load order: shared → store → THIS → roster → …  (flags/extends UR2D_ADAPTERS seasons at load).

const UR_DB = {
  url: 'https://njzcjsiyrlkvqkyfebbp.supabase.co/rest/v1/',
  key: 'sb_publishable_9KrDUS22yrFEq8truAzkYw_HiqkP69B', // browser-safe, read-only
};
const UR_DB_LEAGUES = { ul: 'UL', hl: 'CL', jl: 'JL', al: 'AL' }; // site league id → DB league code — every finished season
const UR_DB_HALVES_FROM = { jl: 12, al: 23 }; // first season split into A/B halves (DB seasons.division)
const UR_DB_CODES = { ul: 'UL', hl: 'CL', jl: 'JL', al: 'AL' }; // every league — small finished-season reads (Teams page order)
const _dbNorm = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9]/g, '');

const _dbCache = {};
function dbGet(path) {
  if (!_dbCache[path]) {
    _dbCache[path] = fetch(UR_DB.url + path, { headers: { apikey: UR_DB.key } })
      .then((r) => { if (!r.ok) throw new Error('DB HTTP ' + r.status + ' · ' + path); return r.json(); })
      .catch((e) => { delete _dbCache[path]; throw e; });
  }
  return _dbCache[path];
}
// The API returns at most 1,000 rows per request — page through bigger reads.
async function dbGetAll(path) {
  const out = [];
  for (let off = 0; ; off += 1000) {
    const rows = await dbGet(path + (path.includes('?') ? '&' : '?') + 'limit=1000&offset=' + off);
    out.push(...rows);
    if (rows.length < 1000) return out;
  }
}
async function dbSeasonId(code, number, division) {
  const rows = await dbGet('seasons?select=id,number,division,leagues!inner(code)&leagues.code=eq.' + code);
  const hit = rows.find((s) => s.number === number && (s.division || null) === (division || null));
  return hit ? hit.id : null;
}
const dbTeams = () => dbGet('teams?select=id,name,short_name');

// Flag every finished season as served from the archive (Mega Table first, then DB) and add the ones the store doesn't
// list (incl. JL/AL A/B halves). Added entries are `dbOnly`: calendar-index consumers skip them, so inserting e.g. AL S22
// between S23A and S21 can't shift the league↔calendar alignment of the seasons that already existed.
(function urDbRegisterSeasons() {
  const A = window.UR2D_ADAPTERS || {};
  const rank = (n, suf) => n * 10 + (suf === 'B' ? 2 : suf === 'A' ? 1 : 0);
  Object.entries(UR_DB_LEAGUES).forEach(([lg, code]) => {
    const ad = A[lg];
    const cur = ad && ad.seasons.find((s) => s.isCurrent);
    if (!cur) return;
    const hf = UR_DB_HALVES_FROM[lg] || Infinity, curR = rank(cur.number, cur.suffix);
    for (let n = cur.number; n >= 1; n--) (n >= hf ? ['B', 'A'] : [null]).forEach((suf) => {
      if (rank(n, suf) >= curR) return;
      const label = 'S' + n + (suf || '');
      let s = ad.seasons.find((x) => x.label === label);
      if (!s) { s = { number: n, suffix: suf, label, year: null, isCurrent: false, snapshot: null, dbOnly: true }; ad.seasons.push(s); }
      s.db = { code, number: n, division: suf };
    });
    // Drop placeholder stubs the DB doesn't know (e.g. JL 'S19' — that season was split into S19A / S19B).
    ad.seasons = ad.seasons.filter((s) => s.isCurrent || s.db || s.liveSource || s.snapshot || rank(s.number, s.suffix) > curR);
    ad.seasons.sort((a, b) => (b.isCurrent - a.isCurrent) || rank(b.number, b.suffix) - rank(a.number, a.suffix));
  });
  // AL S22 raced the same 10 cars as S21 / S23A (same team names).
  const alCars = A.al && (A.al.seasons.find((s) => s.isCurrent) || {}).driverLiveriesByTeam;
  if (alCars) A.al.seasons.filter((s) => s.number === 22 && !s.driverLiveriesByTeam).forEach((s) => { s.driverLiveriesByTeam = alCars; });
})();

// Team look-ups by code: this season's config first, then the league's other seasons (same team code
// within one league = same team in the DB). Liveries: this season ONLY — never another season's car.
function _dbMeta(ad, sdef, field, code) {
  for (const s of [sdef, ...ad.seasons]) for (const o of [s, s.liveSource || {}]) if (o[field] && o[field][code]) return o[field][code];
  return null;
}
function _dbOwn(sdef, field, code) {
  return (sdef[field] && sdef[field][code]) || (sdef.liveSource && sdef.liveSource[field] && sdef.liveSource[field][code]) || null;
}

// MEGA TABLES ARE THE SOURCE OF TRUTH (user, 2026-10-05). A finished season that the league's Mega Table archive
// (ur2d-archive.jsx) covers is read from it; the DB fills seasons no Mega Table covers. Discrepancies: DB-DISCREPANCIES.md.
async function _megaSeason(league, sdef) {
  if (!window.UR_ARCHIVE_SOURCES || !window.UR_ARCHIVE_SOURCES[league] || sdef.suffix || typeof window.loadArchive !== 'function') return null;
  const a = await window.loadArchive(league);
  const s = a && a.seasons && a.seasons[sdef.number];
  return s && s.drivers && s.drivers.length ? s : null;
}

// Final standings of one finished season → the store's raw { teams, drivers } shape (normalizeStandings does the rest).
async function loadDbStandings(league, sdef) {
  const raw = await _loadDbStandingsBase(league, sdef);
  // Mega Table by default, disputed values tagged (ur2d-megacheck.jsx).
  return typeof window.applyMegaOverlay === 'function' ? window.applyMegaOverlay(league, sdef, raw) : raw;
}
async function _loadDbStandingsBase(league, sdef) {
  const ad = window.UR2D_ADAPTERS[league];
  const mega = await _megaSeason(league, sdef).catch(() => null);
  if (mega) {
    const teams = await dbTeams().catch(() => []);
    const dbName = {};
    teams.forEach((t) => { if (!dbName[t.short_name]) dbName[t.short_name] = t.name; });
    const megaTeams = mega.teamsComplete !== false && mega.teams && mega.teams.length ? mega.teams : null;
    const dbT = megaTeams ? null : await loadDbStandingsFromDb(league, sdef).catch(() => null);
    return {
      source: 'megatable',
      teams: megaTeams ? megaTeams.map((t) => ({
        short: t.short, name: _dbOwn(sdef, 'teamNames', t.short) || dbName[t.short] || t.short, pts: Number(t.pts) || 0,
        color: _dbMeta(ad, sdef, 'teamColors', t.short), minilogo: _dbMeta(ad, sdef, 'teamMinilogos', t.short), livery: _dbOwn(sdef, 'teamLiveries', t.short),
      })) : (dbT ? dbT.teams : null),
      drivers: mega.drivers.map((d) => ({ name: d.name, team: d.team || null, pts: Number(d.pts) || 0, wins: d.wins || 0, podiums: d.podiums || 0, poles: d.poles || 0, fastestLaps: d.fastestLaps || 0, country: d.country || undefined })),
    };
  }
  return loadDbStandingsFromDb(league, sdef);
}
async function loadDbStandingsFromDb(league, sdef) {
  const ad = window.UR2D_ADAPTERS[league];
  const { code, number, division } = sdef.db;
  const sid = await dbSeasonId(code, number, division);
  if (sid == null) throw new Error('DB has no ' + code + ' S' + number + (division || ''));
  const [drv, tms, res, teams] = await Promise.all([
    dbGet('v_driver_standings?select=driver_id,driver,points,wins,podiums,poles,fastest_laps&season_id=eq.' + sid + '&order=standing'),
    dbGet('v_team_standings?select=team_id,team,short_name,points&season_id=eq.' + sid + '&order=standing'),
    dbGetAll('v_result_points?select=driver_id,team_id,round&session=eq.race&season_id=eq.' + sid + '&order=round'),
    dbTeams(),
  ]);
  const shortOf = {};
  teams.forEach((t) => { shortOf[t.id] = t.short_name; });
  // A driver's season team = the team they started most races for (ties → the later rounds); none → Independent.
  const tally = {};
  res.forEach((r) => {
    const k = r.team_id == null ? 'IND' : (shortOf[r.team_id] || 'IND');
    const t = (tally[r.driver_id] = tally[r.driver_id] || {});
    t[k] = (t[k] || 0) + 1 + r.round / 1000;
  });
  const seasonTeam = (id) => { const t = tally[id]; return t ? Object.keys(t).sort((a, b) => t[b] - t[a])[0] : null; };
  const drivers = drv.map((d) => ({
    name: d.driver, team: seasonTeam(d.driver_id), pts: Number(d.points) || 0,
    wins: d.wins || 0, podiums: d.podiums || 0, poles: d.poles || 0, fastestLaps: d.fastest_laps || 0,
  }));
  // DB gap (CL S21: most results carry no team) → fill missing teams from our own sheet for that season, if we have one.
  const noTeam = drivers.filter((d) => !d.team || d.team === 'IND');
  if (sdef.liveSource && drivers.length && noTeam.length / drivers.length > 0.25 && typeof window.loadLeaderboardStandings === 'function') {
    try {
      const sheet = await window.loadLeaderboardStandings({ ...sdef.liveSource, sheetTeamNames: sdef.liveSource.teamNames || {} });
      const byName = {};
      (sheet.drivers || []).forEach((d) => { if (d.team) byName[_dbNorm(d.name)] = d.team; });
      noTeam.forEach((d) => { const t = byName[_dbNorm(d.name)]; if (t) d.team = t; });
    } catch (e) { console.warn('[ur2d-db] sheet team fill failed:', e.message); }
  }
  return {
    teams: tms.map((t) => ({
      short: t.short_name, name: _dbOwn(sdef, 'teamNames', t.short_name) || t.team, pts: Number(t.points) || 0,
      color: _dbMeta(ad, sdef, 'teamColors', t.short_name), minilogo: _dbMeta(ad, sdef, 'teamMinilogos', t.short_name),
      livery: _dbOwn(sdef, 'teamLiveries', t.short_name),
    })),
    drivers,
  };
}

// Constructors' order (codes, champion first) of a FINISHED season — any league. Used by the Teams page.
async function dbTeamOrder(league, sdef) {
  const code = UR_DB_CODES[league];
  if (!code || !sdef) throw new Error('no season');
  const sid = await dbSeasonId(code, sdef.number, sdef.suffix || null);
  if (sid == null) throw new Error('DB has no ' + code + ' ' + sdef.label);
  const rows = await dbGet('v_team_standings?select=short_name,standing&season_id=eq.' + sid + '&order=standing');
  if (!rows.length) throw new Error('no team standings');
  return rows.map((r) => r.short_name);
}

// Older UL calendars (S1 → the oldest hand-entered season) from the DB: round, track, date. Calendar extras (GP name,
// flag, creator, Workshop id, laps, milestone, UL time, break) merge in from data/ul-schedules.js (operator posts) by round.
// Appended oldest-last to UR2D_CALENDARS (index-aligned consumers unaffected); pages re-read on 'ur2d-calendars'.
const _fz = (s) => String(s || '').normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]/g, '');
const _sameTrack = (a, b) => { const x = _fz(a), y = _fz(b); return !!x && !!y && (x.includes(y) || y.includes(x)); };
// Track art for archive rounds, keyed by normalized track name (schedule or DB spelling) → slug:
// hero assets/tracks/sm/<slug>.jpg + minimap assets/minimaps/sm/<slug>.png. Add a line per track as art arrives.
const UR_TRACK_ART = { injespeedium: 'inje', inje: 'inje' };
function _applyTrackArt(entry) {
  entry.races.forEach((r) => { const s = UR_TRACK_ART[_fz(r.track)]; if (s && !r.minimap) { r.minimap = 'assets/minimaps/sm/' + s + '.png'; r.trackImage = 'assets/tracks/sm/' + s + '.jpg'; } });
  return entry;
}
function _applyUlSchedule(entry) {
  _applyTrackArt(entry);
  const S = (window.UR_UL_SCHEDULES || {})[entry.label];
  if (!S) return entry;
  const ov = S.races.map(([round, track, grandPrix, iso, creator, ws, laps, special]) => ({ round, track, grandPrix, iso, creator, ws, laps, special }));
  entry.races.forEach((r) => {
    let o = ov.find((x) => x.round === r.round);
    if (!o || !_sameTrack(o.track, r.track)) { const alt = ov.find((x) => _sameTrack(x.track, r.track)); if (alt) o = alt; } // rescheduled round
    if (!o) return;
    Object.assign(r, {
      grandPrix: o.grandPrix || null, iso: o.iso || null, creator: o.creator || null, laps: o.laps || null, special: o.special || null,
      trackUrl: o.ws ? 'https://steamcommunity.com/sharedfiles/filedetails/?id=' + o.ws : null,
      utcByLeague: S.utc ? { ul: (S.utcRounds && S.utcRounds[r.round]) || S.utc } : null,
    });
  });
  if (S.period) entry.period = S.period;
  entry.midSeasonBreakAfter = S.breakAfter || null;
  if (S.utc) entry.ulTimeLabel = [...new Set(entry.races.map((r) => r.utcByLeague && r.utcByLeague.ul).filter(Boolean))].join(' / ');
  return entry;
}
(async function urDbLoadCalendars() {
  const CALS = window.UR2D_CALENDARS;
  if (!CALS) return;
  try {
    const seasons = await dbGet('seasons?select=id,number,division,leagues!inner(code)&leagues.code=eq.UL');
    const have = new Set(CALS.map((c) => c.label));
    const todo = seasons.filter((s) => !s.division && !have.has('S' + s.number)).sort((a, b) => b.number - a.number);
    if (!todo.length) return;
    const races = await dbGetAll('v_races?select=season_id,round,track,race_date&season_id=in.(' + todo.map((s) => s.id).join(',') + ')&order=season_id,round');
    todo.forEach((s) => {
      const rs = races.filter((r) => r.season_id === s.id).map((r) => ({ round: r.round, track: r.track, grandPrix: null, date: r.race_date, trackImage: null, minimap: null }));
      if (!rs.length) return;
      const year = Number(String(rs[0].date || '').slice(0, 4)) || null;
      CALS.push(_applyUlSchedule({ label: 'S' + s.number, period: year ? String(year) : null, year, isCurrent: false, fromDb: true, races: rs }));
    });
    window.dispatchEvent(new Event('ur2d-calendars'));
  } catch (e) { console.warn('[ur2d-db] calendar load failed:', e.message); }
})();

// CL / JL ↔ UL raceday mapping. Posted schedules first (data/cl-schedules.js, data/jl-schedules.js), then the standing rule:
//   CL S1–S6 = halves of UL S7–S9 · CL S7+ = UL season + 3
//   JL S4–S11 = halves of UL S11–S14 · JL S12A/B+ = UL season + 3 (A = R1–5, B = R6–10) · JL S1–S3 unmapped
const _racedaySchedules = () => ({ CL: window.UR_CL_SCHEDULES || {}, JL: window.UR_JL_SCHEDULES || {} });
function urUlSeasonFor(code, label) {
  const S = (_racedaySchedules()[code] || {})[label];
  if (S) return { ul: S.ul, half: S.ulHalf || null };
  const m = String(label || '').match(/^S(\d+)([AB])?$/);
  if (!m) return null;
  const n = Number(m[1]);
  if (code === 'CL') return n >= 7 ? { ul: 'S' + (n + 3), half: null } : null;
  if (code === 'JL') {
    if (n >= 12 && m[2]) return { ul: 'S' + (n + 3), half: m[2] === 'A' ? 1 : 2 };
    if (n >= 4 && n <= 11) return { ul: 'S' + (11 + Math.floor((n - 4) / 2)), half: ((n - 4) % 2) + 1 };
  }
  return null;
}
function urSeasonsOnUl(code, ulLabel) {
  const own = Object.entries(_racedaySchedules()[code] || {}).filter(([, s]) => s.ul === ulLabel).map(([k]) => k);
  if (own.length) return own;
  const n = Number(String(ulLabel || '').replace(/\D/g, ''));
  if (code === 'CL') return n >= 10 ? ['S' + (n - 3)] : [];
  if (code === 'JL') return n >= 15 ? ['S' + (n - 3) + 'A', 'S' + (n - 3) + 'B'] : [];
  return [];
}
// A league's start time(s) on a UL raceday season, as posted — e.g. '16:20 / 17:20' (null when not posted).
function urTimeLabelOn(code, ulLabel) {
  const all = _racedaySchedules()[code] || {};
  const t = urSeasonsOnUl(code, ulLabel).flatMap((k) => { const s = all[k]; return s ? s.races.map((r) => (s.utcRounds && s.utcRounds[r[0]]) || s.utc) : []; });
  const u = [...new Set(t.filter(Boolean))];
  return u.length ? u.join(' / ') : null;
}

// Every team in the DB with its history (any league) — the Teams page's Historic tab. `current` = raced in a league's
// latest season. Seasons are ordered on a UL-equivalent clock (CL/JL via urUlSeasonFor, AL ≈ +3) so first → last reads right.
let _dbTeamHistory = null;
function dbTeamHistory() {
  if (!_dbTeamHistory) {
    _dbTeamHistory = (async () => {
      const [teams, rows, seasons] = await Promise.all([dbTeams(), dbGetAll('v_team_standings?select=team_id,league,number,division,standing,wins'), dbGet('seasons?select=number,division,leagues(code)')]);
      const rank = (n, d) => n * 10 + (d === 'B' ? 2 : d === 'A' ? 1 : 0);
      const latest = {};
      seasons.forEach((s) => { const lg = s.leagues && s.leagues.code; const r = rank(s.number, s.division); if (lg && (!latest[lg] || r > latest[lg])) latest[lg] = r; });
      const ulEq = (lg, n, d) => {
        const half = d === 'B' ? 0.5 : 0;
        if (lg === 'UL') return n;
        if (lg === 'AL') return n + 3 + half;
        const m = urUlSeasonFor(lg, 'S' + n + (d || ''));
        return m ? Number(m.ul.slice(1)) + (m.half === 2 ? 0.5 : 0) : n + 3 + half;
      };
      const by = {};
      rows.forEach((r) => {
        const t = (by[r.team_id] = by[r.team_id] || { seasons: [], titles: [], current: false, winsBy: { UL: 0, CL: 0, JL: 0, AL: 0 } });
        t.winsBy[r.league] = (t.winsBy[r.league] || 0) + (Number(r.wins) || 0);
        const isLatest = rank(r.number, r.division) === latest[r.league];
        t.seasons.push({ lg: r.league, label: 'S' + r.number + (r.division || ''), at: ulEq(r.league, r.number, r.division) });
        if (isLatest) t.current = true;
        else if (r.standing === 1) t.titles.push(r.league + ' S' + r.number + (r.division || ''));
      });
      const codeUses = {};
      teams.forEach((t) => { codeUses[t.short_name] = (codeUses[t.short_name] || 0) + 1; });
      return teams.filter((t) => by[t.id]).map((t) => {
        const tier = { UL: 0, CL: 1, JL: 2, AL: 3 }, h = by[t.id], ss = h.seasons.sort((a, b) => a.at - b.at || (tier[b.lg] ?? 9) - (tier[a.lg] ?? 9)); // ties → lowest league first (= team page order)
        return { id: t.id, code: t.short_name, name: t.name, sharedCode: codeUses[t.short_name] > 1, current: h.current,
          first: ss[0], last: ss[ss.length - 1], seasons: ss.length, list: ss, leagues: [...new Set(ss.map((s) => s.lg))], titles: h.titles, winsBy: h.winsBy };
      }).sort((a, b) => a.name.localeCompare(b.name));
    })().catch((e) => { _dbTeamHistory = null; throw e; });
  }
  return _dbTeamHistory;
}
function useDbTeamHistory() {
  const [st, setSt] = React.useState({ status: 'loading', teams: [] });
  React.useEffect(() => {
    let alive = true;
    dbTeamHistory().then((teams) => alive && setSt({ status: 'ready', teams })).catch(() => alive && setSt({ status: 'error', teams: [] }));
    return () => { alive = false; };
  }, []);
  return st;
}

// Driver → team code for one season (most race starts; ties → later rounds), keyed by normalized driver name.
// Used for AL cars (AL "teams" are car liveries) — the sheets have no AL lineup section.
async function dbDriverTeamCodes(league, sdef) {
  const code = UR_DB_CODES[league];
  const sid = code ? await dbSeasonId(code, sdef.number, sdef.suffix || null) : null;
  if (sid == null) return {};
  const [res, teams] = await Promise.all([dbGetAll('v_result_points?select=team_id,round,drivers(name)&session=eq.race&season_id=eq.' + sid), dbTeams()]);
  const shortOf = {};
  teams.forEach((t) => { shortOf[t.id] = t.short_name; });
  const tally = {};
  res.forEach((r) => {
    if (r.team_id == null || !r.drivers) return;
    const k = _dbNorm(r.drivers.name), c = shortOf[r.team_id];
    const t = (tally[k] = tally[k] || {});
    t[c] = (t[c] || 0) + 1 + r.round / 1000;
  });
  const out = {};
  Object.entries(tally).forEach(([k, t]) => { out[k] = Object.keys(t).sort((a, b) => t[b] - t[a])[0]; });
  return out;
}

// The team a driver has started the most races for, across every league and season in the DB ({ short, name } or null).
// Used where a driver has no team this season (Independents) so cards can still show who they race for.
const _mostRacedCache = {};
function dbMostRacedTeam(name) {
  const key = _dbNorm(name);
  if (!key) return Promise.resolve(null);
  if (!_mostRacedCache[key]) {
    _mostRacedCache[key] = (async () => {
      const person = typeof window.resolvePerson === 'function' ? window.resolvePerson(name) : null;
      const names = [...new Set([name, person && person.sheetName, person && person.display, ...((person && person.aliases) || [])].filter(Boolean))];
      const drv = await dbGet('drivers?select=id,name&name=in.(' + names.map((n) => '"' + String(n).replace(/"/g, '') + '"').join(',') + ')');
      if (!drv.length) return null;
      const [res, teams] = await Promise.all([dbGetAll('v_result_points?select=team_id&session=eq.race&team_id=not.is.null&driver_id=in.(' + drv.map((d) => d.id).join(',') + ')'), dbTeams()]);
      const n = {};
      res.forEach((r) => { n[r.team_id] = (n[r.team_id] || 0) + 1; });
      const top = Object.keys(n).sort((a, b) => n[b] - n[a])[0];
      const t = top && teams.find((x) => String(x.id) === top);
      return t ? { short: t.short_name, name: t.name, starts: n[top] } : null;
    })().catch(() => { delete _mostRacedCache[key]; return null; });
  }
  return _mostRacedCache[key];
}
function useMostRacedTeam(name) {
  const [t, setT] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    setT(null);
    if (name) dbMostRacedTeam(name).then((x) => alive && setT(x));
    return () => { alive = false; };
  }, [name]);
  return t;
}

// The car a driver most recently raced in: newest (season, team) they started for — any league, incl. the current
// season (the DB credits emergency-reserve starts to the team driven for) — that has a livery file for THAT season.
// Used for Independents on the leader cards. → { livery, short, league, season } or null.
const _lastCarCache = {};
const _DB_SITE_LG = { UL: 'ul', CL: 'hl', JL: 'jl', AL: 'al' };
function _dbChrono(code, number, division) {
  const half = division === 'B' ? 0.5 : 0;
  if (code === 'UL') return number;
  if (code === 'AL') return number + 3 + half;
  const m = urUlSeasonFor(code, 'S' + number + (division || ''));
  return m ? Number(m.ul.slice(1)) + (m.half === 2 ? 0.5 : 0) : number + 3 + half;
}
function dbLastCar(name, league) { // league (site id) → only that league's seasons
  const lgCode = league ? UR_DB_CODES[league] : null;
  const key = _dbNorm(name) + (lgCode ? ':' + lgCode : '');
  if (!_dbNorm(name)) return Promise.resolve(null);
  if (!_lastCarCache[key]) {
    _lastCarCache[key] = (async () => {
      const person = typeof window.resolvePerson === 'function' ? window.resolvePerson(name) : null;
      const names = [...new Set([name, person && person.sheetName, person && person.display, ...((person && person.aliases) || [])].filter(Boolean))];
      const drv = await dbGet('drivers?select=id,name&name=in.(' + names.map((n) => '"' + String(n).replace(/"/g, '') + '"').join(',') + ')');
      if (!drv.length) return null;
      const [res, teams, seasons] = await Promise.all([
        dbGetAll('v_result_points?select=season_id,round,team_id&session=eq.race&team_id=not.is.null&driver_id=in.(' + drv.map((d) => d.id).join(',') + ')'),
        dbTeams(), dbGet('seasons?select=id,number,division,leagues(code)'),
      ]);
      const sOf = {}; seasons.forEach((s) => { sOf[s.id] = s; });
      const tOf = {}; teams.forEach((t) => { tOf[t.id] = t; });
      const seen = {};
      res.forEach((r) => {
        const s = sOf[r.season_id], t = tOf[r.team_id];
        if (!s || !t || !s.leagues || (lgCode && s.leagues.code !== lgCode)) return;
        const k = r.season_id + ':' + r.team_id, at = _dbChrono(s.leagues.code, s.number, s.division) * 100 + r.round;
        if (!seen[k] || at > seen[k].at) seen[k] = { at, s, t };
      });
      const alias = window.FDT_ALIAS || {};
      for (const { s, t } of Object.values(seen).sort((a, b) => b.at - a.at)) {
        const lg = _DB_SITE_LG[s.leagues.code], label = 'S' + s.number + (s.division || '');
        const sd = lg && window.UR2D_ADAPTERS[lg] && window.UR2D_ADAPTERS[lg].seasons.find((x) => x.label === label);
        if (!sd) continue;
        const ls = sd.liveSource || {};
        const lv = { ...(ls.teamLiveries || {}), ...(sd.teamLiveries || {}), ...(ls.driverLiveriesByTeam || {}), ...(sd.driverLiveriesByTeam || {}) };
        const codes = [t.short_name, ...(alias[t.short_name] || []), ...Object.keys(alias).filter((c) => (alias[c] || []).includes(t.short_name))];
        const c = codes.find((x) => lv[x]);
        if (c) return { livery: lv[c], short: c, league: s.leagues.code, season: label };
      }
      // No file for that exact car yet → the same team's car from the nearest season in that league (approx: true).
      // User call 2026-10-07: an empty card is worse than a near-season car for Independents.
      const newest = Object.values(seen).sort((a, b) => b.at - a.at)[0];
      if (newest) {
        const { s, t } = newest, lg = _DB_SITE_LG[s.leagues.code], ad = lg && window.UR2D_ADAPTERS[lg];
        const codes = [t.short_name, ...(alias[t.short_name] || []), ...Object.keys(alias).filter((c) => (alias[c] || []).includes(t.short_name))];
        const tgt = _dbChrono(s.leagues.code, s.number, s.division);
        const near = (ad ? ad.seasons : []).filter((sd) => sd.number)
          .map((sd) => ({ sd, d: Math.abs(_dbChrono(s.leagues.code, sd.number, sd.suffix || null) - tgt) })).sort((a, b) => a.d - b.d);
        for (const { sd } of near) {
          const ls = sd.liveSource || {};
          const lv = { ...(ls.teamLiveries || {}), ...(sd.teamLiveries || {}), ...(ls.driverLiveriesByTeam || {}), ...(sd.driverLiveriesByTeam || {}) };
          const c = codes.find((x) => lv[x]);
          if (c) return { livery: lv[c], short: c, league: s.leagues.code, season: sd.label, approx: true, raced: 'S' + s.number + (s.division || '') };
        }
      }
      return null;
    })().catch(() => { delete _lastCarCache[key]; return null; });
  }
  return _lastCarCache[key];
}
function useLastCar(name, league) {
  const [c, setC] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    setC(null);
    if (name) dbLastCar(name, league).then((x) => alive && setC(x));
    return () => { alive = false; };
  }, [name, league]);
  return c;
}

Object.assign(window, { UR_DB, UR_DB_LEAGUES, UR_DB_CODES, dbGet, dbGetAll, dbSeasonId, loadDbStandings, dbTeamOrder, urUlSeasonFor, urSeasonsOnUl, urTimeLabelOn, dbTeamHistory, useDbTeamHistory, dbDriverTeamCodes, dbMostRacedTeam, useMostRacedTeam, dbLastCar, useLastCar });
