import { DateTime, formatDateOnly, getWeekStart, toDateOnly } from "@batch-cooking/date-tools";
import {
  MEALS,
  type Meal,
  type PlanningItemView,
  type PlanningView,
  WEEK_DAYS,
} from "@batch-cooking/shared";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { apiClient } from "../../api/client";
import { type PlanningSlot, RecipePickerDialog } from "../../features/planning/RecipePickerDialog";
import { WeekNavigator } from "../../features/planning/WeekNavigator";
import "./planning-page.scss";

/** Load state for the `GET /planning` call — a discriminated union so a stale/impossible combination (e.g. "loading" with data) can't be represented. */
type PlanningState =
  | { status: "loading" }
  | { status: "loaded"; planning: PlanningView | null }
  | { status: "error" };

/** Meals that close out a "moment of the day" group (Matin/Midi/Après-midi/Soir) — see `.band-end` in planning-page.scss for the resulting border treatment. */
const BAND_END_MEALS: ReadonlySet<Meal> = new Set(["collation", "dejeuner", "gouter"]);

/**
 * Landing page for an authenticated visitor — the household's planning for
 * a selectable week, laid out as a grid (days × meals). Behind
 * {@link RequireAuth} (via `AppLayout`), so this only renders once a
 * session is confirmed.
 *
 * `null` from the API is a normal, common state (no planning for that week
 * yet) — unlike the previous single-day table view this replaces, it isn't
 * rendered as a separate "empty" message: the grid itself, with every cell
 * showing just its "+" button, already communicates that. The "+" itself
 * isn't wired to anything yet (no recipe catalog to search — see the
 * planning page's plan/PR description) — a future task.
 */
export function PlanningPage() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const [weekStart, setWeekStart] = useState<DateTime>(() => getWeekStart(DateTime.utc()));
  const [state, setState] = useState<PlanningState>({ status: "loading" });
  // The slot a `RecipePickerDialog` is currently open for — `null` means
  // closed. Mounting the dialog only while this is set (rather than an
  // always-mounted `isOpen` toggle) resets its internal filter/search
  // state for free on every open, same convention as `WeekNavigator`'s own
  // `CalendarPopover` (features/planning/WeekNavigator.tsx).
  const [openSlot, setOpenSlot] = useState<PlanningSlot | null>(null);

  useEffect(() => {
    let cancelled = false;
    setState({ status: "loading" });

    apiClient
      .getPlanningForWeek(formatDateOnly(weekStart))
      .then((planning) => {
        if (!cancelled) setState({ status: "loaded", planning });
      })
      .catch(() => {
        if (!cancelled) setState({ status: "error" });
      });

    return () => {
      cancelled = true;
    };
  }, [weekStart]);

  /**
   * Applies `updater` to the currently loaded planning's items, patching
   * local state without a full `GET /planning` refetch — same philosophy
   * as `RecipesPage`'s `handleFavoriteToggled`/`handleDeleted`. Builds a
   * placeholder parent `Planning` if none existed yet (the week's very
   * first add, see `addPlanningItem`'s `findOrCreatePlanningForWeek`) —
   * its `id`/`startDate`/`finishDate` are never read anywhere on this page
   * (only `planning.items` is), so a placeholder id is harmless; the next
   * week-navigation away and back re-fetches the real row anyway.
   */
  function patchPlanningItems(updater: (items: PlanningItemView[]) => PlanningItemView[]) {
    setState((prev) => {
      if (prev.status !== "loaded") return prev;
      const planning = prev.planning ?? {
        id: -1,
        startDate: weekStart.toISO() ?? "",
        finishDate: weekStart.plus({ days: 6 }).toISO() ?? "",
        items: [],
      };
      return { status: "loaded", planning: { ...planning, items: updater(planning.items) } };
    });
  }

  function handleAdded(item: PlanningItemView) {
    patchPlanningItems((items) => [...items, item]);
  }

  /** Optimistic removal (same rollback-on-failure idea as `FavoriteStarButton`) — the API call already happened by the time this needs to roll back, `removePlanningItem` having already rejected. */
  async function handleRemove(item: PlanningItemView) {
    patchPlanningItems((items) => items.filter((i) => i.id !== item.id));
    try {
      await apiClient.removePlanningItem(item.id);
    } catch {
      patchPlanningItems((items) => [...items, item]);
    }
  }

  // Enabled only once we know the week has at least one planned recipe —
  // "cuisiner" an empty week would just land on the page's own empty state.
  const hasPlannedRecipes = state.status === "loaded" && (state.planning?.items.length ?? 0) > 0;

  return (
    <div className="planning-page">
      <div className="planning-page__header">
        <h1>{t("planning.title")}</h1>
        <WeekNavigator weekStart={weekStart} onChangeWeek={setWeekStart} />
        <button
          type="button"
          className="planning-page__cook-btn"
          disabled={!hasPlannedRecipes}
          onClick={() => navigate(`/cuisiner?date=${formatDateOnly(weekStart)}`)}
        >
          {t("planning.startCooking")}
        </button>
      </div>

      {state.status === "loading" && (
        <p className="planning-page__status">{t("planning.loading")}</p>
      )}

      {state.status === "error" && (
        <p className="planning-page__status planning-page__status--error">
          {t("common.loadError")}
        </p>
      )}

      {state.status === "loaded" && (
        <PlanningGrid
          weekStart={weekStart}
          planning={state.planning}
          onAddSlot={setOpenSlot}
          onRemoveItem={handleRemove}
        />
      )}

      {openSlot && (
        <RecipePickerDialog
          slot={openSlot}
          onClose={() => setOpenSlot(null)}
          onAdded={handleAdded}
        />
      )}
    </div>
  );
}

/** The week grid itself — 7 day columns × 5 meal rows. */
function PlanningGrid({
  weekStart,
  planning,
  onAddSlot,
  onRemoveItem,
}: {
  weekStart: DateTime;
  planning: PlanningView | null;
  onAddSlot: (slot: PlanningSlot) => void;
  onRemoveItem: (item: PlanningItemView) => void;
}) {
  const { t } = useTranslation();
  const today = toDateOnly(DateTime.utc());
  const days = WEEK_DAYS.map((weekDay, i) => ({ weekDay, date: weekStart.plus({ days: i }) }));
  const items = planning?.items ?? [];

  return (
    <div className="planning-grid-wrapper">
      <table className="planning-grid">
        <thead>
          <tr>
            <th />
            {days.map(({ weekDay, date }) => (
              <th key={weekDay} className={date.hasSame(today, "day") ? "today" : undefined}>
                <span className="day-name">{t(`common.days.${weekDay}`)}</span>
                <span className="day-date">{date.day}</span>
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {MEALS.map((meal) => (
            <tr key={meal} className={BAND_END_MEALS.has(meal) ? "band-end" : undefined}>
              <th>{t(`planning.meals.${meal}`)}</th>
              {days.map(({ weekDay, date }) => (
                <MealCell
                  key={weekDay}
                  isToday={date.hasSame(today, "day")}
                  items={items.filter((item) => item.weekDay === weekDay && item.meal === meal)}
                  onAdd={() => onAddSlot({ date: formatDateOnly(date), weekDay, meal })}
                  onRemove={onRemoveItem}
                />
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

/** One (day, meal) cell: the recipes already planned for it (as pills, each showing its portion count) plus the "+" to add another. */
function MealCell({
  isToday,
  items,
  onAdd,
  onRemove,
}: {
  isToday: boolean;
  items: PlanningItemView[];
  onAdd: () => void;
  onRemove: (item: PlanningItemView) => void;
}) {
  const { t } = useTranslation();

  return (
    <td className={isToday ? "meal-cell today" : "meal-cell"}>
      <div className="meal-cell__content">
        {items.length > 0 && (
          <div className="meal-cell__recipes">
            {items.map((item) => (
              <span key={item.id} className="recipe-chip">
                <span className="recipe-chip__name">
                  {item.recipe.name} · ×{item.portions}
                </span>
                <button
                  type="button"
                  className="recipe-chip__remove"
                  title={t("planning.grid.removeRecipe")}
                  onClick={() => onRemove(item)}
                >
                  ✕
                </button>
              </span>
            ))}
          </div>
        )}
        <button
          type="button"
          className="add-recipe-btn"
          title={t("planning.grid.addRecipe")}
          onClick={onAdd}
        >
          +
        </button>
      </div>
    </td>
  );
}
