import {
  type DietView,
  ErrorCode,
  type IngredientView,
  type Meal,
  type PlanningItemView,
  type RecipeSummaryView,
  type WeekDay,
} from "@batch-cooking/shared";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ApiError, apiClient } from "../../api/client";
import { CheckboxOption } from "../../components/ui/Checkbox";
import { Dialog } from "../../components/ui/Dialog";
import { errorMessageService } from "../../services/error-message.service";
import { DietTagSelect } from "../recipes/DietTagSelect";
import { IngredientPicker } from "../recipes/IngredientPicker";
import { RecipeSourcesPanel } from "../recipes/RecipeSourcesPanel";
import { RecipeTable } from "../recipes/RecipeTable";
import { RecipeTabs, type RecipesPageTab } from "../recipes/RecipeTabs";
import "./recipe-picker-dialog.scss";

/** Debounce for the search field — same value as `RecipesPage`'s. */
const SEARCH_DEBOUNCE_MS = 300;

/** Load state for the filtered catalog list, same discriminated-union shape as `RecipesPage`'s `RecipeListState`. */
type ListState =
  | { status: "loading" }
  | { status: "loaded"; recipes: RecipeSummaryView[] }
  | { status: "error" };

/**
 * The (day, meal) slot a `RecipePickerDialog` is adding a recipe to —
 * `date` is that day's `YYYY-MM-DD` (the specific date within the
 * displayed week, not just its weekday), needed by `POST /planning/items`
 * to resolve which week's `Planning` row to attach to.
 */
export interface PlanningSlot {
  date: string;
  weekDay: WeekDay;
  meal: Meal;
}

/**
 * Recipe-selection dialog opened from a planning grid cell's "+" button
 * (see `PlanningPage.tsx`'s `MealCell`) — the same catalog browsing
 * experience as `/recettes` (`RecipeTabs` + `RecipeTable`, reused as-is),
 * with three extra filters layered on top of the plain name search
 * (ingredients / regime / "convient à tout le foyer" toggle, all wired to
 * `GET /recipes`'s corresponding query params) since browsing here is
 * about finding something to cook, not just looking something up. The
 * "Sources" tab is included too (unlike an earlier version of this dialog
 * — see `ImportRecipePage`'s `planningSlot`, the review/import flow that
 * made including it here worthwhile): picking an already-imported item
 * behaves exactly like picking a regular recipe, and picking one that
 * isn't imported yet hands off to that review screen, which adds the
 * freshly-created recipe straight to this slot once it's saved.
 *
 * Mounted only while open (see `PlanningPage`, same conditional-mount
 * convention as its own `CalendarPopover`) — every piece of local state
 * below resets for free the next time it's reopened, no manual reset
 * needed.
 *
 * Selecting a row doesn't navigate anywhere (unlike `RecipesPage`'s own
 * use of `RecipeTable`) — it switches this same dialog to a small
 * "how many portions?" confirmation step, then calls `POST
 * /planning/items` on submit. The one exception is picking a not-yet-
 * imported source item, which does navigate away entirely (to
 * `/recettes/importer/...`) — that flow has its own portions field
 * already, on the review screen itself.
 */
export function RecipePickerDialog({
  slot,
  onClose,
  onAdded,
}: {
  slot: PlanningSlot;
  onClose: () => void;
  onAdded: (item: PlanningItemView) => void;
}) {
  const { t } = useTranslation();

  const [activeTab, setActiveTab] = useState<RecipesPageTab>("favoris");
  const [search, setSearch] = useState("");
  const [debouncedSearch, setDebouncedSearch] = useState("");
  const [selectedIngredientIds, setSelectedIngredientIds] = useState<number[]>([]);
  const [selectedDietIds, setSelectedDietIds] = useState<number[]>([]);
  const [suitableForHousehold, setSuitableForHousehold] = useState(false);
  const [hasHousehold, setHasHousehold] = useState(false);
  const [isIngredientPickerOpen, setIsIngredientPickerOpen] = useState(false);

  const [ingredientsCatalog, setIngredientsCatalog] = useState<IngredientView[]>([]);
  const [dietsCatalog, setDietsCatalog] = useState<DietView[]>([]);
  const [listState, setListState] = useState<ListState>({ status: "loading" });
  // Set when picking an already-imported source item fails to resolve to a
  // real recipe (see `handleSelectImportedRecipe`) — a rare race (the
  // recipe was deleted between the browse fetch and the click), surfaced
  // the same way any other catalog load error is on this dialog.
  const [sourceSelectError, setSourceSelectError] = useState(false);

  // The recipe picked in step 1 — `null` while still browsing, set once a
  // row is clicked to switch this dialog into its confirmation step.
  const [selectedRecipe, setSelectedRecipe] = useState<RecipeSummaryView | null>(null);
  const [portions, setPortions] = useState("1");
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState<string | null>(null);

  useEffect(() => {
    const timeout = window.setTimeout(() => setDebouncedSearch(search), SEARCH_DEBOUNCE_MS);
    return () => window.clearTimeout(timeout);
  }, [search]);

  // Reference lists + "does the viewer have a household" — loaded once,
  // they don't change while the dialog is open.
  useEffect(() => {
    apiClient
      .getIngredients()
      .then(setIngredientsCatalog)
      .catch(() => setIngredientsCatalog([]));
    apiClient
      .getDiets()
      .then(setDietsCatalog)
      .catch(() => setDietsCatalog([]));
    apiClient
      .getCurrentHouse()
      .then((house) => setHasHousehold(house !== null))
      .catch(() => setHasHousehold(false));
  }, []);

  useEffect(() => {
    // The "sources" tab doesn't query the recipe table at all — same guard
    // as `RecipesPage`'s own identical effect.
    if (activeTab === "sources") return;
    let cancelled = false;
    setListState({ status: "loading" });

    apiClient
      .listRecipes(activeTab, {
        search: debouncedSearch.trim() || undefined,
        suitableForHousehold: suitableForHousehold || undefined,
        ingredientIds: selectedIngredientIds,
        dietIds: selectedDietIds,
      })
      .then((recipes) => {
        if (!cancelled) setListState({ status: "loaded", recipes });
      })
      .catch(() => {
        if (!cancelled) setListState({ status: "error" });
      });

    return () => {
      cancelled = true;
    };
  }, [activeTab, debouncedSearch, selectedIngredientIds, selectedDietIds, suitableForHousehold]);

  const selectedIngredients = ingredientsCatalog.filter((ingredient) =>
    selectedIngredientIds.includes(ingredient.id),
  );

  /** Picking an already-imported source item (`RecipeSourcesPanel`'s "sources" tab) — resolved to its full recipe, then treated exactly like picking that same recipe from one of the regular tabs, moving straight to the confirm-portions step below. */
  function handleSelectImportedRecipe(recipeId: number) {
    setSourceSelectError(false);
    apiClient
      .getRecipe(recipeId)
      .then((recipe) => {
        setSelectedRecipe(recipe);
        setPortions(String(recipe.portions));
      })
      .catch(() => setSourceSelectError(true));
  }

  async function handleConfirm() {
    if (!selectedRecipe) return;
    const parsedPortions = Number(portions);
    if (!Number.isInteger(parsedPortions) || parsedPortions < 1) return;

    setIsSubmitting(true);
    setSubmitError(null);
    try {
      const item = await apiClient.addPlanningItem({
        date: slot.date,
        weekDay: slot.weekDay,
        meal: slot.meal,
        recipeId: selectedRecipe.id,
        portions: parsedPortions,
      });
      onAdded(item);
      onClose();
    } catch (err) {
      const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR;
      setSubmitError(errorMessageService.getLabel(code));
      setIsSubmitting(false);
    }
  }

  if (selectedRecipe) {
    return (
      <Dialog
        onClose={onClose}
        title={t("planning.picker.confirmTitle", { recipe: selectedRecipe.name })}
      >
        <div className="recipe-picker-confirm">
          <label htmlFor="planning-picker-portions">{t("planning.picker.portionsLabel")}</label>
          <input
            id="planning-picker-portions"
            type="number"
            min="1"
            step="1"
            value={portions}
            onChange={(e) => setPortions(e.target.value)}
          />
          {submitError && <p className="field-error">{submitError}</p>}
          <div className="recipe-picker-confirm__actions">
            <button type="button" onClick={() => setSelectedRecipe(null)} disabled={isSubmitting}>
              {t("planning.picker.backButton")}
            </button>
            <button
              type="button"
              className="recipe-picker-confirm__confirm"
              onClick={handleConfirm}
              disabled={isSubmitting}
            >
              {isSubmitting ? t("planning.picker.adding") : t("planning.picker.confirmButton")}
            </button>
          </div>
        </div>
      </Dialog>
    );
  }

  return (
    <Dialog onClose={onClose} title={t("planning.picker.title")} className="recipe-picker-dialog">
      {activeTab !== "sources" && (
        <div className="recipe-picker__filters">
          <input
            type="search"
            className="recipe-picker__search"
            placeholder={t("planning.picker.searchPlaceholder")}
            value={search}
            onChange={(e) => setSearch(e.target.value)}
          />

          <div className="recipe-picker__ingredient-filter">
            <span className="recipe-picker__filter-label">
              {t("planning.picker.ingredientsFilterLabel")}
            </span>
            <div className="recipe-picker__chips">
              {selectedIngredients.map((ingredient) => (
                <span key={ingredient.id} className="filter-chip">
                  {t(`catalog.ingredients.${ingredient.key}`)}
                  <button
                    type="button"
                    onClick={() =>
                      setSelectedIngredientIds((ids) => ids.filter((id) => id !== ingredient.id))
                    }
                  >
                    ✕
                  </button>
                </span>
              ))}
              <button
                type="button"
                className="recipe-picker__toggle-ingredient-picker"
                onClick={() => setIsIngredientPickerOpen((open) => !open)}
                aria-expanded={isIngredientPickerOpen}
              >
                +{" "}
                {isIngredientPickerOpen
                  ? t("planning.picker.hideIngredientPicker")
                  : t("planning.picker.addIngredientFilter")}
              </button>
            </div>
            {isIngredientPickerOpen && (
              <IngredientPicker
                ingredients={ingredientsCatalog}
                excludeIds={selectedIngredientIds}
                onSelect={(ingredient) =>
                  setSelectedIngredientIds((ids) => [...ids, ingredient.id])
                }
              />
            )}
          </div>

          <DietTagSelect
            diets={dietsCatalog}
            value={selectedDietIds}
            onChange={setSelectedDietIds}
          />

          {hasHousehold && (
            <CheckboxOption checked={suitableForHousehold} onChange={setSuitableForHousehold}>
              {t("planning.picker.suitableForHouseholdLabel")}
            </CheckboxOption>
          )}
        </div>
      )}

      <RecipeTabs active={activeTab} onChange={setActiveTab} />

      {activeTab === "sources" ? (
        <>
          {sourceSelectError && (
            <p className="recipes-page__status recipes-page__status--error">
              {t("common.loadError")}
            </p>
          )}
          <RecipeSourcesPanel
            planningSlot={slot}
            onSelectImportedRecipe={handleSelectImportedRecipe}
          />
        </>
      ) : (
        <>
          {listState.status === "loading" && (
            <p className="recipes-page__status">{t("planning.picker.loading")}</p>
          )}
          {listState.status === "error" && (
            <p className="recipes-page__status recipes-page__status--error">
              {t("common.loadError")}
            </p>
          )}
          {listState.status === "loaded" && listState.recipes.length === 0 && (
            <p className="recipes-page__status">{t("planning.picker.empty")}</p>
          )}
          {listState.status === "loaded" && listState.recipes.length > 0 && (
            <RecipeTable
              recipes={listState.recipes}
              selectedId={null}
              onSelect={(id) => {
                const recipe = listState.recipes.find((r) => r.id === id) ?? null;
                setSelectedRecipe(recipe);
                // Pre-fill from the recipe's own written yield rather than
                // always starting at 1 — still freely editable below, this
                // is just a better starting point (see `Recipe.portions`).
                if (recipe) setPortions(String(recipe.portions));
              }}
            />
          )}
        </>
      )}
    </Dialog>
  );
}
