import { createContext, useContext, useEffect, useRef, useState, type ReactElement } from "react";
import { flushSync } from "react-dom";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { zodResolver } from "@hookform/resolvers/zod";
import { addDays, format, isValid, parseISO } from "date-fns";
import { ImagePlus, Info, Loader2, Pencil, Plus, Trash2, X } from "lucide-react";
import {
  useController,
  useForm,
  useFormContext,
  useWatch,
  type FieldErrors,
  type FieldPath,
} from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";

import { DateField } from "@/components/app/DateField";
import { DepartureDatesManager } from "@/components/app/DepartureDatesManager";
import {
  CheckboxField as GenericCheckboxField,
  DropdownField as GenericDropdownField,
  MultiDropdownField as GenericMultiDropdownField,
  RequiredMark,
  TextAreaField as GenericTextAreaField,
  TextField as GenericTextField,
} from "@/components/app/FormFields";
import { ItineraryManager } from "@/components/app/ItineraryManager";
import { Page } from "@/components/app/Page";
import { PageHeading } from "@/components/app/Primitives";
import { UnsavedChangesGuard } from "@/components/app/UnsavedChangesGuard";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { useCompanySettings } from "@/features/company-settings/api";
import { useDestinations } from "@/features/destinations/api";
import {
  useCreateTour,
  useUpdateTour,
  type CreateTourPayload,
  type Tour,
} from "@/features/tours/api";
import { useUploadImage } from "@/features/uploads/api";
import { ApiClientError } from "@/lib/api";
import { toWebP } from "@/lib/image";

export const Route = createFileRoute("/tours/new")({
  head: () => ({
    meta: [
      { title: "Create tour | KareVoyage Operations" },
      { name: "description", content: "Build a new tour product, section by section." },
    ],
  }),
  component: NewTourPage,
});

const toSlug = (name: string) =>
  name
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");

const linesToArray = (text: string) =>
  text
    .split("\n")
    .map((s) => s.trim())
    .filter(Boolean);

const todayIso = () => format(new Date(), "yyyy-MM-dd");

/** `allowPastStartDate`: when editing a tour whose Start Date has already passed, its unchanged
 * original value must still pass validation — only a newly *picked* past date should be rejected.
 * `undefined` (creating a new tour) always requires a future-or-today date.
 * `mode`: the "at least one departure date when Multiple Departures is on" rule below only makes
 * sense at create time — in edit mode `departureDates` is deliberately always `[]` here (they're
 * managed live via DepartureDatesManager instead), so applying that rule to an edit would reject
 * every save on a tour that already has multiple departures, for a field this form never touches. */
const makeTourFormSchema = (mode: "create" | "edit", allowPastStartDate?: string) => {
  /** Required for new tours; left optional in edit mode so tours that predate a field becoming
   * mandatory (null in the database) can still be saved without first being forced to backfill
   * every such field. */
  const requiredOnCreate = () =>
    mode === "create" ? z.string().trim().min(1, "Required") : z.string().trim();
  /** Same create-only-required treatment, for a multi-select array rather than free text. */
  const requiredDocumentsField =
    mode === "create"
      ? z.array(z.string()).min(1, "Select at least one document type")
      : z.array(z.string());

  const shape = z.object({
    title: z.string().trim().min(3, "Enter a tour name"),
    slug: z
      .string()
      .trim()
      .regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "Lowercase, hyphen-separated"),
    shortDescription: z.string().trim().min(1, "Required").max(160, "Max 160 characters"),
    description: z.string().trim().min(10, "Add at least 10 characters"),
    tourType: z.enum(["group_tour", "fit", "private_tour"]),
    category: z.string(),
    badgeLabel: z.string(),
    displayOrder: z.string(),
    country: z.string().trim().min(1, "Required"),
    stateRegion: z.string(),
    destination: z.string().trim().min(1, "Required"),
    /** Set by picking a real Destination — `destination` (name) is kept in sync automatically,
     * not typed by hand, both here and server-side. Required for new tours; optional in edit
     * mode so tours created before the Destinations module existed (destinationId: null) can
     * still be saved without being forced to backfill a destination first. */
    destinationId: requiredOnCreate(),
    primaryImageUrl: z.string(),
    galleryImageUrls: z.array(z.string()),
    durationNights: z.string().trim().min(1, "Required"),
    durationDays: z.string().trim().min(1, "Required"),
    groupSizeMin: z.string().trim().min(1, "Required"),
    groupSizeMax: z
      .string()
      .trim()
      .min(1, "Required")
      .refine((v) => Number(v) <= 20, "Maximum group size cannot exceed 20"),
    bestTimeToVisit: z.string(),
    ageGroup: requiredOnCreate(),
    tourCode: z.string(),
    departureCity: z.string().trim().min(1, "Required"),
    operatedBy: z.string(),
    currency: z.string().trim().min(1, "Required"),
    price: z.string().trim().min(1, "Required"),
    childPrice: z.string(),
    singleSupplement: z.string(),
    discountType: z.string(),
    discountValue: z.string(),
    startDate: z
      .string()
      .trim()
      .min(1, "Required")
      .refine(
        (v) => v >= todayIso() || v === allowPastStartDate,
        "Start date cannot be in the past",
      ),
    endDate: z.string().trim().min(1, "Required"),
    hasMultipleDepartures: z.boolean(),
    departureDates: z.array(z.string()),
    /** Built inline here — submitted together with the tour, inserted in the same transaction.
     * Optional: a tour can be published with none and have days added later from its detail page. */
    itineraryDays: z.array(
      z.object({
        dayNumber: z.number().int().positive(),
        title: z.string().trim().min(1, "Required"),
        activitiesShort: z.string(),
      }),
    ),
    inclusionsText: z.string().trim().min(1, "Add at least one inclusion"),
    exclusionsText: z.string().trim().min(1, "Add at least one exclusion"),
    highlightsText: z.string().trim().min(1, "Add at least one highlight"),
    accommodationType: requiredOnCreate(),
    mealPlan: requiredOnCreate(),
    transportType: requiredOnCreate(),
    physicalRating: z.string(),
    recommendedFor: z.string(),
    tags: z.array(z.string()),
    allowBooking: z.boolean(),
    requiredDocuments: requiredDocumentsField,
    featured: z.boolean(),
    isActive: z.boolean(),
    publishingState: z.enum(["draft", "review", "published"]),
  });

  if (mode !== "create") return shape;

  return shape.refine((v) => !v.hasMultipleDepartures || v.departureDates.length > 0, {
    message: "Add at least one departure date",
    path: ["departureDates"],
  });
};

type TourFormValues = z.infer<ReturnType<typeof makeTourFormSchema>>;

// Shared generic field primitives (`@/components/app/FormFields`), specialized to this form's
// values once via a TS instantiation expression — every existing `<TextField name="..." />`-style
// usage below is unchanged.
const TextField = GenericTextField<TourFormValues>;
const TextAreaField = GenericTextAreaField<TourFormValues>;
const DropdownField = GenericDropdownField<TourFormValues>;
const MultiDropdownField = GenericMultiDropdownField<TourFormValues>;
const CheckboxField = GenericCheckboxField<TourFormValues>;

const DEFAULT_VALUES: TourFormValues = {
  title: "",
  slug: "",
  shortDescription: "",
  description: "",
  tourType: "group_tour",
  category: "",
  badgeLabel: "",
  displayOrder: "0",
  country: "",
  stateRegion: "",
  destination: "",
  destinationId: "",
  primaryImageUrl: "",
  galleryImageUrls: [],
  durationNights: "",
  durationDays: "",
  groupSizeMin: "1",
  groupSizeMax: "20",
  bestTimeToVisit: "",
  ageGroup: "",
  tourCode: "",
  departureCity: "",
  operatedBy: "",
  currency: "INR",
  price: "",
  childPrice: "",
  singleSupplement: "",
  discountType: "",
  discountValue: "",
  startDate: "",
  endDate: "",
  hasMultipleDepartures: false,
  departureDates: [],
  itineraryDays: [],
  inclusionsText: "",
  exclusionsText: "",
  highlightsText: "",
  accommodationType: "",
  mealPlan: "",
  transportType: "",
  physicalRating: "",
  recommendedFor: "",
  tags: [],
  allowBooking: true,
  requiredDocuments: [],
  featured: false,
  isActive: true,
  publishingState: "draft",
};

/** Maps the read shape (nulls, some numeric fields already stringified by Postgres) back into
 * the form's all-strings shape for pre-filling Edit. Departure dates and itinerary days are left
 * empty here on purpose — in edit mode they're managed via their own dedicated tabs on the tour's
 * detail page (Availability/Itinerary), not resubmitted through this form. */
function tourToFormValues(tour: Tour): TourFormValues {
  const str = (v: string | null | undefined) => v ?? "";
  const num = (v: number | null | undefined) => (v === null || v === undefined ? "" : String(v));
  return {
    title: tour.title,
    slug: tour.slug,
    shortDescription: tour.shortDescription,
    description: tour.description,
    tourType: tour.tourType,
    category: str(tour.category),
    badgeLabel: str(tour.badgeLabel),
    displayOrder: String(tour.displayOrder),
    country: tour.country,
    stateRegion: str(tour.stateRegion),
    destination: tour.destination,
    destinationId: str(tour.destinationId),
    primaryImageUrl: str(tour.primaryImageUrl),
    galleryImageUrls: tour.galleryImageUrls,
    durationNights: String(tour.durationNights),
    durationDays: String(tour.durationDays),
    groupSizeMin: String(tour.groupSizeMin),
    groupSizeMax: String(tour.groupSizeMax),
    bestTimeToVisit: str(tour.bestTimeToVisit),
    ageGroup: str(tour.ageGroup),
    tourCode: str(tour.tourCode),
    departureCity: tour.departureCity,
    operatedBy: str(tour.operatedBy),
    currency: tour.currency,
    price: tour.price,
    childPrice: str(tour.childPrice),
    singleSupplement: str(tour.singleSupplement),
    discountType: str(tour.discountType),
    discountValue: str(tour.discountValue),
    startDate: tour.startDate,
    endDate: tour.endDate,
    hasMultipleDepartures: tour.hasMultipleDepartures,
    departureDates: [],
    itineraryDays: [],
    inclusionsText: tour.inclusions.join("\n"),
    exclusionsText: tour.exclusions.join("\n"),
    highlightsText: tour.highlights.join("\n"),
    accommodationType: str(tour.accommodationType),
    mealPlan: str(tour.mealPlan),
    transportType: str(tour.transportType),
    physicalRating: num(tour.physicalRating),
    recommendedFor: str(tour.recommendedFor),
    tags: tour.tags,
    allowBooking: tour.allowBooking,
    requiredDocuments: tour.requiredDocuments,
    featured: tour.featured,
    isActive: tour.isActive,
    publishingState: tour.publishingState,
  };
}

/** Lets deeply-nested tab components (Itinerary, Departure Dates) adapt without prop-drilling
 * `mode`/`tour` through every intermediate tab wrapper. Edit mode carries the full `Tour` — its
 * Itinerary/Dates tabs render the same live, API-backed managers the tour's own detail page uses
 * (not local form state), which need real tour data (title/duration/startDate), not just an id. */
const TourFormModeContext = createContext<{ mode: "create" } | { mode: "edit"; tour: Tour }>({
  mode: "create",
});

const TABS = [
  { value: "basic", label: "Basic Info" },
  { value: "destination", label: "Destination" },
  { value: "details", label: "Tour Details" },
  { value: "pricing", label: "Pricing" },
  { value: "dates", label: "Dates" },
  { value: "content", label: "Inclusions & Highlights" },
  { value: "itinerary", label: "Itinerary" },
  { value: "additional", label: "Additional Info" },
  { value: "publishing", label: "Publishing" },
] as const;

type TabValue = (typeof TABS)[number]["value"];

/** Which fields live on each tab — drives per-tab validation and jump-to-error-tab on submit. */
const TAB_FIELDS: Record<TabValue, FieldPath<TourFormValues>[]> = {
  basic: [
    "title",
    "slug",
    "shortDescription",
    "description",
    "tourType",
    "category",
    "badgeLabel",
    "displayOrder",
  ],
  destination: [
    "country",
    "stateRegion",
    "destination",
    "destinationId",
    "primaryImageUrl",
    "galleryImageUrls",
  ],
  details: [
    "durationNights",
    "durationDays",
    "groupSizeMin",
    "groupSizeMax",
    "bestTimeToVisit",
    "ageGroup",
    "tourCode",
    "departureCity",
    "operatedBy",
  ],
  pricing: ["currency", "price", "childPrice", "singleSupplement", "discountType", "discountValue"],
  dates: ["startDate", "endDate", "hasMultipleDepartures", "departureDates"],
  content: ["inclusionsText", "exclusionsText", "highlightsText"],
  itinerary: [],
  additional: [
    "accommodationType",
    "mealPlan",
    "transportType",
    "physicalRating",
    "recommendedFor",
    "tags",
    "allowBooking",
    "requiredDocuments",
  ],
  publishing: ["featured", "isActive", "publishingState"],
};

// ── Shared field primitives ────────────────────────────────────────────────

function TagsField() {
  const { control } = useFormContext<TourFormValues>();
  const { field } = useController({ control, name: "tags" });
  const [draft, setDraft] = useState("");
  const tags = field.value as string[];

  const addTag = () => {
    const value = draft.trim();
    if (!value || tags.includes(value)) {
      setDraft("");
      return;
    }
    field.onChange([...tags, value]);
    setDraft("");
  };

  return (
    <div className="space-y-2">
      <Label className="kv-eyebrow">Tags / Keywords</Label>
      <div className="flex flex-wrap items-center gap-1.5 rounded-md border border-input px-2 py-1.5">
        {tags.map((tag) => (
          <span
            key={tag}
            className="inline-flex items-center gap-1 rounded-md bg-primary-soft px-2 py-0.5 text-xs text-accent-foreground"
          >
            {tag}
            <button
              type="button"
              onClick={() => field.onChange(tags.filter((t) => t !== tag))}
              aria-label={`Remove ${tag}`}
            >
              <X className="h-3 w-3" />
            </button>
          </span>
        ))}
        <input
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === "Enter" || e.key === ",") {
              e.preventDefault();
              addTag();
            }
          }}
          onBlur={addTag}
          placeholder="Enter tags and press Enter"
          className="min-w-[140px] flex-1 border-0 bg-transparent py-1 text-sm outline-none placeholder:text-muted-foreground"
        />
      </div>
    </div>
  );
}

// ── Tab sections ────────────────────────────────────────────────────────────

function BasicInfoTab() {
  const { control, setValue, getValues } = useFormContext<TourFormValues>();
  return (
    <div className="grid gap-6 lg:grid-cols-2">
      <FormField
        control={control}
        name="title"
        render={({ field }) => (
          <FormItem>
            <FormLabel className="kv-eyebrow">
              Tour Name
              <RequiredMark />
            </FormLabel>
            <FormControl>
              <Input
                placeholder="Enter tour name"
                {...field}
                onChange={(e) => {
                  field.onChange(e);
                  if (!getValues("slug") || getValues("slug") === toSlug(field.value)) {
                    setValue("slug", toSlug(e.target.value));
                  }
                }}
              />
            </FormControl>
            <FormMessage />
          </FormItem>
        )}
      />
      <TextField
        name="slug"
        label="Slug / URL"
        placeholder="Enter slug (auto-generated)"
        hint="Used in website URL"
        required
      />
      <div className="lg:col-span-2">
        <TextAreaField
          name="shortDescription"
          label="Short Description"
          placeholder="Enter a short description (max 160 characters)"
          required
        />
      </div>
      <div className="lg:col-span-2">
        <TextAreaField
          name="description"
          label="Detailed Description"
          placeholder="Enter detailed description of the tour"
          required
        />
      </div>

      <FormField
        control={control}
        name="tourType"
        render={({ field }) => (
          <FormItem>
            <FormLabel className="kv-eyebrow">
              Tour Type
              <RequiredMark />
            </FormLabel>
            <RadioGroup
              value={field.value}
              onValueChange={field.onChange}
              className="flex flex-wrap gap-4 pt-1"
            >
              {[
                { value: "group_tour", label: "Group Tour" },
                { value: "fit", label: "FIT (Custom)" },
                { value: "private_tour", label: "Private Tour" },
              ].map((opt) => (
                <label key={opt.value} className="flex cursor-pointer items-center gap-2 text-sm">
                  <RadioGroupItem value={opt.value} />
                  {opt.label}
                </label>
              ))}
            </RadioGroup>
          </FormItem>
        )}
      />

      <DropdownField name="category" label="Tour Category" groupKey="tour_category" />
      <TextField
        name="badgeLabel"
        label="Badge / Label"
        placeholder="e.g. Bestseller, New, Popular"
        hint="Will be shown on website (Optional)"
      />
      <TextField
        name="displayOrder"
        label="Display Order"
        type="number"
        hint="Lower number shows first"
      />
    </div>
  );
}

/** Visual upload dropzone matching the reference design — inert until ImageKit is wired in Pass 2. */
function PrimaryImageField() {
  const { control } = useFormContext<TourFormValues>();
  const { field } = useController({ control, name: "primaryImageUrl" });
  const uploadImage = useUploadImage();
  const inputRef = useRef<HTMLInputElement>(null);
  const url = field.value as string;

  const handleFile = async (file: File | undefined) => {
    if (!file) return;
    const webpFile = await toWebP(file).catch(() => file);
    uploadImage.mutate(webpFile, {
      onSuccess: (result) => field.onChange(result.url),
      onError: (err) =>
        toast.error(err instanceof ApiClientError ? err.message : "Couldn't upload the image."),
    });
  };

  return (
    <div className="space-y-1.5">
      <Label className="kv-eyebrow">
        Primary Image
        <RequiredMark />
      </Label>
      <input
        ref={inputRef}
        type="file"
        accept="image/*"
        className="hidden"
        onChange={(e) => {
          void handleFile(e.target.files?.[0]);
          e.target.value = "";
        }}
      />
      {url ? (
        <div className="group relative overflow-hidden rounded-lg border border-border">
          <img src={url} alt="Primary" className="h-40 w-full object-cover" />
          <button
            type="button"
            onClick={() => field.onChange("")}
            className="absolute right-2 top-2 rounded-full bg-background/90 p-1.5 text-foreground shadow-sm transition-colors hover:bg-background"
            aria-label="Remove primary image"
          >
            <X className="h-3.5 w-3.5" />
          </button>
        </div>
      ) : (
        <button
          type="button"
          onClick={() => inputRef.current?.click()}
          disabled={uploadImage.isPending}
          className="flex w-full flex-col items-center justify-center gap-1.5 rounded-lg border border-dashed border-border-strong bg-surface-muted/40 px-4 py-8 text-center transition-colors hover:border-primary/40 hover:bg-primary-soft/40 disabled:opacity-60"
        >
          {uploadImage.isPending ? (
            <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
          ) : (
            <ImagePlus className="h-6 w-6 text-muted-foreground" />
          )}
          <span className="text-sm font-medium text-foreground">
            {uploadImage.isPending ? "Uploading…" : "Upload image"}
          </span>
          <span className="text-xs text-muted-foreground">Recommended size: 1200x800px</span>
        </button>
      )}
    </div>
  );
}

function GalleryImagesField() {
  const { control, setValue, getValues } = useFormContext<TourFormValues>();
  const { field } = useController({ control, name: "galleryImageUrls" });
  const uploadImage = useUploadImage();
  const inputRef = useRef<HTMLInputElement>(null);
  const urls = field.value as string[];

  const handleFiles = (files: FileList | null) => {
    if (!files) return;
    Array.from(files).forEach((file) => {
      void toWebP(file)
        .catch(() => file)
        .then((webpFile) => {
          uploadImage.mutate(webpFile, {
            // Read the live value at resolve-time, not a closure snapshot — several uploads can
            // resolve in any order, and a stale array would silently drop earlier additions.
            onSuccess: (result) =>
              setValue("galleryImageUrls", [...getValues("galleryImageUrls"), result.url]),
            onError: (err) =>
              toast.error(
                err instanceof ApiClientError ? err.message : "Couldn't upload the image.",
              ),
          });
        });
    });
  };

  return (
    <div className="space-y-1.5">
      <Label className="kv-eyebrow">Gallery Images</Label>
      <input
        ref={inputRef}
        type="file"
        accept="image/*"
        multiple
        className="hidden"
        onChange={(e) => {
          handleFiles(e.target.files);
          e.target.value = "";
        }}
      />
      {urls.length > 0 ? (
        <div className="grid grid-cols-3 gap-2">
          {urls.map((imgUrl) => (
            <div
              key={imgUrl}
              className="group relative overflow-hidden rounded-lg border border-border"
            >
              <img src={imgUrl} alt="Gallery" className="h-20 w-full object-cover" />
              <button
                type="button"
                onClick={() => field.onChange(urls.filter((u) => u !== imgUrl))}
                className="absolute right-1 top-1 rounded-full bg-background/90 p-1 text-foreground opacity-0 shadow-sm transition-opacity hover:bg-background group-hover:opacity-100"
                aria-label="Remove image"
              >
                <X className="h-3 w-3" />
              </button>
            </div>
          ))}
        </div>
      ) : null}
      <button
        type="button"
        onClick={() => inputRef.current?.click()}
        disabled={uploadImage.isPending}
        className="flex w-full flex-col items-center justify-center gap-1.5 rounded-lg border border-dashed border-border-strong bg-surface-muted/40 px-4 py-6 text-center transition-colors hover:border-primary/40 hover:bg-primary-soft/40 disabled:opacity-60"
      >
        {uploadImage.isPending ? (
          <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
        ) : (
          <ImagePlus className="h-5 w-5 text-muted-foreground" />
        )}
        <span className="text-sm font-medium text-foreground">
          {uploadImage.isPending ? "Uploading…" : "Upload images"}
        </span>
        <span className="text-xs text-muted-foreground">You can upload multiple images</span>
      </button>
    </div>
  );
}

/** Sources from the real Destinations module (not a Master Types group) — picking one sets
 * `destinationId`; `destination` (the display name) is kept in sync for immediate UI feedback,
 * and the server re-derives it from `destinationId` again regardless on save. */
function DestinationSelectField() {
  const { control, setValue } = useFormContext<TourFormValues>();
  const { data, isLoading } = useDestinations({ limit: 100 });
  const destinationsList = data?.rows ?? [];
  const ctx = useContext(TourFormModeContext);

  return (
    <FormField
      control={control}
      name="destinationId"
      render={({ field }) => (
        <FormItem>
          <FormLabel className="kv-eyebrow">
            City / Destination
            {ctx?.mode === "create" ? <RequiredMark /> : null}
          </FormLabel>
          <Select
            value={field.value}
            onValueChange={(value) => {
              field.onChange(value);
              const selected = destinationsList.find((d) => d.id === value);
              if (selected) setValue("destination", selected.name);
            }}
          >
            <FormControl>
              <SelectTrigger>
                <SelectValue placeholder={isLoading ? "Loading…" : "Select a destination"} />
              </SelectTrigger>
            </FormControl>
            <SelectContent>
              {destinationsList.length === 0 ? (
                <p className="px-3 py-2 text-xs text-muted-foreground">
                  No destinations yet — add one under Destinations.
                </p>
              ) : (
                destinationsList.map((d) => (
                  <SelectItem key={d.id} value={d.id}>
                    {d.name}, {d.country}
                  </SelectItem>
                ))
              )}
            </SelectContent>
          </Select>
          <FormMessage />
        </FormItem>
      )}
    />
  );
}

function DestinationTab() {
  return (
    <div className="grid gap-6 lg:grid-cols-2">
      <DropdownField name="country" label="Country" groupKey="country" required />
      <TextField name="stateRegion" label="State / Region" placeholder="Enter state / region" />
      <div className="lg:col-span-2">
        <DestinationSelectField />
      </div>
      <PrimaryImageField />
      <GalleryImagesField />
    </div>
  );
}

/** Read-only — the operating company name is system-controlled (Settings → Company), never
 * hand-typed on a tour. The backend re-derives and overwrites this on save regardless. */
function OperatedByField() {
  const { setValue } = useFormContext<TourFormValues>();
  const { data } = useCompanySettings();
  const companyName = data?.companyName ?? "";

  useEffect(() => {
    if (companyName) setValue("operatedBy", companyName);
  }, [companyName, setValue]);

  return (
    <div className="space-y-1.5">
      <Label className="kv-eyebrow">Tour Operated By</Label>
      <Input value={companyName || "Loading…"} disabled readOnly />
      <p className="text-xs text-muted-foreground">
        Set from Settings → Company profile, not editable per tour.
      </p>
    </div>
  );
}

function TourDetailsTab() {
  return (
    <div className="grid gap-6 lg:grid-cols-2">
      <TextField
        name="durationNights"
        label="Duration (Nights)"
        type="number"
        placeholder="Enter nights"
        required
      />
      <TextField
        name="durationDays"
        label="Duration (Days)"
        type="number"
        placeholder="Enter days"
        required
      />
      <TextField
        name="groupSizeMin"
        label="Minimum Group Size"
        type="number"
        placeholder="Enter minimum pax"
        required
      />
      <TextField
        name="groupSizeMax"
        label="Maximum Group Size"
        type="number"
        placeholder="Enter maximum pax"
        max={20}
        // hint="Maximum 20 travellers per departure"
        required
      />
      <TextField name="bestTimeToVisit" label="Best Time To Visit" placeholder="e.g. Mar – May" />
      <DropdownField name="ageGroup" label="Age Group" groupKey="age_group" required />
      <TextField name="tourCode" label="Tour Code" placeholder="Enter tour code (Optional)" />
      <TextField
        name="departureCity"
        label="Departure City"
        placeholder="Enter departure city"
        required
      />
      <OperatedByField />
    </div>
  );
}

function PricingTab() {
  const { control } = useFormContext<TourFormValues>();
  const discountType = useWatch({ control, name: "discountType" });
  const discountHint =
    discountType === "percentage"
      ? "Interpreted as a % off the base price"
      : discountType === "fixed"
        ? "Interpreted as a flat currency amount off the base price"
        : "Choose a discount type above to see how this value is applied";

  return (
    <div className="grid gap-6 lg:grid-cols-2">
      <DropdownField name="currency" label="Currency" groupKey="currency" required />
      <TextField
        name="price"
        label="Base Price (Per Pax)"
        type="number"
        placeholder="Enter amount"
        required
      />
      <TextField
        name="childPrice"
        label="Child Price (Per Pax)"
        type="number"
        placeholder="Enter amount"
        hint="Age range will be asked in pricing rules"
      />
      <TextField
        name="singleSupplement"
        label="Single Supplement"
        type="number"
        placeholder="Enter amount"
        hint="If applicable"
      />
      <DropdownField name="discountType" label="Discount Type" groupKey="discount_type" />
      <TextField
        name="discountValue"
        label="Discount Value"
        type="number"
        placeholder="Enter value"
        hint={discountHint}
      />
    </div>
  );
}

/**
 * A departure's implied return date — informational only, same math as the auto-calculated
 * Tour End Date.
 *
 * `days - 1` because "days" counts calendar days INCLUDING the departure day: a 6-day tour
 * leaving on the 10th runs 10-11-12-13-14-15 and comes home on the 15th, not the 16th. For a
 * conventional 5N/6D pair this equals `start + nights`, which is what this used to compute —
 * but days is what the business quotes and what the operator types, and it stays correct even
 * when the nights/days pair isn't the usual N/N+1 (a day trip is 0N/1D, and returns the same day).
 */
function returnDateFor(departureIso: string, durationDays: number) {
  const parsed = parseISO(departureIso);
  if (!isValid(parsed) || durationDays < 1) return undefined;
  return addDays(parsed, durationDays - 1);
}

function DepartureDatesField() {
  const { control } = useFormContext<TourFormValues>();
  const durationDays = Number(useWatch({ control, name: "durationDays" }) || 0);
  const startDate = useWatch({ control, name: "startDate" });
  // Departures are the same tour running again on other calendar dates across a season — they
  // shouldn't predate the tour's own listed Start Date, but there's deliberately no upper bound
  // (Tour End Date is just Start + Duration, i.e. the *first* departure's own return leg).
  const minDepartureDate =
    startDate && isValid(parseISO(startDate)) ? parseISO(startDate) : new Date();
  const { field } = useController({ control, name: "departureDates" });
  const dates = [...(field.value as string[])].sort();
  const [addOpen, setAddOpen] = useState(false);
  const [editingDate, setEditingDate] = useState<string | null>(null);

  const addDate = (iso: string) => {
    if (dates.includes(iso)) return;
    field.onChange([...dates, iso].sort());
  };
  const replaceDate = (oldIso: string, newIso: string) => {
    if (oldIso === newIso) return;
    field.onChange([...dates.filter((d) => d !== oldIso), newIso].sort());
  };
  const removeDate = (iso: string) => field.onChange(dates.filter((d) => d !== iso));

  return (
    <div className="space-y-3 lg:col-span-2">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <div>
          <Label className="kv-eyebrow">
            Available Departures
            <RequiredMark />
          </Label>
          <p className="text-xs text-muted-foreground">
            Add additional departure dates for this tour.
          </p>
        </div>
        <Popover open={addOpen} onOpenChange={setAddOpen}>
          <PopoverTrigger asChild>
            <Button type="button" variant="outline" size="sm" className="gap-1.5">
              <Plus className="h-3.5 w-3.5" /> Add Departure
            </Button>
          </PopoverTrigger>
          <PopoverContent className="w-auto p-0" align="end">
            <Calendar
              mode="single"
              disabled={{ before: minDepartureDate }}
              onSelect={(date) => {
                if (!date) return;
                addDate(format(date, "yyyy-MM-dd"));
                setAddOpen(false);
              }}
              autoFocus
            />
          </PopoverContent>
        </Popover>
      </div>

      {dates.length === 0 ? (
        <div className="rounded-lg border border-dashed border-border-strong px-4 py-8 text-center">
          <p className="text-sm font-medium text-foreground">No departure dates yet</p>
          <p className="mt-1 text-xs text-muted-foreground">
            Add the dates this tour departs on — each one gets its own card here.
          </p>
        </div>
      ) : (
        <div className="space-y-2">
          {dates.map((d) => {
            const ret = returnDateFor(d, durationDays);
            return (
              <div
                key={d}
                className="flex items-center justify-between gap-3 rounded-lg border border-border px-3.5 py-2.5"
              >
                <div>
                  <p className="text-sm font-medium text-foreground">
                    {format(parseISO(d), "dd-MM-yyyy")}
                  </p>
                  {ret ? (
                    <p className="text-xs text-muted-foreground">
                      Return: {format(ret, "dd-MM-yyyy")}
                    </p>
                  ) : null}
                </div>
                <div className="flex items-center gap-1">
                  <Popover
                    open={editingDate === d}
                    onOpenChange={(open) => setEditingDate(open ? d : null)}
                  >
                    <PopoverTrigger asChild>
                      <Button
                        type="button"
                        variant="ghost"
                        size="icon"
                        className="h-7 w-7"
                        aria-label={`Edit departure ${d}`}
                      >
                        <Pencil className="h-3.5 w-3.5" />
                      </Button>
                    </PopoverTrigger>
                    <PopoverContent className="w-auto p-0" align="end">
                      <Calendar
                        mode="single"
                        selected={parseISO(d)}
                        disabled={{ before: minDepartureDate }}
                        onSelect={(date) => {
                          if (!date) return;
                          replaceDate(d, format(date, "yyyy-MM-dd"));
                          setEditingDate(null);
                        }}
                        autoFocus
                      />
                    </PopoverContent>
                  </Popover>
                  <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    className="h-7 w-7 text-destructive hover:text-destructive"
                    aria-label={`Remove departure ${d}`}
                    onClick={() => removeDate(d)}
                  >
                    <Trash2 className="h-3.5 w-3.5" />
                  </Button>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function DatesTab() {
  const { control, setValue } = useFormContext<TourFormValues>();
  const { field: multiField } = useController({ control, name: "hasMultipleDepartures" });
  const startDate = useWatch({ control, name: "startDate" });
  const durationDays = useWatch({ control, name: "durationDays" });
  // Flips true the moment the user picks an End Date by hand — once that happens, the auto-calc
  // below stops overwriting it. Without this, the effect's own re-run on the next Start/Duration
  // change would immediately "correct" a manual pick back to the computed value.
  const endDateTouchedRef = useRef(false);

  // End Date auto-fills from Start Date + Duration (Nights) until the user overrides it by hand.
  useEffect(() => {
    if (endDateTouchedRef.current) return;
    const parsedStart = parseISO(startDate);
    if (!startDate || !isValid(parsedStart)) return;
    const days = Number(durationDays) || 0;
    // Nothing sensible to derive from a blank or zero duration, and days - 1 would run backwards.
    if (days < 1) return;
    setValue("endDate", format(addDays(parsedStart, days - 1), "yyyy-MM-dd"));
  }, [startDate, durationDays, setValue]);

  return (
    <div className="grid gap-6 lg:grid-cols-2">
      <FormField
        control={control}
        name="startDate"
        render={({ field }) => (
          <FormItem>
            <FormLabel className="kv-eyebrow">
              Tour Start Date
              <RequiredMark />
            </FormLabel>
            <FormControl>
              <DateField
                value={field.value}
                onChange={field.onChange}
                minDate={new Date()}
                placeholder="Select start date"
              />
            </FormControl>
            <FormMessage />
          </FormItem>
        )}
      />
      <FormField
        control={control}
        name="endDate"
        render={({ field }) => (
          <FormItem>
            <FormLabel className="kv-eyebrow">
              Tour End Date
              <RequiredMark />
            </FormLabel>
            <FormControl>
              <DateField
                value={field.value}
                onChange={(iso) => {
                  endDateTouchedRef.current = true;
                  field.onChange(iso);
                }}
                placeholder="Auto-filled from start date + duration"
                {...(startDate ? { minDate: parseISO(startDate) } : {})}
              />
            </FormControl>
            <p className="text-xs text-muted-foreground">
              Auto-fills from start date + duration — pick a different date to override.
            </p>
            <FormMessage />
          </FormItem>
        )}
      />
      <div className="lg:col-span-2">
        <CheckboxField
          name="hasMultipleDepartures"
          label="Multiple Departures"
          hint="Enable if tour has multiple departure dates"
        />
      </div>
      {multiField.value ? <DepartureDatesSection /> : null}
    </div>
  );
}

/** Create mode collects departure dates as local form state, submitted alongside the tour. Edit
 * mode instead renders the same live, API-backed manager the tour's detail page uses — writes
 * land immediately via their own endpoints, independent of this form's own Save button. */
function DepartureDatesSection() {
  const ctx = useContext(TourFormModeContext);
  if (ctx.mode === "create") return <DepartureDatesField />;
  return (
    <div className="lg:col-span-2 space-y-2">
      <p className="flex items-center gap-1.5 text-xs text-muted-foreground">
        <Info className="h-3.5 w-3.5 shrink-0" />
        Saved immediately — separate from the Save changes button below.
      </p>
      <DepartureDatesManager tour={ctx.tour} />
    </div>
  );
}

function ContentTab() {
  return (
    <div className="grid gap-6 lg:grid-cols-2">
      <TextAreaField
        name="inclusionsText"
        label="Inclusions"
        placeholder="Enter inclusions (one per line)"
        hint="Example: Hotel, Meals, Sightseeing, Transfers"
        required
      />
      <TextAreaField
        name="exclusionsText"
        label="Exclusions"
        placeholder="Enter exclusions (one per line)"
        hint="Example: Flights, Visa, Insurance, Personal Expenses"
        required
      />
      <div className="lg:col-span-2">
        <TextAreaField
          name="highlightsText"
          label="Tour Highlights"
          placeholder="Enter key highlights (one per line)"
          hint="These will be shown on website"
          required
        />
      </div>
    </div>
  );
}

type ItineraryDayValue = TourFormValues["itineraryDays"][number];

/** Itinerary days are only ever *collected* here for the initial create request — once a tour
 * exists, they're managed via the Itinerary tab on its own detail page (its own dedicated
 * endpoints), not resubmitted through this form. */
/** Create mode collects itinerary days as local form state, submitted alongside the tour. Edit
 * mode instead renders the same live, API-backed manager the tour's detail page uses — writes
 * land immediately via their own endpoints, independent of this form's own Save button. */
function ItineraryTab() {
  const ctx = useContext(TourFormModeContext);
  if (ctx.mode === "create") return <ItineraryBuilderField />;
  return (
    <div className="space-y-2">
      <p className="flex items-center gap-1.5 text-xs text-muted-foreground">
        <Info className="h-3.5 w-3.5 shrink-0" />
        Saved immediately — separate from the Save changes button below.
      </p>
      <ItineraryManager tour={ctx.tour} />
    </div>
  );
}

function ItineraryBuilderField() {
  const { control } = useFormContext<TourFormValues>();
  const { field } = useController({ control, name: "itineraryDays" });
  const days = [...(field.value as ItineraryDayValue[])].sort((a, b) => a.dayNumber - b.dayNumber);
  const nextDayNumber = days.length ? Math.max(...days.map((d) => d.dayNumber)) + 1 : 1;

  const [formOpen, setFormOpen] = useState(false);
  const [editingIndex, setEditingIndex] = useState<number | null>(null);
  const [draftDay, setDraftDay] = useState(1);
  const [draftTitle, setDraftTitle] = useState("");
  const [draftActivities, setDraftActivities] = useState("");

  const openAdd = () => {
    setEditingIndex(null);
    setDraftDay(nextDayNumber);
    setDraftTitle("");
    setDraftActivities("");
    setFormOpen(true);
  };
  const openEdit = (day: ItineraryDayValue, index: number) => {
    setEditingIndex(index);
    setDraftDay(day.dayNumber);
    setDraftTitle(day.title);
    setDraftActivities(day.activitiesShort);
    setFormOpen(true);
  };
  const save = () => {
    if (!draftTitle.trim()) return;
    const isDuplicate = days.some((d, i) => d.dayNumber === draftDay && i !== editingIndex);
    if (isDuplicate) {
      toast.error(`Day ${draftDay} is already used — pick a different day number.`);
      return;
    }
    const entry: ItineraryDayValue = {
      dayNumber: draftDay,
      title: draftTitle.trim(),
      activitiesShort: draftActivities.trim(),
    };
    if (editingIndex !== null) {
      field.onChange(days.map((d, i) => (i === editingIndex ? entry : d)));
    } else {
      field.onChange([...days, entry]);
    }
    setFormOpen(false);
  };
  const remove = (index: number) => field.onChange(days.filter((_, i) => i !== index));

  return (
    <div className="space-y-4">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <div>
          <p className="text-sm font-semibold text-foreground">Day-wise Itinerary</p>
          <p className="text-xs text-muted-foreground">
            Add one entry per day — optional here, and can also be added later from the tour's
            detail page.
          </p>
        </div>
        <Button type="button" size="sm" className="gap-1.5" onClick={openAdd}>
          <Plus className="h-3.5 w-3.5" /> Add Day
        </Button>
      </div>

      {formOpen ? (
        <div className="space-y-3 rounded-lg border border-border bg-surface-muted/40 p-4">
          <div className="flex gap-3">
            <div className="w-24 shrink-0 space-y-1.5">
              <Label className="kv-eyebrow">Day #</Label>
              <Input
                type="number"
                min={1}
                value={draftDay}
                onChange={(e) => setDraftDay(Math.max(1, Number(e.target.value) || 1))}
              />
            </div>
            <div className="flex-1 space-y-1.5">
              <Label className="kv-eyebrow">
                Title
                <RequiredMark />
              </Label>
              <Input
                autoFocus
                value={draftTitle}
                onChange={(e) => setDraftTitle(e.target.value)}
                placeholder="e.g. Arrival in Zurich"
              />
            </div>
          </div>
          <div className="space-y-1.5">
            <Label className="kv-eyebrow">Activities (short)</Label>
            <Textarea
              rows={3}
              maxLength={220}
              value={draftActivities}
              onChange={(e) => setDraftActivities(e.target.value)}
              placeholder="A one or two line summary of the day — not a full write-up."
            />
            <p className="text-right text-[11px] tabular-nums text-muted-foreground">
              {draftActivities.length}/220
            </p>
          </div>
          <div className="flex justify-end gap-2">
            <Button type="button" variant="outline" size="sm" onClick={() => setFormOpen(false)}>
              Cancel
            </Button>
            <Button type="button" size="sm" disabled={!draftTitle.trim()} onClick={save}>
              {editingIndex !== null ? "Save changes" : "Add day"}
            </Button>
          </div>
        </div>
      ) : null}

      {days.length === 0 ? (
        <div className="rounded-xl border border-dashed border-border bg-surface-muted/40 p-8 text-center">
          <p className="text-sm font-medium text-foreground">No itinerary yet</p>
          <p className="mt-1 text-sm text-muted-foreground">
            Add the first day above, or skip this and build it later from the tour's detail page.
          </p>
        </div>
      ) : (
        <ol className="space-y-3">
          {days.map((d, index) => (
            <li
              key={`${d.dayNumber}-${index}`}
              className="flex items-start gap-4 rounded-xl border border-border bg-surface p-4"
            >
              <div className="flex h-[46px] w-[46px] shrink-0 flex-col items-center justify-center rounded-lg bg-muted">
                <span className="text-base font-bold leading-none tabular-nums">
                  {String(d.dayNumber).padStart(2, "0")}
                </span>
                <span className="mt-0.5 text-[8.5px] font-semibold uppercase tracking-wider text-muted-foreground">
                  Day
                </span>
              </div>
              <div className="min-w-0 flex-1">
                <p className="text-sm font-medium">{d.title}</p>
                {d.activitiesShort ? (
                  <p className="mt-1 text-xs text-muted-foreground">{d.activitiesShort}</p>
                ) : null}
              </div>
              <div className="flex shrink-0 items-center gap-1">
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  className="h-7 w-7"
                  aria-label={`Edit day ${d.dayNumber}`}
                  onClick={() => openEdit(d, index)}
                >
                  <Pencil className="h-3.5 w-3.5" />
                </Button>
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  className="h-7 w-7 text-destructive hover:text-destructive"
                  aria-label={`Delete day ${d.dayNumber}`}
                  onClick={() => remove(index)}
                >
                  <Trash2 className="h-3.5 w-3.5" />
                </Button>
              </div>
            </li>
          ))}
        </ol>
      )}
    </div>
  );
}

function AdditionalInfoTab() {
  return (
    <div className="grid gap-6 lg:grid-cols-2">
      <DropdownField
        name="accommodationType"
        label="Accommodation Type"
        groupKey="accommodation_type"
        required
      />
      <DropdownField name="mealPlan" label="Meal Plan" groupKey="meal_plan" required />
      <DropdownField
        name="transportType"
        label="Transport Type"
        groupKey="transport_type"
        required
      />
      <TextField
        name="physicalRating"
        label="Physical Rating (1-5)"
        type="number"
        placeholder="Select rating"
      />
      <DropdownField name="recommendedFor" label="Recommended For" groupKey="recommended_for" />
      <TagsField />
      <CheckboxField
        name="allowBooking"
        label="Allow Booking"
        hint="Enable booking for this tour"
      />
      <div className="lg:col-span-2">
        <MultiDropdownField
          name="requiredDocuments"
          label="Document Requirements"
          groupKey="document_type"
          required
        />
      </div>
    </div>
  );
}

function PublishingTab() {
  const { control } = useFormContext<TourFormValues>();
  return (
    <div className="grid gap-6 lg:grid-cols-2">
      <FormField
        control={control}
        name="publishingState"
        render={({ field }) => (
          <FormItem>
            <FormLabel className="kv-eyebrow">
              Publishing State
              <RequiredMark />
            </FormLabel>
            <RadioGroup
              value={field.value}
              onValueChange={field.onChange}
              className="flex flex-wrap gap-4 pt-1"
            >
              {[
                { value: "draft", label: "Draft" },
                { value: "review", label: "Review" },
                { value: "published", label: "Published" },
              ].map((opt) => (
                <label key={opt.value} className="flex cursor-pointer items-center gap-2 text-sm">
                  <RadioGroupItem value={opt.value} />
                  {opt.label}
                </label>
              ))}
            </RadioGroup>
          </FormItem>
        )}
      />
      <div className="space-y-3">
        <CheckboxField
          name="featured"
          label="Featured Tour"
          hint="Show on top of website / home sections"
        />
        <CheckboxField name="isActive" label="Active" hint="Make tour active" />
      </div>
    </div>
  );
}

// ── Page ─────────────────────────────────────────────────────────────────────

function toPayload(
  values: TourFormValues,
  publishingOverride?: TourFormValues["publishingState"],
): CreateTourPayload {
  const num = (s: string) => (s.trim() === "" ? undefined : Number(s));
  return {
    title: values.title.trim(),
    slug: values.slug.trim(),
    shortDescription: values.shortDescription.trim(),
    description: values.description.trim(),
    tourType: values.tourType,
    category: values.category || undefined,
    badgeLabel: values.badgeLabel || undefined,
    displayOrder: num(values.displayOrder) ?? 0,
    country: values.country.trim(),
    stateRegion: values.stateRegion || undefined,
    destination: values.destination.trim(),
    destinationId: values.destinationId || undefined,
    primaryImageUrl: values.primaryImageUrl || undefined,
    galleryImageUrls: values.galleryImageUrls,
    durationNights: num(values.durationNights) ?? 0,
    durationDays: num(values.durationDays) ?? 0,
    groupSizeMin: num(values.groupSizeMin) ?? 1,
    groupSizeMax: num(values.groupSizeMax) ?? 15,
    bestTimeToVisit: values.bestTimeToVisit || undefined,
    ageGroup: values.ageGroup || undefined,
    tourCode: values.tourCode || undefined,
    departureCity: values.departureCity.trim(),
    operatedBy: values.operatedBy || undefined,
    currency: values.currency.trim() || "INR",
    price: num(values.price) ?? 0,
    childPrice: num(values.childPrice),
    singleSupplement: num(values.singleSupplement),
    discountType: values.discountType || undefined,
    discountValue: num(values.discountValue),
    startDate: values.startDate,
    endDate: values.endDate,
    hasMultipleDepartures: values.hasMultipleDepartures,
    departureDates: values.hasMultipleDepartures ? values.departureDates : [],
    itineraryDays: values.itineraryDays.map((d) => ({
      dayNumber: d.dayNumber,
      title: d.title,
      activitiesShort: d.activitiesShort || undefined,
    })),
    inclusions: linesToArray(values.inclusionsText),
    exclusions: linesToArray(values.exclusionsText),
    highlights: linesToArray(values.highlightsText),
    accommodationType: values.accommodationType || undefined,
    mealPlan: values.mealPlan || undefined,
    transportType: values.transportType || undefined,
    physicalRating: num(values.physicalRating),
    recommendedFor: values.recommendedFor || undefined,
    tags: values.tags,
    allowBooking: values.allowBooking,
    requiredDocuments: values.requiredDocuments,
    featured: values.featured,
    isActive: values.isActive,
    publishingState: publishingOverride ?? values.publishingState,
  };
}

const TAB_CONTENT: Record<TabValue, () => ReactElement> = {
  basic: BasicInfoTab,
  destination: DestinationTab,
  details: TourDetailsTab,
  pricing: PricingTab,
  dates: DatesTab,
  content: ContentTab,
  itinerary: ItineraryTab,
  additional: AdditionalInfoTab,
  publishing: PublishingTab,
};

/** Shared by `/tours/new` (create) and `/tours/$id/edit` (edit) — same tabs, same fields, only
 * the submit behavior, defaults, and a couple of tabs' content (Departures/Itinerary, which are
 * managed on the tour's own detail page once it exists) differ by mode. */
export function TourFormPage(props: { mode: "create" } | { mode: "edit"; tour: Tour }) {
  const { mode } = props;
  // `tour` only truly exists in edit mode — the discriminated union guarantees that at every call
  // site, but once destructured into a standalone binding TS can no longer correlate it with
  // `mode` (`!`/`?? ` below are all reached only from branches already gated on `mode === "edit"`).
  const tour = props.mode === "edit" ? props.tour : undefined;
  const navigate = useNavigate();
  const createTour = useCreateTour();
  const updateTour = useUpdateTour(tour?.id ?? "");
  const isPending = mode === "edit" ? updateTour.isPending : createTour.isPending;
  const [activeTab, setActiveTab] = useState<TabValue>("basic");
  const activeIndex = TABS.findIndex((t) => t.value === activeTab);
  const isLastTab = activeIndex === TABS.length - 1;

  const form = useForm<TourFormValues>({
    resolver: zodResolver(makeTourFormSchema(mode, tour?.startDate)),
    defaultValues: tour ? tourToFormValues(tour) : DEFAULT_VALUES,
  });

  const onSaveFailed = (err: unknown) => {
    toast.error(err instanceof ApiClientError ? err.message : "Couldn't save the tour.");
  };

  /** On validation failure, jump straight to the first tab that actually has an error. */
  const jumpToFirstErrorTab = (errors: FieldErrors<TourFormValues>) => {
    const errorFields = Object.keys(errors);
    const firstErrorTab = TABS.find((t) =>
      TAB_FIELDS[t.value].some((f) => errorFields.includes(f)),
    );
    if (firstErrorTab) {
      setActiveTab(firstErrorTab.value);
      toast.error(`Check the highlighted fields in "${firstErrorTab.label}".`);
    } else {
      toast.error("Check the highlighted fields before saving.");
    }
  };

  const saveWithState = (publishingOverride?: TourFormValues["publishingState"]) =>
    form.handleSubmit((values) => {
      if (mode === "edit") {
        const payload = toPayload(values, publishingOverride);
        const {
          departureDates: _departureDates,
          itineraryDays: _itineraryDays,
          ...updatePayload
        } = payload;
        updateTour.mutate(updatePayload, {
          onSuccess: () => {
            toast.success("Tour updated.");
            // Mark the form clean before navigating — otherwise UnsavedChangesGuard's blocker
            // (still seeing isDirty: true, since nothing else resets it after a successful
            // submit) intercepts this exact redirect too.
            flushSync(() => form.reset(values));
            navigate({ to: "/tours/$id", params: { id: tour!.id } });
          },
          onError: onSaveFailed,
        });
      } else {
        createTour.mutate(toPayload(values, publishingOverride), {
          onSuccess: (created) => {
            toast.success(`${created.title} created.`);
            flushSync(() => form.reset(values));
            // Land on the Itinerary tab — itinerary days can only be built once the tour
            // exists, so that's the natural next step right after creation.
            navigate({
              to: "/tours/$id",
              params: { id: created.id },
              search: { tab: "itinerary" },
            });
          },
          onError: onSaveFailed,
        });
      }
    }, jumpToFirstErrorTab)();

  const handleSavePublish = () => void saveWithState("published");
  const handleSaveChanges = () => void saveWithState();

  /** "Save & Continue" validates only the current tab and steps forward — it only reaches the
   * backend once you're on the last tab, so filling one section doesn't get blocked by the
   * (still-empty) fields on sections you haven't visited yet. Create mode only — edit mode saves
   * directly from whichever tab you're on. */
  const handleSaveContinue = async () => {
    const fields = TAB_FIELDS[activeTab];
    const valid = fields.length === 0 || (await form.trigger(fields));
    if (!valid) {
      toast.error("Check the highlighted fields before continuing.");
      return;
    }
    if (isLastTab) {
      void saveWithState();
    } else {
      setActiveTab(TABS[activeIndex + 1]!.value);
    }
  };

  const cancelTo = () =>
    tour ? navigate({ to: "/tours/$id", params: { id: tour.id } }) : navigate({ to: "/tours" });

  return (
    <TourFormModeContext.Provider value={tour ? { mode: "edit", tour } : { mode: "create" }}>
      <Page className="space-y-6">
        <PageHeading
          title={mode === "edit" ? "Edit Tour" : "Create Tour"}
          description={
            tour
              ? `Update ${tour.title}'s details. Departure dates and itinerary are managed from its detail page.`
              : "All fields marked with * are required."
          }
          actions={
            <Button variant="outline" onClick={cancelTo}>
              Cancel
            </Button>
          }
        />

        <Form {...form}>
          <form className="space-y-6" onSubmit={(e) => e.preventDefault()}>
            <Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as TabValue)}>
              <TabsList className="flex h-auto w-full flex-wrap justify-start gap-1 bg-transparent p-0">
                {TABS.map((t) => (
                  <TabsTrigger
                    key={t.value}
                    value={t.value}
                    className="rounded-lg px-3 py-1.5 text-sm data-[state=active]:bg-primary-soft data-[state=active]:text-accent-foreground data-[state=active]:shadow-none"
                  >
                    {t.label}
                  </TabsTrigger>
                ))}
              </TabsList>

              <div className="mt-6 rounded-xl border border-border bg-surface p-6">
                {TABS.map((t) => {
                  const TabContent = TAB_CONTENT[t.value];
                  return (
                    <TabsContent key={t.value} value={t.value}>
                      <TabContent />
                    </TabsContent>
                  );
                })}
              </div>
            </Tabs>

            <div className="flex flex-wrap items-center justify-end gap-2 border-t border-border pt-5">
              <Button type="button" variant="outline" onClick={cancelTo}>
                Cancel
              </Button>
              {mode === "edit" ? (
                <Button type="button" disabled={isPending} onClick={handleSaveChanges}>
                  Save changes
                </Button>
              ) : (
                <>
                  <Button
                    type="button"
                    variant="secondary"
                    disabled={isPending}
                    onClick={() => void handleSaveContinue()}
                  >
                    {isLastTab ? "Save & Continue" : "Continue"}
                  </Button>
                  <Button
                    type="button"
                    disabled={isPending || !isLastTab}
                    title={isLastTab ? undefined : "Finish the remaining tabs first"}
                    onClick={handleSavePublish}
                  >
                    Save & Publish
                  </Button>
                </>
              )}
            </div>
          </form>
        </Form>
      </Page>
      <UnsavedChangesGuard
        isDirty={form.formState.isDirty}
        onSave={mode === "edit" ? handleSaveChanges : undefined}
        isSaving={isPending}
      />
    </TourFormModeContext.Provider>
  );
}

function NewTourPage() {
  return <TourFormPage mode="create" />;
}
