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

const repoRoot = path.resolve(__dirname, "..", "..");
const mealsPath = path.join(repoRoot, "data", "meals.json");
const fullsDir = path.join(repoRoot, "images", "fulls");
const thumbsDir = path.join(repoRoot, "images", "thumbs");

function isNonEmptyString(value) {
  return typeof value === "string" && value.trim().length > 0;
}

function validateThumbnail(meal, index) {
  if (meal.thumbnail === undefined) {
    return;
  }

  if (
    !meal.thumbnail ||
    typeof meal.thumbnail !== "object" ||
    Array.isArray(meal.thumbnail)
  ) {
    throw new Error(`Meal ${index} has an invalid "thumbnail" object`);
  }

  if (meal.thumbnail.focus === undefined) {
    return;
  }

  const { focus } = meal.thumbnail;

  if (!focus || typeof focus !== "object" || Array.isArray(focus)) {
    throw new Error(`Meal ${index} has an invalid "thumbnail.focus" object`);
  }

  for (const axis of ["x", "y"]) {
    if (typeof focus[axis] !== "number" || !Number.isFinite(focus[axis])) {
      throw new Error(
        `Meal ${index} has a non-numeric thumbnail focus value for "${axis}"`
      );
    }

    if (focus[axis] < 0 || focus[axis] > 1) {
      throw new Error(
        `Meal ${index} thumbnail focus "${axis}" must be between 0 and 1`
      );
    }
  }
}

function validateMeals(meals) {
  if (!Array.isArray(meals)) {
    throw new Error("data/meals.json must contain an array");
  }

  const ids = new Set();

  for (const [index, meal] of meals.entries()) {
    if (!meal || typeof meal !== "object") {
      throw new Error(`Meal at index ${index} must be an object`);
    }

    for (const field of ["id", "title", "description"]) {
      if (!isNonEmptyString(meal[field])) {
        throw new Error(`Meal ${index} is missing required string field "${field}"`);
      }
    }

    if (!/^\d+$/.test(meal.id)) {
      throw new Error(`Meal ${index} has a non-numeric id "${meal.id}"`);
    }

    if (meal.position !== undefined && !isNonEmptyString(meal.position)) {
      throw new Error(`Meal ${index} has an invalid "position" value`);
    }

    if (ids.has(meal.id)) {
      throw new Error(`Duplicate meal id "${meal.id}" found in data/meals.json`);
    }

    ids.add(meal.id);

    validateThumbnail(meal, index);
  }
}

function loadMeals() {
  const meals = JSON.parse(fs.readFileSync(mealsPath, "utf8"));

  validateMeals(meals);

  return meals;
}

function saveMeals(meals) {
  validateMeals(meals);
  fs.writeFileSync(mealsPath, `${JSON.stringify(meals, null, 2)}\n`);
}

function getMealImagePaths(mealOrId) {
  const mealId =
    typeof mealOrId === "string" ? mealOrId : mealOrId && typeof mealOrId.id === "string" ? mealOrId.id : null;

  if (!mealId) {
    throw new Error("Expected a meal object or meal id string");
  }

  return {
    fullPath: path.join(fullsDir, `${mealId}.jpg`),
    thumbPath: path.join(thumbsDir, `${mealId}.jpg`),
  };
}

function validateMealAssets(meals, options = {}) {
  const settings = {
    requireFull: true,
    requireThumb: true,
    ...options,
  };
  const missingAssets = [];

  for (const meal of meals) {
    const { fullPath, thumbPath } = getMealImagePaths(meal);

    if (settings.requireFull && !fs.existsSync(fullPath)) {
      missingAssets.push(
        `Meal ${meal.id} is missing full-size image: ${path.relative(repoRoot, fullPath)}`
      );
    }

    if (settings.requireThumb && !fs.existsSync(thumbPath)) {
      missingAssets.push(
        `Meal ${meal.id} is missing thumbnail image: ${path.relative(repoRoot, thumbPath)}`
      );
    }
  }

  if (missingAssets.length > 0) {
    throw new Error(`Missing image assets:\n${missingAssets.join("\n")}`);
  }
}

function getNextMealId(meals) {
  if (meals.length === 0) {
    return "01";
  }

  const nextNumber =
    Math.max(...meals.map((meal) => Number.parseInt(meal.id, 10))) + 1;
  const idWidth = Math.max(
    2,
    ...meals.map((meal) => meal.id.length),
    String(nextNumber).length
  );

  return String(nextNumber).padStart(idWidth, "0");
}

module.exports = {
  fullsDir,
  getNextMealId,
  getMealImagePaths,
  loadMeals,
  mealsPath,
  repoRoot,
  saveMeals,
  thumbsDir,
  validateMealAssets,
};
