"use client";

export const dynamic = "force-dynamic";

import { useRouter } from "next/navigation";
import { useBookingStore } from "@/lib/booking-store";
import { useQuery, useMutation } from "@tanstack/react-query";
import { apiClient } from "@/lib/api-client";
import { useState, useEffect, useCallback, useMemo, useRef, memo } from "react";
import { Armchair, Bed, ChevronLeft, ChevronDown, Train, TrainFront, X } from "lucide-react";
import Image from "next/image";

import CustomModal from "@/components/CustomModal";
import { Skeleton } from "@/components/Skeleton";
import { isChild } from "@/utils/fare-utils";

const BED_POSITION_SUFFIX: Record<string, string> = { lower: 'L', middle: 'M', upper: 'U' };

const buildSeatLabel = (seat: any): string => {
  const base: string = seat.number || seat.label || seat.seatNumber || '';
  if (!base) return '';
  const suffix = seat.bedPosition ? (BED_POSITION_SUFFIX[seat.bedPosition] ?? '') : '';
  return suffix ? `${base}${suffix}` : base;
};

const BedCard = memo(({ bed, isSelected, isAssignedToOther, onToggle }: any) => {
  const seatLabel = bed.label || bed.seatNumber || bed.number || "?";
  const bedPosition = bed.bedPosition || "";
  const bedType =
    bedPosition === "upper"
      ? "Upper"
      : bedPosition === "middle"
        ? "Middle"
        : "Lower";
  const isDisabled = bed.status !== "AVAILABLE" || isAssignedToOther;

  return (
    <button
      onClick={() => onToggle(bed.id)}
      disabled={isDisabled}
      title={
        isAssignedToOther
          ? `Bed ${seatLabel} - already assigned to another passenger`
          : `${bedType} Berth ${seatLabel} - ${bed.status}`
      }
      className={`relative flex flex-col items-center justify-center gap-0.5 w-16 sm:w-[4.5rem] py-2.5 rounded-xl border shadow-sm transition-all duration-150 ${
        isDisabled ? "" : "hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 active:scale-95"
      } ${
        isSelected
          ? "bg-blue-50 border-2 border-blue-500 shadow-blue-200/60 dark:bg-blue-900/30 dark:border-blue-400 dark:shadow-none scale-[1.03]"
          : isAssignedToOther
            ? "bg-purple-50 border-purple-300 cursor-not-allowed dark:bg-purple-900/20 dark:border-purple-700"
            : bed.status === "AVAILABLE"
              ? "bg-green-50 border-green-300 hover:bg-green-100 hover:border-green-400 dark:bg-green-900/20 dark:border-green-700"
              : bed.status === "BOOKED" || bed.status === "BLOCKED"
                ? "bg-red-50 border-red-300 cursor-not-allowed dark:bg-red-900/20 dark:border-red-700"
                : "bg-gray-100 border-gray-300 cursor-not-allowed dark:bg-gray-800 dark:border-gray-700"
      }`}
    >
      {/* bed.png is a portrait (headboard-to-footboard) silhouette; rotate it so the
          berth lies horizontally, matching the direction beds actually run in the coach. */}
      <div className="w-9 h-6 flex items-center justify-center overflow-visible">
        <Image src="/bed.png" alt="bed" width={22} height={36} className="object-contain rotate-90" />
      </div>
      <div className="text-xs font-bold text-gray-900 dark:text-white">
        {seatLabel}
      </div>
      <div className="text-[10px] font-medium text-gray-500 dark:text-gray-400">
        {bedType}
      </div>
    </button>
  );
});

BedCard.displayName = "BedCard";

// A real berth ladder is a single fixed rail mounted at the end of the bay that a
// passenger climbs to reach every level — not a separate rung floating between each
// pair of beds. So this renders once per bay, right after the last berth card, with
// solid rounded rails/rungs (like a real metal ladder) rather than thin decorative lines.
const LadderConnector = memo(() => (
  <div
    className="flex flex-col items-center justify-center flex-shrink-0 self-stretch w-6 sm:w-7 py-1.5"
    title="Ladder to the middle & upper berths"
    aria-hidden="true"
  >
    <svg width="100%" height="100%" viewBox="0 0 24 90" preserveAspectRatio="none" className="text-gray-400 dark:text-gray-500 drop-shadow-sm">
      {/* Side rails */}
      <rect x="2" y="0" width="3.5" height="90" rx="1.75" fill="currentColor" />
      <rect x="18.5" y="0" width="3.5" height="90" rx="1.75" fill="currentColor" />
      {/* Rungs, evenly spaced top (upper) to bottom (lower) */}
      <rect x="2" y="6" width="20" height="4" rx="2" fill="currentColor" />
      <rect x="2" y="30" width="20" height="4" rx="2" fill="currentColor" />
      <rect x="2" y="54" width="20" height="4" rx="2" fill="currentColor" />
      <rect x="2" y="78" width="20" height="4" rx="2" fill="currentColor" />
    </svg>
  </div>
));

LadderConnector.displayName = "LadderConnector";

const SeatButton = memo(
  ({
    seat,
    isSelected,
    isAssignedToOther,
    onToggle,
    isBedCoach,
    bedLabel,
    coachSeatClass,
  }: any) => {
    const seatLabel = seat.number || seat.label || seat.seatNumber || "?";
    const bedWidth = "w-24";
    const width = isBedCoach ? bedWidth : "w-10";
    const isDisabled = seat.status !== "AVAILABLE" || isAssignedToOther;

    return (
      <div className="flex flex-col items-center">
        <button
          onClick={() => onToggle(seat.id)}
          disabled={isDisabled}
          className={`${width} h-11 rounded flex items-center justify-center transition-all ${
            isSelected
              ? "bg-[rgb(20_113_76)] text-white shadow-md scale-105"
              : isAssignedToOther
                ? "bg-purple-400 text-white cursor-not-allowed opacity-75"
                : seat.status === "AVAILABLE"
                  ? "bg-green-500 hover:bg-green-600 text-white cursor-pointer hover:shadow-md"
                  : seat.status === "HELD"
                    ? "bg-yellow-500 text-white cursor-not-allowed opacity-75"
                    : "bg-gray-500 text-white cursor-not-allowed opacity-60"
          }`}
          title={
            isAssignedToOther
              ? `Seat ${seatLabel}${bedLabel} - already assigned to another passenger`
              : `Seat ${seatLabel}${bedLabel} - ${seat.status} - ${coachSeatClass}`
          }
          style={
            isBedCoach
              ? seat.row % 2 === 1
                ? { transform: "scaleY(-1)" }
                : undefined
              : seat.row % 2 === 0
                ? { transform: "scaleY(-1)" }
                : undefined
          }
        >
          {isBedCoach ? (
            <Bed className="w-7 h-7" />
          ) : (
            <Armchair className="w-7 h-7" />
          )}
        </button>
      </div>
    );
  },
);

SeatButton.displayName = "SeatButton";

export default function SeatsPage() {
  const router = useRouter();
  const {
    selectedSchedule,
    outboundSchedule,
    inboundSchedule,
    passengers,
    seatHold,
    setSeatHold,
    setPassengers,
    setSelectedSchedule,
    setOutboundSchedule,
    setInboundSchedule,
    searchCriteria,
    bookingId,
    packageName,
    packageId,
    priceTierId,
    packageTierPriceMinor,
    packageDepartureStationId,
    packageDepartureStationName,
    setPackageContext,
  } = useBookingStore();
  // Maps passenger index -> assigned seat id. A passenger can only get a seat while
  // they are the "active" passenger, which prevents bulk/batch selection across passengers.
  const [passengerSeatMap, setPassengerSeatMap] = useState<Record<number, string>>({});
  // Outbound seat IDs locked in after the outbound hold — used to prevent the
  // same physical seat being picked again on the inbound leg.

  const [activePassengerIndex, setActivePassengerIndex] = useState(0);
  const [selectedCoach, setSelectedCoach] = useState<string | null>(null);
  // Label of the coach the user picked from the Train Coach Preview, waiting to be
  // focused once the (possibly newly-fetched) seat map data for its coach type is ready.
  // Matched by label rather than id since the preview and seatmap endpoints are separate
  // API calls and may not share the same coach id scheme.
  const [pendingCoachLabel, setPendingCoachLabel] = useState<string | null>(null);
  const [showCoachPreview, setShowCoachPreview] = useState(false);
  const [currentJourneyType, setCurrentJourneyType] = useState<
    "outbound" | "inbound"
  >("outbound");
  const [modalState, setModalState] = useState({
    isOpen: false,
    title: "",
    message: "",
    type: "info" as "warning" | "error" | "success" | "info",
    onConfirm: undefined as (() => void) | undefined,
    showCancel: false,
    confirmText: "OK",
  });
  const [autoAssigningReturn, setAutoAssigningReturn] = useState(false);
  // Mobile summary bottom-sheet starts collapsed to a slim bar (badge + Continue button) so
  // it doesn't cover the seat map — the full passenger list/progress bar only shows once the
  // user explicitly expands it.
  const [mobileSummaryExpanded, setMobileSummaryExpanded] = useState(false);

  const isRoundTrip = searchCriteria?.tripType === "ROUND_TRIP";
  const isPackageBooking = !!packageName;
  const currentSchedule =
    isRoundTrip && currentJourneyType === "inbound"
      ? inboundSchedule
      : isRoundTrip
        ? outboundSchedule
        : selectedSchedule;
  const coachTypeId = (currentSchedule as any)?.selectedCoachTypeId;
  const journeyDirection = isRoundTrip
    ? currentJourneyType === "inbound" ? "RETURN" : "OUTBOUND"
    : "ONE_WAY";

  // The hold id/expiry that already covers the CURRENT leg, if any — used to avoid
  // creating a second, redundant hold when the user navigates back to this page (e.g.
  // browser back button, or bouncing between passengers/seats) without actually changing
  // their seat pick.
  const currentLegHoldId = isRoundTrip && currentJourneyType === "inbound" ? seatHold?.returnHoldId : seatHold?.holdId;
  const currentLegHoldExpiresAt = isRoundTrip && currentJourneyType === "inbound" ? seatHold?.returnExpiresAt : seatHold?.expiresAt;
  const isCurrentLegHoldValid =
    !!currentLegHoldId && (!currentLegHoldExpiresAt || new Date(currentLegHoldExpiresAt).getTime() > Date.now());

  // The seat id(s) already recorded against each passenger for the current leg from a
  // previous pass through this page (persisted in the store) — the counterpart of the
  // hold above, so we can tell whether the current on-screen selection actually differs
  // from what's already held.
  const previouslyHeldSeatIds = useMemo(
    () =>
      passengers.map((p) =>
        isRoundTrip
          ? (currentJourneyType === "inbound" ? (p as any).inboundSeatId : (p as any).outboundSeatId)
          : (p as any).seatId,
      ),
    [passengers, isRoundTrip, currentJourneyType],
  );

  // Baseline fare for each leg as it was when this page first loaded — i.e. whatever was
  // picked on the results page ("starting from" price). Captured once and never
  // overwritten, so a later coach-type switch (or just picking a pricier berth) can still
  // be compared against what the user originally saw/selected.
  const originalFaresRef = useRef<{ outbound: number | null; inbound: number | null; oneWay: number | null }>({
    outbound: null,
    inbound: null,
    oneWay: null,
  });
  if (originalFaresRef.current.outbound === null && outboundSchedule?.baseFareAdult != null) {
    originalFaresRef.current.outbound = outboundSchedule.baseFareAdult;
  }
  if (originalFaresRef.current.inbound === null && inboundSchedule?.baseFareAdult != null) {
    originalFaresRef.current.inbound = inboundSchedule.baseFareAdult;
  }
  if (originalFaresRef.current.oneWay === null && selectedSchedule?.baseFareAdult != null) {
    originalFaresRef.current.oneWay = selectedSchedule.baseFareAdult;
  }
  const originalFareForCurrentLeg = isRoundTrip
    ? (currentJourneyType === "inbound" ? originalFaresRef.current.inbound : originalFaresRef.current.outbound)
    : originalFaresRef.current.oneWay;

  // Seat eligibility:
  // - All adults always need their own seat.
  // - For package bookings: first child per adult travels free with no seat;
  //   additional children (beyond one per adult) pay full fare and need a seat.
  //   Free children are pre-marked with a child DOB on the package detail page.
  // - For regular bookings: same "first child per adult free" rule applies.
  // In both cases: children are identified by isChild() (DOB < 5 years).
  // Free children = first `adultCount` children (by position); paid children = the rest.
  const seatEligibility = useMemo(() => {
    const adultIndices = passengers.map((_, i) => i).filter((i) => !isChild(passengers[i]));
    const childIndices = passengers.map((_, i) => i).filter((i) => isChild(passengers[i]));
    const adultCount = adultIndices.length;
    // First `adultCount` children are free (no seat); the rest are paid (need a seat).
    const freeChildIndices = new Set(childIndices.slice(0, adultCount));
    const paidChildIndices = childIndices.slice(adultCount);
    const eligibleSet = new Set<number>([...adultIndices, ...paidChildIndices]);

    // Free children share with an adult (round-robin, for display).
    const sharingWithAdult = new Map<number, string>();
    Array.from(freeChildIndices).forEach((childIdx, offset) => {
      const adultIdx = adultIndices[offset % Math.max(adultIndices.length, 1)];
      if (adultIdx != null) {
        sharingWithAdult.set(childIdx, passengers[adultIdx]?.name || `Adult ${adultIdx + 1}`);
      }
    });

    return { eligibleSet, sharingWithAdult };
  }, [passengers]);

  const seatEligibleIndices = useMemo(
    () => passengers.map((_, i) => i).filter((i) => seatEligibility.eligibleSet.has(i)),
    [passengers, seatEligibility],
  );

  // If this leg already has a valid (unexpired) hold from a previous pass through this
  // page — e.g. the user hit "back" from a later step — restore the seat(s) that hold
  // actually covers instead of leaving the seat map blank and letting them pick (and
  // hold) another seat on top of it. Runs once per leg; the ref stops it from fighting a
  // deliberate deselect/re-pick afterwards.
  const restoredLegRef = useRef<string | null>(null);
  // Indices whose current passengerSeatMap entry came from the restoration above, not a
  // deliberate click this visit. A passenger's next click should be treated as their
  // first real pick (no fare-change modal) even though the map already has an entry for
  // them — only a click AFTER that (replacing their own real pick) is an actual change.
  const restoredIndicesRef = useRef<Set<number>>(new Set());
  // Set by handleAutoAssign right before it updates passengerSeatMap, so the effect below
  // can proceed straight to Continue once that state update actually lands — calling
  // handleContinue() synchronously in the same tick would still see the pre-update
  // passengerSeatMap/allSeatsAssigned from this render's closure.
  const autoContinueAfterAssignRef = useRef(false);
  useEffect(() => {
    const legKey = `${currentSchedule?.id || ''}-${currentJourneyType}`;
    if (restoredLegRef.current === legKey) return;
    restoredLegRef.current = legKey;

    if (!isCurrentLegHoldValid) return;

    const restored: Record<number, string> = {};
    seatEligibleIndices.forEach((i) => {
      const seatId = previouslyHeldSeatIds[i];
      if (seatId) restored[i] = seatId;
    });
    if (Object.keys(restored).length > 0) {
      setPassengerSeatMap(restored);
      restoredIndicesRef.current = new Set(Object.keys(restored).map(Number));
    }
  }, [currentSchedule?.id, currentJourneyType, isCurrentLegHoldValid, seatEligibleIndices, previouslyHeldSeatIds]);

  const {
    data: seatMapData,
    isLoading,
    error,
  } = useQuery({
    queryKey: ["seatmap", currentSchedule?.id, coachTypeId, currentJourneyType, (currentSchedule as any)?.originStationId, (currentSchedule as any)?.destinationStationId],
    queryFn: async () => {
      const scheduleForMap = isRoundTrip && currentJourneyType === "inbound" ? inboundSchedule : (isRoundTrip ? outboundSchedule : selectedSchedule);
      const originId = (scheduleForMap as any)?.originStationId || searchCriteria?.originStationId;
      const destinationId = (scheduleForMap as any)?.destinationStationId || searchCriteria?.destinationStationId;
      const endpoint = `/seats/seatmap/${currentSchedule?.id}?coachTypeId=${coachTypeId}&journeyDirection=${journeyDirection}${originId ? `&originStationId=${originId}` : ''}${destinationId ? `&destinationStationId=${destinationId}` : ''}`;

      const response = await apiClient.get(endpoint);


      const finalData = (response as any)?.data || response;
      return finalData;
    },
    enabled: !!currentSchedule?.id && !!coachTypeId,
  });

  // Whole-train coach layout for the "Preview Train Coach" panel — a separate, lazily
  // fetched list of every coach on this schedule (not just the currently selected coach
  // type), so users can see the full train arrangement and spot the dining coach.
  const { data: trainCoachesData, isLoading: loadingTrainCoaches } = useQuery({
    queryKey: ["trainCoaches", currentSchedule?.id],
    queryFn: async () => {
      const response = await apiClient.get(`/seats/coaches/${currentSchedule?.id}`);
      return (response as any)?.data || response;
    },
    enabled: showCoachPreview && !!currentSchedule?.id,
  });

  const trainCoachList = useMemo(() => {
    const raw = (trainCoachesData as any)?.coaches || trainCoachesData || [];
    if (!Array.isArray(raw)) return [];
    return raw
      .map((c: any, idx: number) => ({
        id: c.id || c.coachId || String(idx),
        coachId: c.coachId || c.id || null,
        label: c.label || c.coachNumber || c.name || c.coachTypeName || `Coach ${idx + 1}`,
        type: String(c.type || c.coachType || c.category || c.coachTypeCode || c.coachTypeName || ""),
        coachTypeName: String(c.coachTypeName || c.type || c.coachType || c.category || ""),
        typeName: c.coachTypeName || c.coachType || c.category || c.type || "",
        coachTypeId: c.coachTypeId ?? c.typeId ?? null,
        remainingSeats: c.remainingSeats ?? c.availableSeats ?? c.available ?? null,
        sequence: c.sequence ?? c.order ?? idx,
      }))
      .sort((a: any, b: any) => a.sequence - b.sequence);
  }, [trainCoachesData]);

  // Resolve the CoachType UUID for a preview-list coach. The preview API now returns
  // coachTypeId directly; fall back to cross-referencing the seatmap data for older
  // API versions that may not include it.
  const resolveCoachTypeIdFromSeatmap = useCallback((previewCoach: any): string | null => {
    if (previewCoach.coachTypeId) return previewCoach.coachTypeId;
    // Fallback: match by label against the current seatmap coaches
    const previewLabel = previewCoach.label || previewCoach.coachNumber || "";
    const allSeatmapCoaches: any[] = (seatMapData as any)?.coaches || (seatMapData as any)?.data?.coaches || [];
    const match = allSeatmapCoaches.find(
      (c: any) => (c.label || c.coachNumber || "") === previewLabel ||
                  c.id === previewCoach.coachId ||
                  c.id === previewCoach.id
    );
    return match?.coachTypeId ?? null;
  }, [seatMapData]);

  const isDiningCoachType = (type: string) => /dining|dpc/i.test(type);

  // Looks up a coach type's lowest per-adult fare from the coach-type/fare data captured
  // when this schedule was first selected on the results page (see booking-store.ts).
  // Returns null if the type can't be matched or has no priced classes.
  const getCoachTypeFare = (coachTypeId: string | null | undefined): number | null => {
    if (!coachTypeId) return null;
    const types = (currentSchedule as any)?.coachTypes || [];
    const match = types.find((ct: any) => ct.coachTypeId === coachTypeId || ct.coachId === coachTypeId);
    const fares = (match?.classes || []).map((c: any) => c.displayAmountMinor ?? c.baseFareMinor).filter((f: number) => f > 0);
    return fares.length ? Math.min(...fares) : null;
  };

  // The current coach type's priced classes (e.g. bed coaches price Upper/Middle/Lower
  // differently) — used to look up the real fare for a specific seat, not just the
  // coach type's cheapest class.
  const currentCoachTypeClasses = useMemo(() => {
    const types = (currentSchedule as any)?.coachTypes || [];
    const match = types.find((ct: any) => ct.coachTypeId === coachTypeId || ct.coachId === coachTypeId);
    return match?.classes || [];
  }, [currentSchedule, coachTypeId]);

  // A specific seat's actual fare: bed positions (Upper/Middle/Lower) are priced as
  // separate classes, so this can differ from the coach type's flat minimum fare.
  const getSeatFare = useCallback(
    (seat: any): number | null => {
      if (!currentCoachTypeClasses.length) return null;
      if (seat?.bedPosition) {        
        const match = currentCoachTypeClasses.find((c: any) =>
          c.name?.toLowerCase().includes(seat.bedPosition),
        );        
        if (match) return match.displayAmountMinor ?? match.baseFareMinor;
      }
      const regular = currentCoachTypeClasses.find((c: any) => /regular/i.test(c.name || ""));
      return (regular || currentCoachTypeClasses[0])?.displayAmountMinor ?? (regular || currentCoachTypeClasses[0])?.baseFareMinor ?? null;
    },
    [currentCoachTypeClasses],
  );

  // Switches the schedule to a different coach type (updates fare/class fields in the
  // booking store, which the seatmap query picks up automatically since it's keyed on
  // coachTypeId) and clears any seat picks made under the old coach type, since they no
  // longer correspond to real seats. Queues the clicked coach's label to be focused once
  // the new seat map data has finished loading.
  const applyCoachTypeSwitch = (coach: any, matchedType: any) => {
    const newFare = getCoachTypeFare(matchedType.coachTypeId || matchedType.coachId);
    const newCoachTypeId = matchedType.coachTypeId || matchedType.coachId;
    const updatedSchedule = {
      ...(currentSchedule as any),
      selectedCoachTypeId: newCoachTypeId,
      selectedCoachTypeCode: matchedType.coachTypeCode || coach.type || "",
      selectedCoachTypeName: matchedType.coachTypeName || coach.coachTypeName || coach.typeName || "",
      selectedSeatClass: matchedType.coachTypeName || coach.coachTypeName || "",
      selectedSeatClassName: matchedType.coachTypeName || coach.coachTypeName || "",
      seatClassName: matchedType.coachTypeName || coach.coachTypeName || "",
      baseFareAdult: newFare ?? (currentSchedule as any)?.baseFareAdult,
      baseFareChild: newFare ?? (currentSchedule as any)?.baseFareChild,
    };

    if (isRoundTrip && currentJourneyType === "inbound") {
      setInboundSchedule(updatedSchedule);
    } else if (isRoundTrip) {
      setOutboundSchedule(updatedSchedule);
      // For package bookings both legs always use the same coach type — mirror the
      // switch to the inbound schedule so the auto-assign fetches the right seatmap.
      if (isPackageBooking && inboundSchedule) {
        setInboundSchedule({
          ...(inboundSchedule as any),
          ...updatedSchedule,
          id: inboundSchedule.id,
          originStationId: (inboundSchedule as any).originStationId,
          destinationStationId: (inboundSchedule as any).destinationStationId,
        });
      }
    } else {
      setSelectedSchedule(updatedSchedule);
    }

    setPassengerSeatMap({});
    setActivePassengerIndex(seatEligibleIndices[0] ?? 0);
    setSelectedCoach(null);
    setPendingCoachLabel(coach.label);
    setShowCoachPreview(false);

    // The user already confirmed this fare change in the coach-switch modal above — reset
    // the per-leg baseline to the NEW coach type's fare so the very next seat pick is
    // compared against it, not the stale pre-switch fare. Without this, picking any seat
    // right after switching would immediately re-trigger the fare-change modal in
    // handleSeatClick for a change the user already agreed to.
    if (newFare != null) {
      if (isRoundTrip && currentJourneyType === "inbound") {
        originalFaresRef.current.inbound = newFare;
      } else if (isRoundTrip) {
        originalFaresRef.current.outbound = newFare;
      } else {
        originalFaresRef.current.oneWay = newFare;
      }
    }

    // For package bookings, sync the stored tier price with the new coach type's fare
    // so the review page totals reflect the switched coach type.
    if (isPackageBooking && packageId && newFare != null) {
      setPackageContext(
        packageId,
        priceTierId ?? '',
        newFare,
        packageName ?? undefined,
        packageDepartureStationId ?? undefined,
        packageDepartureStationName ?? undefined,
      );
    }
  };

  // Same coach type as the one already loaded — no refetch needed, just bring this
  // specific physical coach into view once we can match it against the (already
  // available) seat map data.
  const focusCoachInPlace = (coach: any) => {
    setPendingCoachLabel(coach.label);
    setShowCoachPreview(false);
  };

  const handlePreviewCoachSelect = (coach: any) => {
    if (coach.remainingSeats != null && coach.remainingSeats <= 0) {
      setModalState({
        isOpen: true,
        title: "Coach Full",
        message: `${coach.label} has no remaining seats. Please choose a different coach.`,
        type: "warning",
        onConfirm: undefined,
        showCancel: false,
        confirmText: "OK",
      });
      return;
    }

    // Resolve the real CoachType UUID by cross-referencing the seatmap data,
    // since the preview API (/seats/coaches) returns physical coach IDs, not CoachType UUIDs.
    const resolvedCoachTypeId = resolveCoachTypeIdFromSeatmap(coach);

    // Same coach type as currently loaded — just scroll to it.
    // Only trust the resolved UUID; name/code comparisons are unreliable across
    // different API responses and cause false positives for package bookings.
    const isSameType = !!resolvedCoachTypeId && resolvedCoachTypeId === coachTypeId;

    if (isSameType) {
      focusCoachInPlace(coach);
      return;
    }

    // Different coach type — build matchedType from coachTypes array or synthesise.
    const types = (currentSchedule as any)?.coachTypes || [];
    const matchedType = types.find(
      (ct: any) =>
        (resolvedCoachTypeId && (ct.coachTypeId === resolvedCoachTypeId || ct.coachId === resolvedCoachTypeId)) ||
        (coach.coachTypeName && ct.coachTypeName === coach.coachTypeName) ||
        (coach.type && (ct.coachTypeCode === coach.type || ct.coachTypeName === coach.type)),
    ) ?? {
      coachTypeId: resolvedCoachTypeId,
      coachId: resolvedCoachTypeId,
      coachTypeName: coach.coachTypeName || coach.typeName || coach.type || "",
      coachTypeCode: coach.type || "",
      classes: [],
    };

    const newFare = getCoachTypeFare(matchedType.coachTypeId || matchedType.coachId);
    const currentFare = originalFareForCurrentLeg ?? (currentSchedule as any)?.baseFareAdult ?? null;

    // Always confirm when switching to a different coach type — show fare difference
    // if known, or a generic confirmation if fares can't be resolved.
    if (newFare != null && currentFare != null && newFare !== currentFare) {
      setModalState({
        isOpen: true,
        title: "Fare Will Change",
        message: `Switching to ${coach.label} (${matchedType.coachTypeName || coach.typeName || ""}) changes the fare to ${currentSchedule?.displayCurrency || 'ETB'} ${(newFare / 100 * (isRoundTrip ? 2 : 1)).toFixed(2)} per adult (currently ${currentSchedule?.displayCurrency || 'ETB'} ${(currentFare / 100 * (isRoundTrip ? 2 : 1)).toFixed(2)}). Continue?`,
        type: "warning",
        showCancel: true,
        confirmText: "Switch Coach",
        onConfirm: () => applyCoachTypeSwitch(coach, matchedType),
      });
      return;
    }

    // Same fare or fare unknown — still confirm the coach type switch.
    const coachTypeName = matchedType.coachTypeName || coach.coachTypeName || coach.typeName || "";
    setModalState({
      isOpen: true,
      title: "Switch Coach Type",
      message: `Switch to ${coach.label}${coachTypeName ? ` (${coachTypeName})` : ""}?${
        newFare != null ? ` Fare: ${currentSchedule?.displayCurrency || 'ETB'} ${(newFare / 100 * (isRoundTrip ? 2 : 1)).toFixed(2)} per adult.` : " This will have a fare change."
      }`,
      type: "info",
      showCancel: true,
      confirmText: "Switch Coach",
      onConfirm: () => applyCoachTypeSwitch(coach, matchedType),
    });
  };

  const holdMutation = useMutation({
    mutationFn: async (seatIds: string[]) => {
      const passengersForHold = passengers
        .slice(0, seatIds.length)
        .map((_, i) => ({
          passengerId: `temp-${Date.now()}-${i}`,
          seatId: seatIds[i],
        }));

      const isInbound = currentJourneyType === "inbound";

      // Always use the schedule's own station IDs (set from schedule.origin.id / schedule.destination.id)
      // so they are guaranteed to exist in the trip's TripStopTime records.
      const scheduleForHold = isInbound && inboundSchedule ? inboundSchedule : (outboundSchedule || selectedSchedule);
      const originId = (scheduleForHold as any)?.originStationId || searchCriteria?.originStationId;
      const destinationId = (scheduleForHold as any)?.destinationStationId || searchCriteria?.destinationStationId;

      return apiClient.post(`/seats/hold`, {
        scheduleId: currentSchedule?.id,
        originStationId: originId,
        destinationStationId: destinationId,
        journeyDirection: isInbound ? "RETURN" : "OUTBOUND",
        passengers: passengersForHold,
      });
    },
    onSuccess: (data: any) => {
      const isInbound = currentJourneyType === "inbound";
      if (isInbound) {
        // Merge the return hold into the existing outbound hold
        const current = useBookingStore.getState().seatHold;
        setSeatHold({
          holdId: current?.holdId || "",
          expiresAt: current?.expiresAt || "",
          returnHoldId: data.holdId || data.id,
          returnExpiresAt: data.expiresAt,
        });
      } else {
        setSeatHold({
          holdId: data.holdId || data.id,
          expiresAt: data.expiresAt,
        });
      }
    },
  });

  const bookSeatsMutation = useMutation({
    mutationFn: async (seatIds: string[]) => {
      return Promise.all(
        seatIds.map((seatId) =>
          apiClient
            .patch(`/seats/${seatId}`, {
              status: "BOOKED",
            })
            .catch(() => null),
        ),
      );
    },
  });

  const coaches = useMemo(() => {
    const rawCoaches =
      (seatMapData as any)?.coaches ||
      (seatMapData as any)?.data?.coaches ||
      [];
    return rawCoaches;
  }, [seatMapData]);

  const filteredCoaches = useMemo(() => {

    const coachesWithSeats = coaches.filter((c: any) => {
      // Bed coaches store occupants in rooms.beds, not seats
      if (c.rooms?.length > 0) return c.rooms.some((r: any) => r.beds?.length > 0);
      return c.seats && c.seats.length > 0;
    });

    return coachesWithSeats;
  }, [coaches]);

  // Resolves a coach picked from the Train Coach Preview once its seat map data is
  // actually ready — matching by label (not id) since the preview list and this seat
  // map come from separate API calls. Waits out any in-flight refetch triggered by a
  // coach-type switch before trying to match, so it doesn't act on stale/old-type data.
  useEffect(() => {
    if (!pendingCoachLabel) return;
    if (isLoading) return;
    if (!filteredCoaches.length) return;
    const match = filteredCoaches.find(
      (c: any) => (c.label || c.name || c.coachNumber || "") === pendingCoachLabel,
    );
    setSelectedCoach((match || filteredCoaches[0])?.id || null);
    setPendingCoachLabel(null);
  }, [pendingCoachLabel, filteredCoaches, isLoading]);

  const selectedCoachData = useMemo(
    () => filteredCoaches.find((c: any) => c.id === selectedCoach),
    [filteredCoaches, selectedCoach],
  );

  const getBedPosition = (selectedClass: string): string | null => {
    const lowerClass = selectedClass.toLowerCase();
    if (lowerClass.includes("upper")) return "upper";
    if (lowerClass.includes("middle")) return "middle";
    if (lowerClass.includes("lower")) return "lower";
    return null;
  };

  // Extracted so it can be applied to ANY coach, not just the one currently expanded —
  // Auto Assign needs to look across every coach of this type, not just selectedCoachData.
  const getValidSeatsForCoach = useCallback(
    (coachData: any): any[] => {
      if (!coachData) return [];

      // If coach has rooms, extract all beds from rooms
      if (coachData.rooms?.length > 0) {
        const allBeds: any[] = [];
        coachData.rooms.forEach((room: any) => {
          if (room.beds) {
            allBeds.push(...room.beds);
          }
        });

        let beds = allBeds.filter((s: any) => {
          const seatLabel = s.label || s.number || s.seatNumber || "";
          return seatLabel && !seatLabel.startsWith("-");
        });

        const isBedCoach =
          coachData.seatClass?.toLowerCase().includes("bed") ||
          coachData.mode?.toLowerCase().includes("bed");

        if (isBedCoach && currentSchedule?.selectedSeatClass) {
          const selectedBedPosition = getBedPosition(
            currentSchedule.selectedSeatClass,
          );
          if (selectedBedPosition) {
            beds = beds.filter((s: any) => s.bedPosition === selectedBedPosition);
          }
        }

        return beds;
      }

      // Fallback to old seat structure
      let seats = (coachData.seats || []).filter((s: any) => {
        const seatLabel = s.label || s.number || s.seatNumber || "";
        return seatLabel && !seatLabel.startsWith("-");
      });
      const isBedCoach =
        coachData.isBedCoach === true ||
        seats.some((s: any) => s.bedPosition) ||
        coachData.seatClass?.toLowerCase().includes("bed") ||
        coachData.mode?.toLowerCase().includes("bed");

      if (isBedCoach && currentSchedule?.selectedSeatClass) {
        const selectedBedPosition = getBedPosition(
          currentSchedule.selectedSeatClass,
        );
        if (selectedBedPosition) {
          seats = seats.filter((s: any) => s.bedPosition === selectedBedPosition);
        }
      }

      return seats;
    },
    [currentSchedule?.selectedSeatClass],
  );

  const validSeats = useMemo(
    () => getValidSeatsForCoach(selectedCoachData),
    [getValidSeatsForCoach, selectedCoachData],
  );

  // Every valid seat across every coach of the current type, tagged with which coach it
  // belongs to — lets Auto Assign search the whole train (not just the one expanded coach)
  // and lets handleContinue resolve a passenger's coach label even when their seat came
  // from a coach they never manually expanded.
  const allCoachSeats = useMemo(
    () =>
      filteredCoaches.flatMap((c: any) =>
        getValidSeatsForCoach(c).map((s: any) => ({
          ...s,
          _coachId: c.id,
          _coachLabel: c.label || c.name || c.coachNumber || c.number || "",
        })),
      ),
    [filteredCoaches, getValidSeatsForCoach],
  );

  // Seats already claimed by any passenger in this journey leg, plus outbound
  const assignedSeatIds = useMemo(
    () => new Set(Object.values(passengerSeatMap)),
    [passengerSeatMap],
  );

  // The furthest passenger a user is allowed to jump to — cannot skip ahead of the
  // first seat-eligible passenger who still needs a seat. Passengers who share a seat
  // with an adult (see seatEligibility) never need their own pick.
  const firstUnassignedIndex = useMemo(
    () => seatEligibleIndices.find((i) => !passengerSeatMap[i]) ?? -1,
    [seatEligibleIndices, passengerSeatMap],
  );
  const maxSelectableIndex =
    firstUnassignedIndex === -1
      ? seatEligibleIndices[seatEligibleIndices.length - 1] ?? 0
      : firstUnassignedIndex;

  const isSeatSelected = useCallback(
    (seatId: string) => passengerSeatMap[activePassengerIndex] === seatId,
    [passengerSeatMap, activePassengerIndex],
  );

  const isSeatAssignedToOther = useCallback(
    (seatId: string) =>
      Object.entries(passengerSeatMap).some(
        ([idx, sid]) => Number(idx) !== activePassengerIndex && sid === seatId,
      ),
    [passengerSeatMap, activePassengerIndex],
  );

  const handleSelectPassenger = useCallback(
    (index: number) => {
      if (!seatEligibility.eligibleSet.has(index)) return; // shares a seat with an adult — no pick needed
      if (index > maxSelectableIndex) return; // no skipping ahead of unassigned passengers
      setActivePassengerIndex(index);
    },
    [maxSelectableIndex, seatEligibility],
  );

  // Actually applies a seat pick/deselect for the active passenger — split out from
  // handleSeatClick so a price-difference confirmation can defer this until the user
  // confirms, instead of assigning immediately.
  const commitSeatAssignment = useCallback(
    (seatId: string) => {
      const isDeselecting = passengerSeatMap[activePassengerIndex] === seatId;
      const next = { ...passengerSeatMap };
      if (isDeselecting) {
        delete next[activePassengerIndex];
      } else {
        next[activePassengerIndex] = seatId;
      }
      setPassengerSeatMap(next);
      // Whatever happens now is a deliberate pick — no longer just a restored hold.
      restoredIndicesRef.current.delete(activePassengerIndex);

      if (!isDeselecting) {
        // Move on to the next passenger who still needs a seat — one passenger at a time
        const nextUnassigned = seatEligibleIndices.find(
          (i) => i !== activePassengerIndex && !next[i],
        );
        if (nextUnassigned !== undefined) setActivePassengerIndex(nextUnassigned);
      }
    },
    [passengerSeatMap, activePassengerIndex, seatEligibleIndices],
  );

  const handleSeatClick = useCallback(
    (seatId: string) => {
      // Seat already claimed by a different passenger — never allow duplicate assignment
      const takenByOther = Object.entries(passengerSeatMap).some(
        ([idx, sid]) => Number(idx) !== activePassengerIndex && sid === seatId,
      );
      if (takenByOther) return;

      const currentSeatId = passengerSeatMap[activePassengerIndex];
      // A seat restored from a still-valid hold (see the restore effect above) isn't a
      // choice this passenger has made THIS visit — their next click is their first real
      // pick, not a "change", even though the map already has an entry for them.
      const isRestoredNotYetChosen = restoredIndicesRef.current.has(activePassengerIndex);
      const isDeselecting = currentSeatId === seatId;
      if (isDeselecting) {
        restoredIndicesRef.current.delete(activePassengerIndex);
        commitSeatAssignment(seatId);
        return;
      }

      // A fare-change warning only makes sense when there's an actual prior selection to
      // change FROM — either this same passenger swapping their own pick for a
      // differently-priced one, or picking a seat priced differently from another
      // passenger's already-selected seat this session. A passenger's very first pick has
      // neither, so it must never trigger this modal, regardless of the coach type's
      // "starting from" fare.
      const newSeat = validSeats?.find((s: any) => s.id === seatId);
      const newFare = newSeat ? getSeatFare(newSeat) : null;

      if (newFare != null) {
        let referenceFare: number | null = null;
        let referenceLabel = "your previously selected seat";

        if (isPackageBooking) {
          // For package bookings, compare against the stored tier price (per leg).
          const pkgLegFare = packageTierPriceMinor;
          if (pkgLegFare != null && newFare !== pkgLegFare) {
            referenceFare = pkgLegFare;
          }
        } else if (currentSeatId && !isRestoredNotYetChosen) {
          // This passenger already has a different seat picked — compare against that
          // actual, concrete selection.
          const currentSeat = validSeats?.find((s: any) => s.id === currentSeatId);
          const currentFare = currentSeat ? getSeatFare(currentSeat) : null;
          if (currentFare != null && currentFare !== newFare) {
            referenceFare = currentFare;
          }
        } else {
          // First pick for this passenger — only compare against another passenger's
          // already-selected seat in this session.
          const differingEntry = Object.entries(passengerSeatMap).find(([idx, sid]) => {
            if (Number(idx) === activePassengerIndex) return false;
            const otherSeat = validSeats?.find((s: any) => s.id === sid);
            const otherFare = otherSeat ? getSeatFare(otherSeat) : null;
            return otherFare != null && otherFare !== newFare;
          });

          if (differingEntry) {
            const otherSeat = validSeats?.find((s: any) => s.id === differingEntry[1]);
            referenceFare = otherSeat ? getSeatFare(otherSeat) : null;
            referenceLabel = "another already-selected seat";
          }
        }

        if (referenceFare != null) {
          const seatLabel = buildSeatLabel(newSeat);
          const positionLabel = newSeat?.bedPosition
            ? `${newSeat.bedPosition.charAt(0).toUpperCase()}${newSeat.bedPosition.slice(1)} berth`
            : "This seat";
          const legMultiplier = isPackageBooking && isRoundTrip ? 2 : 1;

          setModalState({
            isOpen: true,
            title: "Fare Will Change",
            message: `${positionLabel} ${seatLabel} costs ${currentSchedule?.displayCurrency || 'ETB'} ${(newFare / 100 * legMultiplier).toFixed(2)}, different from ${referenceLabel} (${currentSchedule?.displayCurrency || 'ETB'} ${(referenceFare / 100 * legMultiplier).toFixed(2)}). Continue with this selection?`,
            type: "warning",
            showCancel: true,
            confirmText: "Continue",
            onConfirm: () => {
              commitSeatAssignment(seatId);
              // For package bookings, sync the stored tier price to the selected berth fare
              // so review/payment/confirmation pages use the correct amount.
              if (isPackageBooking && packageId) {
                setPackageContext(
                  packageId,
                  priceTierId ?? '',
                  newFare,
                  packageName ?? undefined,
                  packageDepartureStationId ?? undefined,
                  packageDepartureStationName ?? undefined,
                );
              }
            },
          });
          return;
        }

        // No fare change — for package bookings still sync the tier price to the
        // actual berth fare so the correct amount is always stored.
        if (isPackageBooking && packageId) {
          setPackageContext(
            packageId,
            priceTierId ?? '',
            newFare,
            packageName ?? undefined,
            packageDepartureStationId ?? undefined,
            packageDepartureStationName ?? undefined,
          );
        }
      } else if (isPackageBooking && packageId) {
        // newFare is null (seat class data unavailable) — leave stored price as-is.
      }

      commitSeatAssignment(seatId);
    },
    [passengerSeatMap, activePassengerIndex, validSeats, getSeatFare, commitSeatAssignment, isPackageBooking, packageTierPriceMinor, packageId, priceTierId, packageName, packageDepartureStationId, packageDepartureStationName, setPackageContext, isRoundTrip, currentSchedule?.displayCurrency],
  );

  const allSeatsAssigned =
    seatEligibleIndices.length > 0 &&
    seatEligibleIndices.every((i) => !!passengerSeatMap[i]);

  // Only ever hold seats once per leg. If the current on-screen picks are exactly what's
  // already held (valid, unexpired), skip the API call entirely and reuse that hold — this
  // is what stops "back, then Continue again" from stacking up a second hold on the same
  // seats. If the user genuinely picked different seats than what was previously held,
  // release the stale hold first (best-effort — an already-expired/released hold shouldn't
  // block picking the new seat(s)) before holding the new selection, so at most one hold for
  // this leg is ever active at a time — for both guest and authenticated sessions.
  const ensureLegHold = async (seatIdsForHold: string[]) => {
    const selectionMatchesExistingHold =
      isCurrentLegHoldValid &&
      seatEligibleIndices.every((i) => passengerSeatMap[i] === previouslyHeldSeatIds[i]);

    if (selectionMatchesExistingHold) {
      return;
    }

    if (isCurrentLegHoldValid && currentLegHoldId) {
      try {
        // Routed through the portal's own server-side proxy (not called on the backend
        // directly) so the release capability never appears in client-side network calls.
        await fetch("/api/seats/release-hold", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ holdId: currentLegHoldId }),
        });
      } catch {
        // Best-effort — an expired/already-released hold shouldn't block picking the new seat(s).
      }
    }

    await holdMutation.mutateAsync(seatIdsForHold);
  };

  const handleContinue = async () => {
    if (!allSeatsAssigned) return;
    // Indexed by original passenger position — holes for passengers who share a seat
    // with an adult (no seat picked, and none required).
    const seatIds = passengers.map((_, i) => passengerSeatMap[i]);
    // Only real, distinct seat ids go to the hold API.
    const seatIdsForHold = seatEligibleIndices.map((i) => passengerSeatMap[i]);

    if (isRoundTrip && currentJourneyType === "outbound") {
      try {
        await ensureLegHold(seatIdsForHold);
        const updatedPassengers = passengers.map((p, i) => {
          // Look up from allCoachSeats (every coach of this type), not just the currently
          // expanded one — Auto Assign can place passengers in a coach that was never
          // manually expanded.
          const seatData = allCoachSeats?.find((s: any) => s.id === seatIds[i]);
          return {
            ...p,
            outboundSeatId: seatIds[i],
            outboundSeatNumber: seatData ? buildSeatLabel(seatData) : '',
            outboundCoachNumber: seatData?._coachLabel || selectedCoachData?.label || selectedCoachData?.name || selectedCoachData?.number || '',
            outboundSeatFareMinor: seatData ? (getSeatFare(seatData) ?? undefined) : undefined,
            outboundBedPosition: seatData?.bedPosition || undefined,
          };
        });
        setPassengers(updatedPassengers);

        if (isPackageBooking && inboundSchedule) {
          setAutoAssigningReturn(true);
          try {
            // Package bookings always use the same coach type for both legs — use the
            // outbound's (just-confirmed) coachTypeId so a prior coach-type switch is
            // reflected in the inbound seatmap fetch even if inboundSchedule wasn't updated.
            const inboundCoachTypeId = (currentSchedule as any)?.selectedCoachTypeId || (inboundSchedule as any).selectedCoachTypeId;
            const inboundOriginId = (inboundSchedule as any).originStationId || searchCriteria?.destinationStationId;
            const inboundDestId = (inboundSchedule as any).destinationStationId || searchCriteria?.originStationId;

            // Fetch the full outbound seatmap to resolve seat number/bedPosition by ID —
            // validSeats only holds the currently-expanded coach and may be empty.
            const outboundCoachTypeId = (currentSchedule as any)?.selectedCoachTypeId;
            const outboundOriginId = (currentSchedule as any)?.originStationId || searchCriteria?.originStationId;
            const outboundDestId = (currentSchedule as any)?.destinationStationId || searchCriteria?.destinationStationId;
            const outboundMapData: any = await apiClient.get(
              `/seats/seatmap/${currentSchedule?.id}?coachTypeId=${outboundCoachTypeId}&journeyDirection=OUTBOUND${outboundOriginId ? `&originStationId=${outboundOriginId}` : ''}${outboundDestId ? `&destinationStationId=${outboundDestId}` : ''}`
            );
            const outboundCoaches: any[] = (outboundMapData as any)?.coaches || (outboundMapData as any)?.data?.coaches || [];
            const allOutboundSeats: any[] = [];
            outboundCoaches.forEach((c: any) => {
              if (c.rooms?.length > 0) {
                c.rooms.forEach((r: any) => r.beds?.forEach((b: any) => allOutboundSeats.push(b)));
              } else {
                (c.seats || []).forEach((s: any) => allOutboundSeats.push(s));
              }
            });

            const inboundMapData: any = await apiClient.get(
              `/seats/seatmap/${inboundSchedule.id}?coachTypeId=${inboundCoachTypeId}&journeyDirection=RETURN${inboundOriginId ? `&originStationId=${inboundOriginId}` : ''}${inboundDestId ? `&destinationStationId=${inboundDestId}` : ''}`
            );
            const inboundCoaches: any[] = (inboundMapData as any)?.coaches || (inboundMapData as any)?.data?.coaches || [];

            const allInboundSeats: any[] = [];
            inboundCoaches.forEach((c: any) => {
              const coachLabel = c.label || c.name || c.coachNumber || '';
              if (c.rooms?.length > 0) {
                c.rooms.forEach((r: any) => r.beds?.forEach((b: any) => allInboundSeats.push({ ...b, _coachLabel: coachLabel })));
              } else {
                (c.seats || []).forEach((s: any) => allInboundSeats.push({ ...s, _coachLabel: coachLabel }));
              }
            });

            const claimedIds = new Set<string>();
            const inboundSeatMap: Record<number, string> = {};

            for (const i of seatEligibleIndices) {
              // Resolve outbound seat from the full seatmap fetch (not validSeats which
              // only holds the currently-expanded coach and is often empty).
              const outboundSeat = allOutboundSeats.find((s: any) => s.id === seatIds[i]);
              const outboundBase = outboundSeat ? (outboundSeat.number || outboundSeat.label || outboundSeat.seatNumber || '') : '';
              const outboundBedPos: string | null = outboundSeat?.bedPosition || null;

              // Priority 1: exact same seat number + same bed position
              // Priority 2: same bed position, any available seat
              // Priority 3: any available seat (fallback)
              const match =
                allInboundSeats.find((s: any) => s.status === 'AVAILABLE' && !claimedIds.has(s.id) && (s.number || s.label || s.seatNumber || '') === outboundBase && (!outboundBedPos || s.bedPosition === outboundBedPos)) ||
                allInboundSeats.find((s: any) => s.status === 'AVAILABLE' && !claimedIds.has(s.id) && outboundBedPos && s.bedPosition === outboundBedPos) ||
                allInboundSeats.find((s: any) => s.status === 'AVAILABLE' && !claimedIds.has(s.id));

              if (match) {
                inboundSeatMap[i] = match.id;
                claimedIds.add(match.id);
              }
            }

            if (claimedIds.size < seatEligibleIndices.length) {
              // Not enough inbound seats found — fall through to manual inbound selection
              setAutoAssigningReturn(false);
              setCurrentJourneyType("inbound");
              setPassengerSeatMap({});
              setActivePassengerIndex(seatEligibleIndices[0] ?? 0);
              setSelectedCoach(null);
              return;
            }

            // Hold inbound seats directly — holdMutation reads currentJourneyType
            // which is still "outbound" at this point, so we call the API directly.
            const inboundHoldData: any = await apiClient.post('/seats/hold', {
              scheduleId: inboundSchedule.id,
              originStationId: inboundOriginId,
              destinationStationId: inboundDestId,
              journeyDirection: 'RETURN',
              passengers: seatEligibleIndices.map((i, offset) => ({
                passengerId: `temp-${Date.now()}-${offset}`,
                seatId: inboundSeatMap[i],
              })),
            });

            const currentHold = useBookingStore.getState().seatHold;
            setSeatHold({
              holdId: currentHold?.holdId || '',
              expiresAt: currentHold?.expiresAt || '',
              returnHoldId: inboundHoldData.holdId || inboundHoldData.id,
              returnExpiresAt: inboundHoldData.expiresAt,
            });

            const withInbound = updatedPassengers.map((p, i) => {
              const inboundSeatId = inboundSeatMap[i];
              const inboundSeatData = inboundSeatId ? allInboundSeats.find((s: any) => s.id === inboundSeatId) : undefined;
              return {
                ...p,
                inboundSeatId,
                inboundSeatNumber: inboundSeatData ? buildSeatLabel(inboundSeatData) : '',
                inboundCoachNumber: inboundSeatData?._coachLabel || '',
                inboundSeatFareMinor: undefined,
                inboundBedPosition: inboundSeatData?.bedPosition || undefined,
              };
            });
            setPassengers(withInbound);

            // Update the stored tier price with the actual berth fare so the review page
            // reflects the correct price when the user picks Upper/Middle/Lower berths
            // (which are priced differently within the same coach type).
            if (packageId) {
              const firstEligibleIdx = seatEligibleIndices[0];
              const outboundSeat = firstEligibleIdx != null
                ? allOutboundSeats.find((s: any) => s.id === seatIds[firstEligibleIdx])
                : null;
              const berthFare = outboundSeat ? getSeatFare(outboundSeat) : null;
              if (berthFare != null) {
                setPackageContext(
                  packageId,
                  priceTierId ?? '',
                  berthFare,
                  packageName ?? undefined,
                  packageDepartureStationId ?? undefined,
                  packageDepartureStationName ?? undefined,
                );
              }
            }

            router.push('/booking/review');
            return;
          } catch {
            // Auto-assign failed — fall through to manual inbound selection
            setAutoAssigningReturn(false);
            setCurrentJourneyType("inbound");
            setPassengerSeatMap({});
            setActivePassengerIndex(seatEligibleIndices[0] ?? 0);
            setSelectedCoach(null);
            return;
          }
        }
      } catch (error: any) {
        setModalState({
          isOpen: true,
          title: "Seat Hold Failed",
          message:
            error?.response?.data?.message ||
            "Failed to hold seats. Please try again.",
          type: "error",
          onConfirm: undefined,
          showCancel: false,
          confirmText: "OK",
        });
        return;
      }
      setCurrentJourneyType("inbound");
      setPassengerSeatMap({});
      setActivePassengerIndex(seatEligibleIndices[0] ?? 0);
      setSelectedCoach(null);
      return;
    }

    try {
      await ensureLegHold(seatIdsForHold);
      const updatedPassengers = passengers.map((p, i) => {
        // See the outbound branch above — resolve from allCoachSeats so a passenger whose
        // seat came from a coach Auto Assign picked (but was never manually expanded)
        // still gets the right coach label.
        const seatData = allCoachSeats?.find((s: any) => s.id === seatIds[i]);
        if (isRoundTrip && currentJourneyType === "inbound") {
          return {
            ...p,
            inboundSeatId: seatIds[i],
            inboundSeatNumber: seatData ? buildSeatLabel(seatData) : '',
            inboundCoachNumber: seatData?._coachLabel || selectedCoachData?.label || selectedCoachData?.name || selectedCoachData?.number || '',
            inboundSeatFareMinor: seatData ? (getSeatFare(seatData) ?? undefined) : undefined,
            inboundBedPosition: seatData?.bedPosition || undefined,
          };
        }
        return {
          ...p,
          seatId: seatIds[i],
          seatNumber: seatData ? buildSeatLabel(seatData) : '',
          coachNumber: seatData?._coachLabel || selectedCoachData?.label || selectedCoachData?.name || selectedCoachData?.number || '',
          seatFareMinor: seatData ? (getSeatFare(seatData) ?? undefined) : undefined,
          bedPosition: seatData?.bedPosition || undefined,
        };
      });
      setPassengers(updatedPassengers);

      // For one-way package bookings, sync the stored tier price with the actual berth
      // fare so review/payment/confirmation pages reflect the correct amount.
      if (!isRoundTrip && isPackageBooking && packageId) {
        const firstEligibleIdx = seatEligibleIndices[0];
        const firstSeatData = firstEligibleIdx != null
          ? allCoachSeats?.find((s: any) => s.id === seatIds[firstEligibleIdx])
          : null;
        const berthFare = firstSeatData ? getSeatFare(firstSeatData) : null;
        if (berthFare != null) {
          setPackageContext(
            packageId,
            priceTierId ?? '',
            berthFare,
            packageName ?? undefined,
            packageDepartureStationId ?? undefined,
            packageDepartureStationName ?? undefined,
          );
        }
      }
    } catch (error: any) {
      setModalState({
        isOpen: true,
        title: "Seat Hold Failed",
        message:
          error?.response?.data?.message ||
          "Failed to hold seats. Please try again.",
        type: "error",
        onConfirm: undefined,
        showCancel: false,
        confirmText: "OK",
      });
      return;
    }
    router.push("/booking/review");
  };

  // Fires once the passengerSeatMap update from handleAutoAssign has actually landed (and
  // every eligible passenger now has a seat) — proceeds straight to Continue so the user
  // doesn't have to click it separately after auto-assigning.
  useEffect(() => {
    if (autoContinueAfterAssignRef.current && allSeatsAssigned) {
      autoContinueAfterAssignRef.current = false;
      handleContinue();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [passengerSeatMap, allSeatsAssigned]);

  const handleAutoAssign = () => {
    const unassignedIndices = seatEligibleIndices.filter((i) => !passengerSeatMap[i]);

    if (unassignedIndices.length === 0) return;

    // Search the whole train (every coach of this type), not just whichever one happens to
    // be expanded — fill the currently-expanded coach first so the result stays visible
    // without switching coaches, then spill over into other coaches only if needed.
    const availableAcrossTrain = allCoachSeats
      .filter((s: any) => s.status === "AVAILABLE" && !assignedSeatIds.has(s.id))
      .sort((a: any, b: any) => {
        const aCurrent = a._coachId === selectedCoach ? 0 : 1;
        const bCurrent = b._coachId === selectedCoach ? 0 : 1;
        return aCurrent - bCurrent;
      });

    if (availableAcrossTrain.length < unassignedIndices.length) {
      setModalState({
        isOpen: true,
        title: "Not Enough Seats",
        message: `Only ${availableAcrossTrain.length} seat(s) available across this coach type, but you need ${unassignedIndices.length} more seat(s). Please select another coach type.`,
        type: "warning",
        onConfirm: undefined,
        showCancel: false,
        confirmText: "OK",
      });
      return;
    }

    const next = { ...passengerSeatMap };
    let lastAssignedCoachId: string | null = null;
    unassignedIndices.forEach((passengerIndex, offset) => {
      const seat = availableAcrossTrain[offset];
      next[passengerIndex] = seat.id;
      lastAssignedCoachId = seat._coachId;
    });
    // Proceed straight to Continue once this update lands — no separate click needed.
    autoContinueAfterAssignRef.current = true;
    setPassengerSeatMap(next);
    setActivePassengerIndex(seatEligibleIndices[seatEligibleIndices.length - 1] ?? 0);
    // Bring whichever coach the last passenger landed in into view, since seats may now
    // span coaches beyond the one that was expanded when Auto Assign was clicked.
    if (lastAssignedCoachId && lastAssignedCoachId !== selectedCoach) {
      setSelectedCoach(lastAssignedCoachId);
    }
  };

  const handleBackToPassengers = () => {
    router.push("/booking/passengers");
  };

  useEffect(() => {
    if (isRoundTrip) {
      if (!outboundSchedule || !inboundSchedule || !passengers.length) {
        router.push(isPackageBooking ? "/booking/passengers" : "/booking/search");
      }
    } else {
      if (!selectedSchedule || !passengers.length) {
        router.push(isPackageBooking ? "/booking/passengers" : "/booking/search");
      }
    }
  }, [
    isRoundTrip,
    isPackageBooking,
    selectedSchedule,
    outboundSchedule,
    inboundSchedule,
    passengers.length,
    router,
  ]);

  useEffect(() => {
    const heldSeatIds = Object.values(passengerSeatMap);
    if (bookingId && heldSeatIds.length > 0) {
      bookSeatsMutation.mutate(heldSeatIds);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [bookingId]);

  // A passenger who shares a seat with an adult should never be "active" for seat
  // picking — snap back to the first seat-eligible passenger if that ever happens
  // (e.g. right after passengers/counts change).
  useEffect(() => {
    if (!seatEligibility.eligibleSet.has(activePassengerIndex) && seatEligibleIndices.length > 0) {
      setActivePassengerIndex(seatEligibleIndices[0]);
    }
  }, [seatEligibility, seatEligibleIndices, activePassengerIndex]);

  const parseSeatArrangement = (
    arrangement: string | null,
    seatClasses?: string[],
  ): number[] => {
    if (!arrangement) return [2, 2];

    // Check if this is a bed coach based on seat classes
    const isBedCoach = seatClasses?.some((sc) =>
      sc?.toLowerCase().includes("bed"),
    );

    if (isBedCoach) {
      // For bed coaches, arrangement like "3+0" means 3 beds stacked vertically
      // We want to render them as single column, so return [1]
      const parts = arrangement
        .split("+")
        .map((p) => parseInt(p.trim()))
        .filter((n) => !isNaN(n) && n > 0);
      return parts.length > 0 ? [Math.max(...parts)] : [3];
    }

    // For regular seats, parse normally (e.g., "3+2" -> [3, 2])
    const parts = arrangement
      .split("+")
      .map((p) => parseInt(p.trim()))
      .filter((n) => !isNaN(n) && n > 0);
    return parts.length >= 2 ? parts : parts.length === 1 ? [parts[0]] : [2, 2];
  };

  const renderCoachSeats = (coach: any, isBedCoach: boolean) => {
    const arrangement = parseSeatArrangement(
      coach.seatArrangement,
      coach.seatClasses || [coach.seatClass],
    );

    if (validSeats.length === 0) {
      return <div className="text-xs text-muted-foreground">No seats</div>;
    }

    const hasBedPositionData = validSeats.some((s: any) => s.bedPosition);
    const seatClassStr =
      typeof selectedCoachData?.seatClass === "string"
        ? selectedCoachData.seatClass
        : selectedCoachData?.seatClass?.name || "";

    // Indian-sleeper-style berth bay: Lower / Middle / Upper laid out horizontally, with
    // the single ladder that actually serves the whole bay shown once at the end.
    const renderBerthBay = (beds: any[], keyPrefix: string) => (
      <div className="flex items-stretch gap-2">
        {beds.map((bed: any) => (
          <BedCard
            key={bed.id}
            bed={bed}
            isSelected={isSeatSelected(bed.id)}
            isAssignedToOther={isSeatAssignedToOther(bed.id)}
            onToggle={handleSeatClick}
          />
        ))}
        {beds.length > 1 && <LadderConnector key={`${keyPrefix}-ladder`} />}
      </div>
    );

    // Two-side compartment: the left bay and right bay each get their own row (berths
    // still laid out horizontally within a row), stacked one above the other and split
    // by a dashed aisle divider — instead of squeezing both sides into a single row.
    const renderCompartment = (leftBay: any[], rightBay: any[], key: string) => (
      <div
        key={key}
        className="bg-gray-50 dark:bg-gray-800/40 rounded-2xl p-4 border border-gray-200 dark:border-gray-700 shadow-sm"
      >
        <div className="flex flex-col items-center gap-3">
          {leftBay.length > 0 && (
            <div className="flex justify-center">{renderBerthBay(leftBay, `${key}-left`)}</div>
          )}
          {leftBay.length > 0 && rightBay.length > 0 && (
            <div className="w-full border-t-2 border-dashed border-gray-300 dark:border-gray-600" />
          )}
          {rightBay.length > 0 && (
            <div className="flex justify-center">{renderBerthBay(rightBay, `${key}-right`)}</div>
          )}
        </div>
      </div>
    );

    // Bay position ordering + left/right side detection shared by both bed layouts below.
    const BERTH_ORDER = ["lower", "middle", "upper"];
    const bedSideIsLeft = (bed: any, leftColByPosition: Record<string, string>) => {
      if (bed.position === "LEFT") return true;
      if (bed.position === "RIGHT") return false;
      const leftCol = leftColByPosition[bed.bedPosition];
      return leftCol ? bed.col === leftCol : true;
    };

    // Bed coach with bed positions (Upper, Middle, Lower)
    if (isBedCoach && hasBedPositionData) {
      // Check if this is VIP_BED or ECONOMY_BED based on room data
      const rooms = (coach as any).rooms || [];
      const hasRooms = rooms.length > 0;

      if (hasRooms) {
        // Room-based layout (VIP_BED with 4 beds, ECONOMY_BED with 6 beds)
        return (
          <div className="space-y-6">
            {rooms.map((room: any) => {
              const isVipBed =
                room.category === "VIP_BED" || room.totalBeds === 4;
              const isEconomyBed =
                room.category === "ECONOMY_BED" || room.totalBeds === 6;

              // Sort beds by position and column
              const sortedBeds = [...(room.beds || [])].sort((a, b) => {
                const posOrder = { upper: 3, middle: 2, lower: 1 };
                const posA =
                  posOrder[a.bedPosition as keyof typeof posOrder] || 0;
                const posB =
                  posOrder[b.bedPosition as keyof typeof posOrder] || 0;
                if (posA !== posB) return posA - posB;
                return (a.col || "").localeCompare(b.col || "");
              });

              return (
                <div
                  key={room.room_id}
                  className="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 border-2 border-gray-200 dark:border-gray-700"
                >
                  {/* Room Header */}
                  <div className="flex items-center justify-between mb-4 pb-2 border-b border-gray-300 dark:border-gray-600">
                    <div>
                      <h4 className="text-sm font-bold text-gray-900 dark:text-white">
                        Room {room.roomNumber}
                      </h4>
                      <p className="text-xs text-gray-500 dark:text-gray-400">
                        {room.category === "VIP_BED"
                          ? "VIP BED"
                          : room.category === "ECONOMY_BED"
                            ? "ECONOMY BED"
                            : room.category}
                      </p>
                    </div>
                    <div className="text-xs text-gray-600 dark:text-gray-400">
                      {room.totalBeds} beds
                    </div>
                  </div>

                  {/* Legend */}
                  <div className="flex flex-wrap gap-2 mb-4 text-[10px]">
                    <div className="flex items-center gap-1">
                      <div className="w-3 h-3 bg-green-50 border border-green-300 rounded" />
                      <span className="text-gray-600 dark:text-gray-400">
                        Available
                      </span>
                    </div>
                    <div className="flex items-center gap-1">
                      <div className="w-3 h-3 bg-red-50 border border-red-300 rounded" />
                      <span className="text-gray-600 dark:text-gray-400">
                        Booked
                      </span>
                    </div>
                  </div>

                  {/* VIP BED Layout — 2-tier compartment (Lower/Upper), left + right of the aisle */}
                  {isVipBed && (() => {
                    const lowerBeds = sortedBeds.filter((b: any) => b.bedPosition === "lower");
                    const upperBeds = sortedBeds.filter((b: any) => b.bedPosition === "upper");
                    const isLeft = (bed: any, idx: number) =>
                      bed.position === "LEFT" ? true : bed.position === "RIGHT" ? false : idx % 2 === 0;

                    const leftBay = [lowerBeds, upperBeds]
                      .map((arr) => arr.find((b: any, i: number) => isLeft(b, i)))
                      .filter(Boolean);
                    const rightBay = [lowerBeds, upperBeds]
                      .map((arr) => arr.find((b: any, i: number) => !isLeft(b, i)))
                      .filter(Boolean);

                    return renderCompartment(leftBay, rightBay, `${room.room_id}-vip`);
                  })()}

                  {/* ECONOMY BED Layout — 3-tier compartment (Lower/Middle/Upper), left + right of the aisle */}
                  {isEconomyBed && (() => {
                    const leftColByPosition: Record<string, string> = { lower: "A", middle: "B", upper: "C" };
                    const leftBay = BERTH_ORDER
                      .map((pos) => sortedBeds.find((b: any) => b.bedPosition === pos && bedSideIsLeft(b, leftColByPosition)))
                      .filter(Boolean);
                    const rightBay = BERTH_ORDER
                      .map((pos) => sortedBeds.find((b: any) => b.bedPosition === pos && !bedSideIsLeft(b, leftColByPosition)))
                      .filter(Boolean);

                    return renderCompartment(leftBay, rightBay, `${room.room_id}-eco`);
                  })()}
                </div>
              );
            })}
          </div>
        );
      }

      // Fallback: beds without room data — group into numbered bays (Lower/Middle/Upper),
      // then pair adjacent bays into two-side compartments, same as the room-based layouts.
      const seatGroups = new Map<string, any[]>();

      for (const seat of validSeats) {
        const baseNumber = seat.seatNumber || seat.number || seat.label || "";
        if (!seatGroups.has(baseNumber)) {
          seatGroups.set(baseNumber, []);
        }
        seatGroups.get(baseNumber)!.push(seat);
      }

      const sortedGroups = Array.from(seatGroups.entries()).sort(([a], [b]) => {
        const numA = parseInt(a) || 0;
        const numB = parseInt(b) || 0;
        return numA - numB;
      });

      const bays = sortedGroups
        .map(([, beds]) =>
          BERTH_ORDER.map((pos) => beds.find((seat: any) => seat.bedPosition === pos)).filter(Boolean),
        )
        .filter((bay) => bay.length > 0);

      return (
        <div className="space-y-4">
          {Array.from({ length: Math.ceil(bays.length / 2) }, (_, i) => {
            const leftBay = bays[i * 2] || [];
            const rightBay = bays[i * 2 + 1] || [];
            return renderCompartment(leftBay, rightBay, `bay-compartment-${i}`);
          })}
        </div>
      );
    }

    // Regular seats with row/column arrangement
    const rowMap = new Map<number, any[]>();
    for (const seat of validSeats) {
      if (!rowMap.has(seat.row)) {
        rowMap.set(seat.row, []);
      }
      rowMap.get(seat.row)!.push(seat);
    }

    const rows = Array.from(rowMap.entries())
      .sort(([a], [b]) => a - b)
      .map(([_, seats]) => seats.sort((a, b) => a.col.localeCompare(b.col)));

    return (
      <div className="space-y-0">
        {rows.map((rowSeats: any[], rowIdx: number) => {
          const groups: any[][] = [];

          // Split seats into groups based on arrangement
          if (arrangement.length === 1) {
            // Single group (all seats together)
            groups.push(rowSeats);
          } else {
            // Multiple groups with aisle separation
            arrangement.forEach((_groupSize, groupIdx) => {
              const startIdx = arrangement
                .slice(0, groupIdx)
                .reduce((sum, size) => sum + size, 0);
              const endIdx = arrangement
                .slice(0, groupIdx + 1)
                .reduce((sum, size) => sum + size, 0);
              const currentGroup = rowSeats.slice(startIdx, endIdx);
              if (currentGroup.length > 0) groups.push(currentGroup);
            });
          }

          const rowNumber = rowSeats[0]?.row || 1;
          const shouldFlipArmchair = rowNumber % 2 === 0;
          const showSpacing = rowIdx % 2 === 1;

          return (
            <div key={`row-${rowNumber}-${rowSeats[0]?.id}`}>
              {shouldFlipArmchair && (
                <div className="flex gap-3 justify-start text-xs text-muted-foreground mb-1">
                  {groups.map((group, gIdx) => (
                    <div
                      key={`num-before-group-${gIdx}`}
                      className="flex gap-0.5"
                    >
                      {group.map((seat: any) => {
                        const seatLabel =
                          seat.label || seat.number || seat.seatNumber || "";
                        return (
                          <div
                            key={`num-${seat.id}`}
                            className="w-10 h-4 flex items-center justify-center text-xs font-bold leading-3 text-foreground"
                          >
                            {seatLabel}
                          </div>
                        );
                      })}
                    </div>
                  ))}
                </div>
              )}
              <div className="flex gap-3 justify-start">
                {groups.map((group, gIdx) => (
                  <div key={`group-${gIdx}`} className="flex gap-0.5">
                    {group.map((seat: any) => (
                      <SeatButton
                        key={seat.id}
                        seat={seat}
                        isSelected={isSeatSelected(seat.id)}
                        isAssignedToOther={isSeatAssignedToOther(seat.id)}
                        onToggle={handleSeatClick}
                        isBedCoach={false}
                        bedLabel=""
                        coachSeatClass={seatClassStr}
                      />
                    ))}
                  </div>
                ))}
              </div>

              {!shouldFlipArmchair && (
                <div className="flex gap-3 justify-start text-xs text-muted-foreground mb-1">
                  {groups.map((group, gIdx) => (
                    <div
                      key={`num-after-group-${gIdx}`}
                      className="flex gap-0.5"
                    >
                      {group.map((seat: any) => {
                        const seatLabel =
                          seat.label || seat.number || seat.seatNumber || "";
                        return (
                          <div
                            key={`num-${seat.id}`}
                            className="w-10 h-4 flex items-center justify-center text-xs font-bold leading-3 text-foreground"
                          >
                            {seatLabel}
                          </div>
                        );
                      })}
                    </div>
                  ))}
                </div>
              )}

              {showSpacing && (
                <div className="h-3 border-b border-gray-200 dark:border-gray-700" />
              )}
            </div>
          );
        })}
      </div>
    );
  };

  if (
    isRoundTrip
      ? !outboundSchedule || (!isPackageBooking && !inboundSchedule) || !passengers.length
      : !selectedSchedule || !passengers.length
  )
    return null;

  if (!coachTypeId) {
    return (
      <div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center p-4">
        <div className="bg-white dark:bg-gray-800 rounded-2xl p-8 text-center max-w-md">
          <p className="text-red-500 font-medium mb-2">
            No coach type selected
          </p>
          <p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
            Please go back and select a coach type
          </p>
          <button
            onClick={() => router.push("/booking/results")}
            className="btn-primary"
          >
            Back to Results
          </button>
        </div>
      </div>
    );
  }

  const assignedCount = seatEligibleIndices.filter((i) => !!passengerSeatMap[i]).length;
  const activePassenger = passengers[activePassengerIndex];
  const isBedCoach =
    selectedCoachData?.isBedCoach === true ||
    selectedCoachData?.rooms?.length > 0 ||
    selectedCoachData?.seatClass?.toLowerCase().includes("bed") ||
    selectedCoachData?.mode?.toLowerCase().includes("bed");

  // Train coach preview content — shared between the desktop side panel and the
  // mobile full-screen modal. Shows every coach on this schedule in order, so users
  // can see where their selected coach type sits relative to the rest of the train
  // and spot the dining coach at a glance.
  const TrainCoachPreviewContent = () => (
    <>
      <p className="text-xs text-gray-500 dark:text-gray-400 mb-3">
        Coach order along the train, front to back.
      </p>

      {/* Legend */}
      <div className="flex flex-wrap gap-3 mb-4 pb-4 border-b border-gray-100 dark:border-gray-800 text-xs text-gray-600 dark:text-gray-400">
        <div className="flex items-center gap-1.5">
          <div className="w-3 h-3 rounded bg-amber-100 border-2 border-amber-400 dark:bg-amber-900/30 dark:border-amber-500" />
          <span>🍽 Dining coach</span>
        </div>
        <div className="flex items-center gap-1.5">
          <div className="w-3 h-3 rounded bg-primary/10 border-2 border-primary" />
          <span>Your selected coach type</span>
        </div>
      </div>

      {loadingTrainCoaches ? (
        <div className="flex items-center justify-center py-10 gap-2">
          <div className="w-6 h-6 border-2 border-primary border-t-transparent rounded-full animate-spin" />
          <span className="text-sm text-gray-500 dark:text-gray-400">Loading coach layout...</span>
        </div>
      ) : trainCoachList.length === 0 ? (
        <p className="text-sm text-gray-500 dark:text-gray-400 py-6 text-center">
          Coach layout is not available for this train.
        </p>
      ) : (
        <div className="flex flex-col items-stretch">
          {/* Locomotive */}
          <div className="flex items-center gap-3 rounded-lg bg-[rgb(20,113,76)] text-white py-2.5 px-3">
            <TrainFront className="w-5 h-5 flex-shrink-0" />
            <span className="text-sm font-bold">Locomotive</span>
          </div>

          {trainCoachList.map((coach: any, coachIndex: number) => {
            const dining = isDiningCoachType(coach.type);
            const isFull = !dining && coach.remainingSeats != null && coach.remainingSeats <= 0;
            const isCurrentType =
              !!coachTypeId &&
              (coach.coachTypeId === coachTypeId ||
                coach.coachId === coachTypeId ||
                coach.type === (currentSchedule as any)?.selectedCoachTypeCode ||
                coach.coachTypeName === (currentSchedule as any)?.selectedCoachTypeName ||
                coach.type === (currentSchedule as any)?.selectedCoachTypeName);
            return (
              <div key={coach.id}>
                {/* Coach connector — same coupling-joint visual as the seat map's coach list */}
                <div className="flex justify-center py-0.5">
                  <div className="flex flex-col items-center gap-px">
                    <div className="w-7 h-1.5 bg-gray-300 dark:bg-gray-600 rounded-sm" />
                    <div className="w-2.5 h-2.5 bg-gray-400 dark:bg-gray-500 rounded-sm" />
                    <div className="w-7 h-1.5 bg-gray-300 dark:bg-gray-600 rounded-sm" />
                  </div>
                </div>

                <button
                  type="button"
                  disabled={dining || isFull}
                  onClick={() => handlePreviewCoachSelect(coach)}
                  title={dining ? "Dining coach — not bookable" : isFull ? "This coach is full" : `Select ${coach.label}`}
                  className={`w-full flex items-center gap-3 rounded-lg border-2 py-2.5 px-3 text-left transition-colors ${
                    dining
                      ? "bg-amber-100 border-amber-400 dark:bg-amber-900/30 dark:border-amber-500 cursor-not-allowed"
                      : isFull
                        ? "bg-gray-100 border-gray-200 dark:bg-gray-800/60 dark:border-gray-700 opacity-60 cursor-not-allowed"
                        : isCurrentType
                          ? "bg-primary/10 border-primary hover:bg-primary/15"
                          : "bg-gray-50 border-gray-200 dark:bg-gray-800 dark:border-gray-700 hover:border-primary/50"
                  }`}
                >
                  <div className="w-8 h-8 flex items-center justify-center flex-shrink-0 rounded-md bg-white/70 dark:bg-black/20">
                    {dining ? (
                      <span className="text-base leading-none">🍽</span>
                    ) : (
                      <span className="text-sm font-bold text-gray-600 dark:text-gray-300">{coachIndex + 1}</span>
                    )}
                  </div>
                  <div className="flex-1 min-w-0">
                    <p className="text-sm font-bold text-gray-900 dark:text-white truncate">
                      {coach.label}
                    </p>
                    {dining ? (
                      <p className="text-[11px] text-amber-700 dark:text-amber-400 font-medium">Dining Coach</p>
                    ) : coach.typeName ? (
                      <p className="text-[11px] text-gray-500 dark:text-gray-400 truncate">{coach.typeName}</p>
                    ) : null}
                  </div>
                  {!dining && (
                    <span
                      className={`text-xs font-semibold flex-shrink-0 ${
                        isFull ? "text-red-500 dark:text-red-400" : "text-gray-600 dark:text-gray-300"
                      }`}
                    >
                      {isFull ? "Full" : coach.remainingSeats != null ? `${coach.remainingSeats} left` : "—"}
                    </span>
                  )}
                </button>
              </div>
            );
          })}
        </div>
      )}
    </>
  );

  // Summary content, split into pieces so the mobile bottom-sheet can show a compact
  // header + always-reachable Continue button by default, and only reveal the full
  // passenger list (SummaryDetails) when the user explicitly expands it — otherwise it
  // permanently covers most of the seat map on small screens.
  const SummaryHeader = () => (
    <div className="flex items-center justify-between mb-1">
      <h3 className="font-bold text-gray-900 dark:text-white">
        Selection Summary
      </h3>
      <span
        className={`text-xs font-semibold px-2 py-0.5 rounded-full ${
          allSeatsAssigned
            ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
            : "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"
        }`}
      >
        {assignedCount}/{seatEligibleIndices.length} selected
      </span>
    </div>
  );

  const SummaryDetails = () => (
    <>
      <p className="text-xs text-gray-500 dark:text-gray-400 mb-4">
        {allSeatsAssigned
          ? "All seats selected — ready to continue"
          : `Selecting seat for ${activePassenger?.name || `Passenger ${activePassengerIndex + 1}`} (${seatEligibleIndices.indexOf(activePassengerIndex) + 1} of ${seatEligibleIndices.length})`}
      </p>

      {/* Progress bar */}
      <div className="h-1.5 bg-gray-100 dark:bg-gray-700 rounded-full mb-4 overflow-hidden">
        <div
          className="h-full bg-[rgb(20,113,76)] rounded-full transition-all duration-300"
          style={{
            width: `${(assignedCount / Math.max(seatEligibleIndices.length, 1)) * 100}%`,
          }}
        />
      </div>

      <div className="space-y-2 mb-5">
        {passengers.map((p, i) => {
          const sharesWithAdult = seatEligibility.sharingWithAdult.get(i);
          if (sharesWithAdult) {
            return (
              <div
                key={i}
                className="w-full flex items-center justify-between py-2 px-2 rounded-lg border border-transparent opacity-70"
              >
                <div className="flex items-center gap-2 min-w-0">
                  <div className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0 bg-gray-200 dark:bg-gray-700 text-gray-500">
                    {i + 1}
                  </div>
                  <span className="text-sm text-gray-700 dark:text-gray-300 truncate block max-w-[120px]">
                    {p.name}
                  </span>
                </div>
                <span className="text-xs font-medium text-gray-400 flex-shrink-0 text-right">
                  Shares seat with {sharesWithAdult}
                </span>
              </div>
            );
          }

          const assignedSeatId = passengerSeatMap[i];
          // allCoachSeats (not validSeats) so a seat Auto Assign placed in a coach the user
          // never manually expanded still shows correctly here.
          const assignedSeat = assignedSeatId
            ? allCoachSeats?.find((s: any) => s.id === assignedSeatId)
            : null;
          const seatLabel = assignedSeat ? buildSeatLabel(assignedSeat) : "";
          const isActive = i === activePassengerIndex;
          const isClickable = i <= maxSelectableIndex;
          return (
            <button
              key={i}
              type="button"
              onClick={() => handleSelectPassenger(i)}
              disabled={!isClickable}
              className={`w-full flex items-center justify-between py-2 px-2 rounded-lg border text-left transition-all ${
                isActive
                  ? "border-[rgb(20,113,76)] bg-[rgb(20,113,76)]/5 dark:bg-[rgb(20,113,76)]/10"
                  : "border-transparent"
              } ${
                isClickable
                  ? "cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/60"
                  : "cursor-not-allowed opacity-50"
              }`}
            >
              <div className="flex items-center gap-2 min-w-0">
                <div
                  className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0 ${
                    assignedSeat
                      ? "bg-[rgb(20,113,76)] text-white"
                      : isActive
                        ? "bg-[rgb(20,113,76)]/20 text-[rgb(20,113,76)] ring-2 ring-[rgb(20,113,76)]"
                        : "bg-gray-200 dark:bg-gray-700 text-gray-500"
                  }`}
                >
                  {i + 1}
                </div>
                <div className="min-w-0">
                  <span className="text-sm text-gray-700 dark:text-gray-300 truncate block max-w-[120px]">
                    {p.name}
                  </span>
                  {isActive && !assignedSeat && (
                    <span className="text-[10px] font-semibold text-[rgb(20,113,76)] uppercase tracking-wide">
                      Now selecting
                    </span>
                  )}
                </div>
              </div>
              <div className="flex flex-col items-end flex-shrink-0">
                <span
                  className={`text-sm font-semibold ${
                    assignedSeat ? "text-[rgb(20,113,76)]" : "text-gray-400"
                  }`}
                >
                  {assignedSeat ? `Seat ${seatLabel}` : "Not Assigned"}
                </span>
              </div>
            </button>
          );
        })}
      </div>
    </>
  );

  const SummaryContinueButton = () => (
    <button
      onClick={handleContinue}
      disabled={!allSeatsAssigned || holdMutation.isPending || autoAssigningReturn}
      className="w-full py-2.5 bg-[rgb(20,113,76)] hover:bg-[rgb(16,89,60)] disabled:opacity-40 disabled:cursor-not-allowed text-white font-bold text-sm rounded-xl transition-all shadow-lg"
    >
      {autoAssigningReturn
        ? "Assigning return seats..."
        : holdMutation.isPending
          ? "Holding seats..."
          : isRoundTrip && currentJourneyType === "outbound" && !isPackageBooking
            ? "Continue to Return Seats"
            : "Continue"}
    </button>
  );

  const SummaryAutoAssignButton = () => (
    <button
      onClick={handleAutoAssign}
      disabled={holdMutation.isPending || allSeatsAssigned}
      className="w-full mt-2 py-2.5 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-40 text-gray-700 dark:text-gray-300 font-semibold text-sm rounded-xl transition-all"
    >
      Auto Assign Seats
    </button>
  );

  const SummaryActions = () => (
    <>
      <SummaryContinueButton />
      <SummaryAutoAssignButton />
    </>
  );

  // Full summary card — used as-is in the desktop sidebar, which has room to show
  // everything at once.
  const SummaryContent = () => (
    <>
      <SummaryHeader />
      <SummaryDetails />
      <SummaryActions />
    </>
  );

  return (
    <>
      <CustomModal
        isOpen={modalState.isOpen}
        onClose={() => setModalState((prev) => ({ ...prev, isOpen: false }))}
        title={modalState.title}
        message={modalState.message}
        type={modalState.type}
        onConfirm={modalState.onConfirm}
        showCancel={modalState.showCancel}
        confirmText={modalState.confirmText}
      />

      {/* Train Coach Preview */}
      {showCoachPreview && (
        <>
          {/* Mobile: full-screen modal */}
          <div className="fixed inset-0 z-[120] bg-white dark:bg-gray-900 flex flex-col lg:hidden">
            <div className="flex items-center justify-between px-4 py-4 border-b border-gray-100 dark:border-gray-800 flex-shrink-0">
              <h2 className="text-base font-bold text-gray-900 dark:text-white flex items-center gap-2">
                <Train className="w-5 h-5 text-primary" />
                Train Coach Preview
              </h2>
              <button
                type="button"
                onClick={() => setShowCoachPreview(false)}
                className="w-9 h-9 flex items-center justify-center rounded-full hover:bg-gray-100 dark:hover:bg-gray-800"
                aria-label="Close"
              >
                <X className="w-5 h-5 text-gray-500" />
              </button>
            </div>
            <div className="flex-1 overflow-y-auto scrollbar-hide p-4">
              <TrainCoachPreviewContent />
            </div>
          </div>

          {/* Desktop: right-side panel, no backdrop — seat selection stays fully usable */}
          <div
            className="hidden lg:flex fixed inset-y-0 right-0 z-[90] w-[380px] bg-white dark:bg-gray-900 shadow-2xl border-l border-gray-200 dark:border-gray-700 flex-col"
            style={{ animation: "drawer-slide-in 0.25s cubic-bezier(0.32,0.72,0,1)" }}
          >
            <div className="flex items-center justify-between px-5 py-4 border-b border-gray-100 dark:border-gray-800 flex-shrink-0">
              <h2 className="text-base font-bold text-gray-900 dark:text-white flex items-center gap-2">
                <Train className="w-5 h-5 text-primary" />
                Train Coach Preview
              </h2>
              <button
                type="button"
                onClick={() => setShowCoachPreview(false)}
                className="w-9 h-9 flex items-center justify-center rounded-full hover:bg-gray-100 dark:hover:bg-gray-800"
                aria-label="Close"
              >
                <X className="w-5 h-5 text-gray-500" />
              </button>
            </div>
            <div className="flex-1 overflow-y-auto scrollbar-hide p-5">
              <TrainCoachPreviewContent />
            </div>
          </div>
          <style>{`@keyframes drawer-slide-in{from{transform:translateX(100%)}to{transform:translateX(0)}}`}</style>
        </>
      )}

      {/* Mobile summary bottom-sheet — collapsed to a single slim row by default so it
          barely dents the seat map; tap it to expand the full passenger list + auto-assign.
          The Continue button always stays visible either way. */}
      <div
        className={`fixed inset-x-0 bottom-0 z-50 lg:hidden bg-white dark:bg-gray-900 rounded-t-2xl shadow-2xl overflow-hidden flex flex-col ${
          mobileSummaryExpanded ? "max-h-[70vh]" : ""
        }`}
        style={{
          animation: "seats-slide-up 0.25s cubic-bezier(0.32,0.72,0,1)",
        }}
      >
        <button
          type="button"
          onClick={() => setMobileSummaryExpanded((v) => !v)}
          className="w-full flex items-center justify-between px-5 py-2.5 flex-shrink-0"
          aria-label={mobileSummaryExpanded ? "Collapse seat selection summary" : "Expand seat selection summary"}
        >
          <span className="text-sm font-semibold text-gray-900 dark:text-white">
            {assignedCount}/{seatEligibleIndices.length} seats selected
          </span>
          <ChevronDown
            className={`w-4 h-4 text-gray-400 transition-transform ${mobileSummaryExpanded ? "rotate-180" : ""}`}
          />
        </button>

        {mobileSummaryExpanded && (
          <div className="px-5 overflow-y-auto flex-1 min-h-0">
            <SummaryDetails />
            <SummaryAutoAssignButton />
          </div>
        )}

        <div className="px-5 pb-4 pt-2 flex-shrink-0 border-t border-gray-100 dark:border-gray-800">
          <SummaryContinueButton />
        </div>
      </div>
      <style>{`@keyframes seats-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}`}</style>

      <div className="min-h-screen bg-gray-50 dark:bg-gray-900">
        {/* Header */}
        <div className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 sticky top-0 z-30">
          <div className="container mx-auto px-4">
            <div className="max-w-6xl mx-auto flex items-center justify-between h-14">
              <button
                onClick={handleBackToPassengers}
                className="flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-400 hover:text-primary transition-colors font-medium"
              >
                <ChevronLeft className="w-4 h-4" />
                Back
              </button>
              <div className="text-center">
                <h1 className="text-base font-bold text-gray-900 dark:text-white">
                  {isPackageBooking
                    ? `Select Seats for ${packageName}`
                    : isRoundTrip
                      ? currentJourneyType === "outbound"
                        ? "Select Outbound Seats"
                        : "Select Return Seats"
                      : "Select Seats"}
                </h1>
                {!allSeatsAssigned && (
                  <p className="text-xs text-gray-500 dark:text-gray-400">
                    Now selecting: {activePassenger?.name || `Passenger ${activePassengerIndex + 1}`}
                  </p>
                )}
              </div>
              <div className="text-sm font-semibold text-[rgb(20,113,76)]">
                {assignedCount}/{passengers.length}
              </div>
            </div>
          </div>
        </div>

        <div className="container mx-auto px-4 py-5">
          <div className="max-w-6xl mx-auto">
            <div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
              {/* ── Seat map panel ── */}
              <div className="lg:col-span-2 space-y-4">
                <div className="flex justify-end">
                  <button
                    type="button"
                    onClick={() => setShowCoachPreview(true)}
                    className="flex items-center gap-2 px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm font-semibold text-gray-700 dark:text-gray-300 hover:border-primary hover:text-primary transition-colors shadow-sm"
                  >
                    <Train className="w-4 h-4" />
                    Preview Train Coach
                  </button>
                </div>
                {isLoading ? (
                  <div className="bg-white dark:bg-gray-800 rounded-2xl p-6 space-y-4">
                    <div className="flex items-center justify-between">
                      <Skeleton className="h-5 w-32" />
                      <Skeleton className="h-8 w-24 rounded-lg" />
                    </div>
                    <div className="grid grid-cols-4 gap-3">
                      {Array.from({ length: 24 }).map((_, i) => (
                        <Skeleton key={i} className="aspect-square rounded-xl" />
                      ))}
                    </div>
                  </div>
                ) : error ? (
                  <div className="bg-white dark:bg-gray-800 rounded-2xl p-8 text-center">
                    <p className="text-red-500 font-medium">
                      Error loading seats
                    </p>
                    <p className="text-sm text-gray-400 mt-1">
                      {(error as any)?.message || "Please try again"}
                    </p>
                  </div>
                ) : filteredCoaches.length === 0 ? (
                  <div className="bg-white dark:bg-gray-800 rounded-2xl p-8 text-center">
                    <p className="text-gray-500 dark:text-gray-400">
                      No coaches available for{" "}
                      {selectedSchedule?.selectedSeatClass}
                    </p>
                  </div>
                ) : (
                  <div className="flex flex-col items-stretch">

                    {/* ── Locomotive head ── */}
                    <div className="px-4">
                      <div className="relative bg-[rgb(20,113,76)] rounded-t-2xl px-5 pt-4 pb-3 text-white overflow-hidden">
                        {/* Decorative top stripe */}
                        <div className="absolute top-0 left-0 right-0 h-1 bg-white/20" />
                        <div className="flex items-center justify-between">
                          <div>
                            <p className="text-[10px] font-bold uppercase tracking-widest text-white/60">
                              EDR Express
                            </p>
                            <p className="text-sm font-bold mt-0.5">
                              {filteredCoaches.length} Coach{filteredCoaches.length !== 1 ? "es" : ""}
                            </p>
                          </div>
                          {/* Cabin windows */}
                          <div className="flex gap-2">
                            {[0, 1, 2].map((i) => (
                              <div
                                key={i}
                                className="w-8 h-5 bg-white/15 border border-white/30 rounded-sm"
                              />
                            ))}
                          </div>
                        </div>
                        {/* Headlight bar */}
                        <div className="mt-3 flex items-center gap-2">
                          <div className="w-5 h-5 bg-yellow-300/80 rounded-full border-2 border-yellow-200/60 shadow-lg shadow-yellow-300/40" />
                          <div className="flex-1 h-1 bg-white/20 rounded-full" />
                          <div className="w-5 h-5 bg-yellow-300/80 rounded-full border-2 border-yellow-200/60 shadow-lg shadow-yellow-300/40" />
                        </div>
                      </div>
                      {/* Nose / front bumper */}
                      <div className="h-3 bg-[rgb(15,85,57)] mx-3 rounded-b-lg" />
                    </div>

                    {/* ── Coach list ── */}
                    {filteredCoaches.map((coach: any, index: number) => {
                      let fSeats: any[] = [];
                      if (coach.rooms?.length > 0) {
                        coach.rooms.forEach((room: any) => {
                          if (room.beds) {
                            fSeats.push(
                              ...room.beds.filter(
                                (b: any) => b.label && !b.label.startsWith("-"),
                              ),
                            );
                          }
                        });
                      } else {
                        fSeats =
                          coach.seats?.filter(
                            (s: any) => s.seatNumber && !s.seatNumber.startsWith("-"),
                          ) || [];
                      }

                      const isBed =
                        coach.isBedCoach === true ||
                        coach.rooms?.length > 0 ||
                        coach.seatClass?.toLowerCase().includes("bed") ||
                        coach.mode?.toLowerCase().includes("bed");

                      if (isBed && currentSchedule?.selectedSeatClass) {
                        const bedPos = getBedPosition(currentSchedule.selectedSeatClass);
                        if (bedPos)
                          fSeats = fSeats.filter((s: any) => s.bedPosition === bedPos);
                      }

                      const available = fSeats.filter((s: any) => s.status === "AVAILABLE").length;
                      const total = fSeats.length;
                      const isExpanded = selectedCoach === coach.id;
                      const coachLabel =
                        coach.label || coach.name || coach.coachNumber || `Coach ${index + 1}`;

                      return (
                        <div key={coach.id}>
                          {/* Coupling joint */}
                          <div className="flex justify-center py-0.5">
                            <div className="flex flex-col items-center gap-px">
                              <div className="w-7 h-2 bg-gray-300 dark:bg-gray-600 rounded-sm" />
                              <div className="w-3 h-3 bg-gray-400 dark:bg-gray-500 rounded-sm" />
                              <div className="w-7 h-2 bg-gray-300 dark:bg-gray-600 rounded-sm" />
                            </div>
                          </div>

                          {/* Coach car */}
                          <div
                            className={`mx-4 border-2 overflow-hidden transition-all duration-200 ${
                              isExpanded
                                ? "border-[rgb(20,113,76)] shadow-lg shadow-[rgb(20,113,76)]/10"
                                : "border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600"
                            }`}
                          >
                            {/* Top colour stripe — brand rail */}
                            <div
                              className={`h-1.5 transition-colors duration-200 ${
                                isExpanded
                                  ? "bg-[rgb(20,113,76)]"
                                  : "bg-gray-200 dark:bg-gray-700"
                              }`}
                            />

                            {/* Clickable header */}
                            <button
                              onClick={() => setSelectedCoach(isExpanded ? null : coach.id)}
                              className={`w-full flex items-center justify-between px-4 py-3 transition-colors ${
                                isExpanded
                                  ? "bg-[rgb(20,113,76)]/5 dark:bg-[rgb(20,113,76)]/10"
                                  : "bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-900/30"
                              }`}
                            >
                              <div className="flex items-center gap-3">
                                <div
                                  className={`w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 font-bold text-sm transition-colors ${
                                    isExpanded
                                      ? "bg-[rgb(20,113,76)] text-white"
                                      : "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300"
                                  }`}
                                >
                                  {index + 1}
                                </div>
                                <div className="text-left">
                                  <div
                                    className={`font-semibold text-sm ${
                                      isExpanded
                                        ? "text-[rgb(20,113,76)]"
                                        : "text-gray-900 dark:text-white"
                                    }`}
                                  >
                                    {coachLabel}
                                  </div>
                                  <div className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
                                    {available} of {total} seats available
                                  </div>
                                </div>
                              </div>
                              <div className="flex items-center gap-3">
                                {/* Mini availability bars */}
                                <div className="hidden sm:flex items-end gap-0.5 h-5">
                                  {Array.from({ length: Math.min(total, 12) }).map((_, i) => (
                                    <div
                                      key={i}
                                      className={`w-1 rounded-sm transition-colors ${
                                        i <
                                        Math.round(
                                          (available / Math.max(total, 1)) *
                                            Math.min(total, 12),
                                        )
                                          ? "h-full bg-green-400"
                                          : "h-3 bg-gray-200 dark:bg-gray-600"
                                      }`}
                                    />
                                  ))}
                                </div>
                                <ChevronDown
                                  className={`w-4 h-4 transition-transform duration-200 flex-shrink-0 ${
                                    isExpanded
                                      ? "rotate-180 text-[rgb(20,113,76)]"
                                      : "text-gray-400"
                                  }`}
                                />
                              </div>
                            </button>

                            {/* Expanded seat map */}
                            {isExpanded && (
                              <div className="border-t border-gray-100 dark:border-gray-800 bg-white dark:bg-gray-800 p-4">
                                <div className="flex flex-wrap gap-3 mb-4">
                                  {[
                                    {
                                      color: "bg-green-50 border border-green-300",
                                      label: "Available",
                                    },
                                    {
                                      color: "bg-blue-50 border-2 border-blue-500",
                                      label: "Selected",
                                    },
                                    {
                                      color: "bg-red-50 border border-red-300",
                                      label: "Booked",
                                    },
                                  ].map(({ color, label }) => (
                                    <div key={label} className="flex items-center gap-1.5">
                                      <div className={`w-4 h-4 ${color} rounded`} />
                                      <span className="text-xs text-gray-600 dark:text-gray-400">
                                        {label}
                                      </span>
                                    </div>
                                  ))}
                                </div>

                                <div className="overflow-x-auto">
                                  <div className="inline-block bg-gray-50 dark:bg-gray-700/30 rounded-xl p-4 border border-gray-200 dark:border-gray-700">
                                    {validSeats.length === 0 ? (
                                      <p className="text-sm text-gray-400 py-4">
                                        No seats in this coach
                                      </p>
                                    ) : (
                                      renderCoachSeats(selectedCoachData, isBedCoach)
                                    )}
                                  </div>
                                </div>
                              </div>
                            )}

                            {/* Bottom colour stripe */}
                            <div
                              className={`h-1.5 transition-colors duration-200 ${
                                isExpanded
                                  ? "bg-[rgb(20,113,76)]"
                                  : "bg-gray-200 dark:bg-gray-700"
                              }`}
                            />
                          </div>
                        </div>
                      );
                    })}

                    {/* ── Rear end cap ── */}
                    <div className="px-4">
                      <div className="flex justify-center py-0.5">
                        <div className="flex flex-col items-center gap-px">
                          <div className="w-7 h-2 bg-gray-300 dark:bg-gray-600 rounded-sm" />
                          <div className="w-3 h-3 bg-gray-400 dark:bg-gray-500 rounded-sm" />
                        </div>
                      </div>
                      <div className="h-4 bg-gray-200 dark:bg-gray-700 rounded-b-2xl" />
                    </div>

                  </div>
                )}
              </div>

              {/* ── Sidebar summary (desktop only) ── */}
              <div className="hidden lg:block lg:col-span-1">
                <div className="bg-white dark:bg-gray-800 rounded-2xl p-5 border border-gray-100 dark:border-gray-700 sticky top-20">
                  <SummaryContent />
                </div>
              </div>
            </div>

            {/* Mobile spacer so the bottom-sheet doesn't cover the last seat — sized to
                match the sheet's current (collapsed/expanded) height. */}
            <div className={`lg:hidden ${mobileSummaryExpanded ? "h-[70vh]" : "h-24"}`} />
          </div>
        </div>
      </div>
    </>
  );
}
