import { useMemo, useState } from "react";
import { Clock, Loader2, RefreshCw, Save } from "lucide-react";
import { useTranslation } from "react-i18next";
import { recalculateRecipeCalories } from "@/api/recipe-management.api";
import { extractApiError } from "@/api/axiosInstance";
import { CategoryBadge } from "@/components/ui/CategoryBadge";
import type { RecipeDetail as RecipeDetailModel } from "@/types/recipe.types";
import type { RecalculateRecipeCaloriesResult } from "@/types/recipe-management.types";
import { nextSort, sortBy, sortIndicator, type SortDirection } from "@/utils/sort.util";

type IngredientSortColumn = "name" | "amount";

interface MacroProps {
  label: string;
  value: number | null;
  suffix: string;
  accent: string;
  highlight?: boolean;
}

function Macro({ label, value, suffix, accent, highlight }: MacroProps) {
  return (
    <div
      className={`rounded-2xl border px-4 py-3 text-center ${
        highlight
          ? "border-brand-300 bg-brand-50 dark:border-brand-700 dark:bg-brand-950/30"
          : "border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900"
      }`}
    >
      <p className={`text-2xl font-extrabold ${accent}`}>
        {value != null ? value : "—"}
        {value != null && <span className="text-sm font-semibold"> {suffix}</span>}
      </p>
      <p className="mt-0.5 text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400">
        {label}
      </p>
    </div>
  );
}

function formatAmount(amount: number | null, unit: string | null): string {
  if (amount == null && !unit) return "";
  if (amount == null) return unit ?? "";
  const display = Number.isInteger(amount) ? amount.toString() : amount.toString();
  return unit ? `${display} ${unit}` : `${display} g`;
}

interface RecipeDetailProps {
  recipe: RecipeDetailModel;
  onRecipeUpdated?: (recipe: RecipeDetailModel) => void;
}

function mergePreview(recipe: RecipeDetailModel, preview: RecalculateRecipeCaloriesResult): RecipeDetailModel {
  return {
    ...recipe,
    calories: preview.newCalories ?? recipe.calories,
    protein: preview.newProtein ?? recipe.protein,
    fat: preview.newFat ?? recipe.fat,
    carbs: preview.newCarbs ?? recipe.carbs,
    ingredients: preview.ingredients.map((ing) => ({
      id: ing.id,
      name: ing.name,
      amountGrams: ing.amountGrams,
      unit: ing.unit,
      sortOrder: ing.sortOrder,
      catalogItemId: ing.catalogItemId,
    })),
  };
}

export function RecipeDetail({ recipe, onRecipeUpdated }: RecipeDetailProps) {
  const { t } = useTranslation();
  const [ingredientSort, setIngredientSort] = useState<{
    column: IngredientSortColumn;
    direction: SortDirection;
  }>({ column: "name", direction: "asc" });
  const [targetCalories, setTargetCalories] = useState(
    recipe.calories != null ? String(recipe.calories) : "",
  );
  const [preview, setPreview] = useState<RecalculateRecipeCaloriesResult | null>(null);
  const [pending, setPending] = useState(false);
  const [applyPending, setApplyPending] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const displayRecipe = useMemo(() => {
    if (preview?.applied && preview.recipe) return preview.recipe;
    if (preview) return mergePreview(recipe, preview);
    return recipe;
  }, [preview, recipe]);

  const originalAmounts = useMemo(
    () => new Map(recipe.ingredients.map((ing) => [ing.id, ing.amountGrams])),
    [recipe.ingredients],
  );

  const sortedIngredients = useMemo(
    () =>
      sortBy(displayRecipe.ingredients, ingredientSort.direction, (ing) =>
        ingredientSort.column === "name" ? ing.name : ing.amountGrams,
      ),
    [displayRecipe.ingredients, ingredientSort],
  );

  const toggleIngredientSort = (column: IngredientSortColumn) => {
    setIngredientSort((current) => nextSort(current, column));
  };

  const runRecalculate = async (apply: boolean) => {
    const target = Number(targetCalories);
    if (!Number.isFinite(target) || target < 50) {
      setError(t("recipes.recalculateCaloriesInvalid"));
      return;
    }

    setError(null);
    if (apply) setApplyPending(true);
    else setPending(true);

    try {
      const result = await recalculateRecipeCalories(recipe.id, target, apply);
      setPreview(result);
      if (result.applied && result.recipe) {
        onRecipeUpdated?.(result.recipe);
        setPreview(null);
      }
    } catch (e) {
      setError(extractApiError(e, t("recipes.recalculateCaloriesFailed")));
    } finally {
      setPending(false);
      setApplyPending(false);
    }
  };

  const showingPreview = preview != null && !preview.applied;

  return (
    <article className="space-y-8">
      <header className="space-y-3">
        <CategoryBadge category={displayRecipe.mealCategory} />
        <h1 className="text-3xl font-extrabold tracking-tight">{displayRecipe.name}</h1>
        {displayRecipe.prepTimeMinutes != null && (
          <p className="inline-flex items-center gap-1.5 text-sm text-slate-500 dark:text-slate-400">
            <Clock className="h-4 w-4" />
            {t("recipes.prepTime", { count: displayRecipe.prepTimeMinutes })}
          </p>
        )}
      </header>

      <section className="grid grid-cols-2 gap-3 sm:grid-cols-4">
        <Macro
          label={t("recipes.calories")}
          value={displayRecipe.calories}
          suffix="kcal"
          accent="text-orange-500"
          highlight={showingPreview}
        />
        <Macro
          label={t("recipes.protein")}
          value={displayRecipe.protein}
          suffix="g"
          accent="text-brand-600"
          highlight={showingPreview}
        />
        <Macro
          label={t("recipes.fat")}
          value={displayRecipe.fat}
          suffix="g"
          accent="text-amber-500"
          highlight={showingPreview}
        />
        <Macro
          label={t("recipes.carbs")}
          value={displayRecipe.carbs}
          suffix="g"
          accent="text-sky-500"
          highlight={showingPreview}
        />
      </section>

      <section className="card space-y-3 p-4">
        <h2 className="text-sm font-bold">{t("recipes.recalculateCaloriesTitle")}</h2>
        <p className="text-sm text-slate-500">{t("recipes.recalculateCaloriesHint")}</p>
        <div className="flex flex-wrap items-end gap-3">
          <label className="block text-sm">
            {t("recipes.targetCalories")}
            <input
              type="number"
              min={50}
              max={10000}
              className="input mt-1 w-36"
              value={targetCalories}
              onChange={(e) => {
                setTargetCalories(e.target.value);
                setPreview(null);
              }}
            />
          </label>
          <button
            type="button"
            className="btn-secondary"
            disabled={pending || applyPending}
            onClick={() => void runRecalculate(false)}
          >
            {pending ? <Loader2 className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />}
            {t("recipes.recalculate")}
          </button>
          {showingPreview ? (
            <button
              type="button"
              className="btn-primary"
              disabled={pending || applyPending}
              onClick={() => void runRecalculate(true)}
            >
              {applyPending ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
              {t("recipes.applyRecalculation")}
            </button>
          ) : null}
        </div>
        {showingPreview ? (
          <p className="text-sm text-brand-700 dark:text-brand-300">
            {t("recipes.recalculatePreview", {
              previous: preview.previousCalories ?? "—",
              next: preview.newCalories ?? "—",
              target: preview.targetCalories,
            })}
            {preview.unlinkedIngredientCount > 0
              ? ` ${t("recipes.recalculateUnlinkedHint", { count: preview.unlinkedIngredientCount })}`
              : ""}
          </p>
        ) : null}
        {error ? (
          <p role="alert" className="text-sm text-red-600 dark:text-red-400">
            {error}
          </p>
        ) : null}
      </section>

      <div className="grid gap-8 lg:grid-cols-[1fr_1.4fr]">
        <section>
          <h2 className="text-lg font-bold">{t("recipes.ingredients")}</h2>
          {displayRecipe.ingredients.length === 0 ? (
            <p className="mt-3 text-sm text-slate-500">{t("recipes.noIngredients")}</p>
          ) : (
            <div className="mt-4 overflow-hidden rounded-2xl border border-slate-200 dark:border-slate-800">
              <div className="grid grid-cols-[1fr_auto] border-b border-slate-200 bg-slate-50 px-4 py-2 text-xs font-semibold uppercase tracking-wide text-slate-500 dark:border-slate-800 dark:bg-slate-800/50">
                <button
                  type="button"
                  className="text-left hover:text-slate-700 dark:hover:text-slate-200"
                  onClick={() => toggleIngredientSort("name")}
                >
                  {t("ingredientCatalog.columns.name")}
                  {sortIndicator(ingredientSort, "name")}
                </button>
                <button
                  type="button"
                  className="text-right hover:text-slate-700 dark:hover:text-slate-200"
                  onClick={() => toggleIngredientSort("amount")}
                >
                  {t("diet.grams")}
                  {sortIndicator(ingredientSort, "amount")}
                </button>
              </div>
              <ul className="divide-y divide-slate-200 dark:divide-slate-800">
                {sortedIngredients.map((ing) => {
                  const previous = originalAmounts.get(ing.id);
                  const changed =
                    showingPreview &&
                    previous != null &&
                    ing.amountGrams != null &&
                    previous !== ing.amountGrams;
                  return (
                    <li key={ing.id} className="grid grid-cols-[1fr_auto] items-center px-4 py-3 text-sm">
                      <span>{ing.name}</span>
                      <span
                        className={`font-medium ${
                          changed ? "text-brand-700 dark:text-brand-300" : "text-slate-500 dark:text-slate-400"
                        }`}
                      >
                        {formatAmount(ing.amountGrams, ing.unit)}
                        {changed ? (
                          <span className="ml-2 text-xs text-slate-400 line-through">
                            {formatAmount(previous, ing.unit)}
                          </span>
                        ) : null}
                      </span>
                    </li>
                  );
                })}
              </ul>
            </div>
          )}
        </section>

        <section>
          <h2 className="text-lg font-bold">{t("recipes.preparation")}</h2>
          {displayRecipe.steps.length === 0 ? (
            <p className="mt-3 text-sm text-slate-500">{t("recipes.noSteps")}</p>
          ) : (
            <ol className="mt-4 space-y-4">
              {displayRecipe.steps.map((step) => (
                <li key={step.id} className="flex gap-4">
                  <span className="grid h-8 w-8 shrink-0 place-items-center rounded-full bg-brand-600 text-sm font-bold text-white">
                    {step.stepNumber}
                  </span>
                  <p className="pt-1 text-sm leading-relaxed text-slate-700 dark:text-slate-300">
                    {step.description}
                  </p>
                </li>
              ))}
            </ol>
          )}
        </section>
      </div>
    </article>
  );
}
