(function (root, factory) {
  const reviewedDraft = factory();
  if (typeof module === "object" && module.exports) {
    module.exports = reviewedDraft;
  } else {
    root.MealSnapshotReviewedDraft = reviewedDraft;
  }
}(typeof globalThis !== "undefined" ? globalThis : this, function () {
  "use strict";

  const DISCLAIMER = "This is an estimate based on your photo. It may miss sauces, oils, ingredients, or portion sizes. Review and adjust before saving.";
  const NUTRITION_FIELDS = Object.freeze(["calories", "protein", "carbs", "fat"]);

  class ReviewedDraftValidationError extends Error {
    constructor(code, message) {
      super(message);
      this.name = "ReviewedDraftValidationError";
      this.code = code;
    }
  }

  function boundedText(value, maximum, code) {
    if (typeof value !== "string" || !value.trim()) {
      throw new ReviewedDraftValidationError(code, "A required reviewed-draft field is missing.");
    }
    return value.trim().slice(0, maximum);
  }

  function nutritionValue(value) {
    if (!Number.isFinite(value) || value < 0 || value > 10_000) {
      throw new ReviewedDraftValidationError("NUTRITION_INVALID", "Reviewed nutrition must be a non-negative number.");
    }
    return value;
  }

  function hasOnlyKeys(value, keys) {
    return Boolean(value)
      && typeof value === "object"
      && !Array.isArray(value)
      && Object.keys(value).length === keys.length
      && Object.keys(value).every((key) => keys.includes(key));
  }

  function createReviewedMealDraft(sourceDraft, options = {}) {
    if (!sourceDraft || typeof sourceDraft !== "object") {
      throw new ReviewedDraftValidationError("DRAFT_REQUIRED", "A local reviewed draft is required.");
    }
    const title = boundedText(sourceDraft.title, 100, "TITLE_REQUIRED");
    if (!Array.isArray(sourceDraft.ingredients) || sourceDraft.ingredients.length === 0) {
      throw new ReviewedDraftValidationError("INGREDIENTS_REQUIRED", "At least one reviewed ingredient is required.");
    }
    if (sourceDraft.ingredients.length > 25) {
      throw new ReviewedDraftValidationError("INGREDIENTS_LIMIT", "The local reviewed draft has too many ingredients.");
    }

    const ingredients = Object.freeze(sourceDraft.ingredients.map((ingredient) => Object.freeze({
      name: boundedText(ingredient?.name, 120, "INGREDIENT_INVALID"),
      amount: boundedText(ingredient?.amount, 120, "INGREDIENT_INVALID")
    })));
    const nutrition = Object.freeze({
      calories: nutritionValue(sourceDraft.calories),
      protein: nutritionValue(sourceDraft.protein),
      carbs: nutritionValue(sourceDraft.carbs),
      fat: nutritionValue(sourceDraft.fat)
    });
    const review = Object.freeze({
      required: true,
      userEdited: options.userEdited === true
    });

    return Object.freeze({
      schemaVersion: "1.0",
      kind: "meal_snapshot_reviewed_draft",
      lifecycle: "local_only",
      source: "local_mock",
      title,
      nutrition,
      ingredients,
      review,
      disclaimer: DISCLAIMER,
      canSave: false,
      shouldSave: false,
      persisted: false
    });
  }

  function validateReviewedMealDraft(draft) {
    const rootKeys = [
      "schemaVersion",
      "kind",
      "lifecycle",
      "source",
      "title",
      "nutrition",
      "ingredients",
      "review",
      "disclaimer",
      "canSave",
      "shouldSave",
      "persisted"
    ];
    if (!hasOnlyKeys(draft, rootKeys)) return false;
    if (draft.schemaVersion !== "1.0"
      || draft.kind !== "meal_snapshot_reviewed_draft"
      || draft.lifecycle !== "local_only"
      || draft.source !== "local_mock"
      || typeof draft.title !== "string"
      || !draft.title
      || draft.title.length > 100
      || draft.disclaimer !== DISCLAIMER
      || draft.canSave !== false
      || draft.shouldSave !== false
      || draft.persisted !== false) return false;

    if (!hasOnlyKeys(draft.nutrition, NUTRITION_FIELDS)
      || !NUTRITION_FIELDS.every((field) => (
        Number.isFinite(draft.nutrition[field])
        && draft.nutrition[field] >= 0
        && draft.nutrition[field] <= 10_000
      ))) {
      return false;
    }
    if (!Array.isArray(draft.ingredients)
      || draft.ingredients.length === 0
      || draft.ingredients.length > 25
      || !draft.ingredients.every((ingredient) => (
        hasOnlyKeys(ingredient, ["name", "amount"])
        && typeof ingredient.name === "string"
        && Boolean(ingredient.name)
        && ingredient.name.length <= 120
        && typeof ingredient.amount === "string"
        && Boolean(ingredient.amount)
        && ingredient.amount.length <= 120
      ))) return false;

    return hasOnlyKeys(draft.review, ["required", "userEdited"])
      && draft.review.required === true
      && typeof draft.review.userEdited === "boolean";
  }

  return Object.freeze({
    DISCLAIMER,
    ReviewedDraftValidationError,
    createReviewedMealDraft,
    validateReviewedMealDraft
  });
}));
