(async () => {
"use strict";

// ============================================================
// MAXITHLON - USA CLUB PRESTIGE HISTORY SCRAPER - V1
// ============================================================
// Run in Chrome Console while logged in to Maxithlon.
//
// PURPOSE
// - Seasons are kept separate: one row per USA club + season.
// - Includes ANY USA club found in the requested competition data,
//   including old/deleted clubs that are now shown as plain text.
// - Club User ID is kept when Maxithlon still supplies it.
//
// EDIT THESE TWO VALUES WHEN REQUIRED:
const START_SEASON = 67;
const END_SEASON = 107;
// ============================================================

const OUTPUT_FILENAME =
  `maxithlon_USA_club_prestige_S${START_SEASON}_to_S${END_SEASON}.xlsx`;

const USA_NATION_ID = 18;

// Conservative request settings. Increase only if you know Maxithlon is coping well.
const REQUEST_DELAY_MS = 250;
const CONCURRENCY = 2;
const RETRIES = 4;

const GLOBAL_COMPETITIONS_URL =
  "https://maxithlon.com/geo/geo_competitions.php?n=69";

const CONTINENTS = [
  { id: 1, label: "Europe" },
  { id: 2, label: "Asia - Africa - Pacific" },
  { id: 3, label: "America" }
];

const METRIC_GROUPS = [
  { key: "usaInc", label: "USA INC" },
  { key: "overseasInc", label: "Overseas INC" },
  { key: "olympic", label: "Olympic Games" },
  { key: "world", label: "World Championships" },
  { key: "worldJunior", label: "World Junior Championships" },
  { key: "worldU21", label: "World U21 Championships" },
  { key: "worldMasters", label: "World Masters Championships" },
  { key: "continental", label: "Continental Championships" },
  { key: "continentalJunior", label: "Continental Junior Championships" },
  { key: "continentalU21", label: "Continental U21 Championships" },
  { key: "continentalMasters", label: "Continental Masters Championships" },
  { key: "championsCup", label: "Champions Cup" }
];

const HEADERS = ["Club", "Club User ID", "Season"];
for (const group of METRIC_GROUPS) {
  HEADERS.push(
    `${group.label} Gold`,
    `${group.label} Silver`,
    `${group.label} Bronze`
  );
}
HEADERS.push("Champions Cup Position", "Champions Cup Points");

// ============================================================
// GENERAL HELPERS
// ============================================================

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

function clean(value) {
  return String(value ?? "")
    .replace(/\u00a0/g, " ")
    .replace(/\r?\n|\r/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function absoluteUrl(href) {
  return new URL(href, location.origin).href;
}

function parseInteger(value) {
  const s = clean(value).replace(/[^0-9-]/g, "");
  if (!s || s === "-") return null;
  const n = Number(s);
  return Number.isFinite(n) ? n : null;
}

function userIdFromHref(href) {
  const m = String(href || "").match(/[?&]u=(\d+)/i);
  return m ? Number(m[1]) : null;
}

function nationIdFromHref(href) {
  const m = String(href || "").match(/[?&]n=(\d+)/i);
  return m ? Number(m[1]) : null;
}

function looksLikeLoginPage(doc) {
  if (!doc) return false;
  if (doc.querySelector('input[type="password"]')) return true;
  const forms = [...doc.querySelectorAll("form")];
  return forms.some(form => {
    const action = String(form.getAttribute("action") || "").toLowerCase();
    const text = clean(form.textContent).toLowerCase();
    return (
      (action.includes("login") || action.includes("accedi")) &&
      (text.includes("login") || text.includes("password"))
    );
  });
}

async function fetchTextWithRetry(url, attempts = RETRIES) {
  let lastError = null;

  for (let attempt = 1; attempt <= attempts; attempt++) {
    try {
      const response = await fetch(url, {
        credentials: "include",
        cache: "no-store"
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status} ${response.statusText}`);
      }

      return await response.text();
    } catch (error) {
      lastError = error;
      console.warn(
        `Fetch failed ${attempt}/${attempts}:`,
        url,
        error
      );
      if (attempt < attempts) {
        await sleep(700 * attempt);
      }
    }
  }

  throw lastError;
}

async function fetchDoc(url) {
  const html = await fetchTextWithRetry(url);
  const doc = new DOMParser().parseFromString(html, "text/html");

  if (looksLikeLoginPage(doc)) {
    throw new Error("Maxithlon login page detected. Please log in again.");
  }

  return doc;
}

async function runPool(items, worker, concurrency = CONCURRENCY) {
  if (!items.length) return;

  let nextIndex = 0;
  let completed = 0;

  async function runner(workerNumber) {
    while (true) {
      const index = nextIndex++;
      if (index >= items.length) return;

      const item = items[index];

      try {
        await worker(item, index, workerNumber);
      } catch (error) {
        console.warn("Job failed:", item, error);
      }

      completed++;
      if (
        completed === 1 ||
        completed % 25 === 0 ||
        completed === items.length
      ) {
        console.log(`Progress: ${completed}/${items.length} jobs completed`);
      }

      await sleep(REQUEST_DELAY_MS);
    }
  }

  await Promise.all(
    Array.from(
      { length: Math.min(concurrency, items.length) },
      (_, i) => runner(i + 1)
    )
  );
}

// ============================================================
// COMPETITION DISCOVERY
// ============================================================

function classifyCompetition(name, scope, nationId = null) {
  const s = clean(name).toLowerCase();

  if (scope === "national") {
    if (!s.includes("individual national championship")) return null;
    return Number(nationId) === USA_NATION_ID ? "usaInc" : "overseasInc";
  }

  if (scope === "global") {
    if (s.includes("champions cup")) return "championsCup";
    if (s.includes("olympic games")) return "olympic";
    if (s.includes("world u21 championship")) return "worldU21";
    if (s.includes("world junior championship")) return "worldJunior";
    if (s.includes("world master championship")) return "worldMasters";
    if (s.includes("world championship")) return "world";
    return null;
  }

  if (scope === "continental") {
    if (s.includes("continental u21 championship")) return "continentalU21";
    if (s.includes("continental junior championship")) return "continentalJunior";
    if (s.includes("continental master championship")) return "continentalMasters";
    if (s.includes("continental championship")) return "continental";
    return null;
  }

  return null;
}

function extractCompetitionOptions(doc, scope, nationId = null, sourceLabel = "") {
  const jobs = [];
  const options = [...doc.querySelectorAll('select[name="m"] option')];

  for (const option of options) {
    const id = parseInteger(option.value);
    const text = clean(option.textContent);
    const match = text.match(/^(\d+)\s*-\s*(.+)$/);

    if (!id || !match) continue;

    const season = Number(match[1]);
    const competitionName = clean(match[2]);

    if (season < START_SEASON || season > END_SEASON) continue;

    const metricKey = classifyCompetition(
      competitionName,
      scope,
      nationId
    );

    if (!metricKey) continue;

    jobs.push({
      competitionId: id,
      season,
      competitionName,
      metricKey,
      scope,
      nationId,
      sourceLabel
    });
  }

  return jobs;
}

function extractNationIds(doc) {
  const map = new Map();

  for (const a of doc.querySelectorAll('a[href*="geo_nazione.php?n="]')) {
    const id = nationIdFromHref(absoluteUrl(a.getAttribute("href") || ""));
    if (!id) continue;

    const name = clean(a.textContent) ||
      clean(a.querySelector("img")?.getAttribute("title")) ||
      `Nation ${id}`;

    if (!map.has(id)) map.set(id, name);
  }

  return [...map.entries()].map(([id, name]) => ({ id, name }));
}

// ============================================================
// USA CLUB / SEASON RECORDS
// ============================================================

const recordMap = new Map();

function normalizedClubName(name) {
  return clean(name).toLocaleLowerCase();
}

function clubIdentity(clubName, userId) {
  if (Number.isFinite(userId)) return `u:${userId}`;
  return `name:${normalizedClubName(clubName)}`;
}

function blankMedals() {
  const obj = {};
  for (const group of METRIC_GROUPS) {
    obj[group.key] = { gold: 0, silver: 0, bronze: 0 };
  }
  return obj;
}

function ensureRecord(clubName, userId, season) {
  const name = clean(clubName);
  if (!name) return null;

  const identity = clubIdentity(name, userId);
  const key = `${season}|${identity}`;

  if (!recordMap.has(key)) {
    recordMap.set(key, {
      club: name,
      userId: Number.isFinite(userId) ? userId : "",
      season: Number(season),
      medals: blankMedals(),
      championsCupPosition: "",
      championsCupPoints: ""
    });
  } else {
    const record = recordMap.get(key);

    // Prefer a surviving numeric club ID if we encounter it later.
    if (
      record.userId === "" &&
      Number.isFinite(userId)
    ) {
      record.userId = userId;
    }

    // Prefer the linked/current display name if supplied.
    if (name) record.club = name;
  }

  return recordMap.get(key);
}

function mergePossiblePlainTextRecord(clubName, userId, season) {
  const name = clean(clubName);

  if (!Number.isFinite(userId)) {
    return ensureRecord(name, null, season);
  }

  const idKey = `${season}|u:${userId}`;
  if (recordMap.has(idKey)) return recordMap.get(idKey);

  const nameKey = `${season}|name:${normalizedClubName(name)}`;

  if (recordMap.has(nameKey)) {
    const oldRecord = recordMap.get(nameKey);
    recordMap.delete(nameKey);
    oldRecord.userId = userId;
    oldRecord.club = name;
    recordMap.set(idKey, oldRecord);
    return oldRecord;
  }

  return ensureRecord(name, userId, season);
}

// ============================================================
// MEDAL TABLE PARSER
// ============================================================

function isUsaRow(row) {
  for (const a of row.querySelectorAll('a[href*="geo_nazione.php?n="]')) {
    const n = nationIdFromHref(absoluteUrl(a.getAttribute("href") || ""));
    if (n === USA_NATION_ID) return true;
  }

  const usFlag = row.querySelector('img[src*="/flags/16/us."]');
  return Boolean(usFlag);
}

function findMedalsByTeamTable(doc) {
  // Robustly identify the CLUB medal table by its fixed 13-column structure:
  // rank | nation | club | male G/S/B | female G/S/B | total G/S/B | total medals
  // This avoids relying on image-title text or other fragile presentation details.
  const tables = [...doc.querySelectorAll("table.man_details")];

  for (const table of tables) {
    const rows = [...table.querySelectorAll("tr")];

    const hasClubRows = rows.some(row => {
      const cells = [...row.children].filter(el => el.tagName === "TD");
      if (cells.length < 13) return false;

      const nationCell = cells[1];
      const clubCell = cells[2];

      const hasNationLink = Boolean(
        nationCell && nationCell.querySelector('a[href*="geo_nazione.php?n="]')
      );
      const hasClubText = Boolean(clubCell && clean(clubCell.textContent));

      // Total medal columns are fixed at 9, 10 and 11 in the supplied
      // Maxithlon statistics pages.
      const g = parseInteger(cells[9]?.textContent);
      const s = parseInteger(cells[10]?.textContent);
      const b = parseInteger(cells[11]?.textContent);

      return (
        hasNationLink &&
        hasClubText &&
        g !== null &&
        s !== null &&
        b !== null
      );
    });

    if (hasClubRows) return table;
  }

  return null;
}

function parseMedalTable(doc, job) {
  const table = findMedalsByTeamTable(doc);

  if (!table) {
    console.warn(
      `NO CLUB MEDAL TABLE FOUND: S${job.season} ${job.competitionName} ` +
      `(m=${job.competitionId})`
    );
    return 0;
  }

  let usaRows = 0;

  for (const row of table.querySelectorAll("tr")) {
    // Use direct children without :scope, for maximum compatibility.
    const cells = [...row.children].filter(el => el.tagName === "TD");
    if (cells.length < 13) continue;

    // The nationality of the CLUB is explicitly in column 2 (index 1).
    // Do not search the whole row for any nation link.
    const nationLink = cells[1].querySelector('a[href*="geo_nazione.php?n="]');
    const clubNationId = nationLink
      ? nationIdFromHref(absoluteUrl(nationLink.getAttribute("href") || ""))
      : null;

    if (clubNationId !== USA_NATION_ID) continue;

    const clubCell = cells[2];
    const clubLink = clubCell.querySelector('a[href*="dettagli_societa.php"]');
    const clubName = clean(
      clubLink ? clubLink.textContent : clubCell.textContent
    );

    if (!clubName) continue;

    const userId = clubLink
      ? userIdFromHref(absoluteUrl(clubLink.getAttribute("href") || ""))
      : null;

    // Fixed TOTAL medal columns in Maxithlon's "Medals Table by team":
    // index 9 = Gold, 10 = Silver, 11 = Bronze.
    const gold = parseInteger(cells[9].textContent) ?? 0;
    const silver = parseInteger(cells[10].textContent) ?? 0;
    const bronze = parseInteger(cells[11].textContent) ?? 0;

    const record = mergePossiblePlainTextRecord(
      clubName,
      userId,
      job.season
    );

    if (!record) continue;

    record.medals[job.metricKey].gold += gold;
    record.medals[job.metricKey].silver += silver;
    record.medals[job.metricKey].bronze += bronze;

    usaRows++;
  }

  return usaRows;
}

// ============================================================
// CHAMPIONS CUP FINAL STANDINGS PARSER
// ============================================================

function findChampionsCupStandingsTable(doc) {
  const direct = doc.querySelector("table#classifiche0");
  if (direct) return direct;

  return [...doc.querySelectorAll("table")].find(table => {
    const headers = [...table.querySelectorAll("th")].map(th =>
      clean(th.textContent).toLowerCase()
    );
    return (
      headers.includes("club") &&
      headers.includes("male") &&
      headers.includes("female") &&
      headers.includes("total")
    );
  }) || null;
}

function parseChampionsCupStandings(doc, job) {
  const table = findChampionsCupStandingsTable(doc);
  if (!table) return 0;

  let usaRows = 0;

  for (const row of table.querySelectorAll("tbody tr, tr")) {
    const cells = [...row.querySelectorAll(":scope > td")];
    if (cells.length < 6) continue;
    if (!isUsaRow(row)) continue;

    const position = parseInteger(cells[0].textContent);
    const clubCell = cells[2];

    if (!clubCell || !Number.isFinite(position)) continue;

    const clubLink = clubCell.querySelector('a[href*="dettagli_societa.php"]');
    const clubName = clean(
      clubLink ? clubLink.textContent : clubCell.textContent
    );

    if (!clubName) continue;

    const userId = clubLink
      ? userIdFromHref(absoluteUrl(clubLink.getAttribute("href") || ""))
      : null;

    const points = parseInteger(cells[cells.length - 1].textContent);

    const record = mergePossiblePlainTextRecord(
      clubName,
      userId,
      job.season
    );

    if (!record) continue;

    record.championsCupPosition = position;
    record.championsCupPoints =
      Number.isFinite(points) ? points : "";

    usaRows++;
  }

  return usaRows;
}

// ============================================================
// XLSX OUTPUT
// ============================================================

async function loadXLSXLibrary() {
  if (window.XLSX) return true;

  const urls = [
    "https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js",
    "https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"
  ];

  for (const src of urls) {
    const loaded = await new Promise(resolve => {
      const script = document.createElement("script");
      script.src = src;
      script.onload = () => resolve(true);
      script.onerror = () => resolve(false);
      document.head.appendChild(script);
    });

    if (loaded && window.XLSX) return true;
  }

  return false;
}

function buildOutputRows() {
  const records = [...recordMap.values()];

  records.sort((a, b) => {
    const nameCompare = a.club.localeCompare(
      b.club,
      undefined,
      { numeric: true, sensitivity: "base" }
    );
    if (nameCompare !== 0) return nameCompare;
    return a.season - b.season;
  });

  return [
    HEADERS,
    ...records.map(record => {
      const row = [
        record.club,
        record.userId,
        record.season
      ];

      for (const group of METRIC_GROUPS) {
        const m = record.medals[group.key];
        row.push(m.gold, m.silver, m.bronze);
      }

      row.push(
        record.championsCupPosition,
        record.championsCupPoints
      );

      return row;
    })
  ];
}

function styleWorksheet(ws, rowCount) {
  const colCount = HEADERS.length;

  ws["!freeze"] = { xSplit: 3, ySplit: 1 };
  ws["!autofilter"] = {
    ref:
      `A1:${XLSX.utils.encode_col(colCount - 1)}${Math.max(1, rowCount)}`
  };

  const widths = [
    { wch: 32 }, // Club
    { wch: 14 }, // Club User ID
    { wch: 9 }   // Season
  ];

  for (let i = 3; i < colCount - 2; i++) {
    widths.push({ wch: 20 });
  }

  widths.push(
    { wch: 24 }, // CC Position
    { wch: 22 }  // CC Points
  );

  ws["!cols"] = widths;

  for (let r = 1; r < rowCount; r++) {
    // Club User ID: numeric when known.
    const userIdAddress = XLSX.utils.encode_cell({ r, c: 1 });
    if (
      ws[userIdAddress] &&
      typeof ws[userIdAddress].v === "number"
    ) {
      ws[userIdAddress].t = "n";
      ws[userIdAddress].z = "0";
    }

    // Season: numeric.
    const seasonAddress = XLSX.utils.encode_cell({ r, c: 2 });
    if (ws[seasonAddress]) {
      ws[seasonAddress].t = "n";
      ws[seasonAddress].z = "0";
    }

    // All medal columns: numeric integers.
    for (let c = 3; c < colCount - 2; c++) {
      const address = XLSX.utils.encode_cell({ r, c });
      if (ws[address]) {
        ws[address].t = "n";
        ws[address].z = "0";
      }
    }

    // Champions Cup Position: numeric when present.
    const posAddress = XLSX.utils.encode_cell({
      r,
      c: colCount - 2
    });
    if (
      ws[posAddress] &&
      typeof ws[posAddress].v === "number"
    ) {
      ws[posAddress].t = "n";
      ws[posAddress].z = "0";
    }

    // Champions Cup Points: numeric with thousands separators.
    const pointsAddress = XLSX.utils.encode_cell({
      r,
      c: colCount - 1
    });
    if (
      ws[pointsAddress] &&
      typeof ws[pointsAddress].v === "number"
    ) {
      ws[pointsAddress].t = "n";
      ws[pointsAddress].z = "#,##0";
    }
  }
}

// ============================================================
// MAIN
// ============================================================

if (
  !Number.isInteger(START_SEASON) ||
  !Number.isInteger(END_SEASON) ||
  START_SEASON > END_SEASON
) {
  alert("Check START_SEASON and END_SEASON near the top of the scraper.");
  return;
}

console.log(
  `USA prestige scraper starting: Seasons ${START_SEASON}-${END_SEASON}`
);

// ------------------------------------------------------------
// 1. Discover global competitions.
// ------------------------------------------------------------

console.log("Reading global competition archive...");
const globalDoc = await fetchDoc(GLOBAL_COMPETITIONS_URL);
const allJobs = extractCompetitionOptions(
  globalDoc,
  "global",
  null,
  "World"
);

console.log(
  "Global competitions selected:",
  allJobs.length
);

// ------------------------------------------------------------
// 2. Discover continental competitions and nation IDs.
// ------------------------------------------------------------

const nationMap = new Map();

for (const continent of CONTINENTS) {
  console.log(`Reading ${continent.label} nation list...`);

  const continentUrl =
    `https://maxithlon.com/geo/geo_continente.php?c=${continent.id}`;
  const continentDoc = await fetchDoc(continentUrl);

  for (const nation of extractNationIds(continentDoc)) {
    if (!nationMap.has(nation.id)) {
      nationMap.set(nation.id, nation.name);
    }
  }

  console.log(`Reading ${continent.label} competition archive...`);

  const competitionsUrl =
    `https://maxithlon.com/geo/geo_competitions.php?c=${continent.id}`;
  const competitionsDoc = await fetchDoc(competitionsUrl);

  const continentJobs = extractCompetitionOptions(
    competitionsDoc,
    "continental",
    null,
    continent.label
  );

  allJobs.push(...continentJobs);
}

console.log(
  "Nations discovered:",
  nationMap.size
);

// ------------------------------------------------------------
// 3. Discover every nation's INC IDs for the requested seasons.
// ------------------------------------------------------------

const nations = [...nationMap.entries()]
  .map(([id, name]) => ({ id, name }))
  .sort((a, b) => a.name.localeCompare(b.name));

const nationalDiscoveryJobs = [];

await runPool(
  nations,
  async nation => {
    const url =
      `https://maxithlon.com/geo/geo_competitions.php?n=${nation.id}`;

    try {
      const doc = await fetchDoc(url);

      const incJobs = extractCompetitionOptions(
        doc,
        "national",
        nation.id,
        nation.name
      );

      nationalDiscoveryJobs.push(...incJobs);

      console.log(
        `INC archive ${nation.name}: ${incJobs.length} requested-season competitions`
      );
    } catch (error) {
      console.warn(
        `Could not read national competition archive for ${nation.name}:`,
        error
      );
    }
  },
  CONCURRENCY
);

allJobs.push(...nationalDiscoveryJobs);

// Deduplicate by competition ID + metric key.
const jobMap = new Map();
for (const job of allJobs) {
  const key = `${job.competitionId}|${job.metricKey}`;
  if (!jobMap.has(key)) jobMap.set(key, job);
}

const jobs = [...jobMap.values()].sort((a, b) => {
  if (a.season !== b.season) return a.season - b.season;
  if (a.metricKey !== b.metricKey) {
    return a.metricKey.localeCompare(b.metricKey);
  }
  return a.competitionId - b.competitionId;
});

console.log(
  `Competition medal/statistics jobs to check: ${jobs.length}`
);

// ------------------------------------------------------------
// 4. Read medal tables for ALL requested competition types.
//    For Champions Cup, also read final standings.
// ------------------------------------------------------------

await runPool(
  jobs,
  async (job, index) => {
    const statsUrl =
      `https://maxithlon.com/manifestazioni/man_stat.php?m=${job.competitionId}`;

    console.log(
      `[${index + 1}/${jobs.length}] S${job.season} ${job.competitionName}` +
      (job.sourceLabel ? ` - ${job.sourceLabel}` : "")
    );

    try {
      const statsDoc = await fetchDoc(statsUrl);
      const usaMedalRows = parseMedalTable(statsDoc, job);

      if (usaMedalRows) {
        console.log(
          `  USA medal-table clubs found: ${usaMedalRows}`
        );
      }
    } catch (error) {
      console.warn(
        `Could not read statistics for m=${job.competitionId}:`,
        error
      );
    }

    if (job.metricKey === "championsCup") {
      const standingsUrl =
        `https://maxithlon.com/manifestazioni/man_classifiche.php?m=${job.competitionId}`;

      try {
        const standingsDoc = await fetchDoc(standingsUrl);
        const usaStandingsRows =
          parseChampionsCupStandings(standingsDoc, job);

        if (usaStandingsRows) {
          console.log(
            `  USA Champions Cup final standings clubs found: ${usaStandingsRows}`
          );
        }
      } catch (error) {
        console.warn(
          `Could not read Champions Cup standings for m=${job.competitionId}:`,
          error
        );
      }
    }
  },
  CONCURRENCY
);

// ------------------------------------------------------------
// 5. Export one XLSX sheet: one row per club + season.
// ------------------------------------------------------------

const outputRows = buildOutputRows();

console.log(
  "USA club-season rows found:",
  outputRows.length - 1
);

const medalTotalsDiagnostic = {};
for (const group of METRIC_GROUPS) {
  medalTotalsDiagnostic[group.label] = { gold: 0, silver: 0, bronze: 0 };
}
for (const record of recordMap.values()) {
  for (const group of METRIC_GROUPS) {
    medalTotalsDiagnostic[group.label].gold += record.medals[group.key].gold;
    medalTotalsDiagnostic[group.label].silver += record.medals[group.key].silver;
    medalTotalsDiagnostic[group.label].bronze += record.medals[group.key].bronze;
  }
}
console.table(medalTotalsDiagnostic);

const grandMedalTotal = Object.values(medalTotalsDiagnostic)
  .reduce((sum, m) => sum + m.gold + m.silver + m.bronze, 0);

if (grandMedalTotal === 0) {
  console.error(
    "ERROR: ALL MEDAL TOTALS ARE ZERO. The workbook will NOT be exported. " +
    "Check the console warnings above for medal-table parsing failures."
  );
  alert(
    "The scraper found ZERO medals across every competition. " +
    "The workbook has NOT been exported because that indicates a parsing failure."
  );
  return;
}

if (outputRows.length === 1) {
  console.warn("No USA club-season records were found.");
}

const loaded = await loadXLSXLibrary();

if (!loaded || !window.XLSX) {
  alert("Could not load the XLSX library. No workbook was downloaded.");
  return;
}

const workbook = XLSX.utils.book_new();
const worksheet = XLSX.utils.aoa_to_sheet(outputRows);

styleWorksheet(worksheet, outputRows.length);

XLSX.utils.book_append_sheet(
  workbook,
  worksheet,
  "USA Prestige by Season"
);

XLSX.writeFile(workbook, OUTPUT_FILENAME);

window.maxithlonUsaPrestigeRows = outputRows;
window.maxithlonUsaPrestigeRecords = [...recordMap.values()];
window.maxithlonUsaPrestigeJobs = jobs;

console.log("Finished. Downloaded:", OUTPUT_FILENAME);
console.log(
  "Excel types: Club=text; Club User ID=numeric when known; Season=numeric; " +
  "all medal counts=numeric; Champions Cup Position=numeric when present; " +
  "Champions Cup Points=numeric when present."
);

})();