import jsPDF from "jspdf";
import html2canvas from "html2canvas";
import i18n from "@/i18n";
import type { RecipeDetail } from "@/types/recipe.types";
import { isNonQuantifiedUnit, normalizeIngredientQuantity } from "@/utils/ingredientQuantity.util";

export interface ShoppingItem {
  name: string;
  totalGrams: number | null;
  totalAmount: number | null;
  unit: string | null;
  quantified: boolean;
  sources: string[];
  contributions: Array<{ recipe: string; amount: number | null }>;
}

/**
 * Aggregates ingredients across the selected recipes.
 * Grouping key normalizes name (trim + lowercase); display keeps original capitalization.
 */
export function buildShoppingList(recipes: RecipeDetail[]): ShoppingItem[] {
  const buckets = new Map<string, ShoppingItem>();

  for (const recipe of recipes) {
    for (const ing of recipe.ingredients) {
      const displayName = ing.name.trim();
      const normName = displayName.toLowerCase();
      const normalized = normalizeIngredientQuantity(ing.amountGrams, ing.unit);
      const unitRaw = normalized.unit;
      const nonQuantified =
        isNonQuantifiedUnit(unitRaw) || (unitRaw === null && normalized.amountGrams === null);

      const key = nonQuantified ? `nq:${normName}` : `g:${normName}`;

      let bucket = buckets.get(key);
      if (!bucket) {
        bucket = {
          name: displayName,
          totalGrams: null,
          totalAmount: null,
          unit: nonQuantified ? unitRaw : null,
          quantified: !nonQuantified,
          sources: [],
          contributions: [],
        };
        buckets.set(key, bucket);
      }

      if (!bucket.sources.includes(recipe.name)) {
        bucket.sources.push(recipe.name);
      }

      if (nonQuantified) {
        continue;
      }

      const amount = normalized.amountGrams;
      if (amount == null || amount <= 0) {
        continue;
      }

      bucket.contributions.push({ recipe: recipe.name, amount });
      bucket.totalGrams = (bucket.totalGrams ?? 0) + amount;
    }
  }

  return Array.from(buckets.values()).sort((a, b) =>
    a.name.localeCompare(b.name, undefined, { sensitivity: "base" }),
  );
}

function roundMacro(value: number): number {
  return Math.round(value * 10) / 10;
}

/** Scales a recipe's macros and ingredient amounts for the planned portion count. */
export function scaleRecipeDetail(recipe: RecipeDetail, portions: number): RecipeDetail {
  return {
    ...recipe,
    calories: recipe.calories != null ? Math.round(recipe.calories * portions) : null,
    protein: recipe.protein != null ? roundMacro(recipe.protein * portions) : null,
    fat: recipe.fat != null ? roundMacro(recipe.fat * portions) : null,
    carbs: recipe.carbs != null ? roundMacro(recipe.carbs * portions) : null,
    ingredients: recipe.ingredients.map((ing) => ({
      ...ing,
      amountGrams: ing.amountGrams != null ? roundMacro(ing.amountGrams * portions) : null,
    })),
  };
}

function formatNumber(value: number): string {
  return Number.isInteger(value) ? value.toString() : value.toFixed(2).replace(/\.?0+$/, "");
}

/** Right-hand main quantity, e.g. "540 g", "7 pcs", "to taste". */
export function formatQuantity(item: ShoppingItem): string {
  if (!item.quantified) {
    return item.unit ? item.unit : i18n.t("pdf.toTaste");
  }
  return `${formatNumber(item.totalGrams ?? 0)} g`;
}

/** Parenthetical breakdown shown when the ingredient comes from more than one recipe. */
export function formatBreakdown(item: ShoppingItem): string | null {
  if (!item.quantified || item.sources.length <= 1) {
    return null;
  }

  const amounts = item.contributions.map((c) => c.amount).filter((a): a is number => a !== null);
  if (amounts.length === 0) {
    return null;
  }

  const unitLabel = "g";
  const allEqual = amounts.every((a) => a === amounts[0]);

  if (allEqual && amounts.length > 1) {
    return `${amounts.length} × ${formatNumber(amounts[0])} ${unitLabel}`;
  }

  return amounts.map((a) => `${formatNumber(a)} ${unitLabel}`).join(" + ");
}

/**
 * Renders every `.pdf-page` element inside #pdf-render-area into an A4 portrait PDF,
 * one source element per page, then triggers a download.
 */
export async function generatePdf(
  fileName = "meal-plan.pdf",
  containerId = "pdf-render-area",
): Promise<void> {
  const container = document.getElementById(containerId);
  if (!container) {
    throw new Error(i18n.t("errors.pdfRenderAreaMissing"));
  }

  const pages = Array.from(container.querySelectorAll<HTMLElement>(".pdf-page"));
  if (pages.length === 0) {
    throw new Error(i18n.t("errors.pdfNothingToExport"));
  }

  const pdf = new jsPDF({ unit: "pt", format: "a4", orientation: "portrait" });
  const pageWidth = pdf.internal.pageSize.getWidth();
  const pageHeight = pdf.internal.pageSize.getHeight();

  for (let i = 0; i < pages.length; i += 1) {
    const canvas = await html2canvas(pages[i], {
      scale: 2,
      backgroundColor: "#ffffff",
      useCORS: true,
      logging: false,
    });

    const imgData = canvas.toDataURL("image/jpeg", 0.92);
    const imgWidth = pageWidth;
    const imgHeight = (canvas.height * imgWidth) / canvas.width;

    if (i > 0) {
      pdf.addPage();
    }

    // If a single rendered page is taller than A4, scale it down to fit one page.
    const finalHeight = Math.min(imgHeight, pageHeight);
    const finalWidth = finalHeight === imgHeight ? imgWidth : (canvas.width * finalHeight) / canvas.height;
    pdf.addImage(imgData, "JPEG", (pageWidth - finalWidth) / 2, 0, finalWidth, finalHeight);
  }

  pdf.save(fileName);
}
