const fs = require("fs");
const path = require("path");

const { getEloAlignmentReport, loadEloData } = require("./lib/elo");
const {
  fullsDir,
  loadMeals,
  repoRoot,
  thumbsDir,
  validateMealAssets,
} = require("./lib/meals");

const indexPath = path.join(repoRoot, "index.html");
const rankingsPath = path.join(repoRoot, "rankings.html");

function listJpgIds(directoryPath) {
  if (!fs.existsSync(directoryPath)) {
    return [];
  }

  return fs
    .readdirSync(directoryPath, { withFileTypes: true })
    .filter(
      (entry) => entry.isFile() && path.extname(entry.name).toLowerCase() === ".jpg"
    )
    .map((entry) => path.basename(entry.name, ".jpg"))
    .sort((left, right) => left.localeCompare(right, undefined, { numeric: true }));
}

function getUnexpectedIds(directoryPath, expectedIds) {
  return listJpgIds(directoryPath).filter((id) => !expectedIds.has(id));
}

function countMatches(text, pattern) {
  return (text.match(pattern) || []).length;
}

function parseRankingsSeedData(rankingsHtml) {
  const match = rankingsHtml.match(
    /<script id="rankings-seed-data" type="application\/json">([\s\S]*?)<\/script>/
  );

  if (!match) {
    throw new Error("Generated rankings.html is missing embedded rankings seed data");
  }

  return JSON.parse(match[1]);
}

function validateGeneratedPages(meals, eloData) {
  if (!fs.existsSync(indexPath)) {
    throw new Error("Generated index.html is missing; run npm run build");
  }

  if (!fs.existsSync(rankingsPath)) {
    throw new Error("Generated rankings.html is missing; run npm run build");
  }

  const indexHtml = fs.readFileSync(indexPath, "utf8");
  const rankingsHtml = fs.readFileSync(rankingsPath, "utf8");
  const galleryArticleCount = countMatches(indexHtml, /<article>/g);
  const rankingCardCount = countMatches(rankingsHtml, /class="ranking-card"/g);

  if (galleryArticleCount !== meals.length) {
    throw new Error(
      `Generated index.html is out of sync: expected ${meals.length} gallery entries, found ${galleryArticleCount}`
    );
  }

  if (rankingCardCount !== meals.length) {
    throw new Error(
      `Generated rankings.html is out of sync: expected ${meals.length} ranking cards, found ${rankingCardCount}`
    );
  }

  if (!rankingsHtml.includes('src="assets/js/rankings.js"')) {
    throw new Error("Generated rankings.html is missing the interactive rankings script");
  }

  const seedData = parseRankingsSeedData(rankingsHtml);

  if (!Array.isArray(seedData.meals) || seedData.meals.length !== meals.length) {
    throw new Error("Generated rankings.html has stale embedded meal seed data");
  }

  if (
    !seedData.elo ||
    !Array.isArray(seedData.elo.entries) ||
    seedData.elo.entries.length !== eloData.entries.length
  ) {
    throw new Error("Generated rankings.html has stale embedded Elo seed data");
  }
}

function main() {
  const meals = loadMeals();
  const eloData = loadEloData();
  const expectedIds = new Set(meals.map((meal) => meal.id));
  const alignment = getEloAlignmentReport(meals, eloData);

  validateMealAssets(meals);

  if (alignment.missingEntryIds.length > 0 || alignment.unexpectedEntryIds.length > 0) {
    const messages = [];

    if (alignment.missingEntryIds.length > 0) {
      messages.push(
        `Missing Elo entries for meal ids: ${alignment.missingEntryIds.join(", ")}`
      );
    }

    if (alignment.unexpectedEntryIds.length > 0) {
      messages.push(
        `Unexpected Elo entries with no meal: ${alignment.unexpectedEntryIds.join(", ")}`
      );
    }

    throw new Error(messages.join("\n"));
  }

  const unexpectedFulls = getUnexpectedIds(fullsDir, expectedIds);

  if (unexpectedFulls.length > 0) {
    throw new Error(
      `Unexpected full-size image files with no meal entry: ${unexpectedFulls.join(", ")}`
    );
  }

  const unexpectedThumbs = getUnexpectedIds(thumbsDir, expectedIds);

  if (unexpectedThumbs.length > 0) {
    throw new Error(
      `Unexpected thumbnail files with no meal entry: ${unexpectedThumbs.join(", ")}`
    );
  }

  validateGeneratedPages(meals, eloData);

  console.log(
    `Validation passed: ${meals.length} meals, ${eloData.entries.length} Elo entries, generated pages and image assets are in sync.`
  );
}

if (require.main === module) {
  try {
    main();
  } catch (error) {
    console.error(error.message);
    process.exitCode = 1;
  }
}
