import { ErrorCode, type RecipeImportDraftView, type RecipeView } from "@batch-cooking/shared";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { ApiError, apiClient } from "../../api/client";
import { SourceLinkIcon } from "../../layouts/nav-icons";
import { errorMessageService } from "../../services/error-message.service";
import { AllergenBadges } from "./badges/AllergenBadges";
import { FavoriteStarButton } from "./badges/FavoriteStarButton";
import { ingredientLabel } from "./ingredients/ingredient-label";
import { StepDescription } from "./steps/StepDescription";
import "./recipes.scss";

/**
 * State {@link RecipeDetailPanel} renders — `"empty"` (no row selected yet)
 * is distinct from `"not-found"` (a selected id that turned out invalid/
 * inaccessible), each with its own message. `"loaded-draft"` is the one
 * variant that isn't a real, saved `Recipe`: a not-yet-imported source
 * item's read-only preview (`RecipeSourcesPanel`'s source tabs) — rendered
 * through this exact same component so viewing one looks like viewing any
 * other recipe, minus every action that doesn't make sense on something
 * that isn't saved yet (favorite/edit/delete — nor a manual "import"
 * button: a source item only ever gets saved as a side effect of adding it
 * to a planning slot, see `RecipePickerDialog`'s `handleSelectDraftItem`,
 * never from this preview). The one action this state does offer is a
 * discreet link to the item's original page, if it has one.
 */
export type RecipeDetailState =
  | { status: "empty" }
  | { status: "loading" }
  | { status: "loaded"; recipe: RecipeView }
  | { status: "loaded-draft"; draft: RecipeImportDraftView }
  | { status: "not-found" }
  | { status: "error" };

/**
 * Right-hand panel of the catalog's master-detail layout (`RecipesPage`) —
 * header (photo + favorite star), name + allergen/disliked-ingredient
 * badges, description, ordered steps. `dislikedIngredientIds` is the
 * *viewer's* personal taste-preference list (`GET
 * /profile/disliked-ingredients`) — crossed here against this recipe's own
 * ingredients to surface just the ones relevant to it, not the viewer's
 * whole list. `onFavoriteToggled`/`onDeleted` are optional — only the
 * `"loaded"` (real recipe) branch ever calls them; callers that only ever
 * pass `"loaded-draft"`/other states (`RecipeSourcesPanel`) can omit them.
 */
export function RecipeDetailPanel({
  state,
  dislikedIngredientIds = [],
  onFavoriteToggled,
  onDeleted,
  showActions = true,
}: {
  state: RecipeDetailState;
  dislikedIngredientIds?: number[];
  onFavoriteToggled?: (recipeId: number, isFavorite: boolean) => void;
  onDeleted?: (recipeId: number) => void;
  /** Default `true`. `false` hides the favorite star and Modifier/Supprimer buttons on the `"loaded"` branch — for a caller previewing someone's pick before deciding what happens next (`RecipePickerDialog`'s browsing step), not viewing one's own catalog. */
  showActions?: boolean;
}) {
  const { t } = useTranslation();

  if (state.status === "empty") {
    return (
      <aside className="recipe-detail-panel">
        <p className="recipe-detail-panel__status">{t("recipes.detail.empty")}</p>
      </aside>
    );
  }
  if (state.status === "loading") {
    return (
      <aside className="recipe-detail-panel">
        <p className="recipe-detail-panel__status">{t("recipes.loading")}</p>
      </aside>
    );
  }
  if (state.status === "not-found") {
    return (
      <aside className="recipe-detail-panel">
        <p className="recipe-detail-panel__status recipe-detail-panel__status--error">
          {t("recipes.notFound")}
        </p>
      </aside>
    );
  }
  if (state.status === "error") {
    return (
      <aside className="recipe-detail-panel">
        <p className="recipe-detail-panel__status recipe-detail-panel__status--error">
          {t("common.loadError")}
        </p>
      </aside>
    );
  }

  if (state.status === "loaded-draft") {
    const { draft } = state;
    return (
      <aside className="recipe-detail-panel">
        <div className="recipe-detail-panel__header">
          <div className="recipe-detail-panel__photo" aria-hidden="true">
            {draft.picture ? <img src={draft.picture} alt="" /> : "🍽️"}
          </div>
          {draft.sourceUrl && (
            <a
              href={draft.sourceUrl}
              target="_blank"
              rel="noreferrer"
              className="recipe-detail-panel__source-link"
              title={t("recipes.sources.detail.viewSource")}
              aria-label={t("recipes.sources.detail.viewSource")}
            >
              <SourceLinkIcon aria-hidden="true" />
            </a>
          )}
        </div>

        <div className="recipe-detail-panel__title-row">
          <div className="recipe-detail-panel__title-main">
            <h2>{draft.name}</h2>
            {draft.portions !== null && (
              <p className="recipe-detail-panel__portions">
                {t("recipes.detail.portions", { count: draft.portions })}
              </p>
            )}
          </div>
        </div>

        {draft.description && (
          <section className="recipe-detail-panel__section recipe-detail-panel__section--description">
            <p className="recipe-detail-panel__description">{draft.description}</p>
          </section>
        )}

        <section className="recipe-detail-panel__section">
          <h3>{t("recipes.stepsTitle")}</h3>
          <ol className="recipe-detail-panel__steps">
            {draft.steps.map((step, index) => (
              // biome-ignore lint/suspicious/noArrayIndexKey: DraftRecipeStepView (packages/shared) has no id — it's an unsaved source preview, nothing to key on yet — and this list is a static read-only render, never reordered/edited here.
              <li key={`${index}-${step.description}`}>
                {step.picture && <img src={step.picture} alt="" />}
                <StepDescription description={step.description} techSteps={step.techSteps} />
              </li>
            ))}
          </ol>
        </section>
      </aside>
    );
  }

  const { recipe } = state;
  const dislikedIngredients = recipe.ingredients
    .map((line) => line.ingredient)
    .filter((ingredient) => dislikedIngredientIds.includes(ingredient.id));

  return (
    <aside className="recipe-detail-panel">
      <div className="recipe-detail-panel__header">
        <div className="recipe-detail-panel__photo" aria-hidden="true">
          {recipe.picture ? <img src={recipe.picture} alt="" /> : "🍽️"}
        </div>
        {showActions && (
          <FavoriteStarButton
            recipeId={recipe.id}
            isFavorite={recipe.isFavorite}
            onToggled={(isFavorite) => onFavoriteToggled?.(recipe.id, isFavorite)}
          />
        )}
      </div>

      <div className="recipe-detail-panel__title-row">
        <div className="recipe-detail-panel__title-main">
          <h2>{recipe.name}</h2>
          <p className="recipe-detail-panel__portions">
            {t("recipes.detail.portions", { count: recipe.portions })}
          </p>
        </div>
        <div className="recipe-detail-panel__title-badges">
          <AllergenBadges allergens={recipe.allergens} />
          {dislikedIngredients.length > 0 && (
            <ul className="disliked-badges">
              {dislikedIngredients.map((ingredient) => (
                <li key={ingredient.id} className="disliked-badge">
                  🚫 {ingredientLabel(ingredient, t)}
                </li>
              ))}
            </ul>
          )}
        </div>
      </div>

      {showActions && (
        <div className="recipe-detail-panel__actions">
          <Link to={`/recettes/${recipe.id}/modifier`} className="recipes-page__new-button">
            {t("recipes.editButton")}
          </Link>
          <DeleteRecipeButton recipeId={recipe.id} onDeleted={() => onDeleted?.(recipe.id)} />
        </div>
      )}

      {recipe.description && (
        <section className="recipe-detail-panel__section recipe-detail-panel__section--description">
          <p className="recipe-detail-panel__description">{recipe.description}</p>
        </section>
      )}

      <section className="recipe-detail-panel__section">
        <h3>{t("recipes.stepsTitle")}</h3>
        {/* Discoverability hint for the highlight/correction feature below
            — nothing about the steps list itself otherwise signals that a
            highlighted technique or a plain-text selection is interactive.
            Tied to `showActions`, same reasoning as `StepDescription`'s own
            `editable` prop right below. */}
        {showActions && (
          <p className="recipe-detail-panel__tech-step-hint">
            {t("recipes.techStepCorrection.discoverabilityHint")}
          </p>
        )}
        <ol className="recipe-detail-panel__steps">
          {recipe.steps.map((step) => (
            <li key={step.id}>
              {step.picture && <img src={step.picture} alt="" />}
              {/* Tied to `showActions` (not unconditionally on): that flag
                  already distinguishes a full recipe view from a lightweight
                  preview (`RecipePickerDialog`'s browsing step, `showActions={false}`)
                  — offering technique corrections in a quick "pick a recipe
                  for planning" preview would be more distracting than
                  useful there, even though the API itself allows it for any
                  viewer who can see the recipe. */}
              <StepDescription
                description={step.description}
                techSteps={step.techSteps}
                editable={showActions}
                recipeId={recipe.id}
                stepId={step.id}
              />
            </li>
          ))}
        </ol>
      </section>
    </aside>
  );
}

/** Delete action with an inline two-step confirmation, same pattern as `HouseholdSettingsPage`'s danger zone. */
function DeleteRecipeButton({ recipeId, onDeleted }: { recipeId: number; onDeleted: () => void }) {
  const { t } = useTranslation();
  const [isConfirming, setIsConfirming] = useState(false);
  const [isDeleting, setIsDeleting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function handleDelete() {
    setIsDeleting(true);
    setError(null);
    try {
      await apiClient.deleteRecipe(recipeId);
      onDeleted();
    } catch (err) {
      const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR;
      setError(errorMessageService.getLabel(code));
      setIsDeleting(false);
    }
  }

  if (!isConfirming) {
    return (
      <button
        type="button"
        className="recipe-detail-panel__danger-button"
        onClick={() => setIsConfirming(true)}
      >
        {t("recipes.deleteButton")}
      </button>
    );
  }

  return (
    <span className="recipe-detail-panel__delete-confirm">
      <button
        type="button"
        className="recipe-detail-panel__danger-button"
        onClick={handleDelete}
        disabled={isDeleting}
      >
        {t("recipes.confirmDeleteButton")}
      </button>
      <button type="button" onClick={() => setIsConfirming(false)} disabled={isDeleting}>
        {t("recipes.cancelDeleteButton")}
      </button>
      {error && <p className="field-error">{error}</p>}
    </span>
  );
}
