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

const { getRankedMeals, syncEloWithMeals } = require("./lib/elo");
const { loadMeals, repoRoot, validateMealAssets } = require("./lib/meals");

const indexTemplatePath = path.join(repoRoot, "templates", "index.html");
const indexOutputPath = path.join(repoRoot, "index.html");
const rankingsTemplatePath = path.join(repoRoot, "templates", "rankings.html");
const rankingsOutputPath = path.join(repoRoot, "rankings.html");
const ratingFormatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0,
});

function detectEol(text) {
  return text.includes("\r\n") ? "\r\n" : "\n";
}

function escapeHtml(value) {
  return String(value)
    .replace(/&/g, "&amp;")
    .replace(/>/g, "&gt;")
    .replace(/</g, "&lt;")
    .replace(/"/g, "&quot;");
}

function serializeJsonForHtml(value) {
  return JSON.stringify(value, null, 2)
    .replace(/</g, "\\u003c")
    .replace(/\u2028/g, "\\u2028")
    .replace(/\u2029/g, "\\u2029");
}

function indentBlock(text, indent) {
  return text
    .split("\n")
    .map((line) => `${indent}${line}`)
    .join("\n");
}

function renderGalleryItem(meal, eol) {
  const attrs = [`class="thumbnail"`, `href="images/fulls/${meal.id}.jpg"`];

  if (meal.position) {
    attrs.push(`data-position="${escapeHtml(meal.position)}"`);
  }

  return [
    "\t\t\t\t<article>",
    `\t\t\t\t\t<a ${attrs.join(" ")}><img src="images/thumbs/${meal.id}.jpg" alt="" /></a>`,
    `\t\t\t\t\t<h2>${escapeHtml(meal.title)}</h2>`,
    `\t\t\t\t\t<p>${escapeHtml(meal.description)}</p>`,
    "\t\t\t\t</article>",
  ].join(eol);
}

function renderGallery(meals, eol) {
  return meals.map((meal) => renderGalleryItem(meal, eol)).join(eol);
}

function formatRating(rating) {
  return ratingFormatter.format(rating);
}

function renderRankingSummary(meals, eloData) {
  const mealLabel = meals.length === 1 ? "meal" : "meals";

  return `\t\t\t\t<p class="ranking-summary">${meals.length} ${mealLabel} seeded at Elo ${formatRating(
    eloData.defaultRating
  )}. Enable JavaScript to vote and reorder them.</p>`;
}

function renderRankingMeta(rankedMeal) {
  const ratingText = `Elo ${formatRating(rankedMeal.rating)}`;

  if (rankedMeal.matches === 0) {
    return `${ratingText} | no votes yet`;
  }

  const matchLabel = rankedMeal.matches === 1 ? "match" : "matches";
  return `${ratingText} | ${rankedMeal.wins}-${rankedMeal.losses} record across ${
    rankedMeal.matches
  } ${matchLabel}`;
}

function renderRankingItem(rankedMeal, placement, eol) {
  return [
    '\t\t\t\t<article class="ranking-card">',
    `\t\t\t\t\t<p class="ranking-card__placement">#${placement}</p>`,
    `\t\t\t\t\t<a class="ranking-card__thumbnail" href="images/fulls/${rankedMeal.id}.jpg"><img src="images/thumbs/${rankedMeal.id}.jpg" alt="${escapeHtml(
      `${rankedMeal.title} thumbnail`
    )}" /></a>`,
    '\t\t\t\t\t<div class="ranking-card__body">',
    `\t\t\t\t\t\t<h2>${escapeHtml(rankedMeal.title)}</h2>`,
    `\t\t\t\t\t\t<p class="ranking-card__meta">${escapeHtml(renderRankingMeta(rankedMeal))}</p>`,
    `\t\t\t\t\t\t<p>${escapeHtml(rankedMeal.description)}</p>`,
    "\t\t\t\t\t</div>",
    "\t\t\t\t</article>",
  ].join(eol);
}

function renderRankings(rankedMeals, eol) {
  return rankedMeals
    .map((rankedMeal, index) => renderRankingItem(rankedMeal, index + 1, eol))
    .join(eol);
}

function renderRankingsSeedData(meals, eloData) {
  return indentBlock(
    serializeJsonForHtml({
      meals,
      elo: eloData,
    }),
    "\t\t\t"
  );
}

function replaceBlock(template, token, replacement) {
  const pattern = new RegExp(`^[\\t ]*\\{\\{${token}\\}\\}$`, "m");

  if (!pattern.test(template)) {
    throw new Error(`Template is missing required block token "{{${token}}}"`);
  }

  return template.replace(pattern, () => replacement);
}

function buildIndex(meals = loadMeals()) {
  validateMealAssets(meals);
  const template = fs.readFileSync(indexTemplatePath, "utf8");
  const eol = detectEol(template);

  return replaceBlock(template, "gallery_items", renderGallery(meals, eol));
}

function buildRankings(
  meals = loadMeals(),
  eloData = syncEloWithMeals(meals)
) {
  validateMealAssets(meals);
  const template = fs.readFileSync(rankingsTemplatePath, "utf8");
  const eol = detectEol(template);
  const rankedMeals = getRankedMeals(meals, eloData);
  const withSummary = replaceBlock(
    template,
    "ranking_summary",
    renderRankingSummary(meals, eloData)
  );
  const withSeedData = replaceBlock(
    withSummary,
    "rankings_seed_data",
    renderRankingsSeedData(meals, eloData)
  );

  return replaceBlock(withSeedData, "ranking_items", renderRankings(rankedMeals, eol));
}

function writeFile(filePath, contents) {
  fs.writeFileSync(filePath, contents);
}

function main() {
  const meals = loadMeals();
  const eloData = syncEloWithMeals(meals);

  writeFile(indexOutputPath, buildIndex(meals));
  writeFile(rankingsOutputPath, buildRankings(meals, eloData));
}

function buildPages() {
  main();
}

if (require.main === module) {
  buildPages();
}

module.exports = {
  buildPages,
  buildIndex,
  buildRankings,
};
